QEMU + GDB
GDBServer
qemu实现了gdbserver协议,这使得gdb可以连接到qemu,并且给qemu发送指令,以及从qemu中读 取信息。通过gdbserver,qemu可以把自己内部的信息暴露出来,或者说,可以让gdb理解它需要调试 的目标是qemu中被模拟的程序(或操作系统),而不是qemu自身。例如,当gdb发送在地址 0x400000 设置一个断点的指令时,qemu可以在被模拟程序执行到 0x400000 时暂停被模拟程序的执 行,并告知gdb客户端,而不是在qemu自身执行到 0x400000 时暂停。如果gdb需要读取内存中的值, qemu可以返回被调试程序的内存数据,而不是自身的内存数据。 还需要强调,由于gdb和gdbserver之间交换的信息仅停留在汇编级,而且gdb完全使用本地的可执行程 序文件中的符号表和调试信息执行源码级信息到汇编级信息之间的转换,因此在使用qemu系统级模拟 时,我们可能会发现一些意料之外的行为。这是因为,在系统级模拟时,QEMU自身相当于站在CPU的 抽象层面,而不是操作系统内核的抽象层面,这使得QEMU自身并不能理解它内部正在运行的操作系统 的相关信息。例如,如果在qemu中运行一个Linux操作系统,其中的每一个进程的虚拟地址空间都有许 多重叠,那么如果此时在 0x400000 打一个断点,qemu并不能识别出这个断点是针对哪个进程的,它所 能做的只是在CPU“执行”到地址为 0x400000 的指令时暂停执行,不论当前执行的到底是哪个进程。又由 于gdb客户端是完全根据用户所指定的可执行文件进行源码级到汇编级的翻译的,所以可能会出现这样 一种情况:
- gdb客户端从 appA 中读取符号表,其中,函数 funcA 的地址为 0x412345 ,它有2个参数
- 设置断点: break funcA ,但实际上,gdb会根据符号表把 funcA 翻译回 0x412345 ,qemu的 gdbserver只能接收到在 0x412345 打断点的指令。
- 在qemu模拟的Linux操作系统中,进程 appB 恰好在执行,且它的函数 funcB (只有1个参数)的 地址恰好也为 0x412345 ,qemu并不能理解这一点,它同样会暂停进程 appB 的执行。
- 根据 appA 的调试信息,gdb客户端知道 funcA 有2个参数,所以它会要求gdbserver读取寄存器 x0 和 x1 的值,用于显示 funcA 的参数。但由于此时暂停执行的实际上是只有1个参数的 funcB , 所以 x1 中保存的是一个随机值, x0 中保存的值很可能也不符合 funcA 的第一个参数的语义。于是 我们会看到虽然触发了断点,但函数参数却像是乱码。
- 同学们在后续chcore用户态相关的实验中,会对这个问题有更进一步的体会和理解。