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 53e034d2e..cf2a23b6a 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 @@ -234,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: @@ -666,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}' @@ -709,8 +708,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 @@ -721,12 +721,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..5e3b208bf 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,32 +41,44 @@ 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}') 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)) - 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}') + 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 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}')