{{ message }}
Repository navigation
Add MIPS64 (n64 ABI) Linux support - #1631
Open
retrocpugeek wants to merge 15 commits into
Open
retrocpugeek wants to merge 15 commits into
retrocpugeek wants to merge 15 commits into
Conversation
Contributor
Author
retrocpugeek
marked this pull request as draft
June 21, 2026 11:00
retrocpugeek
marked this pull request as ready for review
September 5, 2026 05:38
Member
|
Hi, Welcome to QIling Framework and thanks for the PR. |
retrocpugeek
force-pushed
the
feature/mips64be
branch
from
September 6, 2026 04:04
b390b7c to
aa0f7f3
Compare
Contributor
Author
|
Rebased onto Rootfs PR is open: qilingframework/rootfs#38. CI here will fail at submodule checkout until that one merges, since the submodule pointer isn't reachable upstream yet. |
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>
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>
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>
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>
retrocpugeek
force-pushed
the
feature/mips64be
branch
from
September 19, 2026 00:15
aa0f7f3 to
d9b9db1
Compare
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

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.
Not mergeable yet — two external dependencies
the
mips64_linux/,mips64el_linux/andmips64_linux_buildroot/trees.Without it the MIPS64
test_elf.pycases fail withQlErrorFileNotFound.still open). MIPS64 needs
UC_TLB_VIRTUALto reach the n64 load address aboveuseg, which exposes two unicorn bugs. On stock unicorn 2.1.3 the tests thatrun a real glibc binary fail; the shellcode,
stat64andgetdentstests pass.Tracked in MIPS64 real-ELF (glibc) support depends on upstream unicorn vtlb fixes (unicorn #2272/#2347, #2345/#2346) #1645.
This PR is ready for review but should not merge until a unicorn release carries
both fixes and the pin in
pyproject.tomlmoves.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).The
test_elf.pyandtest_elf_multithread.pyMIPS64 cases need the binaries fromrootfs#38; the shellcode tests are self-contained and pass without it.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com