Home > Software design >  How get pointer to virtual table from Box<Trait>?
How get pointer to virtual table from Box<Trait>?

Time:07-23

I want to understand how the virtual table works and possibly use it in the future. I know that DST pointers are fat pointers (usize, usize), where the first pointer is data and the second pointer is virtual table. I was able to get the data by the pointer, but apparently it didn’t work out to get the pointer to the virtual table, because from https://docs.google.com/presentation/d/1q-c7UAyrUlM-eZyTo1pd8SZ0qwA_wYxmPZVOQkoDmH4/edit#slide=id.p the size of the structure should be stored by the pointer in the size variable from the code below, but it is not. Perhaps I do not understand correctly how the virtual table is stored?

My code:

#![feature(pointer_byte_offsets)]

trait MyRule {
    fn some_method1(&self) {}
    fn some_method2(&self) {}
}
struct MyData {
    data: u32,
}

impl MyRule for MyData {}

fn main() {
    unsafe {
        let b: Box<dyn MyRule> = Box::new(MyData { data: 5 });
        let fat_ptr = Box::into_raw(b);
        dbg!(*(fat_ptr.cast::<u32>())); // data = 5
        let vtbl_ptr = fat_ptr.byte_add(8).cast::<*mut usize>();
        let destructor_ptr = (*vtbl_ptr).cast::<usize>();
        let size = destructor_ptr.byte_add(8);
        dbg!(size);
        dbg!(*size); // Not size!!!
    }
}

I think that the problem is that I'm not getting the pointer to the table itself correctly. So what's the right way to do this, and how do you access table elements?

CodePudding user response:

I am not sure if you meant to do this or not, but your code acts as if the vtable pointer is on the heap next to your struct. In fact, the vtable pointer is next to your other pointer on the stack.

Box<dyn T> is essentially equivalent to (*mut {data}, *mut {vtable}), but when you try to access the vtable pointer like so:

        let fat_ptr = Box::into_raw(b);
        dbg!(*(fat_ptr.cast::<u32>())); // data = 5
        let vtbl_ptr = fat_ptr.byte_add(8).cast::<*mut usize>();

The byte_add is offsetting the number that points to the MyData struct on the heap, not moving to a different location on the stack. And then .cast::<*mut usize>() says to interpret that new number as a pointer to a pointer to a usize (aka *mut *mut usize).

Instead, what you really want to do is deconstruct the Box struct to get those two pointers:

let (pointer, vtable) = mem::transmute_copy::<Box<_>, (*const u8, *const usize)>(&b);

And then you only have to offset by 1 usize to get the pointer to size:

let size_ptr = vtable.add(1); // size is offset by 1 usize

Complete example:

use std::mem;

trait MyRule {
    fn some_method1(&self) {}
    fn some_method2(&self) {}
}
struct MyData {
    data: u32,
}

impl MyRule for MyData {}

fn main() {
    unsafe {
        let b: Box<dyn MyRule> = Box::new(MyData { data: 5 });
        assert_eq!(mem::size_of_val(&b), mem::size_of::<usize>()*2);
        
        // copy pointers from Box
        let (pointer, vtable) = mem::transmute_copy::<Box<_>, (*const u8, *const usize)>(&b);
        assert_eq!(pointer as usize, &*b as *const _ as *const u8 as usize);
        dbg!(pointer, &*b as *const _);
        
        let size_ptr = vtable.add(1); // size is offset by 1 usize

        assert_eq!(*size_ptr, mem::size_of::<MyData>());
        dbg!(*size_ptr, mem::size_of::<MyData>());
    }
}

Playground

  • Related