You've stumbled into a complicated question. I'll try to answer it in detail, but for starters, the fact that there are two locations shown for a single stack variable does not mean that it is at two different locations. It means that Hex-Rays is using two different ways of referring to the same location on the the stack. Both shown offsets must be interpreted with care, can be highly misleading, and can also be outright wrong. I will explain all of those points below.
For the sake of discovering local variables on the stack and performing analyses that determine what memory may be overwritten by a given memory write, Hex-Rays needs a consistent way to refer to offsets on the stack. This is complicated because rsp
can change throughout the function, and because not all functions use frame pointers like rbp
. Thus, it has to do something else to keep track of stack offsets. So, Hex-Rays computes the lowest stack value at any point within the function, uses that as the "bottom of the stack", and performs all stack-based analysis relative to that point.
When Hex-Rays prints local variables and displays strings off to the right like [rsp+30h]
, that refers to Hex-Rays' internal computation of where the bottom of the stack is. Since rsp
can change throughout a function, the location rsp+0x30
might mean different things at different points in the function. On x64/Windows binaries that conform to the x64 ABI, rsp
will have a consistent value after the function prolog and before the epilog, so the numbers should be reasonably safe to use after the prolog and before the epilog (though I've noticed that they are sometimes off by 8). Since the prolog and the epilog change rsp
, those numbers won't be meaningful during the prolog or epilog. In particular, on the first line of the function before the prolog has executed, the rsp
number is especially meaningless. On x86 binaries, esp
changes often throughout a function, and so those numbers will be generally unreliable as a way to locate stack variables on x86
.
Meanwhile, when Hex-Rays prints a string like [rbp-38h]
, the 38h
refers to offset within IDA's stack frame for the function. I.e., if you were to look at the top of the function, you might see something like:
.text:000000006202C6F0 var_40= qword ptr -40h
.text:000000006202C6F0 a1 = xmmword ptr -38h ; <--- HERE
.text:000000006202C6F0 var_28= qword ptr -28h
.text:000000006202C6F0 var_18= byte ptr -18h
Although the offsets line up with what's in the stack view, Hex-Rays always prints rbp
, whereas x64 functions can use other non-volatile registers as frame pointers. Therefore, you won't always be able to find the value at rbp-Offset
, either; you would need to substitute whatever register is being used for the frame pointer (such as r11
) in place of rbp
. And note that, like rsp
, rbp
will also change values within the function (e.g. being set in the prolog and restored in the epilog), so even if a function is using rbp
as a frame pointer, the rbp-N
offsets will only make sense after the prolog and before the epilog.
If you'd like more technical details about this, you should take a look at hexrays.hpp
in the IDA SDK. Specifically, there is a diagram showing what Hex-Rays thinks about the stack, and how the rsp
-relative numbers are related to the rbp
-relative numbers. You want to search that .hpp
file for tmpstk_size
. It's also available online. As of the time of writing, it begins on line 4596. I've reproduced it here:
/*
+-----------+ <- inargtop
| prmN |
| ... | <- minargref
| prm0 |
+-----------+ <- inargoff
|shadow_args|
+-----------+
| retaddr |
frsize+frregs +-----------+ <- initial esp |
| frregs | |
+frsize +-----------+ <- typical ebp |
| | | |
| | | fpd |
| | | |
| frsize | <- current ebp |
| | |
| | |
| | | stacksize
| | |
| | |
| | <- minstkref |
stkvar base off 0 +---.. | | | current
| | | | stack
| | | | pointer
| | | | range
|tmpstk_size| | | (what getspd() returns)
| | | |
| | | |
+-----------+ <- minimal sp | | offset 0 for the decompiler (vd)
There is a detail that may add confusion when working with stack variables.
The decompiler does not use the same stack offsets as IDA.
The picture above should explain the difference:
- IDA stkoffs are displayed on the left, decompiler stkoffs - on the right
- Decompiler stkoffs are always >= 0
- IDA stkoff==0 corresponds to stkoff==tmpstk_size in the decompiler
- See stkoff_vd2ida and stkoff_ida2vd below to convert IDA stkoffs to vd stkoff
*/
TL;DR the reason Hex-Rays shows you two different representations for where a variable is on the stack is because Hex-Rays itself, and IDA, use two different ways of representing the stack frame. The rbp-N
numbers refer to the offsets you see in IDA's stack view, and the rsp+N
numbers refer to displacements from what Hex-Rays thinks is the bottom of the stack.
Especially considering that rsp
and rbp
can change throughout a function, the addresses it shows won't be meaningful at every point within the function. Particularly, none of the numbers make sense before the prolog has finished executing, or after the epilog has begun executing. The esp
numbers are particularly unreliable on 32-bit x86
, and Hex-Rays will show frame-relative offsets using the rbp
register even if the function uses a different register like r11
for a frame pointer.
Simple, right? ;-)
0xffff940a1f436b48
and0xffff940a1f436bf9
coming from? A live debugging session? If so, those values are going to depend on where within the function the instruction pointer is at the time you computed those values (e.g. both of them will differ before and after the function prolog).