前面的文章里面,我们一直都是使用Unicorn 提供的 HOOK_CODE 机制来处理错误。这种类似于打log的方式还是不够方便,那么我们能否实现一个简单的调试器来帮助我们快速定位问题呢?比如,查看出错地方的反汇编代码,寄存器参数,下断点等等。
无名侠的代码中,是有一个实现,今天我们来分析一下源码,看看其实现思路。
调试器要支持的调试指令如下:
def show_help(self):
help_info = """
# commands
# set reg <regname> <value>
# set bpt <addr>
# n[ext]
# s[etp]
# r[un]
# dump <addr> <size>
# list bpt
# del bpt <addr>
# stop
# f show ins flow
"""
print (help_info)
调试器使用:
mu = Uc(UC_ARCH_ARM, UC_MODE_ARM)
dbg = udbg.UnicornDebugger(mu, udbg.UDBG_MODE_ALL)
dbg.add_bpt(0x1112233)
mu.emu_start(......)
这段代码在0x1112233处添加了一个断点,当程序执行到0x1112233处的时候就会停下来,陷入调试器的处理,等待调试者的指令。
我们先看UnicornDebugger的构造函数,最重要的就是这几行代码:
if mode == UDBG_MODE_ALL:
mu.hook_add(UC_HOOK_CODE, _dbg_trace, self)
mu.hook_add(UC_HOOK_MEM_UNMAPPED, _dbg_memory, self)
mu.hook_add(UC_HOOK_MEM_FETCH_PROT, _dbg_memory, self)
添加了钩子函数,在每条指令执行前触发回调函数_dbg_trace ,看这个函数:
def _dbg_trace(mu, address, size, self):
self._tracks.append(address)
if not self._is_step and self._tmp_bpt == 0:
if address not in self._list_bpt:
return
if self._tmp_bpt != address and self._tmp_bpt != 0:
return
return _dbg_trace_internal(mu, address, size, self)
第一行,就是将每条指令都添加到了一个列表当中,等到发生错误的时候,你可将列表元素倒序取出来,就形成了一个类似堆栈的东西。
接下来的两个 if 条件,是判断有没有设置断点或者单步,如果都没有设置就不往下执行。
看 _dbg_trace_internal方法逻辑:
def _dbg_trace_internal(mu, address, size, self):
self._is_step = False
print ("======================= Registers =======================")
self.dump_reg()
print ("======================= Disassembly =====================")
if size == 4:
mode = 'arm'
else:
mode = 'thumb'
self.dump_asm(address, size * self.dis_count, mode)
首先,打印寄存器的值,这个就比较简单了,直接使用Unicorn自带的方法就行:
eax = mu.reg_read(UC_X86_REG_EAX)
print(f"EAX: {eax}")