Skip to content

Commit f714c8f

Browse files
authored
Merge pull request #1285 from elicn/fix-gdb-thumb
Fix gdb attach on ARM thumb mode
2 parents 6b4f5c8 + 75d5d58 commit f714c8f

File tree

4 files changed

+46
-31
lines changed

4 files changed

+46
-31
lines changed

qiling/arch/arm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def effective_pc(self) -> int:
6868
"""
6969

7070
# append 1 to pc if in thumb mode, or 0 otherwise
71-
return self.regs.pc + int(self.is_thumb)
71+
return self.regs.pc | int(self.is_thumb)
7272

7373
@property
7474
def disassembler(self) -> Cs:

qiling/core.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -737,8 +737,14 @@ def emu_start(self, begin: int, end: int, timeout: int = 0, count: int = 0):
737737
count : max emulation steps (instructions count); unlimited by default
738738
"""
739739

740-
if self._arch.type in (QL_ARCH.ARM, QL_ARCH.CORTEX_M) and self._arch._init_thumb:
741-
begin |= 1
740+
# FIXME: we cannot use arch.is_thumb to determine this because unicorn sets the coresponding bit in cpsr
741+
# only when pc is set. unicorn sets or clears the thumb mode bit based on pc lsb, ignoring the mode it
742+
# was initialized with.
743+
#
744+
# either unicorn is patched to reflect thumb mode in cpsr upon initialization, or we pursue the same logic
745+
# by determining the endianess by address lsb. either way this condition should not be here
746+
if getattr(self.arch, '_init_thumb', False):
747+
begin |= 0b1
742748

743749
# reset exception status before emulation starts
744750
self._internal_exception = None

qiling/debugger/gdb/gdb.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,11 @@ def __init__(self, ql: Qiling, ip: str = '127.0.0.1', port: int = 9999):
9595
else:
9696
entry_point = ql.os.entry_point
9797

98+
# though linkers set the entry point LSB to indicate arm thumb mode, the
99+
# effective entry point address is aligned. make sure we have it aligned
100+
if hasattr(ql.arch, 'is_thumb'):
101+
entry_point &= ~0b1
102+
98103
# Only part of the binary file will be debugged.
99104
if ql.entry_point is not None:
100105
entry_point = ql.entry_point
@@ -234,7 +239,7 @@ def handle_c(subcmd: str) -> Reply:
234239
reply = f'S{SIGINT:02x}'
235240

236241
else:
237-
if self.ql.arch.regs.arch_pc == self.gdb.last_bp:
242+
if getattr(self.ql.arch, 'effective_pc', self.ql.arch.regs.arch_pc) == self.gdb.last_bp:
238243
# emulation stopped because it hit a breakpoint
239244
reply = f'S{SIGTRAP:02x}'
240245
else:
@@ -666,12 +671,6 @@ def handle_s(subcmd: str) -> Reply:
666671
"""Perform a single step.
667672
"""
668673

669-
# BUG: a known unicorn caching issue causes it to emulate more
670-
# steps than requestes. until that issue is fixed, single stepping
671-
# is essentially broken.
672-
#
673-
# @see: https://github.com/unicorn-engine/unicorn/issues/1606
674-
675674
self.gdb.resume_emu(steps=1)
676675

677676
return f'S{SIGTRAP:02x}'
@@ -709,8 +708,9 @@ def handle_Z(subcmd: str) -> Reply:
709708
# 4 = access watchpoint
710709

711710
if type == 0:
712-
self.gdb.bp_insert(addr)
713-
return REPLY_OK
711+
success = self.gdb.bp_insert(addr, kind)
712+
713+
return REPLY_OK if success else 'E22'
714714

715715
return REPLY_EMPTY
716716

@@ -721,12 +721,9 @@ def handle_z(subcmd: str) -> Reply:
721721
type, addr, kind = (int(p, 16) for p in subcmd.split(','))
722722

723723
if type == 0:
724-
try:
725-
self.gdb.bp_remove(addr)
726-
except ValueError:
727-
return 'E22'
728-
else:
729-
return REPLY_OK
724+
success = self.gdb.bp_remove(addr, kind)
725+
726+
return REPLY_OK if success else 'E22'
730727

731728
return REPLY_EMPTY
732729

qiling/debugger/gdb/utils.py

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def __init__(self, ql: Qiling, entry_point: int, exit_point: int):
1818
self.ql = ql
1919

2020
self.exit_point = exit_point
21-
self.bp_list = []
21+
self.swbp = set()
2222
self.last_bp = None
2323

2424
def __entry_point_hook(ql: Qiling):
@@ -41,32 +41,44 @@ def dbg_hook(self, ql: Qiling, address: int, size: int):
4141
if address == self.last_bp:
4242
self.last_bp = None
4343

44-
elif address in self.bp_list:
44+
elif address in self.swbp:
4545
self.last_bp = address
4646

4747
ql.log.info(f'{PROMPT} breakpoint hit, stopped at {address:#x}')
4848
ql.stop()
4949

50-
# # TODO: not sure what this is about
51-
# if address + size == self.exit_point:
52-
# ql.log.debug(f'{PROMPT} emulation entrypoint at {self.entry_point:#x}')
53-
# ql.log.debug(f'{PROMPT} emulation exitpoint at {self.exit_point:#x}')
50+
def bp_insert(self, addr: int, size: int):
51+
targets = set(addr + i for i in range(size or 1))
5452

55-
def bp_insert(self, addr: int):
56-
if addr not in self.bp_list:
57-
self.bp_list.append(addr)
58-
self.ql.log.info(f'{PROMPT} breakpoint added at {addr:#x}')
53+
if targets.intersection(self.swbp):
54+
return False
55+
56+
for bp in targets:
57+
self.swbp.add(bp)
58+
59+
self.ql.log.info(f'{PROMPT} breakpoint added at {addr:#x}')
60+
61+
return True
62+
63+
def bp_remove(self, addr: int, size: int) -> bool:
64+
targets = set(addr + i for i in range(size or 1))
65+
66+
if not targets.issubset(self.swbp):
67+
return False
68+
69+
for bp in targets:
70+
self.swbp.remove(bp)
5971

60-
def bp_remove(self, addr: int):
61-
self.bp_list.remove(addr)
6272
self.ql.log.info(f'{PROMPT} breakpoint removed from {addr:#x}')
6373

74+
return True
75+
6476
def resume_emu(self, address: Optional[int] = None, steps: int = 0):
6577
if address is None:
6678
address = self.ql.arch.regs.arch_pc
6779

6880
if getattr(self.ql.arch, 'is_thumb', False):
69-
address |= 1
81+
address |= 0b1
7082

7183
op = f'stepping {steps} instructions' if steps else 'resuming'
7284
self.ql.log.info(f'{PROMPT} {op} from {address:#x}')

0 commit comments

Comments
 (0)