Run all mips32 tests as little-endian and big-endian (#2930)

pull/2934/head
OBarronCS 8 months ago committed by GitHub
parent e02396a1c9
commit 33e699708a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -149,6 +149,7 @@ install_apt() {
gcc-riscv64-linux-gnu \
gcc-arm-linux-gnueabihf \
gcc-mips-linux-gnu \
gcc-mipsel-linux-gnu \
gcc-mips64-linux-gnuabi64
# Some tests require i386 libc/ld, eg: test_smallbins_sizes_32bit_big

@ -35,7 +35,7 @@ def qemu_assembly_run():
sys.stdout.flush()
sys.exit(1)
def _start_binary(asm: str, arch: str, *args):
def _start_binary(asm: str, arch: str, endian: Literal["big", "little"] | None = None):
nonlocal qemu
# Clear the context so setting the .arch will also set .bits
@ -43,6 +43,9 @@ def qemu_assembly_run():
context.clear()
context.arch = arch
if endian is not None:
context.endian = endian
binary_tmp_path = make_elf_from_assembly(asm)
qemu_suffix = pwnlib.qemu.archname()
@ -78,6 +81,7 @@ CROSS_ARCH_LIBC = {
"aarch64": "/usr/aarch64-linux-gnu",
"arm": "/usr/arm-linux-gnueabihf",
"mips": "/usr/mips-linux-gnu",
"mipsel": "/usr/mipsel-linux-gnu",
"mips64": "/usr/mips64-linux-gnuabi64/",
"riscv64": "/usr/riscv64-linux-gnu/",
"loongarch64": "/usr/loongarch64-linux-gnu/",
@ -112,6 +116,8 @@ def qemu_start_binary():
# qemu_libs = pwnlib.qemu.ld_prefix(arch=arch)
qemu_libs = CROSS_ARCH_LIBC.get(qemu_suffix, f"/usr/gnemul/qemu-{qemu_suffix}")
assert os.path.isdir(qemu_libs), f"Cannot find cross-arch libraries at path: {qemu_libs}"
qemu = subprocess.Popen(
[
f"qemu-{qemu_suffix}",

@ -1,6 +1,6 @@
.PHONY: all
ARCHS = aarch64 arm riscv64 mips64 mips32
ARCHS = aarch64 arm riscv64 mips64 mips32 mipsel32
CC.aarch64 = aarch64-linux-gnu-gcc
CC.arm = arm-linux-gnueabihf-gcc
@ -9,6 +9,7 @@ CC.riscv64 = riscv64-linux-gnu-gcc
# CC.riscv32 = riscv64-linux-gnu-gcc
CC.mips64 = mips64-linux-gnuabi64-gcc
CC.mips32 = mips-linux-gnu-gcc
CC.mipsel32 = mipsel-linux-gnu-gcc
# CC.loongarch64 = loongarch64-linux-gnu-gcc
ALL_FLAGS = -g
@ -18,7 +19,8 @@ CFLAGS.arm = $(ALL_FLAGS)
CFLAGS.riscv64 = -march=rv64gc -mabi=lp64d $(ALL_FLAGS)
# CFLAGS.riscv32 = -march=rv32gc $(ALL_FLAGS)
CFLAGS.mips64 = $(ALL_FLAGS)
CFLAGS.mips32 = $(ALL_FLAGS)
CFLAGS.mips32 = $(ALL_FLAGS) # Big-endian MIPS
CFLAGS.mipsel32 = $(ALL_FLAGS) # Little-endian MIPS
# CFLAGS.loongarch64 = $(ALL_FLAGS)

@ -56,3 +56,7 @@ def test_basic_mips64(qemu_start_binary):
def test_basic_mips32(qemu_start_binary):
helper(qemu_start_binary, "basic.mips32.out", "mips", endian="big")
def test_basic_mipsel32(qemu_start_binary):
helper(qemu_start_binary, "basic.mipsel32.out", "mips", endian="little")

@ -1,6 +1,7 @@
from __future__ import annotations
import gdb
import pytest
import pwndbg.color
@ -8,7 +9,15 @@ MIPS_GRACEFUL_EXIT = """
li $v0, 0xfa1
li $a0, 0
syscall
.byte 0xFF, 0xFF
.byte 0xFF, 0xFF
.byte 0xFF, 0xFF
.byte 0xFF, 0xFF
.byte 0xFF, 0xFF
"""
# The .bytes form invalid instructions that don't get disassembled,
# leaving blanks lines in the disasm view
MIPS_DELAY_SLOT = f"""
beq $t1, $t0, _target
@ -23,7 +32,8 @@ end:
"""
def test_mips32_delay_slot(qemu_assembly_run):
@pytest.mark.parametrize("endian", ["big", "little"])
def test_mips32_delay_slot(qemu_assembly_run, endian):
"""
MIPS has delay slots, meaning that when a branch is encountered, they is a "delay" in the branch taking effect.
The next instruction sequentially in memory is always executed, and then the result of the branch is applied.
@ -32,7 +42,7 @@ def test_mips32_delay_slot(qemu_assembly_run):
This test makes sure that looking forwards, we determine branch slots directly, and after moving passed them, they stay intact.
"""
qemu_assembly_run(MIPS_DELAY_SLOT, "mips")
qemu_assembly_run(MIPS_DELAY_SLOT, "mips", endian=endian)
dis = gdb.execute("context disasm", to_string=True)
dis = pwndbg.color.strip(dis)
@ -94,11 +104,12 @@ end:
"""
def test_mips32_bnez_instruction(qemu_assembly_run):
@pytest.mark.parametrize("endian", ["big", "little"])
def test_mips32_bnez_instruction(qemu_assembly_run, endian):
"""
Test that conditional branches work, with and without emulation.
"""
qemu_assembly_run(MIPS_BNEZ, "mips")
qemu_assembly_run(MIPS_BNEZ, "mips", endian=endian)
dis_1 = gdb.execute("context disasm", to_string=True)
dis_1 = pwndbg.color.strip(dis_1)
@ -187,14 +198,15 @@ end:
"""
def test_mips32_call_instruction(qemu_assembly_run):
@pytest.mark.parametrize("endian", ["big", "little"])
def test_mips32_call_instruction(qemu_assembly_run, endian):
"""
Ensure that MIPS "branch-and-link" instructions like "JAL" do not get unrolled, and have splits in disassembly correctly.
There's a bug in Capstone which doesn't consider JAL a jump-like/call instruction, so we have to manually add the jump group.
See: https://github.com/capstone-engine/capstone/issues/2448
"""
qemu_assembly_run(MIPS_CALL, "mips")
qemu_assembly_run(MIPS_CALL, "mips", endian=endian)
dis = gdb.execute("context disasm", to_string=True)
dis = pwndbg.color.strip(dis)
@ -264,13 +276,14 @@ value3: .byte 0
"""
def test_mips32_store_instruction(qemu_assembly_run):
@pytest.mark.parametrize("endian", ["big", "little"])
def test_mips32_store_instruction(qemu_assembly_run, endian):
"""
Ensure all store instructions are annotated correctly.
The assembly is very specific - note the .data section and the size of the variables.
"""
qemu_assembly_run(MIPS_STORE_INSTRUCTIONS, "mips")
qemu_assembly_run(MIPS_STORE_INSTRUCTIONS, "mips", endian=endian)
dis = gdb.execute("context disasm", to_string=True)
dis = pwndbg.color.strip(dis)
@ -321,6 +334,10 @@ loads:
lh $t5, 0($s1)
lb $t6, 0($s2)
.byte 0xFF, 0xFF
.byte 0xFF, 0xFF
.byte 0xFF, 0xFF
.data
value1: .word 0
value2: .half 0
@ -328,7 +345,8 @@ value3: .byte 0
"""
def test_mips32_load_instructions(qemu_assembly_run):
@pytest.mark.parametrize("endian", ["big", "little"])
def test_mips32_load_instructions(qemu_assembly_run, endian):
"""
This test ensures our logic for load instructions - including sign-extension - is working correctly.
@ -336,7 +354,7 @@ def test_mips32_load_instructions(qemu_assembly_run):
The signed reads should signed extend from the read size to 32-bits.
"""
qemu_assembly_run(MIPS_LOAD_INSTRUCTIONS, "mips")
qemu_assembly_run(MIPS_LOAD_INSTRUCTIONS, "mips", endian=endian)
gdb.execute("b loads")
gdb.execute("c")
@ -365,21 +383,24 @@ def test_mips32_load_instructions(qemu_assembly_run):
MIPS_BINARY_OPERATIONS = """
li $t0, 10
li $t1, 20
add $t2, $t0, $t1
sub $t3, $t1, $t0
and $t4, $t0, $t1
or $t5, $t0, $t1
xor $t6, $t0, $t1
sll $t7, $t0, 2
srl $t8, $t1, 2
li $t0, 10
li $t1, 20
add $t2, $t0, $t1
sub $t3, $t1, $t0
and $t4, $t0, $t1
or $t5, $t0, $t1
xor $t6, $t0, $t1
sll $t7, $t0, 2
srl $t8, $t1, 2
sllv $t8, $t1, $t8
srlv $t3, $t8, $t5
"""
def test_mips32_binary_operations(qemu_assembly_run):
qemu_assembly_run(MIPS_BINARY_OPERATIONS, "mips")
@pytest.mark.parametrize("endian", ["big", "little"])
def test_mips32_binary_operations(qemu_assembly_run, endian):
qemu_assembly_run(MIPS_BINARY_OPERATIONS, "mips", endian=endian)
dis = gdb.execute("context disasm", to_string=True)
dis = pwndbg.color.strip(dis)
@ -396,8 +417,8 @@ def test_mips32_binary_operations(qemu_assembly_run):
" 0x10000018 <_start+24> xor $t6, $t0, $t1 T6 => 30 (0xa ^ 0x14)\n"
" 0x1000001c <_start+28> sll $t7, $t0, 2 T7 => 40 (0xa << 0x2)\n"
" 0x10000020 <_start+32> srl $t8, $t1, 2 T8 => 5 (0x14 >> 0x2)\n"
"\n"
"\n"
" 0x10000024 <_start+36> sllv $t8, $t1, $t8 T8 => 0x280 (0x14 << 0x5)\n"
" 0x10000028 <_start+40> srlv $t3, $t8, $t5 T3 => 0 (0x280 >> 0x1e)\n"
"────────────────────────────────────────────────────────────────────────────────\n"
)
@ -426,11 +447,12 @@ end:
"""
def test_mips32_multiple_branches_followed(qemu_assembly_run):
@pytest.mark.parametrize("endian", ["big", "little"])
def test_mips32_multiple_branches_followed(qemu_assembly_run, endian):
"""
Ensure that emulation is setup correctly so as to follow multiple branches - bugs in how we handle delay slots and disable the emulator might break this.
"""
qemu_assembly_run(MIPS_JUMPS, "mips")
qemu_assembly_run(MIPS_JUMPS, "mips", endian=endian)
dis = gdb.execute("context disasm", to_string=True)
dis = pwndbg.color.strip(dis)

@ -117,6 +117,7 @@ def get_tests_list(
if result.returncode == 1:
print(tests_collect_output)
print(result.stderr)
sys.exit(1)
elif collect_only == 1:
print(tests_collect_output)

Loading…
Cancel
Save