How to prevent rust from removing a dead function?

Hey,

I have a dprint function, that basically only does println!("{:?}", self). The reason for it is, that one can't println!() in GDB, so, I want to be able to at least be able to do my_struct.dprint() (for debug print). However, Rust evidently, admittedly correctly, sees that dprint() is never used in the binary and keeps removnig it, how can I stop that?

I have lto="off", debug=true, opt-level=0 and it is still getting removed.

Thanks!

It’s better to think of it as “not added” rather than “removed”. Machine code is generated when it is known to be needed, so you need to give rustc a reason to include it.

As far as I know, the most practical way to do this is to add the #[unsafe(no_mangle)] attribute, which in addition to its effect on symbol mangling, also makes the symbol public (even in a binary) and thus makes it required to exist. I’m not aware of a way to force a function to be included without any other effects on it, unfortunately.

4 Likes

You can try std::hint::black_box(YourType::dprint);

1 Like

Hmm, my brain immediately went to https://doc.rust-lang.org/reference/abi.html#the-used-attribute, but I guess that's only for statics.

Probably since you want to be able to name it in gdb easily anyway, the no_mangle is a practical choice.

no_mangle is what Compiler Explorer uses for this purpose, at least

I take that back: simply using black_box keeps the functions but they are not exported under their right names.

core::ops::function::Fn::call:
	leaq	.Lanon.6d7f108380601a48ae9504961e4edc6a.0(%rip), %rdi
	movl	$15, %esi
	jmpq	*std::io::stdio::_print@GOTPCREL(%rip)

core::ops::function::Fn::call:
	movq	$-7, %rax
	retq

core::ops::function::FnMut::call_mut:
	movq	$-7, %rax
	retq

core::ops::function::FnMut::call_mut:
	leaq	.Lanon.6d7f108380601a48ae9504961e4edc6a.0(%rip), %rdi
	movl	$15, %esi
	jmpq	*std::io::stdio::_print@GOTPCREL(%rip)

core::ops::function::FnOnce::call_once{{vtable.shim}}:
	movq	$-7, %rax
	retq

core::ops::function::FnOnce::call_once{{vtable.shim}}:
	leaq	.Lanon.6d7f108380601a48ae9504961e4edc6a.0(%rip), %rdi
	movl	$15, %esi
	jmpq	*std::io::stdio::_print@GOTPCREL(%rip)


.Lanon.6d7f108380601a48ae9504961e4edc6a.0:
	.ascii	"Quack!\n"
1 Like

The same dprint() is on multiple unrelated structs, thus I think #[unsafe(no_mangle)] on all of them would cause a name conflict no?

I didn't know about that, thanks, I'll try it!

Well, rust-gdb does some tricks, maybe it will be able to find my_struct.dprint() even though the name is mangled

To be clear, one can call my_struct.dprint() in GDB when it is in the binary, no demangling needed. (I just put it in the first line in main, but I would prefer not to do this for all my structs)

You can instead use the export_name attribute to give them different names: it should work the same other than not inferring the name, though those docs imply you might also want the used attribute?

I vaguely recall creating a const-pointer to the thing to force the linker’s hand. Unfortunately, that was more than a year ago so I don’t recall the details.

Have you tried defining it as a normal function with runtime polymorphism?

#[unsafe(no_mangle)]
fn dprint(thing: &dyn Debug) {
    println!("{thing:?}");
}

You would probably need to call it like dprint(&my_struct), but maybe this is the simplest choice.

I would be extremely surprised if gdb could automatically create a fat pointer for you.

Great idea, this worked!!

I wrapped it in a macro, I do not know if it is safe.

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};

#[proc_macro_derive(GDBPrinter)]
pub fn debug_printer_derive(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;

    let pointer_name = syn::Ident::new(
    &format!("__{}_gdbprint_keep", name),
        name.span(),
    );

    let expanded = quote! {
        impl #name {
            #[inline(never)]
            pub fn gdbprint(&self) {
                println!("{:?}", self);
            }
        }

        #[used]
        pub static #pointer_name: fn(& #name) = #name::gdbprint;
    };


    TokenStream::from(expanded)
}

EDIT: Simplified because I don't actually need extern "C"

1 Like

Yeah, I don't think that works, I'm trying on this example

use std::fmt::{Debug, Formatter};

struct MyStruct {

}

impl Debug for MyStruct{
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "Hello world!")
    }
}

#[unsafe(no_mangle)]
fn gdbprint(thing: &dyn Debug) {
    println!("{thing:?}");
}

#[unsafe(no_mangle)]
fn mystruct_print(x: &MyStruct) {
    println!("{x:?}")
}

fn main() {
    let x = MyStruct{};
    mystruct_print(&x);
    println!("{:?}", x);
}

Breakpoint on println!("{:?}", x);. I can call mystruct_print but not gdbprint.

(Note that I am using RustRover, not raw gdb, but I have to assume RustRover GDB debugger is a fairly thin wrapper around normal gdb)

1 Like

this also will not work for another reason.

'dyn' does not do what you think it does. In your case the function would likely not be callable at all without causing UB.
What 'dyn' actually does is it collects for each compilation unit (possibly even at a smaller scale idk) all call/usage sites to a specific dyn Trait, and then constructs a v-table only and exclusively only for those types. That means if you just export a function with a dyn {Trait} argument and you never call this function nor use that same dyn {Trait} anywhere else in your crate, you can be rest assured, that not a single v-table was created. With other words the only type that is coercible to your dyn {Trait} is ! (the Never type), and as the Never type is not uninhabited, this means similarly your dyn {Trait} is uninhabited, i.e. no valid values exist for &dyn {Trait}, any value would be UB, thus calling the function would always be UB

2 Likes

Is that the bit that makes it work? Did directly tagging the method with #[used] not work?

Yeah i tried that too, but used can not be applied to functions.

Got it.

Thank you for the follow up and the macro!

1 Like