Strange behavior with pointer on (pseudo) baremetal

Hi I'm trying to understand what happens here when comparing to location in memory (pointer)

fn main() -> ! {

    extern "C" {
        // Boundaries of the .bss section
        static _ebss: u32;
        static _sbss: u32;

        // Boundaries of the .data section
        static _edata: u32;
        static _sdata: u32;

        // Initial values of the .data section (stored in Flash)
        static _sidata: u32;
    }

    let mut cons = Serial;

    unsafe {
        let a = &_sdata as *const u32;
        let b = &_sidata as *const u32;
        let c = &_sdata;
        let d = &_sidata;
        write!(cons, "_sdata {:p}\n", &_sdata).unwrap();
        write!(cons, "_edata {:p}\n", &_edata).unwrap();
        write!(cons, "_sidata {:p}\n", &_sidata).unwrap();
        write!(cons, "_sbss {:p}\n", &_sbss).unwrap();
        write!(cons, "_ebss {:p}\n", &_ebss).unwrap();
        write!(cons, "{:?}\n", ptr::addr_of!(_sidata)).unwrap();
        write!(cons, "{:?} {:?} {}\n", ptr::addr_of!(_sdata), ptr::addr_of!(_sidata), ptr::addr_of!(_sdata) == ptr::addr_of!(_sidata)).unwrap();
        //write!(cons, "{:?} {:?}\n", a, b).unwrap();
        write!(cons, "same1? {}\n", a == b).unwrap();
        write!(cons, "same2? {}\n", ptr::eq(c, d)).unwrap();
        write!(cons, "same2? {}\n", ptr::eq(&_sdata, &_sidata)).unwrap();
        write!(cons, "same3? {}\n", ptr::eq(&_sdata as *const _, &_sidata as *const _)).unwrap();
    }

    loop { }
}

This is the output

$ qemu-system-riscv64 -nographic -machine virt -bios ./target/riscv64imac-unknown-none-elf/release/app
_sdata 0x80001740
_edata 0x80001a38
_sidata 0x80001740
_sbss 0x80001a38
_ebss 0x80001a38
0x80001740
0x80001740 0x80001740 false
same1? false
same2? false
same2? false
same3? false

If i uncomment the line: // write!(cons, "{:?} {:?}\n", a, b).unwrap(); the output change

$ qemu-system-riscv64 -nographic -machine virt -bios ./target/riscv64imac-unknown-none-elf/release/app
_sdata 0x800017e8
_edata 0x80001ae0
_sidata 0x800017e8
_sbss 0x80001ae0
_ebss 0x80001ae0
0x800017e8
0x800017e8 0x800017e8 false
0x800017e8 0x800017e8
same1? true
same2? false
same2? false
same3? false

Can someone can explain the thing ?
Thanks
-Stephane

How are you compiling the code? What target do you use?

see riscv/main.rs at master · maduma/riscv · GitHub

I'm compiling for riscv using 'cargo build --release'

When you modify code, e.g. by uncommenting that line, you end up changing the sizes, and thus positions, of the various segments. The optimizer makes size changes somewhat unpredictable; e.g. sometimes the optimizer may detect constant data that it can merge.

Watch out for autoderef. a == b compares the values stored at those locations, not addresses.

2 Likes

Also watch out for &_sdata and similar; it has the potential to alias a mutable static or a static which has one of the cell types, which is UB if that reference happens to have a lifetime which overlaps a mutable reference.

I believe this is actually UB - you've created an immutable reference to memory you have no reason to believe won't get mutated while it's held (e.g. something internal to write!()). Those all need to be ptr::addr_of! (except for .rodata).

Additionally, you cannot write the copy from .sidata to .data in Rust code, because globals must be initialized before main starts. You're going to need to break out the assembly for that.

2 Likes

I don't want to access the values but just compare addresses, how can i do that.

ptr::addr_of!(_sdata) == ptr::addr_of!(_sidata)

not seems to do the job

I have simplified the code to try understand

fn main() -> ! {

    extern "C" {
        // Boundaries of the .bss section
        static _ebss: u32;
        static _sbss: u32;

        // Boundaries of the .data section
        static _edata: u32;
        static _sdata: u32;

        // Initial values of the .data section (stored in Flash)
        static _sidata: u32;
    }

    //let mut cons = Serial;

    unsafe {
        asm!("mv t3, {}", in(reg) 111);
        let a = &_sdata as *const _ as usize;
        asm!("mv t4, {}", in(reg) a);
        let b = &_sidata as *const _ as usize;
        asm!("mv t5, {}", in(reg) b);
        let c = if a == b {1} else {2};
        asm!("mv t6, {}", in(reg) c);
    }

    loop { }
}
00000000800001ca <main>:
    800001ca:   06f00513                li      a0,111
    800001ce:   8e2a                    mv      t3,a0

00000000800001d0 <.Lpcrel_hi0>:
    800001d0:   00000517                auipc   a0,0x0
    800001d4:   15050513                add     a0,a0,336 # 80000320 <_ebss>
    800001d8:   8eaa                    mv      t4,a0

00000000800001da <.Lpcrel_hi1>:
    800001da:   00000517                auipc   a0,0x0
    800001de:   14650513                add     a0,a0,326 # 80000320 <_ebss>
    800001e2:   8f2a                    mv      t5,a0
    800001e4:   4509                    li      a0,2
    800001e6:   8faa                    mv      t6,a0
    800001e8:   a001                    j       800001e8 <.Lpcrel_hi1+0xe>

the if statement is not there!
What happens here ?
Is it because static are not initialized in rust ?

I ended up doing it using asm. A pure rust solution to compare static addr seems to be UB.

see RAM init code violates pointer provenance and aliasing rules · Issue #69 · rust-embedded/riscv-rt · GitHub

#[inline]
fn cmp_addr<T>(p1: *const T, p2: *const T) -> bool {
   let i: usize;
    unsafe {
        asm!(
            "sub {a}, {a}, {b}",
            "snez {c}, {a}",
            a = in(reg) p1,
            b = in(reg) p2,
            c = out(reg) i,
        );
    }
    i == 0
}

#[entry]
fn main() -> ! {

    extern "C" {
        // Boundaries of the .bss section
        static _ebss: u32;
        static _sbss: u32;

        // Boundaries of the .data section
        static _edata: u32;
        static _sdata: u32;
        // Initial values of the .data section (stored in Flash)
        static _sidata: u32;
    }

    let mut cons = Serial;
    let is_eq = unsafe { cmp_addr(ptr::addr_of!(_sdata), ptr::addr_of!(_sidata)) };
    write!(cons, "{}", is_eq).unwrap();

    loop { }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.