gnzlbg / cargo-asm

cargo subcommand showing the assembly or llvm-ir generated for Rust code
https://github.com/gnzlbg/cargo-asm
Other
1.17k stars 36 forks source link

Some functions fail to produce Rust output or even pick up the right function #121

Open therealprof opened 5 years ago

therealprof commented 5 years ago

I haven't quite figured out what triggers the erratic behaviour but for some functions I'd get something like:

# cargo asm --rust core::ptr::real_drop_in_place
 bx      lr
_ZN8cortex_m10peripheral3dcb43_$LT$impl$u20$cortex_m..peripheral..DCB$GT$12enable_trace17hda6f0a10537b914cE:
 movs    r0, #1
 lsls    r0, r0, #24
 ldr     r1, .LCPI4_0
 ldr     r2, [r1]
 orrs    r2, r0
 str     r2, [r1]
 bx      lr
.LCPI4_0:
_ZN8cortex_m10peripheral3dcb43_$LT$impl$u20$cortex_m..peripheral..DCB$GT$13disable_trace17h420bb4cd508eaa5aE:
 movs    r0, #1
 lsls    r0, r0, #24
 ldr     r1, .LCPI5_0
 ldr     r2, [r1]
 bics    r2, r0
 str     r2, [r1]
 bx      lr
...

or

# cargo asm --rust "<&mut W as core::fmt::Write>::write_char"
 push    {r4, r5, r6, r7, lr}
 sub     sp, #20
 ldr     r0, [r0]
 movs    r6, #0
 str     r6, [sp, #4]
 cmp     r1, #127
 bhi     .LBB1_2
 add     r2, sp, #4
 strb    r1, [r2]
 movs    r4, #1
 b       .LBB1_7
...
.LBB4_8:
 movs    r1, #1
 mov     r0, r1
 add     sp, #20
 pop     {r4, r5, r6, r7, pc}
_ZN20cortex_m_semihosting5debug4exit17h4949d2566b7458ddE:
 push    {r7, lr}
 ldr     r1, .LCPI5_0
 cmp     r0, #0
 bne     .LBB5_2
 adds    r1, r1, #3
.LBB5_2:
 movs    r0, #24
 bl      __syscall
 pop     {r7, pc}
.LCPI5_0:
_ZN20cortex_m_semihosting5debug16report_exception17hef5e5f51f27bb33eE:

In both cases cargo-asm seems to have picked up the incorrect function and to demangle the symbols.

gnzlbg commented 5 years ago

Did Rust mangling scheme change already? I recall there being an RFC about this. Maybe upgrading rustc_demangle fixes this, but I'm not sure.

therealprof commented 5 years ago

No, this is with stable: rustc 1.35.0 (3c235d560 2019-05-20)

There's now support for a new mangling scheme in nightly but it's also not turned on by default yet.

therealprof commented 5 years ago

NB: It works in a lot of of cases and fails in a lot of other cases within the same crate with the same tooling.

e.g.:

# cargo asm --rust core::result::unwrap_failed
 fn unwrap_failed<E: fmt::Debug>(msg: &str, error: E) -> ! {
 sub     sp, #56
 movs    r0, #16
 str     r0, [sp, #8]
 ldr     r0, .LCPI5_0
 str     r0, [sp, #4]
 panic!("{}: {:?}", msg, error)
 ldr     r0, .LCPI5_1
 str     r0, [sp, #48]
 add     r0, sp, #52
 str     r0, [sp, #44]
 ldr     r0, .LCPI5_2
 str     r0, [sp, #40]
 add     r0, sp, #4
 str     r0, [sp, #36]
 movs    r0, #2
     Arguments { (libcore/fmt/mod.rs:316)
     str     r0, [sp, #32]
     add     r1, sp, #36
     str     r1, [sp, #28]
     movs    r1, #0
     str     r1, [sp, #24]
     str     r1, [sp, #20]
     str     r0, [sp, #16]
     ldr     r0, .LCPI5_3
     str     r0, [sp, #12]
     add     r0, sp, #12
     $crate::panicking::panic_fmt(format_args!($fmt, $($arg)*), (libcore/macros.rs:18)
     ldr     r1, .LCPI5_4
     bl      _ZN4core9panicking9panic_fmt17h0d6d5c8b201e3246E
.LCPI5_0:
.LCPI5_1:
.LCPI5_2:
.LCPI5_3:
.LCPI5_4:
gnzlbg commented 5 years ago

You might want to inspect the real assembly generated by rustc (and not the one reported by cargo asm), and see if something is fishy (I think rustc's assembly output is a "best effort").

Right now it is not possible to pass cargo asm an assembly text file, and ask it to find some function in that file, but it shouldn't be hard to extend cargo asm with a --from-file=foo.asm option. That could allow better testing the tool, by providing assembly snippets via the CLI, and verifying the option. The tests we currently have are end-to-end integration tests, and because the toolchain changes all the time, they are quite brittle.

therealprof commented 5 years ago

Ooh. I think we have something here... There're three core::ptr::real_drop_in_place functions in the final linked binary:

080017b8 <core::ptr::real_drop_in_place>:
 80017b8:       4770            bx      lr
 80017ba:       d4d4            bmi.n   8001766 <<cdc_acm::SerialPort<B> as usb_device::class::UsbClass<B>>::get_configuration_descriptors+0x13a>
0800339a <core::ptr::real_drop_in_place>:
 800339a:       4770            bx      lr
08003fee <core::ptr::real_drop_in_place>:
 8003fee:       4770            bx      lr

The write_char function from above looks completely different, though:

08003ff0 <<&mut W as core::fmt::Write>::write_char>:
 8003ff0:       b510            push    {r4, lr}
 8003ff2:       b082            sub     sp, #8
 8003ff4:       6800            ldr     r0, [r0, #0]
 8003ff6:       2200            movs    r2, #0
 8003ff8:       9201            str     r2, [sp, #4]
 8003ffa:       297f            cmp     r1, #127        ; 0x7f
 8003ffc:       d807            bhi.n   800400e <<&mut W as core::fmt::Write>::write_char+0x1e>
 8003ffe:       aa01            add     r2, sp, #4
 8004000:       7011            strb    r1, [r2, #0]
 8004002:       2201            movs    r2, #1
 8004004:       a901            add     r1, sp, #4
 8004006:       f7ff ffa9       bl      8003f5c <<cortex_m_semihosting::hio::HStderr as core::fmt::Write>::write_str>
 800400a:       b002            add     sp, #8
 800400c:       bd10            pop     {r4, pc}
 800400e:       0aca            lsrs    r2, r1, #11
 8004010:       d10e            bne.n   8004030 <<&mut W as core::fmt::Write>::write_char+0x40>
...

Funny enough I've a few slightly differing unwrap_failed functions, too but they seem close enough to the cargo-asm output.

gnzlbg commented 5 years ago

FWIW, cargo-asm doesn't really inspect the final binary. rustc generates LLVM-IR, which is then optimized, but we tell LLVM that instead of generating a binary from that LLVM-IR, they should translate it to "textual assembly" instead. I've always been a bit skeptic about how good that "textual assembly" matches the machine code that is actually generated.

The reason we do it this way is that LLVM actually has support for embedding comments in the textual assembly generated, and we wanted to show those too, but rustc doesn't really support that very good right now. The alternative here would be to compile the binary normally, and then disassemble it, and try to look for the function symbols there. Sometimes I wish I would have gone this way.

therealprof commented 5 years ago

Not so sure disassembling the final binary is a good approach. Even with "debug info" in the final release binary the information level is really lacking which is one of the reasons I care so much about cargo-asm. 😅

gnzlbg commented 5 years ago

Yeah, i'm not sure either. More than about the quality of the assembly displayed, I thought it might be an interesting approach to explore to produce something more faithful to what's executed, and also for speed. Compiling and linking a binary, and then doing a disassembly, is for whatever reason much faster than dumping the textual assembly through LLVM.

AaronKutch commented 5 years ago

This issue is constantly getting in my way. I just encountered a function in a library that I cannot seem to get the assembly for, even when wrapping it in a inline(never) function in a binary. It either gives me code from the main function or does not find it at all. edit: I remembered to add pub to the function and added inline(always) directly on the function in the library, but trying to chain functions and permutating inline(never) and inline(always) is still giving me the wrong function output

gnzlbg commented 5 years ago

even when wrapping it in a inline(never) function in a binary.

I suppose you are then searching for that inline(never) function, that this function is pub, and that this function is not generic, right?

AaronKutch commented 5 years ago

I managed to get a reproduction for the stable channel. I am trying to get the assembly from my specialized-div-rem crate of the u128_div_rem function. It has #[inline] applied to it because only one element of the tuple it returns is used sometimes, so I want to be able to see what the inlining does. Sometimes though, the function inlines further than I want, or cargo asm is acting up and showing the wrong functions even if no inlining is occuring. Usually, I am able to hack around problems like this without having the clone the library and modify it, by doing this: in Cargo.toml,

...
[dependencies]
specialized-div-rem = "0.0.4"
rand = "0.6.5"

in main.rs,

#[inline(never)]
pub fn fun(lkj0: u128, lkj1: u128) -> (u128, u128) {
    specialized_div_rem::u128_div_rem(lkj0, lkj1)
}

fn main() {
    let lkj0 = 7325176125387951268757865215152512u128;
    let lkj1 = 112879451768156781546789154876156715u128;
    dbg!(fun(lkj0, lkj1));
}

and running cargo asm [name of binary]::fun. However, it results in assembly from the wrong function. I can fix it by cloning the library and changing the function to use inline(never) on the library side, but this is far from convenient or practically impossible if I need to do it when using something that binds to a C++ library. Also, I should mention that I have never once seen --rust do anything in all my time with cargo asm.

Another problem I have is that cargo asm [incomplete path] sometimes does not show a helpful list of potential functions. The master branch of the apint crate currently has impls spread across several modules, so I need to run stuff like cargo asm "apint::apint::data::morestuff::<impl bunch of nontrivial mangling stuff that I cannot figure out without cargo asm mentioning it>::wrapping_add_assign". On some libraries like my specialized-div-rem library, cargo asm never shows the helpful list, so I may be unable to actually find the function. If it is possible, I would recommend that functions are addressed just how they would be addressed in Rust code, e.g. cargo asm apint::ApInt::wrapping_add_assign.

AaronKutch commented 5 years ago

It seems to me that cargo-asm is something that belongs in the nightly Rust compiler itself. I understand that cargo-asm relying on optimized compiler output is always going to need fragile hacks if it were able to be "ergonomic". Either way, I suppose we are all waiting on stabilized mangling and internal compiler refactors.