How to programmatically tell if two variables are on the same stack? (in Windows)
-
20-09-2019 - |
Question
I'm in a thread. I have an address. Is that address from a variable on the same stack that I'm using?
static int *address;
void A()
{
int x;
atomic::CAS(address, 0, &x); // ie address = &x
// ...
}
void B()
{
int y;
int * addr = atomic::read(address); // ie addr = address
if (addr && on_same_stack(&y, addr))
{
// B() called from A()
}
else
{
// B() called from different thread than A()
}
}
I need to implement on_same_stack(addr1, addr2)
. I know that the stack(s) on Windows grow as needed, but I also know that there is a limit to the growth, and that (in debug at least) there is stack-overflow-checking code on every function call. So I think it can be done.
Now, I also know that I could/should use thread IDs, etc. But I'm trying to implement some tricky lock-free coding here, and I don't really have the room to store the thread IDs, just a single pointer. (I'm hoping to avoid CMPXCH16). Please trust me that I somewhat know what I'm doing :-).
This is for Windows-only for now. But the more portable the better. (NT/XP/7/CE?)
P.S. this site is called "stackoverflow" so it should be the right place to ask, shouldn't it? :-)
EDIT: adding context, since everyone is asking. I'm implementing a custom call_once similar to pthread_once or boost.threads call_once. I'm attempting to check for recursion. I am very limited with what I have to work with. I can't add function parameters. I can't make assumptions about what the rest of the program is doing, like how much TLS they are already using. Etc. I can only code inside my one function, and make no changes or assumptions about anything outside of that.
Thanks for your questions/answers.
Solution
How about something crazy like (untested):
declspec(__thread) void* stackBottom;
void Thread1Routine(void* arg)
{
volatile int bottom;
stackBottom = ⊥
... (do stuff which ends up calling on_same_stack()) ...
}
bool on_same_stack(void* p)
{
volatile int top;
return ((LONG_PTR)p >= (LONG_PTR)&top) && ((LONG_PTR)p <= (LONG_PTR)stackBottom);
}
(edited to remove theoretical register-based arg passing issues)
OTHER TIPS
Using Win32 Thread Information Block
These examples use the main thread but actually it must work on in any thread.
Example #1:
#include <iostream>
#include <windows.h>
#include <winnt.h>
#include <intrin.h>
inline size_t get_thread_top_stack_size()
{
NT_TIB *s = (NT_TIB*)getTib();
return (size_t)s->StackBase ;
}
int global_var;
int main ()
{
size_t sp_value = 0;
_asm { mov [sp_value], esp }
size_t thread_top_stack = get_thread_top_stack_size();
int my_local_var;
size_t my_local_var_addr = (size_t)&my_local_var;
if (my_local_var_addr < thread_top_stack && my_local_var_addr > sp_value ) {
std::cout << "Yes, on the thread stack";
} else {
std::cout << "No, not on the thread stack";
}
size_t my_global_var_addr = (size_t)&global_var;
if (my_global_var_addr < thread_top_stack && my_global_var_addr> sp_value ) {
std::cout << "Yes, on the thread stack";
} else {
std::cout << "No, not on the thread stack";
}
return 0;
}
Example #2:
#include <windows.h>
#include <winnt.h>
#include <intrin.h>
inline NT_TIB* getTib()
{
return (NT_TIB*)__readfsdword( 0x18 );
}
inline bool is_var_on_the_thread_stack(void* ptr)
{
NT_TIB *nt_tib = getTib();
return (nt_tib->StackBase >= ptr) && (nt_tib->StackLimit <= ptr );
}
int my_global_var;
int main ()
{
int my_thread_var;
if (is_var_on_the_thread_stack(&my_thread_var)) {
std::cout << "Yes, on the thread stack" << std::endl;
} else {
std::cout << "No, not on the thread stack" << std::endl;
}
if (is_var_on_the_thread_stack(&my_global_var)) {
std::cout << "Yes, on the thread stack" << std::endl;
} else {
std::cout << "No, not on the thread stack" << std::endl;
}
return 0;
}
from your comment B() called from A()
, can't you just pass an argument to B()
and set it to a specific value when called from A()
?? (with a default argument value, that would not require a big change)
your sample is not very complete, so here is another attempt making A LOT of assumptions about your problem... what about:
void A()
{
B_lockfree();
}
void B()
{
// acquire a lock here
B_lockfree();
// release your lock here
}
void B_lockfree()
{
// do whatever you want here
}
(well, i can think of a lot of ways, but without knowing the big picture, they may all be completely wrong...)