首页 09-8086汇编
文章
取消

09-8086汇编

编程语言的发展

  • 机器语言

    • 由0和1组成
  • 汇编语言(Assembly Language)

    • 用符号代替了0和1,比机器语言便于阅读和记忆
    • 操作: 将寄存器BX的内容送入寄存器AX
    • 汇编语言: mov ax, bx
  • 高级语言

    • C\C++JavaSwiftGoPython等,更接近于人类的自然语言。

    1

    • 汇编语言与机器语言一一对应,每一条机器指令都有与之对应的汇编指令
    • 汇编语言可以通过编译得到机器语言,机器语言可以通过反汇编得到汇编语言
    • 高级语言可以通过编译得到汇编语言\机器语言,但汇编语言\机器语言几乎不可能还原成高级语言
  • 高级语言反编译表现

    2

  • 汇编语言的特点

    • 可以直接访问、控制各种硬件设备,比如存储器、CPU等,能最大限度地发挥硬件的功能
    • 汇编指令是机器指令的助记符,同机器指令一一对应。每一种CPU都有自己的机器指令集\汇编指令集,所以汇编语言不具备可移植性
    • 知识点过多,开发者需要对CPU等硬件结构有所了解,不易于编写、调试、维护
    • 不区分大小写,比如movMOV是一样的
  • 编译阶段比较

    • 采用高级语言C++和汇编语言编写同一个功能
      • 将a+b的结果赋值给c,然后在屏幕上打印c的结果

    3

  • 汇编语言的用途

    • 编写驱动程序、操作系统(比如Linux内核的某些关键部分)
    • 对性能要求极高的程序或者代码片段,可与高级语言混合使用(内联汇编)
    • 软件安全
      • 病毒分析与防治
      • 逆向、加壳、脱壳、破解、外挂、免杀、加密解密、漏洞、黑客
    • 是理解整个计算机系统的最佳起点和最有效途径
    • 为编写搞笑代码打下基础
    • 弄清代码本质
      • sizeof
      • switchif的效率究竟谁高?
  • 汇编语言的种类

    • 目前讨论比较多的汇编语言有
      • 8086汇编(8086处理器是16bit的CPU)
      • Win32汇编、Win64汇编
      • AT&T汇编(Mac、iOS模拟器)
      • ARM汇编(嵌入式、iOS设备)
    • 入门建议先从学些8086汇编开始,推荐参考书籍:王爽《汇编语言》

学前须知

  • 软件\程序的执行过程

    4

  • 最为关键的是需要了解CPU和内存

  • 在学习汇编语言过程中,遇到的绝大部分指令都是跟内存、CPU有关的

  • 总线概念及分类

    • 总线:一根根导线的集合

      5

    • 每一个CPU芯片都有许多管脚,这些管脚和总线相连,CPU通过总线跟外部器件进行交互

      6

    • 总线的分类:

      • 地址总线、数据总线、控制总线

      7

    • CPU从内存的3号单元读取数据

      • 通过地址线3去内存找到地址为3,通过控制线内看读写命令,然后通过数据线在地址3读取8.

      8

    • 总线

      • 地址总线
        • 它的宽度决定了CPU的寻址能力
        • 8086的地址总线宽度是20,所以寻址能力是1M(2^20)
      • 数据总线
        • 它的宽度决定了CPU的单次数据传送量,也就是数据传送速度
        • 8086的数据总线是16,所以单次最大传递2个字节的数据
      • 控制总线
        • 它的宽度决定了CPU对其他器件的控制能力、能有多少种控制。

      9

内存

  • 各类存储器的逻辑连接情况

    10

  • 内存地址及区域划分

    11

  • 各类存储器的逻辑连接-物理地址对应图

    12

  • 8086的寻址方式

    • CPU访问内存单元时,要给出内存单元的地址

    • 8086有20位地址总线,可以传送20位的地址,1M的寻址能力

    • 但它又是16位结构的CPU,它内部能够一次性处理、传输、暂时存储的地址为16位。如果将地址从内部简单地发出,那么它只能送出16位的地址,表现出来的寻址能力只有64KB

      13

    • 8086采用一种在内部用2个16位地址合成的方法来生成1个20位的物理地址

      14

      15

      16

  • 内存的分段管理

    • 8086是用“起始地址(段地址×16) + 偏移地址 = 物理地址”的方式给出物理地址

    • 为了开发方便,我们可以采取分段的方法来管理内存,比如:

      • 地址10000H~100FFH的内存单元组成一个段,该段的起始地址为10000H,段地址为1000H,大小为100H
      • 地址10000H1007FH、10080H100FFH的内存单元组成2个段,它们的起始地址为:10000H和10080H,段地址为1000H和1008H,大小都为80H

      17

      1
      2
      3
      4
      5
      6
      7
      
      地址合成
      段地址: 1200H
      偏移地址: 1000H
          
      物理地址 = 段地址 * 16 + 偏移地址
      物理地址 = 段地址 * 10H + 偏移地址
      物理地址 = 1200H * 10H + 1000H = 12000H + 1000H = 13000H
      
    • 寻址能力计算

      • 偏移地址为16位,16位地址的寻址能力为64KB,所以一个段的长度最大为64KB

      18

寄存器

  • CPU的典型构成

    19

  • CPU的典型构成

    • 对程序员来说,CPU中最主要部件是寄存器,可以通过改变寄存器的内容来实现对CPU的控制

    • 不同的CPU,寄存器的个数、结构是不相同的(8086是16位结构的CPU)

    • 8086有14个寄存器

      • 都是16位的寄存器
      • 可以存放2个字节

      20

  • 通用寄存器

    • AXBXCXDX这4个寄存器通常用来存放一般性的数据,称为通用寄存器(有时也有特定用途)
    • 通常,CPU会先将内存中的数据存储到通用寄存器中,然后再对通用寄存器中的数据进行运算
    • 假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间
      • CPU首先会将红色内存空间的值放到AX寄存器中:mov ax,红色内存空间
      • 然后让AX寄存器与1相加:add ax,1
      • 最后将值赋值给内存空间:mov 蓝色内存空间,ax

    21

  • 通用寄存器分类

    • AXBXCXDX这4个通用寄存器都是16位的,如下图所示

    22

    • 上一代8086的寄存器都是8位的,为了保证兼容, AXBXCXDX都可分为2个独立的8位寄存器来使用
      • H代表高位寄存器
      • L代表低位寄存器

    23

  • 字节与字

    • 在汇编的数据存储中,有2个比较常用的单位
      • 字节byte,1个字节由8bit组成,可以存储在8位寄存器
      • word,1个字由2个字节组成,这2个字节分别称为字的高字节低字节
    • 比如数据20000(4E20H,0100111000100000B),高字节的值是78,低字节的值是32

    24

    • 1个字可以存在1个16位寄存器中,这个字的高字节、低字节分别存储在这个寄存器的高8位寄存器、低8位寄存器中
  • 段寄存器

    • 8086在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理地址
    • 是什么部件提供段地址?段地址在8086的段寄存器中存放
    • 8086有4个段寄存器:CSDSSSES,当CPU需要访问内存时由这4个段寄存器提供内存单元的段地址
      • CS (Code Segment):代码段寄存器
      • DS (Data Segment):数据段寄存器
      • SS (Stack Segment):堆栈段寄存器
      • ES (Extra Segment):附加段寄存器

    25

    • 每个段寄存器的具体作用是?
  • CS和IP

    • CS为代码段寄存器,IP为指令指针寄存器,它们指示了CPU当前要读取指令的地址

    • 任意时刻,8086CPU都会将CS: IP指向的指令作为下一条需要取出执行的指令

      26

  • 指令的执行过程

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

  • 指令和数据

    • 在内存或者磁盘上,指令和数据没有任何区别,都是二进制信息

    • CPU在工作的时候把有的信息看做指令,有的信息看做数据,为同样的信息赋予了不同的意义

      42

    • CPU根据什么将内存中的信息看做指令?

      • CPU将CS: IP指向的内存单元的内容看做指令
      • 如果内存中的某段内容曾被CPU执行过,那么它所在的内存单元必然被CS: IP指向过
  • jmp指令

    • CPU从何处执行指令是由CS、IP中的内容决定的,我们可以通过改变CS、IP的内容来控制CPU执行目标指令

    • 8086提供了一个mov指令(传送指令),可以用来修改大部分寄存器的值,比如 mov ax,10mov bx,20mov cx,30mov dx,40

    • 但是,mov指令不能用于设置CSIP的值,8086没有提供这样的功能

    • 8086提供了另外的指令来修改CSIP的值,这些指令统称为转移指令,最简单的是jmp指令

      43

      44

  • Debug

    • 下载debug.exe、下载安装DOSBox

      1
      2
      3
      4
      5
      6
      
      把debug.exe放到D盘,打开DOSBox
      z:\>mount c d:/
      Device C is mounted as local directory d:/
      z:\>c:
      c:\>debug
      _
      
    • Debug的常用功能

      • 用Debug的R命令查看、改变CPU寄存器的内容。
      • 用Debug的D命令查看内存中的内容
      • 用Debug的E命令改写内存中的内容
      • 用Debug的U命令将内存中的机器指令翻译成汇编指令
      • 用Debug的T命令执行一条指令
      • 用Debug的A命令以汇编指令的格式在内存中写入一条机器指令
      • q命令: 退出Debug
      • p命令: 类似于step over(t命令类似于step into),可用于跳过loop循环
      • g命令: 跳过签名的代码,停留到指定的代码位置
    • R命令

      • 输入r可以查看所有寄存器的只
      • 输入r 寄存器名称可以修改寄存器的值
      • 输入r axax寄存器的只改为0100H
    • D命令

      • 输入d可以查看内存中的内容
      • 输入d 段地址:偏移地址查看特定位置的内存数据
      • 输入d 段地址:起始偏移地址 结尾偏移地址查看特定位置和特定范围的内存地址
      • 输入d 偏移地址d 起始偏移地址 结尾偏移地址,会将DS的内容作为段地址
    • E命令

      • 输入e 段地址:偏移地址 数据串修改特定位置的内存数据
      • 输入e段地址:偏移地址后按enter也可以修改特定位置的内存数据,数据之间用空格隔开
    • U命令

      • 输入uu 段地址:偏移地址可以将内存中的内容翻译为对应的汇编指令
      • 由3部分组成
        • 最左边一列: 是指令的地址段地址:偏移地址
        • 中间一列: 是指令对应的机器指令
        • 最右边一列: 是汇编指令
    • A命令

      • 输入aa 段地址:偏移地址可以从某位置开始写入汇编指令
  • DS和[address]

    • CPU要读写一个内存单元时,必须要先给出这个内存单元的地址,在8086中,内存地址由段地址和偏移地址组成

    • 8086中有一个DS段寄存器,通常用来存放要访问数据的段地址

      1
      2
      3
      
      mov bx, 1000H
      mov ds, bx
      mov al, [0]
      
      • 上面3条指令的作用将10000H(1000:0)中的内存数据复制到al寄存器中
      • mov al, [address]的意思将DS:address中的内存数据复制到al寄存器中
      • 由于al是8位寄存器,所以是将一个字节的数据复制给al寄存器
    • 8086不支持将数据直接送入段寄存器中,mov ds, 1000H是错误的

  • 大小端

    • 大端模式: 是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中(高低\低高)
    • 小端模式: 是指数据的高字节爆粗在北村的高地址中,二数据的低字节保存在内存的低地址中(高高\低低)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    16bit宽的数0x1234在CPU内存中的存放方式,假设从地址0x4000开始存放
    内存地址				  小端模式						大端模式
    0x4000           0x34               0x12
    0x4001					 0x12               0x34
    32bit宽的数0x12345678,CPU内存的存放方式
    内存地址				  小端模式						大端模式
    0x4000				   0x78							  0x12
    0x4001					 0x56								0x34
    0x4002					 0x34								0x56
    0x4003					 0x12								0x78
    
  • mov指令

    1
    2
    3
    4
    5
    6
    
    mov 寄存器, 数据					比如: mov ax, 8
    mov 寄存器, 寄存器			 比如: mov ax, bx
    mov 寄存器, 内存单元      比如: mov ax, [0]
    mov 内存单元, 寄存器      比如: mov [0], ax
    mov 段寄存器, 寄存器      比如: mov ds, ax
    mov 寄存器, 段寄存器      比如: mov ax, ds
    
  • add和sub指令

    1
    2
    3
    4
    5
    6
    7
    8
    
    add 寄存器, 数据					比如: add ax, 8
    add 寄存器, 寄存器			 比如: add ax, bx
    add 寄存器, 内存单元      比如: add ax, [0]
    add 内存单元, 寄存器      比如: add [0], ax
    sub 寄存器, 数据					比如: add ax, 8
    sub 寄存器, 寄存器			 比如: add ax, bx
    sub 寄存器, 内存单元      比如: add ax, [0]
    sub 内存单元, 寄存器      比如: add [0], ax
    
    • 栈: 是一种具有独特的访问方式的存储空间(后进先出,last in out first)
    • 8086会将CS作为代码段的段地址,将CS:IP指向的指令作为吓一跳需要去除执行的指令
    • 8086会将DS作为数据段的段地址,mov ax, [address]就是去除DS:address的内存数据放到ax寄存器中
    • 8086会将SS作为栈段的段地址,任意时刻,SS:SP指向栈顶元素
    • 8086提供了PUSH(入栈)和POP(出栈)指令来操作站段的数据
      • 比如push ax是将ax的数据入栈,pop ax 是将栈顶的数据送入ax。
  • 栈顶超界

    • 上面描述了执行push、pop指令是,发生的栈顶超界问题,可以看到,当栈满的时候再使用push指令入栈,或栈空的时候再使用pop指令出栈,都将发生栈顶超界问题
    • 栈顶超界是危险的,因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己程序中的,也可能是别的程序中的。但是由于我们在入栈出栈是的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。
  • push和pop

    • pushpop指令的格式可以是如下形式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
      push 寄存器			将一个寄存器中的数据入栈
      pop 寄存器				出栈,用一个寄存器介绍出栈的数据
      push 段寄存器		 将一个段寄存器中的数据入栈
      pop 段寄存器     出栈,用一个段寄存器接受出栈的数据
      push 内存单元		 将一个内存子单元出的子入栈。(注意: 栈操作都是以 								 字为单位)
      pop 内存单元     出栈,用一个内存单元接收出栈的数据
      如:
      mov ax, 1000H
      mov ds, ax
      push [0]
      pop [0]
      
  • 段总结

    • 我们用一个段存放数据,那将它定义为数据段;我们用一个段存放代码,那将它定义为代码段;我们可以用一个段当作栈,那将它定义为栈段
    • 对于数据段,将它的段地址放在DS中,用movaddsub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据来访问;对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令;对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中这样CPU在需要进行栈操作,比如执行push、pop指令等,就将我们定义的站短当作栈空间来用
  • 汇编语言的组成

    1
    2
    3
    4
    5
    6
    7
    8
    
    assume cs:code
    code segment
    	mov ax, 1122h
    	mov bx, 3344h
    	mov ah, 4c00h
    	int 21h
    code ends
    end
    
    • 伪指令 - segment、endsend
  • 中断

    • 中断时由于软件的或硬件的信号,使得CPU暂停当前的任务,转而去执行另一段子程序。
    • 在程序运行过程中,系统出现了一个必须由于CPU立即处理的情况,此时,CPU暂时中止当前程序的执行转而处理这个新情况的过程叫做中断。
    • 中断分类
      • 硬中断: 由外部设备(比如网卡、硬盘)随机引发的
      • 软中断: 由执行终端指令产生的,可以通过程序控制触发
    • 可以通过指令int n产生中断
      • n是中断码,内存中由一张终端向量表,用来存放中断码对应中断处理程序的入口地址。
      • CPU在接收到中断信号后,暂停当前正在执行的程序,跳转到中断码对应的中断向量表地址处,去执行中断处理程序。
      • 常见中断
        • int 10h 用于执行BIOS中断
        • int 3断点中断,用于调试程序
        • int 21h用于执行DOS系统功能条用,AH寄存器存储功能号
  • 打印Hello World

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    
    ;提醒开发者每个段的含义,写不写不影响程序
    assume cs:code, ds:data
      
    ;数据段
    data segment
        db "Hello World!$"
        string db "Hello World!$"
    data ends
      
    ;代码段
    code segment
    start:
         ;设置ds的值
         mov ax, data
         mov ds, ax
         ;打印字符串
         mov dx, 0h
         mov ah, 9h
         int 21h
         ;打印
         mov dx, offset string
         mov ah, 9h
         int 21h
         ;退出程序
         mov ax, 4c00h
         int 21h
    code ends
    ;编译结束,start是程序入口
    ;start所在的段就是代码段
    ;所以cs的值就是code段的段地址
    ;相当于cs的值已经自动设置完毕
    end start
    
  • 通过栈交换两个值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    assume cs:code, ds:data, ss:stack
    ;栈段
    stack segment
          db 10 dup(8)
    stack ends
    ;数据段
    data segment
         db 20 dup(9)
    data ends  
    ;代码段
    code segment   
    start:    
         ;手动设置ss和ds
         mov ax, stack
         mov ss, ax
         mov ax, data
         mov ds, ax
           
         mov ax, 1122h
         mov bx, 3344h
           
         ;使用栈
         mov sp, 10      
         push ax
         push bx
         pop ax
         pop bx                       
         ;退出
         mov ax, 4c00h
         int 21h ;产生中断, 21执行DOS系统功能调用
         
    code ends 
    end start
    
  • call和ret指令

    • call标号
      • 将下一条指令的偏移地址入栈后
      • 转到标号出执行指令
    • ret: 将栈顶的值出栈,赋值给ip
  • 函数使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    assume cs:code, ds:data, ss:stack
    ;栈段
    stack segment
          db 10 dup(8)
    stack ends
    ;数据段
    data segment
         db 20 dup(9)
         string db "HELLO WORLD!$"
    data ends  
    ;代码段
    code segment   
    start:    
         ;手动设置ss和ds
         mov ax, stack
         mov ss, ax
         mov ax, data
         mov ds, ax
           
         ;业务逻辑
         call print
                                  
         ;退出
         mov ax, 4c00h
         int 21h                
           
    ;打印字符串   
    print:
         ;打印,ds:dx告知字符串地址
         mov dx, offset string
         mov ah, 9h
         int 21h
         ret
         
    code ends 
    end start
    
  • 返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    
    assume cs:code, ds:data, ss:stack
    ;栈段
    stack segment
          db 100 dup(0)
    stack ends
    ;数据段
    data segment       
         res dw 0 ;全局变量
         db 100 dup(0)
    data ends  
    ;代码段
    code segment   
    start:    
         ;手动设置ss和ds
         mov ax, stack
         mov ss, ax
         mov ax, data
         mov ds, ax
           
         ;业务逻辑
         call mathFunc3
           
         ;1:mov bx, [0]           
         ;2:mov bx, res   
         mov bx, ax        
         ;退出
         mov ax, 4c00h
         int 21h                
           
    ;返回2的3次方
    ;返回值放到ax寄存器中 
    mathFunc3:
         mov ax, 2
         add ax, ax
         add ax, ax
         ret
    ;返回值放到全局变量res中
    mathFunc2:
         mov ax, 2
         add ax, ax
         add ax, ax
         mov res, ax
         ret  
    ;返回值放到ds:0中
    mathFunc1:
         mov ax, 2
         add ax, ax
         add ax, ax
         mov [0], ax
         ret
         
    code ends 
    end start
    
  • 传递2个参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    
    assume cs:code, ds:data, ss:stack
    ;栈段
    stack segment
          db 100 dup(0)
    stack ends
    ;数据段
    data segment       
         res dw 0 ;全局变量
         db 100 dup(0)
    data ends  
    ;代码段
    code segment   
    start:    
         ;手动设置ss和ds
         mov ax, stack
         mov ss, ax
         mov ax, data
         mov ds, ax
           
         ;业务逻辑  
         push 1122h
         push 3344h
         call sum3 
         add sp, 4 ;移到栈顶,栈(释放)
         mov bx, ax
         ;栈平衡: 函数调用前后的栈顶指针要一致,如果不平衡,栈空间迟早被用完.
         ;外平栈 add sp, 4
         ;内平栈 ret 4
           
         push 2233h
         push 4455h
         call minus
         add sp, 4
         mov bx, ax
           
         ;sum2 
         ;mov word ptr [0], 10h
         ;mov word ptr [2], 20h   
         ;call sum2
         ;mov bx, ax  
           
         ;sum1  
         ;mov cx, 10h
         ;mov dx, 20h
         ;call sum1
         ;mov bx, ax
                  
         ;退出
         mov ax, 4c00h
         int 21h                
      
    ;返回值放a寄存器     
    ;传递两个参数
    ;相减  
    minus:
         mov bp, sp
         mov ax, ss:[bp+2]
         sub ax, ss:[bp+4]
         ret
    ;传递2个参数(放到栈中) 
    ;相加
    sum3:
        ;访问栈中的参数
        mov bp, sp
        mov ax, ss:[bp+2] 
        add ax, ss:[bp+4]    
        ret
    ;传递2个参数(分别放到ds:0, ds:2)
    sum2:
        mov ax, [0]
        add ax, [2]
        ret   
    ;分别放在cx,dx中
    sum1:
         mov ax, cx
         add ax, dx
         ret
         
    code ends 
    end start
    
  • 函数调用的本质

    • 参数: push 参数值
    • 返回值: 返回值存放到ax
    • 栈平衡
  • 局部变量

    1
    2
    3
    4
    5
    6
    
    int sum(int a, int b) {
        int c = 3;
        int d = 4;
        int e = c + d;
        return a + b + e
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    
    assume cs:code, ds:data, ss:stack
    ;栈段
    stack segment
          db 100 dup(0)
    stack ends
    ;数据段
    data segment       
         res dw 0 ;全局变量
         db 100 dup(0)
    data ends  
    ;代码段
    code segment   
    start:    
         ;手动设置ss和ds
         mov ax, stack
         mov ss, ax
         mov ax, data
         mov ds, ax
           
         ;业务逻辑  
         push 1
         push 2
         call sum
         add sp, 4 ;移到栈顶,栈清空  
         mov bx, ax
                  
         ;退出
         mov ax, 4c00h
         int 21h                
      
    ;传递2个参数(放到栈中) 
    ;相加
    sum: 
        ;保护bp
        push bp
        ;访问栈中的参数
        mov bp, sp      
        ;局部变量,减10
        sub sp, 10  ;预留10个字节的空间给局部变量 
          
        ;保护可能会用到的寄存器
        push si
        push di
        push bx     
          
        ;给局部变量空间填充int 3 (cccc), int 3->中断
        ;stosw的作用,将ax的值拷贝到es:di中,di的值同时 +2    
        mov ax, 0cccch
        mov bx, ss
        mov es, bx
        mov di, bp
        sub di, 10
        ;cx决定了stosw的执行次数,rep一次cx减1
        mov cx, 5
        ;rep的作用,重复执行某个指令,次数有cx决定
        rep stosw
          
        ;------ 业务逻辑 --------
        ;定义两个局部变量
        mov word ptr ss:[bp-2], 3
        mov word ptr ss:[bp-4], 4
        ;int e = c + d    
        mov ax, ss:[bp-2]
        add ax, ss:[bp-4]
        mov ss:[bp-6], ax
        ; a + b + e
        mov ax, ss:[bp+2] 
        add ax, ss:[bp+4] 
        add ax, ss:[bp-6]
        ;------ end ---------
                  
        ;恢复寄存器的值
        pop bx
        pop di
        pop si        
        ;恢复sp   
        mov sp, bp
        ;恢复bp
        pop bp 
           
        ret
    code ends 
    end start
    
    • 函数的调用流程(内存)

    1.push 参数

    2.push 函数的返回地址

    3.push bp,保留bp之前的值,方便以后恢复

    4,mov bp, sp,保留sp之前的值,方便以后恢复

    5.sub sp, 空间大小,分配空间给局部变量

    6.保护可能要用到的寄存器

    7.使用cc(int 3 中断)填充局部变量的空间

    8.执行业务逻辑

    9.恢复寄存器之前的值

    10.mov sp, bp,恢复sp之前的值

    11.pop bp,恢复bp之前的值

    12.ret,将函数的返回地址出栈,执行下一条指令

    13.恢复栈平衡 add sp, 参数所占的空间

  • 栈帧(Stack Frame Layout)

    • 就是一个函数执行的环境

    • 包括: 参数、局部变量、返回地址

    • 仅包括在本函数内部执行的所有内存操作

      45

      46

      47

  • JCC跳转指令

本文由作者按照 CC BY 4.0 进行授权

08-动态调试

10-AT&T汇编