生成armv4汇编

显然学习体系结构能随时生成汇编非常方便。教学使用的体系结构是 armv4,不过现代编译器默认生成的已经是 armv8 了,连指令宽度都不一样。有必要学习怎么编译和查看 armv4 编译。

MacOS (Apple Silicon)

这是比较方便的。Apple Silicon 使用的就是 arm 体系结构,因此,使用标配的 clang 就可以生成。

首先要获取 Xcode command line tools。终端输入:

1
$ xcode-select

clang -v 确认安装情况:

1
2
3
4
5
$ clang -v
Apple clang version 17.0.0 (clang-1700.0.13.5)
Target: arm64-apple-darwin24.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin

要想编译一段 C 代码,使用如下的命令:

1
$ clang -target armv4-none-eabi -march=armv4 -mno-thumb -O1 -g -c source.c -o source.o

其中 -target 指定生成目标,-march 进一步指定体系结构,-mno-thumb 禁用 thumb 指令集。

-O1 进行一些基本优化,如果完全不优化,那么变量将不会常驻在寄存器里,每一行代码都会编译出 LDRSTR

-g 指定插入调试信息,这样查看时可以看到汇编对应的代码行。source.c 是源代码的名字。

-c 指定只生成目标文件 source.o,跳过链接阶段,不生成可执行文件。

要想查看汇编,使用如下命令:

1
$ objdump --mcpu=cortex-a7 -S source.o

--mcpu 参数指定目标 cpu。-S 指定包含调试信息。

我们来看个例子:

1
2
3
4
5
6
7
8
9
10
//source .c
int f(int a, int b) {
return a + b;
}

int main() {
int a = 1, b = 2;
int c = f(a, b);
return c;
}

编译并查看得到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ clang -target armv4-none-eabi -march=armv4 -mno-thumb -O1 -g -c source.c -o source.o && objdump --mcpu=cortex-a7 -S source.o

source.o: file format elf32-littlearm

Disassembly of section .text:

00000000 <f>:
; return a + b;
0: e0810000 add r0, r1, r0
4: e1a0f00e mov pc, lr

00000008 <main>:
; return c;
8: e3a00003 mov r0, #3
c: e1a0f00e mov pc, lr

可以看到一些有趣的观察:int a = 1, b = 2 这一行直接消失了,因为 -O1 优化直接算出了 1+2=3,因此最终生成的代码直接给返回值寄存器 r0 写入了立即数 #3。读者可以自行尝试用 -O0 优化会得到什么。

Linux(amd64)

在非 arm 设备上,我们需要专门的交叉编译器 arm-none-eabi-gcc。在 ubuntu 上,安装这个包:

1
$ sudo apt install gcc-arm-none-eabi

对于其他的 Linux 发行版,可以自行搜索一下。安装完成后用如下命令检查:

1
2
3
4
5
6
7
8
9
$ arm-none-eabi-gcc -v
Using built-in specs.
COLLECT_GCC=arm-none-eabi-gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/arm-none-eabi/14.2.0/lto-wrapper
Target: arm-none-eabi
Configured with: /build/arm-none-eabi-gcc/src/gcc-14.2.0/configure --target=arm-none-eabi --prefix=/usr --with-sysroot=/usr/arm-none-eabi --with-native-system-header-dir=/include --libexecdir=/usr/lib --enable-languages=c,c++ --enable-plugins --disable-decimal-float --disable-libffi --disable-libgomp --disable-libmudflap --disable-libquadmath --disable-libssp --disable-libstdcxx-pch --disable-nls --disable-shared --disable-threads --disable-tls --with-gnu-as --with-gnu-ld --with-system-zlib --with-newlib --with-headers=/usr/arm-none-eabi/include --with-python-dir=share/gcc-arm-none-eabi --with-gmp --with-mpfr --with-mpc --with-isl --with-libelf --enable-gnu-indirect-function --with-host-libstdcxx='-static-libgcc -Wl,-Bstatic,-lstdc++,-Bdynamic -lm' --with-pkgversion='Arch Repository' --with-bugurl=https://gitlab.archlinux.org/archlinux/packaging/packages/arm-none-eabi-gcc/-/issues --with-multilib-list=rmprofile
Thread model: single
Supported LTO compression algorithms: zlib zstd
gcc version 14.2.0 (Arch Repository)

对应的编译指令为:

1
$ arm-none-eabi-gcc -march=armv4 -O1 -g -c source.c -o source.o

不再需要 target,因为这是特定的交叉编译器。对应的查看指令:

1
$ arm-none-eabi-objdump -S source.o

也不需要指定目标 cpu。最后附一个相对复杂的函数,用于检验工作是否正常。读者也可以仔细研究编译器是如何重排指令优化跳转的。

1
2
3
4
5
6
7
8
// source.c
int f(int n, int k) {
int b;
b = k + 2;
if (n == 0) b = 10;
else b = b + (n * n) + f(n - 1, k + 1);
return b * k;
}
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
$ arm-none-eabi-gcc -march=armv4 -O1 -g -c source.c -o source.o && arm-none-eabi-objdump -S source.o

source.o: file format elf32-littlearm


Disassembly of section .text:

00000000 <f>:
int f(int n, int k) {
0: e92d4070 push {r4, r5, r6, lr}
4: e1a04001 mov r4, r1
int b;
b = k + 2;
if (n == 0) b = 10;
8: e3500000 cmp r0, #0
c: 03a0000a moveq r0, #10
10: 1a000001 bne 1c <f+0x1c>
else b = b + (n * n) + f(n - 1, k + 1);
return b * k;
}
14: e0000094 mul r0, r4, r0
18: e8bd8070 pop {r4, r5, r6, pc}
b = k + 2;
1c: e2815002 add r5, r1, #2
else b = b + (n * n) + f(n - 1, k + 1);
20: e0255090 mla r5, r0, r0, r5
24: e2811001 add r1, r1, #1
28: e2400001 sub r0, r0, #1
2c: ebfffffe bl 0 <f>
30: e0850000 add r0, r5, r0
34: eafffff6 b 14 <f+0x14>

如果输出的汇编中,mlamul 显示为 unknown,那么说明 objdump 没有被正确配置。特定的交叉编译器应该没有这个问题,Mac 上自带的 objdump 要检查 --mcpu 是否正确配置。