2、代码编译运行¶
2.1 代码准备¶
代码包含异常向量表和几行代码。向量表的原因是 M3 core启动时,会先从这个地址取指令所在地址,然后赋值给PC执行。
/*
* Copyright (c) 2021-2008, Jinping Wu. All rights reserved.
*
* SPDX-License-Identifier: MIT
*/
__Vectors:
.long 0x12345678 /* Top of Stack */
.long Reset_Handler /* Reset Handler */
.long 0x11111111
.long 0x22222222
.thumb_func
Reset_Handler:
mov r0, #1
mov r1, #2
mov r2, #3
b .
mov r2, #4
Note
参考手册 DUI0552A_cortex_m3_dgug.pdf ,core执行时会从 0x4 地址取出要执行的第一行代码的地址,我们这里Reset_Handler函数是入口函数,放到 0x4 地址, thumb模式需要加上个 .thumb_func, Reset_Handler 地址和放入 0x4 的地址值偏差1,core取指时会自动把1减掉后调用到Reset_Handler去执行。
这段代码开头定义了异常向量表,0x4 地址放入口函数 Reset_Handler, Reset_Handler 函数本身给寄存器 r0~r3 分别赋值 1,2,3. 然后b . 跳转到本行,即在一直循环执行这一行。
使用交叉编译工具,先导出:
export PATH="/root/workspace/.toolchains/gcc-arm-none-eabi-10-2020-q4-major/bin/:$PATH"
arm-none-eabi-gcc -nostdlib -e 0x0 -Ttext=0x0 -mthumb -mcpu=cortex-m3 test.S -o test.elf
The Cortex-M3 processor only supports execution of instructions in Thumb state, The following can clear the T bit to 0: • instructions BLX, BX and POP{PC} • restoration from the stacked xPSR value on an exception return • bit[0] of the vector value on an exception entry or reset.
Note
-e 0x0 告诉gcc生成 elf 时,指定入口地址为 0x0, -Ttext=0x0 表示 .text 代码段是从 0x0 开始放的, -mthumb 使用 thumb指令(指令长度16、32混合)、-mcpu=cortex-m3 指定cpu为 cortex-m3.
arm-none-eabi-objdump -xD test.elf > test.asm
DUMP出 ELF 内容,可以看到(部分内容):
test.elf: file format elf32-littlearm
test.elf
architecture: armv7, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00000000
Program Header:
LOAD off 0x00010000 vaddr 0x00000000 paddr 0x00000000 align 2**16
filesz 0x0000001c memsz 0x0000001c flags r-x
private flags = 5000200: [Version5 EABI] [soft-float ABI]
Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000001c 00000000 00000000 00010000 2**2
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .ARM.attributes 0000001b 00000000 00000000 0001001c 2**0
CONTENTS, READONLY
2 .noinit 00000000 0001001c 0001001c 00010037 2**0
CONTENTS
这里可以看到arch变成了armv7
00000000 <__Vectors>:
0: 12345678 eorsne r5, r4, #120, 12 ; 0x7800000
4: 00000011 andeq r0, r0, r1, lsl r0
8: 11111111 tstne r1, r1, lsl r1
c: 22222222 eorcs r2, r2, #536870914 ; 0x20000002
00000010 <Reset_Handler>:
10: 2001 movs r0, #1
12: 2102 movs r1, #2
14: 2203 movs r2, #3
16: e7fe b.n 16 <Reset_Handler+0x6>
18: 2204 movs r2, #4
.text 段从0x0开始,0x4 放的 0x00000011, 而我们的 Reset_Handler 地址为 0x00000010 ,符合我们预期(thumb模式地址+1)
arm-none-eabi-objcopy -O binary test.elf test.bin
从 ELF 中导出可执行 binary 文件。我们一般 QEMU 会使用 ELF文件(qemu会解析),而实际的板子会使用bin文件,比如把bin文件烧到flash
xxd test.bin
00000000: 7856 3412 1100 0000 1111 1111 2222 2222 xV4.........""""
00000010: 0120 0221 0322 fff7 feff 0422 . .!."....."
可执行文件一共就28 bytes
Note
编译链接涉及的内容很庞大,不在我们讨论范围内,只做简单说明,满足我们使用即可。
2.2 运行¶
接下来我们用 qemu 来运行刚刚编译出来的elf, 新开一个终端窗口:
export PATH="/root/workspace/software/qemu/qemu-6.0.0/build/:$PATH"
导出环境变量
qemu-system-arm -machine mps2-an385 -monitor null -semihosting --semihosting-config enable=on,target=native -kernel test.elf -serial stdio -nographic -s -S
Note
QEMU 命令,-machine mps2-an385 表示使用 mps2-an385这个平台, -kernel test.elf 即加载执行我们这里编译出来的 test.elf, -s -S 会让 qemu 停在第一行代码,等待gdb 链接。
再新开一个终端窗口,运行gdb:
export PATH="/root/workspace/.toolchains/gcc-arm-none-eabi-10-2020-q4-major/bin/:$PATH"
导出环境变量
arm-none-eabi-gdb -ex 'target remote localhost:1234' -ex "add-symbol-file test.elf" -q
Note
GDB 命令,add-symbol-file test.elf 即导入我们的elf作为符号表,连接到我们的上一个窗口 qemu 上。
(gdb) display /ni $pc
1: x/i $pc
=> 0x10 <Reset_Handler>: movs r0, #1
当前停在0x10第一条指令,说明core reset成功了,并且从0x4 地址取到了第一条指令的地址(0x10),跳转到 0x10 执行。
(gdb) info reg
r0 0x0 0
r1 0x0 0
r2 0x0 0
r3 0x0 0
r4 0x0 0
r5 0x0 0
r6 0x0 0
r7 0x0 0
r8 0x0 0
r9 0x0 0
r10 0x0 0
r11 0x0 0
r12 0x0 0
sp 0x12345678 0x12345678
lr 0xffffffff -1
pc 0x10 0x10 <Reset_Handler>
xpsr 0x41000000 1090519040
寄存器情况,当前r0~r12都是0,符合预期
Note
栈指针,sp, reset后默认使用MSP,并且地址是从0x0 取出来的,我们代码中0x0 写的是0x12345678,所以core reset起来后,自动从这个地址取值,赋给了 sp 寄存器
xpsr isr number说明(手册DUI0552A_cortex_m3_dgug.pdf page 19)
xpsr 的低位用于说明当前处于哪种模式,reset后默认处于thread mode,对应是0,符合预期。
(gdb) si
0x00000012 in Reset_Handler ()
1: x/i $pc
=> 0x12 <Reset_Handler+2>: movs r1, #2
(gdb) info reg $r1
r1 0x0 0
(gdb) info reg $r0
r0 0x1 1
(gdb)
执行完第一条命令,“mov r0, #1”, r0 变成1, 符合预期
(gdb) info reg
r0 0x1 1
r1 0x2 2
r2 0x3 3
r3 0x0 0
r4 0x0 0
r5 0x0 0
r6 0x0 0
r7 0x0 0
r8 0x0 0
r9 0x0 0
r10 0x0 0
r11 0x0 0
r12 0x0 0
sp 0x12345678 0x12345678
lr 0xffffffff -1
pc 0x16 0x16 <Reset_Handler+6>
xpsr 0x1000000 16777216
一直往下执行到循环体,r0,r1,r2 被赋值位1,2,3. 符合我们的预期。
这样,我们用了最简单的几行代码,让 Cortex M3 跑起来了。