如何在 PC/Visual C 上查明指针是否位于堆栈上
[这是专门针对 PC/Visual C++ 的(尽管任何其他答案都非常有启发性:))]
如何判断指针是否来自堆栈中的对象? 例如:
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);
}
因此只有第二个断言(pB)应该失败。 我正在考虑使用一些内联汇编来确定它是否在 SS 段寄存器或类似的东西内。 有谁知道是否有任何内置函数,或者一个简单的方法来做到这一点?
谢谢! RC
[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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
无论你做什么,它都将是极其特定于平台的并且不可移植。 假设您对此表示同意,请继续阅读。 如果指针指向堆栈中的某个位置,它将位于当前堆栈指针
%esp
和堆栈顶部之间。获取堆栈顶部的一种方法是在
main()
的开头读取它。 然而,这有一些问题:- 堆栈顶部实际上略高,因为 C 运行时在进入
main()
之前初始化堆栈- 在 C++ 中,全局对象的构造函数在
main()
之前调用- 如果您的应用程序是多线程的,则每个线程都有自己单独的堆栈。 在这种情况下,您需要一个描述堆栈基础的线程局部变量。
获取当前堆栈指针的一种方法是使用内联汇编:
谨防内联和优化! 优化器可能会破坏此代码。
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:
Beware of inlining and optimization! The optimizer might break this code.
我会提出第二个问题——你为什么需要知道? 这样不会有什么好处。
我认为如果编译器通过指针比较进行合理的操作并且堆栈向下增长,则此方法可能会起作用:
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:
从技术上讲,在便携式 C 中你无法知道。 参数堆栈是许多但并非所有编译器都遵循的硬件细节。 一些编译器会尽可能使用寄存器作为参数(即快速调用)。
如果您专门在 Windows NT 上工作,您希望通过调用 NtCurrentTeb() 来获取线程执行块。 Joe Duffy 的博客 包含以下信息从中你可以得到堆栈范围。 您检查范围内的指针,您应该可以开始了。
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.
由于您指定了 Visual C 和断言,我将假设您可以使用调试版本。
在这种情况下,您可以利用此特定编译器用于内存检查的栅栏柱:
在调试构建中的所有这些情况下都可以正常工作:(
除了最后一个由于未初始化的内存而导致运行时错误 - 但这仍然有效验证指针的目的。)
这仅适用于调试版本 - 发布版本报告所有这些都为 false。
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:
worked correctly in all these cases in a debug build:
(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.
忽略“为什么”的问题...如果您可以控制顶部堆栈帧,一种简单的方法是将全局变量设置为堆栈对象的地址,然后使用一个函数来检查目标指针是否为在此地址和它在堆栈上创建的变量的地址之间。
当然,这不适用于多个线程,除非您将 TopOfTheStack 线程设置为本地线程。
编译器的堆栈优化也可能会导致问题。
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.
This doesn't work with multiple threads of course, unless you make TopOfTheStack thread local.
Stack optimizations by the compiler may also cause problems.
我不可能相信 Visual C++ 或几乎任何在现代 Windows(或古老的 32 位 OS/2)平台上运行的东西,因为在这些平台上,堆栈动态增长,也就是说,只有当您的程序尝试访问所谓的保护页,这是一个位于当前分配的堆栈块顶部的特制内存的 4 KB 块(无论如何对于 32 位 Windows)。 操作系统会拦截当您的程序尝试访问此保护页时生成的异常,并 (1) 在当前分配的堆栈顶部上方映射正常、有效堆栈的新页面来代替它,并且 (2) 创建另一个页面保护页就在新顶部的上方,因此它可以根据以后的需要增长堆栈。 操作系统会执行此操作,直到堆栈达到其限制,并且通常此限制设置得非常高。
如果您的程序尝试访问属于堆栈未分配部分但位于保护页上方的任何地址,您的程序将崩溃,因为操作系统无法解释这一点。 您的程序只是尝试访问其地址空间之外的内存,即使理论上该指针属于任务的堆栈段。
但是,如果您需要一种方法来查找地址是否属于堆栈的已分配部分(即“堆栈上的对象”),那么对 Joe Duffy 的博客的参考是很好的。 只是不要使用那里描述的 StackLimit,使用其他已在此线程中描述的方法获取当前堆栈顶部,这样您就可以对堆栈的已分配部分进行操作,而不是整个(可能部分未分配)
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
我同意人们的观点,他们认为在不断调整堆栈大小的环境中这很难可靠地完成。
但作为“为什么?” 人们 - 很有趣,我今天想在一个小型嵌入式平台上做到这一点。 我有一个函数,它接受一个指向对象的指针,然后在函数返回后保留该指针一段时间(因为它正在处理指针指向的数据)。
我不希望函数的调用者传入自动变量的地址,因为我不希望数据在仍在处理时被踩踏。 调用者必须传入静态数据或常量数据的地址,并且一个不错的“ASSERT_IS_ON_STACK()”宏可能是一个有用的提醒。
便携的? 一点也不。 可怕的糖果机界面? 绝对地。
这就是小型嵌入式系统的本质——良好的断言会有所帮助。
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.
是的,我知道这是非常不可移植的,但这是为了内部应用程序模仿其他硬件的设施来执行此操作。 看来线程执行块可能是正确的选择。
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.
好的,至于“为什么”:
某些处理器的内存控制器无法执行 DMA 或将内存映射到堆栈段或从堆栈段映射内存; 因此,在跨平台世界中,为了确保我不会从那里发送数据,跨平台断言非常有用。
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.