============================== Using the linker and makefiles ============================== In order to explain what problems the linker solves and how it works we begin with a toy example. The initial program just contains some subprograms that use the simple calling convention from __Session 9__, hence we don't have to care about the stack, function prologues and epilogues etc. ---- TIKZ ---------------------------------------------------------------------- \begin{tikzpicture} \input{flowchart.tex} \renewcommand\BoxWidth {5} \renewcommand\FlowColDist {(\BoxWidth + 2.5)} \SetMargin{4}{10}{0}{3} \Compilation{7.5}{source file: all\_in\_one.s} \PutStatement{0}{print "main"} \PutCallStatement[red!50]{1}{call function foo } \PutCallStatement[blue!50]{2}{call function bar } \PutStatement{3}{print "MAIN"} \PutStatement{4}{halt with exit code 0} \PutLabel{0}{main} \PutStatement{5}{print "foo"} \PutStatement{6}{return} \PutLabel{5}{foo} \PutStatement{7}{print "bar"} \PutStatement{8}{return} \PutLabel{7}{bar} \AddPath{0}{1} \AddPath{1}{2} \AddPath{2}{3} \AddPath{3}{4} \PutAnnotation{0}{\text{Entry point}} \AddPath{5}{6} \AddPath{7}{8} \DrawLocalCallPointer[red!50]{0}{1}{0}{5} \DrawLocalReturnPointer[red!50]{0}{1}{0}{6} \DrawLocalCallPointer[blue!50]{0}{2}{0}{7} \DrawLocalReturnPointer[blue!50]{0}{2}{0}{8} \end{tikzpicture} -------------------------------------------------------------------------------- Printing the strings is done character by character with hard coded `putc` instructions ----- CODE (file=session11/ex01/all_in_one.s,fold) ----------------------------- .text // do something putc 'm' putc 'a' putc 'i' putc 'n' putc '\n' // call some function ldzwq foo, %4 jmp %4, %3 ldzwq bar, %4 jmp %4, %3 // do something after the call putc 'M' putc 'A' putc 'I' putc 'N' putc '\n' halt 0 foo: // do something in foo putc 'f' putc 'o' putc 'o' putc '\n' // return jmp %3, %0 bar: // do something in foo putc 'b' putc 'a' putc 'r' putc '\n' // return jmp %3, %0 -------------------------------------------------------------------------------- and the control flow can easily be observed when the program gets executed: ---- SHELL (path=session11/ex01) ----------------------------------------------- ulmas -o all_in_one all_in_one.s ulm all_in_one -------------------------------------------------------------------------------- Why simply splitting the code is not good enough ================================================ In a first approach we basically just split the source file into `main.s` and `foo.s`. In the `foo.s` just a `.text` was added in the beginning (which is actually not necessary here as that is the default segment): + ----- CODE (file=session11/ex01/main.s) -------------------------------------- .text // do something putc 'm' putc 'a' putc 'i' putc 'n' putc '\n' // call some functions ldzwq foo, %4 jmp %4, %3 // call some functions ldzwq bar, %4 jmp %4, %3 // do something after the call putc 'M' putc 'A' putc 'I' putc 'N' putc '\n' halt 0 ------------------------------------------------------------------------------ + ----- CODE (file=session11/ex01/foo.s) --------------------------------------- .text foo: // do something in foo putc 'f' putc 'o' putc 'o' putc '\n' // return jmp %3, %0 .text bar: // do something in foo putc 'b' putc 'a' putc 'r' putc '\n' // return jmp %3, %0 ------------------------------------------------------------------------------ When you use the ULM assembler you can invoke it with more than one source file. In that case the files are processed in the order they appear and hence joined: ---- SHELL (path=session11/ex01) ----------------------------------------------- ulmas -o main01 main.s foo.s ulm main01 -------------------------------------------------------------------------------- However, that means if you choose the wrong order the code will not run properly: ---- SHELL (path=session11/ex01) ----------------------------------------------- ulmas -o main02 foo.s main.s #ulm main02 -------------------------------------------------------------------------------- Let's figure out what is going on. First, compare the two executables: + :import: session11/ex01/main01 [fold] + :import: session11/ex01/main02 [fold] Both programs have only a text segment and therefore we easily can describe the memory layout when a program gets loaded. For `main01` we have ---- TIKZ ---------------------------------------------------------------------- \begin{tikzpicture} \input{memory.tex} \renewcommand\MemCellWidth {1.5} \DrawMemArrayOpenRight{0}{8} \DrawMemVariable[gray!50]{0}{4}{code from main.s} \DrawMemVariable[gray!50]{4}{8}{code from foo.s} \DrawMemLabel{0}{entry point} \MarginLeft{-1}{0} \end{tikzpicture} -------------------------------------------------------------------------------- and for `main02` ---- TIKZ ---------------------------------------------------------------------- \begin{tikzpicture} \input{memory.tex} \renewcommand\MemCellWidth {1.5} \DrawMemArrayOpenRight{0}{8} \DrawMemVariable[gray!50]{0}{4}{code from foo.s} \DrawMemVariable[gray!50]{4}{8}{code from main.s} \DrawMemLabel{0}{entry point} \MarginLeft{-1}{0} \end{tikzpicture} -------------------------------------------------------------------------------- Because the entry point is by default always at address zero in the second case the execution begins with the first instruction generated from `foo.s`. So the program prints "foo" before it is executing the jump instruction. Because registers and memory cells are by default random initialized on the ULM you get an undefined behaviour once the jump instruction gets executed. If you change the defaults so that registers are zero initialized you get an infinite loop printing "foo" all over. Drawbacks: Ordering and compilation time ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The requirement to know in which order the source files need to be passed to the ULM assembler is just on drawback of this approach. In practical application also the compilation time matters. Making changes in one file would also mean that all source files have to be translated again. Just assume that you have a larger project where you measure this in hours not minutes or seconds. General idea of using a linker ============================== Instead of combining source files each assembly file can be translated into into a so called _object file_ (which basically just denotes that the assembler output is not necessarily executable). The linker then is used to combines these object files into an executable. In our case this process can be described by this flowchart: ---- TIKZ ---------------------------------------------------------------------- \begin{tikzpicture} \input{flowchart.tex} \renewcommand\BoxWidth {5} \renewcommand\FlowColDist {3} \renewcommand\BoxHeight {1.2} \renewcommand\BoxDistance {2.2} \SetMargin{1}{2}{1}{1} \renewcommand\FlowCol {0} \PutStatement{0}{main.s } \PutStatement{1}{main.o} \renewcommand\FlowCol {2} \PutStatement{0}{foo.s } \PutStatement{1}{foo.o} \renewcommand\FlowCol {1} \PutStatement{2}{main} \AddGenPath{0}{1}{0}{0}{ulmas generates} \AddGenPath{2}{1}{2}{0}{ulmas generates} \AddGenPath[above=0.3cm]{1}{2}{0}{1}{ulmld generates} \AddGenPath{1}{2}{2}{1}{} \end{tikzpicture} -------------------------------------------------------------------------------- The overall translation time can be reduced for two reasons. Because the assembly source files can be translated independent of each over this can be done on a multiprocessor system in parallel. As linkage requires the existence of all object files this step is a potential bottleneck of the process. However, the linker only operates on the object files which have a much simpler structure compared to the assembler code. Basically there are no costs for lexical and syntactical analysis and code generation is mainly limited by the throughput of the file system. Requirements for using a linker =============================== When you simply apply the workflow described above you get an error message from the linker (not the assembler): ---- SHELL (path=session11/ex01) ----------------------------------------------- ulmas -o main.o main.s ulmas -o foo.o foo.s ulmld -o main main.o foo.o -------------------------------------------------------------------------------- Creating an executable should not depend on the order the linker receives object files. Therefore the linker requires that the (logical) entry point of the program is defined by a symbol `_start`. The generated executable will contain at the actual entry point instructions to load the address `_start` into a register followed by a jump to this address. When we are talking about `ulmld` there is no need to beat about bush, a generated executable will always begin with these five instructions: ---- CODE (type=s) ------------------------------------------------------------- ldzwq @w3(_start), %1 shldwq @w2(_start), %1 shldwq @w1(_start), %1 shldwq @w0(_start), %1 jmp %1, %2 -------------------------------------------------------------------------------- So in for our toy example the memory layout of a generated executable can be either described by ---- TIKZ ---------------------------------------------------------------------- \begin{tikzpicture} \input{memory.tex} \renewcommand\MemCellWidth {1.7} \DrawMemArrayOpenRight{0}{11} \DrawMemVariable[gray!50]{0}{4}{generated by linker: jump to \_start } \DrawMemVariable[gray!50]{4}{8}{code from main.o} \DrawMemVariable[gray!50]{8}{12}{code from foo.o} \DrawMemLabel{0}{entry point} \DrawMemLabel{4}{\_start} \DrawMemLabel{8}{foo} \DrawMemLabel{10}{bar} \DrawMemPointer{2}{4} \DrawMemPointer[red!50]{5}{8} \DrawMemPointer[blue!50]{6}{10} \MarginLeft{-1}{0} \end{tikzpicture} -------------------------------------------------------------------------------- or by ---- TIKZ ---------------------------------------------------------------------- \begin{tikzpicture} \input{memory.tex} \renewcommand\MemCellWidth {1.7} \DrawMemArrayOpenRight{0}{11} \DrawMemVariable[gray!50]{0}{4}{generated by linker: jump to \_start } \DrawMemVariable[gray!50]{4}{8}{code from foo.o} \DrawMemVariable[gray!50]{8}{12}{code from main.o} \DrawMemLabel{0}{entry point} \DrawMemLabel{8}{\_start} %\begingroup %\renewcommand\MemLabelHeight {1.0} \DrawMemLabel{4}{foo} \DrawMemLabel{6}{bar} %\par\endgroup \begingroup \renewcommand\MemPointerAXF{0.55} \renewcommand\MemPointerBXF{0.35} \renewcommand\PointerDisplaceY {1.5} \DrawMemPointerAbove{2}{8} \par\endgroup \DrawMemPointer[red!40]{10}{4} \DrawMemPointer[blue!40]{11}{6} \MarginLeft{-1}{0} \end{tikzpicture} -------------------------------------------------------------------------------- Why you have to do more then just add a `_start` label ------------------------------------------------------ The logical entry point of our program is the first instruction in `main.s`. However, just adding the label `_start` in `main.s` there will not solve the problem + ----- CODE (file=session11/ex02/main.s,fold) --------------------------------- .text _start: // do something putc 'm' putc 'a' putc 'i' putc 'n' putc '\n' // call some functions ldzwq foo, %4 jmp %4, %3 // call some functions ldzwq bar, %4 jmp %4, %3 // do something after the call putc 'M' putc 'A' putc 'I' putc 'N' putc '\n' halt 0 ------------------------------------------------------------------------------ + ----- CODE (file=session11/ex02/foo.s,fold) ---------------------------------- .text foo: // do something in foo putc 'f' putc 'o' putc 'o' putc '\n' // return jmp %3, %0 .text bar: // do something in foo putc 'b' putc 'a' putc 'r' putc '\n' // return jmp %3, %0 ------------------------------------------------------------------------------ From the linker we still get the same error message: ---- SHELL (path=session11/ex02) ----------------------------------------------- ulmas -o main.o main.s ulmas -o foo.o foo.s ulmld -o main main.o foo.o -------------------------------------------------------------------------------- This seems to be wires as looking at the symbol table of `main.o` clearly shows that a symbol `_start` exists: ---- SHELL (path=session11/ex02) ----------------------------------------------- sed -n '/^#FIXUPS/q;/^#SYMTAB/,$p' main.o -------------------------------------------------------------------------------- Here another detail becomes relevant, symbols can be local or global. By default each symbol defined in the assembly code is local and the linker ignores all local symbols. The reason for that is to make it more manageable to avoid naming conflicts. Most symbols are the result of labels used to implement control structures (like loops). It is already challenging enough to choose here unique identifiers for the scope of translation unit. It would not be feasible to require uniqueness between different units. Furthermore, it reduces the amount of symbols the linker has to consider, and for the above reasons the job of the linker has to be as simple as possible. Whether symbols are local or global can be directly inferred from the symbol tables. If symbol type is indicated by a lowercase letter (like here with '`t`') it is local, otherwise global. In general the type tags of the symbol table have the same meaning as specified for the output of Unix utility __nm__. In order to define global symbols the directive `.globl ` has to be used. So for a working example we apply the following modification: + ----- CODE (file=session11/ex03/main.s,fold) --------------------------------- .text .globl _start _start: // do something putc 'm' putc 'a' putc 'i' putc 'n' putc '\n' // call some functions ldzwq foo, %4 jmp %4, %3 // call some functions ldzwq bar, %4 jmp %4, %3 // do something after the call putc 'M' putc 'A' putc 'I' putc 'N' putc '\n' halt 0 ------------------------------------------------------------------------------ + ----- CODE (file=session11/ex03/foo.s,fold) ---------------------------------- .text .globl foo foo: // do something in foo putc 'f' putc 'o' putc 'o' putc '\n' // return jmp %3, %0 .text .globl bar bar: // do something in foo putc 'b' putc 'a' putc 'r' putc '\n' // return jmp %3, %0 ------------------------------------------------------------------------------ With the we finally get an executable from the linker: ---- SHELL (path=session11/ex03) ----------------------------------------------- ulmas -o main.o main.s ulmas -o foo.o foo.s ulmld -o main main.o foo.o ulm main -------------------------------------------------------------------------------- Also checkout the difference in the symbol tables of `main.o` and `foo.o`: ---- SHELL (path=session11/ex03) ----------------------------------------------- sed -n '/^#FIXUPS/q;/^#SYMTAB/,$p' main.o sed -n '/^#FIXUPS/q;/^#SYMTAB/,$p' foo.o -------------------------------------------------------------------------------- How linkage was done in this example ------------------------------------ In general the executable produced by the linker consists of a text, data and bss segment where each segment is a linkage of corresponding segments found in the object files. In our example we only have text segments. In a first step these just get get appended to the code produced by the linker for jumping to the logical entry point. The memory layout of this intermediate result can be described by ---- TIKZ ---------------------------------------------------------------------- \begin{tikzpicture} \input{memory.tex} \renewcommand\MemCellWidth {1.7} \DrawMemArrayOpenRight{0}{12} \DrawMemVariable[gray!50]{0}{5}{code from linker} \DrawMemVariable[gray!50]{5}{9}{code from main.o} \DrawMemVariable[gray!50]{9}{13}{code from foo.o} \DrawMemLabel{0}{entry point} \DrawMemLabel{5}{\_start} \DrawMemLabel{9}{foo} \DrawMemLabel{11}{bar} %\DrawMemPointer{2}{4} %\DrawMemPointer[red!50]{5}{8} %\DrawMemPointer[blue!50]{6}{10} \MarginLeft{-1}{0} \end{tikzpicture} -------------------------------------------------------------------------------- When you look at the code of `main.o` you see that the assembler decoded the instructions with the undefined symbols `foo` and `bar` as follows: ---- SHELL (path=session11/ex01) ----------------------------------------------- egrep "ldzwq.*(foo|bar)" main.o -------------------------------------------------------------------------------- The fields `X` and `Y` of the `ldzwq` instruction contain a zero bit pattern. The linker has to patch this instructions with the actual addresses of `foo` and `bar`. And also in its own code the linker has to patch the instructions for loading the address of `_start` analogously. In a picture we can indicate what bytes are relevant for fixing the code as follows: ---- TIKZ ---------------------------------------------------------------------- \begin{tikzpicture} \input{memory.tex} \renewcommand\MemCellWidth {1.7} \DrawMemArrayOpenRight{0}{12} \DrawMemVariable[gray!50]{0}{5}{} \DrawMemVariable[gray!50]{5}{9}{} \DrawMemVariable[gray!50]{9}{13}{} \DrawMemVariable[gray!20]{0.25}{0.75}{} \DrawMemVariable[gray!20]{1.25}{1.75}{} \DrawMemVariable[gray!20]{2.25}{2.75}{} \DrawMemVariable[gray!20]{3.25}{3.75}{} \DrawMemVariable[gray!20]{6.25}{6.75}{} \DrawMemVariable[gray!20]{7.25}{7.75}{} \DrawMemLabel{0}{entry point} \DrawMemLabel{5}{\_start} \DrawMemLabel{9}{foo} \DrawMemLabel{11}{bar} %\DrawMemPointer{2}{4} %\DrawMemPointer[red!50]{5}{8} %\DrawMemPointer[blue!50]{6}{10} \MarginLeft{-1}{0} \end{tikzpicture} -------------------------------------------------------------------------------- The linker gets the information relevant for patches from the relocation table (`#FIXUPS`). For example, in `main.o` you find: ---- SHELL (path=session11/ex03) ----------------------------------------------- sed -n '/^#FIXUPS/,$p' main.o -------------------------------------------------------------------------------- The format of each line basically describes "Where to fix" and "How to fix" in more detail: ---- CODE (type=txt) -----------------------------------------------------------
-------------------------------------------------------------------------------- Let's approach these details by example. The code of the linker consists of 5 instructions or 20 bytes. So the address where the text segment of `main.o` will be located is 0x14. The first patch needs to be applied to the instruction which had within `main.o` the address 0x14. As the linker relocated the text segment of `main.o` to address 0x14 this instruction now has address 0x28. At address 0x28 two bytes with an offset of one byte need to be patched, this refers to the bytes with address 0x29 and 0x2A . This answers the question "where to fix". And you see in `main` that actually these two bytes where modified: ---- SHELL (path=session11/ex03) ----------------------------------------------- egrep "ldzwq.*foo" main -------------------------------------------------------------------------------- Next we can reverse engineer how the bytes where patched. The symbol `foo` is defined in `foo.o`: ---- SHELL (path=session11/ex03) ----------------------------------------------- sed -n '/^#FIXUPS/q;/^#SYMTAB/,$p' foo.o -------------------------------------------------------------------------------- It has the type _text_ and value zero. That means it refers to the "address of the text segment of `foo.o` plus zero", and the linker located the text segment of `foo.o` at address 0x50. Analogously you can checkout that instruction ---- SHELL (path=session11/ex03) ----------------------------------------------- egrep "ldzwq.*bar" main.o -------------------------------------------------------------------------------- was patched with ---- SHELL (path=session11/ex03) ----------------------------------------------- egrep "ldzwq.*bar" main -------------------------------------------------------------------------------- Applying all patches can be described as adding these pointers to the pictures describing the memory layout: --- TIKZ ---------------------------------------------------------------------- \begin{tikzpicture} \input{memory.tex} \renewcommand\MemCellWidth {1.7} \DrawMemArrayOpenRight{0}{12} \DrawMemVariable[gray!50]{0}{5}{} \DrawMemVariable[gray!50]{5}{9}{} \DrawMemVariable[gray!50]{9}{13}{} \DrawMemVariable[gray!20]{0.25}{0.75}{} \DrawMemVariable[gray!20]{1.25}{1.75}{} \DrawMemVariable[gray!20]{2.25}{2.75}{} \DrawMemVariable[gray!20]{3.25}{3.75}{} \DrawMemVariable[gray!20]{6.25}{6.75}{} \DrawMemVariable[gray!20]{7.25}{7.75}{} \DrawMemLabel{0}{entry point} \DrawMemLabel{5}{\_start} \DrawMemLabel{9}{foo} \DrawMemLabel{11}{bar} \DrawMemPointer{0.1}{5} \DrawMemPointer{1.1}{5} \DrawMemPointer{2.1}{5} \DrawMemPointer{3.1}{5} \DrawMemPointer[red!50]{6.1}{9} \DrawMemPointer[blue!50]{7.1}{11} \MarginLeft{-1}{0} \end{tikzpicture} -------------------------------------------------------------------------------- Using GNU make for a build system ================================= As soon as the number of translation units grows it is not convenient to manual translate the source files and link the object files. Instead a build system can be used to automatize the process. Such a system should be aware that for a source file an object file needs to be created only once, and recreated only if the source file was modified. Also linkage is only required if an object file was modified or new object files become relevant for creating the target. While there are many build system available, and acclaimed to be superior, we will use __GNU make__ and initially only its most essential features. Dependencies and action rules ----------------------------- For motivating notations and terminologies the flowchart for building our toy example gets rewritten as follows: --- TIKZ ----------------------------------------------------------------------- \begin{tikzpicture} \input{flowchart.tex} \renewcommand\BoxWidth {5} \renewcommand\FlowColDist {3} \renewcommand\BoxHeight {1.2} \renewcommand\BoxDistance {1.8} \SetMargin{1}{2}{1}{3} \renewcommand\FlowCol {0} \PutStatement{0}{main.s } \PutStatement{1}{main.o} \renewcommand\FlowCol {2} \PutStatement{0}{foo.s } \PutStatement{1}{foo.o} \renewcommand\FlowCol {1} \PutStatement{3}{main} \AddDepPath{0}{1}{0}{0}{depends on} \AddDepPath{2}{1}{2}{0}{depends on} \AddDepPath{1}{3}{0}{1}{} \AddDepPath[above=0.7cm]{1}{3}{2}{1}{depends on} \end{tikzpicture} -------------------------------------------------------------------------------- This chart can be interpreted and read as follows: - The executable `main` depends on the object files `main.o` and `foo.o`. The linker generates `main` from these objects, and `main` has to be regenerate it if `main.o` or `foo.o` was modified. - The object file `main.o` depends on `main.s`. The assembler generates `main.o` from `main.s`, and `main.o` has to be regenerated if `main.s` gets changed. - Analogously, the object file `foo.o` depends on `foo.s`, i.e. it gets generated or regenerated depending on `foo.s`. This directed graph and action rules for generating files can be described in _makefiles_ that can be interpreted by the `make` command: :import: session11/ex03/Makefile This file consists of a sequence of rules that have to following format: ---- CODE (type=txt) ----------------------------------------------------------- : -------------------------------------------------------------------------------- For example ---- CODE (type=txt) ----------------------------------------------------------- main.o : main.s ulmas -o main.o main.s -------------------------------------------------------------------------------- It is important to point out that the indenting of the command needs to be done with at least on tab character (make sure your editor does not expand them with spaces). How make works -------------- When you run the `make` command it will first search in the current directory for the following files, and in this order: GNUmakefile, makefile and Makefile. The first file, and only this, gets executed by applying the first rule found in his file. In the above makefile that would be ---- CODE (type=txt) ----------------------------------------------------------- all : main -------------------------------------------------------------------------------- Applying a rule consists of two steps: - If a rule for a dependence exists this rule gets applied first. Hence applying rules happens recursively. Applying the first rule requires that rule ---- CODE (type=txt) --------------------------------------------------------- main : main.o foo.o ulmld -o main main.o foo.o ------------------------------------------------------------------------------ was applied beforehand. Which in turn requires that the rules ---- CODE (type=txt) --------------------------------------------------------- main.o : main.s ulmas -o main.o main.s ------------------------------------------------------------------------------ and ---- CODE (type=txt) --------------------------------------------------------- foo.o : foo.s ulmas -o foo.o foo.s ------------------------------------------------------------------------------ were applied beforehand. And the recursion is terminated by these two rules. - After eventual rules were applied `make` checks if the command for generating/updating the target needs to be executed. For that `make` simply checks if the target does not exist or has a modification time that is older than any of the dependent files. Let's begin with a clean sheet: ---- SHELL (path=session11/ex03) ----------------------------------------------- rm -f *.o main -------------------------------------------------------------------------------- Running `make` now causes to generate all object files and then the executable: ---- SHELL (path=session11/ex03) ----------------------------------------------- make -------------------------------------------------------------------------------- Running `make` again will only trigger the first rule for `all` which has no action command: ---- SHELL (path=session11/ex03) ----------------------------------------------- make -------------------------------------------------------------------------------- With the command __`touch`__ we can update the modification time of a file to _now_ without modifying its content: ---- SHELL (path=session11/ex03) ----------------------------------------------- touch main.s -------------------------------------------------------------------------------- Now `make` thinks that `main.s` has changed and `main.o` needs an update: ---- SHELL (path=session11/ex03) ----------------------------------------------- make -------------------------------------------------------------------------------- Deleting `main.o` will trigger its generation and the linkage of the executable: ---- SHELL (path=session11/ex03) ----------------------------------------------- rm main.o make -------------------------------------------------------------------------------- Deleting only the executable will trigger the linkage only: ---- SHELL (path=session11/ex03) ----------------------------------------------- rm main make -------------------------------------------------------------------------------- Quiz 13: Break bubble sort into pieces! ======================================= Take the program `bubblesort` from session 10, i.e. :import: session10/func/bubblesort.s [fold] and split it into source files as follows: - Source file `crt0.s` (see here why it is called: __crt0__) only contains the code block with label `_start` for initializing the stack, - `main.s` contains only the function `main` and the global variable for the string, - `puts.s` contains only the function `puts`, - `strlen.s` contains only the function `strlen` and - `bubblesort.s` contains only the function `bubblesort`. Also write a makefile for generating and updating needed obeject files and an executable `test_bubble`. On `theon` submit the files with `submit hpc quiz13 crt0.s main.s puts.s strlen.s bubblesort.s` If you don't get any message it means there was no error. Btw: It is certainly overkill but just for fun you can use your makefile to translate the assembly files in __parallel__. #:links: shebang -> https://en.wikipedia.org/wiki/Shebang_(Unix) #:links: `chmod` -> https://man7.org/linux/man-pages/man1/chmod.1.html #phony -> https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html :links: `touch` -> https://man7.org/linux/man-pages/man1/touch.1.html GNU make -> https://www.gnu.org/software/make/manual/html_node/index.html#Top Session 9 -> doc:session09/page01 nm -> https://man7.org/linux/man-pages/man1/nm.1p.html#STDOUT crt0 -> https://en.wikipedia.org/wiki/Crt0 parallel -> https://www.gnu.org/software/make/manual/html_node/Parallel.html#index-parallel-execution