嵌入式软件调试之软件断点

💡 原文中文,约5600字,阅读约需14分钟。
📝

内容提要

软件中断是一种用于调试的指令,在X86处理器中广泛使用。通过插入INT 3指令,可以在程序执行时设置软件断点。INT 3指令的机器码是0xCC,对应的汉字是“烫”。用户模式和内核模式下可以使用DebugBreak()和DbgBreakPoint()等API插入断点。软件断点有一些局限性,只适用于代码段,无法动态加载到ROM中执行的程序。

🔎

延伸解读

EIP回跳的机制与意义

文章解释了INT 3异常触发后,Windows内核的KiTrap03会将EIP减1,使程序指针回退到INT 3指令的起始位置。这是因为调试器设置断点时只替换一个字节,若EIP指向下一条指令,可能落在多字节指令的中间,导致指令错位。回跳确保指针指向原指令起始处,符合“程序指针总是指向将要执行指令”的原则,也避免了程序错误。

0xCD03与0xCC的关键区别

INT 3指令的机器码是单字节0xCC,而INT n指令(n=3)的机器码是双字节0xCD03。文章通过实验表明,使用0xCD03触发断点后,由于KiTrap03的EIP减1操作,EIP会指向0xCD03的第二个字节,造成后续指令错位并引发访问冲突。而0xCC则能正确回退到指令起始处。这提醒开发者不要混用两种编码,否则需手动调整EIP。

软件断点的适用边界

文章归纳了软件断点的局限性:它属于代码类断点,仅适用于代码段,不能用于数据段和I/O空间;对于在ROM中执行的程序(如BIOS),无法动态加载软件断点;在VDT或IDT未就绪或被破坏时,软件断点也无法正常工作。这些限制源于INT 3指令依赖中断描述符表和可写内存,读者在嵌入式或底层调试时需留意这些前提条件。

❓

Q&A

INT 3指令的机器码是什么?为什么说它对应汉字“烫”?

INT 3指令的机器码是0xCC,对应的汉字是“烫”。这是因为0xCC在GBK编码中对应汉字“烫”,所以常被戏称为“烫烫烫”。

在Windows中,调试器如何处理INT 3断点异常,使得EIP指向正确的指令位置?

Windows内核的断点异常处理函数KiTrap03会将EIP的值减1,这样EIP就指向INT 3指令的起始位置,而不是下一条指令。这确保了调试器看到的程序指针指向正确的指令,避免因断点替换多字节指令而导致的错误。

软件断点有哪些局限性?

软件断点属于代码类断点,仅适用于代码段,不适用于数据段和I/O空间;对于在ROM中执行的程序(如BIOS)无法动态加载软件断点;在VDT或IDT还未准备就绪或被破坏的情况下,软件断点无法正常工作。

在用户模式和内核模式下,分别可以使用哪些API插入软件断点?

用户模式下可以使用DebugBreak() API,内核模式下可以使用DbgBreakPoint()或DbgBreakPointWithStatus() API主动插入断点。

INT 3指令和INT n指令(n=3)在机器码上有何不同?

INT 3指令具有独特的单字节机器码0xCC,而INT n指令(n=3)的机器码是0xCD后跟1字节的n值,即0xCD03。两者不同,INT 3是专门用于调试的指令。

调试器在恢复被调试程序执行时,如何处理命中的断点以避免立即再次触发?

调试器会先单步执行一次,设置单步执行标志,然后恢复执行,将断点所在位置的指令执行完。由于设置了单步标志,CPU执行完该指令后会再次中断到调试器,调试器进行内部操作后恢复程序执行,并落实所有断点。这一过程称为“单步走出断点”。如果用户在恢复前取消了该断点,则不需要单步执行。

INT 3指令有哪些特殊用途?

INT 3指令的特殊用途包括:编译器在编译调试版本时用0xCC填充刚刚分配的缓冲区;还用INT 3指令来填充函数或代码段末尾的空闲区域,即用于内存对齐。

🏷️

标签

➡️

继续阅读