fold/third_party/ghidra-decompiler/specs/x86/x86gcc.cspec
trvon c08081a016
feat: vendor x86-64 + 32-bit SLEIGH; precise SSA def-use; write-loop FP guard
Unlocks the x86 family end-to-end and makes the write-loop detector precise
enough for statically-linked binaries.

x86 SLEIGH:
- Vendor Ghidra's x86 language specs (specs/x86, x86:LE:64:default + :32:default);
  add x86-64/x86 .sla build targets. archSpecFor maps x86/64 + x86/32.
- ghidra_lifter: set the x86-64 decode context (longMode/addrsize/opsize) in the
  raw path, and bound decompiler flow at the function end (0xC3 tail fill) so
  variable-length x86 does not run away into zero padding.

Precise SSA def-use (benefits all arches):
- ir::pcode::PcodeOp gains in_def (defining-op index per input); the SSA lifter
  records real def-use. summarizePointerWrites now resolves the store-address
  base via def-use instead of a last-write heuristic — essential on x86 where
  arg registers are reused as scratch (RAX) and sub-registers are written (SIL).
- paramIndexOf is arch-aware (AArch64 x0–x7; SysV x86-64 RDI/RSI/RDX/RCX/R8/R9).

Precision (the key fix at scale):
- A copy bounded by a size *parameter* (memcpy-like `for i<n`) is suppressed:
  if the loop index is `<`-compared against a pure parameter input, the function
  is size-bounded and not flagged. baby_bof's decode_base64 bounds on
  strlen(input) (a computed value, not a param) so it still fires. This cut a
  statically-linked x86-64 binary from 101 findings (≈100 glibc/libstdc++ FPs)
  to 1 — the real decode_base64 → 264-byte overflow.

Perf + reach:
- Cache the pre-pass SSA lift and reuse it in the main pass (re-lifting only the
  few callers of a write-loop callee that need the injected prototype).
- Give whole-binary r2 analysis a generous timeout (FOLD_R2_ANALYSIS_TIMEOUT_MS,
  default 120s) so large static binaries actually analyze (the 3s per-command
  watchdog left them with an empty function list).

Validated: decode_base64 overflow fires on x86-64 (index_amd64.cgi) and AArch64
(index_nocanary.cgi); AArch64 corpus BOF counts unchanged (3/1/1); x86-64 static
FP 101→1. Unit: test_pcode_summary +x86-64 case (9 assertions). meson test 77/77.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:34:28 -04:00

480 lines
13 KiB
XML

<?xml version="1.0" encoding="UTF-8"?>
<compiler_spec>
<data_organization>
<machine_alignment value="2" />
<default_alignment value="1" />
<default_pointer_alignment value="4" />
<pointer_size value="4" />
<wchar_size value="4" />
<short_size value="2" />
<integer_size value="4" />
<long_size value="4" />
<long_long_size value="8" />
<float_size value="4" />
<double_size value="8" />
<long_double_size value="10" /> <!-- aligned-length=12 -->
<size_alignment_map>
<entry size="1" alignment="1" />
<entry size="2" alignment="2" />
<entry size="4" alignment="4" />
<entry size="8" alignment="4" />
<entry size="16" alignment="16" />
</size_alignment_map>
</data_organization>
<global>
<range space="ram"/>
<range space="OTHER"/>
<register name="MXCSR"/>
</global>
<stackpointer register="ESP" space="ram"/>
<returnaddress>
<varnode space="stack" offset="0" size="4"/>
</returnaddress>
<default_proto>
<prototype name="__cdecl" extrapop="4" stackshift="4">
<input>
<pentry minsize="1" maxsize="500" align="4">
<addr offset="4" space="stack"/>
</pentry>
</input>
<output killedbycall="true">
<pentry minsize="4" maxsize="10" metatype="float" extension="float">
<register name="ST0"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
<pentry minsize="5" maxsize="8">
<addr space="join" piece1="EDX" piece2="EAX"/>
</pentry>
<!-- Any struct type is returned by pointer, regardless of size -->
<rule>
<datatype name="struct"/>
<hidden_return/>
</rule>
</output>
<unaffected>
<register name="ESP"/>
<register name="EBP"/>
<register name="ESI"/>
<register name="EDI"/>
<register name="EBX"/>
</unaffected>
<killedbycall>
<register name="ECX"/>
<register name="EDX"/>
<register name="ST0"/>
<register name="ST1"/>
</killedbycall>
<likelytrash>
<register name="EAX"/>
</likelytrash>
</prototype>
</default_proto>
<prototype name="__cdeclf" extrapop="4" stackshift="4">
<input>
<pentry minsize="1" maxsize="500" align="4">
<addr offset="4" space="stack"/>
</pentry>
</input>
<output killedbycall="true">
<pentry minsize="1" maxsize="10">
<register name="ST0"/>
</pentry>
</output>
<unaffected>
<register name="ESP"/>
<register name="EBP"/>
<register name="ESI"/>
<register name="EDI"/>
<register name="EBX"/>
</unaffected>
<killedbycall>
<register name="ECX"/>
<register name="EDX"/>
</killedbycall>
<likelytrash>
<register name="EAX"/>
</likelytrash>
</prototype>
<prototype name="__thiscall" extrapop="4" stackshift="4">
<input>
<pentry minsize="1" maxsize="500" align="4">
<addr offset="4" space="stack"/>
</pentry>
</input>
<output killedbycall="true">
<pentry minsize="4" maxsize="10" metatype="float" extension="float">
<register name="ST0"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
<pentry minsize="5" maxsize="8">
<addr space="join" piece1="EDX" piece2="EAX"/>
</pentry>
</output>
<unaffected>
<register name="ESP"/>
<register name="EBP"/>
<register name="ESI"/>
<register name="EDI"/>
<register name="EBX"/>
</unaffected>
<killedbycall>
<register name="ECX"/>
<register name="EDX"/>
<register name="ST0"/>
<register name="ST1"/>
</killedbycall>
<likelytrash>
<register name="EAX"/>
</likelytrash>
</prototype>
<prototype name="__regparm3" extrapop="4" stackshift="4"> <!-- Used particularly by linux kernel -->
<input>
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EDX"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="ECX"/>
</pentry>
<pentry minsize="1" maxsize="500" align="4">
<addr offset="4" space="stack"/>
</pentry>
</input>
<output killedbycall="true">
<pentry minsize="4" maxsize="10" metatype="float" extension="float">
<register name="ST0"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
<pentry minsize="5" maxsize="8">
<addr space="join" piece1="EDX" piece2="EAX"/>
</pentry>
</output>
<unaffected>
<register name="ESP"/>
<register name="EBP"/>
<register name="ESI"/>
<register name="EDI"/>
<register name="EBX"/>
</unaffected>
<killedbycall>
<register name="ECX"/>
<register name="EDX"/>
<register name="ST0"/>
<register name="ST1"/>
</killedbycall>
<likelytrash>
<register name="EAX"/>
</likelytrash>
</prototype>
<prototype name="__regparm2" extrapop="4" stackshift="4">
<input>
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EDX"/>
</pentry>
<pentry minsize="1" maxsize="500" align="4">
<addr offset="4" space="stack"/>
</pentry>
</input>
<output killedbycall="true">
<pentry minsize="4" maxsize="10" metatype="float" extension="float">
<register name="ST0"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
<pentry minsize="5" maxsize="8">
<addr space="join" piece1="EDX" piece2="EAX"/>
</pentry>
</output>
<unaffected>
<register name="ESP"/>
<register name="EBP"/>
<register name="ESI"/>
<register name="EDI"/>
<register name="EBX"/>
</unaffected>
<killedbycall>
<register name="ECX"/>
<register name="EDX"/>
<register name="ST0"/>
<register name="ST1"/>
</killedbycall>
<likelytrash>
<register name="EAX"/>
</likelytrash>
</prototype>
<prototype name="__regparm1" extrapop="4" stackshift="4">
<input>
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
<pentry minsize="1" maxsize="500" align="4">
<addr offset="4" space="stack"/>
</pentry>
</input>
<output killedbycall="true">
<pentry minsize="4" maxsize="10" metatype="float" extension="float">
<register name="ST0"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
<pentry minsize="5" maxsize="8">
<addr space="join" piece1="EDX" piece2="EAX"/>
</pentry>
</output>
<unaffected>
<register name="ESP"/>
<register name="EBP"/>
<register name="ESI"/>
<register name="EDI"/>
<register name="EBX"/>
</unaffected>
<killedbycall>
<register name="ECX"/>
<register name="EDX"/>
<register name="ST0"/>
<register name="ST1"/>
</killedbycall>
<likelytrash>
<register name="EAX"/>
</likelytrash>
</prototype>
<prototype name="syscall" extrapop="4" stackshift="4">
<input>
<pentry minsize="1" maxsize="4">
<register name="EBX"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="ECX"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EDX"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="ESI"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EDI"/>
</pentry>
<pentry minsize="1" maxsize="4">
<register name="EBP"/>
</pentry>
</input>
<output killedbycall="true">
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
</output>
<unaffected>
<register name="EBX"/>
<register name="ECX"/>
<register name="EDX"/>
<register name="EBP"/>
<register name="EDI"/>
<register name="ESI"/>
<register name="ESP"/>
<register name="DF"/>
</unaffected>
<killedbycall>
<register name="EAX"/>
</killedbycall>
</prototype>
<prototype name="processEntry" extrapop="0" stackshift="0">
<input pointermax="4">
<pentry minsize="1" maxsize="4">
<register name="EDX"/>
</pentry>
<pentry minsize="1" maxsize="500" align="4">
<addr offset="0" space="stack"/>
</pentry>
</input>
<output killedbycall="true">
<pentry minsize="1" maxsize="4">
<register name="EAX"/>
</pentry>
</output>
<unaffected>
<register name="ESP"/>
</unaffected>
<!-- Functions with this prototype don't have a return address. But, if we don't specify one, this prototype will
use the default, which is to have the return address on the stack. That conflicts with how this prototype actually
uses the stack, so we set a fake return address at a EBP, which is unspecified at process entry -->
<returnaddress>
<register name="EBP"/>
</returnaddress>
</prototype>
<resolveprototype name="__cdecl/__regparm">
<model name="__cdecl"/> <!-- The default case -->
<model name="__regparm3"/>
<model name="__regparm2"/>
<model name="__regparm1"/>
</resolveprototype>
<eval_current_prototype name="__cdecl/__regparm"/>
<callfixup name="get_pc_thunk_ax">
<target name="__i686.get_pc_thunk.ax"/>
<target name="__x86.get_pc_thunk.ax"/>
<pcode>
<body><![CDATA[
EAX = * ESP;
ESP = ESP + 4;
]]></body>
</pcode>
</callfixup>
<callfixup name="get_pc_thunk.bp">
<target name="__i686.get_pc_thunk.bp"/>
<target name="__x86.get_pc_thunk.bp"/>
<pcode>
<body><![CDATA[
EBP = * ESP;
ESP = ESP + 4;
]]></body>
</pcode>
</callfixup>
<callfixup name="get_pc_thunk_bx">
<target name="__i686.get_pc_thunk.bx"/>
<target name="__x86.get_pc_thunk.bx"/>
<pcode>
<body><![CDATA[
EBX = * ESP;
ESP = ESP + 4;
]]></body>
</pcode>
</callfixup>
<callfixup name="get_pc_thunk_cx">
<target name="__i686.get_pc_thunk.cx"/>
<target name="__x86.get_pc_thunk.cx"/>
<pcode>
<body><![CDATA[
ECX = * ESP;
ESP = ESP + 4;
]]></body>
</pcode>
</callfixup>
<callfixup name="get_pc_thunk_dx">
<target name="__i686.get_pc_thunk.dx"/>
<target name="__x86.get_pc_thunk.dx"/>
<pcode>
<body><![CDATA[
EDX = * ESP;
ESP = ESP + 4;
]]></body>
</pcode>
</callfixup>
<callfixup name="get_pc_thunk_di">
<target name="__i686.get_pc_thunk.di"/>
<target name="__x86.get_pc_thunk.di"/>
<pcode>
<body><![CDATA[
EDI = * ESP;
ESP = ESP + 4;
]]></body>
</pcode>
</callfixup>
<callfixup name="get_pc_thunk_si">
<target name="__i686.get_pc_thunk.si"/>
<target name="__x86.get_pc_thunk.si"/>
<pcode>
<body><![CDATA[
ESI = * ESP;
ESP = ESP + 4;
]]></body>
</pcode>
</callfixup>
<callfixup name="x86_return_thunk">
<target name="__x86_return_thunk"/>
<pcode>
<body><![CDATA[
EIP = *:4 ESP;
ESP = ESP + 4;
return [EIP];
]]></body>
</pcode>
</callfixup>
<callfixup name="fentry">
<target name="__fentry__"/>
<pcode>
<body><![CDATA[
temp:1 = 0;
]]></body>
</pcode>
</callfixup>
<callfixup name="mcount">
<target name="mcount"/>
<pcode>
<body><![CDATA[
temp:1 = 0;
]]></body>
</pcode>
</callfixup>
<callfixup name="x86_indirect_thunk_ebp">
<target name="__x86_indirect_thunk_ebp"/>
<pcode>
<body><![CDATA[
call [EBP];
]]></body>
</pcode>
</callfixup>
<callfixup name="x86_indirect_thunk_eax">
<target name="__x86_indirect_thunk_eax"/>
<pcode>
<body><![CDATA[
call [EAX];
]]></body>
</pcode>
</callfixup>
<callfixup name="x86_indirect_thunk_ebx">
<target name="__x86_indirect_thunk_ebx"/>
<pcode>
<body><![CDATA[
call [EBX];
]]></body>
</pcode>
</callfixup>
<callfixup name="x86_indirect_thunk_ecx">
<target name="__x86_indirect_thunk_ecx"/>
<pcode>
<body><![CDATA[
call [ECX];
]]></body>
</pcode>
</callfixup>
<callfixup name="x86_indirect_thunk_edx">
<target name="__x86_indirect_thunk_edx"/>
<pcode>
<body><![CDATA[
call [EDX];
]]></body>
</pcode>
</callfixup>
</compiler_spec>