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]>
The stub drove continue by calling emu_start synchronously and only read
the socket again once the target stopped on its own, so the bare \x03 break
byte a client sends to pause a running target was never seen. A guest that
free-runs (e.g. an idle/event loop) could not be interrupted at all --
gdb/Ghidra reported 'Cannot execute this command while the target is running'.
Poll the client socket for the break byte from the per-instruction run hook
(dbg_hook), which does run on the emulation thread during emu_start:
- GdbSerialConn.poll_interrupt(): non-blocking select+recv, True on \x03.
- QlGdbUtils.dbg_hook: throttled (every INTR_POLL_INTERVAL insns) check of an
installed check_interrupt callback; on a break, stop emulation and record it.
- handle_c: reply SIGINT when the stop was an interrupt rather than a
breakpoint or normal exit.
Add a regression test that lets an infinite-loop guest free-run, sends the
bare break byte and asserts the stop-reply is 'S02' (SIGINT). Without the
fix no reply ever arrives, so the test stops the guest itself and fails
rather than hanging the run.
Also verified against a free-running MIPS64 BE guest: \x03 -> S02 in <1ms.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
handle_s answered every single-step ('s') with a SIGTERM stop-reply
whenever ql.emu_state was QL_STATE.STOPPED. But emu_start always leaves
the state STOPPED after running the requested step count, so the guard
was true on every step and gdb clients saw a spurious termination signal,
disconnecting mid-debug (issues #1377, #1538).
Give handle_s the same exit-vs-trap discrimination handle_c already uses:
a step reports SIGTRAP unless it carried pc to the emulation exit point,
in which case the guest has actually terminated and we reply W{exit_code}.
Also wrap the step in the same UcError/KeyboardInterrupt handling as
handle_c so a fault while stepping maps to a signal instead of crashing
the stub, and hoist the shared uc-error->signal map to a module constant.
Add a regression test that single-steps over the gdb stub and asserts the
stop-reply is 'S05' (SIGTRAP), which fails as 'S0f' (SIGTERM) without the fix.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
linux_mips_socket_options used generic/incorrect values. MIPS has its own
SO_* numbering (arch/mips/include/uapi/asm/socket.h) with the buffer/timeout/
type options in the 0x1000 range. The table listed SO_SNDBUF=0x01,
SO_RCVBUF=0x02 (should be 0x1001/0x1002), SO_SND/RCVLOWAT and SO_SND/RCVTIMEO_OLD
all wrong, SO_RCVLOWAT=0x04 silently collided with SO_REUSEADDR (Enum alias),
and SO_OOBINLINE/SO_REUSEPORT were 0x00.
A guest setsockopt(SOL_SOCKET, SO_RCVBUF, ...) (e.g. busybox ping) therefore
raised 'Could not convert emulated socket option 4098'. Correct all values to
the MIPS uapi and add the missing SO_TYPE/SO_ERROR/SO_ACCEPTCONN/SO_PROTOCOL/
SO_DOMAIN.
Add test_elf.ELFTest.test_setsockopt_mips_so_rcvbuf: opens a socket on a MIPS
guest and asserts setsockopt(SOL_SOCKET, SO_RCVBUF) succeeds. Self-contained,
runs on stock unicorn; fails on dev, passes with the fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Extend the record alignment to the legacy getdents path. The kernel rounds
both getdents and getdents64 records up to the alignment of their leading
d_ino (ALIGN(reclen, sizeof(long))); without it a strict-alignment guest
(MIPS) faults walking the buffer, e.g. older glibc busybox 'ls' (which uses
the legacy getdents syscall) crashes on directory listings.
Legacy linux_dirent stores d_type in the record's last byte (offset
d_reclen-1), so the alignment padding goes between d_name and d_type to keep
d_type there -- padding without relocating d_type is what got the earlier
blanket attempt (PR #1419) reverted. getdents64 (d_type before d_name) is
unchanged.
Add test_linux_getdents_alignment: walks legacy getdents records on a
big-endian MIPS guest and asserts each is aligned, tiles the buffer, and
keeps d_type (DT_DIR for '.'/'..'). Runs on stock unicorn.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Modern glibc (recent toolchains) issues clone3 from pthread_create rather
than clone, so thread creation fails on any guest built against it: Qiling
does not return -ENOSYS for unimplemented syscalls (it logs a warning and
leaves the return register untouched), so glibc's clone3->clone fallback
never fires.
Add ql_syscall_clone3, which unpacks struct clone_args and delegates to the
existing clone() handler. Translations: child_stack = stack + stack_size
(clone3 passes the stack base plus a size; legacy clone wants the highest
address), exit_signal folded into the flags' CSIGNAL byte, and an x8664
pre-swap that cancels ql_syscall_clone's arch-specific newtls<->child_tidptr
swap.
Add a self-contained regression test (test_clone3_translates_to_clone) that
drives ql_syscall_clone3 directly and asserts the translation for both the
generic path and the x8664 swap. Runs on stock unicorn; no clone3 binary
needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Calls ql_syscall_getdents64 on a directory and walks the returned
linux_dirent64 records, asserting every record (and thus its leading u64
d_ino) starts on an 8-byte boundary. Before the fix d_reclen was not
rounded up, so records were misaligned and a strict-alignment guest (e.g.
MIPS) faulted with an unaligned load. Self-contained; runs on stock unicorn.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
getdents64 wrote linux_dirent64 records with d_reclen = header + name,
without rounding up to the alignment of the leading u64 d_ino. The next
record's d_ino then lands unaligned, and a strict-alignment guest (e.g.
MIPS/MIPS64) faults with UC_ERR_READ_UNALIGNED when walking the buffer.
x86 tolerates the unaligned load, which is why this was never caught.
Round d_reclen up to the d_ino alignment, matching the kernel. The rounding
is scoped to the getdents64 (is_64) branch only: getdents64 places d_type
before d_name so the trailing pad bytes are inert, whereas the legacy
getdents layout stores d_type at offset d_reclen-1 and would break if padded
the same way -- that is what got the earlier blanket fix (PR #1419) reverted.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>