***************************** From the IR to the executable ***************************** This document aims to describe how to obtain a working executable from the IR generated by `revamb`. This for documentation purposes only, standard users can simply use the `translate` script, which will take care of everything, From the IR to the object file ============================== As an optional step, before proceeding in compiling the IR, it's possible to run the optimization pipeline on the generated IR using the LLVM `opt` tool: .. code-block:: sh opt -O2 -S translated.ll -o translated.opt.ll Depending on the size of the input program this might take some time. The first step to obtain the final executable consists in compiling the IR. To do this, we can use `llc`, the LLVM static compiler, which takes an LLVM IR file and produces an object file: .. code-block:: sh llc -O0 translated.ll -filetype=obj -o translated.o `llc` exposes the compiler backend. The backend can do another series of optimizations: .. code-block:: sh llc -O2 translated.ll -filetype=obj -o translated.o -regalloc=fast Note: as for `opt`, optimizations can take a considerable amount of time, in particular if slow register allocation methods are used (i.e., make sure to use `-regalloc=fast`). Non-trivial register allocation techniques on the `root` function can be prohibitively costly (see also `GeneratedIRReference.rst`_). Support functions ================= The IR produced by `revamb` is mostly self-contained, since all the QEMU helper functions are statically linked in the output module (unless ``--no-link`` was specified, in which case the module won't be functional). Howver, besides the QEMU helper functions, some additional support functions are required to obtain a working program, in particular for initialization purposes. `support.c` provides the default implementation for them. In the following we provides a quick summary of the required functions as they are implemented in `support.c`: :main: The entry point of the program, maps in memory the stack and the heap. It also performs the stack initialization, which is a very delicate operation. As it can be seen in the `prepare_stack` function the environment variables, the arguments and the auxiliary vectors are initialized mimicing the stack initialization performed by the Linux kernel. `main` also performs a call to `syscall_init` a function defined in the generated module, taken from QEMU, which carries on some syscall-related initializations. Finally, the entry point of the module generated by `revamb` (the `root` function) is called. :path: Function invoked by the QEMU Linux syscall emulation layer to *filter* the path being opened by the translated program. For instance, it is possible to catch attempts to open ``/proc/self/map`` and redirect it somewhere else transparently. :unknownPC: Function handling the situation in which the code at an address that should be executed has not been translated (see `GeneratedIRReference.rst`_). It is basically an error handling function that is supposed to never return. :newpc: If the ``--tracing`` option has been given, the calls to the `newpc` functions emitted by `revamb` are turned into calls to an external function, that the user can implement. This function can access the status of the CPU and perform high performance tracing. The remaining functions in `support.c` are of little interest and might be removed in the future. Note also that `support.c` works automatically with all the supported architectures, but needs to know which was the input architecture. For this reason, defined a macro ``TARGET_arch`` (e.g., ``TARGET_arm``) on the compilation command line. Linking ======= Once we have the object file generated by `revamb` we can compile `support.c` and link them together. Be sure to link also `libm.so`, `libz.so` and `librt.so`. In addition to this, we also have to tell the linker to force the *segment variables* (see `GeneratedIRReference.rst`_) at the appropriate location in memory. In fact, each segment of the original binary should be loaded exactly at the same address where it was originally supposed to be loaded. `revamb` emits the required mapping automatically in a file with the same name as the output file plus a ``.li.csv`` suffix. This file is a CSV file composed by three columns: :name: The name of the section in the generated module containing the data of the considered segment. :start: Start address where the segment originally was located and, therefore, where it should be placed by the linker. :end: Corresponding end address. The `li-csv-to-ld-options` script converts this CSV file into parameters for the linker to enforce the location of this sections. As a result of this operation, the actual translated code might end up in an unusual location, but the linker should be able to figure this out. In conclusion, to link the final program: .. code-block:: sh gcc $(li-csv-to-ld-options translated.ll.li.csv) \ translated.o \ support.c \ -DTARGET_arm \ -lz -lm -lrt -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast -g \ -o translated.elf .. _`GeneratedIRReference.rst`: GeneratedIRReference.rst