japaric / cargo-call-stack

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

Leverage `!callees` metadata #15

Open japaric opened 5 years ago

japaric commented 5 years ago

This program:

static X: AtomicBool = AtomicBool::new(false);

#[entry]
#[inline(never)]
fn main() -> ! {
    let mut x: fn() -> u32 = foo;

    if X.load(Ordering::Acquire) {
        x = bar;
    }

    x();
    baz();

    loop {}
}

fn foo() -> u32 {
    unsafe { asm!("" : : "r"(0) "r"(1) "r"(2) "r"(3) "r"(4) "r"(5)) }

    0
}

fn bar() -> u32 {
    unsafe { asm!("" : : "r"(0) "r"(1) "r"(2) "r"(3) "r"(4) "r"(5) "r"(6) "r"(7)) }

    1
}

#[inline(never)]
fn baz() -> u32 {
    // NOTE(asm!) side effect to preserve function calls to this method
    unsafe { asm!("NOP" : : : : "volatile") }

    2
}

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

Produces the following LLVM IR:

; Function Attrs: noinline noreturn nounwind
define void @main() unnamed_addr #2 !dbg !107 {
  ; ..

; `x();`
  %2 = tail call i32 %spec.select() #8, !dbg !138, !callees !139

; call app::baz
  tail call fastcc void @_ZN3app3baz17h9f37979edaee3ecdE(), !dbg !140

  ; ..
}

!139 = !{i32 ()* @_ZN3app3bar17hc173e5a24b32a7e4E, i32 ()* @_ZN3app3foo17h0c16cfbad983ef03E}

The function pointer call x() shows up as an indirect function call but the list of possible callees (foo and bar) are listed in metadata !callees !139.

We can use this information to get the exact list of candidates for some function pointer calls -- right now we are using the LLVM types to build the list of candidates but these can result in many false positives.