Skip to content

core::fmt::Write::write_fmt causes UB when being relocated at runtime even when code-model is pic #113207

Closed
@phip1611

Description

@phip1611

TL;DR: In this thread, I show how machine code emitted for core::fmt::write always jumps to the link address of a certain instruction although the code should be position independent (code-model is pie).

I'm working on a small x86 multiboot2 chainloader with position-independent code. The ELF consists of a small entry written in assembly (that sets up the stack) and rust code for more logic that is compiled/linked with relocation-model=pie. (I've also tried =pie but there was no difference.)

The ELF file a static ELF executable with one single LOAD segment. The Multiboot2 bootloader (GRUB) loads this file with a forced relocation (for example: pushed from 2 MiB to 6 MiB) into physical memory when the CPU is in Multiboot2's I386 machine state.

The assembly routine finds the relocation offset, sets the stack, and jumps to the high-level rust code. While some Rust functionality work properly (such as calling functions, doing calculating, printing text via an x86 I/O port) in the relocated binary, I noticed that

let x = format_args!("hello");
Printer.write_fmt(x); // small abstraction over QEMUs debug con device

causes undefined behavior and weird affects. A weird loop is started where the code above that line is executed again and again.

Interestingly, Printer.write_str("foo") works perfectly fine.

If I disable the relocation during runtime entirely, also core::fmt::Write::write_fmt works as expected and the string is printed to the screen. I had a look with an experienced colleague of mine and we are confident that my stack setup (even in the relocated case) is valid. Hence, the issue needs to be something else. From looking at the objdump, I'm certain that the code indeed uses relative addresses and calls.

I expected to see that the code is in fact relocatable in physical address space.

Instead, many functionality works but core::fmt::Write::write_fmt fails when the static ELF binary is relocated during runtime.

Meta

I'm using Rust in version nightly-2023-06-28. I'm using the following compiler spec JSON:

{
    "llvm-target": "i686-unknown-none",
    "data-layout": "e-m:e-i32:32-f80:128-n8:16:32-S128-p:32:32",
    "arch": "x86",
    "target-endian": "little",
    "target-pointer-width": "32",
    "target-c-int-width": "32",
    "os": "none",
    "executables": true,
    "linker-flavor": "ld.lld",
    "linker": "rust-lld",
    "panic-strategy": "abort",
    "disable-redzone": true,
    "features": "+soft-float,-x87,-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,-fma,-3dnow,-3dnowa"
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-linkageArea: linking into static, shared libraries and binariesC-bugCategory: This is a bug.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions