japaric / cargo-call-stack

Whole program static stack analysis
Apache License 2.0
579 stars 52 forks source link

assertion with 64-bit division #45

Closed japaric closed 3 years ago

japaric commented 3 years ago

Source code

static X: AtomicUsize = AtomicUsize::new(0);

#[entry]
fn main() -> ! {
    X.store(div64 as usize, Ordering::Relaxed);

    loop {}
}

fn div64(x: u64, y: u64) -> u64 {
    x / y
}

#[exception]
fn SysTick() {
    X.fetch_add(1, Ordering::Relaxed);
}

Error message

2021-09-17T10:55:53Z WARN  cargo_call_stack] no type information for `_ZN17compiler_builtins3int19specialized_div_rem11u64_div_rem17hfa5cc727e5163f40E`
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `8`,
 right: `24`: BUG: LLVM reported that `__aeabi_uldivmod` uses 8 bytes of stack but this doesn't match our analysis', src/main.rs:962:29

Machine code

000004ec <__aeabi_uldivmod>:
 4ec:   b510        push    {r4, lr}
 4ee:   b084        sub sp, #16
 4f0:   ac02        add r4, sp, #8
 4f2:   9400        str r4, [sp, #0]
 4f4:   f000 f925   bl  742 <__udivmoddi4>
 4f8:   9a02        ldr r2, [sp, #8]
 4fa:   9b03        ldr r3, [sp, #12]
 4fc:   b004        add sp, #16
 4fe:   bd10        pop {r4, pc}
jonas-schievink commented 3 years ago

Source:

#[naked]
#[cfg_attr(not(feature = "mangled-names"), no_mangle)]
pub unsafe extern "C" fn __aeabi_uldivmod() {
    asm!(
        "push {{r4, lr}}",
        "sub sp, sp, #16",
        "add r4, sp, #8",
        "str r4, [sp]",
        "bl __udivmoddi4",
        "ldr r2, [sp, #8]",
        "ldr r3, [sp, #12]",
        "add sp, sp, #16",
        "pop {{r4, pc}}",
        options(noreturn)
    );
}
japaric commented 3 years ago

LLVM-IR:

; Function Attrs: naked noinline nounwind
define hidden void @__aeabi_uidivmod() unnamed_addr #1 {
start:
  tail call void asm sideeffect alignstack "push {lr}\0Asub sp, sp, #4\0Amov r2, sp\0Abl __udivmodsi4\0Aldr r1, [sp]\0Aadd sp, sp, #4\0Apop {pc}", "~{cc},~{memory}"() #16, !srcloc !0
  unreachable
}

I know -Zemit-stack-sizes (LLVM's stack-sizes feature) ignores all inline assembly when computing the stack usage of a function -- we already warn about this other places like cortex_m::asm where we assume the inline assembly uses 0 bytes of stack, which is usually the case.

So I guess the issue here is that stack-sizes also ignores the naked attribute so it considers this function to be a normal, empty function that uses a few bytes of stack -- even though no extra machine code is generated around the inline asm! call.

I think in general we should identify the #[naked] + inline asm! pattern in LLVM-IR and assume that LLVM is wrong on those and solely rely on cargo-call-stack's disassembler. To get the LLVM-IR for compiler-builtins we would need to do #46

(It would have been nice if they had written these subroutines with global_asm! instead of #[naked] then we wouldn't be seeing this issue)

jonas-schievink commented 3 years ago

I know -Zemit-stack-sizes (LLVM's stack-sizes feature) ignores all inline assembly when computing the stack usage of a function

Ah, I wouldn't have expected that. I would've thought that the stack size analysis runs on MIR.

The proposed solution sounds good though!