views:

656

answers:

10

[This is for PC/Visual C++ specifically (although any other answers would be quite illuminating :))]

How can you tell if a pointer comes from an object in the stack? For example:

int g_n = 0;

void F()
{
    int *pA = &s_n;
    ASSERT_IS_POINTER_ON_STACK(pA);
    int i = 0;
    int *pB = &i;
    ASSERT_IS_POINTER_ON_STACK(pB);
}

so only the second assert (pB) should trip. I'm thinking using some inline assembly to figure out if it's within the SS segment register or something like that. Does anybody know if there's any built in functions for this, or a simple way to do this?

Thanks! RC

+2  A: 

Whatever you do, it'll be extremely platform-specific and non-portable. Assuming you're ok with that, read on. If a pointer points somewhere in the stack, it will lie between the current stack pointer %esp and the top of the stack.

One way to get the top of the stack is to read it in at the beginning of main(). However, this has a few problems: - The top of the stack is actually slightly higher, since the C runtime initializes the stack before entering main() - In C++, global objects' constructors are called before main() - If your application is multithreaded, each thread has its own separate stack. In that case, you'd need a thread-local variable describing the base of the stack

One way to get the current stack pointer is using inline assembly:

uint32_t GetESP(void)
{
    uint32_t ret;
    asm
    {
        mov esp, ret
    }
    return ret;
}

Beware of inlining and optimization! The optimizer might break this code.

Adam Rosenfield
I'd be more worried about the optimizing not running at all on whatever particular translation unit this function is in.
Don't you mean `mov ret, esp`?
SoapBox
You can use the *volatile* type qualifier on ret to prevent the optimization.
swegi
+1  A: 

Technically speaking, in portable C you can't know. A stack for arguments is a hardware detail that is honored on many but not all compilers. Some compilers will use registers for arguments when they can (ie, fastcall).

If you are working specifically on windows NT, you want to grab the Thread Execution Block from calling NtCurrentTeb(). Joe Duffy's blog has information on this and from it you can get the stack range. You check for pointer in range and you should be good to go.

plinth
This worked like a charm. Thanks plinth!
R Caloca
A: 

Ignoring the question of 'why' ... one simple way if you have control of the top stack frame would be to set a global variable to be the address of a stack object, and then use a function that checks if the target pointer is between this address and the address of a variable it creates on the stack.

void* TopOfStack; // someone must populate this in the first stack frame

bool IsOnTheStack(void* p)
{
  int x;

  return (size_t) p < (size_t) TopOfTheStack &&
         (size_t) p > (size_t) &x;
}

This doesn't work with multiple threads of course, unless you make TopOfTheStack thread local.

Stack optimizations by the compiler may also cause problems.

Rob Walker
+2  A: 

I'll second the question - WHY do you need to know? No good can come from this.

I think this method might work, if the compiler does reasonable things with pointer comparisons and the stack grows down:

static void * markerTop = NULL;

int main()
{
    char topOfStack;
    markerTop = &topOfStack;
    ...
}

bool IsOnStack(void * p)
{
    char bottomOfStack;
    void * markerBottom = &bottomOfStack;
    return (p > markerBottom) && (p < markerTop);
}
Mark Ransom
Your code is right, but I would rename "bottom" to "top" and "top" to "bottom" since the 'top' of the stack is usually the most recent addition, even though it is upside down in memory I think naming it upside down is confusing.
SoapBox
Just being pedantic here, but I think you technically need to cast the pointers to unsigned long (or unsigned long long) to do that comparison portably. But I'm not sure portability is on anyone's mind here... :)
Drew Hall
A: 

Yes I am aware this is extremely unportable, but this is for an internal app to mimic other hardware's facilities for doing this. Seems the Thread Execution Block might be the way to go.

R Caloca
A: 

No way I believe for Visual C++ or almost anything that runs on modern Windows (or ancient 32-bit OS/2) platforms, because on those platforms the stack grows dynamically, that is, a new page of the stack is allocated only when your program tries to access the so-called guard page, a 4-KB block (for 32-bit Windows anyway) of specially crafted memory at the top of the currently allocated chunk of the stack. The operating system intercepts the exception that is generated when your program tries to access this guard page and (1) maps a new page of normal, valid stack in place of it, above the top of the currently allocated stack and (2) creates another guard page just above the new top, so it can grow the stack as needed later. The OS does this until the stack reaches its limit, and usually this limit is set very high.

And if your program tries to access any address that belongs to the unallocated part of the stack but lies above the guard page, your program will crash, because the OS has no way to intepret this. Your program just tries to access memory outside of its address space, even if the pointer belongs, theoretically, to the stack segment of the task.

However, if you need a way to find if an address belongs to the allocated part of the stack (that is, "an object on the stack"), the reference to Joe Duffy's blog is good. Just don't use StackLimit described there, get the current top of the stack using other, already described in this thread, methods, so you operate on the allocated part of the stack, not the entire, probably partially unallocated, one

dmityugov
A: 

I'm in agreement with the people who say this is pretty difficult to do reliably in an environment which keeps adjusting the stack size.

But as the for the 'why?' people - funny enough I wanted to do this today on a small embedded platform. I have a function which takes a pointer to an object, and then keeps hold of that pointer for some time after the function returns (because it's processing the data which the pointer pointed to).

I don't want callers of my function to pass in the address of automatic variables, because I don't want the data to get stomped on while it's still being worked on. Callers must pass in the address of static data or const data, and a nice 'ASSERT_IS_ON_STACK()' macro might be a useful reminder.

Portable? Not at all. Horrible Candy-Machine Interface? Absolutely.

Such is the nature of small embedded systems - good assertions can help.

Will Dean
A: 

Ok, as to the "why":

Some processors' memory controllers can't either perform DMAs or map memory to/from the stack segment(s); so in a cross platform world, to make sure I'm not sending data from there, a cross platform assert is quite useful.

R Caloca
Thanks for that explanation, now it makes sense. I'm going to leave my nasty comment in my answer though, to put a little fear into the next person who asks this question.
Mark Ransom
+1  A: 

Since you specified Visual C and asserts, I'm going to assume you can use a debug build. In that case, you can take advantage of the fenceposts that this specific compiler puts for memory checking:

#define IS_POINTER_TO_STACK(vp)   (*((int*)(vp)-1)==0xCCCCCCCC)

worked correctly in all these cases in a debug build:

#define ASSERT(v)  printf("assert: %d\n", v);  //so it doesn't really quit
int g_n = 0;
void test_indirectly(void* vp) {
    ASSERT(IS_POINTER_TO_STACK(vp));
}
void F() {
    int *pA = &g_n;
    ASSERT(IS_POINTER_TO_STACK(pA));         //0

    int i = 0;
    int j = 0;
    int *pB = &i;
    ASSERT(IS_POINTER_TO_STACK(pB));         //1
    ASSERT(IS_POINTER_TO_STACK(&j));         //1

    int *pC = (int*)malloc(sizeof(int));
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    free(pC);
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    pC = new int;
    ASSERT(IS_POINTER_TO_STACK(pC));         //0
    delete pC;

    char* s = "HelloSO";
    char w[6];
    ASSERT(IS_POINTER_TO_STACK("CONSTANT")); //0
    ASSERT(IS_POINTER_TO_STACK(s));          //0
    ASSERT(IS_POINTER_TO_STACK(&w[0]));      //1
    test_indirectly(&s);                     //1

    int* pD; //uninit
    ASSERT(IS_POINTER_TO_STACK(pD));    //runtime error check

}

(except the very last one caused a runtime error due to uninitialized memory - but that still serves the purpose of validating your pointers.)

This only works in the Debug build - the Release build reports false for all of them.

AShelly