18

I am in the process of porting an application from x86 to x64. I am using Visual Studio 2009; most of the code is C++ and some portions are plain C. The __asm keyword is not supported when compiling towards x64 and our application contains a few portions of inline assembler. I did not write this code so I don't know exactly what et is supposed to do:

int CallStackSize() { DWORD Frame; PDWORD pFrame; __asm { mov EAX, EBP mov Frame, EAX } pFrame = (PDWORD)Frame; /*... do stuff with pFrame here*/ } 

EBP is the base pointer to the stack of the current function. Is there some way to obtain the stack pointer without using inline asm? I have been looking at the intrinsics that Microsoft offers as a substitute for inline asm but I could not find anything that gave me something usefull. Any ideas?

Andreas asked what stuff is done with pFrame. Here is the complete function:

int CallStackSize(DWORD frameEBP = 0) { DWORD pc; int tmpint = 0; DWORD Frame; PDWORD pFrame, pPrevFrame; if(!frameEBP) // No frame supplied. Use current. { __asm { mov EAX, EBP mov Frame, EAX } } else Frame = frameEBP; pFrame = (PDWORD)Frame; do { pc = pFrame[1]; pPrevFrame = pFrame; pFrame = (PDWORD)pFrame[0]; // precede to next higher frame on stack if ((DWORD)pFrame & 3) // Frame pointer must be aligned on a DWORD boundary. Bail if not so. break; if (pFrame <= pPrevFrame) break; // Can two DWORDs be read from the supposed frame address? if(IsBadWritePtr(pFrame, sizeof(PVOID)*2)) break; tmpint++; } while (true); return tmpint; } 

The variable pc is not used. It looks like this function walks down the stack until it fails. It assumes that it can't read outside the applications stack so when it fails it has measured the depth of the call stack. This code does not need to compile on _EVERY_SINGLE compiler out there. Just VS2009. The application does not need to run on EVERY_SINGLE computer out there. We have complete control of deployment since we install/configure it ourselves and deliver the whole thing to our customers.

1
  • What stuff is done with pFrame? Commented Dec 4, 2009 at 13:59

6 Answers 6

13

The really right thing to do would be to rewrite whatever this function does so that it does not require access to the actual frame pointer. That is definitely bad behavior.

But, to do what you are looking for you should be able to do:

int CallStackSize() { __int64 Frame = 0; /* MUST be the very first thing in the function */ PDWORD pFrame; Frame++; /* make sure that Frame doesn't get optimized out */ pFrame = (PDWORD)(&Frame); /*... do stuff with pFrame here*/ } 

The reason this works is that in C usually the first thing a function does is save off the location of the base pointer (ebp) before allocating local variables. By creating a local variable (Frame) and then getting the address of if, we're really getting the address of the start of this function's stack frame.

Note: Some optimizations could cause the "Frame" variable to be removed. Probably not, but be careful.

Second Note: Your original code and also this code manipulates the data pointed to by "pFrame" when "pFrame" itself is on the stack. It is possible to overwrite pFrame here by accident and then you would have a bad pointer, and could get some weird behavior. Be especially mindful of this when moving from x86 to x64, because pFrame is now 8 bytes instead of 4, so if your old "do stuff with pFrame" code was accounting for the size of Frame and pFrame before messing with memory, you'll need to account for the new, larger size.

Sign up to request clarification or add additional context in comments.

7 Comments

I don't think the variable can be removed if you take its address. However, the compiler is free to rearrange variables however it likes. Technically there's no language-level guarantee Frame is even located on the stack (but in practice I would expect this to work fine).
My thought exactly, I was on my way to submit a similar answer but I stopped myself because it feels so fragile.
What if you make it volatile?
This should be extremely rare in application code, but in system code it's pretty common. Conservative garbage collectors can make use of it. Mozilla's JavaScript engine uses it for a different reason: to avoid overflowing the C stack. mxr.mozilla.org/mozilla-central/ident?i=JS_CHECK_STACK_SIZE
@JoshLee: yes, volatile int Frame = 0; would avoid the compiler optimizing away the store of 0 to Frame. Frame++ doesn't help at all if optimization is enabled. But we don't actually need that with MSVC or GCC. Using the address is sufficient to get the compiler to take the address of space where it could store a local somewhere in this stack frame. x64 MSVC uses the shadow space (above the return address) first. x86-64 GCC targeting Linux uses the red-zone below RSP. 32-bit MSVC reserves actual space.
|
8

You can use the _AddressOfReturnAddress() intrinsic to determine a location in the current frame pointer, assuming it hasn't been completely optimized away. I'm assuming that the compiler will prevent that function from optimizing away the frame pointer if you explicitly refer to it. Or, if you only use a single thread, you can use the IMAGE_NT_HEADER.OptionalHeader.SizeOfStackReserve and IMAGE_NT_HEADER.OptionalHeader.SizeOfStackCommit to determine the main thread's stack size. See this for how to access the IMAGE_NT_HEADER for the current image.

I would also recommend against using IsBadWritePtr to determine the end of the stack. At the very least you will probably cause the stack to grow until you hit the reserve, as you'll trip a guard page. If you really want to find the current size of the stack, use VirtualQuery with the address you are checking.

And if the original use is to walk the stack, you can use StackWalk64 for that.

3 Comments

It's not seeing how much space is remaining, it's walking back up the chain of previous stack frames. Basically doing a backtrace.
If that's the case there's an API for that: StackWalk64.
I suggest you add that as a new answer then, it sounds like it might well be what the OP needs.
3

There is no guarantee that RBP (the x64's equivalent of EBP) is actually a pointer to the current frame in the callstack. I guess Microsoft decided that despite several new general purpose registers, that they needed another one freed up, so RBP is only used as framepointer in functions that call alloca(), and in certain other cases. So even if inline assembly were supported, it would not be the way to go.

If you just want to backtrace, you need to use StackWalk64 in dbghelp.dll. It's in the dbghelp.dll that's shipped with XP, and pre-XP there was no 64-bit support, so you shouldn't need to ship the dll with your application.

For your 32-bit version, just use your current method. Your own methods will likely be smaller than the import library for dbghelp, much less the actual dll in memory, so it is a definite optimization (personal experience: I've implemented a Glibc-style backtrace and backtrace_symbols for x86 in less than one-tenth the size of the dbghelp import library).

Also, if you're using this for in-process debugging or post-release crash report generation, I would highly recommend just working with the CONTEXT structure supplied to the exception handler.

Maybe some day I'll decide to target the x64 seriously, and figure out a cheap way around using StackWalk64 that I can share, but since I'm still targeting x86 for all my projects I haven't bothered.

Comments

3

Microsoft provides a library (DbgHelp) which takes care of the stack walking, and you should use instead of relying on assembly tricks. For example, if the PDB files are present, it can walk optimized stack frames too (those that don't use EBP).

CodeProject has an article which explains how to use it:

http://www.codeproject.com/KB/threads/StackWalker.aspx

Comments

1

If you need the precise "base pointer" then inline assembly is the only way to go.

It is, surprisingly, possible to write code that munges the stack with relatively little platform-specific code, but it's hard to avoid assembly altogether (depending on what you're doing).

If all you're trying to do is avoid overflowing the stack, you can just take the address of any local variable.

1 Comment

It appears you'll have to use a dedicated assembler (ml64) to create a routine that inspects the stack pointer, discovers the address of the previous stack frame (to account for the movement in the stack pointer when calling your assembler routine), and returns it (I don't know how you'd do this). Then link it in to your C program.
1
.code PUBLIC getStackFrameADDR _getStackFrameADDR getStackFrameADDR: mov RAX, RBP ret 0 END 

Something like that could work for you.

Compile it with ml64 or jwasm and call it using this in your code extern "C" void getstackFrameADDR(void);

1 Comment

x86-64 code doesn't usually have a frame pointer by default. Both GCC and MSVC compile without one.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.