We can pass a flag to Cargo that is in turn passed to rustc
to emit intermediate assembly files for the whole contents of a crate:
$ RUSTFLAGS="--emit=asm" cargo rustc --release
After running this, indeed we can see a whole lot of .s
files under target/$TARGET/release
:
$ ls target/avr-atmega32u4/release/deps/*.{s,elf}
target/avr-atmega32u4/release/deps/avr_config-e311e93c086c3db0.s
target/avr-atmega32u4/release/deps/avr_delay-157ca9fb1a916f1a.s
target/avr-atmega32u4/release/deps/avr_progmem-9dc1e040eb728712.s
target/avr-atmega32u4/release/deps/avr_std_stub-c3510b4296c6559e.s
target/avr-atmega32u4/release/deps/cfg_if-21f3790d6886cc57.s
target/avr-atmega32u4/release/deps/compiler_builtins-d2b51c47ad38c941.s
target/avr-atmega32u4/release/deps/core-195cf775332e0617.s
target/avr-atmega32u4/release/deps/ruduino-15843435a02e3c3a.s
target/avr-atmega32u4/release/deps/rustc_std_workspace_core-7426025ff9d9438f.s
target/avr-atmega32u4/release/deps/ufmt-b3d2cb48639acfb8.s
target/avr-atmega32u4/release/deps/ufmt_write-4e35e82da143e2d5.s
target/avr-atmega32u4/release/deps/worduino_avr-b35d7970ef451ba9.elf*
target/avr-atmega32u4/release/deps/worduino_avr-b35d7970ef451ba9.s
target/avr-atmega32u4/release/deps/worduino_engine-1dc7446bd9d04ccf.s
How do I change some of these and then continue with the same assembly and linking process? So let's say I edit the file
target/avr-atmega32u4/release/deps/worduino_engine-1dc7446bd9d04ccf.s
, how do I then ask Cargo to create a new version of worduino_avr-b35d7970ef451ba9.elf
with otherwise the same link-time settings as the original invocation of cargo rustc
?
To my surprise, it turns out, at least for AVR, the assembly code corresponding to the toplevel crate contains everything needed, no linking required. So for e.g. LLVM intermediate files, we can use llc
for static compilation, and then GCC for trivial linking:
$ RUSTFLAGS="--emit=llvm-bc" cargo rustc --release
$ llc-15 -march=avr -mcpu=atmega32u4 -filetype=obj --function-sections target/avr-atmega32u4/release/deps/worduino_avr-b35d7970ef451ba9.bc -o foo.o
$ avr-gcc -mmcu=atmega32u4 -Os foo.o -o foo.elf
This produces a valid ELF file in foo.elf
, however, it is not exactly the same as the one produced by Rust, which is concerning:
$ md5sum foo.elf target/avr-atmega32u4/release/deps/worduino_avr-b35d7970ef451ba9.elf
5ba0a483b6adbc81faaba180e8e5ad1c foo.elf
198eff7c052172bcff6a9a81306a7bf7 target/avr-atmega32u4/release/deps/worduino_avr-b35d7970ef451ba9.elf
This seems to be because foo.o
itself doesn't exactly match the intermediate object file worduino_avr-b35d7970ef451ba9.worduino_avr.124c28c8-cgu.0.rcgu.o
created by Rust; I'm not sure what exactly is going on here.
Functionally, loading the resulting .elf
file into an AVR simulator, it seems that the result matches the behaviour of the real Rust output, so that's good. Still, it would be better if we could replicate the .elf
file exactly.
Anyway, moving on the next question is how to do the same, but from assembly .s
files instead of .bc
. We should be able to replace llc
with llvm-mc
in the above pipeline:
$ RUSTFLAGS="--emit=asm" cargo rustc --release
$ llvm-mc-15 --arch=avr --mcpu=atmega32u4 -filetype=obj target/avr-atmega32u4/release/deps/worduino_avr-b35d7970ef451ba9.s -filetype=obj -o bar.o
$ avr-gcc -mmcu=atmega32u4 -Os bar.o -o bar.elf
However, here we hit another problem where llvm-mc
mis-assembles the file.