================ Static libraries ================ So far each test program was linked against all the object files from our library. This means that the executables in general contain code that is never used. For example, the program `xhello` contained the code from `putui.o`. We will overcome this problem by creating a __static library__ `libulm.a`: - Using the Unix command __`ar`__ (note that we are using the "real thing", not some reimplemented tool for the ULM) all object files will be bundled in a single file `libulm.a`. - A second tool `ulmranlib` (which is an adaption of the Unix command __`ranlib`__) creates an index for the archive so that the linker can only pick those object file that needed to resolve undefined symbols. The build system needs to be able to update the library. That means if new source files are added the corresponding object files need to be created and added. If source files are deleted (or renamed) the have to be removed. ---- TIKZ ---------------------------------------------------------------------- \begin{tikzpicture} \input{flowchart.tex} \renewcommand\BoxWidth {2} \renewcommand\FlowColDist {3} \renewcommand\BoxHeight {1.2} \renewcommand\BoxDistance {1.8} \SetMargin{1}{12}{1}{3} \Group{0}{0}{1}{4}{Executables } \Group{2}{0}{4}{4}{Library} \renewcommand\FlowCol {0} \PutStatement{0}{xhello.s } \PutStatement{1}{xhello.o} \PutStatement{4}{xhello} \AddDepPath{0}{1}{0}{0}{} \AddDepPath{0}{4}{0}{1}{} \renewcommand\FlowCol {1} \PutStatement{0}{xanswer.s } \PutStatement{1}{xanswer.o} \PutStatement{4}{xanswer} \AddDepPath{1}{1}{1}{0}{} \AddDepPath{1}{4}{1}{1}{} \renewcommand\FlowCol {2} \PutStatement{0}{crt0.s } \PutStatement{1}{crt0.o} \AddDepPath{2}{1}{2}{0}{} \renewcommand\FlowCol {3} \PutStatement{0}{puts.s } \PutStatement{1}{puts.o} \AddDepPath{3}{1}{3}{0}{} \PutStatement{2.5}{libulm.a} \renewcommand\FlowCol {4} \PutStatement{0}{putui.s } \PutStatement{1}{putui.o} \AddDepPath{4}{1}{4}{0}{} \AddDepPath{3}{2-5}{2}{1}{} \AddDepPath{3}{2-5}{3}{1}{} \AddDepPath{3}{2-5}{4}{1}{} \AddDepPath{0}{4}{3}{2-5}{} \AddDepPath{1}{4}{3}{2-5}{} \end{tikzpicture} -------------------------------------------------------------------------------- Using `ar` for creating and maintaining an archive ================================================== Although `ar` is nowadays almost exclusively used for static libraries that is just one possible application for this command. Here you will see some examples for adding files to an archive, as well as extracting and removing files from an archive. Think of creating backups as an alternative use case, or that you want to send several files by email which beforehand get combined into a single file (note that `ar` is much older than `zip` which is also doing compression). --- SHELL (path=session11/ar, hide) -------------------------------------------- rm * -------------------------------------------------------------------------------- Creating an archive for two files --------------------------------- Files are not compressed by `ar` when you add them. So you can use an editor or `cat` to see the content of an archive. Here an example for two text files that will be added to an archive: ---- CODE (file=session11/ar/file1) -------------------------------------------- This is 'file1'. Just a simple text file. -------------------------------------------------------------------------------- ---- CODE (file=session11/ar/file2) -------------------------------------------- This is 'file2'. Just another text file. -------------------------------------------------------------------------------- In the following `ar` is called with option `c`, `r` and `u` to add `file1` and `file2` to an archive named `my_archive`: --- SHELL (path=session11/ar) -------------------------------------------------- ar cru my_archive file1 file2 cat my_archive -------------------------------------------------------------------------------- The options are explained in more detail in the man page of __`ar`__. Here some brief version: - Because of `c` you don't get a warning if `my_archive` does not already exist. But even without this option an archive always gets created if it does not already exist. - From the content of `my_archive` you can see that `ar` also stores the names of the archived files. Using the option `r` means that if you add a file whose filename is already in the archive this entry gets overwritten. - With `u` such an existing entry gets only gets replaced if you add a file that is newer. # Replacing files # --------------- # Let's think of `ar` as a tool for creating a backup of some files. We use # `touch` to simulate that a file was modified and therefore needs to be # replaced in the archive. The addition option `v` for _verbose_ is used so that # we get some feedback what actually gets done by `ar`: # # --- SHELL (path=session11/ar) -------------------------------------------------- # echo "Add some text" >> file2 # -------------------------------------------------------------------------------- # # :import: session11/ar/file2 # # --- SHELL (path=session11/ar) -------------------------------------------------- # ar cru my_archive file1 file2 # cat file2 # cat my_archive # -------------------------------------------------------------------------------- # # :import: session11/ar/file2 # Add another file to the archive ------------------------------- Let's add another file to the archive: ---- CODE (file=session11/ar/file3) -------------------------------------------- This is 'file3'. And I am running out of ideas for some demo text. -------------------------------------------------------------------------------- You just specify to which archive this file gets added: --- SHELL (path=session11/ar) -------------------------------------------------- ar cru my_archive file3 cat my_archive -------------------------------------------------------------------------------- Get a table of content ---------------------- With the option `t` you can get a table of files stored in an archive: --- SHELL (path=session11/ar) -------------------------------------------------- ar t my_archive -------------------------------------------------------------------------------- Extracting a file from an archive --------------------------------- In conjunction with static libraries we never have to extract files from an archive (the linker internally has to do that). But nevertheless you can do that. So here we delete an archived file and restore it afterwards from the archive: --- SHELL (path=session11/ar) -------------------------------------------------- ls rm file1 ls ar x my_archive file1 ls cat file1 -------------------------------------------------------------------------------- Deleting a file from an archive ------------------------------- With the option `d` a member of the archive can be deleted: --- SHELL (path=session11/ar) -------------------------------------------------- ar d my_archive file1 ar t my_archive -------------------------------------------------------------------------------- ulmranlib: Creating an index for an archive =========================================== Before we consider makefiles for automating we manually build a static library. In this connection also the `ulmranlib` command will be introduced. ---- SHELL (path=session11/mini-lib, hide) ------------------------------------- rm *.o libulm.a -------------------------------------------------------------------------------- In a first step all object files are created and added to the archive `libulm.a`: ---- SHELL (path=session11/mini-lib) ------------------------------------------- ulmas -o crt0.o crt0.s ulmas -o putui.o putui.s ulmas -o puts.o puts.s ar cru libulm.a crt0.o putui.o puts.o -------------------------------------------------------------------------------- This result could already be used to create an executable. You simply pass it to the linker like an object file: ---- SHELL (path=session11/mini-lib) ------------------------------------------- ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o libulm.a xhello -------------------------------------------------------------------------------- In its current form the ULM linker also treats `libulm.a` as a sequence of object files. So while we have to pass fewer files to the linker we still have the problem that an executable contains code for unused object files. In the case of `xhello` you can see that it still contains the code from `putui.o`: ---- SHELL (path=session11/mini-lib) ------------------------------------------- grep libulm xhello -------------------------------------------------------------------------------- In order to pick only object files that are needed in order to resolve undefined symbols the archive needs an addition entry that gets created by `ulmranlib` (which as mention above resembles __`ranlib`__). The command expects an archive as argument for which it creates and add such an entry: ---- SHELL (path=session11/mini-lib) ------------------------------------------- ulmranlib libulm.a -------------------------------------------------------------------------------- This created the archive member `__SYMTAB_INDEX`: ---- SHELL (path=session11/mini-lib) ------------------------------------------- ar t libulm.a -------------------------------------------------------------------------------- In this form `libulm.a` finally can be called a static library. As you see this is just a table with entries about what symbols are defined in the stored object files: ---- SHELL (path=session11/mini-lib) ------------------------------------------- ar x libulm.a __SYMTAB_INDEX cat __SYMTAB_INDEX rm __SYMTAB_INDEX -------------------------------------------------------------------------------- If the ULM linker find such an index in the archive it gets used to extract only those object files that resolve at least one unresolved symbol. Check the executable generated in the following to see that it no longer contains code from `putui.o`: ---- SHELL (path=session11/mini-lib) ------------------------------------------- ulmas -o xhello.o xhello.s ulmld -o xhello xhello.o libulm.a grep libulm xhello -------------------------------------------------------------------------------- In general adding another object file can lead to further undefined symbols. For example, if in an object file a function from another object file gets called. Therefore each time an object file was added the linker checks if more object files can be extracted to resolve at least one unresolved symbol. Makefile ======== In a first approach we make some minor modification to the previous makefile. The name of the library is stored in variable `Lib`: ---- CODE (type=txt) ----------------------------------------------------------- Lib := libulm.a -------------------------------------------------------------------------------- and this becomes in addition to the test programs a primary target: ---- CODE (type=txt) ----------------------------------------------------------- all: $(TestTargets) $(Lib) -------------------------------------------------------------------------------- Each test program now depends on this library instead of the list of object files in `LibObjects`: ---- CODE (type=txt) ----------------------------------------------------------- $(TestTargets): % : %.o $(Lib) $(LD) -o $@ $^ -------------------------------------------------------------------------------- This list of object files defines now the dependences for the library which gets created by `ar` and `ulmranlib`: ---- CODE (type=txt) ----------------------------------------------------------- $(Lib): $(LibObjects) ar cru $@ $^ ulmranlib $@ -------------------------------------------------------------------------------- Altogether this leads to: :import: session11/make08/Makefile Now let's test that the build system only updates what is necessary: - First we build everything from scratch: ---- SHELL (path=session11/make08) --------------------------------------------- make clean make -------------------------------------------------------------------------------- - Next we simulate that some source file that is part of the library, e.g. `puts.s` gets modified: ---- SHELL (path=session11/make08) --------------------------------------------- touch puts.s make -------------------------------------------------------------------------------- Only for `puts.s` the object file was regenerated, the library updated and the test programs rebuild by linking the existing object files. - If the source file of a test program gets modified only its object file gets generated and linked against the library: ---- SHELL (path=session11/make08) --------------------------------------------- touch xhello.s make -------------------------------------------------------------------------------- This makefile still has some minor flaw. If you rename or delete a source file from the library its object code does not get removed from the library. In its current form our build system would require that you manually delete `libulm.a`. More tweaks for the makefile ============================ The following makefile takes care of renamed or deleted source files (you don't have to understnad all the details but I hope it encourages you to learn more about GNU make): :import: session11/make09/Makefile The makefiles uses GNU make's features for rules that address __archive members__, __conditional functions__ and the __shell function__. The details will not be covered here (but you can read them up in the documentation). Let's just demonstrate that it actually solves the problem mentioned above. We begin by building everything from scratch: ---- SHELL (path=session11/make09) --------------------------------------------- make clean make -------------------------------------------------------------------------------- The archive contains the objects we expect ---- SHELL (path=session11/make09) --------------------------------------------- ar t libulm.a -------------------------------------------------------------------------------- Now let's rename some file from the library. For example, `puts.s` becomes `new_puts.s`: ---- SHELL (path=session11/make09) --------------------------------------------- mv puts.s new_puts.s make -------------------------------------------------------------------------------- The object file `puts.o` was removed, and `new_puts.o` was generated with the assembler and inserted: ---- SHELL (path=session11/make09) --------------------------------------------- ar t libulm.a -------------------------------------------------------------------------------- Also note that all executables were generated by relinking only. ---- SHELL (path=session11/make09,hide) ---------------------------------------- mv new_puts.s puts.s make -------------------------------------------------------------------------------- :links: static library -> https://en.wikipedia.org/wiki/Static_library `ar` -> https://man7.org/linux/man-pages/man1/ar.1.html `ranlib` -> https://man7.org/linux/man-pages/man1/ranlib.1.html archive members -> https://www.gnu.org/software/make/manual/html_node/Archives.html conditional functions -> https://www.gnu.org/software/make/manual/html_node/Conditional-Functions.html#Conditional-Functions shell function -> https://www.gnu.org/software/make/manual/html_node/Shell-Function.html#Shell-Function