Skip to content

core::ptr::copy_nonoverlapping crashes when writing to odd addresses on ARM thumbv7em-none-eabihf #82945

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
birktj opened this issue Mar 9, 2021 · 15 comments
Labels
C-bug Category: This is a bug. O-Arm Target: 32-bit Arm processors (armv6, armv7, thumb...), including 64-bit Arm in AArch32 state T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@birktj
Copy link

birktj commented Mar 9, 2021

Updated report

After some further investigation it seems that the core::ptr::copy_nonoverlapping function crashes when the dest address is not even on the thumbv7em-none-eabihf platform. In the example above I am copying two bytes and experience the crash when the address copied to is odd.

MWE:

#[inline(never)]
fn mwe() {
    let buf1 = "424242";
    let mut buf2 = [0; 6];
    let buf1_ptr = buf1.as_ptr();
    let buf2_ptr = buf2.as_mut_ptr();

    // Crashes if n is odd, not if n is even
    let n = 1;

    unsafe {
        core::ptr::copy_nonoverlapping(buf1_ptr, buf2_ptr.offset(n), 2);
    }

    // Force usage of `buf2`
    write!(DummyWrite, "Test: {:?}", &buf2).unwrap();
}

Original report

I am using the cortex-m-semihosting crate to write debug messages over SWD, however when writing multi-digit numbers the execution hangs. Looking at the stacktrace from gdb it seems that execution is stuck on core::ptr::copy_nonoverlapping. Experimenting with trivial uses of core::ptr::copy_nonoverlapping does not seem to hang.

hprintln!("Test: {}", 1); // works fine
hprintln!("Test: {}", 11); // hangs

Meta

The same behaviour is also present in rust nightly.

Toolchain: thumbv7em-none-eabihf

rustc --version --verbose:

rustc 1.50.0 (cb75ad5db 2021-02-10)
binary: rustc
commit-hash: cb75ad5db02783e8b0222fee363c5f63f7e2cf5b
commit-date: 2021-02-10
host: x86_64-unknown-linux-gnu
release: 1.50.0

Stacktrace from gdb

#2  <signal handler called>
#3  0x0c004c96 in core::intrinsics::copy_nonoverlapping<u8> ()
    at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b//library/core/src/intrinsics.rs:1866
#4  core::fmt::num::imp::fmt_u32 ()
    at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b//library/core/src/fmt/num.rs:263
#5  core::fmt::num::imp::{{impl}}::fmt ()
    at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b//library/core/src/fmt/num.rs:287
#6  0x0c0041c4 in core::fmt::run ()
    at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b//library/core/src/fmt/mod.rs:1121
#7  core::fmt::write ()
    at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b//library/core/src/fmt/mod.rs:1089
#8  0x0c0026aa in core::fmt::Write::write_fmt<cortex_m_semihosting::hio::HStderr> (
    self=0x20000010 <cortex_m_semihosting::export::HSTDERR+4>, args=...)
    at /home/birk/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/fmt/mod.rs:182
#9  0x0c0025e4 in cortex_m_semihosting::export::hstderr_fmt::{{closure}} ()
    at /home/birk/.cargo/registry/src/git.colasdn.top-1ecc6299db9ec823/cortex-m-semihosting-0.3.7/src/export.rs:49
#10 0x0c003068 in cortex_m::interrupt::free<closure-0,core::result::Result<(), ()>> (f=...)
    at /home/birk/.cargo/registry/src/git.colasdn.top-1ecc6299db9ec823/cortex-m-0.7.2/src/interrupt.rs:64
#11 0x0c00251a in cortex_m_semihosting::export::hstderr_fmt (args=...)
    at /home/birk/.cargo/registry/src/git.colasdn.top-1ecc6299db9ec823/cortex-m-semihosting-0.3.7/src/export.rs:44
#12 0x0c000474 in ex6::__cortex_m_rt_main () at /home/birk/docs/cam/p232/ex6/src/main.rs:41
#13 0x0c00031a in ex6::__cortex_m_rt_main_trampoline ()
    at /home/birk/docs/cam/p232/ex6/src/main.rs:20
@birktj birktj added the C-bug Category: This is a bug. label Mar 9, 2021
@nagisa
Copy link
Member

nagisa commented Mar 9, 2021

What's the signal/interrupt that has been called? Are you sure you didn't hit a triple fault or an interrupt handler that just infinitely loops or something along those lines?

@birktj
Copy link
Author

birktj commented Mar 9, 2021

I assume it is from me entering ^C in the gdb prompt, but this might of course be incorrect. The relevant part of the GDB prompt:

^C
Program received signal SIGINT, Interrupt.
cortex_m_rt::HardFault_ (ef=0x2000fd30)
    at /home/birk/.cargo/registry/src/git.colasdn.top-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:563
563	        atomic::compiler_fence(Ordering::SeqCst);
(gdb) where
#0  cortex_m_rt::HardFault_ (ef=0x2000fd30)
    at /home/birk/.cargo/registry/src/git.colasdn.top-1ecc6299db9ec823/cortex-m-rt-0.6.13/src/lib.rs:563
#1  <signal handler called>
#2  0x0c004c96 in core::intrinsics::copy_nonoverlapping<u8> ()
    at /rustc/cb75ad5db02783e8b0222fee363c5f63f7e2cf5b//library/core/src/intrinsics.rs:1866

@nagisa
Copy link
Member

nagisa commented Mar 9, 2021

Yeah, I don't think it is. You'll want to investigate why you got a hardfault interrupt, it'll tell you something along the lines of an invalid instruction, access to invalid memory or something of that sort.

@birktj
Copy link
Author

birktj commented Mar 9, 2021

The culprit seems to be this instruction:

ldrh.w  r3, [r9, r3, lsl #1] 

Where r3 = 0xb and r9 = 0xc006506 and

(gdb) x/32 0xc006506
0xc006506 <.Lanon.1b6fed0caccf440b7d0323a46875ea57.225>:        0x31303030      0x33303230      0x35303430      0x37303630
0xc006516 <.Lanon.1b6fed0caccf440b7d0323a46875ea57.225+16>:     0x39303830      0x31313031      0x33313231      0x35313431
0xc006526 <.Lanon.1b6fed0caccf440b7d0323a46875ea57.225+32>:     0x37313631      0x39313831      0x31323032      0x33323232
0xc006536 <.Lanon.1b6fed0caccf440b7d0323a46875ea57.225+48>:     0x35323432      0x37323632      0x39323832      0x31333033
0xc006546 <.Lanon.1b6fed0caccf440b7d0323a46875ea57.225+64>:     0x33333233      0x35333433      0x37333633      0x39333833
0xc006556 <.Lanon.1b6fed0caccf440b7d0323a46875ea57.225+80>:     0x31343034      0x33343234      0x35343434      0x37343634
0xc006566 <.Lanon.1b6fed0caccf440b7d0323a46875ea57.225+96>:     0x39343834      0x31353035      0x33353235      0x35353435
0xc006576 <.Lanon.1b6fed0caccf440b7d0323a46875ea57.225+112>:    0x37353635      0x39353835      0x31363036      0x33363236

Why this would fail I don't know, the memory addresses are all valid

@birktj birktj changed the title copy_nonoverlapping hangs on ARM thumbv7em-none-eabihf` core::fmt_u32 crashes on ARM thumbv7em-none-eabihf` Mar 9, 2021
@JohnTitor JohnTitor changed the title core::fmt_u32 crashes on ARM thumbv7em-none-eabihf` core::fmt_u32 crashes on ARM thumbv7em-none-eabihf Mar 9, 2021
@birktj
Copy link
Author

birktj commented Mar 10, 2021

After some experimentation I have determined the following minimal reproducible example:

#[inline(never)]
fn fmt_u32() {
    static DEC_DIGITS_LUT: &[u8; 6] = b"424242";
    let mut buf = [0; 5];
    let buf_ptr = buf.as_ptr() as *mut u8;
    let lut_ptr = DEC_DIGITS_LUT.as_ptr();

    // Crashes if n is odd
    let n = 1;

    unsafe {
        core::ptr::copy_nonoverlapping(lut_ptr, buf_ptr.offset(n), 2);
    }

    let buf_slice = unsafe {
        core::str::from_utf8_unchecked(
            core::slice::from_raw_parts(buf_ptr.offset(n), 2))
    };
    // Force usage of `buf_slice`
    write!(DummyWrite, "Test: {}", buf_slice).unwrap();
}

It seems that core::ptr::copy_nonoverlapping ends up becoming an unaligned write (when n is even it works fine) which causes the crash.

@birktj birktj changed the title core::fmt_u32 crashes on ARM thumbv7em-none-eabihf core::ptr::copy_nonoverlapping crashes when writing to odd addresses on ARM thumbv7em-none-eabihf Mar 10, 2021
@nagisa
Copy link
Member

nagisa commented Mar 10, 2021

Does it work if you specify the -Ctarget-feature=+strict-align rustc flag?

@birktj
Copy link
Author

birktj commented Mar 10, 2021

It seems that my MWE gets fixed however I still hit the bug in core::fmt_u32 on this instruction (from core:::ptr::copy_nonoverlapping):

strh    r1, [r2, r0]

where r0 = 0x25 and r2 = 0x2000feac which also looks like an unaligned write.

@birktj
Copy link
Author

birktj commented Mar 10, 2021

I can now confirm that by combining -C target-feature=+strict-align with -Z build-std=core the issue is fixed.

@nagisa
Copy link
Member

nagisa commented Mar 11, 2021

cc @japaric @jonas-schievink should this target enable strict-align by default? AFAIU use of -strict-align on other ARM targets relies on OS-side handling of unaligned reads, which doesn't exist here?

@japaric
Copy link
Member

japaric commented Mar 11, 2021

I have never run into unaligned memory exception with Cortex-M chips. (*)

The ARMv7-M Architecture Reference Manual says:

The following data accesses support unaligned addressing, and only generate alignment faults when the CCR.UNALIGN_TRP bit is set to 1

  • Non halfword-aligned LDR{S}H{T} and STRH{T}
  • Non halfword-aligned TBH
  • Non word-aligned LDR{T} and STR{T}
    Note
  • Accesses to Strongly Ordered and Device memory types must always be naturally aligned

CCR looks like a register that cannot be accessed / set by software but it's set in hardware by the vendor.

It could be:

  • LLVM changed recently and was doing +strict-align by default for the thumbv7*m-none-eabi* targets but no longer is
  • This particular chip vendor decided to configure the CCR register to make turn all unaligned memory accesses into exceptions. What chip is this by the way?

(*) I have seen this very same code (core::fmt) trigger unaligned memory access exceptions on other targets though, including a Cortex-A compilation target I wrote myself (.json file) -- forgot to add +strict-align and Cortex-A defaults to exception on unaligned access.

AFAIU use of -strict-align on other ARM targets relies on OS-side handling of unaligned reads, which doesn't exist here?

On modern ARM chips, exception on unaligned access is usually configurable. Cortex-A cores have a register, accessible from software, that can change the behavior but defaults to exceptions ON.

I think old ARM chips are not configurable: unaligned access always results in exceptions. OSes like Linux handle unaligned access in interrupt context to support those chips. Those OSes also do not configure modern ARM chips to disable exceptions on unaligned access, from what I've seen.

@birktj
Copy link
Author

birktj commented Mar 11, 2021

What chip is this by the way?

It is an xmc4500 (reference manual). From the reference manual on page 128 it seems like it is possible to to set the UNALIGN_TRP bit which says the following:

Unaligned Access Trap Enable
Enables unaligned access traps:
0b do not trap unaligned halfword and word accesses
1b trap unaligned halfword and word accesses.
If this bit is set to 1, an unaligned access generates a UsageFault.
Unaligned LDM, STM, LDRD, and STRD instructions always fault irrespective of whether UNALIGN_TRP is set to 1.

I am going to try and see if I am able to experiment with that bit later today.

@birktj
Copy link
Author

birktj commented Mar 11, 2021

Yep, I am able to set the CCR.UNALIGN_TRP bit to zero which also seems to fix the problem.

@japaric
Copy link
Member

japaric commented Mar 12, 2021

CCR looks like a register that cannot be accessed / set by software but it's set in hardware by the vendor.

I was wrong about this. The CCR register is part of the SCB peripheral and it's available from firmware and also exposed in the cortex-m crate. ARM Architecture Reference Manual says the reset value of this register is "implementation defined" in the latest revision of the document but "0x0" in older revisions. 0x0 means unaligned memory access does not trigger an exception. In the xmc4500 the reset value of the register is 0x200: only the STKALIGN bit is set; that bit makes unaligned access trigger an exception.

I see two ways to deal with CCR being "implementation defined": either (a) we set +strict-align on all Cortex-M targets, or (b) we clear the CCR register on reset in the cortex-m-rt crate. I think (a) makes the most sense because it preserves vendor's settings and not everyone may be using the cortex-m-rt crate. Adding +strict-align will likely affect code size or performance of existing applications but I don't know what those may be (larger program? slower program? faster program?).

@jieyouxu jieyouxu added O-Arm Target: 32-bit Arm processors (armv6, armv7, thumb...), including 64-bit Arm in AArch32 state T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed needs-triage-legacy labels Feb 18, 2024
@cosmikwolf
Copy link

I just ran into this problem, and on the STM32H7A3, there are some memories (TCM memories like DTCM) for which the UNALIGN_TRP can be set to 0, and still cause the SCB to hit an "unaligned access" fault. setting -Ctarget-feature=+strict-align does not fix the issue, but creating a new target file with "features": "+vfp4d16sp,+strict-align" (I am using the hf variety) does solve the issue.

@taiki-e
Copy link
Member

taiki-e commented May 31, 2025

setting -Ctarget-feature=+strict-align does not fix the issue

I guess this is because you were using a pre-compiled libcore compiled without strict-align. If so, compiling with -Z build-std may work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. O-Arm Target: 32-bit Arm processors (armv6, armv7, thumb...), including 64-bit Arm in AArch32 state T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

7 participants