ABI 是二进制组件之间的约定,包含数据布局、调用约定、符号及对象格式等方面。函数边界两侧即使由不同编译器生成,也要对参数在哪里、哪些寄存器会被修改和返回值如何表示取得一致。
违反 ABI 可能立即崩溃,也可能只是悄悄读错参数或破坏一个以后才使用的值。本文以常见用户态 x86-64 调用为例;系统调用、内核代码和特殊调用约定需要单独看待。
参数先分类,再分配位置
System V AMD64 的前六个常见整数或指针参数使用 RDI、RSI、RDX、RCX、R8、R9;浮点参数使用另一套 XMM 寄存器规则。聚合类型需要按 ABI 分类,不能把所有参数都塞进同一个“前六个”列表。
Windows x64 的前四个参数位置按其类型使用 RCX/RDX/R8/R9 或对应 XMM0–3,调用者预留至少 32 字节的寄存器参数 home/shadow space。混合整数浮点参数并不是两套独立的无限前四名队列。Microsoft x64 调用约定
| 项目 | System V AMD64 常见规则 | Windows x64 常见规则 |
| 整数/指针寄存器 | RDI、RSI、RDX、RCX、R8、R9 | 前四个位置用 RCX、RDX、R8、R9 |
| 标量浮点 | 按规则分配 XMM0–7 | 前四个位置对应 XMM0–3 |
| shadow space | 没有 Windows 式强制四槽区 | 调用者预留 32 字节 |
| red zone | 用户态有 128 字节约定 | 无相同保证 |
| 聚合、变参、向量 | 查分类与特殊规则 | 查独立规则 |
返回值同样按类型分类:标量整数常用 RAX,标量浮点常用 XMM0,某些聚合可拆到多个寄存器,另一些通过隐藏结果指针返回。“128 位结构体统一放 RDX:RAX”并不正确。AMD64 ABI
同一个混合参数声明,寄存器分配为什么不同
观察固定原型 double mix(int a, double b, int c, double d),不涉及变参或聚合。System V 分别消耗整数和浮点参数序列:a 使用 EDI,b 使用 XMM0,c 使用 ESI,d 使用 XMM1。Windows x64 按参数位置选择寄存器:a 使用 ECX,b 使用 XMM1,c 使用 R8D,d 使用 XMM3。
| 参数 | System V AMD64 | Windows x64 |
| a:第 1 个参数,int | EDI | ECX |
| b:第 2 个参数,double | XMM0 | XMM1 |
| c:第 3 个参数,int | ESI | R8D |
| d:第 4 个参数,double | XMM1 | XMM3 |
表中 int 只使用对应的 32 位值;不要从声明推断其寄存器其余位可用于传递其他参数。以下源文件不依赖平台头文件,可以分别生成两种目标的汇编:
double mix(int a, double b, int c, double d) {
return a + b + c + d;
}
clang --target=x86_64-unknown-linux-gnu -O2 -S -masm=intel mix.c -o mix-sysv.s
clang --target=x86_64-pc-windows-msvc -O2 -S -masm=intel mix.c -o mix-win.s
从整数转 double 的指令可辨认两个整数来源,再沿加法链观察浮点输入。这里仅跨目标生成汇编,不需要运行 Windows 程序;返回值都是 XMM0,但输入布局不同,不能通过强制转换函数指针来消除差异。Microsoft 参数传递
参数位置与类型队列,为什么不能混用?
下面不只展示一份固定表,而是让参数类型改变时重新分配位置。System V 侧的 int/double 分别推进自己的序列;Windows 侧保留参数位置,因此第二个位置的 double 是 XMM1,即使 XMM0 尚未用于传参。超过前四个位置后,本例改用栈槽;这与是否还有某个空闲寄存器无关。
正在呈现知识画面
参数位置与类型队列,为什么不能混用?System V 的本例分别消耗整数和浮点序列;Windows 前四个参数按位置选择 GPR 或 XMM,不能把第五个浮点参数重新塞进 XMM0。
{"id":"abi-argument-positions","title":"参数位置与类型队列,为什么不能混用?","summary":"System V 的本例分别消耗整数和浮点序列;Windows 前四个参数按位置选择 GPR 或 XMM,不能把第五个浮点参数重新塞进 XMM0。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div id=\"arguments\" class=\"choices\"></div><div id=\"locations\" class=\"table\"></div><div class=\"metrics\"><div><span data-i18n=\"space\"></span><strong id=\"space\"></strong></div></div><p class=\"hint\" data-i18n=\"hint\"></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#arguments{grid-template-columns:repeat(3,minmax(0,1fr))}#locations .tr{grid-template-columns:.6fr 1fr 1fr}#locations .tr>div{min-height:4.8em}","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;}));}let types=['int','double','int','double','double','int'];function draw(){let ni=0,nf=0;const sys=['EDI','ESI','EDX','ECX','R8D','R9D'],win=['ECX','EDX','R8D','R9D'],rows=types.map((type,i)=>['p'+(i+1)+': '+type,type==='int'?sys[ni++]:'XMM'+nf++,i<4?(type==='int'?win[i]:'XMM'+i):'[RSP+'+(40+(i-4)*8)+']']);types.forEach((type,i)=>{let b=document.querySelector('[data-arg=\"'+i+'\"]');if(!b){b=document.createElement('button');$('#arguments').append(b);}b.textContent='p'+(i+1)+': '+type;b.dataset.arg=i;b.setAttribute('aria-pressed',String(type==='double'));b.onclick=()=>{types[i]=type==='int'?'double':'int';draw()};});table('locations',[['','System V','Windows'],...rows]);set('space','32 byte');$('#locations').dataset.rows=JSON.stringify(rows);}draw();","audio":false,"strings":{"heading":"参数位置与类型队列,为什么不能混用?","hint":"每个参数按钮可直接切换类型;修改类型是在比较新的函数签名。","intro":"点击六个参数,切换 int / double。两种 ABI 同时给出固定原型下的位置,观察混合参数怎样消耗寄存器。","reset":"重置","scope":"固定原型、六个标量 int/double,无聚合、变参、向量或隐藏参数。栈偏移以函数入口 RSP 为基准,Windows 的32字节 home区另外保留。","scopeLabel":"模型范围与约定","space":"Windows 必需 home space"}}
保存约定:谁负责让值跨调用存活
Caller-saved 表示被调用者可以修改,调用者若还需要旧值,应先保存或换位置。Callee-saved 表示被调用者使用该寄存器时,需要在返回前恢复调用者的值。
System V 中 RBX、RBP、R12–R15 属于常见被调用者保存寄存器,RSP 必须按调用返回的约定恢复。RAX、RCX、RDX、RSI、RDI、R8–R11 可被调用破坏。向量寄存器和状态寄存器也有各自规则,不能只检查通用寄存器。
这直接影响寄存器分配:跨调用存活的值不能无保护地放在会被调用破坏的位置。
栈布局与对齐
对常见 System V 调用,call 之前 RSP 满足 16 字节对齐,call 压入 8 字节返回地址后,函数入口的 (RSP+8) 满足该对齐。某些栈上传递向量等情况有更强要求,应以对应 ABI 规则为准。
因此展示七个整数参数时,不能只写 push 第七参数; call 却完全忽略当前栈状态和填充。调用者可以预留整块参数区再写入,不必按源代码参数顺序逐个 push。
局部变量不一定在栈上,优化后可能在寄存器中、被常量替代或完全消失。帧指针也可省略;展开信息描述如何恢复调用者状态,不只是保存一张从 RSP 找旧 RBP 的表。ELF 中运行时展开常涉及 .eh_frame 等信息。
序言与尾声:满足约定的代码,不是固定模板
序言可能保存被调用者保存寄存器、分配栈空间、设置帧指针;尾声恢复相应状态。叶函数可能没有这些动作,优化还可以将保存推迟到真正需要的路径。
下面是一个 Linux System V 下调用外部 twice(long)、再加 1 的 GNU 汇编示例。使用 Intel 语法,入口 RSP 模 16 为 8,减 8 后为后续 call 建立对齐。
.intel_syntax noprefix
.text
.globl plus_one_twice
.type plus_one_twice, @function
plus_one_twice:
.cfi_startproc
sub rsp, 8
.cfi_def_cfa_offset 16
call twice@PLT
add rsp, 8
.cfi_def_cfa_offset 8
add rax, 1
ret
.cfi_endproc
.size plus_one_twice, .-plus_one_twice
.section .note.GNU-stack,"",@progbits
首个 long 参数已经位于 RDI,返回值位于 RAX;这个函数没有使用需要额外保存的 RBX 等寄存器。CFI 随栈调整描述 CFA,以便展开器恢复调用状态。
要在调用后继续使用原参数,就必须保留它
把计算改成 twice(x) + x。x 起初位于 RDI,而 twice 可以合法改写 RDI,因此返回后再直接加 RDI 是错误的。下面先把调用者的 RBX 保存到栈,再用 RBX 保留 x;twice 必须恢复它收到的 RBX,最后本函数再恢复上层调用者的旧值。
# keep_x.s:GNU 汇编,Linux System V AMD64
.intel_syntax noprefix
.text
.globl keep_x
.type keep_x, @function
keep_x:
.cfi_startproc
push rbx
.cfi_def_cfa_offset 16
.cfi_offset rbx, -16
mov rbx, rdi
call twice@PLT
add rax, rbx
pop rbx
.cfi_def_cfa_offset 8
.cfi_restore rbx
ret
.cfi_endproc
.size keep_x, .-keep_x
.section .note.GNU-stack,"",@progbits
入口 RSP 模 16 为 8,push 的 8 字节既保存 RBX,也使 call 前 RSP 模 16 为 0。这里不应照抄上一段的 sub rsp,8 再减一次,否则又会破坏这一调用点的对齐。
extern long keep_x(long);
long twice(long x) { return x * 2; }
int main(void) {
return keep_x(7) != 21 || keep_x(-3) != -9;
}
执行 cc -O2 abi-main.c keep_x.s -o abi-demo 后运行 abi-demo,正常退出状态为 0。只用这里的有限小值,避免让 long 乘法或加法的有符号溢出掩盖 ABI 问题。
一个编译器碰巧没有修改 RDI,不能证明错误写法正确。检查调用边界要依据被调方允许破坏哪些状态,而不是只观察某次机器码;实现变化、插桩或链接到另一个兼容定义都可能让潜伏错误显现。
跨调用的 x,保存在哪儿才可靠?
值是否保住、上层寄存器是否恢复、调用点是否对齐,是三项独立检查。下面允许构造“算出 21,却破坏 RBX”或“值都正确,却额外减栈导致失去对齐”的情况。数值结果是对示例被调方行为的符号推演;一旦违反 ABI,就不能把它当作真实机器执行仍保证返回该数值的承诺。
正在呈现知识画面
跨调用的 x,保存在哪儿才可靠?RDI 允许被被调方改写。用 RBX 保留 x 时必须先保存上层的旧 RBX;这次 push 已经完成对齐,再照抄 sub rsp,8 会破坏它。
{"id":"abi-preservation-alignment","title":"跨调用的 x,保存在哪儿才可靠?","summary":"RDI 允许被被调方改写。用 RBX 保留 x 时必须先保存上层的旧 RBX;这次 push 已经完成对齐,再照抄 sub rsp,8 会破坏它。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"mode\"><button data-mode=\"none\" data-i18n=\"none\"></button><button data-mode=\"bare\" data-i18n=\"bare\"></button><button data-mode=\"saved\" data-i18n=\"saved\"></button></div><label class=\"check\"><input type=\"checkbox\" id=\"extra\"><span data-i18n=\"extra\"></span></label><div id=\"states\" class=\"table\"></div><div class=\"metrics\"><div><span data-i18n=\"result\"></span><strong id=\"result\"></strong></div><div><span data-i18n=\"rbx\"></span><strong id=\"rbx\"></strong></div><div><span data-i18n=\"align\"></span><strong id=\"align\"></strong></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"bad\"></p></div><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{grid-template-columns:repeat(3,minmax(0,1fr))}.metrics strong{font-size:20px}#states .tr{grid-template-columns:1fr 1fr}","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;}));}let mode='saved';function draw(){const push=mode==='saved'?8:0,extra=$('#extra').checked?8:0,mod=(8-push-extra+32)%16,result=mode==='none'?113:21,rbx=mode==='bare'?7:90;table('states',[['entry','RDI=7; RBX=90'],['before call','RSP−'+(push+extra)],['callee','RAX=14; RDI=99'],['after restore','RBX='+rbx]]);set('result',result);set('rbx',rbx);set('align',mod);const valid=result===21&&rbx===90&&mod===0;set('explanation',t(valid?'ok':'bad'));$('#states').dataset.valid=String(valid);pressed(mode);}$('#extra').onchange=draw;document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;draw()});draw();","audio":false,"strings":{"align":"call 前 RSP mod16","bad":"至少一个调用契约失败;结果碰巧正确也不能掩盖其他破坏。","bare":"借用 RBX,不保存","extra":"额外 sub/add rsp,8","heading":"跨调用的 x,保存在哪儿才可靠?","intro":"目标是 twice(x)+x,x=7。选择保存方案,并决定是否额外减栈 8 字节,检查值和调用点对齐。","mode":"保存方案","none":"直接依赖 RDI","ok":"值保存、RBX 恢复与栈对齐三项同时成立。","rbx":"返回时 RBX(应为90)","reset":"重置","result":"本函数结果(目标21)","saved":"push/pop RBX","scope":"固定 System V AMD64 普通标量调用,入口 RSP mod16=8,上层RBX=90。twice 返回14并合法把RDI改为99;仅检查本例调用边界。","scopeLabel":"模型范围与约定"}}
Red zone:不能让临时值无保护地跨调用
System V 用户态在 RSP 以下提供 128 字节 red zone,信号处理等需遵守相关约定。函数可用它保存临时数据而不调整 RSP,叶函数尤其常见。
但嵌套调用会使用更低地址的栈空间,不能指望 red zone 中的值安全跨过普通函数调用。非叶函数也可以在适当区间使用不需跨调用存活的临时数据,因此“只有叶函数在任何意义上能用”过于绝对。内核、中断和其他平台不能直接照搬此保证。
变参:调用方知道实际参数,被调方按约定取出
printf 等函数的声明没有列出全部实参类型,但调用点的编译器知道实际表达式,并应用默认参数提升,例如 float 提升为 double。格式字符串和实际实参是否匹配仍属于语言与接口检查问题。
System V 对变参调用使用 AL 表示向量寄存器参数使用数量的上界,取值范围按 ABI 规定。被调方按需要建立寄存器保存区,va_list 跟踪寄存器区与栈参数区。不是调用方普遍把所有 XMM 参数预先写进被调方的 va_list。
Windows 的变参规则不同,浮点实参在相关位置可能需要同时放入通用寄存器。跨 ABI 手写桥接时必须专门处理,而不能只替换参数寄存器名字。
PIC 与 PIE:减少绝对地址依赖
位置无关代码常利用 PC 相对寻址访问局部数据与代码,对可被动态绑定的符号使用 GOT、PLT 或其他约定路径。不是所有 PIC 符号访问都必然多一次 GOT 间接:绑定、可见性和链接器放松可能允许直接的相对访问。
PIE 是可执行文件的位置无关形式之一,配合加载地址随机化使用。是否需要某种重定位以及重定位发生在何处,要结合对象格式和符号规则。见链接与加载。
跨语言边界速查
核对函数原型、数据大小与对齐、聚合分类、调用约定、异常是否能跨边界、内存由谁释放,以及符号名称。extern "C" 一类声明能指定部分链接与调用规则,但不会自动让所有语言对象都具有兼容布局和生命周期。
参考
Keywords: ABI, calling convention, stack alignment, caller-saved, callee-saved, shadow space, red zone, variadic, CFI, PIC, PIE