最近在做的 GooseHeap OS 很需要对一些全局变量和寄存器进行调试,但单片机很难做调试,于是乎就开始搞鼓 qemu 跑 arduino 虚拟机。
qemu-system-avr
qemu 有 arduino uno 对应的 avr 虚拟机,虽然不够完善,但已有实现的 USART 和 16 位定时器对于 GooseHeap OS 的多任务调试已经够用。
直接用 pacman 安装:
|
|
现在的 qemu-system-avr 支持仿真的微处理器可以通过 -machine help 参数查看:
|
|
可以看到 arduino-uno 的微处理器 ATmega328P 是支持的。
avr-gdb
调试使用 gdb,需要 avr-gdb :
|
|
avr-gdb 可以调试跑在 qemu 下的 avr 架构程序。
第一次调试
对 GooseHeapOS 进行调试,现有的 Makefile 没有加调试参数所以采用以下参数进行构建:
|
|
之后使用 qemu-system-avr 直接调试:
|
|
此时没有任何输出,因为通过 -s -S 参数开启 GDB Server 并暂停 CPU 等待调试。
因为我 gdb 用的 pwndbg,很多 gdb 配置对 avr-gdb 是不兼容的,所以用 -nx -q 不使用任何配置文件。用如下参数在新终端打开 gdb:
|
|
在 gdb 内用如下参数连接 qemu 的 gdb server:
|
|

可见此时停在 0x00000000 地址,地址的最开头,为中断向量表的起始点。
可以用 disassemble __vectors 反编译中断向量表:

可以看到 ATmega328P 的前 0x64 地址放着中断向量表,且每一个中断向量表占用 4 字节。每个中断向量是一个 jmp 指令。此时的 PC 指针停在 0x00000000,怎么证明呢?当然,在 gdb 下是轻而易举的。
通过 p $pc 即可打印出 pc 寄存器:

可我们的 GooseHeap OS 是从 main 开始的,但 main 之前有很多的初始化工作,可以通过 tui enable、tui layout asm 打开一个显示反汇编的窗口,然后通过 si 单步执行一步步看 avr-gcc 编译出来的程序结构。

跳转函数
单步执行显然很慢,因为我们编译程序的时候是保存了函数名的 label,gdb 可以根据函数的 label 打断点进行跳转,比如:
|
|

这样我们就跳转到 main 函数的开头,我们就可以和以往使用 gdb 一样,对 avr 程序进行调试了。
qemu-system-avr 参数
我们刚才使用的 qemu 参数为:
|
|
这里简单讲下每个参数的作用:
| 参数 | 含义 |
|---|---|
-M uno |
-M 是 -machine 的缩写 |
-bios main.elf |
制定加载的固件文件 |
-display none |
不创建图形显示窗口 |
-monitor none |
禁用 QEMU Monitor |
-serial stdio |
将串口映射到宿主机标准输入输出 |
-s |
-gdb tcp::1234 的简写 |
-S |
启动时不执行 CPU 指令 |