Cross compile for RaspberryPi 0 (armv6) failing

I'm implementing a small OS on a RaspberryPi Zero. I've done a similar project in C, using the arm-none-eabi toolchain. I'm now attempting to port the code to Rust by cross-compiling it.

I've followed the popular blogs: OSDev, Philipp Oppermann and seen the popular issues: "how-to-compile-freestanding-binary-for-armv6" and "armv6-z-bare-metal-cross-compilation-fails" (can't put links lol)

I've implemented all the workarounds and fixes mentioned in these posts, and my code worked for naively turning on an LED. However, when I try to add some sort of loop while or for n in 1..10 for example, the .rlib file builds fine, but when I try to link:

I get this error:

/Users/joshdelg/krust/src/ undefined reference to `core::panicking::panic'
/opt/homebrew/Cellar/arm-none-eabi-test/9-2019q4-cs107e/bin/../lib/gcc/arm-none-eabi/9.2.1/../../../../arm-none-eabi/bin/ld: /Users/joshdelg/krust/src/ undefined reference to core::panicking::panic'

I have the following in my Cargo.toml

panic = "abort"

and I've defined my custom build target like this. arm-none-eabihf.json:

    "llvm-target": "arm-none-eabihf",
    "target-endian": "little",
    "target-pointer-width": "32",
    "target-c-int-width": "32",
    "os": "none",
    "env": "eabi",
    "vendor": "unknown",
    "arch": "arm",
    "linker-flavor": "gcc",
    "linker": "arm-none-eabi-gcc",
    "data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
    "executables": true,
    "relocation-model": "static",
    "no-compiler-rt": true,
    "disable-redzone": true,
    "panic-strategy": "abort"

For reference the source code of is as follows:

#![no_std] // don't link the Rust standard library
#![no_main] // disable all Rust-level entry points

#![feature(core_intrinsics, lang_items, rustc_attrs)]

use core::panic::PanicInfo;

extern "C" {
  fn GET32(addr: u32) -> u32;

extern "C" {
  fn PUT32(addr: u32, val: u32);

#[no_mangle] // don't mangle the name of this function
pub extern fn krust_entry() {
  // this function is the entry point, since the linker looks for a function
  // named `_start` by default
  // loop {}
  const FSEL: *mut u32 = 0x20200008 as *mut u32;
  const SET0: *mut u32 = 0x2020001C as *mut u32;
  const CLR0: *mut u32 = 0x20200028 as *mut u32;

  // Set output
  unsafe {
    // let mut existing = volatile_load(FSEL);
    let mut existing = GET32(FSEL as u32);

    existing &= (!0b111);
    existing |= 0b001;

    // volatile_store(FSEL, existing);
    PUT32(FSEL as u32, existing);

    loop {
      unsafe {
        // Set on
        PUT32(SET0 as u32, 1 << 20);

        let mut i = 0;
        while i < 1_000_000 {
          i += 1;
        // Set off
        PUT32(CLR0 as u32, 1 << 20);

        let mut i = 0;
        while i < 1_000_000 {
          i += 1;


/// This function is called on panic.
fn panic(_info: &PanicInfo) -> ! {
    loop {}

pub extern fn __aeabi_unwind_cpp_pr0() {}

#[lang = "eh_personality"]
pub extern fn eh_personality() {}

pub extern fn panic_nounwind() {}

pub extern fn _Unwind_Resume() { loop {} }

I've implemented dummy versions of some of the handlers according to the blog posts to try to skirt this issue. Any help would be appreciated!

could this be a regression of LTO causes undefined references to core::panicking::panic · Issue #79 · rust-lang/compiler-builtins · GitHub ?

do you have LTO enabled?