随着最近发布的 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↩

Logo

更多推荐