OpenRISC—OR1200的编译及ELF文件介绍
上一篇《OpenRISC—OR1200编译链环境》介绍了OR1200编译链的安装,本篇接着介绍编译链的使用以及ELF文件格式。
1. 新建源码
在ubuntu下新建一个简单程序,名称为 example.s,输入以下内容:
.section .text,"ax" .org 0x100 .global _start _start: l.andi r0,r0,0 l.extwz r1,r0 l.extwz r2,r0 l.addi r1,r1,0x0A l.add r2,r2,r1 l.nop 0x0001
2. 进行编译
输入指令:
or1k-elf-as example.s -o example.o
目录下生成example.o文件,使用16进制方式查看文件内容,输入指令:
hexdump example.o -C
输出如下内容:
00000000 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 |.ELF............| 00000010 00 01 00 5c 00 00 00 01 00 00 00 00 00 00 00 00 |...\............| 00000020 00 00 01 d0 00 00 00 00 00 34 00 00 00 00 00 28 |.........4.....(| 00000030 00 07 00 04 00 00 00 00 00 00 00 00 00 00 00 00 |................| 00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * ......
3. 内容介绍
发现前4字节是:0x7F, 0x45, 0x4c, 0x46 ,说明这是一个ELF文件。
ELF格式说明:
ELF(Executable and linkable Format),可执行链接格式,是UNIX系统实验室作为应用程序二进制接口而开发和发布的。
ELF目标文件又三种类型:
(1)可重定向(Relocatable)文件:保存着代码和适当的数据,用来和其他Object文件一起创建一个可执行文件或共享文件。
(2)可执行(Executable)文件:保存着一个用来执行的程序,该文件指出了如何来创建程序进程映像。
(3)共享目标文件:包含两种使用环境中链接的代码和数据。首先,链接器(ld)可以将它和其余可重定向文件和共享目标文件一起处理,生成另外一个目标文件(比如,编译器和链接器把*.o和*.so一起装成一个*.exe文件)。其次,动态链接器(Dynamic Linker)可将它与某个可执行文件及其他共享目标文件组合在一起创建进程映像(比如,动态加载器把*.exe程序和*.so加载进内存执行)。
4. 可重定位ELF文件格式
ELF文件由4部分组成:ELF header, Program header table, Sections 和 Section header table。
(1)ELF header
内容定义如下:
/* * Elf32_Half表示2字节 * Elf32_Word表示4字节 */ #define EI_NIDENT 16 typedef struct { unsigned char e_ident[EI_NIDENT]; Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version,Elf32_Word表示4字节 */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr;
使用指令:
or1k-elf-readelf -h example.o
输出ELF头的信息内容:
ELF Header: Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, big endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: REL (Relocatable file) Machine: OpenRISC 1000 Version: 0x1 Entry point address: 0x0 Start of program headers: 0 (bytes into file) Start of section headers: 464 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 0 (bytes) Number of program headers: 0 Size of section headers: 40 (bytes) Number of section headers: 7 Section header string table index: 4
也可以自己写一个小程序实现读取bin文件以及相应字段的解析,详细见 get_elf_header.c。
(2)查看section内容
借助GNU工具链中的or1k-elf-readelf,可以直接得到section的内容。输入指令:
or1k-elf-readelf -S example.o
输出以下内容:
There are 7 section headers, starting at offset 0x1d0: Section Headers: [Nr] Name Type Addr Off Size ES Flg Lk Inf Al [ 0] NULL 00000000 000000 000000 00 0 0 0 [ 1] .text PROGBITS 00000000 000034 000118 00 AX 0 0 1 [ 2] .data PROGBITS 00000000 00014c 000000 00 WA 0 0 1 [ 3] .bss NOBITS 00000000 00014c 000000 00 WA 0 0 1 [ 4] .shstrtab STRTAB 00000000 0001a4 00002c 00 0 0 1 [ 5] .symtab SYMTAB 00000000 00014c 000050 10 6 4 4 [ 6] .strtab STRTAB 00000000 00019c 000008 00 0 0 1 Key to Flags: W (write), A (alloc), X (execute), M (merge), S (strings) I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown) O (extra OS processing required) o (OS specific), p (processor specific)
总共7个section,其中“.text”这个Section,它的起始地址是0x34,长度为0x118。
(3)查看反汇编结果
使用objdump查看反汇编结果,输入指令:
or1k-elf-objdump -d example.o
输出:
example.o: file format elf32-or1k Disassembly of section .text: 00000000 <_start-0x100>: ... 00000100 <_start>: 100: a4 00 00 00 l.andi r0,r0,0x0 104: e0 20 00 4d l.extwz r1,r0 108: e0 40 00 4d l.extwz r2,r0 10c: 9c 21 00 0a l.addi r1,r1,10 110: e0 42 08 00 l.add r2,r2,r1 114: 15 00 00 01 l.nop 0x1
显示输出分为三栏,左边是指令执行的地址,在程序中我们的第一条指令是从0x100开始的;中间一栏是对应的二进制代码,右边一栏是对应的汇编指令。
在这0x118字节中,前0x100字节都是0x00;接下来的24字节,对比内容可以发现section .text最后24字节正好是6条汇编指令。
5. 可执行ELF文件
通过以上操作,得到一个可重定位的ELF文件,但这个文件还不能执行,需要链接转换为可执行文件。
(1)新建一个链接描述脚本
MEMORY { ram : ORIGIN = 0x00000000, LENGTH = 0x00005000 } SECTIONS { .text : { *(.text) } > ram .data : { *(.data) } > ram .bss : { *(.bss) } > ram } ENTRY (_start)
(2)使用链接器
or1k-elf-ld -T ram.ld example.o -o example.or
(3)可执行ELF的header内容
使用ELF工具分析可执行ELF程序的 header 内容。输入指令:
or1k-elf-readelf -h example.or
输出内容:
ELF Header: Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, big endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: OpenRISC 1000 Version: 0x1 Entry point address: 0x100 Start of program headers: 52 (bytes into file) Start of section headers: 8564 (bytes into file) Flags: 0x0 Size of this header: 52 (bytes) Size of program headers: 32 (bytes) Number of program headers: 1 Size of section headers: 40 (bytes) Number of section headers: 5 Section header string table index: 2
(4)对比两个ELF文件
对比 example.o 和 example.or,发现example.or中多了一个 program headers。
Program header也可以通过结构体来描述:
typedef struct { Elf32_Word p_type; /* Segment type */ Elf32_Off p_offset; /* Segment file offset */ Elf32_Addr p_vaddr; /* Segment virtual address */ Elf32_Addr p_paddr; /* Segment physical address */ Elf32_Word p_filesz; /* Segment size in file */ Elf32_Word p_memsz; /* Segment size in memory */ Elf32_Word p_flags; /* Segment flags */ Elf32_Word p_align; /* Segment alignment */ } Elf32_Phdr;
(5)分析program headers信息
使用ELF工具得到 program headers 的信息,指令如下:
or1k-elf-readelf -l example.or
输出:
Elf file type is EXEC (Executable file) Entry point 0x100 There are 1 program headers, starting at offset 52 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align LOAD 0x002000 0x00000000 0x00000000 0x00118 0x00118 R E 0x2000 Section to Segment mapping: Segment Sections... 00 .text
说明:
该Program header 表示将 example.or 的 0x2000 开始的 0x118 字节放置在内存的 0x00 处。
打开 example.or 可以发现 0x2000 开始的总共 0x118 字节的内容与 example.o 中的 Section .text 的内容一样。
example.or从0x2000开始的总共0x118字节的内容如下:
example.o的Section .text内容:
所以,当这个Program Section加载入内存后,会使得内存的0x100处存放的就是第一条指令,而example.or的入口地址正是0x100。
6. 模拟运行程序
使用or1ksim模拟器运行程序,输入指令:
or1k-sim -t example.or -m1M > example.trace
在目录下生成 example.trace 文件,内容如下:
Seeding random generator with value 0x1f5aaf19 Or1ksim 2012-04-27 Building automata... done, num uncovered: 0/215. Parsing operands data... done. Resetting PIC. loadcode: filename example.or startaddr=00000000 virtphy_transl=00000000 Not COFF file format ELF type: 0x0002 ELF machine: 0x005c ELF version: 0x00000001 ELF sec = 5 Section: .text, vaddr: 0x00000000, paddr: 0x0 offset: 0x00002000, size: 0x00000118 S 00000100: a4000000 l.andi r0,r0,0 r0 = 00000000 flag: 0 S 00000104: e020004d l.extwz r1,r0 r1 = 00000000 flag: 0 S 00000108: e040004d l.extwz r2,r0 r2 = 00000000 flag: 0 S 0000010c: 9c21000a l.addi r1,r1,0xa r1 = 0000000a flag: 0 S 00000110: e0420800 l.add r2,r2,r1 r2 = 0000000a flag: 0 exit(0) @reset : cycles 0, insn #0 @exit : cycles 5, insn #6 diff : cycles 5, insn #6
其中包含5条指令,以第一条为例:
S 00000100: a4000000 l.andi r0,r0,0 r0 = 00000000 flag: 0
S: 表示处于特权模式 PC值: 0x00000100 指令的二进制数编码: 0xa4000000 指令的汇编代码: l.andi r0,r0,0 目的地址: r0 目的地址的新值: 0x00000000 flag的值: 0x00
每一条指令PC加4,表示取下一条指令,程序执行最后r1为0xa,r2位0xa,满足预期。
附:相关示例源码src.zip
参考: