While trying to make my own alternative to the stdarg.h macros for variable arguments functions, a.k.a. functions with an unknown number of arguments, i tried to understand the way the arguments are stored in memory.
Here is a MWE :
#include <stdio.h>
void foo(int num, int bar1, int bar2)
{
printf("%p %p %p %p\n", &foo, &num, &bar1, &bar2);
}
int main ()
{
int i, j;
i = 3;
j = -5;
foo(2, i, j);
return 0;
}
I understand without any problem that the function's address is not in the same place as the arguments' addresses.
But the latter aren't always organized in the same way.
On a x86_32 architecture (mingw32), i get this kind of result :
004013B0 0028FEF0 0028FEF4 0028FEF8
which means that the adresses are in the same order as the arguments.
BUT when I run it on a x86_64 this time the output is :
0x400536 0x7fff53b5f03c 0x7fff53b5f038 0x7fff53b5f034
Where the addresses are obviously in reverse order w.r.t. the arguments.
Therefore my question is (tl;dr) :
Are the arguments' addresses architecture dependent, or also compiler dependent?
It is compiler dependent. Compiler vendors naturally have to obey by the rules of the CPU architecture. A compiler normally obey the platform ABI as well, at least for code that could potentially interoperate with code produced by another compiler. The platform ABI is a specification of calling convention, linking semantic and much more, for a given platform.
E.g. compilers on linux and other unix like operating system adhere to the System V Application Binary Interface, and you'll find in chapter 3.2.3 how parameters are passed to functions (arguments passed in registers are passed left to right and arguments passed in memory(on the stack) are passed from right to left). On Windows, the rules are documented here.
They're ABI dependent. In cases where it doesn't matter (functions that will only be called in a known way), it's entirely compiler dependent and that usually means using registers, which don't have an address (those arguments will have an address if you ask for that address, giving the appearance that everything has an address). Functions that get inlined don't even really have arguments anymore, so the question of what their addresses are is moot - though again they will appear to exist and have an address when you force that happen.
Arguments may not be stored in memory at all, but passed via registers; however the language requires an address to be returned for any symbol operand of &, so your observation may be a result of you actually attempting the observation and the compiler has simply copied the values to those addresses in order that they are addressable.
It might be interesting to see what happens if you request the addresses in a different order that they were passed for example:
printf("%p %p %p %p\n", &num, &bar1, &bar2, &foo) ;
You may or may not get the same result; the point is that teh addresses you observed may be an artefact of the observation rather than of the passing. Certainly in the ARM ABI, the first four arguments to a function are passed in registers R0, R1, R2, & R3, and thereafter are passed vis the stack.
On x86_64 you get the arguments in a "weird" order because they are not actually passed to the function in any memory at all. They are passed in cpu registers. By taking their address you actually force the compiler to generate code that will store the arguments in memory (on the stack in your case) so that you can take the address of them.
You can't implement stdarg macros without interacting with the compiler. In gcc the stdarg macros just wrap a builtin construct because there is no way for you to know where the arguments might be by the time you need them (the compiler might have reused the registers for something). The builtin stdarg support in gcc can significantly change code generation for functions that use them so that the arguments are available at all. I presume the same goes for other compilers.
Related
My understanding is that for the cdecl calling convention, the caller is responsible for cleaning the stack and therefore can pass any number of arguments.
On the other hand, stdcall callees clean the stack and therefore cannot receive varying amounts of arguments.
My question is twofold:
Couldn't stdcall functions also get a parameter about how many variables there are and do the same?
How do cdecl functions know how many arguments they've received?
Couldn't stdcall functions also get a parameter of how many variables are there and do the same?
Yes, sure. You could invent any calling convention. But then that wouldn't be stdcall anymore.
How do cdecl functions know how many arguments they've received?
They don't. They assume to find the required number of arguments in the locations specified by the calling convention. If they are missing, then that's a bug which the code cannot observe. The following code compiles:
printf("%s");
even though it is missing an argument. The result is undefined. For printf-style functions compilers generally issue warnings (if they can) due to knowledge of the functions' internals, but that's not a solution that can be generically applied.
If a caller provides the wrong number or types of arguments, then the behavior is undefined.
Couldn't stdcall functions also get a parameter of how many variables are there and do the same?
If the caller has to pass a separate arg with the number of bytes to be popped, that's more work than just doing add esp, 16 or whatever after the call (cdecl style caller-pops). It would totally defeat the purpose of stdcall, which is to save a few bytes of space at each call site, especially for naive code-gen that wouldn't defer popping args across a couple calls, or reuse the space allocated by a push with mov stores. (There are often multiple call-sites for each function, so the extra 2 bytes for ret imm16 vs. ret is amortized over that.)
Even worse, the callee can't use a variable number efficiently on x86 / x86-64. ret imm16 only works with an immediate (constant embedded in the machine code), so to pop a variable number of bytes above the return address, a function would have to copy the return address high up in the stack and do a plain ret from there. (Or defeat branch return-address branch prediction by popping the return address into a register.)
See also:
Stack cleanup in stdcall (callee-pops) for variable arguments (x86 asm)
What calling convention does printf() in C use? (why stdcall is unusable)
How do cdecl functions know how many arguments they've received?
They don't.
C is designed around the assumption that variadic functions don't know how many args they received, so functions need something like a format string or sentinel to know how many to iterate. For example, the POSIX execl(3) (wrapper for the execve(2) system call) takes a NULL-terminated list of char* args.
Thus calling conventions in general don't waste code-size and cycles on providing a count as a side-channel; whatever info the function needs will be part of the real C-level args.
Fun fact: printf("%d", 1, 2, 3) is well-defined behaviour in C, and is required to safely ignore args beyond the ones referenced by the format string.
So using stdcall and calculating based on the format-string can't work. You're right, if you wanted to make a callee-pops convention that worked for variadic functions, you would need to pass a size somewhere, e.g. in a register. But like I said earlier, the caller knows the right number, so it would be vastly easier to let the caller manage the stack, instead of making the callee dig up this extra arg later. That's why no real-world calling conventions work this way, AFAIK.
Passing the number of arguments in a callee cleans the stack convention would be possible but the additional overhead of the extra parameter outweighs its usefulness. It wastes stack space with the extra parameter and complicates the callees stack handling.
The reason stdcall was invented is because it makes the code smaller. One adjustment in the callee vs adjusting every place it is called (on x86 or on another architecture when there are more parameters than you can pass in registers). The x86 even has a retn # instruction where # is the number of bytes to adjust. Windows NT switched from cdecl to stdcall early in its development and it supposedly reduced the size and improved speed (I believe Larry Osterman blogged about this (mini answer here)).
cdecl functions do not know how many parameters there are. You are allowed (on the ABI level) to pass more arguments than the function will actually use. A printf style function will use the format parameter as a "guide" to access the parameters one by one. When this is done the callee also has to be informed of the type of each parameter (so it knows the size which in turn, in an implementation defined manner, allows it to walk the list of parameters. On Windows x86 the parameters are on the stack, all you need is the parameter size to calculate their offset as you walk the stack). The va_list and its macros in stdarg.h provides the helping glue for C functions to access these parameters.
My summary, based on #IInspectable's answer.
stdcall functions could also get a parameter of how many variables there are, but then that wouldn't be stdcall anymore.
cdecl don't know how many arguments to read. It is assumed that the function will be able to derive the number of arguments based on a pre-determined amount of arguments, like a format string for printf.
If a caller provides the less arguments than could be derived, or of an unexpected type, then the behavior is undefined. (Thanks for the correction #Peter Cordes)
I would like to know what could happen in a situation like this:
int foo()
{
return 1;
}
void bar()
{
void(*fPtr)();
fPtr = (void(*)())foo;
fPtr();
}
Address of function returning int is assigned to pointer of void(*)() type and the function pointed is called.
What does the standard say about it?
Regardless of answer to 1st question: Are we safe to call the function like this? In practise shouldnt the outcome be just that callee (foo) will put something in EAX / RAX and caller (bar) will just ignore the rax content and go on with the program? I'm interested in Windows calling convention x86 and x64.
Thanks a lot for your time
1)
From the C11 standard - 6.5.2.2 - 9
If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined
It is clearly stated that if a function is called using a pointer of type that does not match the type it is defined with, it leads to Undefined Behavior.
But the cast is okay.
2)
Regarding your second question - In case of a well defined Calling convention XXX and implementation YYYY -
You might have disassembled a sample program (even this one) and figured out that it "works". But there are slight complications. You see, the compilers these days are very smart. There are some compilers which are capable of performing precise inter procedural analysis. Some compiler might figure out that you have behavior that is not defined and it might make some assumption that might break the behavior.
A simple example -
Since the compiler sees that this function is being called with type void(*)(), it will assume that it is not supposed to return anything, and it might remove the instructions required to return the correct value.
In this case other functions calling this functions (in a right way) will get a bad value and thus it would have visible bad effects.
PS: As pointed out by #PeterCordes any modern, sane and useful compiler won't have such an optimization and probably it is always safe to use such calls. But the intent of the answer and the example (probably too simplistic) is to remind that one must tread very carefully when dealing with UBs.
What happens in practice depends a lot on how the compiler implements this. You're assuming C is just a thin ("obvious") layer over asm, but it isn't.
In this case, a compiler can see that you're calling a function through a pointer with the wrong type (which has undefined behavior1), so it could theoretically compile bar() to:
bar:
ret
A compiler can assume undefined behavior never happens during the execution of a program. Calling bar() always results in undefined behavior. Therefore the compiler can assume bar is never called and optimize the rest of the program based on that.
1 C99, 6.3.2.3/8:
If a converted
pointer is used to call a function whose type is not compatible with the pointed-to type,
the behavior is undefined.
About sub-question 2:
Nearly all x86 calling conventions I know (cdecl, stdcall, syscall, fastcall, pascal, 64-bit Windows and 64-bit Linux) will allow void functions to modify the ax/eax/rax register and the difference between an int function and a void function is only that the returned value is passed in the eax register.
The same is true for the "default" calling convention on most other CPUs I have already worked with (MIPS, Sparc, ARM, V850/RH850, PowerPC, TriCore). The register name is not eax but different, of course.
So when using these calling convention you can safely call the int function using a void pointer.
There are however calling conventions where this is not the case: I've read about a calling convention that implicitly use an additional argument for non-void functions...
At the asm level only, this is safe in all normal x86 calling conventions for integer types: eax/rax is call-clobbered, and the caller doesn't have to do anything differently to call a void function vs. an int function and ignoring the return value.
For non-integer return types, this is a problem even in asm. Struct returns are done via a hidden pointer arg that displaces the other args, and the caller is going to store through it so it better not hold garbage. (Assuming the case is more complex than the one shown here, so the function doesn't just inline when optimization is enabled.) See the Godbolt link below for an example of calling through a casted function pointer that results in a store through a garbage "pointer" in rdi.
For legacy 32-bit code, FP return values are in st(0) on the x87 stack, and it's the caller's responsibility to not leave the x87 stack unbalanced. float / double / __m128 return values are safe to ignore in 64-bit ABIs, or in 32-bit code using a calling convention that returns FP values in xmm0 (SSE/SSE2).
In C, this is UB (see other answers for quotes from the standard). When possible / convenient, prefer a workaround (see below).
It's possible that future aggressive optimizations based on a no-UB assumption could break code like this. For example, a compiler might assume any path that leads to UB is never taken, so an if() condition that leads to this code running must always be false.
Note that merely compiling bar() can't break foo() or other functions that don't call bar(). There's only UB if bar() ever runs, so emitting a broken externally-visible definition for foo() (like #Ajay suggests) is not a possible consequence. (Except maybe if you use whole-program optimization and the compiler proves that bar() is always called at least once.) The compiler can break functions that call bar(), though, at least the parts of them that lead to the UB.
However, it is allowed (by accident or on purpose) by many current compilers for x86. Some users expect this to work, and this kind of thing is present in some real codebases, so compiler devs may support this usage even if they implement aggressive optimizations that would otherwise assume this function (and thus all paths that lead to it in any callers) never run. Or maybe not!
An implementation is free to define the behaviour in cases where the ISO C standard leaves the behaviour undefined. However, I don't think gcc/clang or any other compiler explicitly guarantees that this is safe. Compiler devs might or might not consider it a compiler bug if this code stopped working.
I definitely can't recommend doing this, because it may well not continue to be safe. Hopefully if compiler devs decide to break it with aggressive no-UB-assuming optimizations, there will be options to control which kinds of UB are assumed not to happen. And/or there will be warnings. As discussed in comments, whether to take a risk of possible future breakage for short-term performance / convenience benefits depends on external factors (like will lives be at risk, and how carefully you plan to maintain in the future, e.g. checking compiler warnings with future compiler versions.)
Anyway, if it works, it's because of the generosity of your compiler, not because of any kind of standards guarantee. This compiler generosity may be intentional and semi-maintained, though.
See also discussion on another answer: the compilers people actually use aim to be useful, not just standards compliant. The C standard allows enough freedom to make a compliant but not very useful implementation. (Many would argue that compilers that assume no signed overflow even on machines where it has well-defined semantics have already gone past this point, though. See also What Every C Programmer Should Know About Undefined Behavior (an LLVM blog post).)
If the compiler can't prove that it would be UB (e.g. if it can't statically determine which function a function-pointer is pointing to), there's pretty much no way it can break (if the functions are ABI-compatible). Clang's runtime UB-sanitizer would still find it, but a compiler doesn't have much choice in code-gen for calling through an unknown function pointer. It just has to call the way the ABI / calling convention says it should. It can't tell the difference between casting a function pointer to the "wrong" type and casting it back to the correct type (unless you dereference the same function pointer with two different types, which means one or the other must be UB. But the compiler would have a hard time proving it, because the first call might not return. noreturn functions don't have to be marked noreturn.)
But remember that link-time optimization / inlining / constant-propagation could let the compiler see which function is pointed to even in a function that gets a function pointer as an arg or from a global variable.
Workarounds (for a function before you take its address):
If the function won't be part of Link-Time-Optimization, you could lie to the compiler and give it a prototype that matches how you want to call it (as long as you're sure you got the asm-level calling convention is compatible).
You could write a wrapper function. It's potentially less efficient (an extra jmp if it just tail-calls the original), but if it inlines then you're cloning the function to make a version that doesn't do any of the work of creating a return value. This might still be a loss if that was cheap compared to the extra I-cache / uop cache pressure of a 2nd definition, if the version that does return a value is used too.
You could also define an alternate name for a function, using linker stuff so both symbols have the same address. That way you can have two prototypes for the same block of compiler-generated machine code.
Using the GNU toolchain, you can use an attribute on a prototype to make it a weak alias (at the asm / linker level). This doesn't work for all targets; it works for ELF object files, but IDK about Windows.
// in GNU C:
int foo(void) { return 4; }
// include this line in a header if you want; weakref is per translation unit
// a definition (or prototype) for foo doesn't have to be visible.
static void foo_void(void) __attribute((weakref("foo"))); // in C++, use the mangled name
int bar_safe(void) {
void (*goo)(void) = (void(*)())foo_void;
goo();
return 1;
}
example on Godbolt for gcc7.2 and clang5.0.
gcc7.2 inlines foo through the weak alias call to foo_void! clang doesn't, though. I think that means that this is safe, and so is function-pointer casting, in gcc. Alternatively it means that this is potentially dangerous, too. >.<
clang's undefined-behaviour sanitizer does runtime function typeinfo checking (in C++ mode only) for calls through function pointers. int () is different from void (), so it will detect and report this UB on x86. (See the asm on Godbolt). It probably doesn't mean it's actually unsafe at the moment, though, because it doesn't yet detect / warn about it at compile time.
Use the above workarounds in the code that takes the address of the function, not in the code that receives a function pointer.
You want to let the compiler see a real function with the signature that it will eventually be called with, regardless of the function pointer type you pass it through. Make an alias / wrapper with a signature that matches what the function pointer will eventually be cast to. If that means you have to cast the function pointer to pass it in the first place, so be it.
(I think it's safe to create a pointer to the wrong type as long as it's not dereferenced. It's UB to even create an unaligned pointer, even if you don't dereference, but that's different.)
If you have code that needs to deref the same function pointer as int foo(args) in one place and void foo(args) in another place, you're screwed as far as avoiding UB.
C11 ยง6.3.2.3 paragraph 8:
A pointer to a function of one type may be converted to a pointer to a
function of another type and back again; the result shall compare
equal to the original pointer. If a converted pointer is used to call
a function whose type is not compatible with the referenced type, the
behavior is undefined.
I would like to know whether the following C code adheres to the C99 and/or C11 standard(s):
void foo(int bar0, int bar1, int bar2) {
int *bars = &bar0;
printf("0: %d\n1: %d\n2: %d\n", bars[0], bars[1], bars[2]);
}
int main(int argc, char **argv) {
foo(8, 32, 4);
return 0;
}
This code snippet compiles and runs as expected when using visual studio 2013 and prints:
0: 8
1: 32
2: 4
No, not anywhere near.
C standard does not guarantee that the function arguments are stored in consecutive memory locations (or, any specific ordering, for that matter). It is up to the compiler and/or the platform (architecture) to decide how the function arguments are passed to the function.
To add some more clarity, there is even no guarantee that the arguments which are to be passed are stored in memory (e.g., stack), at all. They can make use of the hardware registers, too (whenever applicable), for some or all the parameters, to make the operations fast. For example,
PowerPC
The PowerPC architecture has a large number of registers so most functions can pass all arguments in registers for single level calls. [...]
MIPS
The most commonly used calling convention for 32 bit MIPS is the O32 ABI which passes the first four arguments to a function in the registers $a0-$a3; subsequent arguments are passed on the stack. [...]
X86
The x86 architecture is used with many different calling conventions. Due to the small number of architectural registers, the x86 calling conventions mostly pass arguments on the stack, while the return value (or a pointer to it) is passed in a register.
and so on. Check the full wiki article here.
So, in your case, bars[0] is a valid access, but whether bars[1] and bars[2] are valid, depends on the underlying environment (platform/compiler), entirely. Best not to rely on the behavior you're expecting.
That said, just to nitpick, in case you don't intend to use the arguments (if any) passed to main() , you can simply reduce the signature to int main(void) {.
No it does not adhere to any published standard. How arguments and local variables are stored, and where, is up to the compiler. What might work in one compiler might not work in another, or even on a different version of the same compiler.
The C specification doesn't even mention a stack, all it specifies are the scoping rules.
No standard supports this. It's extremely naughty.
Array indexing and pointer arithmetic is only valid for arrays. (Note a small exception: you can read a pointer one past an array or a scalar, but you can't deference it.)
I need to call a function in C by just knowing it address, and no information
on it prototype (I can't cast it to a C function pointer).
The information I have on this function is it address.
I also know the parameters I want to pass to it (Thanks to a void pointer) and
the size of the arguments array (accessed trough the void pointer).
I also want to respect the C calling convention. For x86 version, I pretty much
know how to do it (allocate the space on the stack, copy the parameters to
that space and finally call the function).
The problem is with x64 convention (Linux one for now) where parameters are
passed through registers. I have no idea of the size of each parameter to fill
appropriately registers, I only know the size of the parameter array.
Also, I don't want to depend on gcc so I can't use __builtin_apply that seems
to be not standard and also be pretty dark.
I want to write my own piece of code to support multi compiler and also to
learn interesting stuff.
So basically, the function I want to write as the same prototype as
__builtin_apply which is:
void *call_ptr(void (*fun)(), void *params, size_t size);
I want also the code to write it in C (thanks to asm inline) or pure x64 asm.
So is there a way to do this properly and with respect of the calling
convention ? Or is this impossible with the x64 convention without knowing
exactly the prototype of the function called ?
Especially for x64 calling convention on Linux this will not work at all.
The reason is the very complicated calling convention.
Some examples:
void funcA(float64 x);
void funcB(int64 x);
In these two cases the value "x" is passed to the functions differently because floating point and integer are passed to the functions in different registers.
void funcC(float64 x,int64 y);
void funcD(int64 y,float64 x);
In these two cases the arguments "x" and "y" are in different order. However they are passed to the function in the same way (both functions use the same register for "x" and the same register for "y").
Conclusion: To create a function that does what you want you'd have to pass a string containing the argument types of each argument to the assembler function. The number/size of arguments is definitely not enough. However it would definitely be possible - as long as it must work only on Linux.
I think, all of your decision will not be supported multi-compiler, because the mechanism of passing arguments to function (registers, their order, stack, memory) - it's compiler dependence feature...
I am working on some legacy C code. The original code was written in the mid-90s, targeting Solaris and Sun's C compiler of that era. The current version compiles under GCC 4 (albeit with many warnings), and it seems to work, but I'm trying to tidy it up -- I want to squeeze out as many latent bugs as possible as I determine what may be necessary to adapt it to 64-bit platforms, and to compilers other than the one it was built for.
One of my main activities in this regard has been to ensure that all functions have full prototypes (which many did not have), and in that context I discovered some code that calls a function (previously un-prototyped) with fewer arguments than the function definition declares. The function implementation does use the value of the missing argument.
Example:
impl.c:
int foo(int one, int two) {
if (two) {
return one;
} else {
return one + 1;
}
}
client1.c:
extern foo();
int bar() {
/* only one argument(!): */
return foo(42);
}
client2.c:
extern int foo();
int (*foop)() = foo;
int baz() {
/* calls the same function as does bar(), but with two arguments: */
return (*foop)(17, 23);
}
Questions: is the result of a function call with missing arguments defined? If so, what value will the function receive for the unspecified argument? Otherwise, would the Sun C compiler of ca. 1996 (for Solaris, not VMS) have exhibited a predictable implementation-specific behavior that I can emulate by adding a particular argument value to the affected calls?
EDIT: I found a stack thread C function with no parameters behavior which gives a very succinct and specific, accurate answer. PMG's comment at the end of the answer taks about UB. Below were my original thoughts, which I think are along the same lines and explain why the behaviour is UB..
Questions: is the result of a function call with missing arguments defined?
I would say no... The reason being is that I think the function will operate as-if it had the second parameter, but as explained below, that second parameter could just be junk.
If so, what value will the function receive for the unspecified argument?
I think the values received are undefined. This is why you could have UB.
There are two general ways of parameter passing that I'm aware of... (Wikipedia has a good page on calling conventions)
Pass by register. I.e., the ABI (Application Binary Interface) for the plat form will say that registers x & y for example are for passing in parameters, and any more above that get passed via stack...
Everything gets passed via stack...
Thus when you give one module a definition of the function with "...unspecified (but not variable) number of parameters..." (the extern def), it will not place as many parameters as you give it (in this case 1) in either the registers or stack location that the real function will look in to get the parameter values. Therefore the second area for the second parameter, which is missed out, essentially contains random junk.
EDIT: Based on the other stack thread I found, I would ammended the above to say that the extern declared a function with no parameters to a declared a function with "unspecified (but not variable) number of parameters".
When the program jumps to the function, that function assumes the parameter passing mechanism has been correctly obeyed, so either looks in registers or the stack and uses whatever values it finds... asumming them to be correct.
Otherwise, would the Sun C compiler of ca. 1996 (for Solaris, not VMS) have exhibited a >> predictable implementation-specific behavior
You'd have to check your compiler documentation. I doubt it... the extern definition would be trusted completely so I doubt the registers or stack, depending on parameter passing mechanism, would get correctly initialised...
If the number or the types of arguments (after default argument promotions) do not match the ones used in the actual function definition, the behavior is undefined.
What will happen in practice depends on the implementation. The values of missing parameters will not be meaningfully defined (assuming the attempt to access missing arguments will not segfault), i.e. they will hold unpredictable and possibly unstable values.
Whether the program will survive such incorrect calls will also depend on the calling convention. A "classic" C calling convention, in which the caller is responsible for placing the parameters into the stack and removing them from there, will be less crash-prone in presence of such errors. The same can be said about calls that use CPU registers to pass arguments. Meanwhile, a calling convention in which the function itself is responsible for cleaning the stack will crash almost immediately.
It is very unlikely the bar function ever in the past would give consistent results. The only thing I can imagine is that it is always called on fresh stack space and the stack space was cleared upon startup of the process, in which case the second parameter would be 0. Or the difference between between returning one and one+1 didn't make a big difference in the bigger scope of the application.
If it really is like you depict in your example, then you are looking at a big fat bug. In the distant past there was a coding style where vararg functions were implemented by specifying more parameters than passed, but just as with modern varargs you should not access any parameters not actually passed.
I assume that this code was compiled and run on the Sun SPARC architecture. According to this ancient SPARC web page: "registers %o0-%o5 are used for the first six parameters passed to a procedure."
In your example with a function expecting two parameters, with the second parameter not specified at the call site, it is likely that register %01 always happened to have a sensible value when the call was made.
If you have access to the original executable and can disassemble the code around the incorrect call site, you might be able to deduce what value %o1 had when the call was made. Or you might try running the original executable on a SPARC emulator, like QEMU. In any case this won't be a trivial task!