linker.html   [plain text]


  Inside the Linker 
<div class="doc_author">
  <p>Written by <a href="">Nick Kledzik</a></p>

  <a name="introduction">Introduction</a>

<p>The Darwin linker is a new generation of linker.  It is not "section" based
like traditional linkers which mostly just interlace sections from multiple
object files into the output file.  The Darwin linker is based on "Atoms".
Traditional section based linking work well for simple linking, but their model
makes advanced linking features difficult to implement.  Features like dead code 
stripping, reordering functions for locality, and C++ coalescing require the
linker to work at a finer grain.

<p>An atom is an indivisible chunk of code or data.  An atom has a set of
attributes, such as: name, scope, content-type, alignment, etc.  An atom also
has a list of Fixups.  A Fixup contains: a kind, an optional offset, an optional
addend, and an optional target atom.</p>

<p>The Atom model allows the linker to use standard graph theory models for 
linking data structures.  Each atom is a node, and each Fixup is an edge. 
The feature of dead code stripping is implemented by following edges to mark
all live atoms, and then delete the non-live atoms.</p>
  <a name="Atom model">Atom model</a>

<p>An atom is an indivisible chuck of code or data.  Typically each user
written function or global variable is an atom.  In addition, the compiler may
emit other atoms, such as for literal c-strings or floating point constants, or
for runtime data structures like dwarf unwind info or pointers to initializers.

<p>A simple "hello world" object file would be modeled like this:</p>
<img src="hello.png" alt="hello world graphic"/>
<p>There are two atoms: main and an anonymous atom containing the c-string
literal "hello world".  The Atom "main" has two fixups.  One is the call site
for the call to printf, and the other is a fixup for the instruction that loads
the address of the c-string literal. </p>

  <a name="File model">File model</a>

<p>The linker views the input files as basically containers of Atoms and Fixups,
 and just a few attributes of their own.  The linker works with three kinds
of files: object files, static libraries, and dynamic libraries.  Each kind
of file has reader object which presents the file in the model expected by 
the linker.</p>
<h4> <a>Object File</a> 
An object file is just a container of atoms.  When linking with
an object file, all atoms are added to the initial graph of atoms.

<h4> <a>Static Library (Archive)</a> 
This is the traditional unix static archive which is just a collection of
object files with a "table of contents". When linking with a static library,
by default nothing is added to the initial graph of atoms. Instead, if there
are unresolved references (dangling edges) in the master graph of all atoms, 
and the table of contents for a static library says that one of the object files 
in the library defines one of the missing symbols (dangling edge), 
the set of atoms from the specified object file in the static library is added 
to the master graph of atoms. 

<h4> <a>Dynamic Library (Shared Object)</a> 
Dynamic libraries are unique in that the don't directly add add any atoms.  
Their purpose is to check at build time that all references are resolved and
provide a list of dynamic libraries (SO_NEEDED) that will be needed at runtime. 
The way this is modeled in the linker is that a dynamic library contributes
no atoms to the initial graph of atoms.  Instead, (like static libraries) if
there are unresolved references (dangling edges) in the master graph of all atoms, 
if a dynamic library exports a required symbol, then a "proxy" atom is 
instantiated by the linker.  The proxy atom allows the master atom graph to have
all edges resolved and also records from which dynamic library a symbol came.</p>

  <a name="Linking Steps">Linking Steps</a>
<p>Through the use of abstract Atoms, the core of linking is architecture 
independent and file format independent.  All command line parsing is factored
out into a separate "options" abstraction which enables the linker to be driven
with different command line sets.</p>
<p>The overall steps in linking are:<p>
  <li>Command line processing</li>
  <li>Parsing input files</li>
  <li>Generate output file</li>

<p>The Resolving and Passes steps are done purely on the master graph of atoms, 
so they have no notion of file formats such as mach-o or ELF.</p>

<h4> <a>Resolving</a> 
<p>The resolving step takes all the atoms graphs from each object file and 
combines them into one master object graph.  Unfortunately, it is not as simple
as appending the atom list from each file into one big list.  There are many
cases where atoms need to be coalesced.  That is, two or more atoms need to 
be coalesced into one atom.  This is necessary to support: C language
 "tentative definitions", C++ weak symbols for templates and inlines defined
in headers, and for merging copies of constants like c-strings and floating
point constants.</p>

<p>The linker support coalescing by-name and by-content. By-name is used for
tentative definitions and weak symbols.  By-content is used for constant data
that can be merged. </p>

<p>When one atom has a reference (FixUp) to another atom, there is also a binding
type: by-name, direct, or indirect. A Fixup contains a tagged union that if
the binding type is by-name, the union field is a pointer to a c-string.  If
the binding type is direct, the union is a pointer to an Atom.  If the binding
type is indirect, the union is a index into a table of pointers to Atoms. Below
is a graphical representation of the binding types:</p>
<img src="bindings.png" alt="binding types graphic"/>

<p>Input file Atoms contain only direct and by-name references.  Direct 
references are used for atoms defined in the same object file for which the 
target atom is either unnamed or cannot change.  For instance, calling 
a static function in a translation unit will result in a direct reference 
to the static functions's atom.  Also the FDE (dwarf unwind info) for a function
has a direct reference to its function.  On the other hand references to 
global symbols (e.g. call to printf) use by-name binding in object files.

<p>The resolving process maintains some global linking "state", including:
a "symbol table" which is a map from c-string to Atom*, an indirect symbol
table which is a growable array of Atom*, and for each kind of coalesable
constants there is a content to Atom* map.  With these data structures,
the linker walks all atoms in all input files. For each
atom, it checks if the atom should be in one symbol table or one of the 
coalescing tables.  If so, it attempts to add the atom.  If there already is
a matching atom in that table, that means the current atom needs to be 
coalesced with the found atom.  

<p>To support coalescing, all references to coalesable atoms are changed to
indirect binding and an entry is added to the indirect table which points
to the current chosen atom.  When all input atoms have been processed by
the resolver, there should be only direct and indirect bindings left.  If
there are any NULL entries in the indirect table, that means there are  
undefined references.  The linker then looks to the supplied libraries (both
static and dynamic) to resolve those references.  

<p>Dead code stripping (if requested) is done at the end of resolving.  The
linker does a simple mark-and-sweep. It starts with "root" atoms (like "main"
in a main executable) and follows each references and marks each Atom that
it visits as "live".  When done, all atoms not marked "live" are removed.

<h4> <a>Passes</a> 
<p>The Passes step
is an open ended set of routines that each get a change to modify or enhance
the master graph of atoms. Passes are only run if the master graph of 
atoms is completely resolved (no dangling edges). 
The current set of Passes in the Darwin linker are:</p>
  <li>Objective-C optimizations (Apple)</li>
  <li>stub (PLT) generation</li>
  <li>GOT instantiation</li>
  <li>TLV instantiation (Apple)</li>
  <li>order_file optimization</li>
  <li>branch island generation</li>
  <li>branch shim generation</li>
  <li>dtrace probe processing (Apple)</li>
  <li>compact unwind encoding (Apple)</li>
<p>Some of these passes are specific to Apple's runtime environments.  But many
of the passes are applicable to any OS (such as generating branch island for 
out of range branch instructions).</p>

<p>The general structure of a pass is to walk the master graph inspecting each
atom and doing something.  For instance, the stub pass, walks the graph looking
for atoms with call sites to proxy atoms (e.g. call to printf).  It then
instantiates a "stub" atom (PLT entry) and a "lazy pointer" atom for each 
proxy atom needed, and these new atoms are added to the master graph.  Next
all the noted call sites to proxy atoms are replaced with calls to the 
corresponding stub atom.</p>  

<h4><a>Generate Output File</a> 
<p>Once the passes are done, the output file generator is given a sorted list
of atoms.  Its job is to create the executable content file wrapper and place
the content of the atoms into it. 

  <a name="Future Directions">Future Directions</a>

<p>The current use of sections in mach-o .o files over-constrains the linker.
By default, the linker should preserve the section an atom is in.  But since
all sections must be contiguous in the output, that limits the ability of
the linker to order atoms for locality.  It would be helpful to enrich the
object file with with reason something is in the section it is.  For instance,
is the section found at runtime? Or was the use of a section just a quick
way to group some content together?
<p>The ELF model for sections is a little better than mach-o because ELF
sections have write and execute bits, whereas mach-o sections must be in some
segment and the segment has the write and execute bits.  

<h4><a>Mach-o Object File Format</a> 
The messiest part of the linker is the mach-o parser. This is because mach-o
is a traditional section and symbols based file format.  The parser must infer
atom boundaries using two approaches.  The first is that some section types have  
well defined content which the linker can parse into atoms (e.g.  __cstring, 
__eh_frame). The other approach is a naming convention (which the compiler follows)
by which the linker breaks sections into atoms at any non-local (not starting 
with 'L') symbol. The processing the linker has to do parse mach-o .o files is a
significant part of the link time. 

<p>Given that the assembler writes object files once, whereas the linker reads
them many times (during development), it would make sense to optimize the object
file format to be something the linker can read/parse efficiently.</p>  

<h4><a>New Object File Model</a> 
<p>LLVM has a nice model for its IR.  There are three representations:
the binary bit code file, the in-memory object model, and a textual 
representation.  LLVM contains utility possible code for converting between these
representations.  The same model makes sense for atoms too.  There should be
three representations for atoms: binary file, in-memory, and textual. The Darwin 
linker already has an in-memory C++ object model for Atoms.  All we need is a 
textual representation and binary file format.
<p>Note: in the darwin linker the binary format for input object files is  
independent of the output executable format.  That is, we could have one 
universal object file format which the linker could use as input to produce 
mach-o, ELF, or PE executables.</p>
The object file binary format should be designed to instantiate into atoms
as fast as possible.  The obvious way to do that is that the 
file format would be an array of atoms.  The linker just mmaps in the file and
looks at the header to see how many atoms there and instantiate that many atoms
with the atom attribute information coming from that array.  The trick is 
designing this in a way that can be extended as the Atom mode evolves and new
attributes are added.
In designing a textual format we want something easy for humans to read and
easy for the linker to parse.  Since an atom has lots of attributes most of
which are usually just the default, we should define default values for 
every attribute so that those can be omitted from the text representation.
One possile format is YAML.  Here is the atoms for a simple hello world
program expressed in YAML.
target-triple:   x86_64-apple-darwin11

    - name:    _main
      scope:   linkage-unit
      type:    code
          power: 4
      content: [ 55, 48, 89, e5, 48, 8d, 3d, 00, 00, 00, 00, 30, c0, e8, 00, 00,
                 00, 00, 31, c0, 5d, c3 ]
      - offset: 07
        kind:   pcrel32
        target: 2
      - offset: 0E
        kind:   call32
        target: _fprintf

    - type:    c-string
      merge:   by-content
      content: [ 73, 5A, 00 ]


<p>One big use for the textual format will be writing test cases. The Darwin
linker test suite test cases are written mostly in C/C++ and a few assembly
files.  The use of C means the same test case can be compiled for different
architectures.  But writing test cases in C is problematic because the compiler 
may vary its output over time for its own optimization reasons which my 
inadvertently disable or break the linker feature trying to be tested. By 
writing test cases in the linkers own textual format, we can exactly specify 
every attribute of every atom and thus target specific linker logic.

<h4><a>Debug Info</a> 
<p>Around 2005 when Apple switched from using STABS to using DWARF for debug 
information, we made a design decision to have the linker ignore DWARF in
.o files.  This improves linking performance because the linker is not
copying tons of debug info.  Instead, the linker adds "debug notes" into
output binary that contain the paths of the original .o files. During development
the Darwin debugger will notice the debug notes and the load the dwarf
debug information from the original object files.  For release builds,
a tool named dsymutil is run on the program.  It finds the debug notes and
then the original object files, then reads, merges and optimizes all the dwarf
debug information into one .dSYM file which can be loaded by the debugger
if needed.</p>

<p>The current way DWARF is generated is that all debug information for all
functions in a translation unit are merged and optimized into sections based 
on debug info kind.  For instance the mapping of instructions to source line
numbers for all functions is compressed and put in one section. This does not
play well in an Atom based file format.  One idea is to have the compiler
emit some intermediate representation debug information (one which is 
partitioned per atom) into the Atom based file format.  The linker could 
then have code to convert that intermediate debug into to final dwarf.
This is still an open question.</p>

<h4><a>Extending Atom attributes to ELF and XCOFF</a> 
<p>The current set of attributes defined for Atoms in the darwin linker
were chosen to meet the requirements of developing code to run on iOS and 
Mac OS X.  Below is a list of the attributes and their possible values.
It may just require adding more values to support ELF and XCOFF.  Or there
may need to be new attributes added to capture new functionality.
  <li>Section (I'd like to get rid of this)</li>
  <li>ContentType (currently some of this comes from section)</li>
	  <li>translationUnit  (static functions)</li>
	  <li>linkageUnit      (visibility hidden)</li>
	  <li>tentative         (ANSI C feature)</li>
	  <li>absolute          (assembly code feature)</li>
	  <li>proxy             (stand-in for dynamic library symbol)</li>
	  <li>byName          (weak symbols)</li>
	  <li>byContent       (simple constants)</li>
	  <li>byContentAndReferences (complex constants)</li>
	  <li>notIn              (anonymous)</li>
	  <li>inAsAbsolute       (assembly code feature)</li>
	  <li>inAndNeverStrip    (tell strip tool to leave)</li>
	  <li>inWithRandomName   (mach-o .o feature)</li>
  <li>NeverDeadStrip (boolean)</li>
  <li>IsThumb (ARM specific)</li>
<p>Where does dllexport fit in here?  Where does visibility protected and 
internal fit?  Protected seems like scope=global plus the rule to not 
indirect references to it.  Internal is like hidden plus enables some
compiler optimizations.  I'm not sure the linker needs to know about internal.