Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
cb8793c
Add MIPS64 (n64 ABI) Linux support
retrocpugeek Jun 21, 2026
fd5d9aa
Add examples/shellcode_mips64_run.py
retrocpugeek Jun 21, 2026
a55a294
Route MIPS64 through the MIPS arch-specific syscall constants
retrocpugeek Jun 21, 2026
fa5724c
Support loading MIPS64 ELF binaries
retrocpugeek Jun 21, 2026
2a13498
Bump rootfs submodule to MIPS64 static glibc hello binaries
retrocpugeek Jun 23, 2026
7ad4212
Add MIPS64 customapi examples and function-hook support
retrocpugeek Jun 23, 2026
08d8744
Bump rootfs submodule for MIPS64 dynamic hello binaries
retrocpugeek Jun 23, 2026
7550983
Fix statx byte order on big-endian guests
retrocpugeek Jun 24, 2026
64b8f37
Align getdents64 records to fix unaligned access on MIPS
retrocpugeek Jun 24, 2026
f429846
Add MIPS64 EB statx regression test
retrocpugeek Jun 24, 2026
83fa538
Add clone3 syscall and MIPS64 multithreading example/test
retrocpugeek Jun 25, 2026
37c8b33
Wire up stat64 for MIPS64
retrocpugeek Jun 25, 2026
f1c3890
Align legacy getdents records (fixes n64 unaligned walk)
retrocpugeek Jun 25, 2026
47b9b89
Fix MIPS socket option (SO_*) values
retrocpugeek Jun 25, 2026
8fdae17
tests: add MIPS64 BE Buildroot dynamic-load regression + rootfs split
retrocpugeek Jul 12, 2026
854a8ff
Add MIPS64 gdb-stub support
retrocpugeek Jul 12, 2026
3f02ebe
gdb: report 0 for unavailable registers so strict clients can attach
retrocpugeek Jul 12, 2026
b390b7c
gdb: support async interrupt (ctrl-c) to break into a running guest
retrocpugeek Jul 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions examples/hello_mips64_linux_customapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

# NOTE: this runs a dynamically-linked glibc MIPS64 (n64) binary, which links
# above the 2GB useg and uses ll/sc locks. It requires the MIPS64 virtual-TLB
# fixes in unicorn (https://github.com/unicorn-engine/unicorn issues #2272 /
# the ll-AdEL fix); on an unfixed unicorn it raises a spurious RI/AdEL.

import sys
sys.path.append("..")

from qiling import Qiling
from qiling.os.const import STRING
from qiling.const import QL_VERBOSE

def my_puts(ql: Qiling):
params = ql.os.resolve_fcall_params({'s': STRING})

print(f'puts("{params["s"]}")')

if __name__ == "__main__":
ql = Qiling(["rootfs/mips64_linux/bin/mips64_hello"], "rootfs/mips64_linux", verbose=QL_VERBOSE.DEBUG)
ql.os.set_api("puts", my_puts)
ql.run()
26 changes: 26 additions & 0 deletions examples/hello_mips64el_linux_customapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

# NOTE: this runs a dynamically-linked glibc MIPS64 (n64, little-endian) binary,
# which links above the 2GB useg and uses ll/sc locks. It requires the MIPS64
# virtual-TLB fixes in unicorn (https://github.com/unicorn-engine/unicorn issues
# #2272 / the ll-AdEL fix); on an unfixed unicorn it raises a spurious RI/AdEL.

import sys
sys.path.append("..")

from qiling import Qiling
from qiling.os.const import STRING
from qiling.const import QL_VERBOSE

def my_puts(ql: Qiling):
params = ql.os.resolve_fcall_params({'s': STRING})

print(f'puts("{params["s"]}")')

if __name__ == "__main__":
ql = Qiling(["rootfs/mips64el_linux/bin/mips64el_hello"], "rootfs/mips64el_linux", verbose=QL_VERBOSE.DEBUG)
ql.os.set_api("puts", my_puts)
ql.run()
17 changes: 17 additions & 0 deletions examples/multithreading_mips64_linux.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

import sys
sys.path.append("..")

from qiling import Qiling
from qiling.const import QL_VERBOSE

def my_sandbox(path, rootfs):
ql = Qiling(path, rootfs, verbose=QL_VERBOSE.DEBUG, multithread=True)
ql.run()

if __name__ == "__main__":
my_sandbox(["rootfs/mips64_linux/bin/mips64_multithreading"], "rootfs/mips64_linux")
53 changes: 53 additions & 0 deletions examples/shellcode_mips64_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

import sys

sys.path.append("..")
from qiling import Qiling
from qiling.const import QL_ARCH, QL_OS, QL_ENDIAN, QL_VERBOSE

# MIPS64 n64 shellcode: write(1, "MIPS64 hello\n", 13) then exit_group(0).
#
# it uses the n64 syscall numbers (write=5001, exit_group=5205) and computes the
# address of the message with a bal/daddiu pc-relative trick. assembled with
# binutils mips64-linux-gnuabi64-as:
#
# .set noreorder
# __start:
# li $v0, 5001 # __NR_write
# li $a0, 1 # fd = stdout
# bal load
# nop
# load:
# daddiu $a1, $ra, (msg - load) # a1 = &msg
# li $a2, 13 # len
# syscall
# li $v0, 5205 # __NR_exit_group
# li $a0, 0
# syscall
# msg:
# .ascii "MIPS64 hello\n"
MIPS64EB_LIN = bytes.fromhex('''
2402138924040001041100010000000067e500182406000d0000000c24021455
240400000000000c4d49505336342068656c6c6f0a
''')

# little-endian counterpart of MIPS64EB_LIN: the instruction words are
# byte-swapped while the trailing string is left as-is
MIPS64EL_LIN = bytes.fromhex('''
891302240100042401001104000000001800e5670d0006240c00000055140224
000004240c0000004d49505336342068656c6c6f0a
''')


if __name__ == "__main__":
print("\nLinux MIPS 64bit EB (big-endian) Shellcode")
ql = Qiling(code=MIPS64EB_LIN, archtype=QL_ARCH.MIPS64, ostype=QL_OS.LINUX, endian=QL_ENDIAN.EB, verbose=QL_VERBOSE.DEFAULT)
ql.run()

print("\nLinux MIPS 64bit EL (little-endian) Shellcode")
ql = Qiling(code=MIPS64EL_LIN, archtype=QL_ARCH.MIPS64, ostype=QL_OS.LINUX, endian=QL_ENDIAN.EL, verbose=QL_VERBOSE.DEFAULT)
ql.run()
11 changes: 7 additions & 4 deletions examples/src/linux/multithreading.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ void thread_2(void) {
int main(void) {
pthread_t id_1, id_2;
int ret;
/* pthread_join writes a void* (8 bytes on 64-bit); using an int* here makes
the store unaligned on strict-alignment 64-bit targets (e.g. MIPS64) */
void *retval;

/*Create pthread 1*/
ret=pthread_create(&id_1, NULL, (void *) thread_1, NULL);
Expand All @@ -44,9 +47,9 @@ int main(void) {
}

/*wait thread ending*/
pthread_join(id_1, &ret);
printf("thread 1 ret val is : %d\n", ret);
pthread_join(id_2, &ret);
printf("thread 2 ret val is : %d\n", ret);
pthread_join(id_1, &retval);
printf("thread 1 ret val is : %d\n", (int)(long)retval);
pthread_join(id_2, &retval);
printf("thread 2 ret val is : %d\n", (int)(long)retval);
return 0;
}
36 changes: 36 additions & 0 deletions examples/src/linux/statx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Regression test for statx struct serialization (esp. byte order on
// big-endian guests). statx() fills a `struct statx` whose stx_mode must carry
// the S_IFDIR bit for a directory; if the struct is emitted with the wrong
// endianness the type bits are lost and the directory looks like a plain file.
//
// Build (big-endian ARM, static so it needs no rootfs loader):
// armeb-linux-gnueabi-gcc -O2 -static -o armeb_statx statx.c
//
// Prints "DIR" when statx correctly reports the path as a directory.
#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[])
{
const char *path = (argc > 1) ? argv[1] : "/";
struct statx stx;

memset(&stx, 0, sizeof(stx));

if (syscall(SYS_statx, AT_FDCWD, path, 0, STATX_BASIC_STATS, &stx) != 0) {
perror("statx");
return 1;
}

if (S_ISDIR(stx.stx_mode))
printf("DIR\n");
else
printf("NOTDIR mode=%o\n", (unsigned) stx.stx_mode);

return 0;
}
108 changes: 108 additions & 0 deletions qiling/arch/mips64.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env python3
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

from functools import cached_property
from typing import Optional

from unicorn import (
Uc, UC_ARCH_MIPS, UC_MODE_MIPS64, UC_MODE_BIG_ENDIAN, UC_MODE_LITTLE_ENDIAN,
UC_HOOK_TLB_FILL, UC_TLB_VIRTUAL, UC_PROT_ALL
)
from capstone import Cs, CS_ARCH_MIPS, CS_MODE_MIPS64, CS_MODE_BIG_ENDIAN, CS_MODE_LITTLE_ENDIAN
from keystone import Ks, KS_ARCH_MIPS, KS_MODE_MIPS64, KS_MODE_BIG_ENDIAN, KS_MODE_LITTLE_ENDIAN

from qiling import Qiling
from qiling.arch.arch import QlArch
from qiling.arch import mips_const
from qiling.arch.models import MIPS64_CPU_MODEL
from qiling.arch.register import QlRegisterManager
from qiling.const import QL_ARCH, QL_ENDIAN


class QlArchMIPS64(QlArch):
type = QL_ARCH.MIPS64
bits = 64

def __init__(self, ql: Qiling, *, cputype: Optional[MIPS64_CPU_MODEL], endian: QL_ENDIAN):
# unicorn's default MIPS64 core is an old MIPS III (R4000-class) that lacks
# MIPS IV / MIPS64 instructions such as movn/movz, which glibc emits freely;
# executing them traps as a reserved instruction. default to a generic
# MIPS64 Release 2 core (what mips64/mips64r2 toolchains target) so the full
# ISA is available.
if cputype is None:
cputype = MIPS64_CPU_MODEL.MIPS64_MIPS64R2_GENERIC

super().__init__(ql, cputype=cputype)

self._init_endian = endian

@cached_property
def uc(self) -> Uc:
endian = {
QL_ENDIAN.EB: UC_MODE_BIG_ENDIAN,
QL_ENDIAN.EL: UC_MODE_LITTLE_ENDIAN
}[self.endian]

uc = Uc(UC_ARCH_MIPS, UC_MODE_MIPS64 + endian)

if self.cpu is not None:
uc.ctl_set_cpu_model(self.cpu.value)

# unicorn's MIPS64 CPU-MMU only executes within the low 2GB (useg); any
# access at or above 0x80000000 raises an address exception. MIPS64 ELFs
# link at 0x120000000 and Qiling lays the stack/mmap out well above 4GB,
# so we switch to unicorn's virtual-TLB mode and supply an identity
# mapping (paddr == vaddr). that bypasses the CPU-MMU segment checks while
# keeping virtual == physical, so the rest of Qiling's flat memory model
# (mem.map/read/write, loader, syscall pointer reads) keeps working as-is.
uc.ctl_set_tlb_mode(UC_TLB_VIRTUAL)
uc.hook_add(UC_HOOK_TLB_FILL, self.__tlb_fill_identity)

return uc

@staticmethod
def __tlb_fill_identity(uc: Uc, vaddr: int, access: int, entry, user_data) -> bool:
# identity-map every page to itself (paddr == vaddr); accesses to pages
# that are not actually backed still fault, since there is no physical
# memory behind them
entry.paddr = vaddr & ~0xfff
entry.perms = UC_PROT_ALL

return True

@cached_property
def regs(self) -> QlRegisterManager:
# the register names are shared with MIPS32; unicorn widens them to
# 64 bits under UC_MODE_MIPS64
regs_map = dict(
**mips_const.reg_map
)

pc_reg = 'pc'
sp_reg = 'sp'

return QlRegisterManager(self.uc, regs_map, pc_reg, sp_reg)

@cached_property
def disassembler(self) -> Cs:
endian = {
QL_ENDIAN.EL: CS_MODE_LITTLE_ENDIAN,
QL_ENDIAN.EB: CS_MODE_BIG_ENDIAN
}[self.endian]

return Cs(CS_ARCH_MIPS, CS_MODE_MIPS64 + endian)

@cached_property
def assembler(self) -> Ks:
endian = {
QL_ENDIAN.EL: KS_MODE_LITTLE_ENDIAN,
QL_ENDIAN.EB: KS_MODE_BIG_ENDIAN
}[self.endian]

return Ks(KS_ARCH_MIPS, KS_MODE_MIPS64 + endian)

@property
def endian(self) -> QL_ENDIAN:
return self._init_endian
18 changes: 18 additions & 0 deletions qiling/arch/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,22 @@ class MIPS_CPU_MODEL(Enum):
MIPS32_I7200 = 15


class MIPS64_CPU_MODEL(Enum):
MIPS64_R4000 = 0
MIPS64_VR5432 = 1
MIPS64_5KC = 2
MIPS64_5KF = 3
MIPS64_20KC = 4
MIPS64_MIPS64R2_GENERIC = 5
MIPS64_5KEC = 6
MIPS64_5KEF = 7
MIPS64_I6400 = 8
MIPS64_I6500 = 9
MIPS64_LOONGSON_2E = 10
MIPS64_LOONGSON_2F = 11
MIPS64_MIPS64DSPR2 = 12


class PPC_CPU_MODEL(Enum):
PPC32_401 = 0
PPC32_401A1 = 1
Expand Down Expand Up @@ -429,6 +445,7 @@ class RISCV64_CPU_MODEL(Enum):
ARM_CPU_MODEL,
ARM64_CPU_MODEL,
MIPS_CPU_MODEL,
MIPS64_CPU_MODEL,
PPC_CPU_MODEL,
RISCV_CPU_MODEL,
RISCV64_CPU_MODEL
Expand All @@ -440,6 +457,7 @@ class RISCV64_CPU_MODEL(Enum):
'ARM_CPU_MODEL',
'ARM64_CPU_MODEL',
'MIPS_CPU_MODEL',
'MIPS64_CPU_MODEL',
'PPC_CPU_MODEL',
'RISCV_CPU_MODEL',
'RISCV64_CPU_MODEL',
Expand Down
3 changes: 2 additions & 1 deletion qiling/arch/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from functools import lru_cache

from keystone import (Ks, KS_ARCH_ARM, KS_ARCH_ARM64, KS_ARCH_MIPS, KS_ARCH_X86, KS_ARCH_PPC,
KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_MIPS32, KS_MODE_PPC32, KS_MODE_16, KS_MODE_32, KS_MODE_64,
KS_MODE_ARM, KS_MODE_THUMB, KS_MODE_MIPS32, KS_MODE_MIPS64, KS_MODE_PPC32, KS_MODE_16, KS_MODE_32, KS_MODE_64,
KS_MODE_LITTLE_ENDIAN, KS_MODE_BIG_ENDIAN)

from qiling import Qiling
Expand Down Expand Up @@ -126,6 +126,7 @@ def assembler(arch: QL_ARCH, endianness: QL_ENDIAN, is_thumb: bool) -> Ks:
QL_ARCH.ARM: (KS_ARCH_ARM, KS_MODE_ARM + endian + thumb),
QL_ARCH.ARM64: (KS_ARCH_ARM64, KS_MODE_ARM),
QL_ARCH.MIPS: (KS_ARCH_MIPS, KS_MODE_MIPS32 + endian),
QL_ARCH.MIPS64: (KS_ARCH_MIPS, KS_MODE_MIPS64 + endian),
QL_ARCH.A8086: (KS_ARCH_X86, KS_MODE_16),
QL_ARCH.X86: (KS_ARCH_X86, KS_MODE_32),
QL_ARCH.X8664: (KS_ARCH_X86, KS_MODE_64),
Expand Down
16 changes: 15 additions & 1 deletion qiling/cc/mips.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
#
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework

from unicorn.mips_const import UC_MIPS_REG_V0, UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3
from unicorn.mips_const import (
UC_MIPS_REG_V0, UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3,
UC_MIPS_REG_T0, UC_MIPS_REG_T1, UC_MIPS_REG_T2, UC_MIPS_REG_T3
)

from qiling.cc import QlCommonBaseCC, make_arg_list

Expand All @@ -25,3 +28,14 @@ def getNumSlots(argbits: int):
def unwind(self, nslots: int) -> int:
# TODO: stack frame unwiding?
return self.arch.regs.ra


class mips64n64(mipso32):
# n64 passes the first 8 arguments in registers: a0-a3 followed by a4-a7,
# which are the physical registers $8-$11 (unicorn names them t0-t3). unlike
# o32, n64 reserves no shadow space on the stack.
_argregs = make_arg_list(
UC_MIPS_REG_A0, UC_MIPS_REG_A1, UC_MIPS_REG_A2, UC_MIPS_REG_A3,
UC_MIPS_REG_T0, UC_MIPS_REG_T1, UC_MIPS_REG_T2, UC_MIPS_REG_T3
)
_shadow = 0
1 change: 1 addition & 0 deletions qiling/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class QL_ARCH(IntEnum):
RISCV = 110
RISCV64 = 111
PPC = 112
MIPS64 = 113


class QL_OS(IntEnum):
Expand Down
Loading