GeekOS 中的project1

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

就能正常工作了。

呃,插入

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

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


没有更多推荐了,返回首页