.386
.model flat, C
.code
; Call the existing exported Hello (naked) implemented in C++.
; The symbol is _Hello for C linkage.
extrn Hello_impl:PROC
PUBLIC _Hello2
_Hello2 PROC
; Caller passes x->EAX, y->EBX, psz->ECX
; Push arguments for _Hello_impl (right-to-left): psz, y, x
push ecx
push edx
push eax
call Hello_impl
; move return (EAX) into ECX as required by new convention
mov ecx, eax
; clean up stack (3 dwords)
add esp, 12
ret
_Hello2 ENDP
END
If you compile the above code into a DLL, you will see mostly correct C pseudocode:
int __usercall _Hello2@<eax>(char *pszCaption@<ecx>, int y@<edx>, int a3@<eax>)
{
return Hello_impl(a3, y, pszCaption);
}
Despite a minor error (its original intention was to use ecx as the return value), the logic is largely preserved. Here is its microcode:
; Maturity: MMAT_GENERATED
int __usercall _Hello2@<eax>(char *pszCaption@<ecx>, int y@<edx>, int@<eax>)
0.BLT_NONE
; STKD=C MINREF=0/END=C ARGS: OFF=10/MINREF=10/END=110/SHADOW=0
; ????-BLOCK 0 PROP FAKE [START=100010C4 END=100010C4] MINREFS: STK=0/ARG=10, MAXBSP: 0
; ---------------------------------------------------------------------------
1.BLT_NONE
; ????-BLOCK 1 PROP PUSH [START=100010C4 END=100010CC] MINREFS: STK=0/ARG=10, MAXBSP: C
; USE: rax.8,ecx.4,esp.4,cs.2,(ebx.4,ebp.4,rdi.8,st0.8,st1.8,st2.8,st3.8,st4.8,st5.8,st6.8,st7.8,mm0.8,mm1.8,mm2.8,mm3.8,mm4.8,mm5.8,mm6.8,mm7.8,xmm0.16,xmm1.16,xmm2.16,xmm3.16,xmm4.16,xmm5.16,xmm6.16,xmm7.16,xmm8.16,xmm9.16,xmm10.16,xmm11.16,xmm12.16,xmm13.16,xmm14.16,xmm15.16,GLBLOW,LVARS,ARGS,GLBHIGH)
; DEF: esp.4,LVARS,(cf.1,zf.1,sf.1,of.1,pf.1,rax.8,ecx.4,fps.2,st7.8,fl.1,c0.1,c2.1,c3.1,df.1,if.1,xmm0.16,GLBLOW,RET,ARGS,GLBHIGH)
; DNU: esp.4
1. 1 nop ; 100010C4 u=
1. 2 push ecx.4 ; 100010C4 u=ecx.4,esp.4 d=esp.4,sp+8..
1. 3 nop ; 100010C5 u=
1. 4 push edx.4 ; 100010C5 u=edx.4,esp.4 d=esp.4,sp+4.4
1. 5 nop ; 100010C6 u=
1. 6 push eax.4 ; 100010C6 u=eax.4,esp.4 d=esp.4,sp+0.4
1. 7 mov #0x100010CC.4, ett.4 ; 100010C7 u= d=ett.4
1. 8 nop ; 100010C7 u=
1. 9 nop ; 100010C7 u=
1.10 mov cs.2, seg.2 ; 100010C7 u=cs.2 d=seg.2
1.11 mov #0x10001010.4, eoff.4 ; 100010C7 u= d=eoff.4
1.12 call $_Hello_impl ; 100010C7 u=(rax.8,rcx.8,ebp.4,rdi.8,st0.8,st1.8,st2.8,st3.8,st4.8,st5.8,st6.8,st7.8,mm0.8,mm1.8,mm2.8,mm3.8,mm4.8,mm5.8,mm6.8,mm7.8,xmm0.16,xmm1.16,xmm2.16,xmm3.16,xmm4.16,xmm5.16,xmm6.16,xmm7.16,xmm8.16,xmm9.16,xmm10.16,xmm11.16,xmm12.16,xmm13.16,xmm14.16,xmm15.16,GLBLOW,LVARS,ARGS,GLBHIGH) d=(cf.1,zf.1,sf.1,of.1,pf.1,rax.8,ecx.4,fps.2,st7.8,fl.1,c0.1,c2.1,c3.1,df.1,if.1,xmm0.16,ALLMEM)
; ---------------------------------------------------------------------------
2.BLT_NONE
; ????-BLOCK 2 PROP PUSH [START=100010CC END=100010D2] MINREFS: STK=0/ARG=10, MAXBSP: C
; USE: eax.4,esp.4,cs.2,RET
; DEF: cf.1,zf.1,sf.1,of.1,pf.1,ecx.4,esp.4
; DNU: cf.1,zf.1,sf.1,of.1,pf.1,ecx.4,esp.4
2. 1 mov eax.4, ecx.4 ; 100010CC u=eax.4 d=ecx.4
2. 2 mov #0xC.4, et1.4 ; 100010CE u= d=et1.4
2. 3 cfadd et1.4, esp.4, cf.1 ; 100010CE u=esp.4,et1.4 d=cf.1
2. 4 ofadd et1.4, esp.4, of.1 ; 100010CE u=esp.4,et1.4 d=of.1
2. 5 add et1.4, esp.4, ett.4 ; 100010CE u=esp.4,et1.4 d=ett.4
2. 6 setz ett.4, #0.4, zf.1 ; 100010CE u=ett.4 d=zf.1
2. 7 setp ett.4, #0.4, pf.1 ; 100010CE u=ett.4 d=pf.1
2. 8 sets ett.4, sf.1 ; 100010CE u=ett.4 d=sf.1
2. 9 mov ett.4, esp.4 ; 100010CE u=ett.4 d=esp.4
2.10 nop ; 100010D1 u=
2.11 pop eoff.4 ; 100010D1 u=esp.4,RET d=esp.4,eoff.4
2.12 mov cs.2, seg.2 ; 100010D1 u=cs.2 d=seg.2
2.13 ijmp seg.2, eoff.4 ; 100010D1 u=eoff.4,seg.2
; ---------------------------------------------------------------------------
3.BLT_STOP
As you can see, push ecx.4 and push edx.4 are preserved perfectly. However, if we replace push edx with push ebx in the original assembly, the compiled DLL can no longer be decompiled correctly by IDA. Here is its microcode:
; Maturity: MMAT_GENERATED
int __usercall _Hello2@<eax>(int@<eax>)
0.BLT_NONE
; STKD=4 MINREF=0/END=C ARGS: OFF=10/MINREF=10/END=110/SHADOW=0
; SAVEDREGS: ebx.4
; ????-BLOCK 0 PROP FAKE [START=100010C4 END=100010C4] MINREFS: STK=0/ARG=10, MAXBSP: 0
; ---------------------------------------------------------------------------
1.BLT_NONE
; ????-BLOCK 1 PROP PUSH [START=100010C4 END=100010CC] MINREFS: STK=0/ARG=10, MAXBSP: 4
; USE: eax.4,esp.4,cs.2,(edx.4,rcx.8,ebp.4,rdi.8,st0.8,st1.8,st2.8,st3.8,st4.8,st5.8,st6.8,st7.8,mm0.8,mm1.8,mm2.8,mm3.8,mm4.8,mm5.8,mm6.8,mm7.8,xmm0.16,xmm1.16,xmm2.16,xmm3.16,xmm4.16,xmm5.16,xmm6.16,xmm7.16,xmm8.16,xmm9.16,xmm10.16,xmm11.16,xmm12.16,xmm13.16,xmm14.16,xmm15.16,GLBLOW,LVARS,ARGS,GLBHIGH)
; DEF: esp.4,sp+0.4,(cf.1,zf.1,sf.1,of.1,pf.1,rax.8,ecx.4,fps.2,st7.8,fl.1,c0.1,c2.1,c3.1,df.1,if.1,xmm0.16,GLBLOW,sp+4..,RET,ARGS,GLBHIGH)
; DNU: esp.4
1. 1 nop ; 100010C6 u=
1. 2 push eax.4 ; 100010C6 u=eax.4,esp.4 d=esp.4,sp+0.4
1. 3 mov #0x100010CC.4, ett.4 ; 100010C7 u= d=ett.4
1. 4 nop ; 100010C7 u=
1. 5 nop ; 100010C7 u=
1. 6 mov cs.2, seg.2 ; 100010C7 u=cs.2 d=seg.2
1. 7 mov #0x10001010.4, eoff.4 ; 100010C7 u= d=eoff.4
1. 8 call $_Hello_impl ; 100010C7 u=(rax.8,rcx.8,ebp.4,rdi.8,st0.8,st1.8,st2.8,st3.8,st4.8,st5.8,st6.8,st7.8,mm0.8,mm1.8,mm2.8,mm3.8,mm4.8,mm5.8,mm6.8,mm7.8,xmm0.16,xmm1.16,xmm2.16,xmm3.16,xmm4.16,xmm5.16,xmm6.16,xmm7.16,xmm8.16,xmm9.16,xmm10.16,xmm11.16,xmm12.16,xmm13.16,xmm14.16,xmm15.16,GLBLOW,LVARS,ARGS,GLBHIGH) d=(cf.1,zf.1,sf.1,of.1,pf.1,rax.8,ecx.4,fps.2,st7.8,fl.1,c0.1,c2.1,c3.1,df.1,if.1,xmm0.16,ALLMEM)
; ---------------------------------------------------------------------------
2.BLT_NONE
; ????-BLOCK 2 PROP PUSH [START=100010CC END=100010D2] MINREFS: STK=0/ARG=10, MAXBSP: C
; USE: eax.4,esp.4,cs.2,RET
; DEF: cf.1,zf.1,sf.1,of.1,pf.1,ecx.4,esp.4
; DNU: cf.1,zf.1,sf.1,of.1,pf.1,ecx.4,esp.4
2. 1 mov eax.4, ecx.4 ; 100010CC u=eax.4 d=ecx.4
2. 2 mov #0xC.4, et1.4 ; 100010CE u= d=et1.4
2. 3 cfadd et1.4, esp.4, cf.1 ; 100010CE u=esp.4,et1.4 d=cf.1
2. 4 ofadd et1.4, esp.4, of.1 ; 100010CE u=esp.4,et1.4 d=of.1
2. 5 add et1.4, esp.4, ett.4 ; 100010CE u=esp.4,et1.4 d=ett.4
2. 6 setz ett.4, #0.4, zf.1 ; 100010CE u=ett.4 d=zf.1
2. 7 setp ett.4, #0.4, pf.1 ; 100010CE u=ett.4 d=pf.1
2. 8 sets ett.4, sf.1 ; 100010CE u=ett.4 d=sf.1
2. 9 mov ett.4, esp.4 ; 100010CE u=ett.4 d=esp.4
2.10 nop ; 100010D1 u=
2.11 pop eoff.4 ; 100010D1 u=esp.4,RET d=esp.4,eoff.4
2.12 mov cs.2, seg.2 ; 100010D1 u=cs.2 d=seg.2
2.13 ijmp seg.2, eoff.4 ; 100010D1 u=eoff.4,seg.2
; ---------------------------------------------------------------------------
3.BLT_STOP
As you can observe, the expected push ecx.4 and push ebx.4 instruction sequence is missing entirely. They seem to have been erroneously optimized away, leaving only SAVEDREGS: ebx.4 in the comments.
This incorrect microcode optimization directly leads to faulty C pseudocode generation:
int __usercall _Hello2@<eax>(int a1@<eax>)
{
int v3; // [esp+0h] [ebp-8h]
const char *v4; // [esp+4h] [ebp-4h]
return Hello_impl(a1, v3, v4);
}
Even if we forcefully correct the function prototype to: int __usercall __spoils<eax,ecx> _Hello2@<eax>(int a1@<eax>, int a2@<ebx>, int a3@<ecx>);
The C pseudocode merely turns into:
int __usercall __spoils<eax,ecx> _Hello2@<eax>(int a1@<eax>, int a2@<ebx>, int a3@<ecx>)
{
int v4; // [esp+0h] [ebp-8h]
const char *v5; // [esp+4h] [ebp-4h]
return Hello_impl(a1, v4, v5);
}
Variables v4 and v5 still trigger warnings:
100010C7: variable 'v4' is possibly undefined
100010C7: variable 'v5' is possibly undefined
There is no way to automatically resolve these undefined variables. However, in IDA 6.1, the generated pseudocode was perfectly correct, as shown below:
int __usercall _Hello2<eax>(const char *pszCaption<ecx>, int a2<eax>, int a3<ebx>)
{
return Hello_impl(a2, a3, pszCaption);
}
Therefore, I strongly suspect this is a regression (a bug) introduced in newer versions of the IDA decompiler. This bug creates a severe issue where it is impossible to generate correct C pseudocode for inline assembly or standalone assembly files. I earnestly request the Hex-Rays team to look into this.
A Second Critical Issue: Missing __spoils with LTCG
I recently discovered another serious issue. For x86 files compiled with Link-Time Code Generation (/LTCG or /GL), there is a vast amount of direct cross-function register reuse. However, IDA does not seem to generate the __spoils (spoiled register list) for these functions. I am unsure if this is a bug in the data flow analysis phase or if the attribute simply isn’t being set, but the impact is massive.
This results in a flood of variable 'xx' is possibly undefined errors (the VALUE MAY BE UNDEFINED; hover warnings). This severely degrades the quality of the generated C pseudocode, making it extremely difficult to recompile. Unless every single one of these VALUE MAY BE UNDEFINED; warnings is resolved, treating the C pseudocode as compilable source code is impossible.
Conversely, if this issue were fixed, we could generate accurate PDBs, debug via Visual Studio, and perform global cross-function code analysis much more easily. From there, it would just be a matter of using AI to semantically rename functions, variables, and structs.
Of course, this entirely depends on IDA’s data flow and control flow analysis being flawless—accurately determining which registers and stack segments a function uses and spoils, avoiding the generation of incorrect if/else/while/for/switch blocks, and properly supporting SEH (__try/__except) and C++ try/catch restoration (issues I have reported in the past).
Final Thoughts (Feedback)
To be completely honest, I sometimes struggle to understand why Hex-Rays doesn’t dedicate its efforts to perfecting the disassembly and decompilation of a core architecture (like x86/x64) before rushing to expand support to numerous new architectures (like MIPS, V850) and languages (Rust, Go). Is this purely to catch market hype and open new revenue streams?
While I can understand this from a business perspective, competitors like Binary Ninja, Ghidra, and JEB are aggressively catching up. With the explosive rise of AI-assisted development, I genuinely worry that IDA’s 35-year “moat” of decompilation supremacy might be breached.
I truly want IDA to keep striving for perfection so it can help more reverse engineers. My apologies for the slight rant—it’s just that whenever I use IDA deeply, I run into various issues. It leaves me a bit baffled since IDA is widely regarded as the absolute “Swiss Army Knife” of native C/C++ decompilation.
Thank you for your time and for reading my report.
