NationalSecurityAgency / ghidra

Ghidra is a software reverse engineering (SRE) framework
https://www.nsa.gov/ghidra
Apache License 2.0
52.17k stars 5.91k forks source link

How to place 16-bit local variable on the stack at -0x1 offset? #2383

Open vovaok opened 4 years ago

vovaok commented 4 years ago

I have some code for HCS-08 microcontroller. It has 8-bit big endian CPU. Inside a function the stack frame looks like this:

offset -0x1 0x0 0x1 0x2 0x3 0x4
local_16bit ret_addr param_16bit

But I can't assign data type "word" to local_16bit variable, because "No new data type in the cycle group fits". I solve the issue temporarily by declaring two 8-bit variables at -0x1 and 0x0 offsets, but decompilation quality is poor. image Also I tried to shift the return address in the .cspec file:

    <returnaddress>
      <varnode space="stack" offset="1" size="2"/>
    </returnaddress>

But this action has no effect.

esaulenka commented 4 years ago

Your problem may have following cause:

There are two types of push/pop behavior. In x86, SP points to last occupied place: PUSH is (--SP)=reg, POP is reg=(SP++) In HCS08, SP points to first free space: PUSH is (SP--)=reg, POP is reg=(++SP)

But i didn't find, how this offet indicated in cspec files.

vovaok commented 4 years ago

Your problem may have following cause:

There are two types of push/pop behavior. In x86, SP points to last occupied place: PUSH is (--SP)=reg, POP is reg=(SP++) In HCS08, SP points to first free space: PUSH is (SP--)=reg, POP is reg=(++SP)

But i didn't find, how this offet indicated in cspec files.

Yeah, you're right. And do you have any ideas how to explain it to GHIDRA? Is it a bug or I can adjust stack frame offset somehow?

astrelsky commented 4 years ago
<stackpointer register="name" space="space" growth="positive/negative" reversejustify="true/false"/>

I can't find online documentation for it so this is copypasta

Attributes and Children
register Name of register to use as stack pointer
space Address space that will hold the stack
growth (Optional) negative or positive
reversejustify (Optional) true or false

The <stackpointer> tag informs Ghidra of the main stack mechanism for the compiler. The register attribute gives the name of the register that holds the current offset into the stack, and the space attribute specifies the name of the address space that holds the actual data. This tag triggers the creation of a formal stack space. A separate stack space exists virtually for each function being analyzed where offsets are calculated relative to the incoming value of this register. This provides a concrete storage location for a function's local variables even though the true location is dynamically determined.

By default the stack is assumed to grow in the negative direction, meaning that entries which are deeper on the stack are stored at larger offsets, and each new entry pushed on the stack causes the stackpointer register to be decremented. But this can be changed by setting the growth attribute to positive, which reverses the direction that new entries are pushed on the stack.

vovaok commented 4 years ago

Stackpointer is defined as it should. The problem is that GHIDRA assumes the return address is at zero offset and locals may have negative offsets only. But at the same time I can define a local variable at any offset. The issue is I can't define multi-byte variable at offset -0x1. It seems that there is the boundary between negative and zero offsets and local variable can't overlay it. So I want to move the boundary between 0x0 and 0x1 offsets. Is there a way to perform this?

astrelsky commented 4 years ago

Stackpointer is defined as it should. The problem is that GHIDRA assumes the return address is at zero offset and locals may have negative offsets only. But at the same time I can define a local variable at any offset. The issue is I can't define multi-byte variable at offset -0x1. It seems that there is the boundary between negative and zero offsets and local variable can't overlay it. So I want to move the boundary between 0x0 and 0x1 offsets. Is there a way to perform this?

Oh. My apologies I misunderstood.

esaulenka commented 4 years ago

@astrelsky Andrew, I think this settings is not enough. Thanks for doc - I never seen it before (where it can be found in readable form? I only found raw xml ). As I understand sources, reversejustify applies only for variables, less than single stack change.

HCS8 (and STM8, which I am currently working on) doesnt have this issue - register size always equal to stack change. It seems that to describe correctly its stack feature, one needs to specify non equal stackshift and extrapop values in function prototype. I tried to do this, but failed.

Unfortunately, there is not enough examples.

esaulenka commented 4 years ago

@vovaok, when you set offset=1 for returnaddress, did you see changes in Stack Editor dialog? Looks like bug, that return address always located at zero offset.

vovaok commented 4 years ago

No, there is no difference. It looks like changing the offset of returnaddress is useless. If you see at the bottom of the Stack Editor there is Return Address Offset field. It always shows 0x0 regardless of settings in the .cspec file. If you assign byte DataType to each stack frame entry it becomes clear what purpose has each byte of the frame with their default names (see picture below). image If I understand GHIDRA terminology correctly, the bytes at offsets 0x0 to 0x2 are considered as a result of the function, not even as return address. And I have no idea why there are 3 bytes, not 2. BTW this fact also doesn't depend on corresponding settings in .cspec.

esaulenka commented 4 years ago

And I have no idea why there are 3 bytes, not 2

It is because arguments, passed through the stack, started at offset=3. It is stated in .cspec, and it works.