mirror of
https://github.com/qilingframework/qiling.git
synced 2026-10-12 02:21:40 +00:00
gdb: accept vCont C/S actions carrying any signal
A client that resumes while a signal is pending sends a vCont action of the form 'C<sig>' (continue and deliver) or 'S<sig>' (step and deliver), e.g. 'vCont;S0f:pa410.1996;c:pa410.-1' -- the exact packet reported in issue #1377. handle_v matched only 'c'/'C05' and 's'/'S05', so any other signal fell through to an empty reply and clients aborted the session with 'Invalid remote reply:'. We do not deliver host signals to the guest, so the signal value carries no meaning for us: accept any of them and carry the action out as a plain resume or step, which is what the client asked for. This matters more now that the stub can stop with SIGINT on an async interrupt, since a client may well resume from such a stop with 'C02'. Also stop assuming os.exit_code exists when reporting termination: bare-metal os layers (QlOsMcu) do not define it, which turns the exit path into an AttributeError (seen in issue #1276). The underlying MCU interrupt handling of #1276 is out of scope here. Add a regression test asserting the '#1377' packet is answered with a SIGTRAP stop-reply and that a signalled continue runs the guest to termination; both replies are empty without the fix. Fixes #1377 Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c5a8a9b73e
commit
a13a8c8c1f
@@ -261,8 +261,9 @@ class QlGdb(QlDebugger):
|
||||
# emulation stopped because it hit a breakpoint
|
||||
reply = f'S{SIGTRAP:02x}'
|
||||
else:
|
||||
# emulation has completed successfully
|
||||
reply = f'W{self.ql.os.exit_code:02x}'
|
||||
# emulation has completed successfully. note bare-metal os layers
|
||||
# do not have an exit code (issue #1276)
|
||||
reply = f'W{getattr(self.ql.os, "exit_code", 0):02x}'
|
||||
|
||||
return reply
|
||||
|
||||
@@ -672,10 +673,17 @@ class QlGdb(QlDebugger):
|
||||
for grp in groups:
|
||||
cmd, *tid = grp.split(':', maxsplit=1)
|
||||
|
||||
if cmd in ('c', f'C{SIGTRAP:02x}'):
|
||||
# 'C sig' and 'S sig' resume or step while delivering a signal
|
||||
# to the guest. we do not deliver signals, so the signal value
|
||||
# is ignored and the action is carried out as a plain resume or
|
||||
# step. matching only 'C05' and 'S05' here made clients that
|
||||
# resume with any other pending signal (e.g. 'S0f' after a stop
|
||||
# reply we sent) receive an empty reply and bail out with
|
||||
# 'Invalid remote reply' (issue #1377)
|
||||
if cmd[:1] in ('c', 'C'):
|
||||
return handle_c('')
|
||||
|
||||
elif cmd in ('s', f'S{SIGTRAP:02x}'):
|
||||
elif cmd[:1] in ('s', 'S'):
|
||||
return handle_s('')
|
||||
|
||||
# FIXME: not sure how to handle multiple command
|
||||
@@ -704,7 +712,7 @@ class QlGdb(QlDebugger):
|
||||
# when the step carried pc all the way to the emulation exit point.
|
||||
if getattr(self.ql.arch, 'effective_pc', self.ql.arch.regs.arch_pc) == self.gdb.exit_point:
|
||||
# program terminated; report its exit code
|
||||
return f'W{self.ql.os.exit_code:02x}'
|
||||
return f'W{getattr(self.ql.os, "exit_code", 0):02x}'
|
||||
|
||||
# otherwise, this is just single stepping
|
||||
return f'S{SIGTRAP:02x}'
|
||||
|
||||
@@ -260,6 +260,55 @@ class DebuggerTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(replies, ['S05', 'S05', 'S05'])
|
||||
|
||||
def test_gdbdebug_vcont_signal_actions(self):
|
||||
# regression for issue #1377: a client resuming with a pending signal sends
|
||||
# a 'vCont;S<sig>' or 'vCont;C<sig>' action (e.g. 'S0f' after a stop-reply we
|
||||
# sent). the stub only recognized 'S05' and 'C05', replied empty to anything
|
||||
# else, and the client bailed out with 'Invalid remote reply'. any signal must
|
||||
# be accepted and carried out as a plain step or resume.
|
||||
ql = Qiling(["../examples/rootfs/x8664_linux/bin/x8664_hello"], "../examples/rootfs/x8664_linux", verbose=QL_VERBOSE.OFF)
|
||||
ql.debugger = 'gdb:127.0.0.1:9995'
|
||||
|
||||
replies = []
|
||||
|
||||
def gdb_test_client():
|
||||
# yield to allow ql to launch its gdbserver
|
||||
time.sleep(1.337 * 2)
|
||||
|
||||
with ReadingGdbClient('127.0.0.1', 9995) as client:
|
||||
client.send('qSupported:multiprocess+;swbreak+;hwbreak+;vContSupported+;xmlRegisters=i386')
|
||||
client.read_packet()
|
||||
client.send('QStartNoAckMode')
|
||||
client.read_packet()
|
||||
|
||||
client.send('vCont?')
|
||||
replies.append(client.read_packet())
|
||||
|
||||
# step while delivering SIGTERM; this is the packet reported in #1377
|
||||
client.send('vCont;S0f:p1.1;c:p1.-1')
|
||||
replies.append(client.read_packet())
|
||||
|
||||
# resume while delivering SIGTERM; runs the guest to completion
|
||||
client.send('vCont;C0f:p1.1')
|
||||
replies.append(client.read_packet())
|
||||
|
||||
client.send('k')
|
||||
|
||||
thread = threading.Thread(target=gdb_test_client, daemon=True)
|
||||
thread.start()
|
||||
|
||||
ql.run()
|
||||
thread.join(timeout=30)
|
||||
del ql
|
||||
|
||||
self.assertEqual(replies[0], 'vCont;c;C;s;S')
|
||||
|
||||
# the signalled step is an ordinary step: SIGTRAP, not an empty reply
|
||||
self.assertEqual(replies[1], 'S05')
|
||||
|
||||
# the signalled resume ran to termination and reported an exit code
|
||||
self.assertTrue(replies[2].startswith('W'), f'unexpected reply to signalled resume: {replies[2]!r}')
|
||||
|
||||
def test_gdbdebug_async_interrupt(self):
|
||||
# a free-running guest could not be interrupted at all: the stub is blocked
|
||||
# inside emu_start while the target runs, so the bare '\x03' break byte a
|
||||
|
||||
Reference in New Issue
Block a user