1515
1616import os
1717import socket
18+ import select
1819import re
1920import tempfile
2021from functools import partial
3031)
3132
3233from qiling import Qiling
33- from qiling .const import QL_ARCH , QL_ENDIAN , QL_OS , QL_STATE
34+ from qiling .const import QL_ARCH , QL_ENDIAN , QL_OS
3435from qiling .debugger import QlDebugger
3536from qiling .debugger .gdb .xmlregs import QlGdbFeatures
3637from qiling .debugger .gdb .utils import QlGdbUtils
5657SIGCONT = 17
5758SIGSTOP = 18
5859
60+ # translate a unicorn cpu fault into the closest posix signal, shared by the
61+ # continue ('c') and single-step ('s') handlers when emulation faults
62+ UC_ERROR_SIGMAP = {
63+ UC_ERR_READ_UNMAPPED : SIGSEGV ,
64+ UC_ERR_WRITE_UNMAPPED : SIGSEGV ,
65+ UC_ERR_FETCH_UNMAPPED : SIGSEGV ,
66+ UC_ERR_WRITE_PROT : SIGSEGV ,
67+ UC_ERR_READ_PROT : SIGSEGV ,
68+ UC_ERR_FETCH_PROT : SIGSEGV ,
69+ UC_ERR_READ_UNALIGNED : SIGBUS ,
70+ UC_ERR_WRITE_UNALIGNED : SIGBUS ,
71+ UC_ERR_FETCH_UNALIGNED : SIGBUS ,
72+ UC_ERR_INSN_INVALID : SIGILL
73+ }
74+
5975# common replies
6076REPLY_ACK = b'+'
6177REPLY_EMPTY = b''
@@ -124,6 +140,10 @@ def run(self):
124140 server = GdbSerialConn (self .ip , self .port , self .ql .log )
125141 killed = False
126142
143+ # let the run hook break into a free-running guest when the client sends
144+ # an async interrupt (ctrl-c / \x03); see QlGdbUtils.dbg_hook.
145+ self .gdb .check_interrupt = server .poll_interrupt
146+
127147 def __hexstr (value : int , nibbles : int = 0 ) -> str :
128148 """Encode a value into a hex string.
129149 """
@@ -226,33 +246,24 @@ def handle_c(subcmd: str) -> Reply:
226246 try :
227247 self .gdb .resume_emu ()
228248 except UcError as err :
229- sigmap = {
230- UC_ERR_READ_UNMAPPED : SIGSEGV ,
231- UC_ERR_WRITE_UNMAPPED : SIGSEGV ,
232- UC_ERR_FETCH_UNMAPPED : SIGSEGV ,
233- UC_ERR_WRITE_PROT : SIGSEGV ,
234- UC_ERR_READ_PROT : SIGSEGV ,
235- UC_ERR_FETCH_PROT : SIGSEGV ,
236- UC_ERR_READ_UNALIGNED : SIGBUS ,
237- UC_ERR_WRITE_UNALIGNED : SIGBUS ,
238- UC_ERR_FETCH_UNALIGNED : SIGBUS ,
239- UC_ERR_INSN_INVALID : SIGILL
240- }
241-
242249 # determine signal from uc error; default to SIGTERM
243- reply = f'S{ sigmap .get (err .errno , SIGTERM ):02x} '
250+ reply = f'S{ UC_ERROR_SIGMAP .get (err .errno , SIGTERM ):02x} '
244251
245252 except KeyboardInterrupt :
246253 # emulation was interrupted with ctrl+c
247254 reply = f'S{ SIGINT :02x} '
248255
249256 else :
250- if getattr (self .ql .arch , 'effective_pc' , self .ql .arch .regs .arch_pc ) == self .gdb .last_bp :
257+ if self .gdb .interrupted :
258+ # emulation was stopped by an async interrupt from the client
259+ reply = f'S{ SIGINT :02x} '
260+ elif getattr (self .ql .arch , 'effective_pc' , self .ql .arch .regs .arch_pc ) == self .gdb .last_bp :
251261 # emulation stopped because it hit a breakpoint
252262 reply = f'S{ SIGTRAP :02x} '
253263 else :
254- # emulation has completed successfully
255- reply = f'W{ self .ql .os .exit_code :02x} '
264+ # emulation has completed successfully. note bare-metal os layers
265+ # do not have an exit code (issue #1276)
266+ reply = f'W{ getattr (self .ql .os , "exit_code" , 0 ):02x} '
256267
257268 return reply
258269
@@ -662,10 +673,17 @@ def handle_v(subcmd: str) -> Reply:
662673 for grp in groups :
663674 cmd , * tid = grp .split (':' , maxsplit = 1 )
664675
665- if cmd in ('c' , f'C{ SIGTRAP :02x} ' ):
676+ # 'C sig' and 'S sig' resume or step while delivering a signal
677+ # to the guest. we do not deliver signals, so the signal value
678+ # is ignored and the action is carried out as a plain resume or
679+ # step. matching only 'C05' and 'S05' here made clients that
680+ # resume with any other pending signal (e.g. 'S0f' after a stop
681+ # reply we sent) receive an empty reply and bail out with
682+ # 'Invalid remote reply' (issue #1377)
683+ if cmd [:1 ] in ('c' , 'C' ):
666684 return handle_c ('' )
667685
668- elif cmd in ('s' , f'S { SIGTRAP :02x } ' ):
686+ elif cmd [: 1 ] in ('s' , 'S ' ):
669687 return handle_s ('' )
670688
671689 # FIXME: not sure how to handle multiple command
@@ -678,11 +696,23 @@ def handle_s(subcmd: str) -> Reply:
678696 """Perform a single step.
679697 """
680698
681- self .gdb .resume_emu (steps = 1 )
699+ try :
700+ self .gdb .resume_emu (steps = 1 )
701+ except UcError as err :
702+ # stepping faulted; report the closest posix signal
703+ return f'S{ UC_ERROR_SIGMAP .get (err .errno , SIGTERM ):02x} '
704+
705+ except KeyboardInterrupt :
706+ # emulation was interrupted with ctrl+c
707+ return f'S{ SIGINT :02x} '
682708
683- # if emulation has been stopped, signal program termination
684- if self .ql .emu_state is QL_STATE .STOPPED :
685- return f'S{ SIGTERM :02x} '
709+ # emu_start always leaves emu_state as STOPPED after a step, so that
710+ # cannot tell an ordinary step apart from the guest exiting (see
711+ # issues #1377 and #1538). instead, the guest has terminated only
712+ # when the step carried pc all the way to the emulation exit point.
713+ if getattr (self .ql .arch , 'effective_pc' , self .ql .arch .regs .arch_pc ) == self .gdb .exit_point :
714+ # program terminated; report its exit code
715+ return f'W{ getattr (self .ql .os , "exit_code" , 0 ):02x} '
686716
687717 # otherwise, this is just single stepping
688718 return f'S{ SIGTRAP :02x} '
@@ -823,6 +853,27 @@ def close(self):
823853 self .client .close ()
824854 self .sock .close ()
825855
856+ def poll_interrupt (self ) -> bool :
857+ """Non-blocking check for an async interrupt from the client.
858+
859+ While the target is running the only thing gdb sends is a bare ``\\ x03``
860+ break byte (it waits for a stop reply before sending anything else), so
861+ any readable bytes here are that interrupt or a stray protocol ack.
862+ Returns True if a break was seen. Called from the run hook, so it must
863+ never block.
864+ """
865+
866+ readable , _ , _ = select .select ([self .client ], [], [], 0 )
867+ if not readable :
868+ return False
869+
870+ try :
871+ incoming = self .client .recv (self .BUFSIZE )
872+ except (ConnectionError , OSError ):
873+ return False
874+
875+ return b'\x03 ' in incoming
876+
826877 def readpackets (self ) -> Iterator [bytes ]:
827878 """Iterate through incoming packets in an active connection until
828879 it is terminated.
0 commit comments