Linking organizes symbols, sections, and relocations from object files into executable images or shared objects; loading places the image into the process address space and completes the dynamic preparation required for execution. The two work together but are not the same stage.
The specific format and formulas in this article use Linux ELF and the x86-64 System V ABI as examples. Object formats, calling conventions, and dynamic linking strategies for other platforms should be consulted in their respective specifications.
What else is in an object file besides machine code?
Content
Purpose
.text, .data, .rodata
Code and data with different attributes
. bss-like NOBITS sections
Describes space that needs zero-initialization; does not store an equivalent payload of zero bytes
Symbol table
Definitions, undefined references, binding, visibility, etc.
Relocation entries
Tells subsequent tools how to patch address-dependent fields
Debug information
Associates machine locations with source code and types
Sections are the unit of organization from the linker's perspective; program headers describe segments from the loader's perspective. At runtime, mappings are typically done according to program headers like PT_LOAD, not by mmaping each section name individually. .bss has metadata, but it doesn't need to save every zero byte it represents in the file.
Symbol Resolution and Layout: Deciding Who to Reference First
One object file defines a helper, and another object file references helper. The linker decides which definition corresponds to the reference based on binding, visibility, version, and linking rules, then lays out the relevant content and applies relocations.
Same names do not always conflict: local symbols have their own scope, weak and strong symbols have different rules, and dynamic symbols are also affected by visibility and search scope. Conversely, dynamic symbol resolution cannot be simplified to "whichever library loads first is always used."
Relocation: Accurate Formulas for Each Type
Let S be the target symbol address, A be the addend, and P be the address of the relocation field. R_X86_64_PC32 uses S+A−P, and R_X86_64_64 uses S+A. Different types also specify field width and overflow checks. AMD64 ABI Relocation Table
In a typical call rel32 encoding, the CPU references the next instruction, while the ABI formula uses the field position P. The common addend of −4 already accounts for the difference between them. If the formula is written as S−(P+4)+A while A is also −4, you would subtract 4 twice.
External function calls may use R_X86_64_PLT32, where the target involves a PLT entry and may be relaxed to a direct call by the linker; you cannot require every compiler's external call to show PC32. After understanding the formula, you should check the actual relocation types.
Check Signed Range Before Writing Four Bytes
The rel32 displacement is a signed 32-bit integer. The displacement can be negative when the target is before the call site, using two's complement for little-endian fields; if the target is too far, you cannot simply truncate to the lower 32 bits, or the CPU will jump to another address. The following only models the PC32 calculation for a determined direct target, without simulating symbol lookup or PLT selection.
defpc32(symbol,addend,place):displacement=symbol+addend-placeifnot-(1<<31)<=displacement<(1<<31):raiseOverflowError("PC32 relocation out of range")returndisplacement.to_bytes(4,"little",signed=True)assertpc32(0x2000,-4,0x1001)==bytes.fromhex("fb 0f 00 00")backward=pc32(0x1000,-4,0x2001)assert0x2005+int.from_bytes(backward,"little",signed=True)==0x1000fordisplacementin(-(1<<31),(1<<31)-1):assertint.from_bytes(pc32(displacement,0,0),"little",signed=True)==displacementfordisplacementin(-(1<<31)-1,1<<31):try:pc32(displacement,0,0)exceptOverflowError:passelse:raiseAssertionError("truncated relocation accepted")
Common translation of S and P does not change the displacement, which explains why some PC-relative references within a module do not need to be patched again with the load base address; however, definitions outside the module, absolute address fields, and TLS still have their own rules. You cannot generalize from a single internal relative call to "PIC has no relocations."
Which distance is already accounted for by the −4 addend?
Mark the four-byte field P and the next instruction P+4 to make the addend concrete. Move the target backward, translate the module, or subtract4 twice. Overflow produces no truncated patch: a number writable to a file does not guarantee the CPU reaches the intended address.
d=S+A−P,A=−4,(P+4)+d=S
Preparing the visual
Which distance is already accounted for by the −4 addend?
PC32 writes S+A−P. For this call A=−4 already bridges field P to next-instruction P+4. Translating S,P together preserves displacement; subtracting4 again misdirects the call.
{"id":"pc-relative-relocation","title":"Which distance is already accounted for by the −4 addend?","summary":"PC32 writes S+A−P. For this call A=−4 already bridges field P to next-instruction P+4. Translating S,P together preserves displacement; subtracting4 again misdirects the call.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"target\" data-i18n=\"target\"></label><output for=\"target\" id=\"target-value\"></output><input id=\"target\" type=\"range\" min=\"0\" max=\"1024\" value=\"512\" step=\"16\"></div><div class=\"control\"><label for=\"base\" data-i18n=\"base\"></label><output for=\"base\" id=\"base-value\"></output><input id=\"base\" type=\"range\" min=\"4096\" max=\"16384\" value=\"4096\" step=\"4096\"></div><label class=\"check\"><input type=\"checkbox\" id=\"double\"><span data-i18n=\"double\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"far\"><span data-i18n=\"far\"></span></label><div id=\"positions\" class=\"table\"></div><div class=\"metrics\"><div><span data-i18n=\"patch\"></span><strong id=\"patch\"></strong></div><div><span data-i18n=\"destination\"></span><strong id=\"destination\"></strong></div></div><div id=\"bytes\" class=\"track\"></div><p id=\"equation\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.metrics strong{font-size:17px}#positions .tr{grid-template-columns:1fr 1fr}#equation{white-space:pre-line;min-height:5.4em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function draw(){const base=+$('#base').value,P=base+257,S=$('#far').checked?P+4+2**31:base+ +$('#target').value,A=-4,patch=S+A-P-($('#double').checked?4:0),valid=patch>=-(2**31)&&patch<2**31,dest=P+4+patch;set('target-value',$('#target').value);set('base-value','0x'+base.toString(16));table('positions',[['P','0x'+P.toString(16)],['P+4','0x'+(P+4).toString(16)],['S','0x'+S.toString(16)]]);set('patch',valid?patch:'overflow');set('destination',valid?'0x'+dest.toString(16):'—');cells('bytes',Array.from({length:4},(_,i)=>({text:valid?((patch>>>0)>>>8*i&255).toString(16).padStart(2,'0'):'—',cls:valid?'on':'gold'})));set('equation','S + (−4) − P'+($('#double').checked?' − 4':'')+' = '+patch+'\\n'+(valid?'P+4+disp '+(dest===S?'= S ✓':'≠ S ✗'):'−2³¹ ≤ disp < 2³¹ ✗'));$('#destination').dataset.valid=String(valid&&dest===S);}document.querySelectorAll('input').forEach(e=>e.oninput=draw);draw();","audio":false,"strings":{"base":"Common load base","bytes":"Four little-endian bytes","destination":"CPU destination","double":"Incorrectly subtract4 twice","far":"Target beyond positive rel32 range","heading":"Which distance is already accounted for by the −4 addend?","intro":"The rel32 field P is at module offset0x101, next instruction P+4. Move the target or common base; verify patch and CPU destination.","patch":"Patched displacement","reset":"Reset","scope":"x86-64 ELF direct PC32 model, little-endian signed32 rel32. Binding fixed; no PLT32, relaxation or general PIC relocation model.","scopeLabel":"Model scope and assumptions","target":"Target offset S−base"}}
A Minimal Object File Experiment
Create two files in a separate experiment directory:
First, find the undefined twice symbol, then look at the corresponding relocation, and finally observe how the linked program runs. Specific offsets, instructions, and PIE defaults vary with the toolchain; the focus of the experiment is to map symbol references to patching fields.
Static Libraries: Extracting Object Members According to Rules
.a is an archive of object files. Traditional Unix linkers typically extract members from an archive when handling unresolved references, so command-line order can matter: placing the requester before the static library that provides the definition is a common practice.
Circular dependencies can be handled through mechanisms like repeated scanning of library groups; GNU ld and other linkers support corresponding options, but specific scanning strategies differ. LLD also saves lazy symbols from archives and extracts them upon subsequent references; you cannot describe all linkers as performing an identical left-to-right single scan.
Linker GC retains content reachable from roots based on section and relocation references; roots can also include exported symbols and KEEP in link scripts. It is not just tracing call instructions by disassembling from _start. Splitting sections by function or data helps fine-grained reclamation, but not all inputs are naturally split.
Just because a member entered the link doesn't mean every function in it remains
Continue using the previous caller.c, and extend twice.c to two functions: twice is called by main, and unused_helper has no references. Use GNU ld.bfd for the following experiment to fix the linker and avoid mistaking its archive scanning strategy for a rule of all tools.
/* twice.c: replace the previous version */inttwice(intx){return x *2;}intunused_helper(void){return99;}
The first link extracts the entire twice.o due to the reference from caller.o to twice, so both functions enter the input set. The second link will still extract the same member, but section reclamation finds that .text.unused_helper is not in the retention graph and can remove it. Both programs return 0, and unused_helper should only appear in the un-reclaimed version in nm. Here, O0 is used and LTO is not enabled to avoid mixing frontend inlining/deletion effects into the explanation.
Stage
Granularity
Decision in this Example
Archive Search
Object Member
Extract twice.o to satisfy the twice reference
Section Reclamation
Input Sections and Reference Graph
Retain the section containing twice, remove the unreferenced function section
Final Layout and Relocation
Addresses and Fields of Retained Content
Patch the final displacement for the call in main
If libtwice.a is placed before caller.o, GNU ld.bfd may not extract the member because there is no unresolved reference to twice at that time, ultimately reporting an undefined reference. Restoring the order fixes this; unconditionally using --whole-archive to solve this ordering issue expands the input range, and it itself does not guarantee that all sections are spared from subsequent GC. GNU ld Archive and Section Reclamation Options
How can extracting an object still keep only one function?
Separate membership in linker input from final retention. Traditional archive scanning may skip the member before an unresolved twice exists; forced extraction still allows GC to delete the independent unused section. The model fixes a GNU ld.bfd boundary instead of projecting it onto different linkers such as LLD.
Preparing the visual
How can extracting an object still keep only one function?
Archive extraction selects members; section GC retains root-reachable sections. whole-archive forces input membership but not section retention; traditional order errors can miss the definition entirely.
{"id":"archive-and-section-reachability","title":"How can extracting an object still keep only one function?","summary":"Archive extraction selects members; section GC retains root-reachable sections. whole-archive forces input membership but not section retention; traditional order errors can miss the definition entirely.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><label class=\"check\"><input type=\"checkbox\" id=\"order\" checked><span data-i18n=\"order\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"whole\"><span data-i18n=\"whole\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"gc\" checked><span data-i18n=\"gc\"></span></label><svg id=\"refs\" class=\"diagram\" viewBox=\"0 0 360 180\" role=\"img\"></svg><section class=\"panel\"><h3 data-i18n=\"extracted\"></h3><div id=\"extracted\"></div></section><section class=\"panel\"><h3 data-i18n=\"kept\"></h3><div id=\"kept\"></div></section><section class=\"panel\"><h3 data-i18n=\"unresolved\"></h3><div id=\"unresolved\"></div></section><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n#extracted,#kept,#unresolved{font:14px/1.8 var(--viz-font-mono);min-height:3.6em;overflow-wrap:anywhere}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function draw(){const extracted=$('#order').checked||$('#whole').checked,gc=$('#gc').checked;set('extracted',extracted?'caller.o, twice.o':'caller.o');set('unresolved',extracted?'∅':'twice');set('kept',!extracted?'link error':gc?'main, twice':'main, twice, unused');graph('refs',[{name:'main',x:55,y:85,cls:'on'},{name:'twice',x:180,y:85,cls:extracted?'on':'gold'},{name:'unused',x:305,y:85,cls:extracted&&!gc?'on':''}],[[0,1,true]]);$('#kept').dataset.count=extracted?(gc?2:3):0;}document.querySelectorAll('input').forEach(e=>e.onchange=draw);draw();","audio":false,"strings":{"extracted":"Extracted members","gc":"Enable function-section GC","heading":"How can extracting an object still keep only one function?","intro":"caller.o needs twice; one archive member defines twice and unused. Toggle order, whole-archive and section GC to inspect three distinct sets.","kept":"Retained functions","order":"Place caller.o before archive","reset":"Reset","scope":"Models traditional GNU ld.bfd archive processing; function sections, main root, no LTO, KEEP, extra exports or groups. Not all linkers.","scopeLabel":"Model scope and assumptions","unresolved":"Unresolved references","whole":"whole-archive: force extraction"}}
GOT and PLT: Separating Indirect Access and Resolution Timing
GOT provides address slots, and PLT provides call stubs; they are part of some position-independent and dynamic call paths. In classic lazy function binding, the first call goes through the resolver to find the target and update the corresponding slot, and subsequent calls use the already saved address.
Immediate binding advances the relevant resolution to the loading stage; for example, the Linux dynamic linker can be controlled via LD_BIND_NOW. It does not automatically change the original PLT call instruction to a direct call without indirect overhead just because resolution is advanced. Whether PLT is used, whether it can be relaxed, and whether it is called via GOT still depends on compilation and linking choices. Linux ld.so
Dynamic symbol interposition is also affected by search scope, binding, and visibility. It brings flexibility to replacement implementations and preloading, but may also limit inlining or local binding assumptions; you cannot assert that all malloc calls necessarily go through the exact same path.
Loading and dlopen
dlopen loads shared objects during process execution, dlsym looks up symbols, and dlclose reduces load references, etc. Actual plugin interfaces also need to define ABI, ownership, thread, and unloading conditions, and check for errors; you cannot arbitrarily unload while function pointers or threads are still executing library code. dlopen manual
Dynamically loading ready-made libraries is different from JIT publishing machine code. JIT can handle objects, relocations, and executable permissions in memory, without requiring a unified file process of "write .o, link .so, dlopen". See JIT Compilation.
LTO: Letting Optimization See Cross-Compilation Unit Information
Ordinary independent compilation limits the function bodies the optimizer can see. LTO preserves IR or related intermediate information, performs cross-module analysis and optimization during the linking stage combined with symbol resolution results, and then generates machine code to continue linking. Even without LTO, the linker still performs relocations, section reclamation, and other link optimizations; it cannot be called just concatenation.
Full LTO and ThinLTO have different organizational methods. ThinLTO combines module summaries for global analysis and selective function import, then executes module backends in parallel, and can also use caching. Clang explicitly selects this mode with configurations like -flto=thin; it is not the natural default LTO method for all gold/LLD calls. Clang ThinLTO Documentation
Whether to inline is determined by cost models and visibility constraints, not limited to "only importing hot call sites." LTO may also not know about all dynamic plugins and external definitions; the so-called whole-program boundary needs to be combined with linking and symbol assumptions. Performance gains, memory, and linking time should be measured in actual projects, without giving a unified percentage increase.
Quick Troubleshooting Guide
Phenomenon
Priority Check
undefined reference
Whether definition is generated, library order, name mangling, ABI, and visibility
relocation overflow
Relocation width, layout, and code model
Startup cannot find shared library
Dependency records, search paths, and platform loading rules
Same-named symbol bound to unexpected implementation
Search scope, version, preloading, and visibility
LTO has no expected effect
Compilation and linking configuration, IR availability, and optimization boundaries