Search code examples
rustmipsinline-assembly

Removing appended "break" instruction in inline mips assembly


I have the following (simplified) function using inline assembly, targeting mips:

#[naked]
pub unsafe extern "C" fn test() {
    asm!(
        ".set noreorder",
        "jr $ra",
        "li $v0, 0x123",
        options(noreturn),
    )
}

I expected this to compile into just the 2 specified instructions (in release mode), as it's a naked function, but a break instruction gets appended at the end:

00000000 <test>:
   0:   03e00008        jr      ra
   4:   24020123        li      v0,291
   8:   0000000d        break

I assume this is a countermeasure against undefined behavior by either rustc or llvm, but I need to product the exact assembly I specify in the function.

Is there any way to prevent either rustc, llvm or the assembler from generating this extra instruction generally?

I tested it on existing targets such as mipsel-unknown-none and it also produced a break instruction, but I am compiling on the following custom target, if it matters:

{
    "arch": "mips",
    "cpu": "mips1",
    "data-layout": "e-m:m-p:32:32-i8:8:32-i16:16:32-i32:32-n32-S32",
    "emit-debug-gdb-scripts": false,
    "executables": false,
    "features": "+mips32,+soft-float,+noabicalls",
    "linker": "rust-lld",
    "linker-flavor": "ld.lld",
    "llvm-target": "mipsel-unknown-linux-gnu",
    "relocation-model": "static",
    "target-pointer-width": "32",
    "panic-strategy": "abort",
    "singlethread": true,
    "dynamic-linking": false,
    "function-sections": true
}

I'm also using a #![no_std] and #![no_core] staticlib crate with the required lang items implemented and simply compiling using cargo build --release --target=my-target.json

Edit: After Peter Cordes's suggestion, I tried the same in C with

__attribute__((naked)) void test() {
    __asm__(
        ".set noreorder\n"
        "jr $ra\n"
        "li $v0, 0x123\n"
    );
}

Compiled using

clang -O3 test.c -c -o test.o -target mips-unknown-none

And the result is

00000000 <test>:
   0:   03e00008    jr  ra
   4:   24020123    li  v0,291

Without a break, so it seems it was included by the rust compiler.


Solution

  • Yes! Do one of:

    • Add "trap_unreachable": false to your target.json
    • Build with RUSTFLAGS=-Ztrap-unreachable=no. (nightly-only though)

    Unfortunately it's not very well documented. Further reading: PR where the trap instruction generation was added PR where trap-unreachable=no was added