如何让 qemu 成为 运行 一个 arm thumb 二进制文件?

How to get qemu to run an arm thumb binary?

我正在尝试学习 ARM 汇编的基础知识并编写了一个相当简单的程序来对数组进行排序。我最初使用 armv8-a 选项和 运行 qemu 下的程序组装它,同时使用 gdb 进行调试。这工作正常,程序初始化数组并按预期对其进行排序。

最终我希望能够为我的 Raspberry Pi Pico 编写一些程序集,它有一个 ARM Cortex M0+,我相信它使用 armv6-m 选项。但是,当我更改我的代码中的指令时,它编译得很好,但表现得 st运行gely,因为程序计数器在每条指令后递增 4,而不是我期望的 thumb 递增 2。这导致我的程序无法正常工作。我怀疑 qemu 试图 运行 我的代码,就好像它是为完整的 ARM 指令集而不是 thumb 编译的,但我不确定为什么会这样。

我 运行正在 Ubuntu Linux 20.04 LTS,使用 qemu-arm 版本 4.2.1(从包管理器安装)。 qemu-arm 可执行文件仅 运行 完整的 ARM 二进制文件吗?如果是这样,是否有另一个 qemu 包我可以安装到 运行 thumb 二进制文件?

如果有帮助,这是我的代码:

.arch armv6-m
.cpu cortex-m0plus

.syntax unified
.thumb

.data
arr: .skip 4 * 10
len: .word 10

.section .text
.global _start

.align 2
_start:
    ldr r0, arr_adr @ load the address of the start of the array into register 0
    movs r1, #0 @ clear the counter register
    movs r2, #100

init_loop:
    str r2, [r0,r1] @ store r2's value to the base address of the array plus the offset stored in r1
    subs r2, r2, #10 @ subtract 10 from r2
    adds r1, #4 @ add 4 to the offset (1 word in bytes)
    cmp r1, #40 @ check if we've reached the end of the array
    bne init_loop

    movs r1, #0 @ clear the offset
out_loop:
    mov r3, r1 @ set the index of the minimum value to the current array index

    mov r4, r1 @ set the inner loop index to the outer loop index

in_loop:
    ldr r5, [r0,r3] @ load the minimum index's value to r5
    ldr r6, [r0,r4] @ load the inner loop's next value to r6
    cmp r6, r5 @ compare the two values
    bge in_loop_inc @ if r6 is greater than or equal to r5, increment and restart loop
    mov r3, r4 @ set the minimum index to the current index
in_loop_inc:
    adds r4, #4
    cmp r4, #40 @ check if at end of array
    blt in_loop

    ldr r5, [r0,r3] @ load the minimum index value into r5
    ldr r6, [r0,r1] @ load the current outer loop index value into r6
    str r6, [r0,r3] @ swap the two values
    str r5, [r0,r1]

    adds r1, #4 @ increment outer loop index
    cmp r1, #40 @ check if at end of array
    blt out_loop

loop:
    nop
    b loop

arr_adr: .word arr

感谢您的帮助!

这里有几个概念需要理清:

(1) Arm 与 Thumb:这是两种不同的指令集。大多数 CPU 都支持,有些只支持一个。如果 CPU 支持两者,则两者可同时使用。为了简化一点,如果您跳转到最低有效位设置的地址意味着“转到 Thumb 模式”,而跳转到该位清零的地址意味着“转到 Arm 模式”。 (交互工作比这更复杂,但这是一个很好的初始心智模型。)请注意,所有 Arm 指令都是 4 字节长,但 Thumb 指令可以是 2 或 4 字节长。

(2) A-profile 与 M-profile :这是两个不同的 CPU 架构家族。 M-profile 是“微控制器”; A-profile 是“应用程序处理器”,即“(几乎)其他一切”。 M-profile CPUs 始终支持 Thumb 且仅支持 Thumb 代码。 A-profile CPU 同时支持 Arm 和 Thumb。 Raspberry Pi Pico 是 Cortex-M0+,即 M-profile。

(3) QEMU 系统仿真与 user-mode 仿真:这是两个不同的 QEMU 可执行文件,它们以不同的方式 运行 来宾代码。系统仿真二进制文件(通常 qemu-system-arm)运行 的“裸机代码”,例如整个 OS。来宾代码具有完全控制权,可以处理异常、写入硬件设备等。用户仿真二进制文件(通常为 qemu-arm)适用于 运行ning Linux user-space 二进制文件。来宾代码以非特权模式启动,可以访问通常的 Linux 系统调用。对于系统仿真,要仿真哪个 CPU 取决于您使用 -M--machine 选项 select 的机器类型。对于 user-mode 仿真,默认 CPU 是“A-profile 启用所有支持的功能”(这是 --cpu max)。

您目前正在使用 qemu-arm,这意味着您获得了 user-mode 模拟。这应该支持 Thumb 二进制文件,但除非您将 --cpu 选项传递给它,否则它将使用 A-profile CPU。我还建议使用较新的 QEMU 来完成 M-profile 工作,因为很多 M-profile CPU 错误自版本 4.2 以来已得到修复。我认为 4.2 也太老了,没有 Cortex-M0 CPU.

GDB 应该在 PSR 中告诉您 T 位设置了什么——用它来检查您是处于 Thumb 模式还是 Arm 模式,而不是查看 PC 递增了多少。

目前还没有 Raspberry Pi Pico 的 QEMU 系统仿真(尽管有人已经在其中进行了一些实验性工作)。如果您的程序集只是基本的“使用寄存器和一些内存”,您可以使用 user-mode 模拟器来完成。或者您可以尝试 'microbit' 机器模型,它是一个 Cortex-M0 板——如果您不做特定于 Pi Pico 的事情可能就足够了。

内存映射

MEMORY
{
    ram  : ORIGIN = 0x00000000, LENGTH = 32K
}

SECTIONS
{
   .text : { *(.text*) } > ram
}

strap.s

.cpu cortex-m0
.thumb
.syntax unified

.globl reset_entry
reset_entry:
    .word 0x20001000
    .word reset
    .word hang
    .word hang
    .word hang

.thumb_func
reset:
    ldr r0,=0x40002500
    ldr r1,=4
    str r1,[r0]
    ldr r0,=0x40002008
    ldr r1,=1
    str r1,[r0]

    ldr r0,=0x4000251C
    ldr r1,=0x30
    ldr r2,=0x37
loop_top:
    str r1,[r0]
    adds r1,r1,#1
    ands r1,r1,r2
    b loop_top

.thumb_func
hang:
    b hang

建造

arm-linux-gnueabi-as --warn --fatal-warnings  strap.s -o strap.o
arm-linux-gnueabi-ld strap.o -T memmap -o notmain.elf
arm-linux-gnueabi-objdump -D notmain.elf > notmain.list

检查矢量 table 作为快速检查:

Disassembly of section .text:

00000000 <reset_entry>:
   0:   20001000    andcs   r1, r0, r0
   4:   00000015    andeq   r0, r0, r5, lsl r0
   8:   0000002f    andeq   r0, r0, pc, lsr #32
   c:   0000002f    andeq   r0, r0, pc, lsr #32
  10:   0000002f    andeq   r0, r0, pc, lsr #32

00000014 <reset>:
  14:   4806        ldr r0, [pc, #24]   ; (30 <hang+0x2>)
  16:   4907        ldr r1, [pc, #28]   ; (34 <hang+0x6>)
  18:   6001        str r1, [r0, #0]
  1a:   4807        ldr r0, [pc, #28]   ; (38

看起来不错,

运行它

qemu-system-arm -M microbit -nographic -kernel notmain.elf 

它会输出 0123456701234567...直到你 ctrl-a 然后 x 退出 qemu。

注意这个二进制文件不能在真正的芯片上运行,因为我在欺骗 uart。

你可以用这个 sim 来弄湿你的脚。还有一个来自第一个 cortex-m 芯片的杰出微型,您也可以在该平台上限制自己使用 armv6m 指令。

像这样的qemu和sims对mcu工作的价值非常有限,因为几乎所有的工作都与外设和管脚有关,指令集就像一本书的语言,法语、俄语、英语、德语,一本生物学书是一本生物学书,这本书就是目标,这并不重要。外设是特定于芯片的(pico,特定的stm32芯片,特定的TI tiva C芯片等)。