C 线程堆栈溢出

C 线程堆栈溢出,c,stack-overflow,task,vxworks,C,Stack Overflow,Task,Vxworks,在像vxworks这样的RTOS中,每当我们创建任务时,都会指定堆栈大小。我们可以用C编写一个例程来检查任务的堆栈是否溢出吗 如果您知道堆栈有多大,并且非常小心,那么可以(但不能便携)。如果没有其他方法获取堆栈的基址,则需要在线程的主函数中记录堆栈变量的地址;这为您提供了堆栈顶部的近似值。然后,在检查函数中,获取局部变量的地址;这将为您提供堆栈的底部。如果顶部和底部之间的差异与堆栈大小有关,那么是时候担心了;如果差异大于堆栈大小,那么就太晚了,不必担心-损坏已经造成(但现在需要考虑如何清理)。堆

在像vxworks这样的RTOS中,每当我们创建任务时,都会指定堆栈大小。我们可以用C编写一个例程来检查任务的堆栈是否溢出吗

如果您知道堆栈有多大,并且非常小心,那么可以(但不能便携)。如果没有其他方法获取堆栈的基址,则需要在线程的主函数中记录堆栈变量的地址;这为您提供了堆栈顶部的近似值。然后,在检查函数中,获取局部变量的地址;这将为您提供堆栈的底部。如果顶部和底部之间的差异与堆栈大小有关,那么是时候担心了;如果差异大于堆栈大小,那么就太晚了,不必担心-损坏已经造成(但现在需要考虑如何清理)。

堆栈大小默认为1MB,具体取决于编译器。基于此信息,您可以尝试使用以下内容捕获剩余堆栈:


unsigned long remaining_stack_size() {
    char dummy;
    return 0x000fffff & (unsigned long)&dummy;
    // 0x000fffff is 1MB -1 (1048576 -1)
}
编辑:注意,它实际上返回当前堆栈位置,这是相同的

编辑(2):对于那些说我错了的人,这里有一个概念证明:


#include <stdio.h>
#include <windows.h>

unsigned long remaining_stack_size() {
    char dummy;
    return 0x001fffff & (unsigned long)&dummy + 1; // okay, some minor adjusts
}

void recurse_to_death(unsigned long used, char *p) {
    char buf[32*1024];
    used += 32*1024;
    printf("Used: 0x%08x Remaining: 0x%08x\n", used, remaining_stack_size());
    recurse_to_death(used, buf);
}

DWORD WINAPI my_thread(void *p) {
    printf("Total stack size of this Thread: 0x%08x bytes\n", remaining_stack_size() + 72);
    recurse_to_death(0, NULL);
    return 0;
}

int main(int argc, char *argv) {
    DWORD tid;
    // CreateThread's stack size actually defaults to 1MB+64KB and does not honor lower values
    CreateThread(NULL, NULL, my_thread, NULL, NULL, NULL);
    Sleep(30000);
    return 0;
}

#包括
#包括
无符号长剩余_堆栈_大小(){
模拟字符;
返回0x001fffff&(unsigned long)&dummy+1;//好的,进行一些小调整
}
void递归到死亡(未签名的长时间使用,char*p){
字符buf[32*1024];
使用+=32*1024;
printf(“已使用:0x%08x剩余:0x%08x\n”),已使用,剩余的堆栈大小();
复发至死亡(已使用,buf);
}
DWORD WINAPI我的线程(void*p){
printf(“此线程的总堆栈大小:0x%08x字节\n”,剩余的堆栈大小()+72);
递归到死亡(0,NULL);
返回0;
}
int main(int argc,char*argv){
德沃德工业贸易署;
//CreateThread的堆栈大小实际上默认为1MB+64KB,不支持较低的值
CreateThread(NULL,NULL,my_thread,NULL,NULL,NULL);
睡眠(30000);
返回0;
}

剩余的\u stack\u size()
完美地预测堆栈溢出。

看看您的编译器,它们通常允许您添加前奏函数来完成此操作,或者它们甚至可以自己检查,除非您操作堆栈指针寄存器

并检查操作系统是否允许您安装“保护页”。将线程堆栈中的最后一页标记为非读/非写,捕获SIGSEGV信号,并使用OS/CPU特定的方法来确定失败的是否是保护页。为此,您必须确保函数的stackframe(堆栈传递的参数、局部变量和alloca分配的空间)始终小于页面大小,否则您可以跳过“保护页面” 这是处理它的最佳方法,因为它在正常处理期间没有运行时开销


您可以看到这种高度依赖OS/CPU/编译器的特性。但是我很确定google会为所有系统找到这种技术的可用代码和助手,因为对于低级程序员(例如运行时或解释器实现者)来说,这是一种非常常见的技术.

您可以使用一些技术-通常您有一个低优先级任务,它每隔一秒钟左右嗅探所有其他任务的堆栈状态

答:在任务开始之前,确保堆栈空间已填充已知模式。然后,您可以通过检查模式来找出剩余的“未损坏”堆栈数量

  • 优点:允许您检查堆栈使用的“高水印”
  • 缺点:如果您分配堆栈内存,但由于某种原因不写入堆栈内存,此技术可能无法检测到溢出
你可以简单地嗅探所有其他线程的堆栈指针

  • 缺点:这只是对堆栈指针进行“采样”,因此可能不会注意到短暂的溢出
  • 优点:快速简单

我建议两者结合使用。因为您使用VxWorks TaskInfoGet()函数之类的函数来执行低级操作,所以很难实现远程可移植性。

我不知道VxWorks,但我记得Green Hill的Velocity/UVelocity内核提供了执行此操作的代码。即使他们没有这样做,因为他们提供了用户可以修改的源代码,并且基础设施也在那里,所以添加起来非常容易


编辑:为了披露真相,我和他们一起做了一个暑期实习,将“uVelosity”移植到一个新的架构中。这就是我如何熟悉它对线程堆栈的处理。

如果您的特定应用程序静态分配其线程,则可以将其堆栈放置在静态定义的区域中,并使用链接器映射在这些区域的末尾放置一个符号。然后,您只需要获取当前堆栈指针(如其他答案中所述),并将“堆栈结束段”指针与该地址进行比较。如果每个线程都有某个位置来存储作为堆栈末尾提供给它的地址,那么这也可以用于动态分配。

仅供参考,您可以使用checkStack()从VxWorks中的shell执行类似操作。

当前堆栈位置不是同一件事。它只是内存中的一个地址。您需要知道堆栈顶部或底部的地址以及大小,才能知道如何使用此技术-1事实上,我知道我在这里做什么,试试看。你非常清楚,你为一个专门询问VxWorks的问题提供了一个适用于Windows的答案。适用于Windows的不是答案。这很有用,但更常见的是需要一种非侵入性的方式从另一个线程检查堆栈-用户代码可能不会溢出,但是,如果你开始调用复杂的OS文件系统或网络功能,它们很可能会吃掉你的堆栈。哇,一个带有“stackoverflow”标签的问题实际上涉及到堆栈溢出。。。(对不起,我没有什么有用的话要说)+1作为保护页。也许你也可以在las页面的末尾,在guard页面之前写一个神奇的数字,让一个SGSEGV处理程序检查它是否正确