Converting Rust to C

Hello,

I am currently experimenting with a workflow to transform Rust programs into C code.

The approach I am using is:

Rust source
    ↓ rustc
LLVM IR
    ↓ LLVM-CBE
C source
    ↓ C compiler
Executable

My goal is to obtain C source code so that I can apply existing C source obfuscation tools. There seem to be far fewer source-level obfuscation tools available for Rust than for C.

LLVM-CBE is able to generate C code from the LLVM IR produced by rustc, but when I try to compile the generated C code (after including all the Rust and C libraries), I frequently encounter linker errors such as:

/usr/bin/ld: /tmp/ccv2dnQc.o: in function `core::fmt::rt::Argument::new_display`:
s179264673.cbe.c:(.text+0x365): undefined reference to `_ZN4core3fmt3num3imp52__EC_LT_EC_impl_EC_u20_EC_core_OC__OC_fmt_OC__OC_Display_EC_u20_EC_for_EC_u20_EC_i32_EC_GT_EC_3fmt17hf9cd6342de4889e1E`
collect2: error: ld returned 1 exit status

From what I understand, these symbols correspond to functions generated by the Rust compiler during compilation.

My questions are:

  1. Is there a known way to make this LLVM IR → LLVM-CBE → C workflow work reliably with Rust programs?
  2. Are there specific Rust compilation options that reduce dependencies on Rust runtime symbols?
  3. Does anyone know of alternative approaches for translating Rust into C source code?

Any advice or experience with similar workflows would be greatly appreciated.

Thank you.
Markus

Eyeballing it, that looks like a standard library function that's missing (specifically I think it's <Display for i32>::fmt if I'm unmangling it correctly) - which is shipped already compiled as a static library and linked into your rust build.

You could try using something like cargo -Z build-std but I expect this is only going to kick the can down the road? It should get you closer to what you're after, at least.

Thank you for the suggestion.

I tried rebuilding with cargo -Z build-std, and it did indeed get me further. The generated C compiles more successfully than before, but I still end up with linker errors for Rust runtime and standard library symbols, even after linking against the Rust libraries I could identify.

/usr/bin/ld: /tmp/ccJ0AQ8P.o: in function `std::rt::lang_start::<()>`:
testproj-66687eadd765e3b6.cbe.c:(.text+0x55): undefined reference to `std::rt::lang_start_internal`
/usr/bin/ld: /tmp/ccJ0AQ8P.o: in function `testproj::main`:
testproj-66687eadd765e3b6.cbe.c:(.text+0x127): undefined reference to `<i32 as core::fmt::Display>::fmt`
/usr/bin/ld: testproj-66687eadd765e3b6.cbe.c:(.text+0x140): undefined reference to `std::io::stdio::_print`
/usr/bin/ld: /tmp/ccJ0AQ8P.o: in function `main`:
testproj-66687eadd765e3b6.cbe.c:(.text+0x1b8): undefined reference to `std::rt::lang_start_internal`
collect2: error: ld returned 1 exit status

So it seems that LLVM-CBE correctly translates the LLVM IR it receives into C, but it does not by itself reproduce the complete linking environment and runtime support that Rust normally relies on to build a final executable.

Thank you.
Markus

Did you actually link in standard library?

Yes. I tried linking against the Rust standard library from the Rust sysroot. I'm passing rustlib/x86_64-unknown-linux-gnu/lib/libstd-*.so to gcc, together with libc and libgcc_s.

For example:

gcc rust_then_c.c -o out \
 /home/markus/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-unknown-linux-gnu/lib/libstd-\*.so \
 /lib/x86_64-linux-gnu/libgcc_s.so.1 \
 /lib/x86_64-linux-gnu/libc.so.6 \
 -Wl,--dynamic-linker=/lib64/ld-linux-x86-64.so.2

Is there another library (or a different way of linking the Rust standard library) that rustc normally uses which I'm missing?

Thank you.
Markus

You need the standard library crates compiled by LLVM-CBE. The one compiled using the regular LLVM backend is not ABI compatible. LLVM-CBE changes the symbol names and the calling convention probably also doesn't match exactly.

You probably have to use -Zbuild-std and wire up everything in your codegen backend such that all produced C files from all dependencies get linked together with the current crate.

Thanks for the explanation, I understand now.

It seems that translating the entire Rust ecosystem to C using LLVM-CBE is likely a dead end, since LLVM-CBE produces approximate translations and is far from preserving a consistent ABI or runtime behavior.
I will need to reconsider my approach for translating Rust into C.

@FractalFir recently demonstrated a successful run of their translation tool:

Thanks for the pointer.

The translation tool itself hasn't been published yet, but it looks very promising. I'll definitely keep an eye on the project and I'd be happy to test it once it becomes available.

You can also use their previous project GitHub - FractalFir/rustc_codegen_clr: A Rust compiler backend targeting CIL(.NET IR) and C. · GitHub in the meanwhile