ghidra
ghidra copied to clipboard
Is there a way to tell from High P-code what the storage location is for an argument?
For example, here's a line of decompiled code:
local_158[0] = (Cls1 *)Cls1::Cls1(local_120);
It corresponds to this High P-code:
P-code: (unique, 0x10000131, 4) CALL (ram, 0x41113b, 8) , (register, 0x0, 4) @ 0041163e
register 0 is EAX. So you would think that local_120 is passed via EAX. But it's not. local_120 is passed in ECX:
00411638 8b 8d e4 MOV ECX,dword ptr [EBP + local_120]
fe ff ff
0041163e e8 f8 fa CALL Cls1::Cls1 undefined Cls1(Cls1 * this)
ff ff
Is this a bug? Or are register varnodes in High P-code not meaningful? This is not a bug. The register varnodes are meaningful, but reflect the value that is being passed and not the storage location of the call.
Is there another way to learn that local_120 is passed in ECX here?
Example: 0x41163e in attached oo.exe.zip
This looks like unoptimized code. The decompiler has optimized the stack access away. EAX is copied to the relevant stack location at 411629.
That is, the calling convention specifies that the first argument is passed in ECX. The decompiler essentially starts with that fact but is then free to perform transformations on the code. The value written to EAX by the call at 411621 is the value that eventually makes its way to ECX for the call at 41163e.
Thanks @ghidracadabra! I did eventually come to the same conclusion that this behavior is not a bug.
If you know the target function of the call, you can look at: highfunc.getFunctionPrototype().getParam(i).getStorage()
and determine how the argument is passed.
But is there a way to obtain this information for an indirect call? In the GUI, this is configured/overridden in the "override signature" dialog. But if the user does not override this, is it possible to learn in Java code what signature the decompiler inferred?
From the CALLIND op you can get the arguments and their types.
currentProgram.getCompilerSpec().getPrototypeEvaluationModel(EvaluationModelType.EVAL_CALLED)
returns the default PrototypeModel
used for callees. This default will be used if there is no user override.
You should be able to get the locations used for passing parameters via PrototypeModel.getStorageLocations(...)
. One of the inputs to this method is the array of types of the arguments to the CALLIND op (along with the type of the output).
Thanks again @ghidracadabra.
Let me start by saying that the larger problem I'm trying to solve is whether a particular call might be thiscall. To make that concrete, let's say that this means a value might be passed in %ecx
to the callee. I was hoping that the decompiler would have some information on this.
I dug into the decompiler's parameter analysis code. What a rabbit hole... (links mostly for myself)
- https://sourcegraph.com/github.com/NationalSecurityAgency/ghidra@eaf6f0e6461642ff8bde6c7606559fcf416d83f4/-/blob/Ghidra/Features/Decompiler/src/decompile/cpp/coreaction.hh?L733
- https://sourcegraph.com/github.com/NationalSecurityAgency/ghidra/-/blob/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.cc?L4076
- https://sourcegraph.com/github.com/NationalSecurityAgency/ghidra/-/blob/Ghidra/Features/Decompiler/src/decompile/cpp/heritage.cc?L1396
- https://sourcegraph.com/github.com/NationalSecurityAgency/ghidra/-/blob/Ghidra/Features/Decompiler/src/decompile/cpp/fspec.cc?L5331
Somehow all of this is tied into the heritage passes in complex ways that I don't fully understand. In the code I have found, possible parameters seem to be added as "trials", but only if the prototype model agrees it is possible. Then later some code tries to figure out if the parameter is there only to pass to the function call. This is the information I'm ideally looking for.
I have a few specific questions:
- The
FuncCallSpecs
seems to use the prototype model that you indicated. Is there a reason that all known prototype models aren't used, such as for the calling function? - Is there a reason why an uber-general prototype model that permits most registers/calls isn't used for CALLIND? If I understand correctly, the analysis tries to identify which parameters are actually used, so it seems like it would be more beneficial to include everything in the prototype model? The particular problem I'm seeing is that the default proto is __stdcall which excludes registers. I think I can experiment with this on my own by changing the prototype model in the
DecompilerOptions
. - Is there any possibility of exporting some of this metadata out of the decompiler?