Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Understanding Xcode Link Map Files and Mach‑O Layout

Notes 1

Enable Link Map output in Xcode by setting Target → Build Settings → Write Link Map File to YES. The destination path can be customized; by default it expands to:

$(TARGET_TEMP_DIR)/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt

Example (simulator, arm64):

/Users/dev/Library/Developer/Xcode/DerivedData/LinkMapSample-a1b2c3d4/Build/Intermediates.noindex/LinkMapSample.build/Release-iphonesimulator/LinkMapSample.build/LinkMapSample-LinkMap-normal-arm64.txt

Link Map files describe how the final Mach-O executable is composed: the contributing object files and libraries, how code/data are laid out, and per-symbol placement/size. They are structured in several labeled sections.

Path header

# Path: /Users/dev/Library/Developer/Xcode/DerivedData/LinkMapSample-a1b2c3d4/Build/Products/Release-iphonesimulator/LinkMapSample.app/LinkMapSample

The path of the generated Mach-O binary.

Arch header

# Arch: arm64

The target architecture for the linked image.

Object files table

# Object files:
[ 0] linker synthesized
[ 1] /Users/dev/Library/.../LinkMapSample.app-Simulated.xcent
[ 2] /Users/dev/Library/.../Objects-normal/arm64/HomeViewController.o
[ 3] /Users/dev/Library/.../Objects-normal/arm64/main.o
[ 4] /Users/dev/Library/.../Objects-normal/arm64/ApplicationDelegate.o
[ 5] /Applications/Xcode.app/.../System/Library/Frameworks/Foundation.framework/Foundation.tbd
[ 6] /Applications/Xcode.app/.../usr/lib/libobjc.tbd
[ 7] /Applications/Xcode.app/.../System/Library/Frameworks/UIKit.framework/UIKit.tbd

Every contributing object or text-based stub (tbd) is assigned an index in brackets. These indices are referenced in the symbol table for size attribusion.

Sections and Mach-O context

# Sections:
# Address     Size        Segment Section
0x1000036B0   0x00000420  __TEXT  __text
0x100003AD0   0x0000002C  __TEXT  __stubs
0x100003AFC   0x00000060  __TEXT  __stub_helper
0x100003B5C   0x00000990  __TEXT  __objc_methname
0x1000044EC   0x00000044  __TEXT  __objc_classname
0x100004530   0x00000820  __TEXT  __objc_methtype
0x100004D50   0x00000085  __TEXT  __cstring
0x100004DD8   0x00000170  __TEXT  __entitlements
0x100004F48   0x00000058  __TEXT  __unwind_info
0x100005000   0x00000018  __DATA  __nl_symbol_ptr
0x100005018   0x00000018  __DATA  __got
0x100005030   0x00000040  __DATA  __la_symbol_ptr
0x100005070   0x00000010  __DATA  __objc_classlist
0x100005080   0x00000010  __DATA  __objc_protolist
0x100005090   0x00000008  __DATA  __objc_imageinfo
0x100005098   0x00000C10  __DATA  __objc_const
0x100005CA8   0x00000018  __DATA  __objc_selrefs
0x100005CC0   0x00000010  __DATA  __objc_classrefs
0x100005CD0   0x00000008  __DATA  __objc_superrefs
0x100005CD8   0x00000008  __DATA  __objc_ivar
0x100005CE0   0x000000B8  __DATA  __objc_data
0x100005D98   0x000000D0  __DATA  __data

The binary is a Mach-O image. Verify with the file tool:

file LinkMapSample

Possible output:

LinkMapSample: Mach-O 64-bit executable arm64

Mach-O organizes virtual memory into segments, commonly:

  • __TEXT: read-only, executable code and immutable metadata
  • __DATA: writable data such as globals and Objective‑C runtime blobs
  • __LINKEDIT: loader metadata (e.g., symbol/string tables), read-only

Each segment is subdivided into sections. For example, __objc_methname stores Objective‑C selector strings. The columns above are the start address, byte size (hex), segment name, and section name.

Symbols

# Address     Size        File  Name
0x1000036B0   0x00000044  [  2] -[HomeViewController viewDidLoad]
0x1000036F4   0x00000098  [  3] _main
0x10000378C   0x00000084  [  4] -[ApplicationDelegate application:didFinishLaunchingWithOptions:]
0x100003810   0x00000040  [  4] -[ApplicationDelegate applicationWillResignActive:]
0x100003850   0x00000040  [  4] -[ApplicationDelegate applicationDidEnterBackground:]
0x100003890   0x00000040  [  4] -[ApplicationDelegate applicationWillEnterForeground:]
0x1000038D0   0x00000040  [  4] -[ApplicationDelegate applicationDidBecomeActive:]
0x100003910   0x00000040  [  4] -[ApplicationDelegate applicationWillTerminate:]
0x100003950   0x00000020  [  4] -[ApplicationDelegate window]
0x100003970   0x00000040  [  4] -[ApplicationDelegate setWindow:]
0x1000039B0   0x00000030  [  4] -[ApplicationDelegate .cxx_destruct]
0x100003AD0   0x00000006  [  5] _NSStringFromClass
0x100003AD6   0x00000006  [  7] _UIApplicationMain
0x100003ADC   0x00000006  [  6] _objc_autoreleasePoolPop
0x100003AE2   0x00000006  [  6] _objc_autoreleasePoolPush
0x100003AE8   0x00000006  [  6] _objc_msgSendSuper2
0x100003AEE   0x00000006  [  6] _objc_retainAutoreleasedReturnValue
0x100003AF4   0x00000006  [  6] _objc_storeStrong
0x100003AFC   0x00000010  [  0] helper helper
0x100003B0C   0x0000000A  [  5] _NSStringFromClass
0x100003B16   0x0000000A  [  6] _objc_autoreleasePoolPop
0x100003B20   0x0000000A  [  6] _objc_autoreleasePoolPush
0x100003B2A   0x0000000A  [  6] _objc_msgSendSuper2
0x100003B34   0x0000000A  [  6] _objc_retainAutoreleasedReturnValue
0x100003B3E   0x0000000A  [  6] _objc_storeStrong
0x100003B48   0x0000000A  [  7] _UIApplicationMain
0x100003B5C   0x0000000C  [  2] literal string: viewDidLoad
...

Interpretation:

  • Address: symbol start (hex virtual address)
  • Size: byte length (hex)
  • File: object index as defined in the object file table
  • Name: symbol or string literal descriptor

To attribuet size to a given .o or framework stub, sum the sizes of symbols whose File column equals that object’s index. The ranges can also be grouped by section: for example, any text symbols between the __text start and the next section’s start belong to the text section.

Dead stripped symbols

# Dead Stripped Symbols:
#             Size        File  Name
<<dead>>      0x00000018  [  2] CIE
<<dead>>      0x00000018  [  3] CIE
<<dead>>      0x00000006  [  4] literal string: class
<<dead>>      0x00000008  [  4] literal string: v16@0:8
...

Entries the linker removed during dead‑code elimination. These do not contribute to the final image size but can be informative when reviewing what was discarded.

Minimal Ruby parser to summarize per-file sizes

#!/usr/bin/env ruby
# frozen_string_literal: true

abort('usage: ruby linkmap_size.rb <path-to-LinkMap.txt>') if ARGV.empty?
path = ARGV.first
raise "File not found: #{path}" unless File.file?(path)

section = nil
index_to_name = {}
size_by_index = Hash.new(0)

File.foreach(path) do |line|
  line.rstrip!
  case line
  when /^# Object files:/
    section = :objects
    next
  when /^# Sections:/
    section = :sections
    next
  when /^# Address\s+Size\s+File\s+Name/
    section = :symbols
    next
  when /^# Dead Stripped Symbols:/
    section = :dead
    next
  end

  if section == :objects
    # [ 12] /path/to/Thing.o
    if line =~ /\[\s*(\d+)\]\s+(.*)/
      idx = Integer(Regexp.last_match(1))
      index_to_name[idx] = Regexp.last_match(2)
    end
  elsif section == :symbols
    # 0xADDR 0xSIZE [ N] name
    if line =~ /^0x([0-9a-fA-F]+)\s+0x([0-9a-fA-F]+)\s+\[\s*(\d+)\]\s+/
      size_hex = Regexp.last_match(2)
      idx = Integer(Regexp.last_match(3))
      size_by_index[idx] += size_hex.to_i(16)
    end
  end
end

# Convert to sorted list and print
fmt = ->(b){
  if b >= 1024 * 1024
    format('%.2fMB', b.to_f / (1024 * 1024))
  elsif b >= 1024
    format('%.2fKB', b.to_f / 1024)
  else
    format('%dB', b)
  end
}

total = size_by_index.values.reduce(0, :+)
size_by_index
  .sort_by { |(_i, bytes)| -bytes }
  .each do |idx, bytes|
    name = index_to_name[idx] || "[#{idx}]"
    puts format('%-50s %10s', name, fmt.call(bytes))
  end
puts format('%-50s %10s', 'Total (approx):', fmt.call(total))

Sample output for the examples above:

/Users/dev/.../ApplicationDelegate.o                         8.83KB
/Users/dev/.../HomeViewController.o                            792B
/Users/dev/.../LinkMapSample.app-Simulated.xcent               392B
/Users/dev/.../main.o                                          208B
linker synthesized                                             128B
.../usr/lib/libobjc.tbd                                        112B
.../Foundation.framework/Foundation.tbd                         32B
.../UIKit.framework/UIKit.tbd                                   32B
Total (approx):                                              10.28KB

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.