I'm testing several decompilers against the following C
code
static int bar(int i) {
return ++i;
}
static int apply(int (*fun)(int), int i) {
return i % fun(i);
}
static int foo(int (*app)(int (*fun)(int), int), int i) {
return i / app(bar, i);
}
int main() {
return foo(apply, 7);
}
which is compiled by just clang test.c
.
; main
0x0 push rbp
0x1 mov rbp, rsp
0x4 sub rsp, 0x10
0x8 mov dword ptr [rbp-0x4], 0x0
0xf mov rdi, @apply
0x19 mov esi, 0x7
0x1e call foo
0x23 add rsp, 0x10
0x27 pop rbp
0x28 ret
; foo
0x30 push rbp
0x31 mov rbp, rsp
0x34 sub rsp, 0x20
0x38 mov [rbp-0x8], rdi
0x3c mov [rbp-0xc], esi
0x3f mov eax, [rbp-0xc]
0x42 mov rcx, [rbp-0x8]
0x46 mov esi, [rbp-0xc]
0x49 mov rdi, @bar
0x53 mov [rbp-0x10], eax
0x56 call rcx
0x58 mov edx, [rbp-0x10]
0x5b mov [rbp-0x14], eax
0x5e mov eax, edx
0x60 cdq
0x61 mov esi, [rbp-0x14]
0x64 idiv esi
0x66 add rsp, 0x20
0x6a pop rbp
0x6b ret
I was aware of some limits in argument/parameter detection (from the response to another question). But each decompiler seems, in one way or another, have inconsistency in the type system of its decompiled language (I think they all try to decompile to C
or pseudo-C
).
IDA v.7.4.191122
(evaluation version) gives:
int __cdecl main(int argc, const char **argv, const char **envp)
{
return foo(apply, 7LL, envp);
}
__int64 __fastcall foo(int (__fastcall *a1)(__int64 (__fastcall *)(), _QWORD), unsigned int a2)
{
return (unsigned int)((int)a2 / a1(bar, a2));
}
I don't show results of bar
and apply
because there was already an inconsistency here: IDA detects that foo
is called with 3 arguments in main
, but then it concludes that foo
has actually 2 parameters.
Next, Ghidra v9.1.build.2019-oct-23
:
void main(void)
{
foo(apply,7);
return;
}
ulong foo(code *param_1,uint param_2,undefined8 param_3)
{
int iVar1;
iVar1 = (*param_1)(bar,(ulong)param_2,param_3,param_1);
return (long)(int)param_2 / (long)iVar1 & 0xffffffff;
}
which has an opposite opinion: foo
is called in main
with 2 arguments, but in its definition foo
has 3 parameters.
JEB v.3.8.0.201912242244
(evaluation version):
unsigned long main() {
return foo(&apply, 7L);
}
unsigned long foo(unsigned long param0) {
unsigned int v0 = v1;
param0();
return (unsigned long)(v0 / ((unsigned int)v2));
}
which gives a perfect result for main
, but it then claims that foo
is a function of just 1 parameters (and while it shows param0()
, it keeps param0
as unsigned long
).
Actually, the decompilation results are not correct (which is somehow understandable), but they are even inconsistent. Do I miss some configuration?
IDA
results look pretty good. I believe that the signature ofmain
comes from any kind ofFLIRT
or other function recognitions - It detects that the function ismain
, and therefore gives it the defaultmain
signature. it looks likefoo
disassembled well. You can configure yourself the signature by pressingy
on the function call. You must understand that the whole process is very heuristic, and this info does not appear anywhere in the binary.