Unexpected panic_bounds_check added to assembly output

Hello! I'm developing a bare-metal kernel with rust and I've run into a problem with panic_bounds_check. This function call is automatically added in by the compiler, without the accompanying function definition. I imagine that's because I'm trying to compile this as a staticlib with no_std.

This would be fine if I could shim the panic_bounds_check function by defining my own variation, but that generates a compile error.

The following code will compile with the panic_bounds_check being included:

#![crate_type = "staticlib"]

static ITEMS: [u8; 1] = [0];

pub fn main() {


pub fn get_item(idx: usize) -> u8 {
    return ITEMS[idx];

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

pub extern fn my_panic(_info: &core::panic::PanicInfo) -> ! {
    loop { }

Here is how I'm compiling it

rustc --target thumbv7em-none-eabihf \
    -o out/test.o \
    --crate-type staticlib \
    -O --emit=obj \

Here is the relevant assembly output (line 0x16):

00000000 <get_item>:
   0:	2800      	cmp	r0, #0
   2:	bf04      	itt	eq
   4:	2000      	moveq	r0, #0
   6:	4770      	bxeq	lr
   8:	b580      	push	{r7, lr}
   a:	466f      	mov	r7, sp
   c:	f240 0200 	movw	r2, #0
  10:	2101      	movs	r1, #1
  12:	f2c0 0200 	movt	r2, #0
  16:	f7ff fffe 	bl	0 <_ZN4core9panicking18panic_bounds_check17h9048f255eeb8dcc3E>
  1a:	defe      	udf	#254	; 0xfe

If I try to shim panic_bounds_check, I get an error found duplicate lang item 'panic_bounds_check' even though it's definitely not a duplicate in the actual output.

#[lang = "panic_bounds_check"]
pub extern fn panic_bounds_check() {
    loop {


I am hoping there's a compiler option that I am unaware of which somehow either disables panic bound checking or allows me to shim it properly. Any help pointing me in the right ddirection would be much appreciated.

Thank you for your time.

Is --emit=obj really what you want? The default, --emit=link will give you a .a containing the .o files for all the compiler builtins you need. When I compile your example on aarch64 in release mode, I get 300 .o files :grimacing:... But you usually compile all the Rust code you need to a single .a and link that into whatever your final binary is.

In my case, rustc included panic_bounds_check in the file core-22574ec029e9d229.core.d4e242b2-cgu.0.rcgu.o inside the .a.

P.S. in case you don't know, you can avoid having to define eh_personality by disabling unwinding on panic by using -C panic=abort.


Hey @jessa0, thanks for the reply! I'll start by saying your suggestion technically worked, and it does solve the problem! :partying_face: Hoepfully this is helpful to others who encounter the problem.

Unfortunately for my specific case, compiling with -O --emit=link is not viable. I am compiling without release mode (and so I just get a single .o file) because the target device is a tiny microcontroller. When I do the emit=link thing, it generates an output that takes up 13% of all available space on my device. I guess the output contains all the core functionality that is missing.

I'd rather just shim the specific function that was added to the output, instead of including the entirety of the core library.

I didn't know about -O --emit=link so thank you for that. It's helpful and it actually does solve a lot of problems. But I'm determined to do this the hard way haha. I've found a different workaround.

use core::arch::global_asm;

        b hang

        b hang

This is one way I was able to get the definition shimmed, by just using assembly. It's probably an awful solution but to keep my build size as minimal as possible this technically works.

PS: I didn't know about the panic=abort thing either. Thanks!!!

I'm curious why you are compiling without release mode? That's essentially a requirement of making Rust code viably small and fast..

I just don't think Rust without linking to core is well supported. As the core crate documentation says, it "defines the intrinsic and primitive building blocks of all Rust code."

I'd also look into using -Z build-std=core with cargo and try -Z build-std-features=panic_immediate_abort, which prevents any formatting of panic messages and should prevent panic_bounds_check from being called.

You could also toy with flags like -C opt-level=s, -C codegen-units=1, -C debug-assertions=yes/no, -C lto=yes/no, and -C overflow-checks=yes/no.


I should clarify, I'm not compiling with cargo at all. I'm just emitting stuff with rustc and then using an arm-specific toolchain to link and generate the final .hex file.

You have me convinced though. Including core polyfills a lot of functions that are useful. If I try to shim everything I'll just end up re-creating the core library lol.

I was able to even bring the final file size down significantly by augmenting the linker step! -strip-all seems to remove unused functions.

Here's my final build process:

# Compile rust
rustc --target thumbv7em-none-eabihf \
    -C panic=abort \
    --crate-type staticlib \
    -O --emit=link \
    -o out/kernel.o \
    -C opt-level=3 \
    src/main.rs \

# Compile c
arm-none-eabi-gcc \
    -O3 \
    -Wall \
    -Werror \
    -mcpu=cortex-m7 \
    -mthumb \
    -mfloat-abi=hard \
    -c src/teensy.c -o out/teensy.o

# Generate hex
arm-none-eabi-ld \
    -Map=out/kernel.map \
    -T src/linker.ld \
    -strip-all \
    --gc-sections \
    out/teensy.o out/kernel.o \
    -o out/kernel.elf

arm-none-eabi-objdump -S out/kernel.elf > out/hex.asm

Nice. Yeah, I think this is one thing cargo would win for you over invoking rustc manually, is being able to build a custom libcore for you with no panic formatting, if you need to go to that extreme.