课程笔记

CISC(复杂指令集):Intel 芯片

RISC(精简指令集):ARM 芯片、MAC m1、m2 芯片

disas /r :查看汇编的时候,显示机器码

汇编中,函数的返回值一般在 eax 中。ecx 寄存器一般存储循环次数

寄存器中以 e 开头的,就是 32 位寄存器。如果以 r 开头,就是 64 位寄存器。

通用寄存器:

  • eax:32 位。函数返回值
    • ax:低 16 位。al 和 ah
  • ebx:32 位
  • ecx:32 位。循环次数
  • edx:32 位
  • ebp:32 位 栈底寄存器
  • esp:32 位 栈顶寄存器
  • esi:source
  • edi:dst
  • eip:程序计数器,无法直接操作,需要特定指令间接操作

一般在操作寄存器时,清零一下:xor eax, eax

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
// 开辟新的栈帧,一个函数一个栈帧
push ebp
mov ebp, esp
sub esp, 0CCH
// 保存现场,通过栈保存常用的寄存器
push ebx
push esi
push edi
// 初始化栈内存(可做可不做)
lea edi, [ebp+xxxh]
mov ecx, 33h
mov eax, 0CCCCCCCCh
rep stos dword ptr es:[edi]
// 业务逻辑
mov dword ptr [ebp-8], 0Ah
mov eax, dword ptr [ebp-8]
// 恢复现场
pop edi
pop esi
pop ebx
// 恢复栈帧,下面两行可以用 leave 来替换
mov esp, ebp
pop ebp
// 返回
retx

栈指令:

1
2
3
4
5
6
push 
pop
call
ret
jmp 无条件跳转
jcc 有条件跳转(eflags)

内联汇编:__asm__ volatile ("sgdt gdt_ptr;");

1
2
3
4
5
6
add:
push ebp
mov ebp, esp
...
leave
ret

汇编函数最好写一个 C 语言原型

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
VC的调用约定(微软)
__cdecl
1. 虽然没有写调用约定,但是默认的调用约定时 __cdecl
2. 调用时,参数的压栈顺序是从右向左(所有的调用约定都是从右向左),依靠栈传参
3. 传递的参数是在那个栈里?_tmain 还是 print。
传参是在调用者栈中,在 _tmain,而不是在被调用者 print 中
4. print 函数中怎么取到参数?借助 ebp 寄存器;第一个参数的计算公式:ebp+8
32 位机器,栈的 slot 是 4 字节,计算公式:ebp + 4*2
64 位机器,栈的 slot 是 8 字节,计算公式:rbp + 8*2
5. 因为传参破坏了栈平衡,如何平栈(堆栈平衡)
内平栈:被调用函数自己来平衡栈 ret 4*N
外平栈:调用者来平衡栈

__cdecl 使用外平栈(一般 gcc 使用这个方式)
__stdcall 使用内平栈,windows API 使用的

__fastcall 在 x86 和 x64 机器上有不同。64 位只有这种调用约定
他会借助寄存器传参,就是寄存器+栈的方式传参
x86 下
参数小于等于 2,纯寄存器传参。
用了两个寄存器,传参顺序自右向左,不需要平栈
大于 2,寄存器+栈 传参
用了两个寄存器,其他参数用栈,还是自右向左,内平栈
x64 下,参数小于等于 6,大于 6,寄存器+栈 传参



__asm__ volatile("");
asm volatile("");
__asm__("");