Practicals - part 3

Well, that was a long break.
Last time in practicals 2, we got the following compilation error before we had to learn about cross-compilation and linking.

error: linking with `cc` failed: exit status: 1
  |
  # some lines have been hidden here for the sake of presentability...   
  = note: /usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/11/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
          (.text+0x1b): undefined reference to `main'
          /usr/bin/ld: (.text+0x21): undefined reference to `__libc_start_main'
          collect2: error: ld returned 1 exit status
          
  = note: some `extern` functions couldn't be found; some native libraries may need to be installed or have their path specified
  = note: use the `-l` flag to specify native libraries to link
  = note: use the `cargo:rustc-link-lib` directive to specify the native libraries to link with Cargo (see https://doc.rust-lang.org/cargo/reference/build-scripts.html#cargorustc-link-libkindname)

Analyzing the error

Using the cross-compilation and linking knowledge, we can dissect the above compilation error.

# ...
error: linking with `cc` failed: exit status: 1
# ...

cc stands for C Compiler. It looks like some linking error occured while the C compiler was compiling some files in our codebase. Why the hell is Rust calling a C compiler? Which files need to be compiled using the C compiler?

Well, if we scroll to some lower sections of the error, we see the crt1.o file mentioned. This is an object file that usually gets used as part of the C-runtime. (it must have been written in C, this must be the reason why our compilation process had to summon the C-compiler) - mystery 1 solved.

Mystery two: Why are C runtime files getting involed in our code-base even after we had added the #![no_main] attribute?
The C runtime files got involved because the Rust compiler still thinks that we are compiling for the host's triple-target. The reason why it still thinks that is because we used the command cargo build instead of cargo build --target=<a_different_target>

Compiling for the host's triple-target means that the linker will by default use a pre-defined linker-script that had been made specifically for the host. In our case, the pre-defined linker-script had information relating to the C runtime and that is how the C-runtime files got involved in the drama.

To fix this error, we have to stop the usage of that linker-script that has C-runtime affiliations.
We can implement one of the following solutions:

Solution 1. Provide our very own linker-script that does not have affiliations to the C-runtime files (also called start files).

Solution 2. Instruct the linker to stop including C-runtime file symbols to our object files.

Solution 3. We can stop compiling for any target that has a C-affiliated-linker-script and instead, only compile for targets that have linker-scripts that do not reference the C-runtime. All triple-targets that have Operating systems specified are almost assured to call the C-runtime. All triple-targets that do not have the operating-system specified are less likely to call the C runtime. In short, triple-targets that use the std library are out of our radar.

Solution 1.

Solution 1 is about writing our own linker-script as a solution since a manual linker-script usually overides the default auto-generated script.
Try to implement this on your own. You can view the linker-script used in the no-std-template to get some ideas.

Solution 2.

Solution 2 is achieved by running the following command.

cargo rustc -- -C link-arg=-nostartfiles # learn more about these commands by reading the Cargo and Rustc books

Solution 3.

Solution 3 can be implemented by running the command below :

cargo build --target=riscv32i-unknown-none-elf
# You can replace `riscv32i-unknown-none-elf` with a target that you have already installed
# The target here should have the value 'none' in place of the Operating system

The point of Solution 3 is to build for bare-metal targets only.



FINALLY

And if you compile your program, it compiles without any errors.
That's it! A bare-metal program that literally does nothing, just boiler-plate.
Quite anti-climatic.

Now you've learnt how to build bare-metal programs. You are yet to learn bare-metal debugging, functional-testing, performance-testing and monitoring. Those chapters will be covered later on.

For now, the next chapters will be about the UART. We are so far away from writing UART code. It's a long way off, but we'll get there slowly.