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文件的装载和格式。