It is essential for embedded programmers to realize in which part they involved within the whole embedded development. Errors are more vague in the embedded development compared to web or desktop app development. One way to handle these errors correctly crosses over to understand what journey our code makes until the target processor.
The compiler or the toolchain may support a wide range of platforms. This wide support is important for developers who have a wish to have some flexibility in their platform choice. You may end up using many different architectures and many different compilers in your career. But the concept I’ll try to explain will be extendable to other projects. The main job of the compiler toolset is to translate a high-level language into architecture specific languages. By the end of the module you’ll be able to design your own build environment.
The architecture specific language we need to translate our C-Program into is called assembly. Assembly language does not directly get installed onto your processor. The assembly is translated into machine code, which is a binary encoding of assembly instructions. This machine code is what the processor understands and uses to perform work. The machine code binary representation, these encoded instructions, can be looked up in the processor’s Instruction Set Architecture, or the ISA. Providing specific guidance and optimizations during the build process, is important for our embedded platform as the assembly instructions are the objects that need to be optimized before they are converted into the processor executable.
Next, the assembler converts our assembly code into object code. Object code looks like confusing binary data and it’s not really human readable. This assembly to object code conversion gets repeated from many source files.
Now, object files need to be combined into a single executable where all references between object files need to be resolved. We call these references symbols. Now, this job is performed by the linker.
Finally, the linker provides the linked file to a relocator where which will map all the addresses of code and data into the processor’s memory space. The final file should be your target executable and all that needs to happen is to be installed on your target system.
Embedded engineers are usually very involved with customizing each of these steps for their specific application. The operation of installing a build could be considered part of the build system, but we would be performing this with the help of other tools. In general, you can think of compilation as converting source files into object code. These source files can be assembly or C programs. You can perform this translation as many steps as you like, but eventually, these files have to head into the linker.
You should note that once you compile a project, the executable only work on the architecture you compiled at for. It cannot be run on other systems. Native compilation is when you compile your build for the same system you intend to run the executable on. Typically, we do not natively compile embedded projects as we use a host machine to compile our microcontroller software builds. When you compile an executable on one system and it is intended to run on another, this is referred to as cross compilation.
We have to Cross Compiler microcontroller builds because the microcontroller itself does not usually have an operating system or you don’t have the target platform ready or the resources for programs like GCC to be installed for us to perform a build. (More advanced machines may have this capability, like the Cortex-A ARM processor set.)