Info
This post is auto-generated from RSS feed The Rust Programming Language Forum - Latest topics. Source: Is there any way to properly resolve method pointer for dynamic dispatch?
Hello.
I'm currently working on implementing debug console output for RISC-V bare metal operating system kernel, which uses U-boot as bootloader. At first it seemed pretty straightforward; just implement Write
trait for my DebugConsole
struct, and invoke write!
macro in order to format and print the output.
However, the program crashed in runtime because of illegal instruction memory access, resulting in immediate reset of the machine. After some investigation, I figured out that it was related to the improper resolution of method pointer for my write_str
implementation.
The write
function, which is the actual implementation of write!
macro, takes an reference to a Write
trait object and uses dynamic dispatch in order to invoke write_str
method. This involves looking up the vtable of this trait object reference in order to find the method pointer of the actual implementation. However, the method pointer seems to be relative from the program's base address, not the actual absolute address that we use. Since the relative address does not make sense as an actual physical address, the kernel crashes due to illegal memory access.
Note that the other portion of program code is free of this issue since they are PC-relative, or you may say, position independent. Only the portion that uses dynamic dispatch is problem.
Usually this issue is handled either by the loader relocating vtable entries, or the operating system providing an illusion of virtual memory. However, since this is bare metal environment and the program itself is an operating system, there's no such loader or OS that conveniently provides this feature.
One common workaround, which is taken by some educational operating system, is making the kernel to start at a fixed physical address by adjusting the location counter of the linker, making the vtable addresses to be correct. This works well but fragile since no one knows where the kernel will be loaded without the knowledge of the target board. In order for this approach to correctly work, the implementer should enumerate all possible load addresses.
Another approach is to see the section table and section header of the ELF, figuring out which sections are relocatable, and relocate the kernel binary. The problem of this approach is U-boot doesn't seem to correctly adjust the entry address of ELF. The entry address itself now becomes a problem, not the vtable and its contained addresses. Maybe we can append some special code that jumps to the (PC-relative) program entry at the very beginning of the kernel binary and fool U-boot as if it is a flat binary, but this seems to be hacky and gone way too much.
The Linux kernel contains the relocation information at somewhere in the flat binary, and does this relocation at the very beginning of the initialization process. It also enables MMU, so that the other portion of the code can run under the illusion that the kernel is loaded at some fixed address (0xFFFF_FFFF_FFFF_FFFF - 2GiB for SV39 paging scheme of RISC-V). I think this is the approach I should take, but this requires the portion of the code run before the relocation or initialization of MMU must not rely on dynamic dispatch - which we cannot easily guarantee since as we've seen, the core module of Rust uses this.
So, to wrap up, what I want to ask is;
The problem is basically same with the problem of this post, so please check this out if you need more information.
1 post - 1 participant
🏷️ rust_feed