We already know how to create and use functions in our source code. But the functions do not have to reside in the same file as our main program. We can write and assemble these functions in a separate file and link them in when building the program. The function printf, which we already used several times, is an example of an external function. In the source file where you plan to use the external function, you declare it with the keyword extern , and the assembler knows it does not have to look for the source of the function. The assembler will assume that the function is already assembled in an object file. The external function will be inserted by the linker, provided it can find it in an object file.
Similar to using C functions such as printf, you can build your own set of functions and link them when you need them.
Building and Linking Functions
function4.asm
In the above source, we declared a number of functions as external , as we already did several times before when using printf . There’s nothing new here. But we also declared the variable pi to be global . That means this variable will also be available to external functions.
circle.asm
rect.asm
In circle.asm we want to use the variable pi declared in the main source file as global, which is by the way not a good idea, but we are doing it here for demonstration purposes. Global variables such as pi are difficult to keep track of and could even lead to conflicting variables with the same names. It is best practice to use registers to pass values to a function. Here, we have to specify that pi is external. circle.asm and rect.asm each have two functions, one for computing the circumference and one for computing the area. We have to indicate that these functions are global, similar to the main program. When these functions are assembled, the necessary “overhead” is added, enabling the linker to add these functions to other object code.
Expanding the makefile
makefile
You read the makefile from the bottom up: first the different assembly source files are assembled into object files, and then the object files are linked together in function4, the executable. You can see here the power of using make. When you modify one of the source files, make knows, thanks to the tree structure, which files to re-assemble and link. Of course, if your functions are stable and will not change anymore, there is no need to try to re-assemble them in every makefile. Just store the object file somewhere in a convenient directory and refer to that object file with its complete path in the gcc line of the makefile. An object file is the result of assembling or compiling source code. It contains machine code and also information for a linker about which global variables and external functions are needed in order to produce a valid executable. In our case, the object files all reside in the same directory as our main source, so no paths were specified here.
What about the printf function? Why is no reference made to printf in the makefile? Well, gcc is smart enough to also check C libraries for functions that are referenced in the source code. This means you should not use the names of C functions for naming your own functions! That will confuse everybody, not to mention your linker.
In the code, we used registers to transfer values from the main program to the functions, and vice versa, and that is best practice. For example, before calling r_area, we moved side1 to rdi and side2 to rsi. Then we returned the computed area in rax. To return the result, we could have used a global variable, similar to pi in the section .data section of main. But as we said before, that should be avoided. In the next chapter on calling conventions, we will discuss this more in detail.
Summary
How to use external functions
How to global variables
How to use the makefile and external functions
How to transfer values to and from functions