分析程序隐含的低效率,例如,访问数组元素的顺序的不同a[i][j]
,a[j][i]
了解程序运行时的行为
了解程序漏洞的产生
主要是分析Inter-IA32
,32位操作系统,在X86
架构下的体系指令 (课程学习只学到32位的,还没学X86-64
即64位的)
高级语言程序:不关心CPU,也极少直接访问硬件资源
汇编语言程序:更关心CPU,看到更多
–整数寄存器:8个,eax, ebx, ecx, edx, …
–程序计数器:1个,PC
–条件码寄存器:1个
不同整数数据类型,汇编代码的区分,以add传送指令为例:
– addb (b:byte,8位)
– addw (w:word,16位)
– addl (l:long word,32位)
整数与浮点数的区分:不同指令,不同代码
信息,即数据,放在存储器,CPU中的寄存器
x可以是:常数、寄存器、存储器引用
y可以是:常数、寄存器、存储器引用
–常数:直接给出数本身,如“z = $77 + x”
–寄存器:给出寄存器名字,如指令z = %eax + $44
–存储器引用:
给出能计算存储器地址的形式:`Imm(Eb, Ei, s)`,
地址计算:`Imm+Eb+Ei*s`
例如指令z = %ecx + 9(%eax,%edx,4)
,后一个操作数地址的计算形式是:Imm+Eb+Ei*s=9+eax+edx*4
1.立即数 | 2.寄存器 | 3.存储器 |
---|---|---|
(immediate) | (register) | (memory) |
寻址模式:
Imm
(Eb , Ej , s)由四部分组成立即数偏移
Imm
,基址寄存器Eb , 变址寄存器 Ej , 比例因子s(值只能为1,2,4,8其中一个,也可以没有)存储器地址的计算形式:
Imm+Eb+Ei*s
–mov S,D
相当于 D(目的操作数)=S(源操作数)
mov
:传送
例如:movl %eax, %ebx
要求:两操作数长度一致
movs
:符号扩展传送
例如:movswl %ax, %eax
要求:两操作数前短后长
movz
:零扩展传送
例如:movzwl %bx, %ebx
要求:两操作数前短后长
两个操作数不能都指向存储器
有定义如下:
int A;
char c=‘a’;
unsigned char d=‘a’;
则
d=c; 相当于 movb c, d
A=c; 相当于 movsbl c, A
A=d; 相当于 movzbl d, A
push、pop:栈操作
push功能:将数据进栈
其操作相当于:
subl
$4, %esp
movl
要进栈的数据, (%esp)
pop功能:将栈顶数据弹出
其操作相当于:
movl
%esp, 数据弹出的存储位置
addl
$4, %esp
例子
C语言代码:
int exchange(int *xp, int y)
{
int x = *xp;
*xp = y;
return x;
}
汇编代码
movl 8(%ebp), %edx
movl (%edx), %eax
movl 12(%ebp), %ecx
movl %ecx, (%edx)
出栈操作,先把
8(%ebp)
的数据出栈,传给%edx
,就得到*xp
(%edx)-> %eax
将*xp
的值赋给x
12(%ebp)->%ecx
然后到12(%ebp)
的数据出栈,传给%ecx
,就得到y
%ecx-> (%edx)
将*xp
赋给y
1.一元指令(只有一个操作数):xxx D
inc D,dec D,not D
increase , decrease
例:
inc %eax ;加一
dec %ebx ;减一
not %ecx ;非
若:已知执行上述三条指令前
eax=1,ebx=10,ecx= -1
,则,执行上述指令后,
eax=?ebx=?ecx=?
2,9,1
xxx S,D
add,sub,xor,or,and
add,subtract
例:
add %eax, %ebx ;加
sub %eax, %ecx ;减
xor %eax, %edx ;异或
or %eax, %edi ;或
and %eax, %esi ;与
若:已知执行上述五条指令前
eax=1,ebx=10,ecx=20,edx=1,edi=0,esi=1
,则,执行上述指令后,
ebx=?ecx=?edx=?edi=?esi=
?
11,19, 0,1, 1
xxx k,D
(k是移位次数)是在二进制的基础上进行移位sal,sar,shl,shr (其中sal和shl功能完全等价)
在计算机中只能以二进制的形式进行移位,向左移动 |n| 位,就相当于乘于2n ,其中n也分正负值,向左为正值,向右为负值。
算术左移,操作数左移,最低位补0,逻辑左移,操作数左移,最低位补0。
算术右移,操作数右移,最高位不变,逻辑右移,操作数右移,最高位补0。
左移操作相当于乘法操作,它是会让操作数变大的,也就是说,它随时存在着溢出的风险。若左移操作保留符号位的话,那么左移会出现越来越小的情况,但是你的本意是让这个数变大最后却发现左移后的数变小了,为了规避这种溢出带来的问题,统一了左移操作,也就是说,在左移操作中,寄存器只负责储存这个数(不管是否合乎人意,它只要是个数就行),若没有溢出的时候,则原样存储。所以说左移操作是会改变符号位的。
我们知道右移操作的算术操作是除法,那么在计算机中是肯定让这个数变小的,所以不存在溢出的风险,那么设计者就把是否保留符号位的权力交给了程序编写人员,所以
SAR
和SHR
是两个不同的操作。维基百科中有介绍:
Logical shifts are best used with unsigned numbers
逻辑移位最好用于无符号数In an arithmetic shift, the spaces are filled in such a way to preserve the sign of the number being slid. For this reason, arithmetic shifts are better suited for signed numbers in two’s complement format
在算术移位中,移位空间被补0或者保留高位(即符号位)的方式处理,因此,算术移位操作更适用于两个有符号数的补码操作
字母代表的意思:
s/sh:shift
a:arithmetic
l:left
r:right
例:
sal 1,%eax ;算数左移
sar 3,%eax ;算数右移(前面补符号)最高位不变
shl 4,%eax ;逻辑左移
shr 2,%eax ;逻辑右移(前面补零)
若:已知执行上述三条指令前
eax=16
,则单独执行上述各指令后,eax
各是多少?
100000,11110,100000000,00100
leal S,D
;功能:D <- &S,类似C中的取地址操作
例:leal (%eax), %ebx
指令 | 效果 | 描述 |
---|---|---|
leal S, D | D <-&S | 加载有效地址 |
INC D | D <- D+1 | 加1 |
DEC D | D <- D-1 | 减1 |
NEG D | D <- -D | 取负 |
NOT D | D <- ~D | 取补 |
ADD S, D | D <- D + S | 加 |
SUB S, D | D <- D - S | 减 |
IMUL S, D | D <- D * S | 乘 |
XOR S, D | D <- D ^ S | 异或 |
OR S, D | D <- D | S | 或 |
AND S, D | D <- D & S | 与 |
SAL k, D | D <- D< |
左移 |
SHL k, D | D <- D< |
左移(等同于SAL) |
SAL k, D | D <- D>>L k | 算术右移 |
SHL k, D | D <- D>>L k | 逻辑右移 |
">>A “和”>>L "分别表示算术右移和逻辑右移
SHL
和SAL
两者对应操作的机器码是完全相同的,因此SHL
和SAL
是不同名的同种操作
SAR
和SHR
是两个的操作对应的机器码是不同的,所以SAR
和SHR
是两个不同的操作。
补充:(忘了的偷瞄一下)
分支、循环都涉及转移地址
•实现C语言中的
if
for
while
switch
除了整数寄存器,CPU还维护着一组单个位的条件码(condition code)寄存器,它们描述了最近的算术或逻辑操作的属性。可以检测这些寄存器来执行条件分支指令。
条件值 | 进位 - CF | 零 - ZF | 溢出 - OF | 负 - SF |
---|---|---|---|---|
Carry Flag | Zero Flag | Overflow Flag | Sign Flag | |
1 | 有进位 | 为零 | 溢出 | 为负 |
0 | 无进位 | 非零 | 未溢出 | 非负 |
举个栗子
附:无特殊说明,统一按有符号数计算
比较指令
cmp S,D ; D-S (比较)
类似于sub S,D,设置条件码,但不存减的结果
例如,若S=0 0001110,D=0 0001111,则该指令后各条件值是:无进位,不为零,无溢出,非负。即:CF=0,ZF=0,OF=0,SF=0
test S,D ; D&S (测试)
类似于and S,D,设置条件码,但不存与的结果
跳转指令
无条件转移指令 jmp
地址
格式:jmp 目标地址
条件转移指令jX
地址
j / jmp :jump
单个条件码判断的转移指令
jz
:若结果为零,即zf
=1,则转移
jnz
:若zf
=0,则转移
js
:若结果为负,即sf
=1 ,则转移
jns
:若sf
=0,则转移
j/jmp:jump
z: zero
s: signed
n: not
举个栗子:
设(ax)=0x8000, (bx)=0x8000
cmp bx, ax 因为ax-bx是等于0, 所以zf为真
jz L2 程序将转到语句2
L1: 语句1
L2: 语句2
e和z的区别
e: equal 判断是否相等
比较结果为零,说明两数相等,所以
jz=je,jnz=jne
无符号数比较的转移指令
ja:高于则转移(a > b)
jna:不高于则转移(a <= b)
jb:低于则转移(a < b)
jnb:不低于则转移(a >= b)
j/jmp:jump
a: above
b: below
n: not
举个栗子:
设(al)=1111 1111b, (bl)=0000 0000b,
分别为255,0
cmp al, bl 0低于255,即bl低于al,
jb L2 将转去语句2的位置
L1: 语句1
L2: 语句2
带符号数比较转移指令
jg:大于则转移(a>b)
jng:不大于则转移(a <= b)
jl:小于则转移(a < b)
jnl:不小于则转移a >= b)
j/jmp:jump
g: greater
l: less
n: not
举个栗子:
设(al)=1111 1111b, (bl)=0000 0000b,
分别为-1,0
cmp al, bl 0大于-1,即bl大于al
jg L2 将转去语句2的位置
L1: 语句1
L2: 语句2
CMP
jmp
、条件转移jX
三种循环:
do while 先执行后判断
for 相当于 if + do while
while 相当于 if + do while
do-while | while | for |
---|---|---|
do | while (条件) | for(初始化表达式;条件;更新表达式) |
body | body | body |
while(条件) |
栈:一片内存区域,按栈的操作规则使用
只能从栈顶进行操作
栈的增长方向:低地址方向
esp(extended stack pointer 栈指针):栈顶指针寄存器,指示栈顶位置.
ebp(extended base pointer 帧指针): 栈底指针寄存器, 指示栈底位置
当程序执行的时候,栈指针可以移动,因为大多数信息的访问都是相对于帧指针的.
栈的操作:push,pop
push S:进栈指令
esp - 4 -> p
S ->(esp)
pop D:出栈指令
(esp)-> D
esp + 4 -> esp
eip: extended instruction pointer
“eip指哪里,CPU打哪里”
call 子过程:过程调用指令
保存返回地址
转入子过程
相当于:
push 返回地址
jmp 子过程
ret:过程返回指令
从子过程返回
相当于:pop eip
寄存器:共享资源,需要保护
保护寄存器:保护惯例
调用者保护:eax, ecx, edx
被调用者保护:ebx, edi, esi
每个过程需要存储自己的一些信息:
存储返回信息
保存寄存器
存储本地变量
传递过程参数
栈帧(stack frame):为每个过程分配的栈
esp:栈顶
ebp:栈底
栈帧内容(被调用者的):
调用者的ebp
保存的寄存器
局部变量
参数区
每个过程有三部分:
建立栈帧
程序主体
结束
补充:
int *p;
p是地址,其指向的存储单元存放的须是个int型数据
int **cp;
cp是地址,其指向的存储单元存放的须是一个int*型数据(即整型指针,其指向的存储单元存放的是一个int型数据)
指针类型的强制转换:
设char *p;则p+7 计算为 p+7,
而(int *)p+7则相当于(int *)p+7 ,所以计算为p+28
地址运算符:
未完待续。。。。。。。
计算机组成原理与汇编语言(三)