project2要求解析一个elf文件并执行。
解析elf很简单,只要读取出elf文件中的程序头即Program Headers。
需要知道:
1. 总共有几个Program Headers。
2. 每个Program Headers在文件中和在内存中的起始地址和界限。
3. 程序入口地址。
project1的代码中已经给我们定义好了elf head 和program head 的数据结构了。
/* * ELF header at the beginning of the executable. */ typedef struct { unsigned char ident[16]; unsigned short type; unsigned short machine; unsigned int version; unsigned int entry; unsigned int phoff; unsigned int sphoff; unsigned int flags; unsigned short ehsize; unsigned short phentsize; unsigned short phnum; unsigned short shentsize; unsigned short shnum; unsigned short shstrndx; } elfHeader; /* * An entry in the ELF program header table. * This describes a single segment of the executable. */ typedef struct { unsigned int type; unsigned int offset; unsigned int vaddr; unsigned int paddr; unsigned int fileSize; unsigned int memSize; unsigned int flags; unsigned int alignment; } programHeader;
int Parse_ELF_Executable(char *exeFileData, ulong_t exeFileLength, struct Exe_Format *exeFormat) { int i = 0; Print("elf file length: %d \n", (int)exeFileLength); elfHeader *elf_head = (elfHeader *)exeFileData; exeFormat->numSegments = elf_head->phnum; exeFormat->entryAddr = elf_head->entry; programHeader *ph = (programHeader *)(exeFileData + elf_head->phoff); for(i=0;i<elf_head->phnum;i++) { exeFormat->segmentList[i].offsetInFile = ph[i].offset; exeFormat->segmentList[i].lengthInFile = ph[i].fileSize; exeFormat->segmentList[i].startAddress = ph[i].paddr; exeFormat->segmentList[i].sizeInMemory = ph[i].memSize; exeFormat->segmentList[i].protFlags = ph[i].flags; } return 0; }
使用readelf -a user/a.exe
来观察调试。
完成project2并不难,大家会发现project2运行的时候会出现一些问题。
比如并没有打印出第二个字符串"Hi,This is the second string"
又或者系统会死机,等等各种各样的问题。
在我的机器上,运行之后出现了这个问题。
看libc/entry.c
void _Entry(void) { /* Call main(); arguments won't be needed */ main(0, 0); /* make the inter-selector jump back */ __asm__ __volatile__ ("leave"); __asm__ __volatile__ ("lret"); //pop eip, pop cs. }
问题就出在下面的这两句嵌入汇编上。
察看a.exe反汇编代码。
objdump -d user/a.exe
00001000 <_Entry>: 1000: 83 ec 1c sub $0x1c,%esp 1003: c7 44 24 04 00 00 00 movl $0x0,0x4(%esp) 100a: 00 100b: c7 04 24 00 00 00 00 movl $0x0,(%esp) 1012: e8 09 00 00 00 call 1020 <main> 1017: c9 leave 1018: cb lret 1019: 83 c4 1c add $0x1c,%esp 101c: c3 ret 101d: 90 nop 101e: 90 nop 101f: 90 nop 00001020 <main>: 1020: 55 push %ebp 1021: 89 e5 mov %esp,%ebp 1023: 83 e4 f0 and $0xfffffff0,%esp 1026: 83 ec 10 sub $0x10,%esp 1029: c7 04 24 c0 20 00 00 movl $0x20c0,(%esp) 1030: e8 13 00 00 00 call 1048 <ELF_Print> 1035: c7 04 24 00 21 00 00 movl $0x2100,(%esp) 103c: e8 07 00 00 00 call 1048 <ELF_Print> 1041: b8 00 00 00 00 mov $0x0,%eax 1046: c9 leave 1047: c3 ret 00001048 <ELF_Print>: 1048: 8b 44 24 04 mov 0x4(%esp),%eax 104c: cd 90 int $0x90 104e: c3 ret
可以看到在_Entry入口处的汇编,首先
sub $0x1c,%esp
然而程序却抢先在
add $0x1c,%esp
之前使用leave和lret长跳转返回了,这样程序当然出错了。
就能正常工作了。
呃,插入
__asm__ __volatile__ ("add $0x1c, $esp");
这句确实让人感觉挺不舒服的,暂时也没有更好的解决办法。