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

时间:2022-10-06 03:44:19
X86汇编语言学习手记(3)

作者: Badcoffee

Email: blog.oliver@gmail.com
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+0x10:         movl    $4,0x8060948       ; l=4
    main+0x1a:         movl    $5,0x806094c       ; m=5
    main+0x24:         movl    $6,0x8060950       ; n=6
    main+0x2e:         movl    0x8060908,%eax
    main+0x33:         addl    0x8060904,%eax
    main+0x39:         addl    0x806090c,%eax
    main+0x3f:         addl    0x8060948,%eax
    main+0x45:         addl    0x806094c,%eax
    main+0x4b:         addl    0x8060950,%eax
    main+0x51:         addl    0x8050808,%eax
    main+0x57:         addl    0x805080c,%eax
    main+0x5d:         addl    0x8050810,%eax     ; main+0x2e至main+0x5d,i+j+k+l+m+n+o+p+q
    main+0x63:         leave                      ; 撤销Stack Frame
    main+0x64:         ret                        ; main函数返回  

    现在,让我们在全局变量初始化后的地方设置断点,观察一下这几个全局变量的值:
    > main+0x2e:b                                 ; 设置断点
    > :r                                          ; 运行程序
    mdb: stop at main+0x2e
    mdb: target stopped at:
    main+0x2e:      movl    0x8060908,%eax
    > 0x8060904,03/nap                            ; 察看全局变量 i,j,k的值
    test5`i:
    test5`i:       
    test5`i:        1              
    test5`j:        2              
    test5`k:        3              
    > 0x8060948,03/nap                            ; 察看全局变量l,m,n的值
    test5`l:
    test5`l:       
    test5`l:        4              
    test5`m:        5              
    test5`n:        6              
    > 0x8050808,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                |
       |                                   |
       |                                   |
       +----------------------+ ----> 0x08050000
       |                                   |
       |    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+0x2b:    jae     +0x15           <libc.so.1`exit+0x40>
    >

    此时,程序运行后在_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的代码段,起始地址为0x08050000
    08060000       4K rwx--  /export/home/asm/L3/test5         ; test5的数据段,起始地址为0x08060000
    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:
        0x8060904起始的12字节            
    全局变量l,m,n:
        0x8060948起始的12字节
    全局常量o,p,q:      
        0x8050808起始的12字节
  
    显然,根据这些变量的地址,我们可以初步判断出这些变量属于哪个段:
    由于test5数据段起始地址为 0x08060000 ,我们得出结论:全局变量i,j,k,l,m,n属于数据段
    而test5代码段的起始地址为
0x08050000,我们得出结论:全局常量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(0x34)字节,具体意义可以参考ELF format的相关文档
    ei_magic:   { 0x7f, 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:             0x8050600  e_ehsize:     52  e_shstrndx:   27   ; 程序入口点_start的地址0x8050600
    e_shoff:                0x1584  e_shentsize:  40  e_shnum:      29   ; Section header table的大小是29*40
    e_phoff:                  0x34  e_phentsize:  32  e_phnum:       5

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

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

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

    Program Header[3]:                    ; 描述数据段在内存中如何映射,起始地址0x8060814,大小为0x144
        p_vaddr:      0x8060814       p_flags:    [ PF_X  PF_W  PF_R ]
        p_paddr:      0               p_type:     [ PT_LOAD ]
        p_filesz:     0x118           p_memsz:    0x144
        p_offset:     0x814           p_align:    0x10000

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

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

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

    Section Header[3]:  sh_name: .dynsym   ; 该section保存着动态符号表 
        sh_addr:      0x80501ec       sh_flags:   [ SHF_ALLOC ]
        sh_size:      0x200           sh_type:    [ SHT_DYNSYM ]
        sh_offset:    0x1ec           sh_entsize: 0x10
        sh_link:      4               sh_info:    1
        sh_addralign: 0x4          

    Section Header[4]:  sh_name: .dynstr   ; 该section保存着动态连接时需要的字符串
        sh_addr:      0x80503ec       sh_flags:   [ SHF_ALLOC  SHF_STRINGS ]
        sh_size:      0x11a           sh_type:    [ SHT_STRTAB ]
        sh_offset:    0x3ec           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0x1          

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

    Section Header[6]:  sh_name: .rel.got   ; 该section保存着.got section中部分符号的重定位信息
        sh_addr:      0x8050528       sh_flags:   [ SHF_ALLOC  SHF_INFO_LINK ]
        sh_size:      0x18            sh_type:    [ SHT_REL ]
        sh_offset:    0x528           sh_entsize: 0x8
        sh_link:      3               sh_info:    14
        sh_addralign: 0x4          

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

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

    Section Header[9]:  sh_name: .plt   ; 该section保存着过程连接表(Procedure Linkage Table)
        sh_addr:      0x8050580       sh_flags:   [ SHF_ALLOC  SHF_EXECINSTR ]
        sh_size:      0x80            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0x580           sh_entsize: 0x10
        sh_link:      0               sh_info:    0
        sh_addralign: 0x4          

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

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

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

    Section Header[13]:  sh_name: .rodata   ; 该section保存着只读数据
        sh_addr:      0x8050804       sh_flags:   [ SHF_ALLOC ]
        sh_size:      0x10            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0x804           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0x4          

    Section Header[14]:  sh_name: .got   ; 该section保存着全局的偏移量表
        sh_addr:      0x8060814       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0x34            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0x814           sh_entsize: 0x4
        sh_link:      0               sh_info:    0
        sh_addralign: 0x4          

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

    Section Header[16]:  sh_name: .data      ; 该sections保存着初始化了的数据
        sh_addr:      0x8060900       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0x10            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0x900           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0x4          

    Section Header[17]:  sh_name: .ctors
        sh_addr:      0x8060910       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0x8             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0x910           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0x4          

    Section Header[18]:  sh_name: .dtors
        sh_addr:      0x8060918       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0x8             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0x918           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0x4          

    Section Header[19]:  sh_name: .eh_frame
        sh_addr:      0x8060920       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0x4             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0x920           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0x4          

    Section Header[20]:  sh_name: .jcr
        sh_addr:      0x8060924       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0x4             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0x924           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0x4          

    Section Header[21]:  sh_name: .data.rel.local
        sh_addr:      0x8060928       sh_flags:   [ SHF_WRITE  SHF_ALLOC ]
        sh_size:      0x4             sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0x928           sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0x4          

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

    Section Header[23]:  sh_name: .symtab   ; 该section保存着一个符号表
        sh_addr:      0               sh_flags:   0
        sh_size:      0x540           sh_type:    [ SHT_SYMTAB ]
        sh_offset:    0x92c           sh_entsize: 0x10
        sh_link:      24              sh_info:    53
        sh_addralign: 0x4          

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

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

    Section Header[26]:  sh_name: .stab.index
        sh_addr:      0               sh_flags:   0
        sh_size:      0x24            sh_type:    [ SHT_PROGBITS ]
        sh_offset:    0x12c4          sh_entsize: 0xc
        sh_link:      0               sh_info:    0
        sh_addralign: 0x4          

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

    Section Header[28]:  sh_name: .stab.indexstr
        sh_addr:      0               sh_flags:   0
        sh_size:      0x1c0           sh_type:    [ SHT_STRTAB ]
        sh_offset:    0x13c4          sh_entsize: 0
        sh_link:      0               sh_info:    0
        sh_addralign: 0x1          

    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]  0x00000000 0x00000000  NOTY LOCL  D    0 UNDEF      
       [1]  0x080507ec 0x0000000d  FUNC GLOB  D    0 .init       _init
       [2]  0x08050804 0x00000004  OBJT GLOB  D    0 .rodata     _lib_version
       [3]  0x08050580 0x00000000  OBJT GLOB  D    0 .plt        _PROCEDURE_LINKAGE_TABLE_
       [4]  0x08050600 0x00000075  FUNC GLOB  D    0 .text       _start
       [5]  0x08060900 0x00000000  OBJT GLOB  D    0 .data       __dso_handle
       [6]  0x08060848 0x00000000  OBJT GLOB  D    0 .dynamic    _DYNAMIC
       [7]  0x00000000 0x00000000  NOTY WEAK  D    0 UNDEF       __deregister_frame_info_bases
       [8]  0x08050814 0x00000000  OBJT GLOB  D    0 .rodata     _etext
       [9]  0x08060958 0x00000000  OBJT GLOB  D    0 .bss        _end
      [10]  0x08050590 0x00000000  FUNC WEAK  D    0 UNDEF       _cleanup
      [11]  0x08050675 0x00000001  FUNC WEAK  D    0 .text       _mcount
      [12]  0x08060904 0x00000004  OBJT GLOB  D    0 .data       i
      [13]  0x08060908 0x00000004  OBJT GLOB  D    0 .data       j
      [14]  0x0806090c 0x00000004  OBJT GLOB  D    0 .data       k
      [15]  0x08060948 0x00000004  OBJT GLOB  D    0 .bss        l
      [16]  0x08060954 0x00000004  OBJT GLOB  D    0 .bss        _environ
      [17]  0x08060814 0x00000000  OBJT GLOB  D    0 .got        _GLOBAL_OFFSET_TABLE_
      [18]  0x0806094c 0x00000004  OBJT GLOB  D    0 .bss        m
      [19]  0x0806092c 0x00000000  OBJT GLOB  D    0 .data.rel.l _edata
      [20]  0x08060954 0x00000004  OBJT WEAK  D    0 .bss        environ
      [21]  0x080507f9 0x00000008  FUNC GLOB  D    0 .fini       _fini
      [22]  0x080505a0 0x00000000  FUNC GLOB  D    0 UNDEF       atexit
      [23]  0x08060950 0x00000004  OBJT GLOB  D    0 .bss        n
      [24]  0x08050808 0x00000004  OBJT GLOB  D    0 .rodata     o
      [25]  0x0805080c 0x00000004  OBJT GLOB  D    0 .rodata     p
      [26]  0x00000000 0x00000000  NOTY WEAK  D    0 UNDEF       _Jv_RegisterClasses
      [27]  0x08050810 0x00000004  OBJT GLOB  D    0 .rodata     q
      [28]  0x080505b0 0x00000000  FUNC GLOB  D    0 UNDEF       __fpstart
      [29]  0x08050753 0x00000065  FUNC GLOB  D    0 .text       main
      [30]  0x00000000 0x00000000  NOTY WEAK  D    0 UNDEF       __register_frame_info_bases
      [31]  0x080505c0 0x00000000  FUNC GLOB  D    0 UNDEF       exit

    Symbol Table:  .symtab                     ; 程序链接所需的符号表
     index    value       size     type bind oth ver shndx       name
       [0]  0x00000000 0x00000000  NOTY LOCL  D    0 UNDEF      
       [1]  0x00000000 0x00000000  FILE LOCL  D    0 ABS         test5
       [2]  0x080500d4 0x00000000  SECT LOCL  D    0 .interp    
       [3]  0x080500e8 0x00000000  SECT LOCL  D    0 .hash      
       [4]  0x080501ec 0x00000000  SECT LOCL  D    0 .dynsym    
       [5]  0x080503ec 0x00000000  SECT LOCL  D    0 .dynstr    
       [6]  0x08050508 0x00000000  SECT LOCL  D    0 .SUNW_versi
       [7]  0x08050528 0x00000000  SECT LOCL  D    0 .rel.got   
       [8]  0x08050540 0x00000000  SECT LOCL  D    0 .rel.bss   
       [9]  0x08050548 0x00000000  SECT LOCL  D    0 .rel.plt   
      [10]  0x08050580 0x00000000  SECT LOCL  D    0 .plt       
      [11]  0x08050600 0x00000000  SECT LOCL  D    0 .text      
      [12]  0x080507ec 0x00000000  SECT LOCL  D    0 .init      
      [13]  0x080507f9 0x00000000  SECT LOCL  D    0 .fini      
      [14]  0x08050804 0x00000000  SECT LOCL  D    0 .rodata    
      [15]  0x08060814 0x00000000  SECT LOCL  D    0 .got       
      [16]  0x08060848 0x00000000  SECT LOCL  D    0 .dynamic   
      [17]  0x08060900 0x00000000  SECT LOCL  D    0 .data      
      [18]  0x08060910 0x00000000  SECT LOCL  D    0 .ctors     
      [19]  0x08060918 0x00000000  SECT LOCL  D    0 .dtors     
      [20]  0x08060920 0x00000000  SECT LOCL  D    0 .eh_frame  
      [21]  0x08060924 0x00000000  SECT LOCL  D    0 .jcr       
      [22]  0x08060928 0x00000000  SECT LOCL  D    0 .data.rel.l
      [23]  0x0806092c 0x00000000  SECT LOCL  D    0 .bss       
      [24]  0x00000000 0x00000000  SECT LOCL  D    0 .symtab    
      [25]  0x00000000 0x00000000  SECT LOCL  D    0 .strtab    
      [26]  0x00000000 0x00000000  SECT LOCL  D    0 .comment   
      [27]  0x00000000 0x00000000  SECT LOCL  D    0 .stab.index
      [28]  0x00000000 0x00000000  SECT LOCL  D    0 .shstrtab  
      [29]  0x00000000 0x00000000  SECT LOCL  D    0 .stab.index
      [30]  0x08050000 0x00000000  OBJT LOCL  D    0 .interp     _START_
      [31]  0x08060958 0x00000000  OBJT LOCL  D    0 .bss        _END_
      [32]  0x00000000 0x00000000  FILE LOCL  D    0 ABS         crt1.s
      [33]  0x00000000 0x00000000  FILE LOCL  D    0 ABS         crti.s
      [34]  0x00000000 0x00000000  FILE LOCL  D    0 ABS         values-Xa.c
      [35]  0x00000000 0x00000000  FILE LOCL  D    0 ABS         crtstuff.c
      [36]  0x08060910 0x00000000  OBJT LOCL  D    0 .ctors      __CTOR_LIST__
      [37]  0x08060918 0x00000000  OBJT LOCL  D    0 .dtors      __DTOR_LIST__
      [38]  0x08060920 0x00000000  OBJT LOCL  D    0 .eh_frame   __EH_FRAME_BEGIN__
      [39]  0x08060924 0x00000000  OBJT LOCL  D    0 .jcr        __JCR_LIST__
      [40]  0x08060928 0x00000000  OBJT LOCL  D    0 .data.rel.l p.0
      [41]  0x0806092c 0x00000001  OBJT LOCL  D    0 .bss        completed.1
      [42]  0x08050678 0x00000000  FUNC LOCL  D    0 .text       __do_global_dtors_aux
      [43]  0x08060930 0x00000018  OBJT LOCL  D    0 .bss        object.2
      [44]  0x080506e4 0x00000000  FUNC LOCL  D    0 .text       frame_dummy
      [45]  0x00000000 0x00000000  FILE LOCL  D    0 ABS         test5.c
      [46]  0x00000000 0x00000000  FILE LOCL  D    0 ABS         crtstuff.c
      [47]  0x08060914 0x00000000  OBJT LOCL  D    0 .ctors      __CTOR_END__
      [48]  0x0806091c 0x00000000  OBJT LOCL  D    0 .dtors      __DTOR_END__
      [49]  0x08060920 0x00000000  OBJT LOCL  D    0 .eh_frame   __FRAME_END__
      [50]  0x08060924 0x00000000  OBJT LOCL  D    0 .jcr        __JCR_END__
      [51]  0x080507b8 0x00000000  FUNC LOCL  D    0 .text       __do_global_ctors_aux
      [52]  0x00000000 0x00000000  FILE LOCL  D    0 ABS         crtn.o
      [53]  0x080507ec 0x0000000d  FUNC GLOB  D    0 .init       _init
      [54]  0x08050804 0x00000004  OBJT GLOB  D    0 .rodata     _lib_version
      [55]  0x08050580 0x00000000  OBJT GLOB  D    0 .plt        _PROCEDURE_LINKAGE_TABLE_
      [56]  0x08050600 0x00000075  FUNC GLOB  D    0 .text       _start
      [57]  0x08060900 0x00000000  OBJT GLOB  D    0 .data       __dso_handle
      [58]  0x08060848 0x00000000  OBJT GLOB  D    0 .dynamic    _DYNAMIC
      [59]  0x00000000 0x00000000  NOTY WEAK  D    0 UNDEF       __deregister_frame_info_bases
      [60]  0x08050814 0x00000000  OBJT GLOB  D    0 .rodata     _etext
      [61]  0x08060958 0x00000000  OBJT GLOB  D    0 .bss        _end
      [62]  0x08050590 0x00000000  FUNC WEAK  D    0 UNDEF       _cleanup
      [63]  0x08050675 0x00000001  FUNC WEAK  D    0 .text       _mcount
      [64]  0x08060904 0x00000004  OBJT GLOB  D    0 .data       i
      [65]  0x08060908 0x00000004  OBJT GLOB  D    0 .data       j
      [66]  0x0806090c 0x00000004  OBJT GLOB  D    0 .data       k
      [67]  0x08060948 0x00000004  OBJT GLOB  D    0 .bss        l
      [68]  0x08060954 0x00000004  OBJT GLOB  D    0 .bss        _environ
      [69]  0x08060814 0x00000000  OBJT GLOB  D    0 .got        _GLOBAL_OFFSET_TABLE_
      [70]  0x0806094c 0x00000004  OBJT GLOB  D    0 .bss        m
      [71]  0x0806092c 0x00000000  OBJT GLOB  D    0 .data.rel.l _edata
      [72]  0x08060954 0x00000004  OBJT WEAK  D    0 .bss        environ
      [73]  0x080507f9 0x00000008  FUNC GLOB  D    0 .fini       _fini
      [74]  0x080505a0 0x00000000  FUNC GLOB  D    0 UNDEF       atexit
      [75]  0x08060950 0x00000004  OBJT GLOB  D    0 .bss        n
      [76]  0x08050808 0x00000004  OBJT GLOB  D    0 .rodata     o
      [77]  0x0805080c 0x00000004  OBJT GLOB  D    0 .rodata     p
      [78]  0x00000000 0x00000000  NOTY WEAK  D    0 UNDEF       _Jv_RegisterClasses
      [79]  0x08050810 0x00000004  OBJT GLOB  D    0 .rodata     q
      [80]  0x080505b0 0x00000000  FUNC GLOB  D    0 UNDEF       __fpstart
      [81]  0x08050753 0x00000065  FUNC GLOB  D    0 .text       main
      [82]  0x00000000 0x00000000  NOTY WEAK  D    0 UNDEF       __register_frame_info_bases
      [83]  0x080505c0 0x00000000  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             0x8060830             .rel.got       __deregister_frame_info_bases
        R_386_GLOB_DAT             0x8060838             .rel.got       __register_frame_info_bases
        R_386_GLOB_DAT             0x806083c             .rel.got       _Jv_RegisterClasses

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

    Relocation: .rel.plt
        type                       offset              section   with respect to
        R_386_JMP_SLOT             0x8060820             .rel.plt       _cleanup
        R_386_JMP_SLOT             0x8060824             .rel.plt       atexit
        R_386_JMP_SLOT             0x8060828             .rel.plt       __fpstart
        R_386_JMP_SLOT             0x806082c             .rel.plt       exit
        R_386_JMP_SLOT             0x8060834             .rel.plt       __deregister_frame_info_bases
        R_386_JMP_SLOT             0x8060840             .rel.plt       _Jv_RegisterClasses
        R_386_JMP_SLOT             0x8060844             .rel.plt       __register_frame_info_bases

    Dynamic Section:  .dynamic
     index  tag               value
       [0]  NEEDED           0x104             libc.so.1
       [1]  INIT             0x80507ec        
       [2]  FINI             0x80507f9        
       [3]  HASH             0x80500e8        
       [4]  STRTAB           0x80503ec        
       [5]  STRSZ            0x11a            
       [6]  SYMTAB           0x80501ec        
       [7]  SYMENT           0x10             
       [8]  CHECKSUM         0x6a10           
       [9]  VERNEED          0x8050508        
      [10]  VERNEEDNUM       0x1              
      [11]  PLTRELSZ         0x38             
      [12]  PLTREL           0x11             
      [13]  JMPREL           0x8050548        
      [14]  REL              0x8050528        
      [15]  RELSZ            0x58             
      [16]  RELENT           0x8              
      [17]  DEBUG            0                
      [18]  FEATURE_1        0x1               [ PARINIT ]
      [19]  FLAGS            0                 0
      [20]  FLAGS_1          0                 0
      [21]  PLTGOT           0x8060814


    利用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                  0x0+0x34=0x34
    Program header              0x34+0xa0=0xd4
    Section 1  .interp          0xd4+0x11=0xe5
    000                         0xe5+0x3=0xe8
    Section 2  .hash            0xe8+0x104=0x1ec
    Section 3  .dynsym          0x1ec+0x200=0x3ec
    Section 4  .dynstr          0x3ec+0x11a=0x506
    00                          0x506+0x2=0x508
    Section 5  .SUNW_version    0x508+0x20=0x528
    Section 6  .rel.got         0x528+0x18=0x540
    Section 7  .rel.bss         0x540+0x8=0x548  
    Section 8  .rel.plt         0x548+0x38=0x580
    Section 9  .plt             0x580+0x80=0x600    
    Section 10 .text            0x600+0x1ec=0x7ec             
    Section 11 .init            0x7ec+0xd=0x7f9  
    Section 12 .fini            0x7f9+0x8=0x801
    000                         0x801+0x3=0x804
    Section 13 .rodata          0x804+0x10=0x814
    Section 14 .got             0x814+0x34=0x848
    Section 15 .dynamic         0x848+0xb8=900
    Section 16 .data            0x900+0x10=0x910
    Section 17 .ctors           0x910+0x8=0x918
    Section 18 .dtors           0x918+0x8=0x920
    Section 19 .eh_frame        0x920+0x4=0x924
    Section 20 .jcr             0x924+0x4=0x928
    Section 21 .data.rel.local  0x928+0x4=0x92c
    Section 22 .bss          0x92c+0x0=0x958
    Section 23 .symtab          0x92c+0x540=0xe6c
    Section 24 .strtab          0xe6c+0x20b=1077
    Section 25 .comment         0x1077+0x24d=0x12c4
    Section 26 .stab.index      0x12c4+0x24=0x12e8
    Section 27 .shstrtab        0x12e8+0xdc=0x13c4
    Section 28 .stab.indexstr   0x13c4+0x1c0=0x1584
    Section header table        0x1584+0x488=0x1a0c      ; 29x40=1160=0x488 这是根据Elf header的信息算得的
    ------------------------------------------------------
    图 3-3 test5的ELF文件格式

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

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


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

        Program Header[2]:
            p_vaddr:      0x8050000        p_flags:    [ PF_X  PF_R ]
            p_paddr:      0               p_type:     [ PT_LOAD ]
            p_filesz:     0x814           p_memsz:    0x814
            p_offset:     0               p_align:    0x10000   ; 指定64K页对齐

        Program Header[3]:
            p_vaddr:      0x8060814       p_flags:    [ PF_X  PF_W  PF_R ]
            p_paddr:      0               p_type:     [ PT_LOAD ]
            p_filesz:     0x118           p_memsz:    0x144
            p_offset:     0x814           p_align:    0x10000   ; 指定64K页对齐

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

    问题:为什么要页对齐 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============================ 0x8050000
    ELF header                  0x8050000+0x34=0x8050034
    Program header              0x8050034+0xa0=0x80500d4
    Section 1  .interp          0x80500d4+0x11=0x80500e5
    0                           0x80500e5+0x3=0x80500e8       ; 3字节0填充
    Section 2  .hash            0x80500e8+0x104=0x80501ec
    Section 3  .dynsym          0x80501ec+0x200=0x80503ec
    Section 4  .dynstr          0x80503ec+0x11a=0x8050506
    0                           0x8050506+0x2=0x8050508        ; 2字节0填充
    Section 5  .SUNW_version    0x8050508+0x20=0x8050528
    Section 6  .rel.got         0x8050528+0x18=0x8050540
    Section 7  .rel.bss         0x8050540+0x8=0x8050548  
    Section 8  .rel.plt         0x8050548+0x38=0x8050580
    Section 9  .plt             0x8050580+0x80=0x8050600    
    Section 10 .text            0x8050600+0x1ec=0x80507ec             
    Section 11 .init            0x80507ec+0xd=0x80507f9  
    Section 12 .fini            0x80507f9+0x8=0x8050801
    0                           0x8050801+0x3=0x8050804        ; 3字节0填充
    Section 13 .rodata       0x8050804+0x10=0x8050814   ; o,p,q在代码段的. rodata section中
    -----------------------------------------------------
    Section 14 .got             0x8050814+0x34=0x8050848       ; 这是代码段的第一页也是最后一页,
    Section 15 .dynamic         0x8050848+0xb8=8050900         ; 因此数据段的内容会追加到代码段最后一页末尾
    Section 16 .data            0x8050900+0x10=0x8050910
    Section 17 .ctors           0x8050910+0x8=0x8050918
    Section 18 .dtors           0x8050918+0x8=0x8050920
    Section 19 .eh_frame        0x8050920+0x4=0x8050924
    Section 20 .jcr             0x8050924+0x4=0x8050928
    Section 21 .data.rel.local  0x8050928+0x4=0x805092c
    Section 22 .bss             0x805092c+0x2c=0x8050958
    pending data                0x8050958+0x6a8=8051000         ; 页末是0x6a8字节填充
    ======================================================


                        no mapping


    =================User Data============================ 0x8060000
    ELF header                  0x8060000+0x34=0x8060034      ; 这是数据段的最后一页也是第一页,     
    Program header              0x8060034+0xa0=0x80600d4      ; 因此代码段的内容会追加到数据段第一页之前
    Section 1  .interp          0x80600d4+0x11=0x80600e5
    0                           0x80600e5+0x3=0x80600e8        ; 3字节0填充
    Section 2  .hash            0x80600e8+0x104=0x80601ec
    Section 3  .dynsym          0x80601ec+0x200=0x80603ec
    Section 4  .dynstr          0x80603ec+0x11a=0x8060506
    0                           0x8060506+0x2=0x8060508        ; 2字节0填充
    Section 5  .SUNW_version    0x8060508+0x20=0x8060528
    Section 6  .rel.got         0x8060528+0x18=0x8060540
    Section 7  .rel.bss         0x8060540+0x8=0x8060548  
    Section 8  .rel.plt         0x8060548+0x38=0x8060580
    Section 9  .plt             0x8060580+0x80=0x8060600    
    Section 10 .text            0x8060600+0x1ec=0x80607ec             
    Section 11 .init            0x80607ec+0xd=0x80607f9  
    Section 12 .fini            0x80607f9+0x8=0x8060801
    0                           0x8060801+0x3=0x8060804        ; 3字节0填充
    Section 13 .rodata          0x8060804+0x10=0x8060814
    -----------------------------------------------------
    Section 14 .got             0x8060814+0x34=0x8060848
    Section 15 .dynamic         0x8060848+0xb8=8060900
    Section 16 .data         0x8060900+0x10=0x8060910    ; i,j,k在数据段的.data section中
    Section 17 .ctors           0x8060910+0x8=0x8060918
    Section 18 .dtors           0x8060918+0x8=0x8060920
    Section 19 .eh_frame        0x8060920+0x4=0x8060924
    Section 20 .jcr             0x8060924+0x4=0x8060928
    Section 21 .data.rel.local  0x8060928+0x4=0x806092c
    Section 22 .bss          0x806092c+0x2c=0x8060958    ; l ,m,n在数据段的.bss section中
    0                           0x8060958+0x6a8=8061000        ; 页末是0x6a8字节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 0x1077 -N 0x24d 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+0x10:                      movl    $4,0x8060948
    main+0x1a:                      movl    $5,0x806094c
    main+0x24:                      movl    $6,0x8060950
    main+0x2e:                      movl    0x8060908,%eax
    main+0x33:                      addl    0x8060904,%eax
    main+0x39:                      addl    0x806090c,%eax
    main+0x3f:                      addl    0x8060948,%eax
    main+0x45:                      addl    0x806094c,%eax
    main+0x4b:                      addl    0x8060950,%eax
    main+0x51:                      addl    0x8050808,%eax
    main+0x57:                      addl    0x805080c,%eax
    main+0x5d:                      addl    0x8050810,%eax
    main+0x63:                      leave
    main+0x64:                      ret
    > main+0x2e:b            ; 设置断点
    > :r                     ; 运行
    mdb: stop at main+0x2e
    mdb: target stopped at:
    main+0x2e:      movl    0x8060908,%eax
    > 0x8050000,0x4/naB      ; 查看ELF header头4字节
    0x8050000:     
    0x8050000:      7f     
    0x8050001:      45     
    0x8050002:      4c     
    0x8050003:      46  
    > 0x8050000,0x4/nac      ; 查看ELF header 头4字节
    0x8050000:     
    0x8050000:     
    0x8050001:      E
    0x8050002:      L
    0x8050003:      F
    > 0x80500d4,11/c         ; 查看.interp section
    0x80500d4:      /usr/lib/ld.so.1
    > 0x8050600::dis         ; 查看.text section的第一的过程,也是ELF的入口点
    _start:                         pushl   $0
    _start+2:                       pushl   $0
    _start+4:                       movl    %esp,%ebp
    _start+6:                       pushl   %edx
    _start+7:                       movl    $0x8050590,%eax
    _start+0xc:                     testl   %eax,%eax
    _start+0xe:                     je      +0xf            <_start+0x1d>
    _start+0x10:                    pushl   $0x8050590
    _start+0x15:                    call    -0x75           <PLT=libc.so.1`atexit>
    _start+0x1a:                    addl    $4,%esp
    _start+0x1d:                    movl    $0x8060848,%eax
    _start+0x22:                    testl   %eax,%eax
    _start+0x24:                    je      +7              <_start+0x2b>
    _start+0x26:                    call    -0x86           <PLT=libc.so.1`atexit>
    _start+0x2b:                    pushl   $0x80507f9
    _start+0x30:                    call    -0x90           <PLT=libc.so.1`atexit>
    _start+0x35:                    movl    +8(%ebp),%eax
    _start+0x38:                    leal    +0x10(%ebp,%eax,4),%edx
    _start+0x3c:                    movl    %edx,0x8060954
    _start+0x42:                    andl    $0xf0,%esp
    _start+0x45:                    subl    $4,%esp
    _start+0x48:                    pushl   %edx
    _start+0x49:                    leal    +0xc(%ebp),%edx
    _start+0x4c:                    pushl   %edx
    _start+0x4d:                    pushl   %eax
    _start+0x4e:                    call    +0x19e          <_init>
    _start+0x53:                    call    -0xa3           <PLT=libc.so.1`_fpstart>
    _start+0x58:                    call    +0xfb           <main>
    _start+0x5d:                    addl    $0xc,%esp
    _start+0x60:                    pushl   %eax
    _start+0x61:                    call    -0xa1           <PLT:exit>
    _start+0x66:                    pushl   $0
    _start+0x68:                    movl    $1,%eax
    _start+0x6d:                    lcall   $7,$0
    _start+0x74:                    hlt
    > 0x8050804,0x4/nap      ; 查看.rodata section,包含真正的o,p,q几个全局常量
    _lib_version:
    _lib_version:  
    _lib_version:   1              
    o:              7              
    p:              8              
    q:              9
    > 0x8050900,0x4/nap      ; 查看填充在代码段之后的.data section,这部分实际上是无效的数据
    0x8050900:     
    0x8050900:      0              
    0x8050904:      1        ; 全局变量i    
    0x8050908:      2        ; 全局变量j   
    0x805090c:      3        ; 全局变量k   
    > 0x8060000,0x4/naB      ; 查看填充到数据段之前的ELF header头4字节,这部分实际是无效的
    0x8060000:     
    0x8060000:      7f     
    0x8060001:      45     
    0x8060002:      4c     
    0x8060003:      46
    > 0x8060000,0x4/nac      ; 查看填充到数据段之前的ELF header头4字节,这部分实际是无效的
    0x8060000:     
    0x8060000:     
    0x8060001:      E
    0x8060002:      L
    0x8060003:      F
    > 0x8060804,0x4/nap      ; 查看 填充到数据段之前的 .rodata section, 这部分实际是无效的
    0x8060804:     
    0x8060804:      1              
    0x8060808:      7        ; 全局常量o    
    0x806080c:      8        ; 全局常量p    
    0x8060810:      9        ; 全局常量q
    > 0x8060900,0x4/nap      ; 查看.date section,包含真正的i,j,k几个全局变量
    0x8060900:     
    0x8060900:      0              
    test5`i:        1              
    test5`j:        2              
    test5`k:        3              
    > 0x806092c,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+0x10:            0              
    test5`object.2+0x14:            0              
    test5`l:        4              
    test5`m:        5              
    test5`n:        6              
    test5`environ:  0x8047e00
    > 0x8060958,0x6a8/nab    ; 查看数据段末尾追加的0x6a8字节数据,全部为0
    0x8060958:     
    0x8060958:      0      
    0x8060959:      0      
    0x806095a:      0      
    .................
    .................
    0x8060ffe:      0      
    0x8060fff:      0
    > 0x8060fff,2/nab        ; 验证页边界数据是否映射
    0x8060fff:     
    0x8060fff:      0      
    mdb: failed to read data from target: no mapping for address
    0x8061000:     
    > 0x8050fff,2/nab        ; 验证页边界数据是否映射
    0x8050fff:     
    0x8050fff:      0151   
    mdb: failed to read data from target: no mapping for address
    0x8051000: 


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


相关文档:
    X86 汇编语言学习手记(1)
    X86 汇编语言学习手记(2)
    EXECUTABLE AND LINKABLE FORMAT (ELF)
    Solaris 上的开发环境安装及设置
    Linux AT&T 汇编语言开发指南
    ELF动态解析符号过程(修订版)
    关注: Solaris 10的10大新变化
    Solaris Internals Core Kernel Architecture Jim Mauro,Richard McDougall