要理解指令和汇编部分,必须先掌握C语言内存分配和X86寄存器与栈帧知识

C语言内存分配

参考链接: C语言的内存分配

  1. stack-栈: 由编译器自动分配释放
  2. heap-堆: 一般由程序员分配释放,若程序员不释放 - 程序结束时可能由OS回收
  3. 全局区(静态区):全局变量静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域``,``未初始化的全局变量和未初始化的静态变量在相邻的另一块区``域 - 程序结束释放。
  4. 另外还有一个专门放常量的地方 - 程序结束释放

X86寄存器和栈帧

Stack Frame

参考链接: X86-64寄存器和栈帧

| stack.png | C语言属于面向过程语言,他最大特点就是把一个程序分解成若干过程(函数),比如:入口函数是main,然后调用各个子函数。在对应机器语言中,GCC把过程转化成栈帧(frame),简单的说,每个栈帧对应一个过程

左图中:
- 自下到上地址变大
- 栈帧的生长方向自上到下

X86典型栈帧结构中
-
%ebp指向栈帧开始,%esp指向栈顶**
esp是堆栈指针,无法暂借使用,所以一般使用ebp来存取堆栈 | | :—-: | —- |

寄存器

| image.png |
- x86-32CPU包含8个存储32bits的通用寄存器,从%eax到%esp(见左图👈,由于发展原因其命名历经变化),主要功能为管理栈、传递参数、保存返回值、存储**局部和临时**数据


- 当16bits使用时从
%ax到%sp,高8位以H标识%ah,%bh…低8位以L标识%al,%bl…

- x86-64(x64)的CPU包含16个存储64bits的通用寄存器,都以%r开头(见CSAPP中文第三版3.4)
| | :—-: | —- |


指令

参考链接: 16位汇编程序设计

指令格式

Snipaste_2019-12-20_12-05-12.png

  • 操作符:表示CPU支持的操作
  • 操作数/操作数地址:访问操作数的模式

不同的指令长度\可携带操作数个数\操作数访问模式都可能不同

指令循环

每一条指令的执行都经过“取址-译码-执行”的指令循环
示例代码分析

  1. #include <stdio.h>
  2. int x;
  3. void main()
  4. {
  5. int y;
  6. int z = 0;
  7. unsigned char* cptr;
  8. x = 5;
  9. }

VS下以x86-32调试(x64下采用%r开头的寄存器)反汇编结果与逐行分析
参考链接: 函数参数压栈,栈帧ebp,esp怎样移动的?

  1. --- D:\ProjectSet\CProject\slp\slp.cpp -----------------------------------------
  2. #include <stdio.h>
  3. int x;
  4. void main()
  5. {
  6. 55 push ebp //保存当前栈帧
  7. 8B EC mov ebp,esp //改变栈帧,以后访问参数通过ebp([ebp±k])
  8. ,访问局部变量通过esp
  9. 81 EC E4 00 00 00 sub esp,0E4h //跳过0E4大小地址块用于当前栈帧保存局部变量
  10. 56 push esi
  11. 57 push edi //将ebx,esi,edi压入栈
  12. 8D BD 1C FF FF FF lea edi,[ebp+FFFFFF1Ch]
  13. //加载有效地址,把[ebp+FFFFFF1Ch]
  14. 读取到edi作为参数1,根据补码运算即为[ebp-0E4]
  15. 即当前帧局部变量保存区域基址
  16. B9 39 00 00 00 mov ecx,39h //令ecx等于0x39,作为下面rep指令重复次数
  17. B8 CC CC CC CC mov eax,0CCCCCCCCh //令eax等于CCCCCCCC
  18. F3 AB rep stos dword ptr es:[edi]
  19. //es为辅助段寄存器,es:[edi]即辅助段基址+偏移
  20. //循环使用eax中的值CCCCCCCC初始化es:[edi]指
  21. 向空间(该串表示未初始化),步长为一个双字指针大
  22. 8字节(2*32b),恰好为CCCCCCCC长度,循环0x39
  23. (57)次----总结:循环使用cc初始化辅助段
  24. B9 03 C0 7A 00 mov ecx,7AC003h //将7AC003存入ecx
  25. E8 E0 FA FF FF call 007A1208 //调用地址007A1208处子程序,这里为debugger
  26. 相关的程序,无需深究
  27. int y;
  28. int z = 0;
  29. C7 45 EC 00 00 00 00 mov dword ptr [ebp-14h],0 //将0放入到双字指针ptr指向的位置,
  30. 即地址ebp-14的内存空间
  31. unsigned char* cptr;
  32. x = 5;
  33. C7 05 38 A1 7A 00 05 00 00 00 mov dword ptr ds:[007AA138h],5
  34. //x是全局变量,位于整个程序数据端,非栈内数据
  35. 区域,寄存器ds存放数据段基址,偏移量为007AA138,
  36. imm 5放入007AA138指向的内存空间
  37. }
  • ⭐movdword ptr ds:[007AA138h],5直接对应C7 0538 A1 7A 0005 00 00 00
  • ⭐寄存器再认识:通过上述分析,可以看出来寄存器本身可存放一个32/64bits数,即一个地址,所以寄存器就是指针
  • ⭐内存分配再认识:分析中可以看出

int z = 0对应movdword ptr [ebp-14h],0局部变量通过ebp±k访问,调试验证
image.png
x = 5对应movdword ptr ds:[007AA138h],5全局变量通过程序数据段基址+偏移访问
image.png

  • mov和lea类似,但[]之于二者作用恰好相反: 参考链接: 汇编中mov,lea指令的区别 | 对于变量num=2 | 对于寄存器ax,bx | | :—-: | :—-: | |
    - mov ax,num表示把2放入ax
    - mov ax,[num]表示把&num放入ax
    - lea ax,num表示把%num放入ax
    - lea ax,[num]表示把2放入ax
    |
    - mov bx,[ax]表示取地址:把as指向数据放入bx
    - mov bx,ax表示取ax自身值放入bx
    - lea bx,[ax]表示取ax自身值放入bx
    - lea bx,ax表示取地址:把as指向数据放入bx
    |

  • rep stos: 参考链接 rep stos 指令(Intel汇编)


operand寻址模式

(Operand-addressing Mode)操作数寻址模式决定了指令循环中会被取到哪个数据项

Ch3 指令 - 图6 | 寻址模式 | | 指令 | 地址生成 | 说明 | | :—-: | :—-: | :—-: | :—-: | —- | | 寄存器 | | movAXBX | AX存储BX的值 | 操作数都在通用reg | | 立即 | | movAX0464H | BX存储0464H | 直接给出操作数 | | 直接 | | movAX[0464H] | AX存储地址位0464H的空间中数据 | 给出操作数地址 | |

| 间接 | movAX[BX] | DS10H+BX作为地址,将对应空间数据存入AX | 给出一个操作数地址所在reg | | | 基址
+
变址
| movAX[BX+SI] | DS
10H+BS+SI作为地址,将对应空间数据存入AX | 给出操作数地址所在reg和存放变址的reg | | | 寄存器相对 | movAX[BX+4H] | DS*10H+BX+4H作为地址,取其中数据放入AX | 给出操作数地址所在reg和一个偏移量 |


选择题知识点

  1. program counter程序计数器:存储下条指令地址

instruction register指令寄存器:存储下条指令

  1. 为CPU设计少量fast memory(cache)的目的①使得指令更短②使得指令更快,与compiler无关
  2. debugger停止的原因:不确定,可能由于断点,可能由于异常
  3. 指令集:sparc:RISC,x86: CISC
  4. 如何再断点调试时查看变量值①鼠标指针放在变量上②添加监视,不能使用printf
  5. 当程序执行完一个非分支\跳转语句后,PC指向下条指令
  6. CPU 寄存器:是1word大小的CPU memory,可显式地加载/卸载编译器生成的指令
  7. 当一组连续的内存位置包含整数0xC605CD623A8365000000时,该区域可能有

①整数0xC605CD623A8365000000②字符串③一条指令

  1. 分支语句:把PC设为两个可能值中一个

跳转语句:无条件地把PC设为其操作数

  1. 机器码并没有保存源码所有信息
  2. 经过compiler对优化后的机器码①更短,运行更快②更难以调试,但不能说更clearer
  3. 汇编程序不可能同时使用AX和AL:如图,AL会影响AX

未命名文件.png