From d1b6b8064e3d19eeb2080254ee6d0f89038abfd1 Mon Sep 17 00:00:00 2001 From: elicn Date: Tue, 6 Dec 2022 19:40:54 +0200 Subject: [PATCH 1/4] Use aligned entry point address on thumb mode --- qiling/debugger/gdb/gdb.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 53e034d2e..7b0a4b9ee 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -95,6 +95,11 @@ def __init__(self, ql: Qiling, ip: str = '127.0.0.1', port: int = 9999): else: entry_point = ql.os.entry_point + # though linkers set the entry point LSB to indicate arm thumb mode, the + # effective entry point address is aligned. make sure we have it aligned + if hasattr(ql.arch, 'is_thumb'): + entry_point &= ~0b1 + # Only part of the binary file will be debugged. if ql.entry_point is not None: entry_point = ql.entry_point From d13d91475e9f418cef7b680c0bc1ceac1a52224b Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 7 Dec 2022 17:52:59 +0200 Subject: [PATCH 2/4] Support breakpoints with size --- qiling/debugger/gdb/gdb.py | 14 ++++++-------- qiling/debugger/gdb/utils.py | 33 +++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index 7b0a4b9ee..b0d25a84c 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -714,8 +714,9 @@ def handle_Z(subcmd: str) -> Reply: # 4 = access watchpoint if type == 0: - self.gdb.bp_insert(addr) - return REPLY_OK + success = self.gdb.bp_insert(addr, kind) + + return REPLY_OK if success else 'E22' return REPLY_EMPTY @@ -726,12 +727,9 @@ def handle_z(subcmd: str) -> Reply: type, addr, kind = (int(p, 16) for p in subcmd.split(',')) if type == 0: - try: - self.gdb.bp_remove(addr) - except ValueError: - return 'E22' - else: - return REPLY_OK + success = self.gdb.bp_remove(addr, kind) + + return REPLY_OK if success else 'E22' return REPLY_EMPTY diff --git a/qiling/debugger/gdb/utils.py b/qiling/debugger/gdb/utils.py index f07f347e0..45d04e840 100644 --- a/qiling/debugger/gdb/utils.py +++ b/qiling/debugger/gdb/utils.py @@ -18,7 +18,7 @@ def __init__(self, ql: Qiling, entry_point: int, exit_point: int): self.ql = ql self.exit_point = exit_point - self.bp_list = [] + self.swbp = set() self.last_bp = None def __entry_point_hook(ql: Qiling): @@ -41,7 +41,7 @@ def dbg_hook(self, ql: Qiling, address: int, size: int): if address == self.last_bp: self.last_bp = None - elif address in self.bp_list: + elif address in self.swbp: self.last_bp = address ql.log.info(f'{PROMPT} breakpoint hit, stopped at {address:#x}') @@ -52,15 +52,32 @@ def dbg_hook(self, ql: Qiling, address: int, size: int): # ql.log.debug(f'{PROMPT} emulation entrypoint at {self.entry_point:#x}') # ql.log.debug(f'{PROMPT} emulation exitpoint at {self.exit_point:#x}') - def bp_insert(self, addr: int): - if addr not in self.bp_list: - self.bp_list.append(addr) - self.ql.log.info(f'{PROMPT} breakpoint added at {addr:#x}') + def bp_insert(self, addr: int, size: int): + targets = set(addr + i for i in range(size or 1)) + + if targets.intersection(self.swbp): + return False + + for bp in targets: + self.swbp.add(bp) + + self.ql.log.info(f'{PROMPT} breakpoint added at {addr:#x}') + + return True + + def bp_remove(self, addr: int, size: int) -> bool: + targets = set(addr + i for i in range(size or 1)) + + if not targets.issubset(self.swbp): + return False + + for bp in targets: + self.swbp.remove(bp) - def bp_remove(self, addr: int): - self.bp_list.remove(addr) self.ql.log.info(f'{PROMPT} breakpoint removed from {addr:#x}') + return True + def resume_emu(self, address: Optional[int] = None, steps: int = 0): if address is None: address = self.ql.arch.regs.arch_pc From 3be66aab0bdfc96e18cacdf98eea394c3bffcf32 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 7 Dec 2022 17:54:34 +0200 Subject: [PATCH 3/4] Handle thumb on resume from breakpoint --- qiling/debugger/gdb/gdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index b0d25a84c..a3e12f7d1 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -239,7 +239,7 @@ def handle_c(subcmd: str) -> Reply: reply = f'S{SIGINT:02x}' else: - if self.ql.arch.regs.arch_pc == self.gdb.last_bp: + if getattr(self.ql.arch, 'effective_pc', self.ql.arch.regs.arch_pc) == self.gdb.last_bp: # emulation stopped because it hit a breakpoint reply = f'S{SIGTRAP:02x}' else: From 75d5d580b50950f14185ee4787d6c71a512045d9 Mon Sep 17 00:00:00 2001 From: elicn Date: Wed, 7 Dec 2022 18:02:25 +0200 Subject: [PATCH 4/4] Final touches --- qiling/arch/arm.py | 2 +- qiling/core.py | 10 ++++++++-- qiling/debugger/gdb/gdb.py | 6 ------ qiling/debugger/gdb/utils.py | 7 +------ 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/qiling/arch/arm.py b/qiling/arch/arm.py index 9c6a0ce8b..b290ee8e5 100644 --- a/qiling/arch/arm.py +++ b/qiling/arch/arm.py @@ -68,7 +68,7 @@ def effective_pc(self) -> int: """ # append 1 to pc if in thumb mode, or 0 otherwise - return self.regs.pc + int(self.is_thumb) + return self.regs.pc | int(self.is_thumb) @property def disassembler(self) -> Cs: diff --git a/qiling/core.py b/qiling/core.py index b745668ad..efa05fdb5 100644 --- a/qiling/core.py +++ b/qiling/core.py @@ -737,8 +737,14 @@ def emu_start(self, begin: int, end: int, timeout: int = 0, count: int = 0): count : max emulation steps (instructions count); unlimited by default """ - if self._arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M) and self._arch._init_thumb: - begin |= 1 + # FIXME: we cannot use arch.is_thumb to determine this because unicorn sets the coresponding bit in cpsr + # only when pc is set. unicorn sets or clears the thumb mode bit based on pc lsb, ignoring the mode it + # was initialized with. + # + # either unicorn is patched to reflect thumb mode in cpsr upon initialization, or we pursue the same logic + # by determining the endianess by address lsb. either way this condition should not be here + if getattr(self.arch, '_init_thumb', False): + begin |= 0b1 # reset exception status before emulation starts self._internal_exception = None diff --git a/qiling/debugger/gdb/gdb.py b/qiling/debugger/gdb/gdb.py index a3e12f7d1..cf2a23b6a 100644 --- a/qiling/debugger/gdb/gdb.py +++ b/qiling/debugger/gdb/gdb.py @@ -671,12 +671,6 @@ def handle_s(subcmd: str) -> Reply: """Perform a single step. """ - # BUG: a known unicorn caching issue causes it to emulate more - # steps than requestes. until that issue is fixed, single stepping - # is essentially broken. - # - # @see: https://github.com/unicorn-engine/unicorn/issues/1606 - self.gdb.resume_emu(steps=1) return f'S{SIGTRAP:02x}' diff --git a/qiling/debugger/gdb/utils.py b/qiling/debugger/gdb/utils.py index 45d04e840..5e3b208bf 100644 --- a/qiling/debugger/gdb/utils.py +++ b/qiling/debugger/gdb/utils.py @@ -47,11 +47,6 @@ def dbg_hook(self, ql: Qiling, address: int, size: int): ql.log.info(f'{PROMPT} breakpoint hit, stopped at {address:#x}') ql.stop() - # # TODO: not sure what this is about - # if address + size == self.exit_point: - # ql.log.debug(f'{PROMPT} emulation entrypoint at {self.entry_point:#x}') - # ql.log.debug(f'{PROMPT} emulation exitpoint at {self.exit_point:#x}') - def bp_insert(self, addr: int, size: int): targets = set(addr + i for i in range(size or 1)) @@ -83,7 +78,7 @@ def resume_emu(self, address: Optional[int] = None, steps: int = 0): address = self.ql.arch.regs.arch_pc if getattr(self.ql.arch, 'is_thumb', False): - address |= 1 + address |= 0b1 op = f'stepping {steps} instructions' if steps else 'resuming' self.ql.log.info(f'{PROMPT} {op} from {address:#x}')