ELF 同时服务于不同阶段:编译器生成可重定位目标文件,链接器组合代码与数据,内核及动态加载器建立进程映像。调试器还会读取符号与调试信息。它们访问的是同一个文件的不同索引,而不是必须按顺序执行的几层结构。
理解 ELF 时先回答三个问题:这个值是文件偏移还是虚拟地址;这个表供哪一阶段使用;这部分内容是否真的需要映射到进程中。
两种视图,不是一条包含链
Section 按代码、数据、符号、重定位等用途组织内容。Program header 则描述映射及其他运行需求,其中 PT_LOAD 才表示可加载段。多个 section 可以被同一个 PT_LOAD 覆盖;PT_DYNAMIC、PT_INTERP 等还可能描述已位于 PT_LOAD 范围内的数据,不能把这些条目当作互不重叠的文件分区。
.dynsym 不“属于 PT_DYNAMIC”。PT_DYNAMIC 指向动态条目数组,其中 DT_SYMTAB 指向动态符号表。运行时定位不依赖 .dynsym 这个 section 名字。只读数据也不必和代码共用可执行段,实际权限布局由链接选项决定。ELF 动态链接结构
文件开头的 e_ident 先说明后续结构应怎样解释:
| 字节/字段 | 含义 |
| 0–3 | 0x7f、E、L、F 魔数 |
| EI_CLASS,字节 4 | ELF32 或 ELF64,决定结构布局 |
| EI_DATA,字节 5 | 数据字节序 |
| EI_VERSION,字节 6 | ELF 格式版本 |
| EI_OSABI,字节 7 | 操作系统/ABI 标识 |
| EI_ABIVERSION,字节 8 | 对应 ABI 的版本;不是填充字节 |
| 后续保留字节 | 不应解释为另一组随意拼接的字段 |
剩余 header 中,e_machine 描述目标架构,e_entry 描述入口虚拟地址,e_phoff/e_shoff 是两张表的文件偏移。表项长度与数量分别由 e_*entsize、e_*num 描述;不能用宿主机器的指针大小推断目标文件布局。elf(5)
| e_type | 常见用途 | 不能直接推出的结论 |
| ET_REL | 编译得到的 .o | 通常还不能直接执行 |
| ET_EXEC | 通常是非 PIE 可执行映像 | 不代表整个进程所有地址都固定 |
| ET_DYN | 共享对象,也用于 PIE | 不保证已启用 ASLR,也不单独证明它是主程序 |
| ET_CORE | 进程转储 | 不保证包含所有内存与完整调试信息 |
PIE 是否默认开启取决于工具链和发行版配置。显式使用 -fPIE -pie 更便于教学实验;检查结果时结合 program header、动态标志和工具的解释,不要只看扩展名。
PT_LOAD:文件内容如何对应到内存
设加载偏移为 B,一个 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 时须是相应的二次幂对齐值,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 零填充语义。
反查地址时,先判断它属于哪一种区域
假设调试器给出运行时地址 r,且已知它属于这个 ELF 映像,先减去该映像的加载偏移 B,得到链接虚拟地址 v。然后查找覆盖 v 的 PT_LOAD,最后才尝试换算文件偏移。同一进程的主程序与不同共享库各有自己的加载偏移,不能拿主程序的 B 去解释 libc 中的地址。
下面只计算前述单段的地址分类,不读取 ELF,也不模拟页表。用半开区间可以直接检查两个最容易出错的边界。
OFFSET, VADDR = 0x3000, 0x5000
FILESZ, MEMSZ = 0x600, 0xa00
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(0x4fff) == ("outside", None)
assert locate(0x5000) == ("file", 0x3000)
assert locate(0x55ff) == ("file", 0x35ff)
assert locate(0x5600) == ("zero", None)
assert locate(0x59ff) == ("zero", None)
assert locate(0x5a00) == ("outside", None)
bias = 0x70000000
runtime = bias + 0x5120
assert locate(runtime - bias) == ("file", 0x3120)
如果 v 是 0x5800,机械代公式会得到文件偏移 0x3800,但那个位置并不是此地址的初始数据来源;那里即使存在其他文件内容,也不能用来解释这块零初始化内存。反过来,file 分类只描述映像的初始来源:重定位或程序写入后,运行时字节未必仍与磁盘相同。
一个地址,三种坐标
关键不是先代公式,而是先检查 Δ 属于哪个区间。两条带共用段内位置标尺:上方文件带在 0x600 处结束,下方内存带继续到 0xa00。拖过这个边界,运行地址仍然存在,文件来源却已经消失;改变 B 则不改变来源分类。
Δ=r−B−pvaddr,f=poffset+Δ(0≤Δ<pfilesz)
正在呈现知识画面
一个地址,三种坐标地址换算只适用于有文件来源的区域;零填充尾部没有对应的文件字节。加载偏移只平移运行地址。
{"id":"elf-address-origin","title":"一个地址,三种坐标","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":"一个地址,三种坐标","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 header table 的可执行 ELF 在满足加载要求时仍可能运行;这不代表删除任意字节都安全。strip 通常移除部分符号或调试信息,动态重定位仍需使用的内容必须保留。调试时也可依靠匹配的独立调试文件,不能简单说“strip 后 core 永远无法分析”。
用自己的程序把两张表对起来
#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。不要预设恰好有两个 LOAD,也不要照抄另一台机器的地址。
再把同一源码停在三个不同阶段
在支持这些选项的 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 |
| program header | 此普通编译产物没有 | 描述可加载映像 | 描述可加载映像 |
| printf 符号 | 未定义,等待链接处理 | 通常仍由动态库提供 | 通常仍由动态库提供 |
| initialized 的普通已定义符号值 | 相对所属 section 的偏移 | 链接视角虚拟地址 | 链接视角虚拟地址 |
| 是否能直接运行 | 不能作为普通程序启动 | 能,输出 7 0 | 能,输出 7 0 |
ET_EXEC 也可以动态链接,因此“非 PIE”与“静态链接”是两个问题。比较 initialized 时,连同 Ndx 一起读:ET_REL 中的 Value=0 可以只是该变量位于 .data 开头;未定义 printf 的 Value=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;不能只接受此前扫描出的字符串开头。读取必须在这一张表的边界内找到 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"\0bad", 1)]:
try:
read_name(data, offset)
except ValueError:
pass
else:
raise AssertionError("invalid name accepted")
此函数只负责单次名字读取;文件级解析仍须验证整个字符串表的位置、长度与结构规则。section 名用 e_shstrndx 指定的表,符号名则用相应符号表关联的字符串表。偏移数值相同不意味着名字相同。
接下来可用 动态链接 理解符号如何变成地址,再用 加载与启动 追踪这些地址何时被使用。