<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://listcomp.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://listcomp.com/" rel="alternate" type="text/html" /><updated>2026-02-11T23:08:30-08:00</updated><id>https://listcomp.com/feed.xml</id><title type="html">listcomp Pro</title><subtitle>Yao Yao&apos;s personal blog</subtitle><author><name>Yao Yao</name></author><entry><title type="html">Mach-O Stitching #9: Relational Binaries – Modeling Symbols and Relocations as Database Tables</title><link href="https://listcomp.com/compiler/2026/02/03/mach-o-stitching-9-relational-binaries-modeling-symbols-and-relocations-as-database-tables" rel="alternate" type="text/html" title="Mach-O Stitching #9: Relational Binaries – Modeling Symbols and Relocations as Database Tables" /><published>2026-02-03T00:00:00-08:00</published><updated>2026-02-03T00:00:00-08:00</updated><id>https://listcomp.com/compiler/2026/02/03/mach-o-stitching-9-relational-binaries---modeling-symbols-and-relocations-as-database-tables</id><content type="html" xml:base="https://listcomp.com/compiler/2026/02/03/mach-o-stitching-9-relational-binaries-modeling-symbols-and-relocations-as-database-tables"><![CDATA[<pre><code class="language-mermaid">---
config:
  themeVariables:
    fontFamily: 'monospace'
    fontSize: '14px'
---

classDiagram
    class STRTAB {
        +Index  0 -&gt; "\0"
        +Index  1 -&gt; "_shared_int\0"
        +Index 13 -&gt; "_shared_float\0"
        +Index 27 -&gt; "_main\0"
        +Index 33 -&gt; "_shared_func\0"
    }

    class SYMTAB {
        &lt;&lt;Master Table&gt;&gt;
        +Index 0 -&gt; (n_strx=27, ..., n_sect=15, type="SECT", str="_main")
        +Index 1 -&gt; (n_strx=13, ..., n_sect=1, type="UNDF", str="shared_float")
        +Index 2 -&gt; (n_strx=33, ..., n_sect=1, type="UNDF", str="shared_func")
        +Index 3 -&gt; (n_strx=1, ..., n_sect=1, type="UNDF", str="shared_int")
    }

    class DYSYMTAB {
        &lt;&lt;Index View&gt;&gt;
        +ilocalsym = 0
        +nlocalsym = 0
        +iextdefsym = 0
        +nextdefsym = 1
        +iundefsym = 1
        +nundefsym = 3
    }

    class RELOC_ENTRIES {
        &lt;&lt;Junction Table&gt;&gt;
        +address 0x00000053 -&gt; (pcrel=True, ..., type="BRANCH", symbol_num=2, value="_shared_func")
        +address 0x0000004c -&gt; (pcrel=True, ..., type="BRANCH", symbol_num=2, value="_shared_func")
        +address 0x00000045 -&gt; (pcrel=True, ..., type="BRANCH", symbol_num=2, value="_shared_func")
        +address 0x00000032 -&gt; (pcrel=True, ..., type="GOT_LD", symbol_num=1, value="_shared_float")
        +address 0x0000001f -&gt; (pcrel=True, ..., type="GOT_LD", symbol_num=1, value="_shared_float")
        +address 0x00000012 -&gt; (pcrel=True, ..., type="GOT_LD", symbol_num=3, value="_shared_int")
    }
    
    class ADDRESS_SPACE {
        &lt;&lt;Master Table&gt;&gt;
        +address 0x00000012 -&gt; (machine_code=0x00000000, assembly="disp32&lt;\%rip&gt;")
        +address 0x0000001f -&gt; (machine_code=0x00000000, assembly="disp32&lt;\%rip&gt;")
        +address 0x00000032 -&gt; (machine_code=0x00000000, assembly="disp32&lt;\%rip&gt;")
        +address 0x00000045 -&gt; (machine_code=0x00000000, assembly="disp32&lt;\%rip&gt;")
        +address 0x0000004c -&gt; (machine_code=0x00000000, assembly="disp32&lt;\%rip&gt;")
        +address 0x00000053 -&gt; (machine_code=0x00000000, assembly="disp32&lt;\%rip&gt;")
    }

    SYMTAB ..&gt; STRTAB : "FK Lookup (n_strx)"
    SYMTAB &lt;-- DYSYMTAB : "Slices / Filters"
    RELOC_ENTRIES ..&gt; SYMTAB : "JOIN (symbol_num -&gt; Index)"
    RELOC_ENTRIES ..&gt; ADDRESS_SPACE : "JOIN (address -&gt; address)" 
</code></pre>

<ul>
  <li><strong>strtab</strong> 本质是个 string buffer, 不是 table</li>
  <li><strong>symtab</strong> 可以看做是个 table
    <ul>
      <li>注意它内部的 grouping (by symbol type) 和 sorting (by symbol name; for fast binary searches)</li>
    </ul>
  </li>
  <li><strong>dysymtab</strong> (仅图中我们涉及的部分) 也不是个 table, 更像是 symtab 的一组 indices/pointers</li>
  <li>object file 本身可以看做是个 table, 以 address/offset 为 index, 以 address 上的 machine code/assembly instruction 为 value; 我们称这个 table 为 <strong>address space</strong></li>
  <li><strong>relocs</strong> 可以看做是 symtab 和 address space 的 <em>junction table</em>, 它记录的其实是 <code>(address, symbol, ...)</code> 的 <em>occurrences</em></li>
</ul>

<p>这个 system design 还是很工整的。</p>]]></content><author><name>Yao Yao</name></author><category term="Compiler" /><summary type="html"><![CDATA[```mermaid config: themeVariables: fontFamily: ‘monospace’ fontSize: ‘14px’ —]]></summary></entry><entry><title type="html">Mach-O Stitching #8: Static Linking – Who Does What?</title><link href="https://listcomp.com/compiler/2026/02/02/mach-o-stitching-8-static-linking-who-does-what" rel="alternate" type="text/html" title="Mach-O Stitching #8: Static Linking – Who Does What?" /><published>2026-02-02T00:00:00-08:00</published><updated>2026-02-02T00:00:00-08:00</updated><id>https://listcomp.com/compiler/2026/02/02/mach-o-stitching-8-static-linking--who-does-what</id><content type="html" xml:base="https://listcomp.com/compiler/2026/02/02/mach-o-stitching-8-static-linking-who-does-what"><![CDATA[<h1 id="compiler-proper">“Compiler Proper”</h1>

<p>Throughout this series, <strong>compiler</strong> refers to the <em>compiler proper</em>: the component that translates C $\Rightarrow$ Assembly. Producing an executable is treated as a <strong>separate static linking phase</strong>, orchestrated by a <strong>compiler driver</strong>, such as <code>gcc</code> and <code>clang</code>.</p>

<p>In the <code>gcc</code> toolchain, <code>cc1</code> is the <em>compiler proper</em>; while in <code>clang</code>, it’s <code>clang -cc1</code></p>

<pre><code class="language-txt">gcc hello.c
 ├─ cc1            # C front-end + optimizer + code generation
 ├─ as             # GNU assembler
 └─ ld             # GNU linker
 
clang hello.c
 ├─ clang -cc1     # front-end + LLVM IR + optimization + codegen
 ├─ as             # system assembler (or integrated assembler)
 └─ ld             # system linker (e.g., ld64 on macOS)
</code></pre>

<h1 id="defining-the-boundary-precursor-jobs-vs-core-process-of-static-linking">Defining the Boundary: Precursor Jobs vs. Core Process of Static Linking</h1>

<p>Static Linking 是一个组合动作，它由 Symbol Resolution 和 Relocation 这两大核心步骤组成。而这两步理论上都是由 Static Linker 完成的，<strong>虽然 compiler 和 assembler 的 precursor jobs 是不可或缺的，但这些 precursor jobs 是不算在 static linking 的过程中的</strong>。</p>

<p><strong>我始终觉得这里缺一个更大的概念或者名称来包含这些 precursor jobs 和 static linking</strong>，不如我们称其 <em>Materialization of External Symbols</em>:</p>

<pre><code class="language-txt">┌───────────────────────────────────────┐
│  Materialization of External Symbols  │
│     ┌────────────────────────────┐    │
│     │  Compiler's Precursor Job  │    │
│     └────────────────────────────┘    │
│                  \/                   │
│    ┌─────────────────────────────┐    │
│    │  Assembler's Precursor Job  │    │
│    └─────────────────────────────┘    │
│                  \/                   │
│  ┌─────────────────────────────────┐  │
│  │  Static Linker's Static Linking │  │
│  └─────────────────────────────────┘  │
└───────────────────────────────────────┘
</code></pre>

<p>更具体一点的话：</p>

<pre><code class="language-txt">    [ main.c ]
    +-------------------+
    |  High-level logic |
    +-------------------+
         |
         v
   (( COMPILER ))  &lt;-- PERFORMER: Lexical/Syntax/Semantics Analysis + Codegen for C
         |             READS: C/C++ Code
         |             WRITES: Assembly with "Hints" (@GOT, etc.)
         v
    [ main.s ]
    +-----------+
    |  Assembly |
    +-----------+
         |
         v
   (( ASSEMBLER ))  &lt;-- PERFORMER: Lexical/Syntax + Codegen for Assembly
         |              READS: Assembly &amp; Directives
         |              WRITES: Machine code + Metadata
         v
    [ OBJECT.o ]  (The "Incomplete" Binary)
    +---------------------------------------------+
    | .text   :  Instructions with 0x00 holes     |
    | .symtab :  List of defined/undefined names  |
    | .reloc  :  The "Sticky Notes" for Linker    |
    +---------------------------------------------+
         |
         |  (Combined with other [ .o ] and [ .a ] files)
         v
(( STATIC LINKER ))  &lt;-- PERFORMER: The "straightforward" Static Linking
         |
         |   PHASE 1: SYMBOL RESOLUTION
         |   - Scans all .symtab sections.
         |   - Matches "Undefined" names to "Global" addresses.
         |
         |   PHASE 2: RELOCATION
         |   - Scans all .reloc entries.
         |   - Patches the 0x00 holes in .text with real addresses.
         |
         v
    [ EXECUTABLE ]  (The "Complete" Binary)
    +---------------------------------------------+
    | .text   :  Final code with hardcoded jumps  |
    | .data   :  Variables in fixed locations     |
    +---------------------------------------------+
</code></pre>

<h1 id="compiler-做了哪些工作">Compiler 做了哪些工作</h1>

<p>compiler 为 symbol materialization 做的 precursor job 基本上就是给 assembler 留一些 hint. 比如我们在 <a href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-7-how-to-interpret-relocs-relocation-entries#%E6%96%B9%E6%B3%95%E4%B8%80%E7%9B%B4%E6%8E%A5%E7%BC%96%E8%AF%91-c-rightarrow-assembly-%E4%BB%A5%E4%B8%8B%E7%A7%B0-s-assembly"><code>.s</code> assembly</a> 里看到的是：</p>

<pre><code class="language-nasm">movq _shared_int@GOTPCREL(%rip), %rax
</code></pre>

<blockquote>
  <p>[!info] 注意这是 AT&amp;T 汇编语法，与 Intel 语法略有不同</p>
</blockquote>

<ul>
  <li><code>_shared_int</code> 是 symbol name</li>
  <li><code>@GOTPCREL</code> 整体是 relocation modifier
    <ul>
      <li>其中 <code>GOTPCREL</code> 是 relocation type</li>
    </ul>
  </li>
  <li><code>(%rip)</code> 是 base register</li>
</ul>

<p>assembler 看到这一句自然就明白了这里需要一个 reloc.</p>

<h1 id="assembler-做了哪些工作">Assembler 做了哪些工作</h1>

<p>除了 <code>_shared_int@GOTPCREL(%rip)</code> 这种明显的，assembler 自己还有其他的判断 external symbols 的方法。我们可能长久忽略的一个事情是：<strong>assembler 本身就是一个 Assembly 的 parser</strong>, 它对 symbol 的判断能力是毋庸置疑的。</p>

<blockquote>
  <p>[!faq] 你可能要疑问：“external symbol 的识别” 工作为什么在 compiler 层面做了一遍之后，在 assembler 层面还要做一遍？</p>

  <p>这是 C 编译器的 stage-wise 或者说 stage-independent compilation 的设计。简单说就是相比这么点 overhead，我们更 value 这种设计的 “Separation of Concerns”.</p>
</blockquote>

<p>还是用同一句 instruction，对比一下有：</p>

<pre><code class="language-nasm">movq	_shared_int@GOTPCREL(%rip), %rax  # `.s` assembly (by compiler)
movq	(%rip), %rax                      # `.o` assembly (by assembler)
</code></pre>

<p>这里 assembler 对 <code>_shared_int</code> 这个 external symbol 做了这么几件事情：</p>

<ol>
  <li>把 <code>_shared_int@GOTPCREL</code> 的 $\operatorname{displacement}$ 置 $0$</li>
  <li>给 <code>_shared_int@GOTPCREL</code> 写了一条 reloc 供 static linker 参考</li>
</ol>

<h1 id="static-linker-做了哪些工作">Static Linker 做了哪些工作</h1>

<p>具体的工作流参考 <a href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-7-how-to-interpret-relocs-relocation-entries#%E7%BB%BC%E5%90%88%E5%88%86%E6%9E%90">Mach-O Stitching #7: How to Interpret Relocs (Relocation Entries)</a>.</p>

<p>这里额外提一下 symbol resolution 和 relocation 的分界线，有点微妙：</p>

<ol>
  <li>Static linker received multiple object files.</li>
  <li>Static linker scans all symbol tables. Like it sees the reference to <code>x</code> in <code>file_A.o</code> and finds the definition of <code>x</code> in <code>file_B.o</code>. <strong>Symbol resolution for <code>x</code> is now complete</strong>.</li>
  <li>Now that static linker knows exactly where <code>x</code> will be located in the final memory map, it goes back to <code>x</code>’s <code>reloc.address</code> and overwrites the zeros with the real address. <strong>Relocation for <code>x</code> is now complete</strong>.</li>
</ol>]]></content><author><name>Yao Yao</name></author><category term="Compiler" /><summary type="html"><![CDATA[“Compiler Proper”]]></summary></entry><entry><title type="html">Mach-O Stitching #1: Basic Structure</title><link href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-1-basic-structure" rel="alternate" type="text/html" title="Mach-O Stitching #1: Basic Structure" /><published>2026-01-30T00:00:00-08:00</published><updated>2026-01-30T00:00:00-08:00</updated><id>https://listcomp.com/compiler/2026/01/30/mach-o-stitching-1-basic-structure</id><content type="html" xml:base="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-1-basic-structure"><![CDATA[<p>参考 <a href="https://github.com/aidansteele/osx-abi-macho-file-format-reference">OS X ABI Mach-O File Format Reference</a> (注意 PDF 版本才有图)</p>

<h1 id="0-mach-o-file-types">0. Mach-O File Types</h1>

<table>
  <thead>
    <tr>
      <th>Type</th>
      <th>Constant</th>
      <th>Fully Linked?</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Object</td>
      <td><code>MH_OBJECT</code></td>
      <td>❌</td>
      <td>Compiler output (<code>.o</code> files) - intermediate files that need linking</td>
    </tr>
    <tr>
      <td>Executable</td>
      <td><code>MH_EXECUTE</code></td>
      <td>✅</td>
      <td>Runnable program - standard application binary</td>
    </tr>
    <tr>
      <td>Dynamic Library</td>
      <td><code>MH_DYLIB</code></td>
      <td>✅</td>
      <td>Shared library (<code>.dylib</code> files) - code shared between programs</td>
    </tr>
    <tr>
      <td>Bundle</td>
      <td><code>MH_BUNDLE</code></td>
      <td>✅</td>
      <td>Loadable plugin (<code>.bundle</code>, <code>.plugin</code>) - dynamically loaded modules</td>
    </tr>
    <tr>
      <td>Core Dump</td>
      <td><code>MH_CORE</code></td>
      <td>N/A</td>
      <td>Crash dump - memory snapshot of crashed process</td>
    </tr>
    <tr>
      <td>Dynamic Linker</td>
      <td><code>MH_DYLINKER</code></td>
      <td>✅</td>
      <td>The <code>dyld</code> program itself - loads and links dynamic libraries at runtime</td>
    </tr>
    <tr>
      <td>Preload</td>
      <td><code>MH_PRELOAD</code></td>
      <td>✅</td>
      <td>Fixed address executable - loads at predetermined memory location</td>
    </tr>
    <tr>
      <td>Kernel Extension</td>
      <td><code>MH_KEXT_BUNDLE</code></td>
      <td>❌</td>
      <td>Kernel module (<code>.kext</code>) - loadable kernel extension</td>
    </tr>
    <tr>
      <td>Debug Symbols</td>
      <td><code>MH_DSYM</code></td>
      <td>N/A</td>
      <td>Debug info (<code>.dSYM</code> bundles) - debugging symbols for crash analysis</td>
    </tr>
    <tr>
      <td>Dylib Stub</td>
      <td><code>MH_DYLIB_STUB</code></td>
      <td>Partial</td>
      <td>Linking stub - contains only symbols, no actual code</td>
    </tr>
    <tr>
      <td>Fixed VM Shared Library</td>
      <td><code>MH_FVMLIB</code></td>
      <td>✅</td>
      <td>Obsolete - old-style fixed virtual memory shared library</td>
    </tr>
  </tbody>
</table>

<p>Note that:</p>

<ul>
  <li><strong>Fully Linked?</strong>: Indicates whether the file has gone through the linker and contains a <code>__LINKEDIT</code> segment
    <ul>
      <li><strong>N/A</strong>: These file types don’t follow the standard linking model</li>
    </ul>
  </li>
  <li><strong>Obsolete types</strong> like <code>MH_FVMLIB</code> are rarely seen in modern systems</li>
</ul>

<p>You can check the file type of any Mach-O file using:</p>

<pre><code class="language-bash">ᐅ otool -hv &lt;file&gt;
</code></pre>

<p>The <code>filetype</code> field in the Mach header will show the type.</p>

<h1 id="1-basic-structure-of-a-mach-o-file">1. Basic Structure of a Mach-O File</h1>

<blockquote>
  <p>[!NOTE] <code>ld</code> and <code>dyld</code> on MacOS</p>

  <ul>
    <li><code>ld</code> == <strong>L</strong>ink e<strong>D</strong>itor, the static linker</li>
    <li><code>dyld</code> == <strong>DY</strong>namic <strong>L</strong>ink e<strong>D</strong>itor, the dynamic linker</li>
  </ul>
</blockquote>

<p>A Mach-O file contains three major regions:</p>

<ol>
  <li><em>header</em>:
    <ul>
      <li>identifies the file as a Mach-O file</li>
      <li>indicates the target CPU architecture</li>
      <li>etc.</li>
    </ul>
  </li>
  <li><em>load commands</em>, which specify the layout and linkage characteristics of the file, e.g.:
    <ul>
      <li>the initial layout of the file in virtual memory</li>
      <li>the location of the symbol table (used for dynamic linking)</li>
      <li>the initial execution state of the main thread of the program</li>
      <li>the names of shared libraries that contain definitions for the main executable’s imported symbols</li>
      <li>etc.</li>
    </ul>
  </li>
  <li><em>data</em> (<em>segments/sections</em>)
    <ul>
      <li>Each <em>segment</em> contains $0$ or more sections.</li>
      <li>Each <em>segment</em> defines a region of virtual memory that the dynamic linker maps into the address space of the process.</li>
      <li>The exact number and layout of <em>segments</em> and <em>sections</em> is specified by the <em>load commands</em> and the file type.</li>
      <li>我们这里主要讨论这些 segments：
        <ul>
          <li><code>__TEXT</code>: contains executable code and other read-only data (yes, code is read-only of course)</li>
          <li><code>__DATA</code>: contains writable data</li>
          <li><code>__LD</code>: staging data for the static linker</li>
          <li><code>__LINKEDIT</code>: in user-level fully linked Mach-O files, the <strong>last segment</strong> is the <em>link edit</em> segment, which contains raw data used by the dynamic linker, such as symbol, string, and relocation table entries</li>
        </ul>
      </li>
      <li>另外还有如下这些 segments：
        <ul>
          <li><code>__PAGEZERO</code>: a 4GB (on 64-bit) region of virtual memory at address <code>0</code> with no protection rights assigned, which causes any accesses to <code>NULL</code> to crash.
            <ul>
              <li>designed as a null pointer protection mechanism</li>
            </ul>
          </li>
          <li><code>__OBJC</code>: contains data used by the Objective-C language runtime support library</li>
        </ul>
      </li>
    </ul>
  </li>
</ol>

<h1 id="2-file-content-changes-during-static-linking">2. File Content Changes During Static Linking</h1>

<p>Note that an (relocatable) object file is not fully linked while an executable (object) file is. During static linking (i.e. during the process of <code>main.o</code> $\Rightarrow$ <code>main.out</code>), the following structural transformation occur:</p>

<h2 id="21-new-segments">2.1 New Segments</h2>

<ul>
  <li><strong><code>__PAGEZERO</code> Created</strong></li>
  <li><strong><code>__LINKEDIT</code> Created</strong></li>
</ul>

<h2 id="22-segment-consumption--transformation">2.2 Segment Consumption &amp; Transformation</h2>

<ul>
  <li><strong><code>__LD</code> Consumption</strong>: The compiler-generated <code>(__LD, __compact_unwind)</code> section is consumed. The linker processes this metadata to synthesize the final, optimized <code>(__TEXT, __unwind_info)</code> section.</li>
  <li><strong><code>__TEXT</code> Refinement</strong>:
    <ul>
      <li>Relocations resolved (All internal references fixed up with actual addresses)</li>
      <li>Addresses assigned (Sections get final virtual memory addresses, not just offsets)</li>
      <li>Potentially merged (Multiple object files’ sections would be concatenated)</li>
    </ul>
  </li>
</ul>

<h2 id="23-data--dynamic-linking-setup">2.3 Data &amp; Dynamic Linking Setup</h2>

<ul>
  <li><strong><code>__DATA</code> Refinement</strong>
    <ul>
      <li>Relocations resolved (Pointer values fixed up)</li>
      <li>Addresses assigned (Final virtual memory addresses)</li>
      <li>Symbol pointers added (as placeholders for addresses that <code>dyld</code> will resolve at runtime) to <code>(__DATA, __got)</code> (Global Offset Table)</li>
    </ul>
  </li>
</ul>

<blockquote>
  <p>[!info] <code>__DATA_CONST</code> segment and <strong>RELRO</strong>
In modern Mach-O files, <code>__got</code> is moved to a specific segment called <code>__DATA_CONST</code>.
This allows the dynamic linker to write to them during load time (binding) and then <code>mprotect</code> them to read-only for the rest of the execution (a security feature called <strong>RELRO</strong>, Relocation Read-Only).</p>
</blockquote>

<h2 id="24-load-command-updates">2.4 Load Command Updates</h2>

<ul>
  <li><strong>Entry Point</strong>: <code>LC_MAIN</code> is added to point the kernel to the address of the <code>main()</code> function.</li>
  <li><strong>Dynamic Loader Info</strong>:
    <ul>
      <li><code>LC_LOAD_DYLINKER</code>: Specifies the path to the dynamic linker (usually <code>/usr/lib/dyld</code>).</li>
      <li><code>LC_DYLD_INFO_ONLY</code>: Contains the compressed “rebase” and “bind” opcodes used by <code>dyld</code> at startup.</li>
      <li><code>LC_LOAD_DYLIB</code>: One command is added for every linked framework (e.g., <code>libSystem</code>).</li>
    </ul>
  </li>
  <li><strong>Symbol Tables</strong>: <code>LC_SYMTAB</code> is updated to remove local/temporary labels, and <code>LC_DYSYMTAB</code> is added to categorize symbols for the dynamic linker (local vs. defined external vs. undefined external).</li>
</ul>

<h1 id="3-structure-diagram">3. Structure Diagram</h1>

<p>一个 object file 的结构大致如下：</p>

<pre><code class="language-txt">┌─────────────────────────────────────────────────────────────┐
│                  REGION 1 - MACH-O HEADER                   │
├─────────────────────────────────────────────────────────────┤
│  magic          : 0xFEEDFACF (64-bit)                       │
│  cputype        : CPU_TYPE_ARM64                            │
│  cpusubtype     : CPU_SUBTYPE_ARM64_ALL                     │
│  filetype       : MH_OBJECT                                 │
│  ncmds          : Number of load commands                   │
│  sizeofcmds     : Total size of load commands               │
│  flags          : MH_NOUNDEFS | MH_DYLDLINK | ...           │
├─────────────────────────────────────────────────────────────┤
│                  REGION 2 - LOAD COMMANDS                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ├─ LC_SEGMENT_64 (__TEXT)                                  │
│  │   ├─ vmaddr    : 0x100000000                             │
│  │   ├─ vmsize    : ...                                     │
│  │   └─ sections  : [__text, __stubs, __cstring, ...]       │
│  │                                                          │
│  ├─ LC_SEGMENT_64 (__DATA)                                  │
│  │   ├─ vmaddr    : ...                                     │
│  │   ├─ vmsize    : ...                                     │
│  │   └─ sections  : [__data, __bss, __common, ...]          │
│  │                                                          │
│  ├─ LC_SEGMENT_64 (__LD)                                    │
│  │   ├─ vmaddr    : ...                                     │
│  │   ├─ vmsize    : ...                                     │
│  │   └─ filesize  : ...                                     │
│  │                                                          │
│  ├─ LC_SYMTAB                                               │
│  │   ├─ symoff   : Symbol table offset                      │
│  │   ├─ nsyms    : Number of symbols                        │
│  │   ├─ stroff   : String table offset                      │
│  │   └─ strsize  : String table size                        │
│  │                                                          │
│  ├─ LC_UUID                                                 │
│  │   └─ uuid: [16 bytes]                                    │
│  │                                                          │
│  ├─ LC_FUNCTION_STARTS                                      │
│  │   └─ Function start addresses                            │
│  │                                                          │
│  ├─ LC_DATA_IN_CODE                                         │
│  │   └─ Data-in-code entries                                │
│  │                                                          │
│  └─ LC_CODE_SIGNATURE                                       │
│      └─ Code signature offset and size                      │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                       REGION 3 - DATA                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                   __TEXT SEGMENT                      │  │
│  ├───────────────────────────────────────────────────────┤  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __text SECTION                   │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Executable machine code (your compiled code)    │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __const SECTION                  │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Initialized constant variables                  │  │  │
│  │  │ (The compiler places all nonrelocatable data    │  │  │
│  │  │ declared `const` in this section.)              │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __cstring SECTION                │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ C string literals                               │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __literal4 SECTION               │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ 4-byte literal values (single-precision floats) │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __literal8 SECTION               │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ 8-byte literal values (double-precision floats) │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __stubs SECTION                  │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Symbol stubs                                    │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __eh_frame SECTION               │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Exception handling frame information            │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                   __DATA SEGMENT                      │  │
│  ├───────────────────────────────────────────────────────┤  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __data SECTION                   │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Initialized mutable variables                   │  │  │
│  │  │ (such as writable C strings and data arrays)    │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __const SECTION                  │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Initialized relocatable constant variables      │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __bss SECTION                    │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ uninitialized static variables                  │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __common SECTION                 │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Uninitialized imported symbol definitions       │  │  │
│  │  │   located in the global scope                   │  │  │
│  │  │   (i.e. outside of any function declaration)    │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __dyld SECTION                   │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Placeholder section used by the dynamic linker  │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                ____mod_init_func SECTION        │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Module initialization functions                 │  │  │
│  │  │ (e.g. C++ static constructors)                  │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                ____mod_term_func SECTION        │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Module termination functions                    │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                   __LD SEGMENT                        │  │
│  ├───────────────────────────────────────────────────────┤  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __compact_unwind SECTION         │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Compact unwind information                      │  │  │
│  │  │ (for exception handling/stack unwinding)        │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
</code></pre>

<p>如果它被 fully linked 成 executable, 那么会有如下的 load command 增量：</p>

<pre><code class="language-text">┌─────────────────────────────────────────────────────────────┐
│                  REGION 2 - LOAD COMMANDS                   │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ├─ LC_SEGMENT_64 (__PAGEZERO)                              │
│  │   ├─ vmaddr    : 0x0                                     │
│  │   ├─ vmsize    : 0x100000000                             │
│  │   └─ filesize  : 0                                       │
│  │                                                          │
│  ├─ LC_SEGMENT_64 (__LINKEDIT)                              │
│  │   ├─ vmaddr    : ...                                     │
│  │   ├─ vmsize    : ...                                     │
│  │   └─ filesize  : ...                                     │
│  │                                                          │
│  ├─ LC_DYSYMTAB                                             │
│  │   └─ Dynamic linking metadata                            │
│  │                                                          │
│  ├─ LC_DYLD_INFO_ONLY                                       │
│  │   ├─ rebase_off                                          │
│  │   ├─ bind_off                                            │
│  │   ├─ weak_bind_off                                       │
│  │   ├─ lazy_bind_off                                       │
│  │   └─ export_off                                          │
│  │                                                          │
│  ├─ LC_LOAD_DYLINKER                                        │
│  │   └─ name: /usr/lib/dyld                                 │
│  │                                                          │
│  ├─ LC_LOAD_DYLIB                                           │
│  │   └─ name: /usr/lib/libSystem.B.dylib                    │
│  │                                                          │
│  └─ LC_MAIN                                                 │
│      └─ Entry point                                         │
└─────────────────────────────────────────────────────────────┘
</code></pre>

<p>且会有如下的 segment 增量：</p>

<pre><code class="language-text">┌─────────────────────────────────────────────────────────────┐
│                       REGION 3 - DATA                       │
├─────────────────────────────────────────────────────────────┤
│  ┌───────────────────────────────────────────────────────┐  │
│  │                   __TEXT SEGMENT                      │  │
│  ├───────────────────────────────────────────────────────┤  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __unwind_info SECTION            │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Unwinding information for exception handling    │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                   __DATA SEGMENT                      │  │
│  ├───────────────────────────────────────────────────────┤  │
│  │  ┌─────────────────────────────────────────────────┐  │  │
│  │  │                __got SECTION                    │  │  │
│  │  ├─────────────────────────────────────────────────┤  │  │
│  │  │ Global offset table                             │  │  │
│  │  └─────────────────────────────────────────────────┘  │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                 __LINKEDIT SEGMENT                    │  │
│  ├───────────────────────────────────────────────────────┤  │
│  │  ├─ Rebase Information                                │  │
│  │  │   └─ Addresses to adjust on load                   │  │
│  │  │                                                    │  │
│  │  ├─ Binding Information                               │  │
│  │  │   ├─ Regular bindings                              │  │
│  │  │   ├─ Weak bindings                                 │  │
│  │  │   └─ Lazy bindings                                 │  │
│  │  │                                                    │  │
│  │  ├─ Export Information                                │  │
│  │  │   └─ Exported symbols trie                         │  │
│  │  │                                                    │  │
│  │  ├─ Symbol Table (SYMTAB)                             │  │
│  │  │   ├─ nlist_64 entries                              │  │
│  │  │   └─ Symbol definitions and references             │  │
│  │  │                                                    │  │
│  │  ├─ String Table (STRTAB)                             │  │
│  │  │   └─ Null-terminated symbol name strings           │  │
│  │  │                                                    │  │
│  │  ├─ Indirect Symbol Table                             │  │
│  │  │   └─ Indices into symbol table                     │  │
│  │  │                                                    │  │
│  │  ├─ Relocation Entries (RELOCS)                       │  │
│  │  │   └─ Address fixup information                     │  │
│  │  │                                                    │  │
│  │  ├─ Function Starts                                   │  │
│  │  │   └─ Compressed function start addresses           │  │
│  │  │                                                    │  │
│  │  ├─ Data-in-Code                                      │  │
│  │  │   └─ Non-instruction data in __text                │  │
│  │  │                                                    │  │
│  │  └─ Code Signature                                    │  │
│  │      └─ Digital signature and entitlements            │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
</code></pre>]]></content><author><name>Yao Yao</name></author><category term="Compiler" /><summary type="html"><![CDATA[参考 OS X ABI Mach-O File Format Reference (注意 PDF 版本才有图)]]></summary></entry><entry><title type="html">Mach-O Stitching #2: Header</title><link href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-2-header" rel="alternate" type="text/html" title="Mach-O Stitching #2: Header" /><published>2026-01-30T00:00:00-08:00</published><updated>2026-01-30T00:00:00-08:00</updated><id>https://listcomp.com/compiler/2026/01/30/mach-o-stitching-2-header</id><content type="html" xml:base="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-2-header"><![CDATA[<pre><code class="language-c">// vanilla.c

int a = 10;          // stored in __DATA segment

int main() {         // stored in __TEXT segment
    return a;
}
</code></pre>

<pre><code class="language-bash">ᐅ clang -c vanilla.c -o vanilla.o
</code></pre>

<pre><code class="language-bash">ᐅ otool -h vanilla.o  # Display the Mach-O header
vanilla.o:
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777223          3  0x00           1     4        520 0x00002000
</code></pre>

<p>如果看不懂这些 magic number，可以加上 <code>-v</code> 让 <code>otool</code> 显示 symbolic values:</p>

<pre><code class="language-bash">ᐅ otool -hv vanilla.o
vanilla.o:
Mach header
      magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64   X86_64        ALL  0x00      OBJECT     4        520 SUBSECTIONS_VIA_SYMBOLS
</code></pre>

<p>Header fields explained:</p>

<ul>
  <li><code>magic</code> $\Rightarrow$ a magic number that identifies the Mach-O file format. E.g.:
    <ul>
      <li><code>0xfeedface</code> $\Rightarrow$ big-endian 32-bit</li>
      <li><code>0xfeedfacf</code> $\Rightarrow$ big-endian 64-bit</li>
      <li><code>0xcafebabe</code> $\Rightarrow$ big-endian universal</li>
    </ul>
  </li>
  <li><code>cputype</code> $\Rightarrow$ identifies the CPU architecture family
    <ul>
      <li><code>16777223 == 0x1000007</code> $\Rightarrow$  <code>CPU_TYPE_X86_64</code> (Intel/AMD 64-bit)
        <ul>
          <li>the high bit <code>0x1000000</code> indicates 64-bit capability</li>
          <li>see <code>/usr/include/mach/machine.h</code> for the definitions</li>
        </ul>
      </li>
    </ul>
  </li>
  <li><code>cpusubtype</code>$\Rightarrow$ specifies CPU variant within the architecture family
    <ul>
      <li><code>3</code> $\Rightarrow$  <code>CPU_SUBTYPE_X86_64_ALL</code> (compatible with all x86_64 processors)</li>
    </ul>
  </li>
  <li><code>caps</code>$\Rightarrow$ CPU capability bits (rarely used, usually <code>0</code>)</li>
  <li><code>filetype</code>
    <ul>
      <li><code>1</code> $\Rightarrow$  <code>MH_OBJECT</code> (relocatable object file, like your <code>.o</code> file)</li>
      <li><code>2</code> $\Rightarrow$  <code>MH_EXECUTE</code> (executable)</li>
      <li><code>6</code> $\Rightarrow$  <code>MH_DYLIB</code> (dynamic library)</li>
      <li><code>8</code> $\Rightarrow$  <code>MH_BUNDLE</code> (loadable bundle)</li>
    </ul>
  </li>
  <li><code>ncmds</code> $\Rightarrow$ number of load commands (that describe segments, symbols, etc.) following the header
    <ul>
      <li><code>4</code> commands in your case</li>
    </ul>
  </li>
  <li><code>sizeofcmds</code> $\Rightarrow$  total size in bytes of all load commands
    <ul>
      <li><code>520</code> bytes in your case</li>
    </ul>
  </li>
  <li><code>flags</code> $\Rightarrow$ misc. flags
    <ul>
      <li><code>0x2000</code> $\Rightarrow$  <code>MH_SUBSECTIONS_VIA_SYMBOLS</code>, which indicates the object file’s sections can be divided at symbol boundaries for dead code stripping</li>
    </ul>
  </li>
</ul>]]></content><author><name>Yao Yao</name></author><category term="Compiler" /><summary type="html"><![CDATA[```c // vanilla.c]]></summary></entry><entry><title type="html">Mach-O Stitching #3: Load Commands</title><link href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-3-load-commands" rel="alternate" type="text/html" title="Mach-O Stitching #3: Load Commands" /><published>2026-01-30T00:00:00-08:00</published><updated>2026-01-30T00:00:00-08:00</updated><id>https://listcomp.com/compiler/2026/01/30/mach-o-stitching-3-load-commands</id><content type="html" xml:base="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-3-load-commands"><![CDATA[<h1 id="overview">Overview</h1>

<p>Load commands are <strong>instructions</strong> to the dynamic linker and loader that tell the operating system <strong>how to load and set up a program in memory</strong> when it’s executed.</p>

<p>Why do we need load commanders? When you run a program, the OS doesn’t just dump the file into memory and start executing. It needs to know some information or metadata, which is provided by load commands.</p>

<p>Common load commands include:</p>

<table>
  <thead>
    <tr>
      <th>Load Commands</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>LC_SEGMENT_64</code></td>
      <td>“Map this segment at this address with these permissions”</td>
    </tr>
    <tr>
      <td><code>LC_LOAD_DYLIB</code></td>
      <td>“I need this library to run” (e.g., <code>libc</code>)</td>
    </tr>
    <tr>
      <td><code>LC_MAIN</code></td>
      <td>“Start execution at this offset” (entry point)</td>
    </tr>
    <tr>
      <td><code>LC_DYLD_INFO</code></td>
      <td>“Here’s binding/relocation info for dynamic linking”</td>
    </tr>
    <tr>
      <td><code>LC_CODE_SIGNATURE</code></td>
      <td>“Verify my code signature here”</td>
    </tr>
    <tr>
      <td><code>LC_SYMTAB</code></td>
      <td>“Symbol table is here”</td>
    </tr>
  </tbody>
</table>

<p>The loading process is like:</p>

<ol>
  <li>Kernel reads Mach-O header</li>
  <li>Kernel parses load commands</li>
  <li>For each <code>LC_SEGMENT_64</code>, allocate memory, set permissions, copy data</li>
  <li>For each <code>LC_LOAD_DYLIB</code>, recursively load dependencies</li>
  <li><code>dyld</code> performs symbol binding/relocation</li>
  <li>Jump to entry point specified by <code>LC_MAIN</code></li>
</ol>

<h1 id="example">Example</h1>

<pre><code class="language-c">// vanilla.c

int a = 10;          // stored in __DATA segment

int main() {         // stored in __TEXT segment
    return a;
}
</code></pre>

<pre><code class="language-bash">ᐅ clang -c vanilla.c -o vanilla.o
</code></pre>

<pre><code class="language-bash">ᐅ otool -l vanilla.o  # Display the load commands
</code></pre>

<h2 id="load-command-0-rightarrow-lc_segment_64-rightarrow-引导读取-segmentssections">Load Command #0 $\Rightarrow$ <code>LC_SEGMENT_64</code> $\Rightarrow$ 引导读取 segments/sections</h2>

<pre><code class="language-bash">Load command 0
      cmd LC_SEGMENT_64
  cmdsize 392
  segname                      # segment name is empty, so this is an unnamed segment
   vmaddr 0x0000000000000000   # Virtual Memory Address where this segment should be loaded
   vmsize 0x0000000000000078   # Virtual Memory Size to hold this segment
  fileoff 552                  # this segment's content starts at file (i.e. `vanilla.o`) offset 472 (in byte) 
 filesize 120                  # this segment's content is 112-byte long from `fileoff` in this file 
  maxprot 0x00000007           # 0x7 == Read (4) + Write (2) + Execute (1) permissions
 initprot 0x00000007           # ditto
   nsects 4                    # this unnamed segment has 4 sections
    flags 0x0

Section                        # section #1 is (__TEXT, __text)
  sectname __text              # contains executable code
   segname __TEXT
      addr 0x0000000000000000
      size 0x0000000000000013  # this section is 13-byte long
    offset 552
     align 2^4 (16)
    reloff 672
    nreloc 1
     flags 0x80000400
 reserved1 0
 reserved2 0

Section                        # section #2 is (__DATA, __data)
  sectname __data              # contains initialized global and static variables
   segname __DATA
      addr 0x0000000000000014
      size 0x0000000000000004
       ... ...
       
Section                        # section #3 is (__LD, __compact_unwind)
  sectname __compact_unwind    # contains unwinding information (in Apple's compact format)
   segname __LD
      addr 0x0000000000000018
      size 0x0000000000000020
       ... ...

Section                        # section #4 is (__TEXT, __eh_frame)
  sectname __eh_frame          # contains Exception Handling Frame
   segname __TEXT
      addr 0x0000000000000038
      size 0x0000000000000040
       ... ...
</code></pre>

<blockquote>
  <p>[!NOTE] 注意这个 unnamed segment 起到的是一个 “list of <code>(segment, section)</code>” 的作用</p>

  <p>或者你理解成一个 meta-segment 也行</p>
</blockquote>

<h2 id="load-command-1-rightarrow-lc_build_version-rightarrow-引导-checking-version-compatibility">Load Command 1 $\Rightarrow$ <code>LC_BUILD_VERSION</code> $\Rightarrow$ 引导 checking version compatibility</h2>

<pre><code class="language-bash">Load command 1
      cmd LC_BUILD_VERSION  # Build's Metadata
  cmdsize 24
 platform 1     # magic number, meaning macOS
    minos 13.0  # minimum OS version is macOS 13.0 (Ventura)
      sdk 14.2  # built with macOS 14.2 SDK (Sonoma)
   ntools 0
</code></pre>

<h2 id="load-command-2-rightarrow-lc_symtab-rightarrow-引导读取-symtabstrtab">Load Command 2 $\Rightarrow$ <code>LC_SYMTAB</code> $\Rightarrow$ 引导读取 symtab/strtab</h2>

<pre><code class="language-bash">Load command 2
     cmd LC_SYMTAB  # Symbol Table
 cmdsize 24
  symoff 688  # symbol table is at file (i.e. `vanilla.o`) offset 688 (in byte)
   nsyms 2    # there are 2 symbols
  stroff 720  # string table is at file (i.e. `vanilla.o`) offset 720 (in byte)
 strsize 16   # size of the string table is 16 bytes
</code></pre>

<blockquote>
  <p>[!info] FYI</p>

  <p>The 2 symbols are:</p>

  <pre><code class="language-bash">ᐅ nm vanilla.o
0000000000000014 D _a
0000000000000000 T _main
</code></pre>

  <ul>
    <li><code>T</code>: the symbol is in <code>(__TEXT, __text)</code></li>
    <li><code>D</code>: the symbol is in <code>(__DATA, __data)</code></li>
    <li><code>U</code>: the symbol is undefined</li>
  </ul>
</blockquote>

<h2 id="load-command-3-rightarrow-lc_dysymtab-rightarrow-提供-dynamic-linking-metadata">Load Command 3 $\Rightarrow$ <code>LC_DYSYMTAB</code> $\Rightarrow$ 提供 dynamic linking metadata</h2>

<pre><code class="language-bash">```bash
Load command 3
            cmd LC_DYSYMTAB  # Dynamic Symbol Table
        cmdsize 80

                   ###########################################
                   # Partitions of the existing symbol table #
                   ###########################################
                   # P1: local symbols (non-dynamic)
      ilocalsym 0  # (next available) index (globally in the symtab) for locally defined symbols
      nlocalsym 0  # count of locally defined symbols
      
                   # P2: exported symbols (type 1 of dynamic symbols)
     iextdefsym 0  # (next available) index (globally in the symtab) for externally defined symbols
     nextdefsym 2  # count of externally defined symbols (==1, should be the exported `_main`)
     
                   # P3: imported symbols (type 2 of dynamic symbols)
      iundefsym 2  # (next available) index (globally in the symtab) for undefined symbols
      nundefsym 0  # count of undefined symbols

                   ################################
                   # Pointers to auxiliary tables #
                   ################################
         tocoff 0  # file offset for ToC (rarely used in modern Mach-O; was intended for organizing symbols in multi-module libraries)
           ntoc 0  # count of ToC entries

      modtaboff 0  # file offset for Module Table (rarely used in modern Mach-O; describes individual modules within a multi-module dylib)
        nmodtab 0  # count of Module Table entries
   
   extrefsymoff 0  # file offset for External Reference Table (describes how undefined symbols are used (lazy vs. non-lazy); contains flags indicating reference type)
    nextrefsyms 0  # count of External Reference Table entries
 
 indirectsymoff 0  # file offset for Indirect Symbol Table
  nindirectsyms 0  # count of Indirect Symbol Table entries
  
      extreloff 0  # file offset for External Relocation Entries
        nextrel 0  # count of External Relocation Entries
        
      locreloff 0  # file offser for Local Relocation Entries
        nlocrel 0  # count of Local Relocation Entries
</code></pre>

<blockquote>
  <p>[!caution] <code>extdef</code> means <strong>external &amp; defined</strong>, not <em>externally defined</em></p>

  <p>See <a href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-6-how-to-interpret-dysymtab#experiment-result">Mach-O Stitching #6: How to Interpret Dysymtab &gt; Experiment Result</a>.</p>
</blockquote>

<p>A <strong>dynamic symbol</strong> is a symbol (function or variable name) that is either:</p>

<ul>
  <li><strong>Imported</strong> (thus undefined locally) from an external shared library (<code>dylib</code>) that will be linked at runtime
    <ul>
      <li><strong>可以理解成 “current module’s symbol debts”</strong>, e.g. <code>printf</code></li>
    </ul>
  </li>
  <li><strong>Exported</strong> from the current binary (thus defined locally) so other modules can use it
    <ul>
      <li><strong>可以理解成 “current module’s symbol contribution”</strong>, e.g. current module’s global variables and functions (they are <code>extern</code> by default)</li>
    </ul>
  </li>
</ul>

<blockquote>
  <p>[!caution] The name “dynamic symbol” is MISLEADING</p>

  <p>dynamic symbol 并不意味着 dynamic linking: 它可能被 dynamic linker resolve, 也可能被 static linker resolve</p>
</blockquote>

<blockquote>
  <p>[!caution] The name “dynamic symbol table” is MISLEADING</p>

  <p>首先 there is NO such table as “dynamic symbol table”.</p>

  <p>其次 <code>LC_DYSYMTAB</code> 不仅仅包含了 dynamic symbol 的信息，它还有很多 auxiliary 的信息。</p>
</blockquote>

<blockquote>
  <p>[!NOTE] Why <code>iundefsym = 2</code> but <code>nundefsym = 0</code>?</p>

  <p>Because <code>iundefsym = 2</code> means <strong>“if there were any undefined symbols, they would start at index <code>2</code>“</strong>. Note that symbol <code>_main</code> and <code>_a</code> have occupied indices <code>0</code> and <code>1</code> in our example.</p>

  <p>In short, <code>iundefsym</code> is just pointing to where the undefined symbols <em>would</em> begin in the symbol table, and <code>nundefsym = 0</code> means there are actually <strong>no undefined symbols</strong> at that position.</p>
</blockquote>

<blockquote>
  <p>[!NOTE] Symbol grouping in symtab</p>

  <p>考虑到 <code>LC_DYSYMTAB</code> 这样的设计，我们可以推测出：symtab 中的 symbols 必须要按照 <code>localsym/extdefsym/undefsym</code> 这样做了 grouping.</p>

  <p>symbols 在物理上是不能散乱地排列的。</p>
</blockquote>

<blockquote>
  <p>[!info] symbols 在组内的 sorting</p>

  <p>对于 <code>extdefsym</code> 和 <code>undefsym</code>，Mach-O 还要求在组内按<strong>symbol name 进行排序</strong>。这意味着 <code>dyld</code> 可以使用 binary search 快速定位 symbol, 而不需要线性扫描。</p>
</blockquote>]]></content><author><name>Yao Yao</name></author><category term="Compiler" /><summary type="html"><![CDATA[Overview]]></summary></entry><entry><title type="html">Mach-O Stitching #4: How C Variables Materialize in Segments &amp;amp; Sections</title><link href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-4-how-c-variables-materialize-in-segments-sections" rel="alternate" type="text/html" title="Mach-O Stitching #4: How C Variables Materialize in Segments &amp;amp; Sections" /><published>2026-01-30T00:00:00-08:00</published><updated>2026-01-30T00:00:00-08:00</updated><id>https://listcomp.com/compiler/2026/01/30/mach-o-stitching-4-how-c-variables-materialize-in-segments--sections</id><content type="html" xml:base="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-4-how-c-variables-materialize-in-segments-sections"><![CDATA[<h1 id="code">Code</h1>

<pre><code class="language-c">// test.c

int global_elink = 1;          // External Linkage  =&gt; (__DATA,__data)         
static int global_ilink = 3;   // Internal Linkage  =&gt; (__DATA,__data)
const int constant_val = 7;    // Read-only         =&gt; (__TEXT,__const) 

void func() {
    static int count = 15;     // Static Storage    =&gt; (__DATA,__data)
    int local_val = 31;        // Automatic Storage =&gt; Stack only, no symbol
    
    global_ilink += 0;  // global_ilink might be wiped as a dead symbol even when `clang -O0`
                        // this statement keeps it alive
}
</code></pre>

<pre><code class="language-bash">ᐅ clang -O0 -c test.c -o test.o
</code></pre>

<h1 id="__text-segment"><code>__TEXT</code> Segment</h1>

<pre><code class="language-bash">ᐅ otool -tv test.o  # Display the contents of the (__TEXT,__text) section. 
                    # With the -v flag, this disassembles the text. 
                    # With the -V flag, it also symbolically disassembles the operands.
test.o:
(__TEXT,__text) section
_func:
0000000000000000	pushq	%rbp
0000000000000001	movq	%rsp, %rbp
0000000000000004	movl	$0x1f, -0x4(%rbp)  # int local_val = 31;
000000000000000b	movl	(%rip), %eax
0000000000000011	addl	$0x0, %eax
0000000000000014	movl	%eax, (%rip)
000000000000001a	popq	%rbp
000000000000001b	retq
</code></pre>

<pre><code class="language-bash">ᐅ otool -v -s __TEXT __const test.o  # Display the contents of the (__TEXT,__const) section. 
test.o:
Contents of (__TEXT,__const) section
0000000000000018	07 00 00 00
                    #---# # const int constant_val = 7;
</code></pre>

<p>where <code>07 00 00 00</code> is little-endian <code>0x07</code> for <code>const int constant_val = 7;</code>.</p>

<h1 id="__data-segment"><code>__DATA</code> Segment</h1>

<pre><code class="language-bash">ᐅ otool -dv test.o
test.o:
(__DATA,__data) section
0000000000000010	01 00 00 00 0f 00 00 00 03 00 00 00
                    #---------#                         # int global_elink = 1;
                                #---------#             # static int count = 15;
                                            #---------# # static int global_ilink = 3;
</code></pre>

<p>where:</p>

<ul>
  <li><code>01 00 00 00</code> is little-endian <code>0x01</code> for <code>int global_ext = 1;</code></li>
  <li><code>03 00 00 00</code> is little-endian <code>0x03</code> for <code>static int global_ilink = 3;</code></li>
  <li><code>0f 00 00 00</code> is little-endian <code>0x0f</code> for local <code>static int count = 15;</code></li>
</ul>]]></content><author><name>Yao Yao</name></author><category term="Compiler" /><summary type="html"><![CDATA[Code]]></summary></entry><entry><title type="html">Mach-O Stitching #5: How to Interpret Symtab</title><link href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-5-how-to-interpret-symtab" rel="alternate" type="text/html" title="Mach-O Stitching #5: How to Interpret Symtab" /><published>2026-01-30T00:00:00-08:00</published><updated>2026-01-30T00:00:00-08:00</updated><id>https://listcomp.com/compiler/2026/01/30/mach-o-stitching-5-how-to-interpret-symtab</id><content type="html" xml:base="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-5-how-to-interpret-symtab"><![CDATA[<h1 id="1-lc_symtab-rightarrow-指定-symtabstrtab-的位置">1. <code>LC_SYMTAB</code> $\Rightarrow$ 指定 symtab/strtab 的位置</h1>

<p>我们继续沿用这个代码：</p>

<pre><code class="language-c">// vanilla.c

int a = 10;          // stored in __DATA segment

int main() {         // stored in __TEXT segment
    return a;
}
</code></pre>

<pre><code class="language-bash">ᐅ clang -c vanilla.c -o vanilla.o
</code></pre>

<pre><code class="language-bash">ᐅ otool -l vanilla.o  # Display the load commands
</code></pre>

<p>可得 <code>LC_SYMTAB</code>:</p>

<pre><code class="language-bash">Load command 2
     cmd LC_SYMTAB  # Symbol Table
 cmdsize 24
  symoff 688  # symbol table is at file (i.e. `vanilla.o`) offset 688 (in byte)
   nsyms 2    # there are 2 symbols
  stroff 720  # string table is at file (i.e. `vanilla.o`) offset 720 (in byte)
 strsize 16   # size of the string table is 16 bytes
</code></pre>

<h1 id="2-nlist_64-rightarrow-symtab-的-entry">2. <code>nlist_64</code> $\Rightarrow$ symtab 的 entry</h1>

<p>如果我们 seek 到 file offset <code>688</code>，可以查看 symtab 的 binary contents:</p>

<pre><code class="language-bash">ᐅ xxd -s 688 -l 48 -a -d -e vanilla.o  
# -s: start from &lt;offset&gt;
# -l: length to read
# -a: empty lines (like all '\0' values) are compactly represented by a '*'
# -d: use decimal addresses
# -e: use little-endian
00000688: 00000007 0000020f 00000014 00000000   # for symbol `_a`
00000704: 00000001 0000010f 00000000 00000000   # for symbol `_main`
00000720: 616d5f00 5f006e69 00000061 00000000   ._main._a.......
</code></pre>

<blockquote>
  <p>[!caution] <code>xxd</code> with <code>-e</code> option shows little-endian</p>

  <p>比 big-endian 直观一点。</p>

  <p>endianness 记不住的话，有一个诀窍：我们日常的计数都是 little-endian (least significant byte (LSB) at the lowest memory address).</p>
</blockquote>

<p>symtab 的 entry/record 的 size 是 16 bytes. 每个 entry/record conceptually 是一个 <code>struct nlist_64</code> object:</p>

<pre><code class="language-c">// See https://llvm.org/doxygen/BinaryFormat_2MachO_8h_source.html#l01017
struct nlist_64 {
  uint32_t n_strx;   // char index into the string table
  uint16_t n_desc;   // additional info for non-`N_STAB`-typed symbols
  uint8_t  n_sect;   // number of the section that this symbol can be found in, or `NO_SECT` if the symbol is not to be found in any section.
  uint8_t  n_type;   // type flag
  uint64_t n_value;  // this value is different for each type of symbol, e.g.
                         // for the `N_SECT` symbol type, this is the address (offset from the start of the segment) of the symbol. 
                         // for `N_UNDF | N_EXT`, this is not used.
};
</code></pre>

<p>以 <code>_a</code> 这个 symbol 为例，binary contents 是这么划分的：</p>

<table>
  <thead>
    <tr>
      <th><code>n_strx</code></th>
      <th><code>n_desc</code></th>
      <th><code>n_sect</code></th>
      <th><code>n_type</code></th>
      <th><code>n_value</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>00000007</code></td>
      <td><code>0000</code></td>
      <td><code>02</code></td>
      <td><code>0f</code></td>
      <td><code>00000014 00000000</code></td>
    </tr>
    <tr>
      <td>$4$ bytes</td>
      <td>$2$ bytes</td>
      <td>$1$ byte</td>
      <td>$1$ byte</td>
      <td>$8$ bytes</td>
    </tr>
  </tbody>
</table>

<p><code>objdump --syms</code> 的结果与 <code>nlist_64</code> 也是对应的：</p>

<pre><code class="language-bash">ᐅ objdump --syms vanilla.o

vanilla.o:     file format mach-o-x86-64

SYMBOL TABLE:
0000000000000014 g       0f SECT   02 0000 [.data] _a
0000000000000000 g       0f SECT   01 0000 [.text] _main

# 0000000000000014 == n_value (big-endian)
#               0f == n_type  == 0b 0000 1111
#                     =&gt; g    == "global", i.e. EXTERNAL flag is set (from n_type)
#                     =&gt; SECT == symbol type is N_SECT (from n_type)
#               02 == n_sect
#             0000 == n_desc
#          [.data] == section that this symbol is associated
#               _a == name of this symbol
</code></pre>

<h2 id="n_strx"><code>n_strx</code></h2>

<p>表示这个 symbol 对应的 string (i.e. 这个 symbol 的 name) 在 strtab 中的 index</p>

<blockquote>
  <p>[!caution] 注意这个 index 是 index of characters，不是 index of strings</p>

  <p>比如 <code>n_strx == 2</code> 表示 symbol name 的起始的 char 是 <code>strtab[2]</code>，而不是表示 “the 2nd string”.</p>
</blockquote>

<h2 id="n_type"><code>n_type</code></h2>

<p>是一个 byte value，我们会用以下 4 个 masks 去探测它的值：</p>

\[\operatorname{n\_type} = \underbrace{b_{7} \, b_{6} \, b_{5}}_{\operatorname{N\_STAB}} \;\; \underbrace{b_{4}}_{\operatorname{N\_PEXT}} \;\; \underbrace{b_{3} \, b_{2} \, b_{1}}_{\operatorname{N\_TYPE}} \;\; \underbrace{b_{0}}_{\operatorname{N\_EXT}}\]

<pre><code class="language-c">N_EXT  = 0x01 = 0b 0000 0001  // 查看 `n_type` 的 bit 0
N_TYPE = 0x0e = 0b 0000 1110  // 查看 `n_type` 的 bit 1,2,3, these bits define the type of the symbol.
N_PEXT = 0x10 = 0b 0001 0000  // 查看 `n_type` 的 bit 4
N_STAB = 0xe0 = 0b 1110 0000  // 查看 `n_type` 的 bit 5,6,7

/*************************/
/***** EXTERNAL flag *****/
/*************************/
if n_type &amp; N_EXT == 1:
    // This symbol is an EXTERNAL symbol,
    //   meaning this symbol is either defined outside this file, 
    //   or is defined in this file but can be referenced by other files.

/************************/
/***** SYMBOL TYPES *****/
/************************/
switch (n_type &amp; N_TYPE):
    case N_UNDF == 0x0 == 0b 0000:
        // This symbol is UNDEFINED. 
        // Undefined symbols are symbols referenced in this module but defined in a different module.
        // The `n_sect` field is set to `NO_SECT`

    case N_ABS  == 0x2 == 0b 0010:
        // This symbol is ABSOLUTE (considered DEFINED). 
        // The linker does not change the value of an absolute symbol. 
        // The `n_sect` field is set to `NO_SECT`.
    
    case N_INDR == 0xa == 0b 1010:
        // This symbol is DEFINED to be the same as another symbol. 
        // The `n_value` field is an index into the string table specifying the name of the other symbol. 
        // When that symbol is linked, both this and the other symbol have the same defined type and value.
    
    case N_PBUD == 0xc == 0b 1100:
        // This symbol is considered UNDEFINED and the image is using a PRE-BOUND value for the symbol. 
        // The `n_sect` field is set to `NO_SECT`.

    case N_SECT == 0xe == 0b 1110:
        // This symbol is DEFINED in the section number given in `n_sect`

/*********************************/
/***** PRIVATE EXTERNAL flag *****/
/*********************************/
if n_type &amp; N_PEXT == 1:
    // This symbol is marked as having limited global scope.
    // When the file is fed to the static linker, it clears the `N_EXT` bit for each symbol with the 
    //   `N_PEXT` bit set. (The `ld` option `-keep_private_externs` turns off this behavior.) 
    // With OS X GCC, you can use the `__private_extern__` function attribute to set this bit.

/***************************************************/
/***** SYMBOLIC-DEBUGGING TABLE (stab)-related *****/
/***************************************************/
if n_type &amp; N_STAB != 0:
    // If any of these 3 bits are set, the symbol is a `stab` entry. 
    // In that case, the entire `n_type` field is interpreted as a `stab` value. 
    // See `/usr/include/mach-o/stab.h` for valid `stab` values.
</code></pre>

<h2 id="n_sect"><code>n_sect</code></h2>

<p>An integer specifying the number of the section that this symbol can be found in, or <code>NO_SECT</code> if the symbol is not to be found in any section of this image. The sections are contiguously numbered across segments, starting from 1, according to the order they appear in the <code>LC_SEGMENT</code> load commands.</p>

<h2 id="n_desc"><code>n_desc</code></h2>

<h3 id="explanations">Explanations</h3>

<p>是一个 16-bit value，我们会用以下 3 类 bit masks 去探测它的值：</p>

\[\operatorname{n\_desc} = \underbrace{b_{15} \, b_{14} \, b_{13} \, b_{12} \, b_{11} \, b_{10} \, b_{9} \, b_{8}}_{\text{library ordinal}} \;\; \underbrace{b_{7} \, b_{6} \, b_{5} \, b_{4}}_{\text{attribute flags}} \;\; \underbrace{b_{3} \, b_{2} \, b_{1} \, b_{0}}_{\operatorname{REFERENCE\_TYPE}}\]

<pre><code class="language-c">REFERENCE_TYPE = 0xF = 0b 0000 1111  // 查看 `n_desc` 的 bit 0,1,2,3
// 4 Attribute Flags
REFERENCED_DYNAMICALLY = 0x10 = 0b 0001 0000  // 查看 `n_desc` 的 bit 4
N_DESC_DISCARDED       = 0x20 = 0b 0010 0000  // 查看 `n_desc` 的 bit 5
N_WEAK_REF             = 0x40 = 0b 0100 0000  // 查看 `n_desc` 的 bit 6
N_WEAK_DEF             = 0x80 = 0b 1000 0000  // 查看 `n_desc` 的 bit 7

/***************************/
/***** REFERENCE TYPES *****/
/***************************/
switch (n_desc &amp; REFERENCE_TYPE):
    /***** External References *****/
    case REFERENCE_FLAG_UNDEFINED_NON_LAZY         == 0x0 == 0b 0000:
        // This symbol is a reference to an EXTERNAL NON-LAZY (data) symbol 
        //   (like a global variable from another library).
    case REFERENCE_FLAG_UNDEFINED_LAZY             == 0x1 == 0b 0001:
        // This symbol is a reference to an EXTERNAL LAZY (that will be resolved on first use) symbol 
        //   (that is, to a function call).
    
    /***** Local Definitions *****/
    case REFERENCE_FLAG_DEFINED                    == 0x2 == 0b 0010:
        // This symbol is DEFINED in this module and PUBLICLY VISIBLE.
    case REFERENCE_FLAG_PRIVATE_DEFINED            == 0x3 == 0b 0011:
        // This symbol is DEFINED in this module and PRIVATELY VISIBLE 
        //   only to modules within this shared library.
    
    /***** Private Inter-Module References *****/
    case REFERENCE_FLAG_PRIVATE_UNDEFINED_NON_LAZY == 0x4 == 0b 0100:
        // This symbol is a reference to an NON-LAZY (data) symbol in another module in this file, 
        //   and is PRIVATELY VISIBLE only to modules within this shared library.
    case REFERENCE_FLAG_PRIVATE_UNDEFINED_LAZY     == 0x5 == 0b 0101:
        // This symbol is a reference to an LAZY (that will be resolved on first use) symbol 
        //   (that is, to a function call) in another module in this file, 
        //   and is PRIVATELY VISIBLE only to modules within this shared library.
        
/***************************/
/***** ATTRIBUTE FLAGS *****/
/***************************/
if n_desc &amp; REFERENCED_DYNAMICALLY == 1:
    // This bit marks the symbols that runtime APIs like `dlsym()` might look up by name.
    // The `strip` tool preserves these symbols even during aggressive stripping.

if n_desc &amp; N_DESC_DISCARDED == 1:
    // This bit is reserved for the dynamic linker's internal use at runtime.
    // Don't set this yourself.
    
if n_desc &amp; N_WEAK_REF == 1:
    // This symbol is a weak reference. 
    // If the dynamic linker cannot find a definition for this symbol, it sets the address of this symbol 
    //   to `0` (i.e. `NULL`), instead of causing a link error. 
    // The static linker sets this symbol given the appropriate weak-linking flags.
    
if n_desc &amp; N_WEAK_DEF == 1:
    // This symbol is a weak definition.
    // If the static linker or the dynamic linker finds another (non-weak) definition for this symbol, 
    //   the weak definition is ignored. 
    // Only symbols in a coalesced section (like C++ template instantiations or inline functions that might 
    //   appear in multiple object files) can be marked as a weak definition.
    
/***************************/
/***** LIBRARY ORDINAL *****/
/***************************/
/* 
  Only when two-level namespace binaries is enabled (when `MH_TWOLEVEL` flag is set in the Mach-O header), 
  `n_desc` 的 bit 8,9,10,11,12,13,14,15 构成一个 byte 表示 library ordinal (i.e. the position number or order 
  number of libraries)
  
    - library ordinal `== 0` 表示 "the current image itself"
    - library ordinal `1&lt;=i&lt;=254` 表示 "the $i^{th}$ dylib" (numbered according to the order of 
        `LC_LOAD_DYLIB` load commands in the binary)
    - library ordinal `== 255` 表示 "find this symbol in the executable that loaded me, not in a dylib I load"
 */
</code></pre>

<blockquote>
  <p>[!NOTE] reference/definition distinction</p>

  <p>The distinction exists at the <strong>binary/linker level</strong>, not the source language level (i.e. the distinction is not language-specific). Once any language compiles down to machine code in Mach-O format (on macOS/iOS), the symbol table must distinguish between:</p>

  <ul>
    <li>Symbols this binary provides (definitions)</li>
    <li>Symbols this binary needs from elsewhere (references)</li>
  </ul>
</blockquote>

<h3 id="library-ordinal-255-and-plugins-scenario">Library Ordinal <code>255</code> and Plugins Scenario</h3>

<p>Normally, the dependency flow is:</p>

<pre><code class="language-txt">    Executable program
        ↓ (loads and uses)
    Dynamic library
</code></pre>

<p>The executable depends on the library.</p>

<p>With plugins, the dependency flow is <strong>reversed</strong>:</p>

<pre><code class="language-txt">    Executable program (host application)
        ↑ (plugin uses symbols from)
    Plugin (loaded at runtime)
</code></pre>

<p>The plugin depends on the executable that loaded it.</p>

<p>Imagine a photo editing app with a plugin architecture:</p>

<p><strong>PhotoApp (executable):</strong></p>

<pre><code class="language-c">// Defines utility functions for plugins to use
void registerFilter(const char* name, FilterFunc func);
Image* getCurrentImage();
</code></pre>

<p><strong>BlurPlugin.bundle (plugin):</strong></p>

<pre><code class="language-c">// Uses functions from PhotoApp
void initPlugin() {
    registerFilter("Gaussian Blur", applyBlur);  // Calls function from host app
    Image* img = getCurrentImage();              // Calls function from host app
}
</code></pre>

<p>When the <code>BlurPlugin</code> symbol table has symbols referencing <code>registerFilter</code> and <code>getCurrentImage</code>:</p>

<ul>
  <li>These symbols are <strong>undefined</strong> in the plugin (references)</li>
  <li>Their library ordinal is <strong>255</strong></li>
  <li>This tells the dynamic linker: “find this symbol in the executable that loaded me (i.e. my parent executable), not in a dylib”</li>
</ul>

<p>This allows plugins to call back into the host application that loaded them.</p>

<h2 id="n_value"><code>n_value</code></h2>

<p>An $8$-byte integer that contains the value of the symbol. The format of this value is different for each type of symbol table entry (as specified by the <code>n_type</code> field). For the <code>N_SECT</code> symbol type, <code>n_value</code> is the address of the symbol. See the description of the <code>n_type</code> field for information on other possible values.</p>

<h1 id="3-strtab-rightarrow-本质是个-string-buffer">3. strtab $\Rightarrow$ 本质是个 string buffer</h1>

<p>strtab 紧跟在 symtab 后面。由于我们这里有两个 symbols (<code>nsym == 2</code>)，然后每个 <code>nlist_64</code> 是 16-bytes，所以 strtab 的起始位置在 <code>688 + 16 * 2 == 720</code> offset.</p>

<p>strtab 的本质是一个 C-string 的 sequence. E.g.:</p>

<pre><code class="language-bash">ᐅ xxd -d -s 720 vanilla.o
00000720: 005f 6d61 696e 005f 6100 0000 0000 0000  ._main._a..
</code></pre>

<table>
  <thead>
    <tr>
      <th>Offset</th>
      <th>Content</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>000002d0</code></td>
      <td><code>\0</code></td>
    </tr>
    <tr>
      <td><code>000002d1</code></td>
      <td><code>_main\0</code></td>
    </tr>
    <tr>
      <td><code>000002d7</code></td>
      <td><code>_a\0</code></td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>[!NOTE] strtab 第一个 entry almost always 是 <code>\0</code></p>

  <p>这是个 convention so that (1) unnamed or anonymous symbols (which are rare) can have <code>n_strx == 0</code> and (2) invalid or deleted symbols can have <code>n_strx == 0</code> as a special marker</p>
</blockquote>

<p>在我们的 <code>LC_SYMTAB</code> 例子中，<code>strsize == 16</code> 其实是因为有 padding for alignment，因为理论上来说，一个 <code>\0</code> 加上一个 <code>_main\0</code> 再加上一个 <code>_a\0</code> 只占了 10-byte 的空间，但为了 alignment，这个 strtab 会被 pad 成：</p>

<table>
  <thead>
    <tr>
      <th>Offset</th>
      <th>Content</th>
      <th>Size</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>000002d0</code></td>
      <td><code>\0</code></td>
      <td>1</td>
    </tr>
    <tr>
      <td><code>000002d1</code></td>
      <td><code>_main\0</code></td>
      <td>6</td>
    </tr>
    <tr>
      <td><code>000002d7</code></td>
      <td><code>_a\0</code></td>
      <td>3</td>
    </tr>
    <tr>
      <td><code>000002d8</code></td>
      <td><code>\0\0\0\0\0\0\0</code></td>
      <td>6</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p>[!NOTE] foreign keys to strtab</p>

  <p>可以把 <code>n_strx</code> 理解成 symtab 到 strtab 的一个 foreign key. 这个设计使得这两个 tables 都是 aligned, reading 的效率非常高</p>
</blockquote>

<h1 id="4-symtabstrtab-的物理位置">4. symtab/strtab 的物理位置</h1>

<p>如果我们拿到 executable:</p>

<pre><code class="language-bash">ᐅ clang vanilla.o -o vanilla.out
</code></pre>

<p>再去和 object file 对比会发现：</p>

<ul>
  <li>symta/strtab 在 object file 中不属于任何的 segment/section, 它只是单纯地 appended 在 object file 的尾部</li>
  <li>symta/strtab 在 executable 中属于 <code>__LINKEDIT</code> segment</li>
</ul>

<p>这个判断依据是：object file 中，<code>LC_SYMTAB</code> 的 <code>symoff</code> 和 <code>stroff</code> 和其他的 segment/section 没有重叠；而 executable 中的情况是：</p>

<pre><code class="language-bash">Load command 3
      cmd LC_SEGMENT_64
  cmdsize 72
  segname __LINKEDIT
   vmaddr 0x0000000100008000
   vmsize 0x0000000000004000
  fileoff 32768
 filesize 208     # __LINKEDIT 的 range 是 [32768, 32976)
      ... ...
      
Load command 6
     cmd LC_SYMTAB
 cmdsize 24
  symoff 32896
   nsyms 3        # symtab 的 range 是 [32896, 32944), inside __LINKEDIT
  stroff 32944
 strsize 32       # strtab 的 range 是 [32944, 32976), inside __LINKEDIT
</code></pre>

<blockquote>
  <p>[!note] <code>__LINKEDIT</code> segment typically contains <strong>no sections</strong>, unlike <code>__TEXT</code> and <code>__DATA</code></p>

  <p>It’s just a blob of raw linking metadata that other load commands (like <code>LC_SYMTAB</code>, <code>LC_DYSYMTAB</code>, <code>LC_DYLD_INFO</code>) reference by providing offsets into it.</p>

  <p>也真是这个原因，我们用 <code>otool</code> 没法查看 <code>__LINKEDIT</code></p>
</blockquote>]]></content><author><name>Yao Yao</name></author><category term="Compiler" /><summary type="html"><![CDATA[1. LC_SYMTAB $\Rightarrow$ 指定 symtab/strtab 的位置]]></summary></entry><entry><title type="html">Mach-O Stitching #6: How to Interpret Dysymtab</title><link href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-6-how-to-interpret-dysymtab" rel="alternate" type="text/html" title="Mach-O Stitching #6: How to Interpret Dysymtab" /><published>2026-01-30T00:00:00-08:00</published><updated>2026-01-30T00:00:00-08:00</updated><id>https://listcomp.com/compiler/2026/01/30/mach-o-stitching-6-how-to-interpret-dysymtab</id><content type="html" xml:base="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-6-how-to-interpret-dysymtab"><![CDATA[<h1 id="basic-structure-of-dysymtab">Basic Structure of Dysymtab</h1>

<p>我们在 <a href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-3-load-commands#load-command-3-rightarrow-lc_dysymtab-rightarrow-%E6%8F%90%E4%BE%9B-dynamic-linking-metadata">Mach-O Stitching #3: Load Commands &gt; <code>LC_DYSYMTAB</code></a> 中已经详述。</p>

<h1 id="experiment-code">Experiment Code</h1>

<pre><code class="language-c">// secondary.c
int shared_int = 42;
int shared_float = 100.0;
void shared_func() {}
</code></pre>

<pre><code class="language-c">// main.c
extern int shared_int; 
extern float shared_float;
void shared_func();

int main() { 
    shared_int = 43;
    shared_float = 101.0;
    shared_float = 102.0;
    shared_func(); 
    shared_func(); 
    shared_func(); 
    return 0; 
}
</code></pre>

<pre><code class="language-bash"># ᐅ clang -O0 -c secondary.c -o secondary.o
ᐅ clang -O0 -c main.c -o main.o
# ᐅ clang -O0 main.o secondary.o -o main.out
</code></pre>

<p>这里是一个大的 static linking 的 experiment code，但演示 dysymtab 只用 <code>main.o</code> 就可以了。</p>

<h1 id="experiment-result">Experiment Result</h1>

<p>查看 object file 的 symtab:</p>

<pre><code class="language-bash">ᐅ objdump --syms main.o
main.o:     file format mach-o-x86-64

SYMBOL TABLE:
0000000000000000 g       0f SECT   01 0000 [.text] _main
0000000000000000 g       01 UND    00 0000 _shared_float
0000000000000000 g       01 UND    00 0000 _shared_func
0000000000000000 g       01 UND    00 0000 _shared_int
</code></pre>

<p>查看 object file 的 dysymtab:</p>

<pre><code class="language-bash">ᐅ ᐅ otool -l main.o | grep -B 1 -A 19 "LC_DYSYMTAB"
Load command 3
            cmd LC_DYSYMTAB
        cmdsize 80
        
      ilocalsym 0  # starting from symtab[0]
      nlocalsym 0  # there is 0 localsym
     
     iextdefsym 0  # starting from symtab[0]
     nextdefsym 1  # there is 1 extdefsym 
                   # meaning symtab[0] == `_main` is external defined
      
      iundefsym 1  # starting from symtab[1]
      nundefsym 3  # there are 3 undefsyms 
                   # meaning symtab[1:4] == [`_shared_float`, `_shared_func`, `_shared_int`] are undefined
            ... ...
</code></pre>

<blockquote>
  <p>[!NOTE] Symbol Grouping &amp; Sorting</p>

  <p>See <a href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-3-load-commands#load-command-3-rightarrow-lc_dysymtab-rightarrow-%E6%8F%90%E4%BE%9B-dynamic-linking-metadata">Mach-O Stitching #3: Load Commands &gt; <code>LC_DYSYMTAB</code></a></p>
</blockquote>

<blockquote>
  <p>[!NOTE] <code>extdef</code> means <strong>external &amp; defined</strong>, not <em>externally defined</em></p>

  <ul>
    <li>if <code>n_type &amp; N_EXT == 1</code> $\Rightarrow$ symbol is external</li>
    <li>if <code>n_type &amp; N_TYPE == N_ABS or N_INDR or N_SECT</code> $\Rightarrow$ symbol is defined</li>
    <li>if both of the above conditions are satisfied $\Rightarrow$ symbol is a <code>extdefsym</code></li>
  </ul>

  <p>See <a href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-5-how-to-interpret-symtab#n_type">Mach-O Stitching #5: How to Interpret Symtab &gt; <code>n_type</code></a>.</p>
</blockquote>

<blockquote>
  <p>[!NOTE] <code>_main</code> 与 <code>LC_MAIN</code></p>

  <p><code>_main</code> 通常被视为 <code>extdef</code>, 目的是为了让 OS 或者 <code>dyld</code> 能够从文件外部“看到”并跳转到这个函数。如果你把 <code>main</code> 设为 <code>static</code> (虽然 C 标准不允许这样做)，linker 就找不到它，程序也就无法启动。</p>

  <p><code>LC_MAIN</code> 中指定的 offset 就是 <code>_main</code> 在 executable 中的起始位置。</p>
</blockquote>]]></content><author><name>Yao Yao</name></author><category term="Compiler" /><summary type="html"><![CDATA[Basic Structure of Dysymtab]]></summary></entry><entry><title type="html">Mach-O Stitching #7: How to Interpret Relocs (Relocation Entries)</title><link href="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-7-how-to-interpret-relocs-relocation-entries" rel="alternate" type="text/html" title="Mach-O Stitching #7: How to Interpret Relocs (Relocation Entries)" /><published>2026-01-30T00:00:00-08:00</published><updated>2026-01-30T00:00:00-08:00</updated><id>https://listcomp.com/compiler/2026/01/30/mach-o-stitching-7-how-to-interpret-relocs-relocation-entries</id><content type="html" xml:base="https://listcomp.com/compiler/2026/01/30/mach-o-stitching-7-how-to-interpret-relocs-relocation-entries"><![CDATA[<p>可参考 CSAPP $\rhd$ Chapter 7 – Linking $\rhd$ Sec. 7.7 – Relocation.</p>

<hr />

<h1 id="experiment-code">Experiment Code</h1>

<pre><code class="language-c">// secondary.c
int shared_int = 42;
int shared_float = 100.0;
void shared_func() {}
</code></pre>

<pre><code class="language-c">// main.c
extern int shared_int; 
extern float shared_float;
void shared_func();

int main() { 
    shared_int = 43;
    shared_float = 101.0;
    shared_float = 102.0;
    shared_func(); 
    shared_func(); 
    shared_func(); 
    return 0; 
}
</code></pre>

<pre><code class="language-bash">ᐅ clang -O0 -c secondary.c -o secondary.o
ᐅ clang -O0 -c main.c -o main.o
ᐅ clang -O0 main.o secondary.o -o main.out
</code></pre>

<h1 id="relocs-的物理位置">Relocs 的物理位置</h1>

<blockquote>
  <p>[!caution] 一般称为 “relocation entries” 而不是 “relocation table”</p>

  <p>虽然当你说 “relocation table” 时，somehow 大家也能懂你说的是啥，但更常用的叫法是 “relocation entries”.</p>
</blockquote>

<p>in object files:</p>

<ul>
  <li>✅ relocs 存储在 section 外部，有点类似 symtab/strtab 的情况 (Mach-O 是如此；ELF 略有不同)
    <ul>
      <li>每个 section 都有自己的 <code>reloff/nreloc</code> 字段</li>
      <li>但 <code>reloff</code> 不在 section 自己的范围内</li>
    </ul>
  </li>
  <li>❌ relocs 不存储在 <code>LC_DYLD_INFO</code> 或者 <code>LC_DYLD_INFO_ONLY</code></li>
  <li>❌ relocs 不存储在 dysymtab 的 <code>extreloff/nextrel</code> 或者 <code>locreloff/nlocrel</code> 字段规定的范围内</li>
</ul>

<pre><code class="language-txt">Mach-O Header
Load Commands
  - Segment commands
  - Section headers (with reloff/nreloc)

Section Data:
  __text section data
  __data section data
  __const section data
  ...

Relocation Entries:
  Relocations for __text
  Relocations for __data
  Relocations for __const
  ...

Symbol Table
String Table
</code></pre>

<p>in executables:</p>

<ul>
  <li>dynamic linking 的 relocation 过程已经完成了，剩下一些 dynamic linking 要用的 relocation 信息已经不能称为 relocs 了</li>
  <li>所以狭义来讲，executable 中没有 relocs</li>
  <li>但广义上的 dynamic linking 的 relocation 信息还是有的，但也不是 relocs 的格式了</li>
</ul>

<h1 id="查看-relocs">查看 Relocs</h1>

<h2 id="human-readable-textual-representation">Human-Readable Textual Representation</h2>

<p>直接用 <code>otool</code> 查看的话：</p>

<pre><code class="language-bash">ᐅ otool -r main.o  # Display the relocation entries. 
main.o:

Relocation information (__TEXT,__text) 8 entries
address  pcrel length extern type    scattered symbolnum/value
00000053 1     2      1      2       0         2
0000004c 1     2      1      2       0         2
00000045 1     2      1      2       0         2
0000003a 1     2      0      1       0         2
00000032 1     2      1      3       0         1
00000027 1     2      0      1       0         2
0000001f 1     2      1      3       0         1
00000012 1     2      1      3       0         3

Relocation information (__LD,__compact_unwind) 1 entries
address  pcrel length extern type    scattered symbolnum/value
00000000 0     3      0      0       0         1
</code></pre>

<p>有点难读，给 <code>otool</code> 加上 <code>-v</code> option 再看看：</p>

<pre><code class="language-bash">ᐅ otool -rv main.o
main.o:

Relocation information (__TEXT,__text) 8 entries
address  pcrel length extern type    scattered symbolnum/value
00000053 True  long   True   BRANCH  False     _shared_func
0000004c True  long   True   BRANCH  False     _shared_func
00000045 True  long   True   BRANCH  False     _shared_func
0000003a True  long   False  SIGNED  False     2 (__TEXT,__literal4)
00000032 True  long   True   GOT_LD  False     _shared_float
00000027 True  long   False  SIGNED  False     2 (__TEXT,__literal4)
0000001f True  long   True   GOT_LD  False     _shared_float
00000012 True  long   True   GOT_LD  False     _shared_int

Relocation information (__LD,__compact_unwind) 1 entries
address  pcrel length extern type    scattered symbolnum/value
00000000 False quad   False  UNSIGND False     1 (__TEXT,__text)
</code></pre>

<blockquote>
  <p>[!NOTE] Float numbers like <code>101.0</code> are stored in <code>(__TEXT, __literal4)</code> section for optimization</p>

  <p><code>2 (__TEXT,__literal4)</code> 前面的 <code>2</code> 表示 offset $2$ in the section.</p>

  <p>但 <code>43</code> 这样的 <code>int</code> 就不需要这么处理。原因是：<code>float</code> 的 size 太大，直接塞到 assembly instruction 里面会导致 instruction 本身的 size 太大；而 <code>int</code> 就不会有这个问题。</p>

  <p>这算是个比较特殊的 reloc，我们这里就不深究了。我们这里专注于 symbols.</p>
</blockquote>

<p>我们拿这条输出为例：</p>

<pre><code class="language-bash">  address  pcrel length extern type    scattered symbolnum/value
  00000012 True  long   True   GOT_LD  False     _shared_int
# 00000012 1     2      1      3       0         3
</code></pre>

<p>它的意思是：</p>

<ul>
  <li><code>address</code> 指 section 内部的 offset，意思是 “在 section 的这个位置上有一个需要 relocation 的 symbol”
    <ul>
      <li>注意这里 <code>00000012</code> 是 <code>0x12</code> 的意思</li>
    </ul>
  </li>
  <li><code>pcrel</code>is a flag indicating if the relocation is PC-relative</li>
  <li><code>length</code> represents the size of the symbol to be relocated
    <ul>
      <li><code>0</code> = byte, <code>1</code> = word, <code>2</code> = long, <code>3</code> = quad</li>
      <li>length 为 $i$ 则表示长度为 $2^i$ bytes</li>
    </ul>
  </li>
  <li><code>extern</code> is a flag indicating if the symbol is external</li>
  <li><code>type</code>: see <code>&lt;mach-o/x86_64/reloc.h&gt;</code>，比如：
    <ul>
      <li><code>X86_64_RELOC_UNSIGNED</code> $\Rightarrow$ 绝对地址模式</li>
      <li><code>X86_64_RELOC_GOT_LOAD</code> $\Rightarrow$ <code>movq symbol@GOTPCREL(%rip), %rax</code> 模式</li>
      <li><code>X86_64_RELOC_BRANCH</code> $\Rightarrow$ <code>call/jmp</code> 指令的分支目标</li>
      <li>等等</li>
    </ul>
  </li>
  <li><code>scattered</code> is a flag indicating if the relocation is scattered (rarely used)
    <ul>
      <li>比方说当你的 section 非常大，你 <code>address</code> 即使溢出也表示不到一个合法的 section offset，此时就要用到 scattered relocation 技术</li>
    </ul>
  </li>
  <li><code>symbolnum/value</code>:
    <ul>
      <li>if <code>extern == True</code>, this is the symbol index</li>
      <li>if <code>extern == False</code>, this is the section ordinal</li>
    </ul>
  </li>
</ul>

<h2 id="binary-representation">Binary Representation</h2>

<p>我们这里以 <code>(__TEXT, __text)</code> section 的 relocs 为例 (注意其他的 section 也可能会有 relocs):</p>

<ol>
  <li>用 <code>otool -l</code> 查看 section 的 <code>LC_SEGMENT_64</code> 中的 <code>reloff/nreloc</code> 字段</li>
  <li>用 <code>xxd -s</code> 定位到 object file 的 <code>reloff</code> 位置</li>
  <li>每条 reloc 的 size 是 8-byte</li>
</ol>

<p>还是以这条 reloc 为例：</p>

<pre><code class="language-bash">  address  pcrel length extern type    scattered symbolnum/value
  00000012 True  long   True   GOT_LD  False     _shared_int
# 00000012 1     2      1      3       0         3
</code></pre>

<p>它对应的 binary raw data 是：</p>

<pre><code class="language-bash"># -d: use decimal addresses
# -e: use little endian
# 2000 is the `reloff` of `(__TEXT, __text)`
ᐅ xxd -s 2000 -d -e main.o
00002000: 00000053 2d000002 0000004c 2d000002
00002016: 00000045 2d000002 0000003a 15000002
00002032: 00000032 3d000001 00000027 15000002
00002048: 0000001f 3d000001 00000012 3d000003
                            #---------------#  &lt;- reloc for `_shared_int` at 0x00000012
</code></pre>

<p>如果从 LSB (least significant bit) 开始，这 8-byte 可以拆分成这么一个结构：</p>

<pre><code class="language-c">struct relocation_info {
    uint32_t r_symbolnum:24, // bit 0-23
             r_pcrel:1,      // bit 24
             r_length:2,     // bit 25-26
             r_extern:1,     // bit 27
             r_type:4;       // bit 28-31
    int32_t  r_address;      // bit 32-63 
};
</code></pre>

<p>于是有：</p>

<table>
  <thead>
    <tr>
      <th>Fields</th>
      <th><code>r_address</code></th>
      <th> </th>
      <th><code>r_symbolnum</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Hex. Values</td>
      <td><code>0x00000012</code></td>
      <td><code>0x3d</code></td>
      <td><code>0x000003</code></td>
    </tr>
  </tbody>
</table>

<p>而 <code>0x3d == 0b 0011 1101</code>，所以：</p>

<table>
  <thead>
    <tr>
      <th>Fields</th>
      <th><code>r_symbolnum</code></th>
      <th><code>r_extern</code></th>
      <th><code>r_length</code></th>
      <th><code>r_pcrel</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Bin. Values</td>
      <td><code>0011</code></td>
      <td><code>1</code></td>
      <td><code>10</code></td>
      <td><code>1</code></td>
    </tr>
    <tr>
      <td>Dec. Values</td>
      <td><code>3</code></td>
      <td><code>1</code></td>
      <td><code>2</code></td>
      <td><code>1</code></td>
    </tr>
  </tbody>
</table>

<p>你会发现还差一个 <code>scattered</code> flag 在 binary raw data 中没有体现，这是因为这个 flag 可以用 <code>address</code> 推断：</p>

<ul>
  <li>non-scattered relocation $\iff$ <code>MSB(r_address) == 0</code>  (<code>address</code> 的 most significant bit，也就是 reloc 的 bit $63$)</li>
  <li>scattered relocation $\iff$ <code>MSB(r_address) == 1</code></li>
</ul>

<p>这么算下来，reloc 的 binary raw data 与 <code>otool -r</code> 的输出是一致的。</p>

<h1 id="digression-matching-c-statements-to-assembly-instructions">Digression: Matching C Statements to Assembly Instructions</h1>

<p>Relocs 需要配合 assembly code 一起理解，所以这里我们要先搞清楚 “哪些 assembly instructions 对应了哪个 C Statement” 这个问题。</p>

<p>其实有很多办法，但下面这两个方法结合起来用会更方便理解。</p>

<h2 id="方法一直接编译-c-rightarrow-assembly-以下称-s-assembly">方法一：直接编译 C $\Rightarrow$ Assembly (以下称 “<code>.s</code> assembly”)</h2>

<pre><code class="language-bash">ᐅ clang -S -O0 main.c -fverbose-asm -o main.s
</code></pre>

<p>得到的 assembly 是：</p>

<pre><code class="language-nasm">	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 13, 0
	.section	__TEXT,__literal4,4byte_literals
	.p2align	2, 0x0                          ## -- Begin function main
LCPI0_0:
	.long	0x42cc0000                      ## float 102
LCPI0_1:
	.long	0x42ca0000                      ## float 101
	.section	__TEXT,__text,regular,pure_instructions
	.globl	_main
	.p2align	4, 0x90
_main:                                  ## @main
	.cfi_startproc
## %bb.0:
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset %rbp, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register %rbp
	subq	$16, %rsp
	movl	$0, -4(%rbp)
	movq	_shared_int@GOTPCREL(%rip), %rax
	movl	$43, (%rax)
	movq	_shared_float@GOTPCREL(%rip), %rax
	movss	LCPI0_1(%rip), %xmm0            ## xmm0 = [1.01E+2,0.0E+0,0.0E+0,0.0E+0]
	movss	%xmm0, (%rax)
	movq	_shared_float@GOTPCREL(%rip), %rax
	movss	LCPI0_0(%rip), %xmm0            ## xmm0 = [1.02E+2,0.0E+0,0.0E+0,0.0E+0]
	movss	%xmm0, (%rax)
	movb	$0, %al
	callq	_shared_func
	movb	$0, %al
	callq	_shared_func
	movb	$0, %al
	callq	_shared_func
	xorl	%eax, %eax
	addq	$16, %rsp
	popq	%rbp
	retq
	.cfi_endproc
                                        ## -- End function
.subsections_via_symbols
</code></pre>

<p>注意 register 的名称：</p>

<ul>
  <li><code>%rbp</code>: Register of Base Pointer (points to the base of current stack frame)</li>
  <li><code>%rsp</code>: Register of Stack Pointer (points to the top of the stack; a.k.a. $SP$)</li>
  <li><code>%rax</code>: Register of Accumulator (this is a general purpose register)</li>
  <li><code>%rip</code>: Register of Instruction Pointer (points to the next instruction; a.k.a. program counter or $PC$)</li>
</ul>

<p>注意 register 的逻辑：</p>

<ul>
  <li>register name 是固定的，你可以理解成一个 constant pointer to a fixed address</li>
  <li><code>(%rxx)</code> 表示 use the value stored in <code>%rxx</code> as an address</li>
</ul>

<p>注意这几个 <code>move</code> 命令的区别：</p>

<ul>
  <li><code>movq</code>: Move a Quadword (8-byte)</li>
  <li><code>movl</code>: Move a Longword (4-byte)</li>
  <li><code>movess</code>: Move a Scalar Single-precision (4-byte)</li>
  <li><code>movb</code>: Move a Byte (1-byte)</li>
</ul>

<h2 id="方法二先编译-c-rightarrow-object然后从-object-中反编译出-assembly-以下称-o-assembly">方法二：先编译 C $\Rightarrow$ Object，然后从 object 中反编译出 assembly (以下称 “<code>.o</code> assembly”)</h2>

<pre><code class="language-bash"># -g: 编译时附带 debug 信息
ᐅ clang -g -O0 -c main.c -o main.o

# -S: 反汇编时显示源码 
ᐅ llvm-objdump -S main.o
</code></pre>

<p>得到的结果是：</p>

<pre><code class="language-nasm">main.o:	file format mach-o 64-bit x86-64

Disassembly of section __TEXT,__text:

0000000000000000 &lt;_main&gt;:
; int main() {
       0: 55                           	pushq	%rbp
       1: 48 89 e5                     	movq	%rsp, %rbp
       4: 48 83 ec 10                  	subq	$0x10, %rsp
       8: c7 45 fc 00 00 00 00         	movl	$0x0, -0x4(%rbp)
;     shared_int = 43;
       f: 48 8b 05 00 00 00 00         	movq	(%rip), %rax            ## 0x16 &lt;_main+0x16&gt;
      16: c7 00 2b 00 00 00            	movl	$0x2b, (%rax)
;     shared_float = 101.0;
      1c: 48 8b 05 00 00 00 00         	movq	(%rip), %rax            ## 0x23 &lt;_main+0x23&gt;
      23: f3 0f 10 05 39 00 00 00      	movss	0x39(%rip), %xmm0       ## 0x64 &lt;_main+0x64&gt;
      2b: f3 0f 11 00                  	movss	%xmm0, (%rax)
;     shared_float = 102.0;
      2f: 48 8b 05 00 00 00 00         	movq	(%rip), %rax            ## 0x36 &lt;_main+0x36&gt;
      36: f3 0f 10 05 22 00 00 00      	movss	0x22(%rip), %xmm0       ## 0x60 &lt;_main+0x60&gt;
      3e: f3 0f 11 00                  	movss	%xmm0, (%rax)
;     shared_func();
      42: b0 00                        	movb	$0x0, %al
      44: e8 00 00 00 00               	callq	0x49 &lt;_main+0x49&gt;
;     shared_func();
      49: b0 00                        	movb	$0x0, %al
      4b: e8 00 00 00 00               	callq	0x50 &lt;_main+0x50&gt;
;     shared_func();
      50: b0 00                        	movb	$0x0, %al
      52: e8 00 00 00 00               	callq	0x57 &lt;_main+0x57&gt;
;     return 0;
      57: 31 c0                        	xorl	%eax, %eax
      59: 48 83 c4 10                  	addq	$0x10, %rsp
      5d: 5d                           	popq	%rbp
      5e: c3
</code></pre>

<h1 id="how-a-reloc-guides-relocation">How a Reloc Guides Relocation</h1>

<blockquote>
  <p>[!caution] Relocs are a TODO list</p>

  <p>注意这个逻辑关系：</p>

  <ul>
    <li>relocs 是 assembler 生成的，它相当于是一个 TODO list</li>
    <li>relocation 是 static-linker 执行的，它在执行的时候要参考 relocs</li>
  </ul>

  <p>所以我认为 relocs 叫 <strong>Future</strong> Relocation Entries 会更好理解</p>
</blockquote>

<blockquote>
  <p>[!caution] dynamic linking 也会有 “relocation” 操作，但执行逻辑与 static linker 不同，它也不需要 relocs</p>

  <p>dynamic linker 参考的是 rebasing 和 binding 信息，这些信息的格式与 relocs 不同</p>
</blockquote>

<blockquote>
  <p>[!NOTE] 用词</p>

  <p>现在一般的用法是：</p>

  <ul>
    <li><em>relocation</em> 专指 “static linker 的 relocation”</li>
    <li>“dynamic linker 的 relocation” 用 <em>fixup</em> 表示</li>
  </ul>
</blockquote>

<h2 id="lets-find-the-relocation-site-first">Let’s find the relocation site first</h2>

<p>我们还是以这条 reloc 为例：</p>

<pre><code class="language-bash">address  pcrel length extern type    scattered symbolnum/value
00000012 True  long   True   GOT_LD  False     _shared_int
</code></pre>

<p>它针对的是这句 instruction:</p>

<pre><code class="language-nasm">;     shared_int = 43;
       f: 48 8b 05 00 00 00 00         	movq	(%rip), %rax
  # addr: 0f 10 11 12 13 14 15
                   ##          &lt;- reloc &lt;address == 0x12&gt;
                   #---------# &lt;- reloc &lt;length == long == 4-byte&gt; 
  
      16: c7 00 2b 00 00 00            	movl	$0x2b, (%rax)
</code></pre>

<p>那么这条 reloc 的意思就是：<strong><code>0x0f</code> 这句 instruction 的后面 4 bytes (对应 symbol <code>_shared_int</code>) 需要 patch</strong>.</p>

<h2 id="assembly-小课堂">Assembly 小课堂</h2>

<p>我们这一节的目的是拆解 <code>48 8b 05 00 00 00 00</code>.</p>

<p>首先完整的 x86 指令编码方案可以表示为：</p>

<pre><code class="language-txt">┌────────┬────────┬────────┬─────┬──────────────┬───────────┐
│ Prefix │ Opcode │ ModR/M │ SIB │ Displacement │ Immediate │
└────────┴────────┴────────┴─────┴──────────────┴───────────┘
   可选      必需      可选    可选       可选           可选
</code></pre>

<p>下面我们讨论这些 fields 的意义。</p>

<h3 id="prerequisite-addressing-modes">Prerequisite: Addressing Modes</h3>

<pre><code class="language-nasm"># Register Addressing (寄存器寻址)
    # 不访问内存，完全在 CPU 内部完成
movq %rbx, %rax  # 将 rbx 的值复制到 rax

# Immediate Addressing (立即数寻址)
    # 常用于给变量赋初值
    # 吐槽：这里根本没有 "寻址" 动作，你的 immediate 直接就在 instruction 内部，寻什么寻？
movq $100, %rax    # 将数值 100 放入 rax

# Register Indirect Addressing (寄存器间接寻址)
    # 类似于 C 语言中的 dereferencing
movq (%rbx), %rax  # 类似于 rax = *rbx

# Displacement Addressing (偏移量寻址)
    # 这是函数局部变量访问的标准方式
movl -8(%rbp), %eax  # 类似于 eax = *(%rbp - 8)
                     # %rbp 是 stack top

# SIB Addressing (比例索引寻址)
    # final_addr == base + index * scale + displacement
    # 吐槽：虽然名字 SIB 只包括了 Scale/Index/Base，但是 displacement 是存在的
    # 这是访问数组元素的标准方式
movl 8(%rbx, %rcx, 4), %eax  # 类似于 eax = *(%rbx + %rcx * 4 + 8) 

# RIP-Relative Addressing (RIP 相对寻址)
    # 访问全局变量的标准方式
movq _my_global_var(%rip), %rax  # 访问全局变量 _my_global_var，它的地址由 RIP 相对计算
</code></pre>

<blockquote>
  <p>[!faq] 是否存在 “immediate indirect addressing”? 形如 <code>rax = *(100)</code> where <code>100</code> is an address</p>

  <p><strong>否</strong>。你大可以把 immediate 存入某个 register，然后再 register indirect addressing.</p>
</blockquote>

<blockquote>
  <p>[!faq] Register Indirect Addressing 和 Displacement Addressing 是 SIB Addressing 的特殊形式吗？</p>

  <p><strong>逻辑上可以这么认为</strong>。但是区分开来是因为它们的应用场景显著不同。</p>
</blockquote>

<blockquote>
  <p>[!faq] RIP-Relative Addressing 是 SIB Addressing 的特殊形式吗？</p>

  <p><strong>逻辑上有相似之处，但底层编码差异很大</strong>。原因是：</p>

  <ul>
    <li><code>%rip</code> 在 CPU 架构中是非常特殊的寄存器，它由分支预测器和取指单元控制，并不像 <code>%rax</code>、<code>%rbx</code> 那样位于通用寄存器组 (GPRs) 中</li>
    <li>而 SIB 的设计初衷是使用 GPRs</li>
    <li>如果你硬要把 <code>%rip</code> 引入 SIB 计算电路中，需要额外的硬件连线，这会增加芯片设计的复杂度</li>
  </ul>
</blockquote>

<blockquote>
  <p>[!faq] SIB Addressing 如何读取数据元素？</p>

  <p>假设你有一个 <code>struct</code> 的 array，每个 <code>struct</code> 的 size 是 8-byte，然后要访问的 field <code>foo</code> 在 <code>struct</code> 内的 offset 是 4-byte，当前 index 是 <code>i</code>，那么 <code>array[i].foo</code> 就能编译成：<code>4(%rax, %rbx, 8)</code> where:</p>

  <ul>
    <li><code>%rax == array</code> 即 array 的首地址</li>
    <li><code>%rbx == i</code></li>
  </ul>
</blockquote>

<h3 id="overlineoperatornamemodrm-field-and-the-second-operand">$\overline{\operatorname{ModR/M}}$ Field and the Second Operand</h3>

<p>$\operatorname{ModR/M}$ 的 general form 是：</p>

<pre><code class="language-txt">┌───────┬────────┬────────┐
│  Mod  │  Reg   │  R/M   │
└───────┴────────┴────────┘
   2-bit   3-bit    3-bit
</code></pre>

<ul>
  <li>$\overline{\operatorname{Mod}}$ means “mode”</li>
  <li>$\overline{\operatorname{R/M}}$ means “register or memory”, 但实际应用中也常用来表示某种 mode, 这算是一种 tech debt 了</li>
</ul>

<p>把它放回 x86 指令编码方案中有：</p>

<pre><code class="language-txt">┌────────┬────────┬────────┬─────┬──────────────┬───────────┐
│ Prefix │ Opcode │ ModR/M │ SIB │ Displacement │ Immediate │
└────────┴────────┴────────┴─────┴──────────────┴───────────┘
            ______/        \_______
           /                       \
          ┌───────┬────────┬────────┐
          │  Mod  │  Reg   │  R/M   │
          └───────┴────────┴────────┘
</code></pre>

<p>考虑 <code>op operand1 operand2</code> 这个 general form：</p>

<ul>
  <li><code>op</code> 自然是被 encode 到了 $\overline{\operatorname{Opcode}}$</li>
  <li><code>operand1</code> 一定会被 encode 到 $\overline{\operatorname{Reg}}$</li>
  <li>但是 <code>operand2</code> 就要靠 $\overline{\operatorname{Mod}}$ 和 $\overline{\operatorname{R/M}}$ 一起决定了</li>
</ul>

<table>
  <thead>
    <tr>
      <th>$\overline{\operatorname{Mod}}$</th>
      <th>$\overline{\operatorname{R/M}}$</th>
      <th>Addressing</th>
      <th>Remark</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>11</code></td>
      <td>此时必定为 register 编号</td>
      <td><code>%reg</code></td>
      <td> </td>
    </tr>
    <tr>
      <td><code>01</code></td>
      <td>只要不是 <code>100</code>，则必定为 register 编号</td>
      <td><code>disp8(%reg)</code></td>
      <td>默认 $\overline{\operatorname{SIB}}$ 为空，$\overline{\operatorname{R/M}}$ 后面的 1-byte 为 $\overline{\operatorname{Displacement}}$</td>
    </tr>
    <tr>
      <td><code>10</code></td>
      <td>只要不是 <code>100</code>，则必定为 register 编号</td>
      <td><code>disp32(%reg)</code></td>
      <td>默认 $\overline{operatorname{SIB}}$ 为空，$\overline{\operatorname{R/M}}$ 后面的 4-byte 为 $\overline{\operatorname{Displacement}}$</td>
    </tr>
    <tr>
      <td><code>00</code></td>
      <td>只要不是 <code>100</code> 或者 <code>101</code></td>
      <td><code>(%reg)</code></td>
      <td> </td>
    </tr>
    <tr>
      <td><code>00</code></td>
      <td><code>101</code></td>
      <td><code>disp32(%rip)</code></td>
      <td>RIP-Relative addressing</td>
    </tr>
    <tr>
      <td><code>00/01/10</code></td>
      <td><code>100</code></td>
      <td>对应的 SIB addressing</td>
      <td>比如 <code>Mod == 10; R/M == 100</code> 就是 <code>disp32(%reg)</code> 的 SIB 形式</td>
    </tr>
  </tbody>
</table>

<p>那么问题来了：是否存在编号为 <code>100</code> 的 register? 如果存在的话，假设它叫 <code>%reg</code>，我如何表示 <code>disp8(%reg)</code>?</p>

<p>首先<strong>存在编号为 <code>100</code> 的 register</strong>，但情况有点复杂。</p>

<p>在 macOS 常见的 Intel x86-64 架构中，operand size 对应着不同的数据类型：</p>

<table>
  <thead>
    <tr>
      <th><strong>operand size</strong></th>
      <th><strong>汇编后缀 (AT&amp;T)</strong></th>
      <th><strong>寄存器示例</strong></th>
      <th><strong>C 语言对应类型</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>8-bit</strong></td>
      <td><code>b</code> (byte)</td>
      <td><code>%al</code>, <code>%bl</code></td>
      <td><code>char</code></td>
    </tr>
    <tr>
      <td><strong>16-bit</strong></td>
      <td><code>w</code> (word)</td>
      <td><code>%ax</code>, <code>%bx</code></td>
      <td><code>short</code></td>
    </tr>
    <tr>
      <td><strong>32-bit</strong></td>
      <td><code>l</code> (long)</td>
      <td><code>%eax</code>, <code>%ebx</code></td>
      <td><code>int</code></td>
    </tr>
    <tr>
      <td><strong>64-bit</strong></td>
      <td><code>q</code> (quadword)</td>
      <td><code>%rax</code>, <code>%rbx</code></td>
      <td><code>long long</code> 或指针</td>
    </tr>
  </tbody>
</table>

<ul>
  <li>在 32 位世代，Intel 只分配 3-bit 通用寄存器编号，我们称为 <code>r0 ~ r7</code></li>
  <li>到了 64 位时代，register 变宽了，数量也变多了，有了 <code>r8 ~ r15</code>；但 Intel 不想推翻重来，于是就在原有指令前面塞一个 [[#$\overline{\operatorname{REX}}$ Prefix]]，<strong>相当于是打了个补丁</strong></li>
  <li>所以编号 <code>100</code> 的 register 可能是 <code>r4</code> (因为 <code>0b 100 == 4</code>)，也可能是 <code>r12</code> (<code>0b 100</code> 加 prefix 构成 <code>0b 1100 == 12</code>)</li>
</ul>

<blockquote>
  <p>[!info] 32-bit operand size 下，<code>r4</code> 即是 <code>%esp</code></p>
</blockquote>

<p>那现在假设我们要 <code>disp8(%esp)</code>，它就只能用 SIB 的形式来写，算是一种 hack：</p>

<ul>
  <li><code>Mod == 01</code></li>
  <li><code>R/M == 100</code></li>
  <li><code>SIB</code> 的 <code>Base == 100</code></li>
  <li>相当于写成 <code>disp8(base=100, index=0, scale=1)</code> 而不是 <code>disp8(reg=100)</code></li>
</ul>

<h3 id="overlineoperatornamesib-field">$\overline{\operatorname{SIB}}$ Field</h3>

<p>$\overline{\operatorname{SIB}}$ 的 general form 是：</p>

<pre><code class="language-txt">┌───────┬─────────┬────────┐
│ Scale │  Index  │  Base  │
└───────┴─────────┴────────┘
   2-bit   3-bit     3-bit
</code></pre>

<ul>
  <li>$\overline{\operatorname{Index}}$ 和 $\overline{\operatorname{Base}}$ 都是 register 编号</li>
  <li>$\overline{\operatorname{Scale}}$ 就 2-bit，取值 <code>00/01/10/11</code>，表示具体的 scale 值 <code>1/2/4/8</code></li>
</ul>

<blockquote>
  <p>[!caution] assembly 中 SIB addressing 的 instruction 一定是写 “具体的 scale 值”，即一定是 <code>(base, index, scale = 1/2/4/8)</code> 形式</p>

  <p>只是这个 instruction 被 encode 成 binary 时，这个 “具体的 scale 值” 会被 encode 成 $\overline{\operatorname{Scale}}$ 的 2-bit</p>
</blockquote>

<blockquote>
  <p>[!faq] 如果 <code>scale &gt; 8</code>，该怎么办？</p>

  <p>比如说我想实现 <code>(base, index, scale = 12)</code>，就只能曲线救国：</p>

  <ol>
    <li>先把 <code>index * 12</code> 存入一个临时 register <code>temp</code></li>
    <li>然后把 <code>(base, index, scale = 12)</code> 改写成 <code>(base, temp, scale = 1)</code></li>
  </ol>
</blockquote>

<p>放回 x86 指令编码方案中有：</p>

<pre><code class="language-txt">                 ┌───────┬─────────┬────────┐
                 │ Scale │  Index  │  Base  │
                 └───────┴─────────┴────────┘
                  \                        /
                   ––––––––       –––––––––
                           \     /
┌────────┬────────┬────────┬─────┬──────────────┬───────────┐
│ Prefix │ Opcode │ ModR/M │ SIB │ Displacement │ Immediate │
└────────┴────────┴────────┴─────┴──────────────┴───────────┘
            ______/        \_______
           /                       \
          ┌───────┬────────┬────────┐
          │  Mod  │  Reg   │  R/M   │
          └───────┴────────┴────────┘
</code></pre>

<h3 id="overlineoperatornamerex-prefix">$\overline{\operatorname{REX}}$ Prefix</h3>

<p>我们回到指令的编码 <code>48 8b 05 00 00 00 00</code>.</p>

<p><code>0x48 == 0b 0100 1000</code> is the REX (REgister eXtension; X86-64 specific) prefix (to the <code>Opcode</code>).</p>

<blockquote>
  <p>[!caution] 注意 <code>0x48</code> 这 8-bit 的一段的名称就叫 REX</p>

  <p>然后它本质上是个 prefix, 所以也总被称为 “REX prefix” (有点类似 “a Camry sedan” 这样的构词法)，并不是 “prefix to REX” 的意思。</p>
</blockquote>

<blockquote>
  <p>[!caution] 还有其他类型的 prefix，不止 REX 这一种</p>
</blockquote>

<p>把这 8-bit 拆分一下有：</p>

<ul>
  <li><code>0100</code> 是 $\overline{\operatorname{REX}}$ 的固定标志位，即形如 <code>0100 XXXX</code> 的都是 REX prefix</li>
  <li><code>1000</code> 是 4 个 flags, 从 MSB 到 LSB 分别是:
    <ul>
      <li><code>W == 1</code> $\Rightarrow$ width flag
        <ul>
          <li><code>W == 0</code> 表示 32-bit operand size (默认)</li>
          <li><code>W == 1</code> 表示 64-bit operand size (扩展)</li>
        </ul>
      </li>
      <li><code>R == 0</code> $\Rightarrow$ register flag, 用于强化 $\overline{\operatorname{ModR/M}} \rhd \overline{\operatorname{Reg}}$ 所表示的 register 范围</li>
      <li><code>X == 0</code>$\Rightarrow$ index flag, 用于强化 $\overline{\operatorname{SIB}} \rhd \overline{\operatorname{Index}}$ 所表示的 register 范围</li>
      <li><code>B == 0</code>$\Rightarrow$ base flag, 用于强化 $\overline{\operatorname{ModR/M}} \rhd \overline{\operatorname{R/M}}$、或者  $\overline{\operatorname{SIB}} \rhd \overline{\operatorname{Base}}$ 所表示的 register 范围
        <ul>
          <li>以上 3 个 flags 的逻辑都是一致的</li>
          <li>flag 为 <code>0</code> 表示你 3-bit 的 register 编号对应 <code>r0 ~ r7</code></li>
          <li>flag 为 <code>1</code> 表示你 3-bit 的 register 编号对应 <code>r8 ~ r15</code></li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

<pre><code class="language-txt">                            REX.X     REX.B
                              |         |
                              *         *
                 ┌───────┬─────────┬────────┐
                 │ Scale │  Index  │  Base  │
                 └───────┴─────────┴────────┘
                  \                        /
                   ––––––––       –––––––––
                           \     /
┌────────┬────────┬────────┬─────┬──────────────┬───────────┐
│  REX   │ Opcode │ ModR/M │ SIB │ Displacement │ Immediate │
└────────┴────────┴────────┴─────┴──────────────┴───────────┘
    |       ______/        \_______
    |      /                       \
┌────────┐┌───────┬────────┬────────┐
│0100WRXB││  Mod  │  Reg   │  R/M   │
└────────┘└───────┴────────┴────────┘
                      *        *
                      |        |
                    REX.R    REX.B 
</code></pre>

<h2 id="综合分析">综合分析</h2>

<p><code>shared_int = 43;</code> 这一句的 assembly 的逻辑是：</p>

<pre><code class="language-c">rax = &amp;(shared_int);
*rax = 43;
</code></pre>

<p>实际的 <code>.o</code> assembly 是：</p>

<pre><code class="language-nasm">;     shared_int = 43;
       f: 48 8b 05 00 00 00 00         	movq	(%rip), %rax   # rax = *(base=rip, displacement=0)
  # addr: 0f 10 11 12 13 14 15
                   ##          &lt;- reloc &lt;address == 0x12&gt;
                   #---------# &lt;- reloc &lt;length == long == 4-byte&gt; 
  
      16: c7 00 2b 00 00 00            	movl	$0x2b, (%rax)  # *rax = 43       
</code></pre>

<p>具体到这个 instruction 的成分：</p>

<pre><code class="language-txt"># movq	(%rip), %rax
# 48 8b 05 00 00 00 00

0x48 == 0b 0100 1000  # REX.W == 1
0x8b                  # Opcode for MOV
                        # 因为 REX.W == 1, 所以具体是 MOVQ
0x05 == 0b 0000 0101  # Mod == 00
                      # Reg == 000, 对应 %rax
                      # R/M == 101, 对应 RIP-Relative addressing, 即 disp32(%rip) 
0x00 00 00 00         # Displacement == 0
</code></pre>

<p>所以 reloc 的逻辑是：</p>

<pre><code class="language-c">// .o assembly logic
int d_shared_int = 0;  // displacement for shared_int
register rax = *(base=rip, displacement=d_shared_int);  // movq	(%rip), %rax
                                                        // rax is supposed to point to shared_int
*rax = 43;

// reloc logic
reloc.address = &amp;d_shared_int;
reloc.type = "GOT_LD";
// ...
mark_for_alteration(address=reloc.address, type=reloc.type, ...);
</code></pre>

<p>注意这条 reloc 只是指定了 <strong>去 GOT 中找一条 entry，它记录了 <code>shared_int</code> 的地址</strong>，但是注意一个问题：<strong>object file 中并没有 GOT</strong>. 所以 <strong>具体定位到那一条 GOT entry 是 static linker 的工作</strong>，具体说来：</p>

<ul>
  <li>static linker 扫描 object file，收集 relocs</li>
  <li>static linker 根据 relocs 生成 GOT (因为 static linker 知道 external variables 在哪儿)</li>
  <li>static linker 此时已经知道了 GOT 在 executable 中的物理位置了，它再回头算 <code>reloc.address</code> 与对应的 GOT entry 之间的 offset (注意还有 <code>%rip</code> 要参与运算)
    <ul>
      <li>这个算出来的 offset 就是 external variables 的 <code>displacement</code></li>
    </ul>
  </li>
  <li>stataic linker 把这个 <code>displacement</code> 写入 <code>reloc.address</code></li>
  <li>executable 生成完毕，等到 loader 执行</li>
</ul>

<pre><code class="language-txt">(__TEXT,__text) in object file        (__DATA, .data/.bss) in executable
+--------------------+                +--------------------------+
| Instruction:       |                | External _shared_int     |
| movq	(%rip), %rax |                | Address: 0x3000          |
+---------+----------+                | Value: 42                |
          |                           +--------------------------+
          |                                         ^
          v                                         |
(.got) in executable                                | 
+-------------------------+                         |
| [Entry for _shared_int] |                         |
| Content: 0x3000         | ------------------------+
+-------------------------+ 
</code></pre>]]></content><author><name>Yao Yao</name></author><category term="Compiler" /><summary type="html"><![CDATA[可参考 CSAPP $\rhd$ Chapter 7 – Linking $\rhd$ Sec. 7.7 – Relocation.]]></summary></entry><entry><title type="html">Linking the Dots #1: An Example of Linking in C</title><link href="https://listcomp.com/compiler/2026/01/16/linking-the-dots-1-an-example-of-linking" rel="alternate" type="text/html" title="Linking the Dots #1: An Example of Linking in C" /><published>2026-01-16T00:00:00-08:00</published><updated>2026-01-16T00:00:00-08:00</updated><id>https://listcomp.com/compiler/2026/01/16/linking-the-dots-1-an-example-of-linking</id><content type="html" xml:base="https://listcomp.com/compiler/2026/01/16/linking-the-dots-1-an-example-of-linking"><![CDATA[<h1 id="source-code">Source Code</h1>

<pre><code class="language-c">// secondary.c

int get_answer() {
    return 42;
}

int another_answer = 100;
</code></pre>

<pre><code class="language-c">// main.c

// Forward declaration of a function
extern int get_answer();

// `extern` is often considered redundant there,
//   because compiler assumes any function without a "body" `{}` is external,
//   i.e. function names have external linkage by default.
// int get_anwser();

// Forward declaration of a variable
// `extern` is necessary here
extern int another_answer;

// Without `extern`, it becomes a tenative definition.
//   More on that later.
// int another_answer;

int main() {
    return get_answer() + another_answer;
}
</code></pre>

<h1 id="pre-linking-steps">Pre-linking Steps</h1>

<p>Step 1 - Preprocessing (C $\Rightarrow$ C)</p>

<pre><code class="language-bash">ᐅ gcc -E main.c -o main.i
ᐅ gcc -E secondary.c -o secondary.i
</code></pre>

<p>Step 2 - Compilation (C $\Rightarrow$ Assembly)</p>

<pre><code class="language-bash">ᐅ gcc -S main.i -o main.s
ᐅ gcc -S secondary.i -o secondary.s

# ᐅ or 
#   ᐅ cc1 main.c -o main.s
#   ᐅ cc1 secondary.c -o secondary.s
</code></pre>

<blockquote>
  <p>[!NOTE] <code>cc1</code> 包含了 preprocessing 步骤，所以可以直接处理 <code>.c</code></p>
</blockquote>

<p>Step 3 - Assembling (Assembly $\Rightarrow$ Relocatable Object/Binary)</p>

<pre><code class="language-bash">ᐅ gcc -c main.s -o main.o
ᐅ gcc -c secondary.s -o secondary.o

# or 
#   ᐅ as main.s -o main.o
#   ᐅ as secondary.s -o secondary.o
</code></pre>

<p>Or directly C $\Rightarrow$ Relocatable Object/Binary:</p>

<pre><code class="language-bash">ᐅ gcc -c main.c -o main.o
ᐅ gcc -c secondary.c -o secondary.o
</code></pre>

<h1 id="linking">Linking</h1>

<p>Relocatable Object/Binary $\Rightarrow$ Executable Object/Binary</p>

<h2 id="static-linking">Static Linking</h2>

<p><strong>Method 1:</strong> use the object file <code>secondary.o</code> directly:</p>

<pre><code class="language-bash">ᐅ gcc main.o secondary.o -o main_static.out
</code></pre>

<p><strong>Method 2:</strong> create a <em>static library archive</em> from <code>secondary.o</code>:</p>

<pre><code class="language-bash">ᐅ ar rcs secondary.a secondary.o

# link like above
ᐅ gcc main.o secondary.a -o main_static.out
</code></pre>

<ul>
  <li><code>ar</code> is an archive tool, just like <code>zip</code> or <code>tar</code>
    <ul>
      <li>option <code>r</code>: replace/add files to archive</li>
      <li>option <code>c</code>: create archive if it doesn’t exist</li>
      <li>option <code>s</code>: create an index (symbol table) for faster linking</li>
    </ul>
  </li>
  <li>注意 <code>.a</code> 并不是 Mach-O format, 它也不是 object file, 它单纯就是个能被 link 的压缩包格式</li>
</ul>

<blockquote>
  <p>[!NOTE] 如果你有很多个 dependencies，那么 <em>static library archive</em> 无疑是工程上的 <strong>best practice</strong>.</p>
</blockquote>

<h2 id="dynamic-linking">Dynamic Linking</h2>

<p><strong>Method:</strong> create a dynamic lib from <code>secondary.o</code>:</p>

<pre><code class="language-bash">ᐅ gcc -dynamiclib secondary.o -o secondary.dylib
ᐅ gcc main.o secondary.dylib -o main_dynamic.out
</code></pre>

<blockquote>
  <p>[!NOTE] <code>.dylib</code> on macOS is equivalent to <code>.so</code> on Linux.</p>
</blockquote>

<blockquote>
  <p>[!NOTE] <code>.dylib</code> 是 Mach-O format</p>
</blockquote>

<blockquote>
  <p>[!NOTE] <code>.dylib</code> 不是 object file, 因为 “object”, “executable”, “dynamic lib” 是三种平行的文件类型</p>
</blockquote>

<h2 id="digression-search-pattern-of-gcc--l--l">Digression: Search Pattern of <code>gcc -L -l</code></h2>

<p>我们也可以这么做 static linking:</p>

<pre><code class="language-bash">ᐅ ar rcs libsecondary.a secondary.o
ᐅ gcc main.o -L. -lsecondary -o main_static.out
</code></pre>

<p>也可以这么做 dynamic linking:</p>

<pre><code class="language-bash">ᐅ gcc -dynamiclib secondary.o -o libsecondary.dylib
ᐅ gcc main.o -L. -lsecondary -o main_dynamic.out
</code></pre>

<p>这里 <code>gcc -L -l</code> 有特定的 search pattern:</p>

<ul>
  <li><code>-L.</code> 指定 search directory 为 <code>.</code> (current folder)</li>
  <li>它这个 search pattern 会默认你的 lib name 以 <code>lib</code> 开头，所以 <code>-lsecondary</code>  实际指定的是 <code>libsecondary</code> 这个 name
    <ul>
      <li>所以我们在创建 dylib 时特意起了 <code>libsecondary</code> 这个名字</li>
      <li>这个 search pattern 主要是为了区分 user lib 和 system lib</li>
    </ul>
  </li>
</ul>

<p>此时就有一个问题：如果我们同时有 <code>libsecondary.a</code> 和 <code>libsecondary.dylib</code>, <code>-lsecondary</code> 如何确定是 static linking 还是 dynamic linking? 它其实有一个规则：</p>

<ul>
  <li>优先找 <code>libsecondary.dylib</code> 做 dynamic linking</li>
  <li>没找到 <code>dylib</code> 就找 <code>libsecondary.a</code> 做 static linking</li>
  <li>它不会去找 <code>libsecondary.o</code></li>
</ul>

<p>如果我们同时有 <code>libsecondary.a</code> 和 <code>libsecondary.dylib</code>, 我们可以用 option <code>-static</code> force 执行 static linking:</p>

<pre><code class="language-bash">ᐅ gcc main.o -L. -static -lsecondary -o main_static.out
</code></pre>

<h1 id="inspection">Inspection</h1>

<h2 id="check-for-dependencies">Check for Dependencies</h2>

<pre><code class="language-bash">ᐅ otool -L main_static.out
main_static.out:
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.61.1)
</code></pre>

<ul>
  <li><code>main_static.out</code> does NOT need <code>secondary.dylib</code> nor <code>secnondary.o</code> because it’s already wrapped inside the binary</li>
</ul>

<blockquote>
  <p>[!info] <code>libSystem.B.dylib</code> plays <strong>the same <em>role</em></strong> on macOS that <code>glibc.so</code> plays on Linux (as the <strong>C standard library</strong>)</p>

  <p>它俩实现上略有不同：<code>glibc.so</code> 是一个 monolith lib; <code>libSystem.B.dylib</code> 是一个 facade to multiple libs.</p>
</blockquote>

<pre><code class="language-bash">ᐅ otool -L main_dynamic.out
main_dynamic.out:
	secondary.dylib (compatibility version 0.0.0, current version 0.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.61.1)
</code></pre>

<ul>
  <li><code>main_dynamic.out</code> knows it needs the external <code>secondary.dylib</code></li>
</ul>

<h2 id="check-for-symbols">Check for Symbols</h2>

<pre><code class="language-bash">ᐅ nm main_static.out
0000000100000000 T __mh_execute_header
0000000100004000 D _another_answer       # linked variable
0000000100003f90 T _get_answer           # linked function
0000000100003f60 T _main
</code></pre>

<pre><code class="language-bash">ᐅ nm main_dynamic.out
0000000100000000 T __mh_execute_header
                 U _another_answer      # undefined variable
                 U _get_answer          # undefined function
0000000100003f70 T _main
</code></pre>

<ul>
  <li><code>T</code>: the symbol is in <code>(__TEXT, __text)</code></li>
  <li><code>D</code>: the symbol is in <code>(__DATA, __data)</code> (initialized data)</li>
  <li><code>U</code>: the symbol is undefined</li>
</ul>]]></content><author><name>Yao Yao</name></author><category term="Compiler" /><summary type="html"><![CDATA[Source Code]]></summary></entry></feed>