Chinaunix首页 | 论坛 | 博客
  • 博客访问: 372358
  • 博文数量: 64
  • 博客积分: 2975
  • 博客等级: 少校
  • 技术积分: 831
  • 用 户 组: 普通用户
  • 注册时间: 2007-01-14 10:59
文章存档

2014年(2)

2012年(7)

2010年(40)

2009年(5)

2008年(8)

2007年(2)

分类: LINUX

2010-05-01 15:38:36

ld.so分析3

内核中load_elf_binary如何执行

1.load_elf_binary
fs/binfmt_elf.c

/*
 * These are the functions used to load ELF style executables and shared
 * libraries.  There is no binary dependent code anywhere else.
 */

#define INTERPRETER_NONE 0
#define INTERPRETER_AOUT 1
#define INTERPRETER_ELF 2


static int load_elf_binary(struct linux_binprm * bprm, struct pt_regs * regs)
{
    struct file *interpreter = NULL; /* to shut gcc up */
     unsigned long load_addr = 0, load_bias;
    int load_addr_set = 0;
    char * elf_interpreter = NULL;
    unsigned int interpreter_type = INTERPRETER_NONE;
    unsigned char ibcs2_interpreter = 0;
    mm_segment_t old_fs;
    unsigned long error;
    struct elf_phdr * elf_ppnt, *elf_phdata;
    unsigned long elf_bss, k, elf_brk;
    int elf_exec_fileno;
    int retval, size, i;
    unsigned long elf_entry, interp_load_addr = 0;
    unsigned long start_code, end_code, start_data, end_data;
    struct elfhdr elf_ex;
    struct elfhdr interp_elf_ex;
      struct exec interp_ex;
    char passed_fileno[6];

    /* Get the exec-header */
    elf_ex = *((struct elfhdr *) bprm->buf);

    retval = -ENOEXEC;
    /* First of all, some simple consistency checks */
    //检查magic
    if (memcmp(elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
        goto out;

    //既非可执行文件又非动态链接库,动态链接库也可直接执行
    if (elf_ex.e_type != ET_EXEC && elf_ex.e_type != ET_DYN)
        goto out;
    if (!elf_check_arch(&elf_ex))//体系结构检查
        goto out;
    if (!bprm->file->f_op||!bprm->file->f_op->mmap)//不能mmap,error
        goto out;

    /* Now read in all of the header information */

    retval = -ENOMEM;
    /*
    typedef struct elf32_hdr{
  unsigned char    e_ident[EI_NIDENT];
  Elf32_Half    e_type;
  Elf32_Half    e_machine;
  Elf32_Word    e_version;
  Elf32_Addr    e_entry;  
  Elf32_Off    e_phoff;
  Elf32_Off    e_shoff;
  Elf32_Word    e_flags;
  Elf32_Half    e_ehsize;
  Elf32_Half    e_phentsize;
  Elf32_Half    e_phnum;
  Elf32_Half    e_shentsize;
  Elf32_Half    e_shnum;
  Elf32_Half    e_shstrndx;
} Elf32_Ehdr;
    */
    //e_phentsize 该成员保存着在文件的程序头表(program header table)
    //中一个入口的大小(以字节计数)。所有的入口都是同样的大小。
    
    //e_phnum 该成员保存着在程序头表中入口的个数。因此,e_phentsize和e_phnum
    //的乘机就是表的大小(以字节计数).假如没有程序头表(program header table),
    //e_phnum变量为0。
    size = elf_ex.e_phentsize * elf_ex.e_phnum;
    if (size > 65536)
        goto out;
    elf_phdata = (struct elf_phdr *) kmalloc(size, GFP_KERNEL);
    if (!elf_phdata)
        goto out;

    //读入 program headers
    retval = kernel_read(bprm->file, elf_ex.e_phoff, (char *) elf_phdata, size);
    if (retval < 0)
        goto out_free_ph;

    retval = get_unused_fd();
    if (retval < 0)
        goto out_free_ph;
    get_file(bprm->file);
    //保存原始打开文件
    fd_install(elf_exec_fileno = retval, bprm->file);//flush old exec不会关闭

    elf_ppnt = elf_phdata;//program headers
    elf_bss = 0;
    elf_brk = 0;

    start_code = ~0UL;// -1
    end_code = 0;
    start_data = 0;
    end_data = 0;

    for (i = 0; i < elf_ex.e_phnum; i++) {//处理每一个program headers,寻找PT_INTERP
        if (elf_ppnt->p_type == PT_INTERP) {
            retval = -EINVAL;
              if (elf_interpreter)//已经有interpreter
                goto out_free_dentry;

            /* This is the program interpreter used for
             * shared libraries - for now assume that this
             * is an a.out format binary
             */
            /*
typedef struct elf32_phdr{
  Elf32_Word    p_type;
  Elf32_Off    p_offset;
  Elf32_Addr    p_vaddr;
  Elf32_Addr    p_paddr;
  Elf32_Word    p_filesz;
  Elf32_Word    p_memsz;
  Elf32_Word    p_flags;
  Elf32_Word    p_align;
} Elf32_Phdr;
            */
            retval = -ENOMEM;
            elf_interpreter = (char *) kmalloc(elf_ppnt->p_filesz,
                               GFP_KERNEL);
            if (!elf_interpreter)
                goto out_free_file;

            retval = kernel_read(bprm->file, elf_ppnt->p_offset,
                       elf_interpreter,
                       elf_ppnt->p_filesz);//读入interp
            if (retval < 0)
                goto out_free_interp;
            /* If the program interpreter is one of these two,
             * then assume an iBCS2 image. Otherwise assume
             * a native linux image.
             redhat 7.2 中是 /lib/ld-linux.so.2
             */
            if (strcmp(elf_interpreter,"/usr/lib/libc.so.1") == 0 ||
                strcmp(elf_interpreter,"/usr/lib/ld.so.1") == 0)
                ibcs2_interpreter = 1;
#if 0
            printk("Using ELF interpreter %s\n", elf_interpreter);
#endif
#ifdef __sparc__
            if (ibcs2_interpreter) {
                unsigned long old_pers = current->personality;
                struct exec_domain *old_domain = current->exec_domain;
                struct exec_domain *new_domain;
                struct fs_struct *old_fs = current->fs, *new_fs;
                get_exec_domain(old_domain);
                atomic_inc(&old_fs->count);

                set_personality(PER_SVR4);
                interpreter = open_exec(elf_interpreter);

                new_domain = current->exec_domain;
                new_fs = current->fs;
                current->personality = old_pers;
                current->exec_domain = old_domain;
                current->fs = old_fs;
                put_exec_domain(new_domain);
                put_fs_struct(new_fs);
            } else
#endif
            {
                interpreter = open_exec(elf_interpreter);//打开/lib/ld-linux.so.2
            }
            retval = PTR_ERR(interpreter);
            if (IS_ERR(interpreter))
                goto out_free_interp;
            retval = kernel_read(interpreter, 0, bprm->buf, BINPRM_BUF_SIZE);//读入头部
            if (retval < 0)
                goto out_free_dentry;

            /* Get the exec headers */
            interp_ex = *((struct exec *) bprm->buf);//可能是a.out
            interp_elf_ex = *((struct elfhdr *) bprm->buf);//可能是elf
        }
        elf_ppnt++;
    }

    /* Some simple consistency checks for the interpreter */
    if (elf_interpreter) {//有interp,执行/lib/ld-linux.so.2时,没有,或静态链接的可执行文件也没有
        interpreter_type = INTERPRETER_ELF | INTERPRETER_AOUT;

        /* Now figure out which format our binary is */
        if ((N_MAGIC(interp_ex) != OMAGIC) &&
            (N_MAGIC(interp_ex) != ZMAGIC) &&
            (N_MAGIC(interp_ex) != QMAGIC))
            interpreter_type = INTERPRETER_ELF;//是interp elf

        if (memcmp(interp_elf_ex.e_ident, ELFMAG, SELFMAG) != 0)
            interpreter_type &= ~INTERPRETER_ELF;//是interp aout

        retval = -ELIBBAD;
        if (!interpreter_type)
            goto out_free_dentry;

        /* Make sure only one type was selected */
        if ((interpreter_type & INTERPRETER_ELF) &&
             interpreter_type != INTERPRETER_ELF) {
            printk(KERN_WARNING "ELF: Ambiguous type, using ELF\n");
            interpreter_type = INTERPRETER_ELF;
        }
    }

    /* OK, we are done with that, now set up the arg stuff,
       and then start this sucker up */

    if (!bprm->sh_bang) {
        char * passed_p;

        if (interpreter_type == INTERPRETER_AOUT) {//a.out
          sprintf(passed_fileno, "%d", elf_exec_fileno);//原始打开文件号
          passed_p = passed_fileno;

          if (elf_interpreter) {//interp文件名
            retval = copy_strings_kernel(1,&passed_p,bprm);
            if (retval)
                goto out_free_dentry;
            bprm->argc++;//打开文件号作为参数
          }
        }
    }

    /* Flush all traces of the currently running executable */
    retval = flush_old_exec(bprm);//清除旧的执行影像
    if (retval)
        goto out_free_dentry;

    /* OK, This is the point of no return */
    current->mm->start_data = 0;
    current->mm->end_data = 0;
    current->mm->end_code = 0;
    current->mm->mmap = NULL;
    current->flags &= ~PF_FORKNOEXEC;
    elf_entry = (unsigned long) elf_ex.e_entry;//原文件代码入口

    /* Do this immediately, since STACK_TOP as used in setup_arg_pages
       may depend on the personality.  */
    SET_PERSONALITY(elf_ex, ibcs2_interpreter);

    /* Do this so that we can load the interpreter, if need be.  We will
       change some of these later */
    current->mm->rss = 0;
    setup_arg_pages(bprm); /* XXX: check error */

2.setup_arg_pages
load_elf_binayr->setup_arg_page
fs/exec.c

//把arg pages页和进程挂钩
int setup_arg_pages(struct linux_binprm *bprm)
{
    unsigned long stack_base;
    struct vm_area_struct *mpnt;
    int i;

    stack_base = STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE;//堆栈基址

    bprm->p += stack_base;//变换成地址
    if (bprm->loader)
        bprm->loader += stack_base;
    bprm->exec += stack_base;

    mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//为堆栈段分配vm_area_struct结构
    if (!mpnt)
        return -ENOMEM;
   
    down(¤t->mm->mmap_sem);
    {
        mpnt->vm_mm = current->mm;
        mpnt->vm_start = PAGE_MASK & (unsigned long) bprm->p;//下对齐
        mpnt->vm_end = STACK_TOP;
        mpnt->vm_page_prot = PAGE_COPY;
        mpnt->vm_flags = VM_STACK_FLAGS;
        mpnt->vm_ops = NULL;
        mpnt->vm_pgoff = 0;
        mpnt->vm_file = NULL;
        mpnt->vm_private_data = (void *) 0;
        insert_vm_struct(current->mm, mpnt);
        current->mm->total_vm = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT;
    }

    for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
        struct page *page = bprm->page[i];
        if (page) {
            bprm->page[i] = NULL;
            current->mm->rss++;//驻内页
            put_dirty_page(current,page,stack_base);//该页挂入进程空间
        }
        stack_base += PAGE_SIZE;
    }
    up(¤t->mm->mmap_sem);
   
    return 0;
}


3.返回到load_elf_binary

    current->mm->start_stack = bprm->p;

    /* Try and get dynamic programs out of the way of the default mmap
       base, as well as whatever program they might try to exec.  This
       is because the brk will follow the loader, and is not movable.  */

    //普通可执行文件load_bias=0;动态链接库load_bias=0x8000 0000,即2G处(单独执行时,给ld-linux.so.2让路)
/*
例如/lib/ld-2.3.2.so执行时的maps如下
[root@mail /proc/30019]# cat maps
80000000-80015000 r-xp 00000000 08:01 272070     /lib/ld-2.3.2.so
80015000-80016000 rw-p 00014000 08:01 272070     /lib/ld-2.3.2.so
bfffe000-c0000000 rwxp fffff000 00:00 0
/lib/libc-2.3.2.so执行时的maps如下
[root@mail /proc/30097]# cat /proc/14541/maps 
40000000-40015000 r-xp 00000000 08:01 272070     /lib/ld-2.3.2.so
40015000-40016000 rw-p 00014000 08:01 272070     /lib/ld-2.3.2.so
80000000-80133000 r-xp 00000000 08:01 272077     /lib/libc-2.3.2.so
80133000-80137000 rw-p 00132000 08:01 272077     /lib/libc-2.3.2.so
80137000-80139000 rwxp 00000000 00:00 0
bfffe000-c0000000 rwxp fffff000 00:00 0
*/
    load_bias = ELF_PAGESTART(elf_ex.e_type==ET_DYN ? ELF_ET_DYN_BASE : 0);

    /* Now we do a little grungy work by mmaping the ELF image into
       the correct location in memory.  At this point, we assume that
       the image should be loaded at fixed address, not at a variable
       address. */

    old_fs = get_fs();
    set_fs(get_ds());
    for(i = 0, elf_ppnt = elf_phdata; i < elf_ex.e_phnum; i++, elf_ppnt++) {
        //处理每一个program headers
/*
typedef struct elf32_phdr{
  Elf32_Word    p_type;
  Elf32_Off    p_offset;//该成员给出了该段的驻留位置相对于文件开始处的偏移。
  Elf32_Addr    p_vaddr;//该成员给出了该段在内存中的首字节地址。(连接器推荐的加载基址)
  Elf32_Addr    p_paddr;
  Elf32_Word    p_filesz;//该成员给出了文件映像中该段的字节数;它可能是 0 。
  Elf32_Word    p_memsz;//该成员给出了内存映像中该段的字节数;它可能是 0 。
  Elf32_Word    p_flags;//该成员给出了和该段相关的标志。定义的标志值如下所述。
  Elf32_Word    p_align;
} Elf32_Phdr;
*/
        int elf_prot = 0, elf_flags;
        unsigned long vaddr;

        if (elf_ppnt->p_type != PT_LOAD)//必须是PT_LOAD
            continue;

        if (elf_ppnt->p_flags & PF_R) elf_prot |= PROT_READ;
        if (elf_ppnt->p_flags & PF_W) elf_prot |= PROT_WRITE;
        if (elf_ppnt->p_flags & PF_X) elf_prot |= PROT_EXEC;

        elf_flags = MAP_PRIVATE|MAP_DENYWRITE|MAP_EXECUTABLE;

        vaddr = elf_ppnt->p_vaddr;
        if (elf_ex.e_type == ET_EXEC || load_addr_set) {
            //是可执行文件或者起始加载地址已设置
            elf_flags |= MAP_FIXED;
        }
        error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, elf_prot, elf_flags);
        //不判断出错 ???
       

/*
elf文件有两种视图,一种节表,是从程序编译连接的角度看。一种是程序头,是从程序执行的角度看。

举例看看这两种视图的关系

[root@mail /proc/30097]# readelf -l /bin/ls

Elf file type is EXEC (Executable file)
Entry point 0x8049690
There are 7 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000e0 0x000e0 R E 0x4
  INTERP         0x000114 0x08048114 0x08048114 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x08048000 0x08048000 0x0fa98 0x0fa98 R E 0x1000
  LOAD           0x010000 0x08058000 0x08058000 0x00348 0x006c8 RW  0x1000
  DYNAMIC        0x010114 0x08058114 0x08058114 0x000d0 0x000d0 RW  0x4
  NOTE           0x000128 0x08048128 0x08048128 0x00020 0x00020 R   0x4
  GNU_EH_FRAME   0x00f960 0x08057960 0x08057960 0x0002c 0x0002c R   0x4

 Section to Segment mapping:
  Segment Sections...
   00    
   01     .interp
   02     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata

.eh_frame_hdr .eh_frame
   03     .data .dynamic .ctors .dtors .jcr .got .bss
   04     .dynamic
   05     .note.ABI-tag
   06     .eh_frame_hdr

注意下面的Section to Segment mapping,说明了每个程序头包含了哪些节。我们关心的是代码段程序头和数据段程序头。
  LOAD           0x000000 0x08048000 0x08048000 0x0fa98 0x0fa98 R E 0x1000
  LOAD           0x010000 0x08058000 0x08058000 0x00348 0x006c8 RW  0x1000
分别对应
   02     .interp .note.ABI-tag .hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata

.eh_frame_hdr .eh_frame
   03     .data .dynamic .ctors .dtors .jcr .got .bss

代码段开始文件地址是0,开始虚拟地址是0x8048000,文件大小是0xfa98,内存大小是0xfa98,flag是可读可执行,对齐大小是4k
数据段开始文件地址是0x10000,开始虚拟地址0x8058000,文件大小是0x348,内存大小是0x6c8,flag是可读可写,对齐大小是4

k

下面列出节表
[zws@mail /proc/1]$readelf -S /bin/ls
There are 26 section headers, starting at offset 0x10444:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048114 000114 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048128 000128 000020 00   A  0   0  4
  [ 3] .hash             HASH            08048148 000148 00028c 04   A  4   0  4
  [ 4] .dynsym           DYNSYM          080483d4 0003d4 0005e0 10   A  5   1  4
  [ 5] .dynstr           STRTAB          080489b4 0009b4 0003ea 00   A  0   0  1
  [ 6] .gnu.version      VERSYM          08048d9e 000d9e 0000bc 02   A  4   0  2
  [ 7] .gnu.version_r    VERNEED         08048e5c 000e5c 000070 00   A  5   1  4
  [ 8] .rel.dyn          REL             08048ecc 000ecc 000028 08   A  4   0  4
  [ 9] .rel.plt          REL             08048ef4 000ef4 000278 08   A  4  11  4
  [10] .init             PROGBITS        0804916c 00116c 000017 00  AX  0   0  4
  [11] .plt              PROGBITS        08049184 001184 000500 04  AX  0   0  4
  [12] .text             PROGBITS        08049690 001690 00ab4c 00  AX  0   0 16
  [13] .fini             PROGBITS        080541dc 00c1dc 00001b 00  AX  0   0  4
  [14] .rodata           PROGBITS        08054200 00c200 003760 00   A  0   0 32
  [15] .eh_frame_hdr     PROGBITS        08057960 00f960 00002c 00   A  0   0  4
  [16] .eh_frame         PROGBITS        0805798c 00f98c 00010c 00   A  0   0  4
  [17] .data             PROGBITS        08058000 010000 000114 00  WA  0   0 32
  [18] .dynamic          DYNAMIC         08058114 010114 0000d0 08  WA  5   0  4
  [19] .ctors            PROGBITS        080581e4 0101e4 000008 00  WA  0   0  4
  [20] .dtors            PROGBITS        080581ec 0101ec 000008 00  WA  0   0  4
  [21] .jcr              PROGBITS        080581f4 0101f4 000004 00  WA  0   0  4
  [22] .got              PROGBITS        080581f8 0101f8 000150 04  WA  0   0  4
  [23] .bss              NOBITS          08058360 010360 000368 00  WA  0   0 32
  [24] .gnu_debuglink    PROGBITS        00000000 010360 000010 00      0   0  4
  [25] .shstrtab         STRTAB          00000000 010370 0000d2 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

从开始文件地址和内存大小可看出
代码段包含的节从[0]到[16],数据段包含的节从[17]到[23],和前面的显示正好一致。

由于内存映射以页为单位,映射起始地址向下对齐到页边界,映射大小向上对齐到页边界,因此在进行内存映射的时候,
代码段映射关系是虚拟地址[0x8048000,0x8048000+0x10000)->文件偏移[0,0x10000)
数据段映射关系是虚拟地址[0x8058000,0x1000)->文件偏移[0x10000,0x10000+0x1000)

/bin/ls的文件大小是小于0x11000的,数据的映射超出了,不过没有关系,超出的部分会被当做零页分配。可见这个文件都被映

射了。显然0x8048000就是这个文件镜像加载的起始地址,这个地址后面有用。

我还注意到[23].bss节type是NOBITS,说明文件中没有对应内容,这从[24].gnu_debuglink的文件偏移和[23].bss的文件偏移相

等侧面证明。但是它却是有大小的,0x368,Flag是WA,说明可写且需要分配。

bss是未初始化节,程序中的未初始化变量都放在这个节中,由于未初始化变量的值默认都为0,因此也就不再文件中为其分配空

间了。但是到了内存中就不同了,必须为其分配空间,且清0。这一点后面还会谈到。

还有就是程序中的常量被放在.rodata节中,常量只能读,不能写,代码段是可读,可执行,因此.rodata节被插入在代码段中,没

有创建额外的程序头了。

可执行文件的一般从0x08000000(512M)开始编址,可执行文件加载时不重定位。
动态链接库的一般从0开始编址.动态链接库加载时重定位,重定位地址从0x40000000(1G)开始。

映射的时候,代码段只读映射该页,而数据段COW映射该页.因此虽然上面代码段和数据段是连续的,但是页属性是不同的。


代码段和数据段在文件中的映射可能有重叠,例如
[zws@mail ~]$ readelf -l /lib/libc-2.3.2.so

Elf file type is DYN (Shared object file)
Entry point 0x159d0
There are 7 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00000034 0x00000034 0x000e0 0x000e0 R E 0x4
  INTERP         0x1312f0 0x001312f0 0x001312f0 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x00000000 0x00000000 0x132804 0x132804 R E 0x1000
  LOAD           0x132820 0x00133820 0x00133820 0x02c90 0x056c4 RW  0x1000
  DYNAMIC        0x1350d4 0x001360d4 0x001360d4 0x000d8 0x000d8 RW  0x4
  NOTE           0x000114 0x00000114 0x00000114 0x00020 0x00020 R   0x4
  GNU_EH_FRAME   0x131304 0x00131304 0x00131304 0x0032c 0x0032c R   0x4

假设实际加载地址是x
代码段映射关系是虚拟地址[x+0,x+0+0x133000)->文件偏移[0,0x133000)
数据段映射关系是虚拟地址[x+0x133000,x+0x133000+0x6000)->文件偏移[0x132000,0x132000+0x3000)+零页[0x132000+

0x3000,0x133000+0x6000)

文件偏移有一页重叠,但这种重叠不会引起冲突和访问错误.

总之,ELF文件的节和程序头关系紧密,其中暗藏玄机,值得细细揣摩。

*/

4.计算start_code,end_code等


        if (!load_addr_set) {
            load_addr_set = 1;
            //load_addr 计算整个镜像加载基址
            /*
                    load_bias   load_addr   
            ET_EXEC        0x00000000  0x08048000  /bin/ls
             ET_DYN     0x80000000  0x80000000   /lib/ld-linux.so.2
            */
            load_addr = (elf_ppnt->p_vaddr - elf_ppnt->p_offset);//链接器推荐的镜像加载基址,对于可执

行文件就是该地址,对于动态链接库,一般是0
            if (elf_ex.e_type == ET_DYN) {//load_bias是0x80000000
                load_bias += error -//动态链接库实际加载地址-内核推荐的加载地址=加载偏移(一般

是0)
                             ELF_PAGESTART(load_bias + vaddr);
                load_addr += error;//一般load_addr==error
            }
        }
        k = elf_ppnt->p_vaddr;//通常代码段和数据段紧挨在一起,代码段在前,数据段在后
        if (k < start_code) start_code = k;//start_code 的初值为0xffffffff,定位代码段开始地址
        if (start_data < k) start_data = k;//start_data初值为0,定位数据段开始

        k = elf_ppnt->p_vaddr + elf_ppnt->p_filesz;//file size,不是mem size

        if (k > elf_bss)//elf_bss初值为0
            elf_bss = k;//计算bss起始地址
        if ((elf_ppnt->p_flags & PF_X) && end_code <  k)
            end_code = k;//代码段结束地址
        if (end_data < k)
            end_data = k;//数据段结束地址
        k = elf_ppnt->p_vaddr + elf_ppnt->p_memsz;//mem size
        if (k > elf_brk)
            elf_brk = k;
        //[elf_bss,elf_brk)之间是bss section
    }
    set_fs(old_fs);

    //全部重定位
    elf_entry += load_bias;
    elf_bss += load_bias;//bss为初始化段开始
    elf_brk += load_bias;//brk动态内存分配起始地址
    start_code += load_bias;
    end_code += load_bias;
    start_data += load_bias;
    end_data += load_bias;

    if (elf_interpreter) {
        //elf_entry被覆盖
        if (interpreter_type == INTERPRETER_AOUT)
            elf_entry = load_aout_interp(&interp_ex,
                             interpreter);
        else
            elf_entry = load_elf_interp(&interp_elf_ex,
                            interpreter,
                            &interp_load_addr);

5.load_elf_interp


/* This is much more generalized than the library routine read function,
   so we keep this separate.  Technically the library read function
   is only provided so that we can read a.out libraries that have
   an ELF header */

static unsigned long load_elf_interp(struct elfhdr * interp_elf_ex,
                     struct file * interpreter,
                     unsigned long *interp_load_addr)
{
    struct elf_phdr *elf_phdata;
    struct elf_phdr *eppnt;
    unsigned long load_addr = 0;
    int load_addr_set = 0;
    unsigned long last_bss = 0, elf_bss = 0;
    unsigned long error = ~0UL;
    int retval, i, size;

    /* First of all, some simple consistency checks */
    if (interp_elf_ex->e_type != ET_EXEC &&
        interp_elf_ex->e_type != ET_DYN)
        goto out;
    if (!elf_check_arch(interp_elf_ex))
        goto out;
    if (!interpreter->f_op || !interpreter->f_op->mmap)
        goto out;

    /*
     * If the size of this structure has changed, then punt, since
     * we will be doing the wrong thing.
     */
    if (interp_elf_ex->e_phentsize != sizeof(struct elf_phdr))
        goto out;

    /* Now read in all of the header information */

    size = sizeof(struct elf_phdr) * interp_elf_ex->e_phnum;
    if (size > ELF_MIN_ALIGN)
        goto out;
    elf_phdata = (struct elf_phdr *) kmalloc(size, GFP_KERNEL);
    if (!elf_phdata)
        goto out;

    //读入interp的prgoram header
    retval = kernel_read(interpreter,interp_elf_ex->e_phoff,(char *)elf_phdata,size);
    error = retval;
    if (retval < 0)
        goto out_close;

    eppnt = elf_phdata;
    for (i=0; ie_phnum; i++, eppnt++) {
      if (eppnt->p_type == PT_LOAD) {
        int elf_type = MAP_PRIVATE | MAP_DENYWRITE;
        int elf_prot = 0;
        unsigned long vaddr = 0;
        unsigned long k, map_addr;

        if (eppnt->p_flags & PF_R) elf_prot =  PROT_READ;
        if (eppnt->p_flags & PF_W) elf_prot |= PROT_WRITE;
        if (eppnt->p_flags & PF_X) elf_prot |= PROT_EXEC;
        vaddr = eppnt->p_vaddr;
        if (interp_elf_ex->e_type == ET_EXEC || load_addr_set)
            elf_type |= MAP_FIXED;
/*
readelf -l /lib/ld-linux.so.2

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00000000 0x00000000 0x15229 0x15229 R E 0x1000
  LOAD           0x015240 0x00016240 0x00016240 0x00300 0x00758 RW  0x1000
  DYNAMIC        0x015490 0x00016490 0x00016490 0x000b0 0x000b0 RW  0x4

load_addr + vaddr=0,mmap将从1G处开始处映射
*/

        map_addr = elf_map(interpreter, load_addr + vaddr, eppnt, elf_prot, elf_type);

        if (!load_addr_set && interp_elf_ex->e_type == ET_DYN) {
        //只计算ET_DYN
        load_addr = map_addr - ELF_PAGESTART(vaddr);
        load_addr_set = 1;
        }

        /*
         * Find the end of the file mapping for this phdr, and keep
         * track of the largest address we see for this.
         */
        k = load_addr + eppnt->p_vaddr + eppnt->p_filesz;
        if (k > elf_bss)//elf bss
        elf_bss = k;

        /*
         * Do the same thing for the memory mapping - between
         * elf_bss and last_bss is the bss section.
         [elf_bss,last_bss)是bss section
         */
        k = load_addr + eppnt->p_memsz + eppnt->p_vaddr;
        if (k > last_bss)//last bss
        last_bss = k;
      }
    }

    /* Now use mmap to map the library into memory. */

    /*
     * Now fill out the bss section.  First pad the last page up
     * to the page boundary, and then perform a mmap to make sure
     * that there are zero-mapped pages up to and including the
     * last bss page.
     */
    padzero(elf_bss);//清bss
    elf_bss = ELF_PAGESTART(elf_bss + ELF_MIN_ALIGN - 1);    /* What we have mapped so far */

    /* Map the last of the bss segment */
    if (last_bss > elf_bss)//未映射到文件的部分,分配0页
        do_brk(elf_bss, last_bss - elf_bss);//匿名映射[addr,addr+len)

    *interp_load_addr = load_addr;//镜像加载基址
    error = ((unsigned long) interp_elf_ex->e_entry) + load_addr;//入口地址

out_close:
    kfree(elf_phdata);
out:
    return error;
}


6.返回load_elf_binary

        allow_write_access(interpreter);
        fput(interpreter);
        kfree(elf_interpreter);

        if (elf_entry == ~0UL) {
            printk(KERN_ERR "Unable to load interpreter\n");
            kfree(elf_phdata);
            send_sig(SIGSEGV, current, 0);
            return 0;
        }
    }

    kfree(elf_phdata);

    if (interpreter_type != INTERPRETER_AOUT)
        sys_close(elf_exec_fileno);//ELF不需要

    set_binfmt(&elf_format);

    compute_creds(bprm);
    current->flags &= ~PF_FORKNOEXEC;
    //注意bprm->p被更新,指向argc地址
    bprm->p = (unsigned long)
      create_elf_tables((char *)bprm->p,
            bprm->argc,
            bprm->envc,
            (interpreter_type == INTERPRETER_ELF ? &elf_ex : NULL),
            load_addr, load_bias,
            interp_load_addr,
            (interpreter_type == INTERPRETER_AOUT ? 0 : 1));

7.create_elf_tables


static elf_addr_t *
create_elf_tables(char *p, int argc, int envc,
          struct elfhdr * exec,
          unsigned long load_addr,
          unsigned long load_bias,
          unsigned long interp_load_addr, int ibcs)
{
    elf_caddr_t *argv;
    elf_caddr_t *envp;
    elf_addr_t *sp, *csp;
    char *k_platform, *u_platform;
    long hwcap;
    size_t platform_len = 0;

    /*
     * Get hold of platform and hardware capabilities masks for
     * the machine we are running on.  In some cases (Sparc),
     * this info is impossible to get, in others (i386) it is
     * merely difficult.
     */

    hwcap = ELF_HWCAP;//CPU特性描述字
    k_platform = ELF_PLATFORM;//CPU类型名 例如i686

    //p指向argv字符串首地址 即下面的0xbffffc07处
/*
0xbffffc02:      "i686"
0xbffffc07:      "/root/3/88"
0xbffffc12:      "PWD=/root"
0xbffffc1c:      "HOSTNAME=proxy"
0xbffffc2b:      "QTDIR=/usr/lib/qt-2.3.1"
0xbffffc43:      "LESSOPEN=|/usr/bin/lesspipe.sh %s"
0xbffffc65:      "KDEDIR=/usr"
*/
    if (k_platform) {//一般不为空
        platform_len = strlen(k_platform) + 1;
        u_platform = p - platform_len;
        __copy_to_user(u_platform, k_platform, platform_len);//i686
    } else
        u_platform = p;

    /*
     * Force 16 byte _final_ alignment here for generality.
     * Leave an extra 16 bytes free so that on the PowerPC we
     * can move the aux table up to start on a 16-byte boundary.
     */
     //向低地址方向对齐到16字节边界,再减16字节
                //较新的内核使用一个随机数,因此布局有所不同
    sp = (elf_addr_t *)((~15UL & (unsigned long)(u_platform)) - 16UL);
    csp = sp;
    //DLINFO -> 动态链接信息?? 这些信息是为ld.so准备的,ld.so需要用到
    csp -= ((exec ? DLINFO_ITEMS*2 : 4) + (k_platform ? 2 : 0));//DLINFO_ITEMS*2 + 2,DLINFO_ITEMS定义为13
    csp -= envc+1;
    csp -= argc+1;
    //ibcs 0->a.out 1->elf
    csp -= (!ibcs ? 3 : 1);// 1-> argc    /* argc itself */
    if ((unsigned long)csp & 15UL)//不与16字节边界对齐
    //下移sp,使argc对齐到16字节边界
        sp -= ((unsigned long)csp & 15UL) / sizeof(*sp);
/*
内存布局如下
  position            content                     size (bytes) + comment
  ------------------------------------------------------------------------
  stack pointer ->  [ argc = number of args ]     4
                    [ argv[0] (pointer) ]         4   (program name)
                    [ argv[1] (pointer) ]         4
                    [ argv[..] (pointer) ]        4 * x
                    [ argv[n - 1] (pointer) ]     4
                    [ argv[n] (pointer) ]         4   (= NULL)

                    [ envp[0] (pointer) ]         4
                    [ envp[1] (pointer) ]         4
                    [ envp[..] (pointer) ]        4
                    [ envp[term] (pointer) ]      4   (= NULL)

                    [ auxv[0] AT_PHDR (Elf32_auxv_t) ]    8
                    [ auxv[1] AT_PHENT (Elf32_auxv_t) ]    8
                    [ auxv[2] AT_PHNUM (Elf32_auxv_t) ]   8
                    [ auxv[3] AT_BASE (Elf32_auxv_t) ]   8
                    [ auxv[4] AT_FLAGS (Elf32_auxv_t) ]   8
                    [ auxv[5] AT_ENTRY (Elf32_auxv_t) ]   8
                    [ auxv[6] AT_UID (Elf32_auxv_t) ]   8
                    [ auxv[7] AT_EUID (Elf32_auxv_t) ]   8
                    [ auxv[8] AT_GID (Elf32_auxv_t) ]   8
                    [ auxv[9] AT_EGID (Elf32_auxv_t) ]   8
                    [ auxv[10] AT_HWCAP (Elf32_auxv_t) ]   8
                    [ auxv[11] AT_PAGESZ (Elf32_auxv_t) ]   8
                    [ auxv[12] AT_CLKTCK (Elf32_auxv_t) ]   8
                    [ auxv[13] AT_PLATFORM (Elf32_auxv_t) ]   8
                    [ auxv[14] (Elf32_auxv_t) ] 8   (= AT_NULL vector)

         [ padding ]                   0 - 15                   
                    [ padding ]                   16                   
                    [ padding ]                   0 - 15                   

         [k_platform]                  0 - 65
                    [ argument ASCIIZ strings ]   >= 0
                    [ environment ASCIIZ str. ]   >= 0
                       [filename] >=0

  (0xbffffffc)      [ end marker ]                4   (= NULL)

  (0xc0000000)      < top of stack >              0   (virtual)
*/
    /*
     * Put the ELF interpreter info on the stack
     */
#define NEW_AUX_ENT(nr, id, val) \
      __put_user ((id), sp+(nr*2)); \
      __put_user ((val), sp+(nr*2+1)); \
    //开始存放辅助向量
    sp -= 2;
    NEW_AUX_ENT(0, AT_NULL, 0);//end of vector
    if (k_platform) {
        sp -= 2;
        NEW_AUX_ENT(0, AT_PLATFORM, (elf_addr_t)(unsigned long) u_platform);
    }
    sp -= 3*2;
    NEW_AUX_ENT(0, AT_HWCAP, hwcap);
    NEW_AUX_ENT(1, AT_PAGESZ, ELF_EXEC_PAGESIZE);// 4096
    NEW_AUX_ENT(2, AT_CLKTCK, CLOCKS_PER_SEC);// 100

    if (exec) {//elf interp
        sp -= 10*2;

        NEW_AUX_ENT(0, AT_PHDR, load_addr + exec->e_phoff);
        NEW_AUX_ENT(1, AT_PHENT, sizeof (struct elf_phdr));
        NEW_AUX_ENT(2, AT_PHNUM, exec->e_phnum);
        NEW_AUX_ENT(3, AT_BASE, interp_load_addr);//interp加载基址,如果就是/lib/ld-linux.so.2或静态链

接可执行文件,则为0
        NEW_AUX_ENT(4, AT_FLAGS, 0);
        NEW_AUX_ENT(5, AT_ENTRY, load_bias + exec->e_entry);//原程序入口
        NEW_AUX_ENT(6, AT_UID, (elf_addr_t) current->uid);
        NEW_AUX_ENT(7, AT_EUID, (elf_addr_t) current->euid);
        NEW_AUX_ENT(8, AT_GID, (elf_addr_t) current->gid);
        NEW_AUX_ENT(9, AT_EGID, (elf_addr_t) current->egid);
    }
#undef NEW_AUX_ENT

    sp -= envc+1;
    envp = (elf_caddr_t *) sp;
    sp -= argc+1;
    argv = (elf_caddr_t *) sp;
    if (!ibcs) {//a.out
        __put_user((elf_addr_t)(unsigned long) envp,--sp);
        __put_user((elf_addr_t)(unsigned long) argv,--sp);
    }
    //处理argv数组
    __put_user((elf_addr_t)argc,--sp);//argc入栈
    current->mm->arg_start = (unsigned long) p;//arg_start
    while (argc-->0) {
        __put_user((elf_caddr_t)(unsigned long)p,argv++);
        p += strlen_user(p);//计算下一个字符串的长度,更新p
    }
    __put_user(NULL, argv);
    //处理envp数组
    current->mm->arg_end = current->mm->env_start = (unsigned long) p;
    while (envc-->0) {
        __put_user((elf_caddr_t)(unsigned long)p,envp++);
        p += strlen_user(p);
    }
    __put_user(NULL, envp);
    current->mm->env_end = (unsigned long) p;
    return sp;//返回argc地址
}


8.返回load_elf_binary

    /* N.B. passed_fileno might not be initialized? */
    if (interpreter_type == INTERPRETER_AOUT)
        current->mm->arg_start += strlen(passed_fileno) + 1;//多了passed_fileno参数
    current->mm->start_brk = current->mm->brk = elf_brk;//动态分配内存起始地址
    current->mm->end_code = end_code;
    current->mm->start_code = start_code;
    current->mm->start_data = start_data;
    current->mm->end_data = end_data;
    current->mm->start_stack = bprm->p;

    /* Calling set_brk effectively mmaps the pages that we need
     * for the bss and break sections
     */
    set_brk(elf_bss, elf_brk);//elf_bss上取整

9.set_brk

static void set_brk(unsigned long start, unsigned long end)
{
    start = ELF_PAGEALIGN(start);//上取整到页边界
    end = ELF_PAGEALIGN(end);
    if (end <= start)
        return;
    do_brk(start, end - start);
}


10.返回load_elf_binary
    padzero(elf_bss);//对最后一映射文件的页中的bss清零

#if 0
    printk("(start_brk) %lx\n" , (long) current->mm->start_brk);
    printk("(end_code) %lx\n" , (long) current->mm->end_code);
    printk("(start_code) %lx\n" , (long) current->mm->start_code);
    printk("(start_data) %lx\n" , (long) current->mm->start_data);
    printk("(end_data) %lx\n" , (long) current->mm->end_data);
    printk("(start_stack) %lx\n" , (long) current->mm->start_stack);
    printk("(brk) %lx\n" , (long) current->mm->brk);
#endif

    if ( current->personality == PER_SVR4 )
    {
        /* Why this, you ask???  Well SVr4 maps page 0 as read-only,
           and some applications "depend" upon this behavior.
           Since we do not have the power to recompile these, we
           emulate the SVr4 behavior.  Sigh.  */
        /* N.B. Shouldn't the size here be PAGE_SIZE?? */
        down(¤t->mm->mmap_sem);
        error = do_mmap(NULL, 0, 4096, PROT_READ | PROT_EXEC,
                MAP_FIXED | MAP_PRIVATE, 0);
        up(¤t->mm->mmap_sem);
    }

#ifdef ELF_PLAT_INIT
    /*
     * The ABI may specify that certain registers be set up in special
     * ways (on i386 %edx is the address of a DT_FINI function, for
     * example.  This macro performs whatever initialization to
     * the regs structure is required.
     */
    ELF_PLAT_INIT(regs);//清空所有寄存器
#endif
/*
#define ELF_PLAT_INIT(_r)    do { \
    _r->ebx = 0; _r->ecx = 0; _r->edx = 0; \
    _r->esi = 0; _r->edi = 0; _r->ebp = 0; \
    _r->eax = 0; \
} while (0)
*/

    start_thread(regs, elf_entry, bprm->p);//一般此处的elf_entry是interp的entry

11.start_thread

//清fs,gs
#define start_thread(regs, new_eip, new_esp) do {        \
    __asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0));    \
    set_fs(USER_DS);                    \
    regs->xds = __USER_DS;                    \
    regs->xes = __USER_DS;                    \
    regs->xss = __USER_DS;                    \
    regs->xcs = __USER_CS;                    \
    regs->eip = new_eip;/*设置eip,一般指向ld-linux.so.2的入口*/                    \
    regs->esp = new_esp;/*设置esp,指向argc地址*/                    \
} while (0)

12.返回load_elf_binary

    if (current->ptrace & PT_PTRACED)
        send_sig(SIGTRAP, current, 0);//如果进程被调试,通知父进程
    retval = 0;
out:
    return retval;

    /* error cleanup */
out_free_dentry:
    allow_write_access(interpreter);
    fput(interpreter);
out_free_interp:
    if (elf_interpreter)
        kfree(elf_interpreter);
out_free_file:
    sys_close(elf_exec_fileno);
out_free_ph:
    kfree(elf_phdata);
    goto out;
}
 
阅读(2457) | 评论(0) | 转发(0) |
0

上一篇:ld.so分析2

下一篇:ld.so分析4

给主人留下些什么吧!~~