Some compilers, says old gcc or egcs, apply ABI-breaking optimization for static functions within single file, like passing arguments or returning results with arbitrary registers.
Consider some source code like:
// Original foobar.c
// This example targets MIPS o32 ABI.
// Shared subroutine
// Compiler decided to use $16, $17 to pass a0 and a1 to minimize stack usage and move between registers.
static void __bar(int a0, int a1) {
// Something very complicated
}
// ...
void foo(int a0, int a1) {
// ...
/*
This call was compiled to something like:
ori $16, $0, 0x1
jal __bar
ori $17, $0, 0x1
*/
__bar(1, 1);
// ...
}
// ...
Suppose someone want to restore / reimplement foobar.c from the compiled assembly without access to the original source.
One would probably like to decompile / rewrite some part first, says start from foo() or other standard functions. However, in order to test the correctness of the implementation, one must deal with calls to non-standard ABI routines.
A trivial way is to workaround with global register variables provided by gcc / clang:
// Restoration of foobar.c
// void __bar(int asm("s0"), int asm("s1"))
// External function in assembly, says foobar.s, which is from compiled original foobar.c.
void __bar();
volatile register int s0 asm ("s0"); // $16 = s0
volatile register int s1 asm ("s1"); // $17 = s1
// ...
void foo(int a0, int a1) {
// ...
// __bar(1, 1);
s0 = 1; s1 = 1;
__bar();
// ...
}
// ...
The question is:
- Does gcc / clang supports customize calling convention for some specific functions?
- Are there any way to deal with non-standard ABI calls more elegantly?