GeekOS 中的project1

来源:互联网 发布:js this.classname 编辑:程序博客网 时间:2024/05/18 12:42

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                   nop00001020 <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__ ("add $0x1c, $esp");   
  __asm__ __volatile__ ("lret");

就能正常工作了。

呃,插入

__asm__ __volatile__ ("add $0x1c, $esp");   

这句确实让人感觉挺不舒服的,暂时也没有更好的解决办法。


原创粉丝点击