From 04544930e516a4f8274d135a4903a6679039590e Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Wed, 8 Nov 2023 14:59:06 +0800 Subject: [PATCH 1/5] qdb improvements: 1. allow multi-breakpoints, 2. both hooks and patches are now effective in qdb --- qiling/debugger/qdb/qdb.py | 26 +++++++++++++++++--------- qiling/utils.py | 13 ++++++++----- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 09f589d1f..032624092 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -5,11 +5,11 @@ import cmd -from typing import Optional, Tuple, Union +from typing import Optional, Tuple, Union, List from contextlib import contextmanager from qiling import Qiling -from qiling.const import QL_OS, QL_ARCH, QL_ENDIAN +from qiling.const import QL_OS, QL_ARCH, QL_ENDIAN, QL_STATE from qiling.debugger import QlDebugger from .utils import setup_context_render, setup_branch_predictor, setup_address_marker, SnapshotManager, run_qdb_script @@ -25,7 +25,7 @@ class QlQdb(cmd.Cmd, QlDebugger): The built-in debugger of Qiling Framework """ - def __init__(self, ql: Qiling, init_hook: str = "", rr: bool = False, script: str = "") -> None: + def __init__(self, ql: Qiling, init_hook: List[str] = [], rr: bool = False, script: str = "") -> None: """ @init_hook: the entry to be paused at @rr: record/replay debugging @@ -45,9 +45,10 @@ def __init__(self, ql: Qiling, init_hook: str = "", rr: bool = False, script: st super().__init__() - self.dbg_hook(init_hook) + # filter out entry_point of loader if presented + self.dbg_hook(list(filter(lambda d: int(d, 0) != self.ql.loader.entry_point, init_hook))) - def dbg_hook(self, init_hook: str): + def dbg_hook(self, init_hook: List[str]): """ initial hook to prepare everything we need """ @@ -67,7 +68,7 @@ def bp_handler(ql, address, size, bp_list): if bp.hitted: return - qdb_print(QDB_MSG.INFO, f"hit breakpoint at 0x{self.cur_addr:08x}") + qdb_print(QDB_MSG.INFO, f"hit breakpoint at {self.cur_addr:#x}") bp.hitted = True ql.stop() @@ -78,8 +79,9 @@ def bp_handler(ql, address, size, bp_list): if self.ql.os.type == QL_OS.BLOB: self.ql.loader.entry_point = self.ql.loader.load_address - elif init_hook and self.ql.loader.entry_point != int(init_hook, 0): - self.do_breakpoint(init_hook) + elif init_hook: + for each_hook in init_hook: + self.do_breakpoint(each_hook) if self.ql.entry_point: self.cur_addr = self.ql.entry_point @@ -128,7 +130,13 @@ def _run(self, address: int = 0, end: int = 0, count: int = 0) -> None: self.ql.os.run() else: - self.ql.emu_start(begin=address, end=end, count=count) + + # use os.run instead if QL_STATE is NOT_SET, for keeping integrity of hooks and patches in Qdb + if self.ql._state is QL_STATE.NOT_SET: + self.ql.os.run() + + else: + self.ql.emu_start(begin=address, end=end, count=count) @contextmanager def _save(self, reg=True, mem=True, hw=False, fd=False, cpu_context=False, os=False, loader=False): diff --git a/qiling/utils.py b/qiling/utils.py index f76e9610b..f4ca25a85 100644 --- a/qiling/utils.py +++ b/qiling/utils.py @@ -350,15 +350,18 @@ def __int_nothrow(v: str, /) -> Optional[int]: return None # qdb init args are independent and may include any combination of: rr enable, init hook and script - for a in args: - if a == 'rr': + arg_init_hook = [] + for arg in args: + if arg == 'rr': kwargs['rr'] = True - elif __int_nothrow(a) is not None: - kwargs['init_hook'] = a + elif __int_nothrow(arg) is not None: + arg_init_hook.append(arg) else: - kwargs['script'] = a + kwargs['script'] = arg + else: + kwargs['init_hook'] = arg_init_hook else: raise QlErrorOutput('Debugger not supported') From a5e52277a26d9f53fc62eed56f512e0418964e37 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Thu, 9 Nov 2023 14:46:44 +0800 Subject: [PATCH 2/5] postpone the hooks integrity feature --- qiling/debugger/qdb/qdb.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 032624092..d5ab27e58 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -130,13 +130,7 @@ def _run(self, address: int = 0, end: int = 0, count: int = 0) -> None: self.ql.os.run() else: - - # use os.run instead if QL_STATE is NOT_SET, for keeping integrity of hooks and patches in Qdb - if self.ql._state is QL_STATE.NOT_SET: - self.ql.os.run() - - else: - self.ql.emu_start(begin=address, end=end, count=count) + self.ql.emu_start(begin=address, end=end, count=count) @contextmanager def _save(self, reg=True, mem=True, hw=False, fd=False, cpu_context=False, os=False, loader=False): From 6799389ed3098fb4c0ac5a4e5cef95ae907ca7be Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Fri, 10 Nov 2023 17:05:29 +0800 Subject: [PATCH 3/5] add qdb feature: keep integrity of hooks and patches --- qiling/debugger/qdb/qdb.py | 57 ++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index d5ab27e58..d20a96309 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -9,7 +9,7 @@ from contextlib import contextmanager from qiling import Qiling -from qiling.const import QL_OS, QL_ARCH, QL_ENDIAN, QL_STATE +from qiling.const import QL_OS, QL_ARCH, QL_ENDIAN, QL_STATE, QL_VERBOSE from qiling.debugger import QlDebugger from .utils import setup_context_render, setup_branch_predictor, setup_address_marker, SnapshotManager, run_qdb_script @@ -76,13 +76,6 @@ def bp_handler(ql, address, size, bp_list): self.ql.hook_code(bp_handler, self.bp_list) - if self.ql.os.type == QL_OS.BLOB: - self.ql.loader.entry_point = self.ql.loader.load_address - - elif init_hook: - for each_hook in init_hook: - self.do_breakpoint(each_hook) - if self.ql.entry_point: self.cur_addr = self.ql.entry_point else: @@ -90,6 +83,39 @@ def bp_handler(ql, address, size, bp_list): self.init_state = self.ql.save() + # stop emulator once interp. have been done emulating + if addr_elf_entry := getattr(self.ql.loader, 'elf_entry'): + handler = self.ql.hook_address(lambda ql: ql.stop(), addr_elf_entry) + else: + handler = self.ql.hook_address(lambda ql: ql.stop(), self.ql.loader.entry_point) + + # suppress logging temporary + _verbose = self.ql.verbose + self.ql.verbose = QL_VERBOSE.DISABLED + + # init os for integrity of hooks and patches, + self.ql.os.run() + + handler.remove() + + # ignore the memory unmap error for now, due to the MIPS memory layout issue + try: + self.ql.mem.unmap_all() + except: + pass + + self.ql.restore(self.init_state) + + # resotre logging verbose + self.ql.verbose = _verbose + + if self.ql.os.type is QL_OS.BLOB: + self.ql.loader.entry_point = self.ql.loader.load_address + + elif init_hook: + for each_hook in init_hook: + self.do_breakpoint(each_hook) + if self._script: run_qdb_script(self, self._script) else: @@ -123,14 +149,7 @@ def _run(self, address: int = 0, end: int = 0, count: int = 0) -> None: if getattr(self.ql.arch, 'is_thumb', False): address |= 0b1 - # assume we're running PE if on Windows - if self.ql.os.type == QL_OS.WINDOWS: - self.ql.count = count - self.ql.entry_point = address - self.ql.os.run() - - else: - self.ql.emu_start(begin=address, end=end, count=count) + self.ql.emu_start(begin=address, end=end, count=count) @contextmanager def _save(self, reg=True, mem=True, hw=False, fd=False, cpu_context=False, os=False, loader=False): @@ -374,7 +393,6 @@ def do_start(self, *args) -> None: """ if self.ql.arch != QL_ARCH.CORTEX_M: - self.ql.restore(self.init_state) self.do_context() @@ -535,6 +553,11 @@ def do_show(self, *args) -> None: show some runtime information """ + qdb_print(QDB_MSG.INFO, f"Entry point: {self.ql.loader.entry_point:#x}") + + if addr_elf_entry := getattr(self.ql.loader, 'elf_entry'): + qdb_print(QDB_MSG.INFO, f"ELF entry: {addr_elf_entry:#x}") + for info_line in self.ql.mem.get_formatted_mapinfo(): qdb_print(QDB_MSG.INFO, info_line) From 6da99b9c6cfe1095ae04927a2544adcf694b055c Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Fri, 10 Nov 2023 17:06:25 +0800 Subject: [PATCH 4/5] add qdb feature: keep integrity of hooks and patches and show both entry points for elf and ld --- qiling/debugger/qdb/qdb.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index d20a96309..5e4a48e23 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -548,11 +548,12 @@ def do_show_args(self, argc: int = -1): args = reg_args + stk_args qdb_print(QDB_MSG.INFO, f'args: {args}') - def do_show(self, *args) -> None: + def do_show(self, keyword: Optional[str] = None, *args) -> None: """ show some runtime information """ +<<<<<<< Updated upstream qdb_print(QDB_MSG.INFO, f"Entry point: {self.ql.loader.entry_point:#x}") if addr_elf_entry := getattr(self.ql.loader, 'elf_entry'): @@ -560,6 +561,21 @@ def do_show(self, *args) -> None: for info_line in self.ql.mem.get_formatted_mapinfo(): qdb_print(QDB_MSG.INFO, info_line) +======= + info_lines = iter(self.ql.mem.get_formatted_mapinfo()) + + # print filed name first + qdb_print(QDB_MSG.INFO, next(info_lines)) + + # keyword filtering + if keyword: + lines = filter(lambda line: keyword in line, info_lines) + else: + lines = info_lines + + for line in lines: + qdb_print(QDB_MSG.INFO, line) +>>>>>>> Stashed changes qdb_print(QDB_MSG.INFO, f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") qdb_print(QDB_MSG.INFO, f"Marked symbol: {[{key:hex(val)} for key,val in self.marker.mark_list]}") From 6ea227530169465251f5545b39b5a0686795a5c0 Mon Sep 17 00:00:00 2001 From: ucgJhe Date: Fri, 10 Nov 2023 17:08:53 +0800 Subject: [PATCH 5/5] add qdb feature: simple filter for command show --- qiling/debugger/qdb/qdb.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/qiling/debugger/qdb/qdb.py b/qiling/debugger/qdb/qdb.py index 5e4a48e23..a24940ae4 100644 --- a/qiling/debugger/qdb/qdb.py +++ b/qiling/debugger/qdb/qdb.py @@ -553,15 +553,11 @@ def do_show(self, keyword: Optional[str] = None, *args) -> None: show some runtime information """ -<<<<<<< Updated upstream qdb_print(QDB_MSG.INFO, f"Entry point: {self.ql.loader.entry_point:#x}") if addr_elf_entry := getattr(self.ql.loader, 'elf_entry'): qdb_print(QDB_MSG.INFO, f"ELF entry: {addr_elf_entry:#x}") - for info_line in self.ql.mem.get_formatted_mapinfo(): - qdb_print(QDB_MSG.INFO, info_line) -======= info_lines = iter(self.ql.mem.get_formatted_mapinfo()) # print filed name first @@ -575,7 +571,6 @@ def do_show(self, keyword: Optional[str] = None, *args) -> None: for line in lines: qdb_print(QDB_MSG.INFO, line) ->>>>>>> Stashed changes qdb_print(QDB_MSG.INFO, f"Breakpoints: {[hex(addr) for addr in self.bp_list.keys()]}") qdb_print(QDB_MSG.INFO, f"Marked symbol: {[{key:hex(val)} for key,val in self.marker.mark_list]}")