The existing shellcode tests end emulation from their execve hook, so they
never reach the trailing '/bin/sh' string and never execute it as code. These
two deliberately let execve return and fall through into it, which raises an
undefined/reserved instruction exception on ARM64 and MIPS.
Without the preceding commits both fail with
QlErrorCoreHook: _hook_intr_cb : not handled.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Same class of crash as the ARM/ARM64 fix: MIPS Linux only hooked the
syscall exception, so any other CPU exception reached the unhandled-interrupt
dispatcher and raised QlErrorCoreHook.
In examples/shellcode_run.py the rootfs-less MIPS shellcode runs past its
denied execve into the trailing "/bin/sh" string, which decodes as a
reserved instruction (exception 20) -- delivered as SIGILL on a real kernel.
Hook the reserved-instruction exception and route it through the existing
hook_cpu_exception handler. Add a small EXCP enum to mips_const so the
syscall and reserved-instruction codes are named rather than magic numbers.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
ARM and ARM64 Linux only hooked the svc syscall trap (exception 2), so any
other CPU exception fell through to the unhandled-interrupt dispatcher and
raised QlErrorCoreHook, crashing the framework.
This surfaces in examples/shellcode_run.py: since execve was patched to
return -1 for paths outside the rootfs, rootfs-less shellcode no longer
stops at execve and instead runs into its trailing data. On ARM64 that data
decodes as an undefined instruction (exception 1), which a real kernel would
deliver as SIGILL and terminate the process.
Hook EXCP.UDEF and emulate that termination by stopping cleanly, and replace
the magic syscall-trap number with the named EXCP.SWI constant.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Drives ql_syscall_statx on a big-endian MIPS context and checks that
stx_mode, read back in the guest's byte order, still reports a directory
(S_IFDIR). Before the fix the statx struct was emitted little-endian
regardless of guest endianness, so the type bits were byte-swapped and a
directory looked like a regular file. Self-contained (no rootfs binary,
runs on stock unicorn).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Statx32/Statx64 (and their StatxTimestamp structs) were defined only as
little-endian ctypes.Structure, and ql_syscall_statx used them regardless
of guest endianness. Every other stat-family struct already has an explicit
big-endian variant selected by ql.arch.endian; statx was the lone gap.
On a big-endian guest (e.g. MIPS/MIPS64 EB) this byte-swaps every statx
field. In particular stx_mode loses its S_IFDIR bit, so a directory looks
like a plain file and `busybox ls /` just echoes the argument instead of
listing the directory.
Add Statx{32,64}EB / StatxTimestamp{32,64}EB (ctypes.BigEndianStructure,
reusing the little-endian field layouts) and pick them when the guest is
big-endian. The little-endian path is byte-for-byte unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
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]>
The first line reads `from future import __annotations__`, with the
module name and the imported symbol swapped. The intent was clearly the
PEP 563 future statement `from __future__ import annotations`.
As written, the line fails in both possible situations:
- without the third-party `future` package installed (the common case),
the import raises `ModuleNotFoundError`, so `QILING_IDA` is never
defined;
- with that package installed, the import silently succeeds but binds
the module's empty `__annotations__` dict, so PEP 563 is *not*
enabled — while the annotations in this file (`ql: Qiling`) rely on
it, `Qiling` being imported under `TYPE_CHECKING` only.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Pru9dwqPVrDakERYZwzjUx
FiberManager.free/set/get set `self.last_error` on the FiberManager
instance, but nothing ever reads that attribute. The Windows last-error
that GetLastError returns lives on `ql.os.last_error`, so FlsFree,
FlsSetValue and FlsGetValue never actually reported ERROR_INVALID_PARAMETER
for an invalid FLS index. Route the three writes to `self.ql.os.last_error`,
consistent with every other kernel32 hook.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
call_dll_entrypoint() caught the DllMain UcError and restored registers but
left ql._state at STARTED, since emu_start() only clears it on success. The
'emu_state is not STARTED' gate then silently skipped DllMain for every DLL
loaded afterward. Reset the state to STOPPED on the exception path.
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]>