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.
}
可以看到是这里调用了用户程序user/a.exe
问题就出在下面的这两句嵌入汇编上。
察看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__ ("leave");
__asm__ __volatile__ ("lret");
__asm__ __volatile__ ("lret");
改成了
__asm__ __volatile__ ("add $0x1c, $esp");
__asm__ __volatile__ ("lret");
就能正常工作了。
呃,插入
__asm__ __volatile__ ("add $0x1c, $esp");
这句确实让人感觉挺不舒服的,暂时也没有更好的解决办法。