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

《80X86汇编语言程序设计》课后习题答案(华中科技大学王元珍版本) 《80X86汇编语言程序设计》课后习题答案(个人版本) 说明 我个人在学习《80X86汇编语言程序设计》(王元珍等主编,华中科技大学出版社出版)的过程中,发现并未找到课后习题的标准答案,因此就自己做了一份,源代码可以在我的github找到,欢迎一起交流,答案仅供参考。 第一章 预备知识 1.本章简介摘录 汇编语言是一种面向机器的、能够充分体用机器硬件特性的低级语言,他随机器 阅读详情
作者: Badcoffee
Email: blog.oliver@gmail.com
2004年11月

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

这是作者在学习X86汇编过程中的学习笔记,难免有错误和疏漏之处,欢迎指正。作者将随时修改错误并将新的版本发布在自己的Blog站点上。严格说来,本篇文档更侧重于C语言和C编译器方面的知识,如果涉及到基本的汇编语言的内容,可以参考相关文档。
X86 汇编语言学习手记(1)在作者的Blog上发布以来,得到了很多网友的肯定和鼓励,并且还有热心网友指出了其中的错误,作者已经将文档中已发现的错误修正后更新在Blog上。

    上一篇文章通过分析一个最简的C程序,引出了以下概念:
        Stack Frame 栈框架 和 SFP 栈框架指针
        Stack aligned 栈对齐
       
Calling Convention  调用约定 和 ABI ( Application Binary Interface) 应用程序二进制接口
    本章中,将通过进一步的实验,来深入了解这些概念。如果还不了解这些概念,可以参考 X86汇编语言学习手记(1)
       
1. 局部变量的栈分配

    上篇文章已经分析过一个最简的C程序,
    下面我们分析一下C编译器如何处理局部变量的分配,为此先给出如下程序:

    #vi test2.c

    int main()
    {
        int i;
        int j=2;
        i=3;
        i=++i;
        return i+j;
    }

    编译该程序,产生二进制文件,并利用mdb来观察程序运行中的stack的状态:
    #gcc test2.c -o test2
    #mdb test2
    Loading modules: [ libc.so.1 ]
    > main::dis
   
main:           pushl   %ebp
   
main+1:         movl    %esp,%ebp          ; main至main+1,创建Stack Frame
    main+3:         subl    $8,%esp            ; 为局部变量i,j分配栈空间,并保证栈16字节对齐
    main+6:         andl    $0xf0,%esp
   
main+9:         movl    $0,%eax
   
main+0xe:       subl    %eax,%esp          ; main+6至main+0xe,再次保证栈16字节对齐
   
main+0x10:      movl    $2,-8(%ebp)        ; 初始化局部变量j的值为2
    main+0x17:      movl    $3,-4(%ebp)         ; 给局部变量i赋值为3
    main+0x1e:      leal    -4(%ebp),%eax      ; 将局部变量i的地址装入到EAX寄存器中
    main+0x21:      incl    (%eax)             ; i++
    main+0x23:      movl    -8(%ebp),%eax      ; 将j的值装入EAX
    main+0x26:      addl    -4(%ebp),%eax      ; i+j并将结果存入EAX,作为返回值
    main+0x29:      leave                    ; 撤销Stack Frame
   
main+0x2a:      ret                      ; main函数返回
    >
    > main+0x10:b         ; 在
地址 main+0x10处 设置断点
    > main+0x1e:b         ; 在
main+0x1e 设置断点
    > main+0x29:b         ; main+0x1e 设置断点
    > main+0x2a:b         ; main+0x1e 设置断点
       
    下面的mdb的4个命令在一行输入,中间用分号间隔开,命令的含义在注释中给出:
    > :r;<esp,10/nap;<ebp=X;<eax=X    ; 运行程序(:r 命令)
    mdb: stop at main+0x10                ; 以ESP寄存器为起始地址,指定格式输出16字节的栈内容( <esp,10/nap 命令)
    mdb: target stopped at:                ; 在最后输出EBP和EAX寄存器的值( <ebp=X 命令 和 <eax=X 命令 )
    main+0x10:      movl    $2,-8(%ebp)    ; 程序运行后在main +0x10处指令执行前中断,此时栈分配后还未初始化
    0x8047db0:     
    0x8047db0:      0xddbebca0             ; 这是变量j,4字节,未初始化,此处为栈顶, ESP的值就是0x8047db0  
    0x8047db4:      0xddbe137f             ; 这是 变量 i, 4字节,未初始化
    0x8047db8:      0x8047dd8              ; 这是_start的SFP(_start的EBP),4字节由main 的SFP指向它
    0x8047dbc:      _st art+0x5d            ; 这是_start调用main之前压栈的下条指令地址,main返回后将恢复给EIP
    0x8047dc0:      1              
    0x8047dc4:      0x8047de4      
    0x8047dc8:      0x8047dec      
    0x8047dcc:      _st art+0x35    
    0x8047dd0:      _fini          
    0x8047dd4:      ld.so.1`atexit_fini
    0x8047dd8:      0                      ; _start的SFP指向的内容为0,证明_start是程序的入口
    0x8047ddc:      0              
    0x8047de0:      1              
    0x8047de4:      0x8047eb4      
    0x8047de8:      0              
    0x8047dec:      0x8047eba      
                    8047db8              ; 这是main当前EBP寄存器的值,即main的SFP
                    0                   ; EAX的值,当前为0

    > :c;<esp,10/nap;<ebp=X;<eax=X    ; 继续运行程序(:c 命令),其余3命令同上,打印16字节栈和EBP,EAX内容

    mdb: stop at main+0x1e
    mdb: target stopped at:
    main+0x1e:      leal    -4(%ebp),%eax  ; 程序运行到断点main+0x1e处停止,此时局部变量i,j赋值已完成
    0x8047db0:     
    0x8047db0:      2                      ; 这是变量j,4字节,值为2, 此处为栈顶, ESP的值就是0x8047db0
    0x8047db4:      3                      ; 这是变量i,4字节,值为3
    0x8047db8:      0x8047dd8              ; 这是_start的SFP,4字节
    0x8047dbc:      _st art+0x5d            ; 这是返回_start后的EIP
    0x8047dc0:      1              
    0x8047dc4:      0x8047de4      
    0x8047dc8:      0x8047dec      
    0x8047dcc:      _st art+0x35    
    0x8047dd0:      _fini          
    0x8047dd4:      ld.so.1`atexit_fini
    0x8047dd8:      0              
    0x8047ddc:      0              
    0x8047de0:      1              
    0x8047de4:      0x8047eb4      
    0x8047de8:      0              
    0x8047dec:      0x8047eba      
                    8047db8              ; 这是main当前EBP寄存器的值,即main的SFP
                    0                   ; EAX的值,当前为0
    > :c;<esp,10/nap;<ebp=X;<eax=X
    ; 继续运行程序,打印16字节栈和EBP,EAX内容
    mdb: stop at main+0x29
    mdb: target stopped at:
    main+0x29:      leave                  ; 运行到断点main+0x29处停止,计算已经完成,即将撤销Stack Frame
    0x8047db0:     
    0x8047db0:      2                      ; 这是变量j,4字节,值为2此处为栈顶, ESP的值就是0x8047db0      
    0x8047db4:      4                      ; 这是i++以后的变量i,4字节,值为3
    0x8047db8:      0x8047dd8              ; 这是_start的SFP,4字节
    0x8047dbc:      _st art+0x5d            ; 这是返回_start后的EIP
    0x8047dc0:      1              
    0x8047dc4:      0x8047de4      
    0x8047dc8:      0x8047dec      
    0x8047dcc:      _st art+0x35    
    0x8047dd0:      _fini          
    0x8047dd4:      ld.so.1`atexit_fini
    0x8047dd8:      0              
    0x8047ddc:      0              
    0x8047de0:      1              
    0x8047de4:      0x8047eb4      
    0x8047de8:      0              
    0x8047dec:      0x8047eba      
                    8047db8              ; 这是main当前EBP寄存器的值,即main的SFP       
                    6                   ; EAX的值 ,即函数的返回值 ,当前为6               
    > :c;<esp,10/nap;<ebp=X;<eax=X
    ; 继续运行程序,打印16字节栈和EBP,EAX内容
    mdb: stop at main+0x2a
    mdb: target stopped at:
    main+0x2a:      ret                   ; 运行到断点main+0x2a处停止,Stack Frame已被撤销,main即将返回
    0x8047dbc:     
    0x8047dbc:      _st art+0x5d            ; Stack Frame已经被撤销,栈顶是返回_start后的EIP,main的栈已被释放
    0x8047dc0:      1              
    0x8047dc4:      0x8047de4      
    0x8047dc8:      0x8047dec      
    0x8047dcc:      _st art+0x35    
    0x8047dd0:      _fini          
    0x8047dd4:      ld.so.1`atexit_fini
    0x8047dd8:      0              
    0x8047ddc:      0              
    0x8047de0:      1              
    0x8047de4:      0x8047eb4      
    0x8047de8:      0              
    0x8047dec:      0x8047eba      
    0x8047df0:      0x8047ed6      
    0x8047df4:      0x8047edd      
    0x8047df8:      0x8047ee4      
                    8047dd8             ; _start的SFP,之前存储在 地址 0x8047db8 ,main的Stack Frame撤销时恢复                            6                  ; EAX的值 ,即函数的返回值 ,当前为6               
    > :s;<esp,10/nap;<ebp=X;<eax=X
   ; 单步执行下条指令(:s 命令),打印16字节栈和EBP,EAX内容
    mdb: target stopped at:
    _st art+0x5d:    addl    $0xc,%esp      ; 此时main已经返回,_start+0x5d曾经存储在 地址 0x8047dbc
    0x8047dc0:     
    0x8047dc0:      1                      ; main已经返回 _start +0x5d已经被弹出
    0x8047dc4:      0x8047de4      
    0x8047dc8:      0x8047dec      
    0x8047dcc:      _st art+0x35    
    0x8047dd0:      _fini          
    0x8047dd4:      ld.so.1`atexit_fini
    0x8047dd8:      0                      ; _start的SFP指向的内容为0,证明_start是程序的入口              
    0x8047ddc:      0              
    0x8047de0:      1              
    0x8047de4:      0x8047eb4      
    0x8047de8:      0              
    0x8047dec:      0x8047eba      
    0x8047df0:      0x8047ed6      
    0x8047df4:      0x8047edd      
    0x8047df8:      0x8047ee4      
    0x8047dfc:      0x8047ef3      
                    8047dd8            ; _start的SFP,之前存储在 地址 0x8047db8 ,main的Stack Frame撤销时恢复 
                    6                  ; EAX的值为6, 还是main函数的返回值                 
    >


    通过mdb对程序运行时的寄存器和栈的观察和分析,可以得出局部变量在栈中的访问和分配及释放方式:
        1.局部变量的分配,可以通过esp减去所需字节数
            subl    $8,%esp
        2.局部变量的释放,可以通过leave指令
            leave      
        3.局部变量的访问,可以通过ebp减去偏移量
            movl    -8(%ebp),%eax
            addl    -4(%ebp),%eax

    问题:当存在2个以上的局部变量时,如何进行栈对齐?
    在上篇文章中,提到 subl $8,%esp语句除了分配栈空间外,还有一个作用就是栈对齐。那么本例中,由于i和j正好是8字节,那么如果存在2个以上的局部变量时,如何同时满足空间分配和栈对齐呢?

2. 两个以上的局部变量的栈分配

    在之前的C程序中,增加局部变量定义k,程序如下:
    # vi test3.c

    int main()
    {
        int i, j=2, k=4;
        i=3;
        i=++i;
        k=i+j+k;
        return k;
    }

    编译该程序后,用mdb反汇编得出如下结果:
    # gcc test3.c -o test3   
    # mdb test3
    Loading modules: [ libc.so.1 ]
    > main::dis
    main:               pushl   %ebp
    main+1:             movl    %esp,%ebp            ; main至main+1,创建Stack Frame
    main+3:            subl   $0x18,%esp         ; 为局部变量i,j,k分配栈空间,并保证栈16字节对齐
    main+6:             andl    $0xf0,%esp
    main+9:             movl    $0,%eax
    main+0xe:           subl    %eax,%esp            ; main+6至main+0xe,再次保证栈16字节对齐
    main+0x10:          movl    $2,-8(%ebp)          ; j=2
    main+0x17:          movl    $4,-0xc(%ebp)        ; k=4
    main+0x1e:          movl    $3,-4(%ebp)          ; i=3
    main+0x25:          leal    -4(%ebp),%eax        ; 将i的地址装入到EAX
    main+0x28:          incl    (%eax)               ; i++
    main+0x2a:          movl    -8(%ebp),%eax        ; 将j的值装入到 EAX
    main+0x2d:          movl    -4(%ebp),%edx        ; 将i的值装入到 EDX
    main+0x30:          addl    %eax,%edx            ; j+i,结果存入EDX
    main+0x32:          leal    -0xc(%ebp),%eax      ; 将k的地址装入到EAX
    main+0x35:          addl    %edx,(%eax)          ; i+j+k,结果存入地址ebp-0xc即k中
    main+0x37:          movl    -0xc(%ebp),%eax      ; 将k的值装入EAX,作为返回值
    main+0x3a:          leave                        ; 撤销Stack Frame
    main+0x3b:          ret                          ; main函数返回
    >
 

    问题:为什么3个变量分配了0x18字节的栈空间?
    在2个变量的时候,分配栈空间的指令是: subl $8,%esp
    而在3个局部变量的时候,分配栈空间的指令是: subl $0x18,%esp
    3个整型变量只需要0xc字节,为何实际上分配了0x18字节呢?
    答案就是: 保持16字节栈对齐

    在 X86 汇编语言学习手记(1)里,已经说明过gcc默认的编译是要16字节栈对齐的, subl $8,%esp会使栈16字节对齐,而8字节空间只能满足2个局部变量,如果再分配4字节满足第3个局部变量的话,那栈地址就不再16字节对齐的,而同时满足空间需要而且保持16字节栈对齐的最接近的就是0x18。

    如果,各定义一个50字节和100字节的字符数组,在这种情况下,实际分配多少栈空间呢?答案是0x8+0x40+0x70,即184字节。
    下面动手验证一下:

    # vi test4.c
    int main()
    {
        char str1[50];
        char str2[100];
        return 0;
    }
    # mdb test4
    Loading modules: [ libc.so.1 ]
    > main::dis
    main:               pushl   %ebp
    main+1:             movl    %esp,%ebp
    main+3:            subl   $0xb8,%esp   ; 为两个字符数组分配栈空间,同时保证16字节对齐
    main+9:             andl    $0xf0,%esp
    main+0xc:           movl    $0,%eax
    main+0x11:          subl    %eax,%esp
    main+0x13:          movl    $0,%eax
    main+0x18:          leave
    main+0x19:          ret
    > 0xb8=D                              ; 16进制换算10进制
                    184            
    > 0x40+0x70+0x8=X                     ; 表达式计算,结果指定为16进制
                    b8             
    >

    问题:定义了多个局部变量时,栈分配顺序是怎样的?
    局部变量栈分配的顺序是按照变量声明先后的顺序,同一行声明的变量是按照从左到右的顺序入栈的,在test2.c中,变量声明如下:
        int i, j=2, k=4;
    而反汇编的结果中:

        movl    $2,-8(%ebp)          ; j=2
        movl    $4,-0xc(%ebp)        ; k=4
        movl    $3,-4(%ebp)          ; i=3
    其中不难看出,i,j,k的栈中的位置如下图:

 +----------------------------+------> 高地址
| EIP (_start函数的返回地址) |
+----------------------------+
| EBP (_start函数的EBP) | <------ main函数的EBP指针(即SFP框架指针)
+----------------------------+
| i (EBP-4) |
+----------------------------+
| j (EBP-8) |
+----------------------------+
| k (EBP-0xc) |
+----------------------------+------> 低地址

图 2-1

3. 小结

    这次通过几个试验程序,进一步了解了局部变量在栈中的分配和释放以及位置,并再次回顾了上篇文章中涉及到的以下概念:
        SFP 栈框架指针
        Stack aligned 栈对齐
    并且,利用Solaris提供的mdb工具,直观的观察到了栈在程序运行中的动态变化,以及Stack Frame的创建和撤销,根据给出的图例的内容( 图 2-1图 1-1),可以更清晰的了解IA32架构中栈在内存中的布局(Stack Layer)。


相关文档:
    X86 汇编语言学习手记(1)
    Solaris 上的开发环境安装及设置
    Linux AT&T 汇编语言开发指南
    ELF动态解析符号过程(修订版)
    关注: Solaris 10的10大新变化
简明x86汇编语言教程 简明x86汇编语言教程第○章 写在前面 我不想夸大或者贬低汇编语言。但我想说,汇编语言改变了20世纪的历史。与前辈相比,我们这一代编程人员足够的幸福,因为我们有各式各样的编程语言,我们可以操作键盘、坐在显示器面前,甚至使用鼠标、语音识别。我们可以使用键盘、鼠标来驾驭“个人计算机”,而不是和一群人共享一台使用笨重的继电器、开关去操作的巨型机。相比之下,我们的前辈不得不使用机器语言 阅读详情

相关推荐

STM32F407编译下载全流程:从CubeMX到HEX烧录

嵌入式固件开发中,编译与下载是连接代码与硬件的关键闭环。其本质是将高级语言描述的逻辑,经交叉编译(ARM GCC)、链接(ELF生成)、格式转换(HEX/BIN)后,通过调试接口(SWD/UART)写入MCU Flash存储器的过程。该流程涉及工具链协同、二进制格式特性(如Intel HEX的地址映射与无元数据设计)、启动模式(System Memory Bootloader)、Flash选项字节(RDP/WRP)等底层机制,直接决定固件可运行性与安全性。典型应用场景包括STM32F407开发板调试、量产固

weixin_28840811的博客 231

x86---32汇编(2)---局部变量

  在C/C++编程中,我们经常会用到局部变量,笔者想知道在汇编语言中是 如何使用局部变量的,根据《X86汇编语言》中的例子,才弄懂了汇编是如何 分配局部变量。用栈指针减去一个值(需要分配的内存大小)。  代码: #include <stdio.h> #include <tchar.h> extern "C" void CalculateSums_(int a, ...

xuelanga000的博客 155

24、企业治理、风险与合规:精益思维的应用与实践

本文探讨了如何运用精益思维优化企业治理、风险与合规(GRC)流程,分析了职责分离与合规流程的误区,提出了预防性与检测性控制措施的合理搭配,并通过价值流映射、减少合规反馈循环等方法实现流程优化。以Etsy的PCI-DSS实施为例,展示了如何在满足法规要求的同时保持创新活力。文章还强调了构建协作型GRC文化的重要性,并展望了未来GRC的发展趋势,包括数字化转型、全球化风险管理及与可持续发展的结合。

solidity8miner的博客 86

x86-64汇编:过程和数组

假设过程P调用过程Q,Q执行后返回P,包含以下机制: 传递控制。在进入Q的过程时,程序计数器被设置为Q的代码的起始地址,返回时,把程序计数器调用Q后面那条指令的地址。 传递数据。P向Q传递参数,Q向P返回值。 分配和释放内存。Q为局部变量分配存储空间,返回时释放。 栈和程序计数器存放着传递控制和数据、分配内存的所有信息。 运行时栈 在过程P调用过程Q时,P以及所有在向上追溯到P调用链的过程,暂...

莱尼的博客 1227

x86_64汇编之一:AT&T汇编语法

汇编

humanof的专栏 1429

x86汇编知识以及应用详解

基础知识: 指令规则:movl $0 %eax,把常数0加载到eax寄存中。这里的后缀字母l表示long:4字节,此外还有w表示word:2字节(为了服从传统而已,其实现代意义的word就是4字节了),b表示byte:1字节。寄存器前面都得加个%,我猜这个是从编译器的指令模板来的,编译的指令模板就是movl %0 %1这样,其中%0表示占位符,也就是待替入的寄存器。 x86的栈是高地址--->低地址增长的形式。 x86寄存器名字不好记,没有arm的叫法容易区分(arm是R0-R15,多好听呀)。

biao2488890051的博客 3201

X86汇编语言学习手记(2) (转贴)

X86汇编语言学习手记(2)这是作者在学习X86汇编过程中的学习笔记,难免有错误和疏漏之处,欢迎指正。作者将随时修改错误并将新的版本发布在自己的Blog站点上。严格说来,本篇文档更侧重于C语言和C编译器方面的知识,如果涉及到基本的汇编语言的内容,可以参考相关文档。自X86 汇编语言学习手记(1)在作者的Blog上发布以来,得到了很多网友的肯定和鼓励,并且还有热心网友指出了其中的错误,作者已经将

597

[转载]X86汇编语言学习手记(2)

X86汇编语言学习手记(2)作者: BadcoffeeEmail: blog.oliver@gmail.com2004年11月原文出处: http://blog.csdn.net/yayong版权所有: 转载时请务必以超链接形式标明文章原始出处、作者信息及本声明这是作者在学习X86汇编过程中的学习笔记,难免有错误和疏漏之处,欢迎指正。作者将随时修改错误并将新的版本发布在自己的Blog站点上。严格说

spearous的专栏 1093

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

X86汇编语言学习手记(1)作者: BadcoffeeEmail: blog.oliver@gmail.com2004年10月原文出处: http://blog.csdn.net/yayong版权所有: 转载时请务必以超链接形式标明文章原始出处、作者信息及本声明这是作者在学习X86汇编过程中的学习笔记,难免有错误和疏漏之处,欢迎指正。作者将随时修改错误并将新的版本发布在自己的Blog站点上。严格说

庖丁坊 1543

深入学习X86汇编语言与处理器编程

X86汇编语言是针对X86架构的低级语言,是早期软件开发者与计算机硬件沟通的桥梁。它与机器语言有极高的相似性,但比机器语言更易读、易写。在现代计算机系统中,X86汇编语言依然占据重要地位,尤其在操作系统、嵌入式系统开发等领域。随着技术的发展,汇编语言也引入了一些高级特性以支持更复杂的编程需求。这些特性包括:宏指令(Macros):允许定义可重用的代码块,提高编程效率。结构体(Structures)与联合体(Unions):提供了一种组织数据和进行数据对齐的手段。

weixin_28988985的博客 1385

X86汇编语言学习手记

X86汇编语言学习手记这是作者在学习X86汇编过程中的学习笔记,难免有错误和疏漏之处,欢迎指正。严格说来,本篇文档更侧重于C语言和C编译器方面的知识,如果涉及到具体汇编语言的内容,可以参考相关文档。1. 编译环境   OS: Solaris 9 X86   Compiler: gcc 3.3.2   Linker: Solaris Link Editors 5.x   Debug Tool: 

zhy05的专栏 1411

全套KX驱动皮肤

全套KX驱动皮肤

上一篇: X86汇编语言学习手记(1)
下一篇: HOOK专题
linkqishi
博客等级 码龄21年 0粉丝 18原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值