To make incremental builds fast on macOS, the static linker (ld) ignores the debug information. It can easily be a magnitude bigger than the rest of the program and slow down link time. Instead the linker emits a debug map which contains the location of all the object files it relocated so that debug info consumers (such as the debugger) know where to find the DWARF debug info.

This approach works great during development and greatly speeds up the build-debug cycle. Keeping the object files around is not a great way to archive the debug info however. This is where dsymutil comes it. It links together the debug information from all the object files and generates a dSYM bundle.

In addition to merging debug info and relocating the addresses, dsymutil also optimizes the resulting DWARF by removing redundant information. This is especially useful for C++ where different compile units will often contain references to the same type. Think for example about types from the Standard Template Library being included in many translation units.

The new --staticstics flag quantifies the impact of dsymutil on the size of the .debug_info section. It prints a table after linking with the object file name, the size of the section in the object file and the size contributed to the linked dSYM. The table is sorted by the output size, listing the object files with the largest contribution first.

Let’s take a look at the output for llvm-lipo. I’ve omitted a significant portion of the output for brevity.

.debug_info section size (in bytes)
-------------------------------------------------------------------------------
Filename                                           Object         dSYM   Change
-------------------------------------------------------------------------------
libLLVMBitReader.a(BitcodeReader.cpp.o)          1737930b     1430628b  -19.40%
libLLVMCore.a(Metadata.cpp.o)                    1509986b      853656b  -55.54%
libLLVMCore.a(AsmWriter.cpp.o)                   1263156b      792942b  -45.74%
libLLVMCore.a(Verifier.cpp.o)                    1293937b      732571b  -55.40%
libLLVMMCParser.a(AsmParser.cpp.o)                955659b      719688b  -28.17%
...
libLLVMSupport.a(Watchdog.cpp.o)                    1830b        1629b  -11.62%
libLLVMSupport.a(regfree.c.o)                        747b         810b    8.09%
libLLVMSupport.a(regerror.c.o)                       633b         682b    7.45%
libLLVMSupport.a(regstrlcpy.c.o)                     206b         222b    7.48%
libLLVMSupport.a(Valgrind.cpp.o)                     232b         209b  -10.43%
libLLVMSupport.a(ABIBreak.cpp.o)                      95b          70b  -30.30%
-------------------------------------------------------------------------------
Total                                           57892179b    32421404b  -56.41%
-------------------------------------------------------------------------------

When looking at these numbers, it’s important to keep a few things in mind. The debug info section is essentially a tree of debug info entries (DIEs). Removing redundant DIEs (uniquing) in dsymutil means merging these trees.

Consider the example below. The left tree has 4 nodes: a through d, and the right tree has 3 nodes, b, d and e.

   +---+       +---+
   | a |       | b |
   +---+       +---+
     |           |
     |           |
     v           v
   +---+       +---+
   | b |       | d |
   +---+       +---+
    / \          |
   /   \         |
  v     v        v
+---+ +---+    +---+
| c | | d |    | e |
+---+ +---+    +---+

The merged tree has 5 nodes, which is an increase from the original 4 in the left tree, while the right tree has been completely eliminated.

   +---+
   | a |
   +---+
     |
     |
     v
   +---+
   | b |
   +---+
    / \
   /   \
  v     v
+---+ +---+
| c | | d |
+---+ +---+
         |
         |
         v
       +---+
       | e |
       +---+

This explains why the size sometimes increases rather than decreases. More commonly however, this effect will manifest itself as a smaller decrease for some object files than others. The takeaway is that this is not necessarily a bad thing. The algorithm used by dsymutil and the order in which the DIEs are traversed determine to which compile unit a uniqued DIE gets attributed.

At the bottom of the output we see the total decrease. For llvm-lipo, dsymutil was able to get rid of a little more than half the debug info.