如何在 Ubuntu 18.04 上设置 ARM64 Playground
随着最近发布的 Apple Silicon(Apple 笔记本电脑转向 64 位 ARM 架构),终于到了学习 ARM64 的好时机! 由于实际的 ARM64 系统有点难以获得,以下是如何在标准 Ubuntu 18.04 x64 系统上设置基本开发平台的方法。我们将能够用它编译、反汇编、执行和调试 ARM64 程序。 关于术语的说明:许多安装的工具将被命名为“aarch64”,就我们的目的而言,它
随着最近发布的 Apple Silicon(Apple 笔记本电脑转向 64 位 ARM 架构),终于到了学习 ARM64 的好时机!
由于实际的 ARM64 系统有点难以获得,以下是如何在标准 Ubuntu 18.04 x64 系统上设置基本开发平台的方法。我们将能够用它编译、反汇编、执行和调试 ARM64 程序。
关于术语的说明:许多安装的工具将被命名为“aarch64”,就我们的目的而言,它实际上等同于“ARM64”。
如果您对系统主题感兴趣,请随时在twitter上关注我,我会在其中发布类似这样的内容 :)
安装工具
安装交叉编译器+工具链
首先,让我们安装一个用于交叉编译 C 和 C++ 的工具链。
sudo apt install gcc-8-aarch64-linux-gnu
sudo apt install g++-8-aarch64-linux-gnu
这给了我们(除其他外):
-
aarch64-linux-gnu-gcc-8
:C 的交叉编译器 -
aarch64-linux-gnu-g++-8
:C++ 的交叉编译器
安装QEMU
我们的 x64 系统将无法运行本工具链生成的二进制文件,因此我们需要进行仿真。 QEMU 是一个高质量的模拟器(以及更多),它能够在模拟的用户空间环境中运行不同架构的二进制文件。
sudo apt install qemu
这给了我们(除其他外):
qemu-aarch64
:ARM64 二进制文件的用户空间模拟器
安装支持多架构的 GDB
我们可以使用 QEMU 和 GDB 来调试我们的二进制文件,但是我们需要一个支持非本地架构的特殊版本的 GDB。
sudo apt install gdb-multiarch
这给了我们:
gdb-multiarch
交叉编译程序
现在我们拥有了将 C/C++ 交叉编译为 ARM64 二进制文件、查看生成的程序集并运行/调试二进制文件所需的一切。让我们从编译开始。
这是一个名为arm64main.cpp
的小型 C++ 程序。
#include <iostream>
int main() {
std::cout << "Hello from ARM64!\n";
}
这是编译它的方法。
$ aarch64-linux-gnu-g++-8 -o arm64main arm64main.cpp -static
$ file arm64main
arm64main: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=7b1bbf64436de3f0e268d1d8ab93d2123d4dcaef, not stripped
注意-static
标志。我们需要这个,因为默认情况下交叉编译器会生成依赖于 ARM64 版本的动态链接器的动态二进制文件,而我们没有。生成静态二进制文件是解决这个问题的一种简单方法,因为我们只是在玩。
凉爽的!现在我们有了一个可以反汇编、执行和调试的 ARM64 二进制文件。
反汇编一个二进制文件
我们对学习 ARM64 架构很感兴趣,所以能够反汇编二进制文件很重要,这样我们就可以看到它包含的 ARM64 指令。
我们可以使用工具链获得的aarch64-linux-gnu-objdump
来做到这一点。对于从 C++ 编译的二进制文件,我们还可以使用aarch64-linux-gnu-c++filt
实用程序来解开符号名称。
$ aarch64-linux-gnu-objdump -d arm64main | aarch64-linux-gnu-c++filt
这将反汇编二进制文件。例如,主函数如下所示:
000000000040090c <main>:
40090c: a9bf7bfd stp x29, x30, [sp, #-16]!
400910: 910003fd mov x29, sp
400914: f00007e0 adrp x0, 4ff000 <free_mem+0x20>
400918: 910a0001 add x1, x0, #0x280
40091c: b0000ae0 adrp x0, 55d000 <_GLOBAL_OFFSET_TABLE_+0x48>
400920: f9436800 ldr x0, [x0, #1744]
400924: 94005a2d bl 4171d8 <std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)>
400928: 52800000 mov w0, #0x0 // #0
40092c: a8c17bfd ldp x29, x30, [sp], #16
400930: d65f03c0 ret
挺整洁的!现在我们可以通过在手册中查找这些指令的作用来了解架构。
执行和调试二进制文件
我们希望为 Playground 提供的最后一个功能是能够执行并理想地调试我们的二进制文件。我们可以使用 QEMU 来做到这一点。
我们可以使用qemu-aarch64
执行我们的二进制文件。
$ qemu-aarch64 ./arm64main
Hello from ARM64!
事实上,我们实际上也可以像调用原生二进制文件一样调用二进制文件:
$ ./arm64main
Hello from ARM64!
这是因为 QEMU 通过 binfmt_misc 将自己注册为 ARM64 ELF 二进制文件的解释器。1
除了简单地执行之外,我们还可以使用 QEMU 和 GDB 调试和单步调试 ARM64 二进制文件。
为此,我们将需要两个终端窗口。
在第一个窗口中,使用-g
标志运行 QEMU,这将在端口上生成调试服务器。
$ qemu-aarch64 -g 1234 ./arm64main
在第二个窗口中,使用 GDB 连接到服务器。
$ gdb-multiarch ./arm64main
(gdb) target remote :1234
Remote debugging using :1234
0x00000000004007c4 in _start ()
(gdb)
好的! GDB 已连接到 QEMU 的调试服务器,现在我们进行大部分正常的调试活动。我们可以设置断点,检查寄存器和内存状态。
(gdb) break main
Breakpoint 1 at 0x400920
(gdb) continue
Continuing.
Breakpoint 1, 0x0000000000400920 in main ()
(gdb) bt
#0 0x0000000000400920 in main ()
(gdb) x/i $pc
=> 0x400920 <main+20>: ldr x0, [x0, #1744]
(gdb) info reg x0
x0 0x55d000 5623808
(gdb) x/8x $sp
0x40008003f0: 0x00800400 0x00000040 0x00493bf4 0x00000000
0x4000800400: 0x00000000 0x00000000 0x00400810 0x00000000
(gdb) set disassemble-next-line on
(gdb) si
0x0000000000400924 in main ()
=> 0x0000000000400924 <main+24>: 2d 5a 00 94 bl 0x4171d8 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc>
(gdb)
0x00000000004171d8 in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) ()
=> 0x00000000004171d8 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc+0>: fd 7b be a9 stp x29, x30, [sp, #-32]!
(gdb)
0x00000000004171dc in std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) ()
=> 0x00000000004171dc <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc+4>: fd 03 00 91 mov x29, sp
学习GDB还有很多其他的资源,这里就不赘述了。
结论
而已!我们现在有一个开发平台,可以让我们编译、反汇编、执行和调试 ARM64 二进制文件,这样我们就可以试验和了解 ARM64 架构。
如果您喜欢本指南,请随时在 Twitter 上关注我,我在 Twitter 上发布了有关 C++ 和其他有趣的低级主题的推文。
https://twitter.com/offlinemark
1.https://en.wikipedia.org/wiki/Binfmt_misc↩
更多推荐
所有评论(0)