Skip to content

Commit

Permalink
Merge pull request #1285 from elicn/fix-gdb-thumb
Browse files Browse the repository at this point in the history
Fix gdb attach on ARM thumb mode
  • Loading branch information
xwings committed Dec 9, 2022
2 parents 6b4f5c8 + 75d5d58 commit f714c8f
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 31 deletions.
2 changes: 1 addition & 1 deletion qiling/arch/arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
10 changes: 8 additions & 2 deletions qiling/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 12 additions & 15 deletions qiling/debugger/gdb/gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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}'
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down
38 changes: 25 additions & 13 deletions qiling/debugger/gdb/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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}')
Expand Down

0 comments on commit f714c8f

Please sign in to comment.