如何让 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芯片等)。
我正在尝试学习 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芯片等)。