课堂上/PA 以 x86 (IA32) 为主授课?
今天连手机都是 64-bit 了……
本讲内容
In computing, a word is the natural unit of data used by a particular processor design. The number of bits in a word (the word size, word width, or word length)...
16 bit PC 寄存器 (64 KiB 寻址能力,KiB 级内存,无乘除法/浮点数)
我们需要更大的内存!更大的数据宽度!
8086 处理器 4,096 倍的地址空间
64 位地址空间能索引 17,179,869,184 GiB 内存
int
类型的长度
在逻辑世界里描述日常世界,2,147,483,647 已经足够大了
程序经历 .c → .o (编译) 和 .o → a.out (链接)
例如我们熟悉的 x86 calling convention
区别于 API (Application Programming Interface)
ABI:约定 binary 的行为
printf
都无法实现,必须借助外部库函数caller stack frame:
callee:
void bar(int *);
int foo(int x) {
bar(&x);
return x;
}
试着把内存/寄存器用数学符号表示出来
编译选项:-m32 -O2 -fno-pic
(便于大家理解)
000004f0 <foo>:
4f0: 83 ec 18 sub $0x18,%esp
4f3: 8d 44 24 1c lea 0x1c(%esp),%eax
4f7: 50 push %eax
4f8: e8 13 00 00 00 call 510 <bar>
4fd: 8b 44 24 20 mov 0x20(%esp),%eax
501: 83 c4 1c add $0x1c,%esp
504: c3 ret
用途 | 64b | 低32b | 低16b | 低8b | 8-15b |
---|---|---|---|---|---|
返回值 | %rax | %eax | %ax | %al | %ah |
%rbx | %ebx | %bx | %bl | %bh | |
参数4 | %rcx | %ecx | %cx | %cl | %ch |
参数3 | %rdx | %edx | %dx | %dl | %dh |
参数2 | %rsi | %esi | %si | %sil | |
参数1 | %rdi | %edi | %di | %dil | |
%rbp | %ebp | %bp | %bpl | ||
栈顶 | %rsp | %esp | %sp | %spl |
x86-64 扩充了很多寄存器!
用途 | 64b | 低32b | 低16b | 低8b | 8-15b |
---|---|---|---|---|---|
参数5 | %r8 | %r8d | %r8w | %r8b | |
参数6 | %r9 | %r9d | %r9w | %r9b | |
%r10 | %r10d | %r10w | %r10b | ||
链接 | %r11 | %r11d | %r11w | %r11b | |
C unsued | %r12 | %r12d | %r12w | %r12b | |
%r13 | %r13d | %r13w | %r13b | ||
%r14 | %r14d | %r14w | %r14b | ||
%r15 | %r15d | %r15w | %r15b | (没有) |
int f(int a, int b) {
return a + b;
}
00000510 <add_32>:
510: 8b 44 24 08 mov 0x8(%esp),%eax
514: 03 44 24 04 add 0x4(%esp),%eax
518: c3 ret
0000000000000630 <add_64>:
630: 8d 04 37 lea (%rdi,%rsi,1),%eax
633: c3 retq
int max(int a, int b) {
if (a > b) return a;
else return b;
}
00000514 <max_32>:
514: 8b 44 24 04 mov 0x4(%esp),%eax
518: 3b 44 24 08 cmp 0x8(%esp),%eax
51c: 7d 04 jge 522 <max+0xe>
51e: 8b 44 24 08 mov 0x8(%esp),%eax
522: c3 ret
0000000000000640 <max_64>:
640: 39 f7 cmp %esi,%edi
642: 89 f0 mov %esi,%eax
644: 0f 4d c7 cmovge %edi,%eax
647: c3 retq
支持 6 个参数的传递:rdi, rsi, rdx, rcx, r8, r9
例子:
void plus(int a, int b) {
fprintf(stdout, "%d + %d = %d\n", a, b, a + b);
}
fprintf
实际调用的是 __fprintf_chk@plt
stdout
, %d + %d = %d\n
, a
, b
, a + b
0000000000000700 <plus>:
700: 44 8d 0c 37 lea (%rdi,%rsi,1),%r9d
704: 89 f9 mov %edi,%ecx
706: 48 8b 3d 03 09 20 00 mov 0x200903(%rip),%rdi # 201010 <stdout@@GLIBC_2.2.5>
70d: 48 8d 15 b0 00 00 00 lea 0xb0(%rip),%rdx # 7c4 <_IO_stdin_used+0x4>
714: 41 89 f0 mov %esi,%r8d
717: 31 c0 xor %eax,%eax
719: be 01 00 00 00 mov $0x1,%esi
71e: e9 5d fe ff ff jmpq 580 <__fprintf_chk@plt>
plus
的最后一条指令:
71e: e9 5d fe ff ff jmpq 580 <__fprintf_chk@plt>
printf
,而是调用的有堆栈检查的版本mov $0x1, %esi
jmp
是因为函数末尾默认有一条 ret
指令__fprintf_chk@plt
的 ret
指令返回到 plus
的调用者call
指令;如果 plus
返回 printf
的结果,依然是 jmp
ret
对分支预测是很大的挑战printf
好读,但指令执行起来会稍慢一些
000005b4 <plus>:
5b4: 83 ec 14 sub $0x14,%esp
5b7: 8b 44 24 18 mov 0x18(%esp),%eax
5bb: 8b 54 24 1c mov 0x1c(%esp),%edx
5bf: 8d 0c 10 lea (%eax,%edx,1),%ecx
5c2: 51 push %ecx
5c3: 52 push %edx
5c4: 50 push %eax
5c5: 68 60 06 00 00 push $0x660
5ca: 6a 01 push $0x1
5cc: ff 35 00 00 00 00 pushl 0x0
5d2: e8 fc ff ff ff call 5d3 <plus+0x1f>
5d7: 83 c4 2c add $0x2c,%esp
5da: c3 ret
5db: 90 nop
x86-64
void f(int x) {... &x ...}
会发生什么?编译器会给参数分配内存,保证后续访问合法
void bar(int *);
int foo(int x) {
bar(&x);
return x;
}
swap
in x86-64总体来说,x86-64 是更
void swap(int *x, int *y);
交换两个指针指向的数字mov 0x8(%esp),%edx
mov 0xc(%esp),%eax
mov (%edx),%ecx
mov (%eax),%ebx
mov %ebx,(%edx)
mov %ecx,(%eax)
pop %ebx
mov (%rdi),%eax
mov (%rsi),%edx
mov %edx,(%rdi)
mov %eax,(%rsi)
int fact(int n) { int res = 1;
do { res *= n; n--; } while (n > 0);
return res; }
mov $0x1,%eax
nopl (%rax)
.L1: imul %edi,%eax
sub $0x1,%edi
test %edi,%edi
jg .L1
repz retq
两个诡异代码:
nopl (%rax)
:内存对齐 (padding)repz retq
:防止连续分支指令0000000000000704 <fib>:
704: 55 push %rbp
705: 53 push %rbx
706: 89 fd mov %edi,%ebp
708: 31 db xor %ebx,%ebx
70a: 48 83 ec 08 sub $0x8,%rsp
70e: 83 fd 01 cmp $0x1,%ebp
711: 7e 0f jle 722 <fib+0x1e>
713: 8d 7d ff lea -0x1(%rbp),%edi
716: 83 ed 02 sub $0x2,%ebp
719: e8 e6 ff ff ff callq 704 <fib>
71e: 01 c3 add %eax,%ebx
720: eb ec jmp 70e <fib+0xa>
722: 8d 43 01 lea 0x1(%rbx),%eax
725: 5a pop %rdx
726: 5b pop %rbx
727: 5d pop %rbp
728: c3 retq
编译器:把 C 代码 “原样翻译” 成汇编代码
int foo(int x, int y) {
x++; y++;
asm ("endbr64;"
".byte 0xf3, 0x0f, 0x1e, 0xfa"
);
return x + y;
}
int foo(int x, int y) {
int z;
x++; y++;
asm (
"addl %1, %2; "
"movl %2, %0; "
: "=r"(z) // output
: "r"(x), "r"(y) // input
);
return z;
}
实际的编译器可能会
两个建议
同时,付出也是必要的