前言

本章我们将通过几个示例,本文以 Linux 系统为例,深入 C 语言的汇编代码,这能帮助我们更好理解程序的工作原理,例如栈的工作机制。

本文需要 C 语言基本语法、GCC 编译、GDB 调试的前置芝士。


如何调试 C 语言汇编代码

不需要安装额外的工具,直接使用 GDB 进行汇编代码调试即可,当然通过配置你的 IDE 也可以进行更舒适的调试。

首先书写源文件,假设我们已经书写了 main.c 文件,然后按照下面的方法编译:

1
2
gcc main.c -o main.s -S  # 生成汇编代码
gcc main.s -o main -g # 附加调试信息

我们只要不在源代码中添加调试信息,而只在汇编代码中添加调试信息,即可直接使用 GDB 调试汇编代码。

1
2
gdb main
# 正常添加断点和运行代码, 使用形如 p $eax 查看寄存器

一段简单的汇编代码

在这段代码中,我们不对代码如何工作作介绍,仅用于介绍 C 语言汇编代码的基本框架。在之后的代码中,我们将不再解释框架性质的代码。

源码解释

1
2
3
4
5
6
7
#include <stdio.h>

int main() {
int x = 10;
printf("%d\n", x);
return 0;
}
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
    .file   "main.c"                # 文件名 main.c
.text # 代码段开始提示
.section .rodata # 只读数据段开始提示
.LC0:
.string "%d\n" # 字符串常量
.text # 代码段开始提示
.globl main # 声明 main
.type main, @function # 声明 main 为函数
main:
.LFB0: # 标志 main 函数开始
.cfi_startproc # 开启调试信息
endbr64 # 安全措施: 清空分支预测缓存区
pushq %rbp # rsp -= 8; *rsp = rbp; # 用于回溯栈指针
.cfi_def_cfa_offset 16 # 配置调试信息规则
.cfi_offset 6, -16 # 配置调试信息规则
movq %rsp, %rbp # rbp = rsp # 设置当前函数栈基
.cfi_def_cfa_register 6 # 配置调试信息规则
subq $16, %rsp # rsp -= 16; # 分配 16B 栈空间
movl $10, -4(%rbp) # *(rbp - 4) = 10; # 创建变量 x = 10
movl -4(%rbp), %eax # eax = *(rbp - 4); # 将 x 加载到临时寄存器
movl %eax, %esi # esi = eax ; # 将临时寄存器加载到第二参数寄存器
leaq .LC0(%rip), %rax # rax = &"%d\n"; # 将字符串指针加载到临时寄存器
movq %rax, %rdi # rdi = rax; # 将临时寄存器加载到第一参数寄存器
movl $0, %eax # eax = 0; # 重置临时寄存器
call printf@PLT # 调度 printf 函数, PLT 表示使用动态链接
movl $0, %eax # eax = 0 # 重置临时寄存器
leave # rsp = rbp + 8; rbp = *rbp; # 回溯栈指针
.cfi_def_cfa 7, 8 # 配置调试信息规则
ret # rip = *rsp; rsp += 8; # 回溯代码指针
.cfi_endproc # 关闭调试信息
.LFE0: # 标志 main 函数结束
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:


递归 - 栈是如何工作的?

我们希望通过这个章节,介绍栈是如何工作的。这里我们将在最后详细模拟函数调用中的栈变化。

源码解释

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

int fib(int x) {
if (x == 0) return 0;
if (x == 1) return 1;
return fib(x - 2) + fib(x - 1);
}

int main() {
const int n = 10;
printf("%d\n", fib(n));
return 0;
}
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
    .file   "main.c"
.text
.globl fib
.type fib, @function
fib:
.LFB0:
# fib 函数开始
.cfi_startproc
endbr64
pushq %rbp # rsp -= 8; *rsp = rbp; # 用于回溯栈指针
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp # rbp = rsp; # 设置当前函数栈基
.cfi_def_cfa_register 6
pushq %rbx # rsp -= 8; *rsp = rbx; # 用于回溯 rbx 寄存器
subq $24, %rsp # rsp -= 24; # 分配 24B 栈空间
.cfi_offset 3, -24
movl %edi, -20(%rbp) # *(rbp - 20) = edi; # 创建第一参数 x
# if (x == 0) { $eax = 0; goto return; }
cmpl $0, -20(%rbp) # *(rbp - 20) ? 0; # 比较结果将存储在标志寄存器
jne .L2 # 不相等跳转到标志 .L2, 实际查看标志寄存器, 然后修改 rip
movl $0, %eax # eax = 0; # 设置函数返回值
jmp .L3 # 无条件跳转到标志 .L3, 实际修改 rip
.L2:
# if (x == 1) { $eax = 1; goto return; }
cmpl $1, -20(%rbp) # *(rbp - 20) ? 1; # 比较结果将存储在标志寄存器
jne .L4 # 不相等跳转到标志 .L4
movl $1, %eax # eax = 1;
jmp .L3 # 无条件跳转到标志 .L3 # 设置函数返回值
.L4:
# $eax = fib(x - 2) + fib(x - 1)
movl -20(%rbp), %eax # eax = *(rbp - 20);
subl $2, %eax # eax -= 2;
movl %eax, %edi # edi = eax; # 设置第一参数
call fib # 调用函数 fib, 结果存储在 eax 寄存器
movl %eax, %ebx # ebx = eax
movl -20(%rbp), %eax # eax = *(rbp - 20);
subl $1, %eax # eax -= 1;
movl %eax, %edi # edi = eax; # 设置第一参数
call fib # 调用函数 fib, 结果存储在 eax 寄存器
addl %ebx, %eax # eax += ebx; # 且为函数返回值
.L3:
# return $eax;
movq -8(%rbp), %rbx # rbx = *(rbp - 8); # 回溯 rbx 寄存器
leave # rsp = rbp + 8; rbp = *rbp; # 回溯栈指针
.cfi_def_cfa 7, 8
ret # rip = *rsp; rsp += 8; # 回溯代码指针
.cfi_endproc
.LFE0:
# fib 函数结束
.size fib, .-fib
.section .rodata
.LC0:
.string "%d\n" # 字符串常量
.text
.globl main
.type main, @function
main:
.LFB1:
# main 函数开始
.cfi_startproc
endbr64
pushq %rbp # rsp -= 8; rsp = *rbp; # 用于回溯栈指针
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp # rbp = rsp; # 设置当前函数栈基
.cfi_def_cfa_register 6
subq $16, %rsp # rsp -= 16; # 分配 16B 栈空间
movl $10, -4(%rbp) # *(rbp - 4) = 10; # 创建变量 n = 10;
movl -4(%rbp), %eax # eax = *(rbp - 4);
movl %eax, %edi # edi = eax # 设置第一参数
call fib # 调用函数 fib
movl %eax, %esi # esi = eax # 设置第二参数
leaq .LC0(%rip), %rax # rax = &"%d\n";
movq %rax, %rdi # rdi = rax; # 设置第一参数
movl $0, %eax # eax = 0; # 重置临时寄存器
call printf@PLT # 调用函数 printf, PLT 表示使用动态链接
movl $0, %eax # eax = 0; # 设置函数返回值
leave # rsp = rbp + 8; rbp = *rbp; # 回溯栈指针
.cfi_def_cfa 7, 8
ret # rip = *rsp; rsp += 8; # 回溯代码指针
.cfi_endproc
.LFE1:
# main 函数结束
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:

栈空间变化

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
============================================================
after main.s: 55:
endbr64
┌════┐
(base) └════┘ 0x555555555197 <main>
┌════┐
└════┘ 0x0
┌════┐
rsp -> └════┘ 0x7ffff7db4d90 <__libc_start_call_main+128>

============================================================
after main.s: 59:
pushq %rbp
movq %rsp, %rbp
┌════┐
(base) └════┘ 0x555555555197 <main>
┌════┐
└════┘ 0x0
┌════┐
└════┘ 0x7ffff7db4d90 <__libc_start_call_main+128>
rbp ┐ ┌════┐
rsp ┴> └════┘ 0x1 <- rbp rbp 结束标志

============================================================
after main.s: 62:
subq $16, %rsp
movl $10, -4(%rbp)
┌════┐
(base) └════┘ 0x555555555197 <main>
┌════┐
└════┘ 0x0
┌════┐
└════┘ 0x7ffff7db4d90 <__libc_start_call_main+128>
┌════┐
rbp -> └════┘ 0x1 <- rbp rbp 结束标志
─════─ (int)10 存储第一参数
┌────┐
│ │
rsp -> └────┘ (未定义, 用于 16B 对齐)

============================================================
after main.s: 65:
call fib
┌════┐
(base) └════┘ 0x555555555197 <main>
┌════┐
└════┘ 0x0
┌════┐
└════┘ 0x7ffff7db4d90 <__libc_start_call_main+128>
┌════┐
rbp -> └════┘ 0x1 rbp 结束标志
─════─ (int)10 存储第一参数
┌────┐
│ │
└────┘ (未定义, 用于 16B 对齐)
┌════┐
rsp -> └════┘ 0x5555555551ab <main+29> 用于 rip 回溯

============================================================
after main.s: 12:
pushq %rbp
movq %rsp, %rbp
┌════┐
(base) └════┘ 0x555555555197 <main>
┌════┐
└════┘ 0x0
┌════┐
└════┘ 0x7ffff7db4d90 <__libc_start_call_main+128>
┌════┐
-24 └════┘ 0x1 rbp 结束标志
─════─ (int)10 存储第一参数
┌────┐
│ │
└────┘ (未定义, 用于 16B 对齐)
┌════┐
└════┘ 0x5555555551ab <main+29> 用于 rip 回溯
rbp ┐ ┌════┐
rsp ┴> └════┘ 0x7fffffffdde0 <base-24> 用于 rbp 回溯

============================================================
after main.s: 14:
pushq %rbx
┌════┐
(base) └════┘ 0x555555555197 <main>
┌════┐
└════┘ 0x0
┌════┐
└════┘ 0x7ffff7db4d90 <__libc_start_call_main+128>
┌════┐
-24 └════┘ 0x1 rbp 结束标志
─════─ (int)10 存储第一参数
┌────┐
│ │
└────┘ (未定义, 用于 16B 对齐)
┌════┐
└════┘ 0x5555555551ab <main+29> 用于 rip 回溯
┌════┐
rbp -> └════┘ 0x7fffffffdde0 <base-24> 用于 rbp 回溯
┌════┐
rsp -> └════┘ ? 用于 rbx 回溯

============================================================
after main.s: 17:
subq $24, %rsp
movl %edi, -20(%rbp)
┌════┐
(base) └════┘ 0x555555555197 <main>
┌════┐
└════┘ 0x0
┌════┐
└════┘ 0x7ffff7db4d90 <__libc_start_call_main+128>
┌════┐
-24 └════┘ 0x1 rbp 结束标志
─════─ (int)10 存储第一参数
┌────┐
│ │
└────┘ (未定义, 用于 16B 对齐)
┌════┐
└════┘ 0x5555555551ab <main+29> 用于 rip 回溯
┌════┐
rbp -> └════┘ 0x7fffffffdde0 <base-24> 用于 rbp 回溯
┌════┐
└════┘ ? 用于 rbx 回溯
┌────┐
└────┘ (未定义, 用于 16B 对齐)
─════─ edi 存储第一参数
┌────┐
│ │
rsp -> └────┘ (未定义, 用于 16B 对齐)

============================================================
after main.s: 48:
leave
┌════┐
(base) └════┘ 0x555555555197 <main>
┌════┐
└════┘ 0x0
┌════┐
└════┘ 0x7ffff7db4d90 <__libc_start_call_main+128>
┌════┐
rbp -> └════┘ 0x1 rbp 结束标志
─════─ (int)10 存储第一参数
┌────┐
│ │
└────┘ (未定义, 用于 16B 对齐)
┌════┐
rsp -> └════┘ 0x5555555551ab <main+29> 用于 rip 回溯

============================================================
after main.s: 50:
ret
┌════┐
(base) └════┘ 0x555555555197 <main>
┌════┐
└════┘ 0x0
┌════┐
└════┘ 0x7ffff7db4d90 <__libc_start_call_main+128>
┌════┐
rbp -> └════┘ 0x1 <- rbp rbp 结束标志
─════─ (int)10 存储第一参数
┌────┐
│ │
rsp -> └────┘ (未定义, 用于 16B 对齐)

malloc - 指针是如何工作的?

我们将以三层 malloc 为例,了解指针不断索引的过程。我们将给出工作流程图,然后希望通过汇编优化它。

源码解释

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

int main() {
int ***p = (int ***)malloc(sizeof(int **));
*p = (int **)malloc(sizeof(int *));
**p = (int *)malloc(sizeof(int));
***p = 10;
return 0;
}
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
    .file   "main.c"
.text
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
pushq %rbx
subq $24, %rsp # rsp -= 24; # 分配 24B 栈空间
.cfi_offset 3, -24
movl $8, %edi # edi = 8; # 设置第一参数为 sizeof(int **)
call malloc@PLT # 调用函数 malloc
movq %rax, -24(%rbp) # *(rbp - 24) = rax; # 获取函数返回值
movl $8, %edi # edi = 8; # 设置第一参数为 sizeof(int *)
call malloc@PLT # 调用函数 malloc, PLT 表示使用动态链接
movq %rax, %rdx # rdx = rax;
movq -24(%rbp), %rax # rax = *(rbp - 24);
movq %rdx, (%rax) # *rax = rdx;
movq -24(%rbp), %rax # rax = *(rbp - 24);
movq (%rax), %rbx # rbx = *rax;
movl $4, %edi # edi = 4; # 设置第一参数为 sizeof(int);
call malloc@PLT # 调用函数 malloc, PLT 表示使用动态链接
movq %rax, (%rbx) # *rbx = rax;
movq -24(%rbp), %rax # rax = *(rbp - 24);
movq (%rax), %rax # rax = *rax;
movq (%rax), %rax # rax = *rax;
movl $10, (%rax) # *rax = 10;
movq -24(%rbp), %rax # rax = *(rbp - 24);
movq (%rax), %rax # rax = *rax;
movq (%rax), %rax # rax = *rax;
movl (%rax), %eax # eax = *rax;
movl %eax, %esi # esi = eax; # 设置第二参数
leaq .LC0(%rip), %rax # rax = &"%d\n";
movq %rax, %rdi # rdi = rax; # 设置第一参数
movl $0, %eax # eax = 0; # 重置临时寄存器
call printf@PLT # 调用函数 printf, PLT 表示使用动态链接
movl $0, %eax # eax = 0; # 设置返回值
movq -8(%rbp), %rbx
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:

工作流程

尝试优化

显而易见,编译器生成的汇编代码做了许多 “愚蠢” 的事情:

  • alloc int ** and connect 流程中使用 rdx 寄存器,而在 alloc int * and connect 流程中使用 rbx 寄存器,其中虚线部分通过一种 “愚蠢” 方式实际从 rdxrbx 拷贝。这里我们可以少用一个寄存器,并减少若干次拷贝。
  • indexing and assignment 流程重新从 int *** 索引到 int *,实际上一流程结束 rax 寄存器存储的已是 int *,编译器遗忘了 rax 中的当前值。这里我们可以直接复用 rax,减少若干次索引。
  • 同样地,indexing and printf 流程中,我们可以复用 rax,减少若干次索引。
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
    .file   "main.c"
.text
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
pushq %rbx
subq $24, %rsp
.cfi_offset 3, -24
movl $8, %edi
call malloc@PLT
movq %rax, -24(%rbp)
movl $8, %edi
call malloc@PLT
movq %rax, %rbx # movq %rax, %rdx
movq -24(%rbp), %rax #
movq %rbx, (%rax) # movq %rdx, (%rax)
# movq -24(%rbp), %rax # 优化 1: 少用一个寄存器, 减少若干次拷贝
# movq (%rax), %rbx #
movl $4, %edi
call malloc@PLT
movq %rax, (%rbx)
# movq -24(%rbp), %rax # 优化 2: 减少若干次索引
# movq (%rax), %rax #
# movq (%rax), %rax #
movl $10, (%rax)
# movq -24(%rbp), %rax # 优化 3: 减少若干次索引
# movq (%rax), %rax #
# movq (%rax), %rax #
movl (%rax), %eax
movl %eax, %esi
leaq .LC0(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
movq -8(%rbp), %rbx
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:


循环 - 循环是如何工作的?

这里我们通过一个线性筛的示例讲解循环的工作流程。

源码解释

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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
    .file   "test.c"
.text # 代码段开始提示
.globl vis # 声明 vis
.bss # 未初始化数据段开始提示
.align 32 # 将下面的变量对齐到 32B
.type vis, @object # 声明 vis 为变量
.size vis, 101 # 声明 vis 大小为 101B
vis:
.zero 101 # 零初始化 101B 的 vis
.globl prime # 声明 prime
.align 32 # 将下面的变量对齐到 32B
.type prime, @object # 声明 prime 为变量
.size prime, 404 # 声明 prime 大小为 404B
prime:
.zero 404 # 零初始化 404B 的 prime
.text
.globl prime_sieve_euler # 声明 prime_sieve_euler
.type prime_sieve_euler, @function # 声明 prime_sieve_euler 为函数
prime_sieve_euler:
.LFB6:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
## for i 循环初始化
# int n = ::n, cnt = 0, i = 2;
# vis[1] = true;
# goto .L2;
movl %edi, -20(%rbp) # *(rbp - 20) = edi; # int n = ::n;
movl $0, -12(%rbp) # *(rbp - 12) = 0; # int cnt = 0;
movb $1, 1+vis(%rip) # *(vis + 1) = 1; # vis[1] = true;
movl $2, -8(%rbp) # *(rbp - 8) = 2; # int i = 2;
jmp .L2
.L8:
## for i 循环主体
# if (!vis[i] == 0) {
# goto .L3;
# } else {
# prime[cnt++] = i;
# goto .L3;
# }
movl -8(%rbp), %eax # eax = *(rbp - 8); # eax = i;
cltq # rax = eax; # 有符号拓展
leaq vis(%rip), %rdx # rdx = vis;
movzbl (%rax,%rdx), %eax # eax = *(rdx + rax) # eax = vis[i]; 无符号拓展
xorl $1, %eax # eax ^= 1; # eax = !vis[i];
testb %al, %al # al & al ? 0; # eax ? 0;
je .L3 # ==? jmp .L3
movl -12(%rbp), %eax # eax = *(rbp - 12); # eax = cnt;
leal 1(%rax), %edx # edx = rax + 1; # edx = cnt + 1;
movl %edx, -12(%rbp) # *(rbp - 12) = edx; # cnt = edx;
cltq # rax = eax; # 有符号拓展
leaq 0(,%rax,4), %rcx # rcx = rax * 4;
leaq prime(%rip), %rdx # rdx = prime;
movl -8(%rbp), %eax # eax = *(rbp - 8); # eax = i;
movl %eax, (%rcx,%rdx) # *(rdx + rcx) = eax; # prime[cnt] = i;
.L3:
## for j 循环初始化
# int j = 0;
# goto .L4;
movl $0, -4(%rbp) # *(rbp - 4) = 0; # int j = 0;
jmp .L4 # jmp .L4
.L7:
## for j 循环主体
# vis[prime[j] * i] = true;
# if (i % prime[j] == 0) {
# goto .L10;
# } else {
# j++;
# goto .L4;
# }
movl -4(%rbp), %eax # eax = *(rbp - 4); # eax = j;
cltq # rax = eax; # 有符号拓展
leaq 0(,%rax,4), %rdx # rdx = rax * 4;
leaq prime(%rip), %rax # rax = prime
movl (%rdx,%rax), %eax # eax = *(rax + rdx); # eax = prime[j];
imull -8(%rbp), %eax # eax *= *(rbp - 8); # eax *= i; 有符号乘法
cltq # rax = eax; # 有符号拓展
leaq vis(%rip), %rdx # rdx = vis;
movb $1, (%rax,%rdx) # *(rdx + rax) = 1; # vis[rax] = true;
movl -4(%rbp), %eax # eax = *(rbp - 4); # eax = j;
cltq # rax = eax; # 有符号拓展
leaq 0(,%rax,4), %rdx # rdx = rax * 4;
leaq prime(%rip), %rax # rax = prime;
movl (%rdx,%rax), %ecx # ecx = *(rax + rdx); # ecx = prime[j];
movl -8(%rbp), %eax # eax = *(rbp - 8); # eax = i;
cltd # edx:eax = eax; # 将 eax 有符号拓展为 edx:eax 拼接
idivl %ecx # eax = edx:eax / ecx; edx = edx:eax % ecx;
movl %edx, %eax # eax = edx;
testl %eax, %eax # eax & eax ? 0;
je .L10 # ==? goto .L10;
## for j 单循环结束
addl $1, -4(%rbp) # *(rbp - 4) += 1; # j++;
.L4:
## for j 单循环检查
# if (j >= cnt) {
# goto .L6; # 结束
# } else if (n >= prime[j] * i) {
# goto .L7; # 继续
# } else {
# goto .L6; # 结束
# }
movl -4(%rbp), %eax # eax = *(rbp - 4); # eax = j;
cmpl -12(%rbp), %eax # eax ? *(rbp - 12); # cnt ? eax;
jge .L6 # >=? jmp .L6
movl -4(%rbp), %eax # eax = *(rbp - 4); # eax = j;
cltq # rax = eax; # 无符号拓展
leaq 0(,%rax,4), %rdx # rdx = rax * 4;
leaq prime(%rip), %rax # rax = prime;
movl (%rdx,%rax), %eax # eax = *(rax + rdx); # eax = prime[j]
imull -8(%rbp), %eax # eax *= *(rbp - 8); # eax *= i; 有符号乘法
cmpl %eax, -20(%rbp) # *(rbp - 20) ? eax; # eax ? n
jge .L7 # >=? jmp .L7
jmp .L6
.L10:
nop # ; # 空语句
.L6:
## for i 单循环结束
# i++;
# goto .L2;
addl $1, -8(%rbp) # *(rbp - 8) += 1; # i++;
.L2:
## for i 单循环检查
# if (i <= n) {
# goto .L8; # 继续
# } else {
# return cnt; # 结束
# }
movl -8(%rbp), %eax # eax = *(rbp - 8); # eax = i;
cmpl -20(%rbp), %eax # eax ? *(rbp - 20); # i ? n;
jle .L8 # <=? jmp .L8;
movl -12(%rbp), %eax # eax = *(rbp - 12); # eax = cnt;
popq %rbp
.cfi_def_cfa 7, 8
ret # return eax
.cfi_endproc
.LFE6:
.size prime_sieve_euler, .-prime_sieve_euler
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
.LFB7:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $100, %edi
call prime_sieve_euler
movl %eax, %esi
leaq .LC0(%rip), %rax
movq %rax, %rdi
movl $0, %eax
call printf@PLT
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE7:
.size main, .-main
.ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4: