Allow function to have multiple declarations

Why such thing needed.

  1. Compiler often choose use only return function ( return 0; return 1 etc. ) as one implementation for multiple only return functions with different declaration.
    As example calls to int A(int B, Int C) {return 0;} and int N(){return 0;} will be made to one function xor rax,rax; ret;
  2. Functions that works with template instances parameters. One function can work with template instance of different types.
    If decompiler and user can choose from list that function declaration used in particular function call (similar to choose union member) it can help to correct decompiler output.
1 Like

Do call types address your problem?

1 Like

In mentioned cases one function can have multiple call types, but Ida don’t support it, so it creates hurdles in decompilation.

1 Like

Can you confirm my understanding of the issue: call type specialization is only helpful if the caller itself is not polymorphic (either in terms of specialized template code being merged, or just other code being merged).

For example, one might have the following:

template <typename T>
void Add(T a, T b)
{
  return a + b;
}

template <typename T>
void Increment(T x) {
  return Add(x, 1);
}

int main() {
  return Increment<int>(0) - Increment<const int>(0);
}

In the above example, the code of Add<int> and Add<const int> will be identical, and so in the binaries these functions would be represented by a single function at the same address. This would be acceptable with call types, except that we also have Increment, also polymorphic, which calls Add. And so even if you apply a call type to a call to Increment, it doesn’t help you with its decompilation, because there are logically two Increments, and each of the Increments has “its own” Add.

Is this understanding of your issue correct?

1 Like

Example close, but not exact. Let have vector of struct A ptr, and vector of struct B ptr, function Add(vector T, T) in binary will be one function because template instances both vectors of ptr. Without multiple calltypes per function one chosen calltype will screw up decompiler output for another case.
PS What tag you use to make code block in text?

1 Like

Example close, but not exact. Let have vector of struct A ptr, and vector of struct B ptr, function Add(vector T, T) in binary will be one function because template instances both vectors of ptr.

So you have something like the following?

int main()
{
  int x;
  std::vector<int *> a;
  std::vector<const int *> b;
  a.push_back(&x);
  b.push_back(&x);
}

Without multiple calltypes per function one chosen calltype will screw up decompiler output for another case.

And in the above example, if you don’t manually specify that a.push_back(nullptr) has a int *&(std::vector<int *>::*)(int *) call type, and b.push_back(nullptr) has a call type of const int *&(std::vector<const int *>::*)(const int *), that the decompilation doesn’t look right?

Going back to your original ask of multiple prototypes per function. What does the ideal user interaction look like for you?

  • Set multiple prototypes, and have a choice to “decompile with prototype”? If so, how should call types set within a version of the function with a chosen prototype behave? What about variable names / other types – should they port across versions?
  • Set multiple call types, as done now, but perhaps have a smart option that is aware of the set of previously assigned call types for the called function, so that you can choose among them?
  • Something else?

PS What tag you use to make code block in text?

There is a little icon, </>, in the text box when adding messages. It ends up using Markdown syntax: single backticks for inline code, and then if you select multiple lines of text, then it will wrap the selected text with triple backticks.

1 Like

Close example. Real code that uses stl templates can create much worse cases of compiler reusing functions for template instances.

How i imagine UX. Window for edit calltype have buttons to add extra calltype and delete it. Don’t now how best to show calltypes list for function.
At decompilation stage decompiler make guess by type of function parameters and it 's count, that calltype are appropriate. If guess was wrong, user can chose correct calltype from list similar as it done then choosing union member for variable that have it.

1 Like

Elegant way to implement this could be to allow for functions (and m_call/m_icall mops, cot_calls ) to have a union as a type. The UI to select particular union member is already there.

2 Likes

no
because in that case each call needs to be set individually
still the best solution
is to have 3 contexts
as I suggested in my topic

1 Like

Then the union picker UI has to be enhanced with “apply in current function” checkbox.

1 Like

If decompiler know parameters types that applied to particular function call in upper decompiled function, in that case, it can guess appropriate calltype from function calltypes list. If decompiler don’t know function parameters types in upper call, guess can’t be made, take first in list and user intervention are needed to clarify. Yes in each call to function with multiple calltypes we need to set that calltype is used, but this give user more control compared to “contexts”, and most time it can be done automatically.

1 Like

Here is a minimal code to test the functionality.
It contains all the common cases: function call, obfuscated call, function pointer, function pointer inside a structure.

; nasm -f elf64 test.asm -o test.o
section .text
global test

test:
  mov r12, rdi
  mov r13, rsi

  lea rbp, [func-0x20]

  mov rdi, 1
  call func

  add rbp, 0x20
  
  mov rsi, 3
  mov rdi, 2
  call func

  mov rdx, 6
  mov rsi, 5
  mov rdi, 4
  call rbp
 
  mov rdi, 7
  call r12
  
  mov rdx, 10
  mov rsi, 9
  mov rdi, 8
  call [r13+8]

  mov rsi, 12
  mov rdi, 11
  call r12
  
  mov rcx, 16
  mov rdx, 15  
  mov rsi, 14
  mov rdi, 13
  call [r13+8]

  ret

func:

ret

Best decompiled code I was able to get was:

__int64 __fastcall test(void (__fastcall *a1)(__int64, __int64), struct_a2 *struct_a2)
{
  func(n2: 1LL);
  ((void __fastcall(__int64, __int64))func)(n2: 2LL, 3LL);
  ((void __fastcall(__int64, __int64, __int64))func)(n2: 4LL, 5LL, 6LL);
  ((void __fastcall(__int64))a1)(7LL);
  struct_a2->pfunc8(8LL, 9LL, 10LL);
  a1(11LL, 12LL);
  return ((__int64 __fastcall(__int64, __int64, __int64, __int64))struct_a2->pfunc8)(13LL, 14LL, 15LL, 16LL);
}
1 Like

correction, with unions I can get

union funcs
{
  void (__fastcall *three_args)(__int64, __int64, __int64);
  void (__fastcall *two_args)(__int64, __int64);
  void (__fastcall *one_arg)(__int64);
  void (__fastcall *four_args)(__int64, __int64, __int64, __int64);
};


struct struct_a2
{
  _BYTE gap0[8];
  funcs pfunc8;
};



__int64 __fastcall test(funcs funcs_, struct_a2 *struct_a2)
{
  __int64 result; // rax

  func(n2: 1LL);
  ((void (__fastcall *)(__int64, __int64))func)(n2: 2LL, 3LL);
  ((void (__fastcall *)(__int64, __int64, __int64))func)(n2: 4LL, 5LL, 6LL);
  funcs_.one_arg(7LL);
  struct_a2->pfunc8.three_args(8LL, 9LL, 10LL);
  funcs_.two_args(11LL, 12LL);
  struct_a2->pfunc8.four_args(13LL, 14LL, 15LL, 16LL);
  return result;
}
2 Likes

Hello all and thank you for these examples and discussion. I have some more questions about the UI side of this proposed feature.

If you are in the decompiler view, looking at a function A that calls a polymorphic B, and you have set a specific call site type for the call to B, then upon clicking B, would you like it to show you B decompiled according to specifically clicked call-site site in A?

Some more questions about the interaction if you answered “yes” to this:

  • Should there be some kind of visual aid to said the contextual version / decompilation you’re looking at?
  • When looking at a general or context-specific decompilation, what mechanisms should be available for switching the type context?
  • If you rename a variable/parameter, or add a comment, or set a type to a local/parameter while looking at a specific type context for a decompilation, then what should be the interface, if any, to ask if you want that variable applied to all contexts or the general context?
1 Like

If you are in the decompiler view, looking at a function A that calls a polymorphic B, and you have set a specific call site type for the call to B, then upon clicking B, would you like it to show you B decompiled according to specifically clicked call-site site in A?

I think it is not necessary. If case of B is stub function there are nothing to decompile differently. In case of function B is for templates you can choose any of calltypes because this function work the same for all of it. Main purpose for multi calltypes is to have correct decompilation of function A.

1 Like