Add MIPS64 (n64 ABI) Linux support#1631
Conversation
Introduce QL_ARCH.MIPS64 so Qiling can emulate MIPS64 Linux user-space
shellcode in both big- and little-endian. The native backends already
support it (Unicorn UC_MODE_MIPS64, Capstone/Keystone CS/KS_MODE_MIPS64).
- QlArchMIPS64: 64-bit arch class reusing the shared MIPS register map
- n64 calling convention and syscall ABI: the first eight arguments are
passed in registers (a0-a3 and a4-a7, the physical registers $8-$11)
with no on-stack shadow space, unlike o32
- n64 syscall table (base 5000) generated from the Linux uapi header
asm/unistd_n64.h
- wire MIPS64 through the Linux/blob OS layers, ELF machine detection
(EM_MIPS + ELFCLASS64) and n64 doubleword stack alignment
- add big- and little-endian MIPS64 shellcode tests
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up note: reconcile with #1630This PR and #1630 both modify the MIPS branch in #1630 introduces a shared Once both land, MIPS64 should also be wired to |
Demonstrate MIPS64 (n64 ABI) Linux shellcode emulation in both big- and little-endian, using a write/exit_group shellcode that prints to stdout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Linux/POSIX syscall layer has several arch-specific code paths keyed on QL_ARCH.MIPS (open flags, socket type/domain/level/option/ip-option tables, mmap flags, SHMLBA, the sigset/sigaction layout, SIGSTOP=23, the MIPS sigprocmask variant, the MIPS stat struct, the pipe()-returns-in-registers convention and set_thread_area). These values are MIPS-arch-specific and shared by the n64 ABI, so MIPS64 must take the same paths. Notably the MIPS64 stat struct already existed but was reached via QL_ARCH.MIPS + bits==64; with a distinct QL_ARCH.MIPS64 type that branch was missed and stat fell back to the x86 layout. The struct definitions are parameterised on arch.bits/native_type, so the n64 layouts fall out correctly once the branch fires. pread64's o32 stack-offset hack is intentionally left MIPS-only: under n64 the 64-bit offset arrives in a register, so the generic argument handling is already correct. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two unicorn-level obstacles prevented MIPS64 ELFs from running; both are
handled in QlArchMIPS64 so the rest of Qiling is unaffected:
- Address space: unicorn's MIPS64 CPU-MMU only executes within the low 2GB
(useg); MIPS64 ELFs link at 0x120000000 and Qiling lays the stack/mmap out
above 4GB. Switch the engine to unicorn's virtual-TLB mode and install an
identity TLB-fill hook (paddr == vaddr). This bypasses the CPU-MMU segment
checks while keeping virtual == physical, so the flat memory model (mem
map/read/write, loader writes, syscall pointer reads) keeps working as-is.
- Instruction set: unicorn's default MIPS64 core is an old MIPS III (R4000)
that lacks MIPS IV / MIPS64 instructions such as movn/movz, which compilers
emit freely; they trap as reserved instructions. Default MIPS64 to a generic
MIPS64 Release 2 core (add MIPS64_CPU_MODEL) so the full ISA decodes.
Also give FunctionHook a MIPS64 branch (shares the MIPS R_MIPS_* relocation
type numbers) so it initializes for MIPS64 ELFs.
Add static-ELF tests (big- and little-endian) that load at 0x120000000 and
execute a movz, exercising both fixes. They reference the mips64 rootfs
binaries.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Points examples/rootfs at the rootfs commit that replaces the freestanding
MIPS64 BE/EL hello binaries with real static-glibc ones, so the
test_elf_linux_mips64{eb,el}_static tests exercise full libc startup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
function_hook.py's get_ret_pc/context_fixup/set_ret handled MIPS (o32) but not MIPS64 (n64), so set_api/function interception raised on MIPS64. n64 returns via $ra like o32, so fold MIPS64 into the MIPS branch. Add hello_mips64_linux_customapi.py and hello_mips64el_linux_customapi.py (mirroring hello_mips32_linux_customapi.py) that run the dynamic n64 hello binaries and intercept puts(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Picks up the rootfs commit adding the dynamic n64 hello binaries + loader and libc used by the hello_mips64*_linux_customapi.py examples. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
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 qilingframework#1419) reverted. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
statx() filled a little-endian statx struct regardless of guest endianness, so on a big-endian guest stx_mode was byte-swapped and lost its S_IFDIR bit (fixed in 7550983). Add examples/src/linux/statx.c and a static MIPS64 EB test binary, plus test_elf_linux_mips64eb_statx asserting statx reports "/" as a directory. Without the fix the test sees a byte-swapped mode (NOTDIR). Bumps the rootfs submodule pin to include the mips64_statx binary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implement ql_syscall_clone3 (sched.py) by translating struct clone_args into the legacy clone() argument set and delegating. Modern glibc issues clone3 from pthread_create, so without this thread creation fails on any arch built against a recent toolchain (Qiling does not return -ENOSYS for unimplemented syscalls, so glibc's clone3->clone fallback never fires). Translations: child_stack = stack + stack_size (clone3 gives base+size, legacy clone wants the top), exit_signal folded into the flags' CSIGNAL byte, and an x8664 pre-swap to cancel ql_syscall_clone's arch-specific newtls<->child_tidptr swap. Also fix examples/src/linux/multithreading.c: pthread_join writes a void* (8 bytes on 64-bit), so passing an int* makes the store unaligned and faults on strict-alignment 64-bit targets (MIPS64). Use a void* dest. Add examples/multithreading_mips64_linux.py and a mips64eb regression test mirroring the other arches; rootfs pin bumped for the new binary. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
get_stat64_struct (used by the stat64/fstat64/lstat64/fstatat64 handlers, and by the plain lstat handler which also routes through pack_stat64_struct) had no MIPS64 branch: a 64-bit MIPS guest fell through to the little-endian x86 stat64 struct, byte-swapping and misplacing every field. busybox 'ls -la' on a big-endian MIPS64 (Octeon) rootfs showed garbage perms/size as a result. Handle MIPS64 like get_stat_struct does -- return the 64-bit MIPS stat struct (LinuxMips64Stat / LinuxMips64EBStat by endianness); n64 has no distinct stat64, its layout is the 64-bit stat. Also stop warning 'stat64 on a 64bit system' for MIPS64, where this path is legitimately reached. Add test_elf.ELFTest.test_elf_linux_mips64eb_stat64: drives ql_syscall_stat64 and ql_syscall_lstat on a directory and asserts S_ISDIR reads back correctly under the MIPS64 BE struct (fails on the x86 fallback). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
__getdents_common did not round legacy linux_dirent records up to the alignment of their word-sized d_ino, so on n64 (8-byte d_ino) the next record's d_ino landed unaligned and a strict-alignment guest (MIPS) faulted while walking the buffer -- e.g. older glibc busybox 'ls' (which uses the legacy getdents syscall) crashed on directory listings. Pad each record to the d_ino alignment like the kernel does. Legacy linux_dirent stores d_type in the record's last byte (offset d_reclen-1), so the padding goes between d_name and d_type to keep d_type there; padding without relocating d_type is what got the earlier blanket fix (PR qilingframework#1419) reverted. getdents64 is unchanged (its d_type precedes d_name). Add test_elf.ELFTest.test_elf_linux_mips64eb_getdents: walks a real directory's records and asserts each is 8-byte aligned, tiles the buffer, and keeps d_type (DT_DIR for '.'/'..'). Existing x86/x8664 getdents tests still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
examples/rootfs now provides mips64_linux_buildroot, a dedicated rootfs
carrying the Buildroot toolchain's ld.so.1 + glibc, so a dynamically-linked
non-PIE MIPS64 binary can be brought up by its own dynamic loader with
libc.so.6 mapped and relocated before main(). The default mips64_linux tree
ships a different glibc build whose ld.so cannot load that binary, and both
binaries hard-code interp /lib64/ld.so.1, so the two glibc environments live
in separate trees.
- bump examples/rootfs (mips64_hello_buildroot moved into
mips64_linux_buildroot/, with matching lib/{libc.so.6,ld.so.1} + lib64)
- test_elf.py: test_elf_linux_mips64eb_buildroot_dynamic
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
qiling has QL_ARCH.MIPS64 as a full architecture, but its gdbserver had
no MIPS64 register description, so `ql.debugger = "gdb:..."` failed for
MIPS64 targets. Add generic (endianness-agnostic) support:
- debugger/gdb/xml/mips64/{target,mips64-cpu,mips64-cp0,mips64-fpu}.xml:
64-bit register description mirroring the 32-bit mips/ set (GPRs,
lo/hi/pc, cp0, fpu); architecture mips:isa64r2.
- xmlregs.py: wire QL_ARCH.MIPS64 into the register map (reuses the
shared MIPS unicorn reg_map; register values are read 64-bit wide).
- gdb.py: handle QL_ARCH.MIPS64 in the '?' stop-reply handler (it was a
KeyError) and skip the frame-pointer reg pair as for MIPS. Also make
qXfer:exec-file:read fall back to the host path when the executable was
loaded from outside the rootfs (it previously raised ValueError).
- tests/test_debugger.py: add test_gdbdebug_mips64 (mips64_hello_static).
Verified with gdb-multiarch (set architecture mips:isa64r2; set endian
big): registers, memory, disassembly, single-step, software breakpoints,
continue and backtraces all work against a big-endian MIPS64 guest.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Registers that a target description declares but the backend cannot supply (e.g. MIPS cp0 badvaddr/cause and the fpu regs, which unicorn does not expose) were sent as '<unavailable>' in the g/p replies. Plain gdb tolerates that, but stricter clients abort: Ghidra's ghidragdb raises "value is not available" / "Cannot convert <reg>'s value: '<unavailable>'" when a register in gdb's 'general' group is unavailable, which prevented attaching Ghidra's debugger to a MIPS64 target. Report such registers as zero instead. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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. Verified against a free-running MIPS64 BE guest: \x03 -> S02 in <1ms; the existing gdb-stub regression test still passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds
QL_ARCH.MIPS64so Qiling can emulate MIPS64 Linux user-space in both big-and little-endian, built out from raw shellcode up to full dynamically-linked
glibc ELF binaries. The native backends already support MIPS64 (Unicorn
UC_MODE_MIPS64, Capstone/KeystoneMIPS64); this wires it through the frameworkand fixes the big-endian / n64 correctness issues that only surface once real
glibc binaries run.
What's included
QlArchMIPS64(qiling/arch/mips64.py) — 64-bit arch class reusing theshared MIPS register map; defaults to a MIPS64R2-class CPU model.
a0–a7(the tail four are the physical registers
$8–$11) with no o32 shadow space;n64 syscall table (base 5000) from
asm/unistd_n64.h; MIPS64 routed through theMIPS arch-specific syscall constants.
EM_MIPS+ELFCLASS64detection and n64 doubleword stackalignment; static, PIE, and non-PIE (
ET_EXEC) dynamically-linked big-endianMIPS64 binaries.
order on BE guests; legacy
getdentsandgetdents64record alignment for then64 unaligned walk; MIPS
SO_*socket option values.debugger/gdb/xml/mips64/)and
QL_ARCH.MIPS64wiring in the gdb debugger, soql.debugger = "gdb:..."works for MIPS64 targets (verified with
gdb-multiarch: registers, memory,disassembly, single-step, breakpoints, continue, backtraces). Registers a
target description declares but the backend can't supply are reported as zero
rather than
<unavailable>, so stricter clients (e.g. Ghidra'sghidragdb)can attach. The stub also honours an async interrupt (ctrl-c /
\x03): therun hook polls the client socket during
emu_start, so a free-running guest(an idle/event loop) can be paused from the client rather than only stopping
at a breakpoint or exit.
tree split into its own rootfs, and shellcode / ELF / statx / multithread
regression tests.
Commits
Core arch & n64 ABI
QlArchMIPS64, n64 calling convention +syscall table, ELF machine detection, doubleword stack alignment
Examples & features
examples/shellcode_mips64_run.pyBig-endian / n64 correctness
Rootfs & test binaries
Debugging
QL_ARCH.MIPS64in the gdb debugger)Testing
The MIPS64 paths are covered by the existing suites:
test_shellcode.py(
test_linux_mips64eb/test_linux_mips64el),test_elf.py(MIPS64 EB static,Buildroot dynamic, statx, stat64, getdents byte-order/alignment),
test_elf_multithread.py(clone3 multithreading), andtest_debugger.py(
test_gdbdebug_mips64).Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com