抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

call printf(逐行解释)

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main()
{
int a = 0;
int b = 1;
float c = 2.3;
double d = 4.5;
char e = 'e';
short f = 2;
printf("%d %d %.2f %0.2f %c %d\n", a, b, c, d, e, f);
return 7;
}

使用Compiler Explorer编译,得到以下汇编代码

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
.LC2:
; ASCII string 自动补\0
.string "%d %d %.2f %0.2f %c %d\n"
main:
; prologue
push rbp
mov rbp, rsp
; expand stack frame with 32 bytes
; call main 前,已经push了返回地址令入栈
; 前面push rbp之后,rsp已经16bytes对齐
sub rsp, 32
; int a = 0
mov DWORD PTR [rbp-4], 0

; int b = 1
mov DWORD PTR [rbp-8], 1
; 通过rip相对寻址得到.LCO的地址
; 用DWORD PTR的方式读取这个地址
; 将此地址开始的4bytes存入xmm0低32bit
; 将xmm0高96bit清零。(当movss的source位mem)
movss xmm0, DWORD PTR .LC0[rip]

; float c = 2.3
movss DWORD PTR [rbp-12], xmm0
; 将此地址开始的8bytes存入xmm0第64bit
; xmm0高64bit被清零。(当movsd的source为mem时)
movsd xmm0, QWORD PTR .LC1[rip]

; double d = 4.5
; [rbp-24]是为了8byte对齐
movsd QWORD PTR [rbp-24], xmm0

; 将字母e的ASCII编码101存入[rbp-25]的位置
mov BYTE PTR [rbp-25], 101

; 将立即数2存入[rbp-28]的位置
; [rbp-28]是为了2bytes对齐
mov WORD PTR [rbp-28], 2

; 将[rbp-28]地址开始的WORD
; 以signed扩展方式,存入ecx寄存器
; printf的第六个参数,char e
movsx edi, WORD PTR [rbp-28]
movsx ecx, BYTE PTR [rbp-25]

; packed xor,对xmm2寄存器清零
pxor xmm2, xmm2

; 将[rbp-12]地址开始的DWORD,4bytes
; 转换成double,8bytes,存入xmm2寄存器低64位
; xmm2高64位不受影响
cvtss2sd xmm2, DWORD PTR [rbp-12]

; 将xmm2寄存器内容,存入rsi寄存器
movq rsi, xmm2

; 将[rbp-24]地址开始的QWORD,4bytes,存入xmm0寄存器
movsd xmm0, QWORD PTR [rbp-24]

; 将[rbp-8]地址开始的DWORD,4bytes,存入edx寄存器
; printf的第3个参数,b
mov edx, DWORD PTR [rbp-8]

; 将[rbp-8]地址开始的DWORD,4bytes,存入eax寄存
mov eax, DWORD PTR [rbp-4]

; copy edi 寄存器到r8d,即r8的低32bit
; printf的第7个参数,short f
mov r8d, edi

; copy xmm0寄存器到xmm1寄存器
; printf的第5个参数,浮点数d
; move 16-byte aligned packed double-precision float
; 如果时mem操作数,必须16字节对齐
movapd xmm1, xmm0

; copy rsi寄存器到xmm0的低64位
; printf的第4个参数,浮点数c
; movq是一条SSE2指令,不是ATT格式
movq xmm0, rsi
; copy eax 到esi,printf的第2个参数,a
mov esi, eax

; copy .LC2的地址到edi,内存模式时FLAT,printf的第一个参数
mov edi, OFFSET FLAT:.LC2

; copy 2 to eax
; printf时variadic function,2表示参数中有2个浮点数
mov eax, 2
; 将下一条指令地址压栈
; 跳转到printf的地址,在printf的最后使用ret弹出此地址
call printf

; return 7
mov eax, 7

; shorthand epilogue,等价于下面两条指令:
; mov rsp,rbp
; pop rbp
; 这行代码,恢复了rsp和rbp的值
leave
; pop return address, return to that address
ret
.LC0:
; float 2.3所占的4bytes的unsigned int 的值
.long 1075000115
.LC1:
; double 4.5所占的8bytes的两次unsigned int解析的数值
.long 0
.long 107492147

操作浮点数及xmm寄存器的指令,均来自SSE1或2指令集

汇编器指令

  • .string:定义字符串,自动补0,同.asciz
  • .long:定义DWORD,4bytes

关于两个浮点数

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main(int argc, char **argv)
{
float fv = 2.3;
double fd = 4.5;
printf("%u\n", *((unsigned int *)&fv));
printf("%u\n", *((unsigned int *)&fd));
printf("%u\n", *((unsigned int *)&fd + 1));
return 0;
}

输出:

1
2
3
1075000115
0
1074921472

参数传递符合Linux Calling Convention

call printf时的传参,严格按照Linux Calling Convention进行

rip 寄存器相对寻址

这是x64新增的寻址方式,rip寄存器永远指向下一条指令的地址,自动被CPU更新,在x64下,可以read。本文测试代码中,取浮点数常量的两条汇编指令如下:

1
2
3
movss xmm0,DWORD PTR .LCO[rip]
.....
movsd xmm0,QWORD PTR .LC1[rip]

编译成object文件后,查看这两条指令,变为

1
2
movss xmm0,DWORD PTR [rip+0x0]
movsd xmm0,QWORD PTR [rip+0x0]

所以汇编指令写成.LC0[rip]和.LC1[rip],只是告诉汇编器,用rip相对寻址的方式,计算到两个Label的地址

编译后显示[rip+0x0],显然这里需要重新定位。后面的注释表示执行到这条语句的时候,rip寄存器的值,即下一条指令的虚拟地址。

编译成可执行二进制文件后,这两条指令变成:

1
2
3
movss xmm0, DWORD PTR [rip+0xee8]
.....
movss xmm0, QWORD PTR [rip+0xedf]

评论