循环system()和cout的线程会破坏堆栈

Threads looping system() and cout corrupt the stack

本文关键字:线程 堆栈 cout system 循环      更新时间:2023-10-16

运行以下代码的进程因分段错误而崩溃:

#include <stdlib.h>
#include <iostream>
#include <pthread.h>
void* f( void* )
{
   while( true )
   {
      // It crashes inside this call (with cerr, too).
      std::cout << 0;
   }
   return NULL;
}
int main()
{
   pthread_t t;
   pthread_create( &t, NULL, &f, NULL );
   while( true )
   {
      // It crashes with any script/app; true is just simple.
      system( "true" );
   }
   return 0;
}

大约每隔几秒钟执行一次就会崩溃(输出有数千到数百万个'0')。上面的代码会使cout << 0调用中的几个函数崩溃。根据f()中调用的额外函数或放在堆栈上的数据,它会在不同的地方崩溃。在gdb中,有时堆栈对于函数调用的顺序没有意义。由此我推断堆栈已损坏。

我发现多线程应用程序调用fork()有一些问题(参见两个提到堆栈损坏的注释)。如果文件描述符没有设置为FD_CLOEXEC,那么分叉/克隆进程会复制它们。但是,没有显式创建的文件描述符。(我试着在fileno( stdout )fileno( stderr )上设置FD_CLOEXEC,没有任何积极的变化。)

即使没有显式的文件描述符,我可以不混合线程和fork()吗?我是否只需要用等效的功能替换system()调用?或者内核中有导致崩溃的错误,并且在2.6.30之后已经修复了?

其他细节

我在一个ARM AT91处理器(armv5tejl)上运行它,Linux 2.6.30(为我的特定外设设置了一些覆盖和补丁)用GCC 4.3.2编译。

Linux 2.6.30 #1 Thu May 29 15:43:04 CDT 2014 armv5tejl GNU/Linux

我一直在用-g-O0[交叉]编译它,但没有这些,它仍然崩溃:

arm-atmel-linux-gnueabi-g++ -o system_thread system_thread.cpp -lpthread

我也尝试过-fstack-protector-all标志:有时它在__stack_chk_fail()中崩溃,但有时其他函数指针或数据损坏,它更早崩溃。

它加载的库(从strace):

libpthread.so.0
libstdc++.so.6
libm.so.6
libgcc_s.so.1
libc.so.6

注意:因为它有时不会崩溃,并不是真正响应^C,我通常在后台运行它:

$ killall -9 system_thread; rm -f log; system_thread >log &

我已经为几个不同的体系结构和Linux内核版本编译了这个程序,但是我没有看到它在其他地方崩溃:

Linux 3.10.29 #1 Wed Feb 12 17:12:39 CST 2014 armv5tejl GNU/Linux
Linux 3.6.0-dirty #3 Wed May 28 13:53:56 CDT 2014 microblaze GNU/Linux
Linux 3.13.0-27-generic #50-Ubuntu SMP Thu May 15 18:06:16 UTC 2014 x86_64 x86_64 GNU/Linux
Linux 3.8.0-35-generic #50~precise1-Ubuntu SMP Wed Dec 4 17:25:51 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

编辑:请注意,在相同的体系结构(armv5tejl)上,它不会在Linux 3.10.29上崩溃。此外,在我的"设备"的早期版本(较旧的服务器和客户端应用程序)上运行时,它不会崩溃,具有相同的Linux版本- 2.6.30。所以操作系统的环境是有一定影响的。

BusyBox v1.20.1提供system()调用的sh

这在使用您提到的2.6.30内核的ARM处理器上是可重现的,但在master中不能。我们可以使用git bisect来找到这个bug被修复的地方(大约需要16次迭代)。请注意,由于git bisect是为了找到回归,但在这种情况下,master是"好",而过去的版本是"坏",我们需要颠倒"好"answers"坏"的含义。

被分割发现的罪魁祸首是这个提交,修复涉及fork()的"用户空间数据损坏实例"。此症状与您描述的症状非常相似,并且也可能损坏堆栈外的内存。在将此提交和所需的父组件移植到2.6.30内核后,您发布的代码不再崩溃。