1

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?

4
  • IDA results look pretty good. I believe that the signature of main comes from any kind of FLIRT or other function recognitions - It detects that the function is main, and therefore gives it the default main signature. it looks like foo disassembled well. You can configure yourself the signature by pressing y on the function call. You must understand that the whole process is very heuristic, and this info does not appear anywhere in the binary. Dec 26, 2019 at 9:02
  • Thank you. I understand that the decompilation currently bases on heuristics so it gives sometimes unattended results, but I supposed that it should be consistent. Dec 26, 2019 at 10:24
  • 1
    I saw it several times in IDA, that it creates a good signature for a function but sometimes calls it with extra/missing parameters. I believe it's a bug, rather than something configurable. Dec 26, 2019 at 10:27
  • Thank again. Could you please pick your comments as response so I can accept it. Dec 26, 2019 at 10:40

1 Answer 1

1

IDA results look pretty good. I believe that the signature of main comes from any kind of FLIRT or other function recognitions - It detects that the function is main, and therefore gives it the default main signature. it looks like foo disassembled well. You can configure yourself the signature by pressing y on the function call. You must understand that the whole process is very heuristic, and this info does not appear anywhere in the binary.

I saw it several times in IDA, that it creates a good signature for a function but sometimes calls it with extra/missing parameters. I believe it's a bug, rather than something configurable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.