Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix gdb attach on ARM thumb mode #1285

Merged
merged 4 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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