assemblyrustreverse-engineeringrust-cargoroundtrip

Relink after editing intermediate assembly files


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?


Solution

  • 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.