How to interpose drop handlers?

I would like to interpose all drop handlers in a Rust program. Specifically, I want to wrap the actual drop handler call with a prologue and an epilogue.

fn drop(&mut self) {
    prologue();
    real_drop(self); // The actual drop handler defined by the original code
    epilogue();
}

In my specific case the prologue would be incrementing a counter at a fixed memory address and the epilogue decrementing it.

I believe this requires rustc compiler change, and I'm ready to do so. Actually, I am already building the program with a custom rustc and LLVM toolchain.

I am grateful to any help. Thanks in advance.

The keywords to search for are "drop glue" and the handling of "drop flags".

Out of interest, doing the incr/decr for each Drop::drop run, or once for the whole drop glue? (i.e. incr(); drop(b); decr(); incr(); drop(a); decr(); or incr(); drop(b); drop(a); decr();?) It'd also potentially be useful to know generally what you're using the indicator for, to mitigate XY issues.

At the library level you can of course use a wrapper type to do this wrapping in specific cases where it matters the most, e.g. struct Wrapped<T>(ManuallyDrop<T>); impl<T> Drop for Wrapped<T> { fn drop(&mut self) { unsafe { incr(); self.0.drop(); decr(); }}.

Thank you for your information. I think I will need the incr/decr for each drop() run.

I am doing so because I am experimenting to use unwind to deal with out-of-memory (OOM) error on embedded systems. When OOM happens, the system will kill the current task using unwinding, much like forcibly injecting a panic!() to it.

The important background information is that I am using segmented stacks on the system. That is to say, the call stack is not a contiguous memory chunk but is a linked list of lots of small chunks. Indeed, I have a runtime to support the memory allocation/free for these small stack chunks.

The segmented stack however makes handling OOM errors non-trivial. When a drop handler is running, it may call other functions, i.e., the drop handlers of the struct's fields. Such function calls may result in memory allocation to extend the stack, thus may trigger OOM error. In such situation, it works just like panic!() in the drop handler, where some of the fields will not get properly dropped. Worse, I am seeing hardfault (similar to segmentation fault on Linux), and I believe this is caused by starting unwinding from inside a drop handler.

Now let's get back to why I need to interpose drop(). When the counter is positive, my runtime can know that the current task is executing a drop handler when OOM occurs. The runtime can then use some reserved memory to let the drop handler finish before it starts to unwind the task.

Hope it clarifies.

Thank you for your suggestion. I think I am close to get it working. I can now insert some instructions to increment the counter before calling into the drop handlers, but I failed to add instructions to decrement the counter after returning from the drop handler calls.

Specifically, I am modifying the codegen_drop_terminator method in src/mir/block.rs. I am adding the extra logic at the end of this method, wrapping around the actual code generation of the drop handler call. Since the compiler is self-compiling, I guard the additional logic with environment variables so that when it self-compiles the logic remains the same, but only when I start to compile for my Cortex-M4F target I let it emit the incr/decr code.

The code I added is shown as below. The increment part is working great. The decrement part cause rustc to segmentation fault which I do not quite understand.

I greatly appreciate any suggestion that might let me get the decrement part correct. Thanks!

// Before drop handler call (working)
if bx.sess().target.llvm_target.as_ref() == "thumbv7em-none-eabihf" {
    if let Ok(_) = std::env::var("INTERPOSE_DROP") {
        let i32_type = bx.type_i32();
        let i32_ptr_type = bx.type_ptr_to(i32_type);
        let align = bx.tcx().data_layout.pointer_align.abi;

        // A constant which is the address storing the i32
        let address_val = bx.const_u32(0x2000_0004);

        // Cast the integer to a pointer to i32
        let ptr = bx.inttoptr(address_val, i32_ptr_type);

        // Load the 32-bit value from the address
        let loaded_value = bx.load(i32_type, ptr, align);

        // Increment the value by 1
        let increment = bx.const_i32(1);
        let incremented_value = bx.add(loaded_value, increment);

        // Store the incremented value back to the address
        bx.store(incremented_value, ptr, align);
    }
}

let merging_succ = helper.do_call(
    self,
    bx,
    fn_abi,
    drop_fn,
    args,
    Some((ReturnDest::Nothing, target)),
    unwind,
    &[],
    mergeable_succ,
);

// After drop handler call (didn't work)
// Code here is causing rustc to segmentation fault
// while compiling the alloc crate
if bx.sess().target.llvm_target.as_ref() == "thumbv7em-none-eabihf" {
    if let Ok(_) = std::env::var("INTERPOSE_DROP") {
        let i32_type = bx.type_i32();
        let i32_ptr_type = bx.type_ptr_to(i32_type);
        let address_val = bx.const_u32(0x2000_0004);
        let align = bx.tcx().data_layout.pointer_align.abi;

        // Cast the integer to a pointer to i32
        let ptr = bx.inttoptr(address_val, i32_ptr_type);

        // Load the 32-bit value from the address
        let loaded_value = bx.load(i32_type, ptr, align);

        // Increment the value by 1
        let decrement = bx.const_i32(1);
        let decremented_value = bx.sub(loaded_value, decrement);

        // Store the incremented value back to the address
        bx.store(decremented_value, ptr, align);
    }
}

return merging_succ;

do_call will emit a basic block terminator, so you can't put any instructions after it in the same basic block. You have to switch to the target block first before adding new instructions.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.