RISC(精简指令集)指令数量少、功能简单、一般长度固定编译器实现复杂,CISC(夏杂指令集) 指令数目庞大、复夏杂、指令长度不固定。
汇编基本格式
Intel格式:mov eax, 5 分别是指令助记符、目标操作数、源操作数 window英特尔比较多 AT&T格式: movl $5 %eax 分别是指令助记符、源操作数、目标操作数 Linux、Unix比较多
操作数可以是立即数也可以是一个寄存器的值
mov eax, 5(源操作数是立即数5,目标操作数是寄存器eax,这条指令表示将5赋值给eax寄存器)
操作数也可以是某个内存地址的值
mov eax, dword ptr[exb] mov byte ptr[esp+8], 5 mov word ptr[eax] 1234h 将EBX寄存器的值作为一个地址,将这个地址内的数据赋值给EAX寄存器
dword ptr 表示使用指针间接寻址
字节(byte)、字(word)两个字节、双字(dword)两个字、四字(qword)。
常用汇编指令
数学运算
加法:add 减法: sub 乘法:mul / imul 除法:div / idiv
add eax, 5 eax = eax + 5; 给eax寄存器的值加5(立即数),同时eax也是运算后保存结果的地方
add eax, ebx eax = eax + ebx; 给eax寄存器的值加一个数后保存在ebx寄存器中
sub esp, 8 esp = esp - 8; esp寄存器的值减去8,esp寄存器指向栈顶减去8相当于把栈顶的位置向下移动8个字节
mul ebx (eax = eax*ebx;) iuml eax, dword ptr[ebx] (eax = eax**ebx;) 带i前缀的是有符号数的整数运算,没有i前缀的是无符号数的整数运算
div ebx idiv dword ptr[esi] 被除数:eax 商:eax 除数:edx 因为除法结果除了商还有余数,执行前需要将被除数放入eax计算器中,然后在指令中提供除数,除法运算的商会保留在eax计算器中,余数保存在edx计算器中
逻辑运算
与:and 或:or 非: not 异或: xor
and eax, 1 eax = eax & 1; 运算结果保存在目标操作数中,将eax寄存器的值与1进行与运算,并将结果保存在eax寄存器中
or eax, ebx eax = eax | ebx; 将eax寄存器的值与ebx进行或运算,并将结果保存在eax寄存器中
not eax eax =!eax; 将对eax寄存器的每一位进行取反,并将结果保存在eax寄存器中
xor eax, ecx eax = eax ^ ecx;
比较
比较大小:cmp 测试:test
cmp eax, 1 用于比较两个操作数,实际上用的是减法指令,用第一个数减去第二个操作数,不保存运算结果只设置标志寄存器,以用来反映比较结果
test eax, 1 用于比较和测试的指令,主要用于执行两个操作数之间按位逻辑与操作,并根据结果设置标志寄存器eflags的状态标志,同样不保存实际的运算结果
函数调用
调用: call 返回:ret 系统调用: sysenter/ syscall (32位) 系统调用返回:sysexit/ sysret (64位) 中断返回:iret
函数的调用在汇编上就是由两条基本的指令来实现的,call、ret指令 call负责调用,ret指令负责返回原来调用的位置继续执行 一个程序的执行本质就是一层一层的函数调用,所以要存储很多返回地址,而且要一层一层对应存储。X86下选择把这些地址存放在堆栈里面,因为堆栈遵循先进后出、后进先出访问规则,随着函数调用一层层的堆叠,堆栈存储的返回值也对应着一层层堆叠
call xxxx 首先将EIP也就是call指令后面那条指令的地址存入堆栈,这一步目的是在保存返回地址,用于return指令知道能够返回哪里,第二步跳转到后面地址
ret 从堆栈顶部弹出4个字节,装载到EIP寄存器中,然后跳转到EIP指向的地方执行
堆栈操作
压栈:push 出栈:pop 寄存器批量入栈:pushad 寄存器批量出栈:popad eflags寄存器入栈:pushfd eflags寄存器出栈:popfd
堆一般是负责动态分配内存的地方。栈是进程内存空间里的一片区域,栈的数据结构中栈遵循先进后出、后进先出访问规则。堆栈的增长方向是高地址向低地址方向进行的,栈的顶部是低地址、栈的底部是高地址,其中ESP寄存器始终指向栈顶的位置 堆栈的重要的两个指令: 入栈/压栈(push)就是向堆栈里面放入一个数据,随着数据被push入堆栈,栈顶寄存器ESP的值会自动变化 出栈/弹栈(pop)就是向堆栈里面取出一个数据,随着数据被取出,栈顶寄存器ESP的值会自动变化 也可以通过加减指令修改栈顶的位置
跳转
条件跳转:je/jz/jne/jnz/jb/jnb/jg/jng 无条件跳转:jmp
jmp xxxx jmp dword ptr[ebx] 无条件跳转,相当与goto指令,跳转到某个地方开始执行,jmp指令后面是要跳转的地方,可以是一个相对地址实际是一个偏移加上eip的值,也可以是一个绝对地址指定跳转到某一地址
JE/JNE:如果等于/不等于(零标志置位/未置位),则跳转 JZ/JNZ:如果等于/不等于(零标志置位/未置位),则跳转 JB/JNB:如果小于/不小于(进位标志置位/未置位),则跳转 JL/JNL:如果小于/不小于(符号标志与溢出标志不一致/一致),则跳转。 JGE/JNGE:如果大于等于/不大于等于(符号标志与溢出标志一致/不一致),则跳转 JLE/JNLE:如果小于等于/不小于等于(零标志置位或符号标志与溢出标志不一致),则跳转。 JO/JNO:如果溢出/未溢出,则跳转。 JP/JNP:如果奇偶标志置位/未置位,则跳转
N: not,取反 E:equal,相等 B:below,在...以下 L:less,小于 G: greater,大于 O: overflow,溢出 P: parity,奇偶性
这些条判断指令是如何判断条件的呢?与标置寄存器有紧密关系,eflags寄存器中留了一些标志位用来记录CPU执行指令过程中的标记和状态,在一些计算指令和比较指令执行时会同步影响这些标记位 ZF:零标志位 OF:溢出标志位 CF:进出标志位 PF:奇偶标志位 SF:符号标志位
赋值
普通:mov 单字节:movsb 字:movsw 双字:movsd 四字:movsq 取地址:lea
mov eax, 8 eax = 8; 把8这个立即数赋值给eax寄存器
mov eax, dword ptr[ebx] eax = *ebx; 将EBX寄存器的值作为一个地址,将这个地址内的数据赋值给EAX寄存器
movsb、movsw、movsd、movsq 字符串赋值 默认以esi寄存器为源指针,edi寄存器作为目的指针,来进行内存间的数据复制
尝试通过汇编工具分析一下吧!!!
#include <stdio.h>
void if_test(int a) {
if (a == 1) {
printf("success");
}
else {
printf("failed");
}
}
char* switch_case_test(int status) {
char* mag = NULL;
switch (status){
case 200:
mag = "ok";
break;
case 301:
mag = "redirect";
break;
case 404:
mag = "not found";
break;
case 502:
mag = "server internal error";
break;
}
return mag;
}
void while_test() {
int n = 100;
while (n > 0) {
printf("n=%d\n", n--);
}
}
void do_while_test() {
int sum = 0;
int n = 1;
do
{
sum += n;
n++;
} while (n <= 100);
printf("Sum from 1 to 100 is: %d\n", sum);
}
void for_test() {
int sum = 0;
for (int i = 0; i <= 100; i++)
{
sum += i;
}
printf("Sum from 0 to 100 is: %d\n", sum);
}
int main() {
// 可以调用函数来测试
if_test(1);
printf("%s\n", switch_case_test(;
while_test();
do_while_test();
for_test();
return 0;
}
函数调用过程的汇编分析
参数如何传递?返回值如何传递?即函数调用约定
1、__cdecl
使用栈空间传递参数 函数参数按从右往左的方向传递 调用者负责释放参数空间 返回值在寄存器中
2、__stdcall
使用栈空间传递参数 函数参数按从右往左的方向传递 被调用函数负责释放参数空间 返回值在寄存器中
3、__fastcall
前两个参数使用寄存器传递 其余参数使用栈从右往左的方向传递 被调用函数负责释放参数空间 返回值在寄存器中