Search code examples
x86rustwrapperabi

Why are there extra ASM instructions in a naked Rust function?


I am wrapping a low level ABI in Rust, taking advantage of the naked function feature. Here is my code and relevant disassembly

#![feature(asm)]
#![feature(naked_functions)]

struct MyStruct {
    someVar: i64, // not important
                  // ...
}

impl MyStruct {
    #[naked]
    extern "C" fn wrap(&self) {
        unsafe {
            asm!("NOP" :::: "volatile");
            // not sure if the volatile option is needed, but I
            // figured it wouldn't hurt
        }
    }
}

Disassembled with LLDB:

ABIWrap`ABIWrap::{{impl}}::wrap:
  * 0x100001310 <+0>:  movq   %rdi, -0x10(%rbp)
  * 0x100001314 <+4>:  movq   %rsi, -0x8(%rbp)
  * 0x100001318 <+8>:  movq   -0x10(%rbp), %rax
  * 0x10000131c <+12>: movq   -0x8(%rbp), %rcx
  * 0x100001320 <+16>: movq   %rax, -0x20(%rbp)
  * 0x100001324 <+20>: movq   %rcx, -0x18(%rbp)
    0x100001328 <+24>: nop    
    0x100001329 <+25>: retq   
    0x10000132a <+26>: nopw   (%rax,%rax)

The 6 lines preceding the NOP (I've marked with *) are what I am confused by. Shouldn't the naked directive leave, for lack of a better term, a naked function?

I am attempting to allow the arguments to just pass through this function to the ABI as it follows roughly the same calling convention as Rust, I just need to swap one or two of the registers, hence the inline assembly.

Is there a way to get rid of these 6 preceding instructions? I am calling against the ABI a lot and the previous way I was calling against it was causing a decent amount of overhead. I want to make sure that the registers containing any important values aren't overwritten.

Side note: is the "volatile" option needed? I wasn't sure but added it anyways.


Solution

  • After tinkering with this more, (and figuring how to effectively disassemble my release build), I found that the extra instructions are only added during the debug build (or at least when -O0).

    When compiling the code with -O2, I found that all of the assembly was inlined, but that is easily fixed with an #[inline(never)] directive. Now the arguments are being passed correctly without the extra instructions ruining my registers :)

    Now I just need to have the code only run -O2 on just these functions and not the rest of the debug build...