免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2363 | 回复: 2
打印 上一主题 下一主题

X86汇编语言学习手记(3) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2004-12-27 20:29 |只看该作者 |倒序浏览
X86汇编语言学习手记(3)
作者: BadcoffeeEmail: blog.oliver@gmail.com2004年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           
    >
    此时,程序运行后在_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           
........由于CU blog的问题,本文超过64K,因此看全文请到
http://blog.csdn.net/yayong


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/768/showart_8814.html

论坛徽章:
0
2 [报告]
发表于 2008-08-23 19:15 |只看该作者
回贴支持。谢谢:)

论坛徽章:
0
3 [报告]
发表于 2008-08-23 22:36 |只看该作者
回贴支持。谢谢:)
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP