リンクは、オブジェクトファイル内のシンボル、セクション、リロケーションを結合して実行可能イメージまたは共有オブジェクトを構成します。ロードは、そのイメージをプロセスのアドレス空間に配置し、実行に必要な動的な準備を完了させます。これらは互いに連携しますが、異なるフェーズです。
本文の具体的な形式と数式は、Linux ELF および x86-64 System V ABI を例に挙げています。他のプラットフォームのオブジェクト形式、呼び出し規約、ダイナミックリンク戦略については、それぞれの仕様を参照してください。
オブジェクトファイルには機械語以外に何があるか
内容 役割
.text、.data、.rodata コードおよび異なる属性を持つデータ
.bss などの NOBITS セクション ゼロ初期化が必要な領域を示し、対応するゼロバイトのペイロードは保存しない
シンボルテーブル 定義、未定義参照、バインディング、可視性など
リロケーションエントリ 後続のツールがアドレス依存フィールドをどのように修正すべきかを指示する
デバッグ情報 機械語の位置をソースコードや型に関連付ける
セクションはリンク時の視点での組織単位です。プログラムヘッダはロード時の視点でのセグメントを記述します。実行時には、PT_LOAD などのプログラムヘッダに基づいてマッピングされ、セクション名ごとに個別に mmap されるわけではありません。.bss にはメタデータが存在しますが、それが表す各ゼロバイトをファイルに保存する必要はありません。
シンボル解決とレイアウト:誰を参照するかを先に決める
1つのオブジェクトファイルが helper を定義し、別のオブジェクトファイルが helper を参照しているとします。リンカは、バインディング、可視性、バージョン、リンクルールに基づいて、その参照がどの定義に対応するかを決定し、関連する内容をレイアウトしてリロケーションを適用します。
同名でも必ずしも衝突するわけではありません。ローカルシンボルには独自のスコープがあり、弱シンボルと強シンボルには異なるルールがあり、ダイナミックシンボルは可視性と検索範囲の影響も受けます。逆に、ダイナミックシンボルの解決を「最初にロードされたライブラリが常に優先される」と単純化することもできません。
リロケーション:各タイプには正確な数式がある
S をターゲットシンボルのアドレス、A を addend、P をリロケーションフィールドの位置とする。x86-64 における R_X86_64_PC32 は S+A−P を使用し、R_X86_64_64 は S+A を使用します。異なるタイプでは、フィールドの幅とオーバーフローチェックも規定されています。AMD64 ABI リロケーションテーブル
PC相対リロケーション:P は修正対象フィールドであり、次の命令ではない
call オペコード
アドレス 0x1000
4バイトのオフセットフィールド
P = 0x1001
次の命令
アドレス 0x1005
S = 0x2000、A = −4:S + A − P = 0xFFB
CPU は次のアドレスにオフセットを加算する:0x1005 + 0xFFB = 0x2000。さらに4を減算してはならない。
典型的な call rel32 のエンコーディングでは、CPU が相対的に参照するのは次の命令ですが、ABI の数式ではフィールド位置 P が使用され、一般的な addend −4 が両者の差を既に処理しています。もし数式が S−(P+4)+A と書かれ、かつ A が −4 である場合、4 が重複して減算されてしまいます。
外部関数呼び出しでは R_X86_64_PLT32 が使用される可能性があり、そのターゲットは PLT エントリに関連し、リンカによって直接呼び出しに緩和される場合があります。すべてのコンパイラが外部呼び出しで PC32 を明示的に表示することを要求することはできません。数式を理解した後は、実際のリロケーションタイプを確認すべきです。
4バイト書き込む前に、符号付き範囲を確認する
rel32 オフセットは符号付き32ビット数です。ターゲットが呼び出し点より前にある場合、オフセットは負になり、リトルエンディアンフィールドへの書き込みには2の補数が使用されます。ターゲットが遠すぎる場合、下位32ビットに単純に切り捨てることはできず、そうすると CPU は別のアドレスにジャンプしてしまいます。以下では、すでに直接ターゲットが確定している PC32 の計算のみをモデル化し、シンボル検索や PLT の選択はシミュレートしません。
def pc32 ( symbol , addend , place ) :
displacement = symbol + addend - place
if not - ( 1 < < 31 ) <= displacement < ( 1 < < 31 ) :
raise OverflowError ( " PC32 relocation out of range" )
return displacement . to_bytes ( 4 , " little" , signed = True )
assert pc32 ( 0x 2000, - 4 , 0x 1001) == bytes . fromhex ( " fb 0f 00 00" )
backward = pc32 ( 0x 1000, - 4 , 0x 2001)
assert 0x 2005 + int . from_bytes ( backward , " little" , signed = True ) == 0x 1000
for displacement in ( - ( 1 < < 31 ) , ( 1 < < 31 ) - 1 ) :
assert int . from_bytes ( pc32 ( displacement , 0 , 0 ) , " little" , signed = True ) == displacement
for displacement in ( - ( 1 < < 31 ) - 1 , 1 < < 31 ) :
try :
pc32 ( displacement , 0 , 0 )
except OverflowError :
pass
else :
raise AssertionError ( " truncated relocation accepted" )
S と P を共通に平行移動してもオフセットは変化しないため、モジュール内部のいくつかの PC 相対参照がロードベースアドレスに合わせて再修正を必要としない理由が説明できます。しかし、モジュール外で定義されたシンボル、絶対アドレスフィールド、TLS などはそれぞれ独自のルールを持っています。1つの内部相対呼び出しから「PIC にはリロケーションがない」と一般化することはできません。
P、次の命令、そして −4、誰がすでに距離を補正したのか?
まず4バイトフィールド P をマーキングし、次に次の命令 P+4 をマーキングすると、addend の役割は明確になります。以下では、ターゲットを呼び出し点の前に配置することも、モジュール全体を平行移動することも、意図的に4を重複して減算することもできます。オフセットが範囲外の場合、切り捨てられた4バイトは生成されません。「ファイルに書き込める数字」であっても、CPU が正しいアドレスに到達できるとは限らないからです。
d = S + A − P , A = − 4 , ( P + 4 ) + d = S
図を準備しています 再読み込み
P、次の命令、そして −4、誰がすでに距離を補正したのか? PC32 には S+A−P が書き込まれ、典型的な call の A=−4 はフィールド位置を次の命令の基準に変換している。S と P を一緒に平行移動してもオフセットは変わらず、4を重複して減算すると誤ったアドレスにジャンプする。
{"id":"pc-relative-relocation","title":"P、次の命令、そして −4、誰がすでに距離を補正したのか?","summary":"PC32 には S+A−P が書き込まれ、典型的な call の A=−4 はフィールド位置を次の命令の基準に変換している。S と P を一緒に平行移動してもオフセットは変わらず、4を重複して減算すると誤ったアドレスにジャンプする。","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":"共通ロードベースアドレス","bytes":"リトルエンディアン4バイト","destination":"CPU の実際の到達地点","double":"誤って4を重複して減算","far":"ターゲットが rel32 の前方範囲を超えている","heading":"P、次の命令、そして −4、誰がすでに距離を補正したのか?","intro":"フィールド位置 P はモジュールオフセット 0x101 に固定され、call の次の命令は P+4 に位置する。ターゲットやロードベースアドレスを移動させ、修正オフセットと CPU の実際の到達地点を確認せよ。","patch":"オフセットの書き込み","reset":"リセット","scope":"x86-64 ELF 直接 PC32 の教育用モデル。rel32 はリトルエンディアン2の補数、範囲チェックは符号付き32ビット。シンボルバインディングは既に決定済みであり、PLT32、緩和、またはすべての PIC リロケーションはシミュレートしない。","scopeLabel":"モデルの範囲と規約","target":"ターゲットモジュールオフセット S−base"}}
最小限のオブジェクトファイル実験
独立した実験ディレクトリに2つのファイルを作成します。
extern int twice ( int ) ;
int main ( void ) { return twice ( 21 ) == 42 ? 0 : 1 ; }
int twice ( int x ) { return x * 2 ; }
cc - O0 - c caller.c - o caller.o
cc - O0 - c twice.c - o twice.o
readelf - sW caller.o
readelf - rW caller.o
objdump - dr caller.o
cc caller.o twice.o - o demo
./demo
まず未定義の twice シンボルを探し、次にそれに対応するリロケーションを確認し、最後にリンク後のプログラムがどのように実行されるかを観察します。具体的なオフセット、命令、PIE のデフォルト値はツールチェーンによって異なりますが、実験の重点はシンボル参照と修正フィールドを対応させることです。
静的ライブラリ:ルールに従ってオブジェクトメンバを抽出する
.a はオブジェクトファイルのアーカイブです。伝統的な Unix リンカは、アーカイブを処理する際に未解決の参照に基づいてメンバを抽出するため、コマンドラインの順序が重要になる場合があります。参照元を提供する定義を持つ静的ライブラリの前に配置するのが一般的な書き方です。
循環依存は、ライブラリグループ化による再スキャンなどのメカニズムで処理できます。GNU ld や他のリンカは対応するオプションをサポートしていますが、具体的なスキャン戦略は異なります。LLD はアーカイブ内の遅延シンボルを保存し、後続の参照時に抽出します。すべてのリンカを完全に同一の左から右への単一スキャンとして記述することはできません。
リンカの GC は、セクションやリロケーションなどの参照関係に基づいて、ルートから到達可能な内容を保持します。ルートにはエクスポートされたシンボルやリンクスクリプトの KEEP も含まれます。これは単に _start から逆アセンブルして call 命令を追跡するものではありません。関数やデータごとにセクションを分割することで、細粒度の回収が可能になりますが、すべての入力が自然に分割されているわけではありません。
1つのメンバがリンクに含まれても、その中のすべての関数が残るとは限らない
引き続き前述の caller.c を使用し、twice.c を拡張して2つの関数にします。twice は main から呼び出されますが、unused_helper は参照されません。GNU ld.bfd を使用して以下の実験を行い、リンカを固定して、アーカイブスキャン戦略をすべてのツールのルールと誤認しないようにします。
int twice ( int x ) { return x * 2 ; }
int unused_helper ( void ) { return 99 ; }
gcc - O0 - ffunction-sections - fdata-sections - c twice.c - o twice.o
ar rcs libtwice.a twice.o
gcc - fuse-ld= bfd caller.o ./libtwice.a - o with-unused
gcc - fuse-ld= bfd caller.o ./libtwice.a - Wl ,--gc-sections - o trimmed
nm with-unused
nm trimmed
./with-unused
./trimmed
最初のリンクでは、caller.o による twice への参照により twice.o 全体が抽出されるため、2つの関数がすべて入力集合に含まれます。2回目のリンクでも同じメンバが抽出されますが、セクション回収により .text.unused_helper が保持グラフに含まれていないことが判明し、削除できます。両方のプログラムは 0 を返しますが、nm において unused_helper は未回収バージョンにのみ表示されるはずです。ここでは O0 を使用し LTO を無効にすることで、フロントエンドのインライン化や削除効果を説明に混入させないようにしています。
フェーズ 粒度 本例での決定
アーカイブ検索 オブジェクトメンバ twice への参照を満たすために twice.o を抽出
セクション回収 入力セクションとその参照グラフ twice があるセクションを保持し、未使用関数のセクションを削除
最終レイアウトとリロケーション 保持内容のアドレスとフィールド main の呼び出しに対して最終オフセットを修正
もし libtwice.a を caller.o の前に配置した場合、GNU ld.bfd はその時点で twice の未解決参照を持っていないため、メンバを抽出しない可能性があります。その結果、未定義参照のエラーになります。順序を元に戻せば解決します。この順序の問題を解決するために無条件で --whole-archive を使用すると入力範囲が拡大し、さらにそれ自体がすべてのセクションを後続の GC から守ることを保証するものではありません。GNU ld アーカイブおよびセクション回収オプション
2回の選択:まずオブジェクトメンバを通過させ、次にどのセクションを保持するかを決定
caller.o
未解決参照:twice
twice.o を抽出
twice + unused_helper
--gc-sections
到達可能な twice のみを保持
-ffunction-sections は細粒度の境界を提供する。ルートと参照グラフが保持結果を決定する。
アーカイブメンバが抽出されていない場合、内部にセクションが存在しても、後続のレイアウトに自動的に参加することはない。
1つの .o を抽出したのに、なぜ1つの関数のみ残るのか?
「リンク入力に含まれたこと」と「最終的に生き残ったこと」を異なる集合として描くことで、ライブラリの順序とセクション回収の両方を同時に説明できます。伝統的なアーカイブスキャン時に twice の未解決参照がまだ存在しない場合、メンバを抽出しない可能性があります。強制抽出した後でも、unused がある独立したセクションは GC 中に削除される可能性があります。ここでは GNU ld.bfd の教育用の境界を固定し、結果を LLD などの異なる実装にそのまま適用しないようにしています。
図を準備しています 再読み込み
1つの .o を抽出したのに、なぜ1つの関数のみ残るのか? アーカイブ抽出はメンバ単位で行われ、セクション回収はルート到達可能な参照グラフに基づいて行われる。whole-archive はメンバを入力に強制するが、すべての関数セクションが保持されるわけではない。伝統的なアーカイブの順序が間違っていると、定義が全く抽出されない可能性がある。
{"id":"archive-and-section-reachability","title":"1つの .o を抽出したのに、なぜ1つの関数のみ残るのか?","summary":"アーカイブ抽出はメンバ単位で行われ、セクション回収はルート到達可能な参照グラフに基づいて行われる。whole-archive はメンバを入力に強制するが、すべての関数セクションが保持されるわけではない。伝統的なアーカイブの順序が間違っていると、定義が全く抽出されない可能性がある。","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":"抽出済みメンバ","gc":"関数セクション GC を有効化","heading":"1つの .o を抽出したのに、なぜ1つの関数のみ残るのか?","intro":"caller.o は twice を参照し、アーカイブメンバは twice と unused を同時に定義する。入力順序、whole-archive、セクション回収を個別に切り替えて、3つの集合を観察せよ。","kept":"最終的に保持された関数","order":"caller.o をアーカイブの前に配置","reset":"リセット","scope":"教育用シミュレーションは GNU ld.bfd の伝統的な単一アーカイブ処理を模倣する。関数ごとに独立したセクション、ルートは main、LTO、KEEP、追加のエクスポート、またはライブラリグループ化は有効にしていない。すべてのリンカのルールではない。","scopeLabel":"モデルの範囲と規約","unresolved":"未解決参照","whole":"whole-archive:強制抽出"}}
GOT と PLT:間接アクセスと解決のタイミングを分けて考える
GOT はアドレススロットを提供し、PLT は呼び出しスタブを提供します。これらは一部の位置独立コードやダイナミック呼び出しパスの一部です。古典的な遅延関数バインディングでは、最初の呼び出しはターゲットを検索して対応するスロットを更新し、後続の呼び出しはすでに保存されたアドレスを利用します。
即時バインディングは、関連する解決をロードフェーズに前倒しします。例えば、Linux ダイナミックリンカは LD_BIND_NOW を通じて制御できます。しかし、解決が前倒しされたからといって、元の PLT 呼び出し命令が間接オーバーヘッドのない直接呼び出しに自動的に変更されるわけではありません。PLT を使用するかどうか、緩和できるかどうか、GOT を通じて呼び出すかどうかは、コンパイルとリンクの選択に依存します。Linux ld.so
ダイナミックシンボルのインターポージションも、検索スコープ、バインディング、可視性の影響を受けます。これは実装の置換やプリロードに柔軟性をもたらす一方で、インライン化やローカルバインディングの仮定を制限する可能性があります。すべての malloc 呼び出しが必ず同じパスを通ると断言することはできません。
ロードと dlopen
ダイナミック ELF 起動:マッピングとリロケーション後にのみプログラムロジックに入る
カーネルは実行可能ファイルのプログラムヘッダを読み取る
ロードセグメントに基づいてアドレス空間を構築し、インタプリタを識別する
1
ダイナミックリンカが依存関係と必要なリロケーションを処理する
シンボル、TLS などを解決し、戦略に基づいてバインディングを処理する
2
ランタイム初期化を実行する
共有ライブラリの初期化と言語の起動ロジック
3
プログラムエントリから main などの言語エントリへ継続
main はカーネルが直接ジャンプする唯一のエントリ概念ではない
4
図は一般的な Linux ダイナミック ELF を範囲としている。静的、他の形式、およびプラットフォームのフローは異なる。
dlopen はプロセス実行中に共有オブジェクトをロードし、dlsym はシンボルを検索し、dlclose はロード参照を減少させます。実際のプラグインインターフェースでは、ABI、所有権、スレッド、アンロード条件を定義し、エラーをチェックする必要があります。関数ポインタやスレッドがまだライブラリコードを実行している間に、無秩序にアンロードすることはできません。dlopen マニュアル
ダイナミックロードによる既存ライブラリの使用と、JIT によるマシンコードの公開は異なります。JIT はメモリ上でオブジェクト、リロケーション、実行可能権限を処理でき、「.o を書き、.so にリンク、dlopen を実行する」というファイルフローを統一して通る必要はありません。JIT コンパイル を参照してください。
LTO:最適化がコンパイルユニット間の情報を見られるようにする
通常の独立したコンパイルでは、最適化器が見ることができる関数本体が制限されます。LTO は IR や関連する中間情報を保持し、リンクフェーズでシンボル解決結果と組み合わせてモジュール間の分析と最適化を実行し、その後マシンコードを生成してリンクを続行します。LTO がなくても、リンカはリロケーション、セクション回収、その他のリンク最適化を実行するため、「単に連結するだけ」と呼ぶことはできません。
Full LTO と ThinLTO の組織方法は異なります。ThinLTO はモジュールのサマリーを組み合わせてグローバル分析と選択的な関数インポートを実行し、その後モジュールバックエンドを並列実行し、キャッシュを使用することもできます。Clang は -flto=thin などの設定でこのモードを明示的に選択します。これは、gold や LLD の呼び出しにおける天然のデフォルトの LTO 方式ではありません。Clang ThinLTO ドキュメント
インライン化の可否はコストモデルや可視性などの制約によって決定され、「ホットパスの呼び出しのみをインポートする」ことに限りません。LTO がすべてのダイナミックプラグインや外部定義を把握しているわけでもなく、所谓的「全プログラム境界」はリンクとシンボルの仮定と組み合わせて考える必要があります。パフォーマンスの向上、メモリ使用量、リンク所要時間は実際のプロジェクトで測定すべきであり、一律の向上率を提供するものではありません。
問題特定のためのクイックリファレンス
現象 優先チェック項目
undefined reference 定義が生成されているか、ライブラリの順序、名前修飾、ABI と可視性
relocation overflow リロケーションの幅、レイアウト、コードモデル
起動時に共有ライブラリが見つからない 依存関係の記録、検索パス、プラットフォームのロードルール
同名シンボルが予期しない実装にバインドされる 検索スコープ、バージョン、プリロードと可視性
LTO が期待した効果を発揮しない コンパイルとリンクの設定、IR の利用可能性、最適化の境界
参考文献
Keywords: ELF, シンボル解決, リロケーション, PC32, PLT32, アーカイブ, GOT, PLT, 遅延バインディング, dlopen, LTO, ThinLTO