X86汇编语言学习手记(3) 作者: Badcoffee Email: blog.oliver@gmail.com 2004年12月 原文出处: [url]http://blog.csdn.net/yayong[/url] 版权所有: 转载时请务必以超链接形式标明文章原始出处、作者信息及本声明 这是作者在学习X86汇编过程中的学习笔记,难免有错误和疏漏之处,欢迎指正。 严格说来,本篇文档所涉及到的内容并非局限于X86汇编领域,关于ELF文件格式、C语言、编译器及其它相关知识,还需参考相关文档。 作者会将反馈的错误修订后更新在自己的Blog站点上。 在X86汇编语言学习手记(1)(2)中,可以看到栈(Stack)作为进程执行过程中数据的临时存储区域,通常包含如下几类数据: 局部变量 函数调用的返回地址 函数调用的入口参数 STP 栈框架指针 (可以通过编译器优化选项去除) 本章中,将继续通过实验,了解全局变量和静态变量在进程中是如何存储和分配的。 注:不同的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 |