ELFは異なる段階で機能する。コンパイラは再配置可能なオブジェクトファイルを生成し、リンカはコードとデータを結合し、カーネルやダイナミックローダはプロセスイメージを構築する。デバッガはシンボルやデバッグ情報も読み込む。これらは、順番に実行される複数のレイヤーではなく、同じファイルの異なるインデックスにアクセスしている。
ELFを理解する際は、まず3つの質問に答えよ。その値はファイルオフセットか仮想アドレスか。そのテーブルはどの段階で使われるか。その内容は本当にプロセスにマップされる必要があるか。
2つのビュー、包含チェーンではない
同じELF:sectionが内容を整理し、segmentがロード要件を記述
リンク時ビュー:section
ロード時ビュー:プログラムヘッダ
.text
PT_LOAD:R + X
.rodata、.dynsym、.dynstr
PT_LOAD:R
.data、.bss、.dynamic
PT_LOAD:R + W
図のレイアウトは固定テンプレートではありません。PT_DYNAMICはダイナミックエントリを指し、エントリがシンボルテーブルなどのデータを参照します。
Sectionはコード、データ、シンボル、リロケーションなどの用途に応じて内容を整理します。Program headerはマップやその他の実行時の要件を記述し、その中でPT_LOADのみがロード可能なセグメントを示します。複数のsectionが1つのPT_LOADでカバーされることもあります。PT_DYNAMICやPT_INTERPなどは、すでにPT_LOADの範囲内にあるデータを記述している場合があり、これらのエントリを互いに重ならないファイルの区切りとして扱うことはできません。
.dynsymは「PT_DYNAMICに属する」わけではありません。PT_DYNAMICはダイナミックエントリ配列を指し、その中のDT_SYMTABがダイナミックシンボルテーブルを指します。実行時の解決は.dynsymというsection名に依存しません。読み取り専用データはコードと同じ実行可能セグメントに共有する必要はなく、実際の権限レイアウトはリンカオプションによって決定されます。ELFダイナミックリンク構造
ELFヘッダはインデックスのエントリポイント
ファイル先頭のe_identは、後の構造をどのように解釈すべきかをまず示します。
バイト/フィールド 意味
0–3 0x7f、E、L、F のマジックナンバー
EI_CLASS、バイト4 ELF32またはELF64。構造レイアウトを決定
EI_DATA、バイト5 データのバイトオーダー
EI_VERSION、バイト6 ELFフォーマットのバージョン
EI_OSABI、バイト7 OS/ABIの識別子
EI_ABIVERSION、バイト8 対応するABIのバージョン。パディングバイトではない
後の予約バイト 別のランダムなフィールドの組み合わせとして解釈すべきではない
残りのヘッダでは、e_machineはターゲットアーキテクチャを記述し、e_entryはエントリポイントの仮想アドレスを記述します。e_phoff/e_shoffは2つのテーブルのファイルオフセットです。エントリの長さと数はそれぞれe_*entsize、e_*numで記述されます。ホストマシンのポインタサイズからターゲットファイルのレイアウトを推測してはいけません。elf(5)
e_type 一般的な用途 直接導き出せない結論
ET_REL コンパイルされた.o 通常、直接実行はできない
ET_EXEC 通常はPIEではない実行可能イメージ プロセス内の全アドレスが固定されているわけではない
ET_DYN 共有オブジェクト。PIEにも使用される ASLRが有効になっている保証はなく、単独でメインプログラムであることを証明するものでもない
ET_CORE プロセスダンプ 全メモリと完全なデバッグ情報が含まれている保証はない
PIEがデフォルトで有効かどうかは、ツールチェーンやディストリビューションの設定に依存します。明示的に-fPIE -pieを使用すると、教育実験でより扱いやすくなります。結果を確認する際は、プログラムヘッダ、ダイナミックフラグ、ツールの解釈を組み合わせ、拡張子だけを見てはいけません。
PT_LOAD:ファイル内容がメモリにどのように対応するか
ロードオフセットをBとし、1つのPT_LOADの主要なフィールドは以下の通りです。p_offset(ファイル上の起点)、p_vaddr(リンク時の視点での仮想起点)、p_filesz(ファイル内容の長さ)、p_memsz(メモリ上の長さ)、p_flags(読み書き実行の権限)、そしてp_align(アラインメントの制約)。
このセグメントのファイル上でサポートされる領域において、ファイルオフセットfに対応する実行時のアドレスは次の式で表されます。
runtime_address = B + p_vaddr + (f - p_offset)
これは、fがそのセグメントのファイル上の範囲内にあることを要求します。p_memszはp_filesz以上でなければならず、超える分のメモリ領域はゼロで初期化されます。p_alignが1より大きい場合は、対応する2のべき乗のアラインメント値でなければならず、p_vaddrとp_offsetは合同条件を満たし、ロードにはページアラインメントの規則も満たす必要があります。ELF Program Loading
例えば、読み書き可能なセグメントでp_offset=0x3000、p_vaddr=0x5000、p_filesz=0x600、p_memsz=0xa00の場合、ファイルの[0x3000, 0x3600)はメモリ[B+0x5000, B+0x5600)に対応し、続く[B+0x5600, B+0x5a00)はゼロになります。ファイルオフセット0x3120はB+0x5120に対応します。
実際のマップはページ単位で行われ、ファイル末尾のページのゼロ埋めと、その後の匿名ページは異なる手順が必要になる場合があります。p_memszをそのままファイルマップの長さとして扱い、ファイル末尾以降が自然にELFのゼロ埋めセマンティクスに従うと考えるのは誤りです。
同じセグメント内でも、前半分のみがファイルオフセットに変換可能
ファイルオフセット
0x3000 ≤ f < 0x3600
0x600 バイト:ファイルから取得
リンク時の仮想アドレス
0x5000 ≤ v < 0x5600
ファイルでサポートされる初期化済みコンテンツ
0x5600 ≤ v < 0x5a00
0x400 バイト:ゼロで初期化
実行時アドレス = B + v。右側のゼロ埋め領域には対応するファイルバイトがありません。
区間の右端は含まれない:0x5600はすでにゼロ埋め領域に入り、0x5a00はこのセグメントから外れている。
アドレスを逆引きする際は、まずそれがどの領域に属するかを判断する
デバッガが実行時アドレスrを与え、それがこのELFイメージに属していることが分かっていると仮定します。まず、そのイメージのロードオフセットBを引いて、リンク時の仮想アドレスvを取得します。次に、vをカバーするPT_LOADを検索し、最後にファイルオフセットへの変換を試みます。同じプロセス内のメインプログラムと異なる共有ライブラリはそれぞれ独自のロードオフセットを持つため、メインプログラムのBを使ってlibc内のアドレスを解釈することはできません。
以下では、前述の単一セグメントのアドレス分類のみを計算し、ELFを読み込んだりページテーブルをシミュレートしたりはしません。半開区間を使うことで、最も間違いやすい2つの境界を直接チェックできます。
OFFSET , VADDR = 0x 3000 , 0x 5000
FILESZ , MEMSZ = 0x 600 , 0x a00
def locate ( v ) :
delta = v - VADDR
if not 0 <= delta < MEMSZ :
return ( " outside" , None )
if delta >= FILESZ :
return ( " zero" , None )
return ( " file" , OFFSET + delta )
assert locate ( 0x 4fff) == ( " outside" , None )
assert locate ( 0x 5000) == ( " file" , 0x 3000)
assert locate ( 0x 55ff) == ( " file" , 0x 35ff)
assert locate ( 0x 5600) == ( " zero" , None )
assert locate ( 0x 59ff) == ( " zero" , None )
assert locate ( 0x 5a00) == ( " outside" , None )
bias = 0x 70000000
runtime = bias + 0x 5120
assert locate ( runtime - bias ) == ( " file" , 0x 3120)
vが0x5800の場合、機械的に式に代入するとファイルオフセット0x3800が得られますが、その位置はこのアドレスの初期データのソースではありません。そこに他のファイルコンテンツが存在したとしても、そのゼロ初期化されたメモリを説明するために使用することはできません。逆に、file分類はイメージの初期ソースを記述しているだけです。リロケーションやプログラムによる書き込み後、実行時のバイトはディスク上の内容と一致しない場合があります。
1つのアドレス、3つの座標
重要なのは、まず式に代入することではなく、Δがどの区間に属するかをチェックすることです。2つの帯はセグメント内の位置目盛りを共有しています。上のファイル帯は0x600で終わり、下のメモリ帯は0xa00まで続きます。この境界を越えると、実行アドレスは存在しますが、ファイルのソースはすでに消えています。Bを変えても、ソースの分類は変わりません。
Δ = r − B − p v a dd r , f = p o ff se t + Δ ( 0 ≤ Δ < p f i l esz )
図を準備しています 再読み込み
1つのアドレス、3つの座標 アドレスの変換はファイルソースを持つ領域にのみ適用されます。ゼロ埋めされた尾部には対応するファイルバイトがありません。ロードオフセットは実行アドレスを単にシフトさせるだけです。
{"id":"elf-address-origin","title":"1つのアドレス、3つの座標","summary":"アドレスの変換はファイルソースを持つ領域にのみ適用されます。ゼロ埋めされた尾部には対応するファイルバイトがありません。ロードオフセットは実行アドレスを単にシフトさせるだけです。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"pos\" data-i18n=\"pos\"></label><output for=\"pos\" id=\"pos-value\"></output><input id=\"pos\" type=\"range\" min=\"0\" max=\"2560\" value=\"288\" step=\"1\"></div><div class=\"control\"><label for=\"bias\" data-i18n=\"bias\"></label><output for=\"bias\" id=\"bias-value\"></output><input id=\"bias\" type=\"range\" min=\"0\" max=\"8\" value=\"4\" step=\"1\"></div><div class=\"track-row\"><span data-i18n=\"filemap\"></span><div id=\"filemap\" class=\"track\"></div></div><div class=\"track-row\"><span data-i18n=\"memmap\"></span><div id=\"memmap\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"file\"></span><strong id=\"file\"></strong></div><div><span data-i18n=\"link\"></span><strong id=\"link\"></strong></div><div><span data-i18n=\"runtime\"></span><strong id=\"runtime\"></strong></div><div><span data-i18n=\"origin\"></span><strong id=\"origin\"></strong></div></div><p class=\"formula\" id=\"equation\"></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.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.track{position:relative;height:40px;gap:0;background:var(--surface);border-radius:5px}.track i{display:block;background:var(--accent-soft);border:1px solid var(--accent)}.track i.gold{background:var(--second-soft);border-color:var(--second)}.track b{position:absolute;top:-3px;bottom:-3px;width:2px;background:var(--ink)}.metrics strong{font-size:18px}#equation{min-height:6em;white-space:pre-line}","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).classList.add('table');$('#'+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 band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}const hex=n=>'0x'+n.toString(16);function draw(){const d=+$('#pos').value,B=+$('#bias').value*4096,kind=d<1536?'backed':d<2560?'zero':'outside';set('pos-value',hex(d));set('bias-value',hex(B));set('file',kind==='backed'?hex(0x3000+d):'∅');set('link',hex(0x5000+d));set('runtime',hex(B+0x5000+d));set('origin',t(kind));for(const id of ['filemap','memmap']){$('#'+id).innerHTML='<i style=\"width:60%\"></i>'+(id==='memmap'?'<i class=\"gold\" style=\"width:40%\"></i>':'')+'<b style=\"left:calc('+d/2560*100+'% - 1px)\"></b>';}set('equation','Δ = r − B − p_vaddr = '+hex(d)+'\\n'+(kind==='backed'?'f = p_offset + Δ = '+hex(0x3000+d):kind==='zero'?'p_filesz ≤ Δ < p_memsz; f = ∅':'Δ ≥ p_memsz; f = ∅'));$('#origin').dataset.kind=kind;}document.querySelectorAll('input').forEach(e=>e.oninput=draw);draw();","audio":false,"strings":{"backed":"ファイルバイト","bias":"ロードオフセット B(ページ)","file":"ファイルオフセット f","filemap":"ファイル:0x3000からの0x600バイト","heading":"1つのアドレス、3つの座標","intro":"セグメント内の位置をドラッグして、ファイル、リンクアドレス、実行アドレスの対応関係を確認してください。","link":"リンクアドレス v","memmap":"メモリ:0x5000からの0xa00バイト","origin":"初期ソース","outside":"セグメント外","pos":"セグメント起点からの相対位置 Δ(B)","reset":"リセット","runtime":"実行アドレス r","scope":"本文の単一PT_LOADを使用します。単位はバイトです。初期ソースのみを表示し、ページテーブル、リロケーション後の書き込み、物理的な残留はシミュレートしません。","scopeLabel":"モデルの範囲と規約","zero":"ゼロ初期化"}}
一般的なsectionの役割による速查
役割 一般的な名前 読む重点
プログラムの内容 .text、.rodata、.data SHF_ALLOCフラグの有無、および最終的なセグメントの権限
ゼロ初期化ストレージ .bss、SHT_NOBITS メモリ上のサイズはあるが、ファイル内には同じサイズの有効なペイロードがない
シンボル .symtab、.dynsym 通常シンボルテーブルと実行時ダイナミックシンボルテーブルの役割分担が異なる
名前 .strtab、.dynstr、.shstrtab 文字列のオフセットは正しい文字列テーブルを指している必要がある
リロケーション .rel.、.rela. RELとRELAでのaddendの格納方法が異なる
ダイナミック情報 .dynamic、.interp 依存関係、シンボルアドレス、インタプリタのパスなど
初期化 .init_array、.fini_array 通常は関数ポインタを格納し、.init/.finiの命令内容とは異なる
デバッグ .debug_* 通常はロードされず、個別のデバッグファイルに分割されている場合がある
sh_nameはsection名文字列テーブル内のオフセットであり、ポインタではありません。sh_link、sh_infoの解釈はsectionのタイプに依存します。例えば、リロケーションテーブルはシンボルテーブルやターゲットsectionと関連付ける必要があり、それらにグローバルな固定の意味を押し付けてはいけません。
sectionヘッダテーブルのない実行可能ELFでも、ロード要件を満たせば実行できる可能性があります。これは、任意のバイトを削除しても安全という意味ではありません。stripは通常、一部のシンボルやデバッグ情報を削除しますが、ダイナミックリロケーションでまだ使用される内容は保持する必要があります。デバッグ時には一致する個別のデバッグファイルに頼ることもでき、「strip後はcoreファイルは永遠に分析できない」と単純に言うことはできません。
自分のプログラムで2つのテーブルを照合する
#include < stdio.h>
int initialized = 7 ;
unsigned char zeros[ 8192 ] ;
int main ( void )
{
printf ( " %d %u \n " , initialized, ( unsigned ) zeros[ 8191 ] ) ;
return 0 ;
}
cc - g - fPIE - pie elf-demo.c - o elf-demo
./elf-demo
readelf - hW elf-demo
readelf - SW elf-demo
readelf - lW elf-demo
readelf - dW elf-demo
readelf - rW elf-demo
readelf -- dyn-syms - W elf-demo
出力は7と0になるはずです。まず、-SWの出力で.data/.bssを見つけ、次に-lWの末尾にあるSection to Segment mappingを確認し、最後に該当するLOADのFileSizとMemSizを比較します。ちょうど2つのLOADがあるとは限らないと想定せず、他のマシンのアドレスをそのままコピーしないでください。
さらに、同じソースコードを3つの異なる段階で止める
これらのオプションをサポートするLinux GCC/Clangツールチェーンでは、以下のビルドを続けて行います。ここでは明示的にPIEと非PIEを指定し、ディストリビューションのデフォルト値が比較に影響しないようにします。
cc - g - fPIE - c elf-demo.c - o elf-demo.o
cc - g - fno-pie - no-pie elf-demo.c - o elf-demo-exec
./elf-demo-exec
readelf - hW elf-demo.o
readelf - lW elf-demo.o
readelf - sW elf-demo.o
readelf - hW elf-demo-exec
readelf - sW elf-demo
観察点 elf-demo.o elf-demo(PIE) elf-demo-exec
e_type REL DYN EXEC
プログラムヘッダ この通常のコンパイル成果物には存在しない ロード可能イメージを記述 ロード可能イメージを記述
printfシンボル 未定義。リンク待ち 通常、共有ライブラリによって提供される 通常、共有ライブラリによって提供される
initializedの通常定義済みシンボル値 所属するsectionへの相対オフセット リンク時の視点での仮想アドレス リンク時の視点での仮想アドレス
直接実行可能か 通常のプログラムとして起動できない 可能。7 0を出力 可能。7 0を出力
ET_EXECでもダイナミックリンクできるため、「非PIE」と「静的リンク」は別の問題です。initializedを比較する際は、Ndxと一緒に読み取ってください。ET_RELでのValue=0は、単にその変数が.dataの先頭にあるだけかもしれません。未定義のprintfのValue=0は、関数がアドレス0にあることを意味するものではありません。TLSシンボルには専用のオフセットの意味があり、すべてのst_valueを単にBに足すことはできません。ELFシンボル値
BSSのサイズがファイルにそのまま追加されない理由
zerosは8192バイトありますが、その初期コンテンツは「サイズとゼロクリアの規則」で表現できるため、ファイル内に8192個のゼロを保存する必要はありません。readelf -SWで.bssのNOBITSタイプとSizeを確認し、readelf -lWで書き込み可能なLOADのMemSizとFileSizを確認してください。差額には、アラインメントや他のゼロ初期化オブジェクトも含まれる可能性があるため、それがちょうど8192に一致する必要はありません。
ファイルの総サイズには、ELFテーブル、デバッグ情報、シンボル、パディングなども含まれるため、すべてのLOADのFileSizを単純に足してファイルの長さとすることはできません。セグメントの範囲自体も、排他的な区切りであることが保証されているわけではありません。同様に、MemSizはイメージの仮想メモリ上のニーズを表しており、プロセスが現在保持している物理ページ数を直接示すものではありません。これらの量を理解した上で、「ディスク上のファイルサイズが大きい」「アドレス空間が大きい」「現在の物理メモリ使用量が大きい」を区別できるようになりましょう。
パーサを書く際は、まず構造の境界を守る
ファイルバッファを直接Elf64_Ehdr*にキャストすると、バイトオーダー、アラインメント、切り捨て、ELF32の処理を無視することになります。信頼性の高いパーサでは、まずe_identを検証し、ターゲットのエンコーディングに従ってフィールドを読み取り、オフセットと長さがすべてファイルの範囲内にあるかを確認する必要があります。乗算と加算のオーバーフローチェックは、アクセスの前に発生させる必要があります。
数量フィールドには拡張エンコーディングが存在します。e_phnumのPN_XNUM、e_shnumの拡張値、およびSHN_XINDEXは、section 0と組み合わせてデコードする必要があります。文字列テーブルについても、オフセットと終了境界を検証する必要があります。教育現場で「64バイトを読み取っていくつかのフィールドを出力する」のは、フォーマット識別に過ぎず、完全なELFバリデーターとは呼びません。
文字列のオフセットも、それ自体のテーブル内で解釈する必要がある
例えば、文字列テーブルがb"\0.text\0.data\0"の場合、オフセット1は.textに対応し、オフセット7は.dataに対応します。オフセットは既存の文字列の途中を指すこともでき、オフセット2はtextに対応します。これまでにスキャンした文字列の先頭のみを受け入れるわけではありません。読み取りは、この1つのテーブルの境界内でNULを見つける必要があり、後ろのsectionにまでスキャンしてはいけません。ELF String Table
def read_name ( table , offset ) :
if not 0 <= offset < len ( table ) :
raise ValueError ( " string offset outside table" )
end = table . find ( b " \0 " , offset )
if end == - 1 :
raise ValueError ( " unterminated name" )
return table [ offset : end ]
table = b " \0 .text\0 .data\0 "
assert read_name ( table , 0 ) == b " "
assert read_name ( table , 1 ) == b " .text"
assert read_name ( table , 2 ) == b " text"
assert read_name ( table , 7 ) == b " .data"
for data , offset in [ ( table , - 1 ) , ( table , len ( table ) ) , ( b " \0 bad" , 1 ) ] :
try :
read_name ( data , offset )
except ValueError :
pass
else :
raise AssertionError ( " invalid name accepted" )
この関数は単なる1回の名前読み取りを担当するだけです。ファイルレベルの解析では、文字列テーブル全体の位置、長さ、構造ルールを検証する必要があります。section名はe_shstrndxで指定されたテーブルを使用し、シンボル名は対応するシンボルテーブルに関連付けられた文字列テーブルを使用します。オフセットの数値が同じだからといって、名前が同じであるとは限りません。
次に、ダイナミックリンク を使って、シンボルがどのようにアドレスになるかを理解し、ロードと起動 を使って、これらのアドレスがいつ使用されるかを追跡します。