导图社区 程序的机器级表示
跳转表在汇编代码中与目标文件反汇编中的不同;相关寄存器(在caller和callee中都将要用到的寄存器)入栈。
编辑于2022-04-07 00:04:49程序的机器级表示
控制流
基础概念
条件码
即标志位,每个条件码占一个bit。所有条件码存在一个特殊的条件码寄存器中
常见条件码
ZF
零标志。结果为0时ZF置1.
OF
有符号数溢出标志
CF
最高位产生进位,无符号数溢出
SF
计算结果为负数,则SF置1
设置/改变条件码
cmpl S,D
计算 Des - Src,但是不储存结果,只改变标志位。也不改变Des和Src的值。
test S,D
计算S & D,根据计算结果改变标志位,但是不影响S和D的值,也不储存计算结果。 
setX系列指令

条件码的设置是条件跳转/传送的基础
汇编跳转指令
无条件跳转
jmp + 标号
jx系列指令

条件传送
cmovX系列指令
描述:if (Test) Dest <-- Src 先计算一个条件操作的两种结果,然后根据条件选择某一个
优势
更好的匹配现代处理器的特性
CPU无需做分支预测,
避免预测错误的代价
问题
计算代价较大
两个计算过程都需要运行 。 一般来说,只有两个计算过程都比较简单的时候,才能够发挥优势。
可能产生非法操作
 在p为0的时候,仍然会去引用 *p, 从而产生非法操作
可能产生意料之外的赋值
 两个表达式都对x进行了赋值,最终得到的x的结果与计算顺序有关(竞争)
示例
使用条件传送的汇编
使用跳转指令的汇编
控制模式
分支
switch
跳转表
实质
程序维护跳转表的表头指针,它是一个指针数组的首地址
即系统维护的这个表头指针是”指向指针的指针“
访问方式
以表头指针为基地址,用偏移量访问
假设.L4是跳转表的表头指针。该表有6个元素。 要访问第若干个元素,则: movl .L4(,%eax,4) , %ebx .L4(%eax , 4)取的是地址为.L4+4*%eax的内存 中的内容,也就是一个地址。这个地址是即将跳 转到的程序段的首地址
结构
跳转表结构
default
case 1
case 2
......
case n
含有跳转表的代码的结构
特殊情况
fall through
case之间缺少break,执行完case k后继续执行case k+1等等 代码的具体差别:缺少一个jmp + default段入口地址
多个x值对应相同操作
跳转表中(就是那个地址数组)对应 case 5 和 case 6 的元素值是也一样的。 实例的跳转表长这样: 0x80484f0: 0x08048400 0x080483d6 0x080483e2 0x080483f0 0x8048500: 0x08048400 0x080483f8 0x080483f8 解析一下: 0x80484f0是指向跳转表头的指针,也就是0x08048400的地址。 地址数组的内容如下: 0x08048400 default 0x080483d6 case1入口 0x080483e2 case2入口 0x080483f0 case3入口 0x08048400 default (其实是case4,但因为4在条件中没出现所以x=4被归入default) 0x080483f8 case5入口 0x080483f8 case6入口 (5、6一样)
x是有符号数,却使用ja的原因分析
因为当x是负数时,经过switch判断时,也应该跳转到default, 用无符号数的判断规则: 由于负数的最高位为1,当x是负数时机器判断出来是比6要大的,CF和ZF会被置为0,跳转到default; 用有符号数的判断规则(使用jg): 负数会小于6,达不到这样的效果。它可能跳到一个奇怪的地方去。
case多个分支差别特别大时的情况
编译器会直接使用跳转指令而不是跳转表,因为这样的话跳转表会特别特别大。如果最终大的case是10000,那么跳转表就要有10000个节点,这显然是不经济的。
跳转表在汇编代码中与目标文件反汇编中的不同
在.s文件中.L4只是一个标号
在.o文件反汇编的结果中.L4变成了一个偏移量
在可执行文件中.L4是一个具体地址
根据这个地址可以用gdb工具查看内存看到跳转表。
实例(非常重要!)
if-else
Expression ?condition1 :condition2
循环
while
do-while
for
goto
过程调用
基础概念
栈操作指令
push X
X是将要入栈的操作数;入栈的目的地是当前程序的栈顶。
pop Des
特别注意:Des描述的是弹栈操作将要把当前栈顶的值弹出到哪里去。被弹出的对象是缺省的。
不允许针对单字节的出入栈(只能4byte一起出)
栈的基本性质
向下(低地址)增长
esp减少:push esp增加:pop
后进先出
参数
esp栈顶
ebp栈底
阶段
set up
caller's ebp 入栈
相关寄存器(在caller和callee中都将要用到的寄存器)入栈

将ebp与esp拉平
对齐(可选)
body
local variable 入栈
特点
以ebp作为基地址,到caller的传递参数区取变量
最典型的是mov 8(%ebp),%eax; 因为它上面至少有一个返回地址, 和一个caller的ebp,所以偏移量 至少是8.
顺序
与编译器有关
为将要被调用的函数准备参数
特点
以esp作为基地址
顺序
根据函数参数列表,从右到左依次压入栈
返回地址入栈
从callee中返回
栈的建立
栈是在程序进行的过程中逐步建立的,不是一开始就建好,后面只用就行
finish
将返回值(如果有的话)放入eax中准备返回
从过程中返回 ret
弹出返回地址(pop %eip)
跳转到该地址
回收局部变量 leave
mov %ebp,%esp
释放栈空间
pop %ebp
恢复caller的ebp
实例
特殊情况
嵌套
递归
汇编基础
基本概念
汇编代码AT&T格式
指令编码
源操作数
目的操作数
操作数类型
立即数
寄存器
内存
寄存器
通用寄存器
ebx
ecx
eax
固定用于传递函数的返回值
ax
低16位
al
ax的低8位,也是eax的低八位
ah
ax的高八位,也即eax[15:8].
edx
esi
source index
edi
destination index
特殊寄存器
esp
栈顶指针
ebp
栈的基地址(栈底指针)
eip
指令计数器pc,储存下一条指令的地址
gdb工具基本命令
p(print)
x(examine)
display
i reg
b(break)
调试父子进程
Linux ps 命令
取得进程的pid
attach pid
数据显示
形式
t(二进制)
x(十六进制)
d(十进制)
字节数
b(byte)
h(双字节)
w(四字节)
常见指令
编译器会根据指令后面的操作数的长度给指令加后缀。 加l(如mov --> movl),说明mov的操作数是32位的。 加w说明是16位 加b说明是8位
转移类指令
mov
寻址方式
直接寻址
寄存器-->寄存器
movl %eax,%ebx
立即数-->寄存器
movl $3,%eax
间接寻址
基址变址寻址计算公式:  比例因子s只能是1,2,4,8.
寄存器 <-->内存
movl arr(%edx,%eax,4) , %ebx
内存变址寻址的一般公式: 偏移量(a,b,c)--> 偏移量+a+b*c 注意a、b、c、偏移量均可以缺省,但逗号不可省略。 偏移量和a、b缺省值为0,c的缺省值为1.
立即数-->内存
lea
cpu中有专门的地址计算单元。lea指令最初只用于地址计算,后来发现也可以做简单算术操作,且比使用乘法指令速度更快。
取变量的地址
lea S,D
&S --> D
完成某些三元运算
lea C(%ebx,%edx,B),%eax
计算x+B*y+C
地址计算
lea Bias(X,Y,Z),D
算术操作指令
单操作数
一般操作
inc
自增
dec
自减
neg
取补,即按位取反再加1
not
按位取反
也属于逻辑运算
64位系统特殊操作

双操作数
普通算术运算
加了后缀
addl S,D
subl,S,D
D-S --> D
imull S,D
S*D --> D
位运算
逻辑运算
按位运算
andl S,D
orl S,D
xorl S,D
移位运算
sall S,D
算术左移 Dest = Dest << Src
shll S,D
sarl S,D
shrl S,D