X86汇编语言学习手记(3)

X86汇编语言学习手记(3)

作者: Badcoffee

Email: [email protected]
2004年12月

原文出处: http://blog.csdn.net/yayong
版权所有: 转载时请务必以超链接形式标明文章原始出处、作者信息及本声明

这是作者在学习X86汇编过程中的学习笔记,难免有错误和疏漏之处,欢迎指正。
严格说来,本篇文档所涉及到的内容并非局限于X86汇编领域,关于ELF文件格式、C语言、编译器及其它相关知识,还需参考相关文档。
作者会将反馈的错误修订后更新在自己的Blog站点上。

    在X86汇编语言学习手记(1)(2)中,可以看到栈(Stack)作为进程执行过程中数据的临时存储区域,通常包含如下几类数据:
        局部变量
        函数调用的返回地址
        函数调用的入口参数
        SFP 栈框架指针 (可以通过编译器优化选项去除)
    本章中,将继续通过实验,了解全局变量和全局常量在进程中是如何存储和分配的。


注:不同的Calling Convention对入口参数的规定是有一定差别的,函数调用入口参数也有可能通过寄存器来传递。
    例如IBM的Power PC和AMD的Opteron,函数的入口参数全部或部分就是通过寄存器来传递的。


1. 全局变量和全局常量的实验

    延续之前的方式,给出一个简单的C程序,其中声明的全局变量分为3种:
        初始化过的全局变量
        未初始化的全局变量       
        全局常量

    #vi test5.c

    int i=1;
    int j=2;
    int k=3;
    int l,m;
    int n;
    const int o=7;
    const int p=8;
    const int q=9;
    int main()
    {
        l=4;
        m=5;
        n=6;

        return i+j+k+l+m+n+o+p+q;
    }

    # gcc test5.c -o test5
    # mdb test5
    Loading modules: [ libc.so.1 ]
    > main::dis
    main:              pushl   %ebp             ; main至main+1,创建Stack Frame
    main+1:            movl    %esp,%ebp
    main+3:            subl    $8,%esp
    main+6:            andl    $0xf0,%esp
    main+9:            movl    $0,%eax
    main+0xe:          subl    %eax,%esp         ; main+3至main+0xe,为局部变量预留栈空间,并保证栈16字节对齐
    main+0×10:         movl    $4,0×8060948       ; l=4
    main+0×1a:         movl    $5,0×806094c       ; m=5
    main+0×24:         movl    $6,0×8060950       ; n=6
    main+0×2e:         movl    0×8060908,%eax
    main+0×33:         addl    0×8060904,%eax
    main+0×39:         addl    0×806090c,%eax
    main+0×3f:         addl    0×8060948,%eax
    main+0×45:         addl    0×806094c,%eax
    main+0×4b:         addl    0×8060950,%eax
    main+0×51:         addl    0×8050808,%eax
    main+0×57:         addl    0×805080c,%eax
    main+0×5d:         addl    0×8050810,%eax     ; main+0×2e至main+0×5d,i+j+k+l+m+n+o+p+q
    main+0×63:         leave                      ; 撤销Stack Frame
    main+0×64:         ret                        ; main函数返回   

    现在,让我们在全局变量初始化后的地方设置断点,观察一下这几个全局变量的值:
    > main+0×2e:b                                 ; 设置断点
    > :r                                          ; 运行程序
    mdb: stop at main+0×2e
    mdb: target stopped at:
    main+0×2e:      movl    0×8060908,%eax
    > 0×8060904,03/nap                            ; 察看全局变量 i,j,k的值
    test5`i:
    test5`i:        
    test5`i:        1               
    test5`j:        2               
    test5`k:        3               
    > 0×8060948,03/nap                            ; 察看全局变量l,m,n的值
    test5`l:
    test5`l:        
    test5`l:        4               
    test5`m:        5               
    test5`n:        6               
    > 0×8050808,03/nap                            ; 察看全局变量o,p,q的值
    o:
    o:              
    o:              7               
    p:              8               
    q:              9               
    > 
    
    概念:进程地址空间 Process Address Space

       +———————-+ —-> 0xFFFFFFFF (4GB)
       |                                   |
       |   Kernel Space           |
       |                                   |
       +———————-+ —-> _kernel_base (0xE0000000)
       |                                   |
       |   Other Library           |
       :                                   :
       :                                   :
       |                                   |
       +———————-+
       |     data section            |
       |   Lib C Library           |
       |     text section             |
       :                                   :
       :                                   :
       +———————-+
       |                                   |
       |                                   |
       :                                   :
       :       grow up                :
       :                                   :
       |    User Heap               |
       |                                   |
       +———————-+
       |      bss                        |
       |                                   |
       |    User Data                |
       |                                   |
       +———————-+      
       |                                   |
       |    User Text                |
       |                                   |
       |                                   |
       +———————-+ —-> 0×08050000
       |                                   |
       |    User Stack              |
       |                                   |
       :     grow down             :
       :                                   :
       :                                   :
       |                                   |
       |                                   |
       +———————-+ —-> 0

       图 3-1 Solaris在IA32上的进程地址空间

如图3-1所示,Solaris在IA32上的进程地址空间和Linux是相似的,在用户进程的4GB地址空间内:

    Kernel总是映射到用户地址空间的最高端,从宏定义_kernel_base至0xFFFFFFFF的区域
    用户进程所依赖的各个共享库紧接着Kernel映射在用户地址空间的高端
    最后是用户进程地址空间在地址空间的低端
    
    各共享库的代码段,存放着二进制可执行的机器指令,是由kernel把该库ELF文件的代码段map到虚存空间,属性是read/exec/share
    各共享库的数据段,存放着程序执行所需的全局变量,是由kernel把ELF文件的数据段map到虚存空间,属性为read/write/private
    用户代码段,存放着二进制形式的可执行的机器指令,是由kernel把ELF文件的代码段map到虚存空间,属性为read/exec
    用户代码段之上是数据段,存放着程序执行所需的全局变量,是由kernel把ELF文件的数据段map到虚存空间,属性为 read/write/private
    用户代码段之下是栈(stack),作为进程的临时数据区,是由kernel把匿名内存map到虚存空间,属性为read/write/exec
    用户数据段之上是堆(heap),当且仅当malloc调用时存在,是由kernel把匿名内存map到虚存空间,属性为read/write/exec
    
    注意Stack和Heap的区别和联系:
    相同点:
       1. 都是来自于kernel分配的匿名内存,和磁盘上的ELF文件无关
       2. 属性均为read/write/exec
    不同点:
       1.栈的分配在C语言层面一般是通过声明局部变量,调用函数引起的;堆的分配则是通过显式的调用(malloc)引起的
       2.栈的释放在C语言层面是对用户透明的,用户不需要关心,由C编译器产生的相应的指令代劳;堆则需显式的调用(free)来释放
       3.栈空间的增长方向是从高地址到低地址;堆空间的增长方向是由低地址到高地址
       4.栈存在于任何进程的地址空间;堆则在程序中没有调用malloc的情况下不存在

    用户地址空间的布局随着CPU和OS的不同,略有差异,以上都是基于X86 CPU在Solaris OS上的情况的讨论。
    
    使用pmap命令,可以观察到系统中的指定进程的地址空间分布情况,下面就是用pmap观察bash进程的一个例子:
    
    # pmap 1030
    1030:   -bash
    08045000      12K rw—    [ stack ]                        ; bash的栈
    08050000     444K r-x–  /usr/bin/bash                      ; bash文本段
    080CE000      72K rwx–  /usr/bin/bash                      ; bash的数据段
    080E0000     156K rwx–    [ heap ]                         ; bash的堆
    DD8C0000       8K r-x–  /usr/lib/locale/zh_CN.GB18030/methods_zh_CN.GB18030.so.2   ; 共享库的文本段
    DD8D1000       4K rwx–  /usr/lib/locale/zh_CN.GB18030/methods_zh_CN.GB18030.so.2   ; 共享库的数据段
    DD8E0000     324K r-x–  /usr/lib/locale/zh_CN.GB18030/zh_CN.GB18030.so.2
    DD940000       8K rwx–  /usr/lib/locale/zh_CN.GB18030/zh_CN.GB18030.so.2
    DD950000       4K rwx–    [ anon ]                         ; 匿名内存, 由映射/dev/zero设备来创建的
    DD960000      12K r-x–  /usr/lib/libmp.so.2
    DD973000       4K rwx–  /usr/lib/libmp.so.2
    DD980000     628K r-x–  /usr/lib/libc.so.1
    DDA2D000      24K rwx–  /usr/lib/libc.so.1
    DDA33000       4K rwx–  /usr/lib/libc.so.1
    DDA50000       4K rwx–    [ anon ]
    DDA60000     548K r-x–  /usr/lib/libnsl.so.1
    DDAF9000      20K rwx–  /usr/lib/libnsl.so.1
    DDAFE000      32K rwx–  /usr/lib/libnsl.so.1
    DDB10000      44K r-x–  /usr/lib/libsocket.so.1
    DDB2B000       4K rwx–  /usr/lib/libsocket.so.1
    DDB30000     152K r-x–  /usr/lib/libcurses.so.1
    DDB66000      28K rwx–  /usr/lib/libcurses.so.1
    DDB6D000       8K rwx–  /usr/lib/libcurses.so.1
    DDB80000       4K r-x–  /usr/lib/libdl.so.1
    DDB90000     292K r-x–  /usr/lib/ld.so.1
    DDBE9000      16K rwx–  /usr/lib/ld.so.1
    DDBED000       8K rwx–  /usr/lib/ld.so.1
    total      2864K

    问题:全局变量和全局常量在进程地址空间的位置?

    显然,根据前面的叙述,全局变量在用户的数据段,那么全局常量呢,是数据段吗?
    同样的,可以利用mdb将test5进程挂起,然后用pmap命令求证一下:   
    # mdb test5         
    Loading modules: [ libc.so.1 ]
    > ::sysbp _exit         ; 在系统调用_exit处设置断点
    > :r                    ; 运行程序
    mdb: stop on entry to _exit
    mdb: target stopped at:
    libc.so.1`exit+0×2b:    jae     +0×15           <libc.so.1`exit+0×40>
    >

    此时,程序运行后在_exit处挂起,可以利用pmap在另一个终端内查看test5进程的地址空间了:
    # ps -ef | grep test5
        root  1387  1386  0 02:23:53 pts/1    0:00 test5
        root  1399  1390  0 02:25:03 pts/3    0:00 grep test5
        root  1386  1338  0 02:23:41 pts/1    0:00 mdb test5
    # pmap -F 1387       ; 用pmap强制查看
    1387:   test5
    08044000      16K rwx–    [ stack ]         ; test5的stack
    08050000       4K r-x–  /export/home/asm/L3/test5         ; test5的代码段,起始地址为0×08050000
    08060000       4K rwx–  /export/home/asm/L3/test5         ; test5的数据段,起始地址为0×08060000
    DDAC0000     628K r-x–  /usr/lib/libc.so.1
    DDB6D000      24K rwx–  /usr/lib/libc.so.1
    DDB73000       4K rwx–  /usr/lib/libc.so.1
    DDB80000       4K r-x–  /usr/lib/libdl.so.1
    DDB90000     292K r-x–  /usr/lib/ld.so.1
    DDBE9000      16K rwx–  /usr/lib/ld.so.1
    DDBED000       8K rwx–  /usr/lib/ld.so.1
     total      1000K


    可以看到,由于test5程序没有使用malloc来申请内存,所以没有heap的映射
    前面用mdb观察过这些全局变量和常量的初始化值,它们的地址分别是:
    全局变量i,j,k:
        0×8060904起始的12字节             
    全局变量l,m,n:
        0×8060948起始的12字节
    全局常量o,p,q:       
        0×8050808起始的12字节
   
    显然,根据这些变量的地址,我们可以初步判断出这些变量属于哪个段:
    由于test5数据段起始地址为0×08060000,我们得出结论:全局变量i,j,k,l,m,n属于数据段
    而test5代码段的起始地址为
0×08050000,我们得出结论:全局常量o,p,q属于代码段

    得出这个结论的确有点让人意外:全局常量竟然在代码段。
    却又似乎在情理之中:数据段内存映射后的属性是r/w/x,而常量要求是只读属性,所以在代码段(r-x)就合情合理了。

    问题:为什么这些全局变量地址不是连续的?
    
    很容易注意到,全局变量i,j,k和l,m,n以及全局常量o,p,q是连续声明的,但地址实际上并不连续,而是在3段连续12字节的地址上。
    当然,全局常量属于代码段,所以地址和全局变量是分开的;那么,为什么全局变量也并非连续呢?
    前面谈到数据段实际上是从ELF格式的二进制文件映射到进程的地址空间的,就通过分析ELF文件格式来寻找答案吧:

    # file test5
        test5:          ELF 32-bit LSB executable 80386 Version 1, dynamically linked, not stripped
    # elfdump test5
    
    ELF Header                            ; ELF头信息共52(0×34)字节,具体意义可以参考ELF format的相关文档
    ei_magic:   { 0×7f, E, L, F }         ; ELF的幻数
    ei_class:   ELFCLASS32          ei_data:      ELFDATA2LSB   ; 32位的ELF文件,小端(LSB)编码
    e_machine:  EM_386              e_version:    EV_CURRENT    ; Intel 80386, 版本1
    e_type:     ET_EXEC                                         ; 可执行文件
    e_flags:                     0
    e_entry:             0×8050600  e_ehsize:     52  e_shstrndx:   27   ; 程序入口点_start的地址0×8050600
    e_shoff:                0×1584  e_shentsize:  40  e_shnum:      29   ; Section header table的大小是29*40
    e_phoff:                  0×34  e_phentsize:  32  e_phnum:       5

    Program Header[0]:                    描述Program header table本身在内存中如何映射
        p_vaddr:      0×8050034       p_flags:    [ PF_X  PF_R ]
        p_paddr:      0               p_type:     [ PT_PHDR ]
        p_filesz:     0xa0            p_memsz:    0xa0
        p_offset:     0×34            p_align:    0

    Program Header[1]:                    ; 描述程序装载器的路径名(.interp section)存放在文件的位置
        p_vaddr:      0               p_flags:    [ PF_R ]
        p_paddr:      0               p_type:     [ PT_INTERP ]
        p_filesz:     0×11            p_memsz:    0
        p_offset:     0xd4            p_align:    0

    Program Header[2]:                    描述代码段在内存中如何映射,起始地址0×8050000,大小为 0×814
        p_vaddr:      0×8050000       p_flags:    [ PF_X  PF_R ]
        p_paddr:      0               p_type:     [ PT_LOAD ]
        p_filesz:     0×814           p_memsz:    0×814
        p_offset:     0               p_align:    0×10000

    Program Header[3]:                    ; 描述数据段在内存中如何映射,起始地址0×8060814,大小为0×144
        p_vaddr:      0×8060814       p_flags:    [ PF_X  PF_W  PF_R ]
        p_paddr:      0               p_type:     [ PT_LOAD ]
        p_filesz:     0×118           p_memsz:    0×144
        p_offset:     0×814           p_align:    0×10000

    Program Header[4]:                    描述动态链接信息(.dynamic section)在内存中如何映射
        p_vaddr:      0×8060848       p_flags:    [ PF_X  PF_W  PF_R ]
        p_paddr:      0               p_type:     [ PT_DYNAMIC ]
        p_filesz:     0xb8            p_memsz:    0
        p_offset:     0×848           p_align:    0

    Section Header[1]:  sh_name: .interp  ; 该section保存了程序的解释程序(interpreter)的路径
        sh_addr:      0×80500d4       sh_flags:   [ SHF_ALLOC ]
        sh_size:      0×11            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0xd4            sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×1           

    Section Header[2]:  sh_name: .hash    ; 该section保存着一个符号的哈希表
        sh_addr:      0×80500e8       sh_flags:   [ SHF_ALLOC ]
        sh_size:      0×104           sh_type:    [ SHT_HASH ]
        sh_offset:    0xe8            sh_entsize: 0×4
        sh_link:      3               sh_info:    0
        sh_addralign: 0×4           

    Section Header[3]:  sh_name: .dynsym   ; 该section保存着动态符号表  
        sh_addr:      0×80501ec       sh_flags:   [ SHF_ALLOC ]
        sh_size:      0×200           sh_type:    [ SHT_DYNSYM ]
        sh_offset:    0×1ec           sh_entsize: 0×10
        sh_link:      4               sh_info:    1
        sh_addralign: 0×4           

    Section Header[4]:  sh_name: .dynstr   ; 该section保存着动态连接时需要的字符串
        sh_addr:      0×80503ec       sh_flags:   [ SHF_ALLOC  SHF_STRINGS ]
        sh_size:      0×11a           sh_type:    [ SHT_STRTAB ]
        sh_offset:    0×3ec           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×1           

    Section Header[5]:  sh_name: .SUNW_version   ; 该section是SUN扩展的,保存版本信息
        sh_addr:      0×8050508       sh_flags:   [ SHF_ALLOC ]
        sh_size:      0×20            sh_type:    [ SHT_SUNW_verneed ]
        sh_offset:    0×508           sh_entsize: 0
        sh_link:      4               sh_info:    1
        sh_addralign: 0×4           

    Section Header[6]:  sh_name: .rel.got   ; 该section保存着.got section中部分符号的重定位信息
        sh_addr:      0×8050528       sh_flags:   [ SHF_ALLOC  SHF_INFO_LINK ]
        sh_size:      0×18            sh_type:    [ SHT_REL ]
        sh_offset:    0×528           sh_entsize: 0×8
        sh_link:      3               sh_info:    14
        sh_addralign: 0×4           

    Section Header[7]:  sh_name: .rel.bss   ; 该section保存着.bss section中部分符号的重定位信息
        sh_addr:      0×8050540       sh_flags:   [ SHF_ALLOC  SHF_INFO_LINK ]
        sh_size:      0×8             sh_type:    [ SHT_REL ]
        sh_offset:    0×540           sh_entsize: 0×8
        sh_link:      3               sh_info:    22
        sh_addralign: 0×4           

    Section Header[8]:  sh_name: .rel.plt   ; 该section保存着.plt section中部分符号的重定位信息
        sh_addr:      0×8050548       sh_flags:   [ SHF_ALLOC  SHF_INFO_LINK ]
        sh_size:      0×38            sh_type:    [ SHT_REL ]
        sh_offset:    0×548           sh_entsize: 0×8
        sh_link:      3               sh_info:    9
        sh_addralign: 0×4           

    Section Header[9]:  sh_name: .plt   ; 该section保存着过程连接表(Procedure Linkage Table)
        sh_addr:      0×8050580       sh_flags:   [ SHF_ALLOC  SHF_EXECINSTR ]
        sh_size:      0×80            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×580           sh_entsize: 0×10
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[10]:  sh_name: .text   ; 该section保存着程序的正文部分,即可执行指令
        sh_addr:      0×8050600       sh_flags:   [ SHF_ALLOC  SHF_EXECINSTR ]
        sh_size:      0×1ec           sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×600           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[11]:  sh_name: .init    ; 该section保存着可执行指令,它构成了进程的初始化代码
        sh_addr:      0×80507ec       sh_flags:   [ SHF_ALLOC  SHF_EXECINSTR ]
        sh_size:      0xd             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×7ec           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×1           

    Section Header[12]:  sh_name: .fini    ; 该section保存着可执行指令,它构成了进程的终止代码
        sh_addr:      0×80507f9       sh_flags:   [ SHF_ALLOC  SHF_EXECINSTR ]
        sh_size:      0×8             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×7f9           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×1           

    Section Header[13]:  sh_name: .rodata   ; 该section保存着只读数据
        sh_addr:      0×8050804       sh_flags:   [ SHF_ALLOC ]
        sh_size:      0×10            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×804           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[14]:  sh_name: .got   ; 该section保存着全局的偏移量表
        sh_addr:      0×8060814       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0×34            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×814           sh_entsize: 0×4
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[15]:  sh_name: .dynamic   ; 该section保存着动态连接的信息
        sh_addr:      0×8060848       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0xb8            sh_type:    [ SHT_DYNAMIC ]
        sh_offset:    0×848           sh_entsize: 0×8
        sh_link:      4               sh_info:    0
        sh_addralign: 0×4           

    Section Header[16]:  sh_name: .data      ; 该sections保存着初始化了的数据
        sh_addr:      0×8060900       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0×10            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×900           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[17]:  sh_name: .ctors
        sh_addr:      0×8060910       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0×8             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×910           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[18]:  sh_name: .dtors
        sh_addr:      0×8060918       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0×8             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×918           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[19]:  sh_name: .eh_frame
        sh_addr:      0×8060920       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0×4             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×920           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[20]:  sh_name: .jcr
        sh_addr:      0×8060924       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0×4             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×924           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[21]:  sh_name: .data.rel.local
        sh_addr:      0×8060928       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0×4             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×928           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[22]:  sh_name: .bss   ; 该sectiopn保存着未初始化的数据
        sh_addr:      0×806092c       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0×2c            sh_type:    [ SHT_NOBITS ]   ; 指示不占据ELF空间sh_size是内存大小
        sh_offset:    0×92c           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[23]:  sh_name: .symtab   ; 该section保存着一个符号表
        sh_addr:      0               sh_flags:   0
        sh_size:      0×540           sh_type:    [ SHT_SYMTAB ]
        sh_offset:    0×92c           sh_entsize: 0×10
        sh_link:      24              sh_info:    53
        sh_addralign: 0×4           

    Section Header[24]:  sh_name: .strtab   ; 该section保存着字符串表
        sh_addr:      0               sh_flags:   [ SHF_STRINGS ]
        sh_size:      0×20b           sh_type:    [ SHT_STRTAB ]
        sh_offset:    0xe6c           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×1           

    Section Header[25]:  sh_name: .comment   ; 该section保存着版本控制信息
        sh_addr:      0               sh_flags:   0
        sh_size:      0×24d           sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×1077          sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×1           

    Section Header[26]:  sh_name: .stab.index
        sh_addr:      0               sh_flags:   0
        sh_size:      0×24            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0×12c4          sh_entsize: 0xc
        sh_link:      0               sh_info:    0
        sh_addralign: 0×4           

    Section Header[27]:  sh_name: .shstrtab   ; 该section保存着section名称
        sh_addr:      0               sh_flags:   [ SHF_STRINGS ]
        sh_size:      0xdc            sh_type:    [ SHT_STRTAB ]
        sh_offset:    0×12e8          sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×1           

    Section Header[28]:  sh_name: .stab.indexstr
        sh_addr:      0               sh_flags:   0
        sh_size:      0×1c0           sh_type:    [ SHT_STRTAB ]
        sh_offset:    0×13c4          sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0×1           

    Interpreter:
        /usr/lib/ld.so.1

    Version Needed Section:  .SUNW_version
            file                        version
            libc.so.1                   SYSVABI_1.3          

    Symbol Table:  .dynsym                     ; 动态解析和链接所需的符号表
     index    value       size     type bind oth ver shndx       name
       [0]  0×00000000 0×00000000  NOTY LOCL  D    0 UNDEF       
       [1]  0×080507ec 0×0000000d  FUNC GLOB  D    0 .init       _init
       [2]  0×08050804 0×00000004  OBJT GLOB  D    0 .rodata     _lib_version
       [3]  0×08050580 0×00000000  OBJT GLOB  D    0 .plt        _PROCEDURE_LINKAGE_TABLE_
       [4]  0×08050600 0×00000075  FUNC GLOB  D    0 .text       _start
       [5]  0×08060900 0×00000000  OBJT GLOB  D    0 .data       __dso_handle
       [6]  0×08060848 0×00000000  OBJT GLOB  D    0 .dynamic    _DYNAMIC
       [7]  0×00000000 0×00000000  NOTY WEAK  D    0 UNDEF       __deregister_frame_info_bases
       [8]  0×08050814 0×00000000  OBJT GLOB  D    0 .rodata     _etext
       [9]  0×08060958 0×00000000  OBJT GLOB  D    0 .bss        _end
      [10]  0×08050590 0×00000000  FUNC WEAK  D    0 UNDEF       _cleanup
      [11]  0×08050675 0×00000001  FUNC WEAK  D    0 .text       _mcount
      [12]  0×08060904 0×00000004  OBJT GLOB  D    0 .data       i
      [13]  0×08060908 0×00000004  OBJT GLOB  D    0 .data       j
      [14]  0×0806090c 0×00000004  OBJT GLOB  D    0 .data       k
      [15]  0×08060948 0×00000004  OBJT GLOB  D    0 .bss        l
      [16]  0×08060954 0×00000004  OBJT GLOB  D    0 .bss        _environ
      [17]  0×08060814 0×00000000  OBJT GLOB  D    0 .got        _GLOBAL_OFFSET_TABLE_
      [18]  0×0806094c 0×00000004  OBJT GLOB  D    0 .bss        m
      [19]  0×0806092c 0×00000000  OBJT GLOB  D    0 .data.rel.l _edata
      [20]  0×08060954 0×00000004  OBJT WEAK  D    0 .bss        environ
      [21]  0×080507f9 0×00000008  FUNC GLOB  D    0 .fini       _fini
      [22]  0×080505a0 0×00000000  FUNC GLOB  D    0 UNDEF       atexit
      [23]  0×08060950 0×00000004  OBJT GLOB  D    0 .bss        n
      [24]  0×08050808 0×00000004  OBJT GLOB  D    0 .rodata     o
      [25]  0×0805080c 0×00000004  OBJT GLOB  D    0 .rodata     p
      [26]  0×00000000 0×00000000  NOTY WEAK  D    0 UNDEF       _Jv_RegisterClasses
      [27]  0×08050810 0×00000004  OBJT GLOB  D    0 .rodata     q
      [28]  0×080505b0 0×00000000  FUNC GLOB  D    0 UNDEF       __fpstart
      [29]  0×08050753 0×00000065  FUNC GLOB  D    0 .text       main
      [30]  0×00000000 0×00000000  NOTY WEAK  D    0 UNDEF       __register_frame_info_bases
      [31]  0×080505c0 0×00000000  FUNC GLOB  D    0 UNDEF       exit

    Symbol Table:  .symtab                     ; 程序链接所需的符号表
     index    value       size     type bind oth ver shndx       name
       [0]  0×00000000 0×00000000  NOTY LOCL  D    0 UNDEF       
       [1]  0×00000000 0×00000000  FILE LOCL  D    0 ABS         test5
       [2]  0×080500d4 0×00000000  SECT LOCL  D    0 .interp     
       [3]  0×080500e8 0×00000000  SECT LOCL  D    0 .hash       
       [4]  0×080501ec 0×00000000  SECT LOCL  D    0 .dynsym     
       [5]  0×080503ec 0×00000000  SECT LOCL  D    0 .dynstr     
       [6]  0×08050508 0×00000000  SECT LOCL  D    0 .SUNW_versi 
       [7]  0×08050528 0×00000000  SECT LOCL  D    0 .rel.got    
       [8]  0×08050540 0×00000000  SECT LOCL  D    0 .rel.bss    
       [9]  0×08050548 0×00000000  SECT LOCL  D    0 .rel.plt    
      [10]  0×08050580 0×00000000  SECT LOCL  D    0 .plt        
      [11]  0×08050600 0×00000000  SECT LOCL  D    0 .text       
      [12]  0×080507ec 0×00000000  SECT LOCL  D    0 .init       
      [13]  0×080507f9 0×00000000  SECT LOCL  D    0 .fini       
      [14]  0×08050804 0×00000000  SECT LOCL  D    0 .rodata     
      [15]  0×08060814 0×00000000  SECT LOCL  D    0 .got        
      [16]  0×08060848 0×00000000  SECT LOCL  D    0 .dynamic    
      [17]  0×08060900 0×00000000  SECT LOCL  D    0 .data       
      [18]  0×08060910 0×00000000  SECT LOCL  D    0 .ctors      
      [19]  0×08060918 0×00000000  SECT LOCL  D    0 .dtors      
      [20]  0×08060920 0×00000000  SECT LOCL  D    0 .eh_frame   
      [21]  0×08060924 0×00000000  SECT LOCL  D    0 .jcr        
      [22]  0×08060928 0×00000000  SECT LOCL  D    0 .data.rel.l 
      [23]  0×0806092c 0×00000000  SECT LOCL  D    0 .bss        
      [24]  0×00000000 0×00000000  SECT LOCL  D    0 .symtab     
      [25]  0×00000000 0×00000000  SECT LOCL  D    0 .strtab     
      [26]  0×00000000 0×00000000  SECT LOCL  D    0 .comment    
      [27]  0×00000000 0×00000000  SECT LOCL  D    0 .stab.index 
      [28]  0×00000000 0×00000000  SECT LOCL  D    0 .shstrtab   
      [29]  0×00000000 0×00000000  SECT LOCL  D    0 .stab.index 
      [30]  0×08050000 0×00000000  OBJT LOCL  D    0 .interp     _START_
      [31]  0×08060958 0×00000000  OBJT LOCL  D    0 .bss        _END_
      [32]  0×00000000 0×00000000  FILE LOCL  D    0 ABS         crt1.s
      [33]  0×00000000 0×00000000  FILE LOCL  D    0 ABS         crti.s
      [34]  0×00000000 0×00000000  FILE LOCL  D    0 ABS         values-Xa.c
      [35]  0×00000000 0×00000000  FILE LOCL  D    0 ABS         crtstuff.c
      [36]  0×08060910 0×00000000  OBJT LOCL  D    0 .ctors      __CTOR_LIST__
      [37]  0×08060918 0×00000000  OBJT LOCL  D    0 .dtors      __DTOR_LIST__
      [38]  0×08060920 0×00000000  OBJT LOCL  D    0 .eh_frame   __EH_FRAME_BEGIN__
      [39]  0×08060924 0×00000000  OBJT LOCL  D    0 .jcr        __JCR_LIST__
      [40]  0×08060928 0×00000000  OBJT LOCL  D    0 .data.rel.l p.0
      [41]  0×0806092c 0×00000001  OBJT LOCL  D    0 .bss        completed.1
      [42]  0×08050678 0×00000000  FUNC LOCL  D    0 .text       __do_global_dtors_aux
      [43]  0×08060930 0×00000018  OBJT LOCL  D    0 .bss        object.2
      [44]  0×080506e4 0×00000000  FUNC LOCL  D    0 .text       frame_dummy
      [45]  0×00000000 0×00000000  FILE LOCL  D    0 ABS         test5.c
      [46]  0×00000000 0×00000000  FILE LOCL  D    0 ABS         crtstuff.c
      [47]  0×08060914 0×00000000  OBJT LOCL  D    0 .ctors      __CTOR_END__
      [48]  0×0806091c 0×00000000  OBJT LOCL  D    0 .dtors      __DTOR_END__
      [49]  0×08060920 0×00000000  OBJT LOCL  D    0 .eh_frame   __FRAME_END__
      [50]  0×08060924 0×00000000  OBJT LOCL  D    0 .jcr        __JCR_END__
      [51]  0×080507b8 0×00000000  FUNC LOCL  D    0 .text       __do_global_ctors_aux
      [52]  0×00000000 0×00000000  FILE LOCL  D    0 ABS         crtn.o
      [53]  0×080507ec 0×0000000d  FUNC GLOB  D    0 .init       _init
      [54]  0×08050804 0×00000004  OBJT GLOB  D    0 .rodata     _lib_version
      [55]  0×08050580 0×00000000  OBJT GLOB  D    0 .plt        _PROCEDURE_LINKAGE_TABLE_
      [56]  0×08050600 0×00000075  FUNC GLOB  D    0 .text       _start
      [57]  0×08060900 0×00000000  OBJT GLOB  D    0 .data       __dso_handle
      [58]  0×08060848 0×00000000  OBJT GLOB  D    0 .dynamic    _DYNAMIC
      [59]  0×00000000 0×00000000  NOTY WEAK  D    0 UNDEF       __deregister_frame_info_bases
      [60]  0×08050814 0×00000000  OBJT GLOB  D    0 .rodata     _etext
      [61]  0×08060958 0×00000000  OBJT GLOB  D    0 .bss        _end
      [62]  0×08050590 0×00000000  FUNC WEAK  D    0 UNDEF       _cleanup
      [63]  0×08050675 0×00000001  FUNC WEAK  D    0 .text       _mcount
      [64]  0×08060904 0×00000004  OBJT GLOB  D    0 .data       i
      [65]  0×08060908 0×00000004  OBJT GLOB  D    0 .data       j
      [66]  0×0806090c 0×00000004  OBJT GLOB  D    0 .data       k
      [67]  0×08060948 0×00000004  OBJT GLOB  D    0 .bss        l
      [68]  0×08060954 0×00000004  OBJT GLOB  D    0 .bss        _environ
      [69]  0×08060814 0×00000000  OBJT GLOB  D    0 .got        _GLOBAL_OFFSET_TABLE_
      [70]  0×0806094c 0×00000004  OBJT GLOB  D    0 .bss        m
      [71]  0×0806092c 0×00000000  OBJT GLOB  D    0 .data.rel.l _edata
      [72]  0×08060954 0×00000004  OBJT WEAK  D    0 .bss        environ
      [73]  0×080507f9 0×00000008  FUNC GLOB  D    0 .fini       _fini
      [74]  0×080505a0 0×00000000  FUNC GLOB  D    0 UNDEF       atexit
      [75]  0×08060950 0×00000004  OBJT GLOB  D    0 .bss        n
      [76]  0×08050808 0×00000004  OBJT GLOB  D    0 .rodata     o
      [77]  0×0805080c 0×00000004  OBJT GLOB  D    0 .rodata     p
      [78]  0×00000000 0×00000000  NOTY WEAK  D    0 UNDEF       _Jv_RegisterClasses
      [79]  0×08050810 0×00000004  OBJT GLOB  D    0 .rodata     q
      [80]  0×080505b0 0×00000000  FUNC GLOB  D    0 UNDEF       __fpstart
      [81]  0×08050753 0×00000065  FUNC GLOB  D    0 .text       main
      [82]  0×00000000 0×00000000  NOTY WEAK  D    0 UNDEF       __register_frame_info_bases
      [83]  0×080505c0 0×00000000  FUNC GLOB  D    0 UNDEF       exit

    Hash Section:  .hash  
    bucket    symndx    name
         0  [1]         _init
         1  [2]         _lib_version
            [3]         _PROCEDURE_LINKAGE_TABLE_
         2  [4]         _start
            [5]         __dso_handle
         4  [6]         _DYNAMIC
         9  [7]         __deregister_frame_info_bases
            [8]         _etext
        10  [9]         _end
        12  [10]        _cleanup
            [11]        _mcount
            [12]        i
        13  [13]        j
        14  [14]        k
        15  [15]        l
        16  [16]        _environ
            [17]        _GLOBAL_OFFSET_TABLE_
            [18]        m
            [19]        _edata
            [20]        environ
        17  [21]        _fini
            [22]        atexit
            [23]        n
        18  [24]        o
        19  [25]        p
        20  [26]        _Jv_RegisterClasses
            [27]        q
        25  [28]        __fpstart
        26  [29]        main
        29  [30]        __register_frame_info_bases
            [31]        exit

        13  buckets contain        0 symbols
        10  buckets contain        1 symbols
         5  buckets contain        2 symbols
         2  buckets contain        3 symbols
         1  buckets contain        5 symbols
        31  buckets               31 symbols (globals)

    Global Offset Table: 13 entries
     ndx     addr      value    reloc              addend   symbol
    [00000]  08060814  08060848 R_386_NONE         00000000 
    [00001]  08060818  00000000 R_386_NONE         00000000 
    [00002]  0806081c  00000000 R_386_NONE         00000000 
    [00003]  08060820  08050596 R_386_JMP_SLOT     00000000 _cleanup
    [00004]  08060824  080505a6 R_386_JMP_SLOT     00000000 atexit
    [00005]  08060828  080505b6 R_386_JMP_SLOT     00000000 __fpstart
    [00006]  0806082c  080505c6 R_386_JMP_SLOT     00000000 exit
    [00007]  08060830  00000000 R_386_GLOB_DAT     00000000 __deregister_frame_info_bases
    [00008]  08060834  080505d6 R_386_JMP_SLOT     00000000 __deregister_frame_info_bases
    [00009]  08060838  00000000 R_386_GLOB_DAT     00000000 __register_frame_info_bases
    [00010]  0806083c  00000000 R_386_GLOB_DAT     00000000 _Jv_RegisterClasses
    [00011]  08060840  080505e6 R_386_JMP_SLOT     00000000 _Jv_RegisterClasses
    [00012]  08060844  080505f6 R_386_JMP_SLOT     00000000 __register_frame_info_bases

    Relocation: .rel.got
        type                       offset              section   with respect to
        R_386_GLOB_DAT             0×8060830             .rel.got       __deregister_frame_info_bases 
        R_386_GLOB_DAT             0×8060838             .rel.got       __register_frame_info_bases 
        R_386_GLOB_DAT             0×806083c             .rel.got       _Jv_RegisterClasses 

    Relocation: .rel.bss
        type                       offset              section   with respect to
        R_386_COPY                 0×8060954             .rel.bss       _environ 

    Relocation: .rel.plt
        type                       offset              section   with respect to
        R_386_JMP_SLOT             0×8060820             .rel.plt       _cleanup 
        R_386_JMP_SLOT             0×8060824             .rel.plt       atexit 
        R_386_JMP_SLOT             0×8060828             .rel.plt       __fpstart 
        R_386_JMP_SLOT             0×806082c             .rel.plt       exit 
        R_386_JMP_SLOT             0×8060834             .rel.plt       __deregister_frame_info_bases 
        R_386_JMP_SLOT             0×8060840             .rel.plt       _Jv_RegisterClasses 
        R_386_JMP_SLOT             0×8060844             .rel.plt       __register_frame_info_bases 

    Dynamic Section:  .dynamic
     index  tag               value
       [0]  NEEDED           0×104             libc.so.1
       [1]  INIT             0×80507ec         
       [2]  FINI             0×80507f9         
       [3]  HASH             0×80500e8         
       [4]  STRTAB           0×80503ec         
       [5]  STRSZ            0×11a             
       [6]  SYMTAB           0×80501ec         
       [7]  SYMENT           0×10              
       [8]  CHECKSUM         0×6a10            
       [9]  VERNEED          0×8050508         
      [10]  VERNEEDNUM       0×1               
      [11]  PLTRELSZ         0×38              
      [12]  PLTREL           0×11              
      [13]  JMPREL           0×8050548         
      [14]  REL              0×8050528         
      [15]  RELSZ            0×58              
      [16]  RELENT           0×8               
      [17]  DEBUG            0                 
      [18]  FEATURE_1        0×1               [ PARINIT ]
      [19]  FLAGS            0                 0
      [20]  FLAGS_1          0                 0
      [21]  PLTGOT           0×8060814


    利用elfdump可以查看ELF文件格式的详细信息,可以在符号表.dynsym和.symtab中找到程序中定义的全局变量和全局常量:
       i,j,k在.data section中
       l,m,n在.bss section中
       o,p,q在.rodata section中
    
    概念:ELF(Executable and Linking Format) 可执行连接格式

    ELF格式是UNIX系统实验室(USL)作为应用程序二进制接口(Application Binary Interface,ABI)而开发和发布的。
    目前,ELF格式是Unix/Linux平台上应用最广泛的二进制工业标准之一

    下图从不同视角给出了ELF文件的一般格式:

    Linking 视角                       Execution 视角
    ============                      ==============
    ELF header                        ELF header
    Program header table (optional)   Program header table
    Section 1                         Segment 1
    …                               Segment 2
    Section n                         …
    Section header table              Section header table (optional)

    图 3-2 ELF文件格式 摘自 EXECUTABLE AND LINKABLE FORMAT (ELF)

    可以根据test5 ELF文件的Program header table和Section header table中文件偏移量的信息描绘出test5的内容:

    entry name      起始文件偏移+实际大小=下个entry起始偏移
    ——————————————————-
    ELF header                  0×0+0×34=0×34
    Program header              0×34+0xa0=0xd4
    Section 1  .interp          0xd4+0×11=0xe5
    000                         0xe5+0×3=0xe8
    Section 2  .hash            0xe8+0×104=0×1ec
    Section 3  .dynsym          0×1ec+0×200=0×3ec
    Section 4  .dynstr          0×3ec+0×11a=0×506
    00                          0×506+0×2=0×508
    Section 5  .SUNW_version    0×508+0×20=0×528
    Section 6  .rel.got         0×528+0×18=0×540
    Section 7  .rel.bss         0×540+0×8=0×548   
    Section 8  .rel.plt         0×548+0×38=0×580
    Section 9  .plt             0×580+0×80=0×600     
    Section 10 .text            0×600+0×1ec=0×7ec              
    Section 11 .init            0×7ec+0xd=0×7f9   
    Section 12 .fini            0×7f9+0×8=0×801
    000                         0×801+0×3=0×804
    Section 13 .rodata          0×804+0×10=0×814
    Section 14 .got             0×814+0×34=0×848
    Section 15 .dynamic         0×848+0xb8=900
    Section 16 .data            0×900+0×10=0×910
    Section 17 .ctors           0×910+0×8=0×918
    Section 18 .dtors           0×918+0×8=0×920
    Section 19 .eh_frame        0×920+0×4=0×924
    Section 20 .jcr             0×924+0×4=0×928
    Section 21 .data.rel.local  0×928+0×4=0×92c
    Section 22 .bss          0×92c+0×0=0×958
    Section 23 .symtab          0×92c+0×540=0xe6c
    Section 24 .strtab          0xe6c+0×20b=1077
    Section 25 .comment         0×1077+0×24d=0×12c4
    Section 26 .stab.index      0×12c4+0×24=0×12e8
    Section 27 .shstrtab        0×12e8+0xdc=0×13c4
    Section 28 .stab.indexstr   0×13c4+0×1c0=0×1584
    Section header table        0×1584+0×488=0×1a0c      ; 29×40=1160=0×488 这是根据Elf header的信息算得的
    ——————————————————
    图 3-3 test5的ELF文件格式

    # ls -al test5
    -rwxr-xr-x    1 root     other        6668 2004-12-19 06:56 test5

    可以看到test5的大小是0×1a0c字节,即6688字节。
    可以看到,.bss section用于保存未初始化的全局变量,因此不占据ELF文件空间;
    ELF文件装入时,会按照Section header table 22中的.bss的相关属性,为.bss映射相应大小的内存空间,并初始化为0


    ELF文件的Program header table描述了如何将ELF装入内存:
        Program Header 2 描述了用户代码段的起始地址和大小: 0×8050000+0×814=0×8050814
        Program Header 3 描述了用户数据段的起始地址和大小: 0×8060814+0×144=0×8060958
    
    问题:为何前面pmap得到的结果是数据段从0×8060000开始,而ELF文件的Program Header 3却是从0×8060814开始?
    
    如果查一下ELF文件的格式规范的话,就能找到答案:

        Program Header[2]:
            p_vaddr:      0×8050000       p_flags:    [ PF_X  PF_R ]
            p_paddr:      0               p_type:     [ PT_LOAD ]
            p_filesz:     0×814           p_memsz:    0×814
            p_offset:     0               p_align:    0×10000   ; 指定64K页对齐

        Program Header[3]:
            p_vaddr:      0×8060814       p_flags:    [ PF_X  PF_W  PF_R ]
            p_paddr:      0               p_type:     [ PT_LOAD ]
            p_filesz:     0×118           p_memsz:    0×144
            p_offset:     0×814           p_align:    0×10000   ; 指定64K页对齐

    p_align指定了映射代码段和数据段的时候,必须按照64K页对齐的方式,即起始映射地址必须以0000结尾。
    代码段的起始地址正好满足该条件,如果数据段不考虑页对齐的话,应该紧跟代码段的下一个字节即0×8050814开始。
    但是正因为64K页对齐的缘故,只能从最接近0×8050814的64K页对齐地址0×8060000开始。
    而实际上,在对ELF进行内存映射时,是按页为单位进行映射的,test5的大小是0×1a0c,不足1页大小,代码段和数据段都是在第1页。
    映射发生时,先映射这第1页到0×8050000的代码段,属性read/exec;再映射这1页到0×8060000的数据段,属性 read/write/exec。
    这样,实际上的数据段起始地址就是0×8060000+0×814=0×8060814

    问题:为什么要页对齐 page align?

    首先,内存映射是以页为最小单位的,这是因为Solaris的内存管理是页式内存管理(目前大多数现代OS都是如此);
    其次,代码段和数据段因为有不同的权限要求(代码段要求只读),因此必须进行2次映射;
    最后,就是效率的要求;

    尽管ELF文件的映射是solaris内核中seg_vn段驱动程序来完成的,但仍可以通过系统调用mmap(2)来学习基本的内存映射常识。
    对于其它系统,如Linux情况也类似。
    那为何在本例中是要求64K页对齐呢?答案是:这也是Solaris在Sparc上的页对齐要求

    概念:ELF文件loading

    根据Program header table及section header table描绘出test5代码段及数据段的内部情况就很容易了: 

    entry name          起始地址+实际大小=下个entry起始地址
    =================User Text============================ 0×8050000
    ELF header                  0×8050000+0×34=0×8050034
    Program header              0×8050034+0xa0=0×80500d4
    Section 1  .interp          0×80500d4+0×11=0×80500e5
    0                           0×80500e5+0×3=0×80500e8       ; 3字节0填充
    Section 2  .hash            0×80500e8+0×104=0×80501ec
    Section 3  .dynsym          0×80501ec+0×200=0×80503ec
    Section 4  .dynstr          0×80503ec+0×11a=0×8050506
    0                           0×8050506+0×2=0×8050508       ; 2字节0填充
    Section 5  .SUNW_version    0×8050508+0×20=0×8050528
    Section 6  .rel.got         0×8050528+0×18=0×8050540
    Section 7  .rel.bss         0×8050540+0×8=0×8050548   
    Section 8  .rel.plt         0×8050548+0×38=0×8050580
    Section 9  .plt             0×8050580+0×80=0×8050600     
    Section 10 .text            0×8050600+0×1ec=0×80507ec              
    Section 11 .init            0×80507ec+0xd=0×80507f9   
    Section 12 .fini            0×80507f9+0×8=0×8050801
    0                           0×8050801+0×3=0×8050804       ; 3字节0填充
    Section 13 .rodata       0×8050804+0×10=0×8050814   ; o,p,q在代码段的. rodata section中
    —————————————————–
    Section 14 .got             0×8050814+0×34=0×8050848      ; 这是代码段的第一页也是最后一页,
    Section 15 .dynamic         0×8050848+0xb8=8050900         ; 因此数据段的内容会追加到代码段最后一页末尾
    Section 16 .data            0×8050900+0×10=0×8050910
    Section 17 .ctors           0×8050910+0×8=0×8050918
    Section 18 .dtors           0×8050918+0×8=0×8050920
    Section 19 .eh_frame        0×8050920+0×4=0×8050924
    Section 20 .jcr             0×8050924+0×4=0×8050928
    Section 21 .data.rel.local  0×8050928+0×4=0×805092c
    Section 22 .bss             0×805092c+0×2c=0×8050958
    pending data                0×8050958+0×6a8=8051000         ; 页末是0×6a8字节填充
    ======================================================


                        no mapping


    =================User Data============================ 0×8060000
    ELF header                  0×8060000+0×34=0×8060034     ; 这是数据段的最后一页也是第一页,      
    Program header              0×8060034+0xa0=0×80600d4      ; 因此代码段的内容会追加到数据段第一页之前
    Section 1  .interp          0×80600d4+0×11=0×80600e5
    0                           0×80600e5+0×3=0×80600e8       ; 3字节0填充
    Section 2  .hash            0×80600e8+0×104=0×80601ec
    Section 3  .dynsym          0×80601ec+0×200=0×80603ec
    Section 4  .dynstr          0×80603ec+0×11a=0×8060506
    0                           0×8060506+0×2=0×8060508       ; 2字节0填充
    Section 5  .SUNW_version    0×8060508+0×20=0×8060528
    Section 6  .rel.got         0×8060528+0×18=0×8060540
    Section 7  .rel.bss         0×8060540+0×8=0×8060548   
    Section 8  .rel.plt         0×8060548+0×38=0×8060580
    Section 9  .plt             0×8060580+0×80=0×8060600     
    Section 10 .text            0×8060600+0×1ec=0×80607ec              
    Section 11 .init            0×80607ec+0xd=0×80607f9   
    Section 12 .fini            0×80607f9+0×8=0×8060801
    0                           0×8060801+0×3=0×8060804       ; 3字节0填充
    Section 13 .rodata          0×8060804+0×10=0×8060814
    —————————————————–
    Section 14 .got             0×8060814+0×34=0×8060848
    Section 15 .dynamic         0×8060848+0xb8=8060900
    Section 16 .data         0×8060900+0×10=0×8060910   ; i,j,k在数据段的.data section中
    Section 17 .ctors           0×8060910+0×8=0×8060918
    Section 18 .dtors           0×8060918+0×8=0×8060920
    Section 19 .eh_frame        0×8060920+0×4=0×8060924
    Section 20 .jcr             0×8060924+0×4=0×8060928
    Section 21 .data.rel.local  0×8060928+0×4=0×806092c
    Section 22 .bss          0×806092c+0×2c=0×8060958   ; l,m,n在数据段的.bss section中
    0                           0×8060958+0×6a8=8061000        ; 页末是0×6a8字节0填充
    =======================================================
    图 3-4 test5的代码段和数据段内部结构

    
    以下各section因为section header table中的sh_flags不包含SHF_ALLOC,因此不会被映射到内存:
        Section 23 .symtab
            符号表,不映射到内存,strip命令可以去除
        Section 24 .strtab
            字符串表,主要保存着和 .symtab的名字字符串以及其它字符串,不映射到内存,strip命令可以去除
        Section 25 .comment
            保存着该二进制程序相关的信息,格式内容决定于二进制程序本身,不映射到内存,strip命令保留该section
        Section 26 .stab.index
            保存着该二进制程序相关的信息,格式内容决定于二进制程序本身,不映射到内存,strip命令可以去除
        Section 27 .shstrtab
            字符串表,只保存section name,不映射到内存,strip命令保留该section
        Section 28 .stab.indexstr
            字符串表,不映射到内存,strip命令可以去除



    有了图 3-4,就能清楚的找到全局变量和全局常量的在数据段和代码段的精确位置,也就能回答前面提出的问题:
       全局常量o,p,q属于代码段的.rodata section,这个section因为属于代码段而具有只读属性,用于保存只读数据
       全局变量i,j,k属于数据段的.data section,用于保存有初值的全局变量,这个section同时在ELF文件和内存中占据空间
       全局变量l,m,n属于代码段的.bss section,用于保存未初始化的全局变量,这个section占据内存空间而不占据ELF文件空间
    由于分属于几个不同的section,地址空间必定不连续了
       
    下面把ELF文件的装入归纳如下:

       * 第一个代码段页面包含了 ELF header、Program header table以及其他信息
       * 最后的代码段页末尾追加一个数据段开始的拷贝
       * 第一个数据段页面前有一个代码段结束的拷贝
       * 最后的数据段页面也许会包含与正在运行的进程无关的文件信息


2. ELF文件装载的验证

    ELF文件本身的格式可以直接用工具观察二进制文件,下面的命令可以观察到.comment section的相关内容:
    bash-2.05# od -A x -c -j 0×1077 -N 0×24d test5 
    0000000   G   N   U       C       c   r   t   1   .   s  /0   a   s   :
    0000010       F   o   r   t   e       D   e   v   e   l   o   p   e   r
    0000020       7       C   o   m   p   i   l   e   r       C   o   m   m
    0000030   o   n       7   .   0       I   A   3   2   -   i   t   e   a
    0000040   m       2   0   0   1   /   1   2   /   1   2  /0   G   N   U
    0000050       C       c   r   t   i   .   s  /0   a   s   :       F   o
    0000060   r   t   e       D   e   v   e   l   o   p   e   r       7    
    0000070   C   o   m   p   i   l   e   r       C   o   m   m   o   n    
    0000080   7   .   0       I   A   3   2   -   i   t   e   a   m       2
    0000090   0   0   1   /   1   2   /   1   2  /0  /0   @   (   #   )   S
    00000a0   u   n   O   S       5   .   9       G   e   n   e   r   i   c
    00000b0   _   1   1   2   2   3   4   -   0   3       N   o   v   e   m
    00000c0   b   e   r       2   0   0   2  /0   G   C   C   :       (   G
    00000d0   N   U   )       3   .   3   .   2  /0   a   s   :       F   o
    00000e0   r   t   e       D   e   v   e   l   o   p   e   r       7    
    00000f0   C   o   m   p   i   l   e   r       C   o   m   m   o   n    
    0000100   7   .   0       I   A   3   2   -   i   t   e   a   m       2
    0000110   0   0   1   /   1   2   /   1   2  /0   G   C   C   :       (
    0000120   G   N   U   )       3   .   3   .   2  /0   a   s   :       F
    0000130   o   r   t   e       D   e   v   e   l   o   p   e   r       7
    0000140       C   o   m   p   i   l   e   r       C   o   m   m   o   n
    0000150       7   .   0       I   A   3   2   -   i   t   e   a   m    
    0000160   2   0   0   1   /   1   2   /   1   2  /0   G   C   C   :    
    0000170   (   G   N   U   )       3   .   3   .   2  /0   a   s   :    
    0000180   F   o   r   t   e       D   e   v   e   l   o   p   e   r    
    0000190   7       C   o   m   p   i   l   e   r       C   o   m   m   o
    00001a0   n       7   .   0       I   A   3   2   -   i   t   e   a   m
    00001b0       2   0   0   1   /   1   2   /   1   2  /0   G   N   U    
    00001c0   C       c   r   t   n   .   o  /0   a   s   :       F   o   r
    00001d0   t   e       D   e   v   e   l   o   p   e   r       7       C
    00001e0   o   m   p   i   l   e   r       C   o   m   m   o   n       7
    00001f0   .   0       I   A   3   2   -   i   t   e   a   m       2   0
    0000200   0   1   /   1   2   /   1   2  /0   l   d   :       S   o   f
    0000210   t   w   a   r   e       G   e   n   e   r   a   t   i   o   n
    0000220       U   t   i   l   i   t   i   e   s       -       S   o   l
    0000230   a   r   i   s       L   i   n   k       E   d   i   t   o   r
    0000240   s   :       5   .   9   -   1   .   2   7   6  /0
    000024d

    显然,.comment section的内容是编译器和链接器的版本信息。

    观察ELF载入内存后的情况则需要该ELF程序的进程在系统中挂起,才能读到相关内容。
    利用mdb可以查看装入内存中的test5的代码段和数据段值,下面就验证一下图 3-4:

    # mdb test5
    Loading modules: [ libc.so.1 ]
    > main::dis
    main:                           pushl   %ebp
    main+1:                         movl    %esp,%ebp
    main+3:                         subl    $8,%esp
    main+6:                         andl    $0xf0,%esp
    main+9:                         movl    $0,%eax
    main+0xe:                       subl    %eax,%esp
    main+0×10:                      movl    $4,0×8060948
    main+0×1a:                      movl    $5,0×806094c
    main+0×24:                      movl    $6,0×8060950
    main+0×2e:                      movl    0×8060908,%eax
    main+0×33:                      addl    0×8060904,%eax
    main+0×39:                      addl    0×806090c,%eax
    main+0×3f:                      addl    0×8060948,%eax
    main+0×45:                      addl    0×806094c,%eax
    main+0×4b:                      addl    0×8060950,%eax
    main+0×51:                      addl    0×8050808,%eax
    main+0×57:                      addl    0×805080c,%eax
    main+0×5d:                      addl    0×8050810,%eax
    main+0×63:                      leave
    main+0×64:                      ret
    > main+0×2e:b            ; 设置断点
    > :r                     ; 运行
    mdb: stop at main+0×2e
    mdb: target stopped at:
    main+0×2e:      movl    0×8060908,%eax
    > 0×8050000,0×4/naB      ; 查看ELF header头4字节
    0×8050000:      
    0×8050000:      7f      
    0×8050001:      45      
    0×8050002:      4c      
    0×8050003:      46   
    > 0×8050000,0×4/nac      ; 查看ELF header头4字节
    0×8050000:      
    0×8050000:      
    0×8050001:      E
    0×8050002:      L
    0×8050003:      F
    > 0×80500d4,11/c         ; 查看.interp section
    0×80500d4:      /usr/lib/ld.so.1
    > 0×8050600::dis         ; 查看.text section的第一的过程,也是ELF的入口点
    _start:                         pushl   $0
    _start+2:                       pushl   $0
    _start+4:                       movl    %esp,%ebp
    _start+6:                       pushl   %edx
    _start+7:                       movl    $0×8050590,%eax
    _start+0xc:                     testl   %eax,%eax
    _start+0xe:                     je      +0xf            <_start+0×1d>
    _start+0×10:                    pushl   $0×8050590
    _start+0×15:                    call    -0×75           <PLT=libc.so.1`atexit>
    _start+0×1a:                    addl    $4,%esp
    _start+0×1d:                    movl    $0×8060848,%eax
    _start+0×22:                    testl   %eax,%eax
    _start+0×24:                    je      +7              <_start+0×2b>
    _start+0×26:                    call    -0×86           <PLT=libc.so.1`atexit>
    _start+0×2b:                    pushl   $0×80507f9
    _start+0×30:                    call    -0×90           <PLT=libc.so.1`atexit>
    _start+0×35:                    movl    +8(%ebp),%eax
    _start+0×38:                    leal    +0×10(%ebp,%eax,4),%edx
    _start+0×3c:                    movl    %edx,0×8060954
    _start+0×42:                    andl    $0xf0,%esp
    _start+0×45:                    subl    $4,%esp
    _start+0×48:                    pushl   %edx
    _start+0×49:                    leal    +0xc(%ebp),%edx
    _start+0×4c:                    pushl   %edx
    _start+0×4d:                    pushl   %eax
    _start+0×4e:                    call    +0×19e          <_init>
    _start+0×53:                    call    -0xa3           <PLT=libc.so.1`_fpstart>
    _start+0×58:                    call    +0xfb           <main>
    _start+0×5d:                    addl    $0xc,%esp
    _start+0×60:                    pushl   %eax
    _start+0×61:                    call    -0xa1           <PLT:exit>
    _start+0×66:                    pushl   $0
    _start+0×68:                    movl    $1,%eax
    _start+0×6d:                    lcall   $7,$0
    _start+0×74:                    hlt
    > 0×8050804,0×4/nap      ; 查看.rodata section,包含真正的o,p,q几个全局常量
    _lib_version:
    _lib_version:   
    _lib_version:   1               
    o:              7               
    p:              8               
    q:              9
    > 0×8050900,0×4/nap      ; 查看填充在代码段之后的.data section,这部分实际上是无效的数据
    0×8050900:      
    0×8050900:      0               
    0×8050904:      1        ; 全局变量i     
    0×8050908:      2        ; 全局变量j    
    0×805090c:      3        ; 全局变量k    
    > 0×8060000,0×4/naB      ; 查看填充到数据段之前的ELF header头4字节,这部分实际是无效的
    0×8060000:      
    0×8060000:      7f      
    0×8060001:      45      
    0×8060002:      4c      
    0×8060003:      46
    > 0×8060000,0×4/nac      ; 查看填充到数据段之前的ELF header头4字节,这部分实际是无效的
    0×8060000:      
    0×8060000:      
    0×8060001:      E
    0×8060002:      L
    0×8060003:      F
    > 0×8060804,0×4/nap      ; 查看填充到数据段之前的.rodata section,这部分实际是无效的
    0×8060804:      
    0×8060804:      1               
    0×8060808:      7        ; 全局常量o     
    0×806080c:      8        ; 全局常量p     
    0×8060810:      9        ; 全局常量q
    > 0×8060900,0×4/nap      ; 查看.date section,包含真正的i,j,k几个全局变量
    0×8060900:      
    0×8060900:      0               
    test5`i:        1               
    test5`j:        2               
    test5`k:        3               
    > 0×806092c,0xb/nap      ; 查看.bss section,包含真正的l,m,n几个全局变量
    test5`completed.1:
    test5`completed.1:              
    test5`completed.1:              0               
    test5`object.2: 0               
    test5`object.2+4:               0               
    test5`object.2+8:               0               
    test5`object.2+0xc:             0               
    test5`object.2+0×10:            0               
    test5`object.2+0×14:            0               
    test5`l:        4               
    test5`m:        5               
    test5`n:        6               
    test5`environ:  0×8047e00
    > 0×8060958,0×6a8/nab    ; 查看数据段末尾追加的0×6a8字节数据,全部为0
    0×8060958:      
    0×8060958:      0       
    0×8060959:      0       
    0×806095a:      0       
    ……………..
    ……………..
    0×8060ffe:      0       
    0×8060fff:      0
    > 0×8060fff,2/nab        ; 验证页边界数据是否映射
    0×8060fff:      
    0×8060fff:      0       
    mdb: failed to read data from target: no mapping for address
    0×8061000:      
    > 0×8050fff,2/nab        ; 验证页边界数据是否映射
    0×8050fff:      
    0×8050fff:      0151    
    mdb: failed to read data from target: no mapping for address
    0×8051000:  


3. 小结
    本次实验再次分析和验证了全局变量和全局常量在进程地址空间的位置以及和ELF文件的关系,并涉及到以下几方面的概念:
       Process Address Space 进程地址空间
       ELF EXECUTABLE AND LINKABLE FORMAT 可执行链接格式
       Page align 页对齐
    并且,利用Solaris提供的mdb,pmap,elfdump,od工具,直接观察到ELF文件的装载和格式

你可能感兴趣的:(X86汇编语言学习手记(3))