简体   繁体   English

在 pthreads start_routine function 中声明数组导致分段错误

[英]Declaring array in pthreads start_routine function causing segmentation fault

#include <stdio.h>
#include <pthread.h>

void* function(void* arg){
  int picture[4096][4096];
}

int main(){
  int N=10, S=10;
  pthread_t pids[10];
  pthread_create(&pids[0], NULL, function, NULL);
  pthread_join(pids[0], NULL);
  return 0;
}

I compiled the above code with: gcc test.c -pthread .我编译了上面的代码: gcc test.c -pthread

On running the executable, it crashes, displaying: Segmentation fault .在运行可执行文件时,它会崩溃,并显示: Segmentation fault

But, if I remove the int picture[4096][4096];但是,如果我删除int picture[4096][4096]; definition, it doesn't crash.定义,它不会崩溃。

What could be the reason for this?这可能是什么原因?

The crashing program is:崩溃的程序是:

#include <stdio.h>
#include <pthread.h>

void *function(void *arg)
{
  int picture[4096][4096]; // 4096*4096*sizeof(int) = 67108864 bytes = 64 MB
}

int main()
{
  pthread_t pids[10];
  pthread_create(&pids[0],NULL, function, NULL);
  pthread_join(pids[0],NULL);
  return 0;
}

The program crashes at execution time:程序在执行时崩溃:

$ gcc p.c -lpthread
$ ./a.out 
Segmentation fault (core dumped)

Thread stack layout线程栈布局

The default stack size for a thread in the GLIBC/pthread is 8 MB . GLIBC/pthread中线程的默认堆栈大小为8 MB At thread creation time, the Thread descriptor also called Task Control Block (TCB), is stored at the bottom of the stack and a red zone (guard page of 4 KB without read/write permission is set at the top of the stack).在线程创建时,线程描述符也称为任务控制块(TCB),存储在堆栈底部和一个红色区域(在堆栈顶部设置了 4 KB 的没有读/写权限的保护页)。 The stack grows from the high to low addresses.堆栈从高地址向低地址增长。

Result of the program under the control of strace : strace控制下的程序结果:

$ strace -f ./a.out
[...]
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
mmap(NULL, 8392704, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7fee8d4dc000
mprotect(0x7fee8d4dd000, 8388608, PROT_READ|PROT_WRITE) = 0
brk(NULL)                               = 0x556cf1b72000
brk(0x556cf1b93000)                     = 0x556cf1b93000
clone(child_stack=0x7fee8dcdbfb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTIDstrace: Process 3338 attached
, parent_tid=[3338], tls=0x7fee8dcdc700, child_tidptr=0x7fee8dcdc9d0) = 3338
[pid  3338] set_robust_list(0x7fee8dcdc9e0, 24 <unfinished ...>
[pid  3337] futex(0x7fee8dcdc9d0, FUTEX_WAIT, 3338, NULL <unfinished ...>
[pid  3338] <... set_robust_list resumed>) = 0
[pid  3338] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x7fee8d4dcef0} ---
[pid  3337] <... futex resumed>)        = ?
[pid  3338] +++ killed by SIGSEGV (core dumped) +++
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)

In the preceding:在前面:

  • The pthread library grabs the default stack size with a call to getrlimit() which returns 8 MB: prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0 pthread 库通过调用getrlimit()获取默认堆栈大小,返回 8 MB: prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
  • The pthread library allocates the stack zone of 8MB + 4 KB of guard page with a call to mmap() with no read/write permissions (ie PROT_NONE ): mmap(NULL, 8392704, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7fee8d4dc000 pthread 库通过调用mmap()分配 8MB + 4 KB 保护页的堆栈区域,没有读/写权限(即PROT_NONE ): mmap(NULL, 8392704, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7fee8d4dc000
  • The pthreads library calls mprotect() to set the read/write (ie PROT_READ|PROT_WRITE ) permission on the memory zone except the first 4 KB of guard page (will serve to detect the stack overflows) mprotect(0x7fee8d4dd000, 8388608, PROT_READ|PROT_WRITE) = 0 The pthreads library calls mprotect() to set the read/write (ie PROT_READ|PROT_WRITE ) permission on the memory zone except the first 4 KB of guard page (will serve to detect the stack overflows) mprotect(0x7fee8d4dd000, 8388608, PROT_READ|PROT_WRITE) = 0
  • The thread is created with a call to clone() (the beginning of the stack is set at 0x7fee8dcdbfb0) clone(child_stack=0x7fee8dcdbfb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[3338], tls=0x7fee8dcdc700, child_tidptr=0x7fee8dcdc9d0) = 3338通过调用clone()创建线程(堆栈的开头设置为 0x7fee8dcdbfb0) clone(child_stack=0x7fee8dcdbfb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[3338], tls=0x7fee8dcdc700, child_tidptr=0x7fee8dcdc9d0) = 3338

Hence the following memory space layout:因此以下 memory 空间布局:

              +      +--------------------+ 0x7fee8d4dc000
              |      |                    |
      4 KB    |      |      RED ZONE      |
   (PROT_NONE)|      |    (guard page)    |
              +      +--------------------+ 0x7fee8d4dd000
              |      |                    |
              |      |                    |
              |      |          ^         |
    8192 KB   |      |          |         |
(PROT_READ/WRITE)    |        Stack       |
              |      |          |         |
              |      |          |         |
              |      +--------------------+ 0x7fee8dcdbfb0
              |      |                    |
              |      |     TCB + TLS      |
              |      |                    |
              +      +--------------------+ 0x7fee8dcdd000

Why your program crashed为什么你的程序崩溃了

The thread entry point defines a table of 4096x4096x4 bytes which is equal to 64 MB.线程入口点定义了一个4096x4096x4字节的表,等于 64 MB。 This is too much for the 8 MB long stack area.这对于 8 MB 长堆栈区域来说太多了 However, we could expect no crash at all as the function defines a huge local table but there is no read/write access into it.但是,我们可以预期根本不会崩溃,因为 function 定义了一个巨大的本地表,但没有对其进行读/写访问。 So, no crash should occur .因此,不应发生崩溃

The strace logs show that the crash occurs upon access to address 0x7fee8d4dcef0 which is above the stack area in the allocated memory zone: [pid 3338] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x7fee8d4dcef0} --- strace日志显示,访问地址0x7fee8d4dcef0时发生崩溃,该地址位于分配的 memory 区域中的堆栈区域上方: [pid 3338] --- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x7fee8d4dcef0} ---

It is actually in the guard page :它实际上在保护页面中

              +      +--------------------+ 0x7fee8d4dc000
              |      |                    |
      4 KB    |      |      RED ZONE <--------- Trap @ si_addr=0x7fee8d4dcef0
   (PROT_NONE)|      |                    |            si_code=SEGV_ACCERR
              +      +--------------------+ 0x7fee8d4dd000
              |      |                    |
              |      |                    |
              |      |          ^         |
    8192 KB   |      |          |         |
(PROT_READ/WRITE)    |        Stack       |
              |      |          |         |
              |      |          |         |
              |      +--------------------+ 0x7fee8dcdbfb0
              |      |                    |
              |      |     TCB + TLS      |
              |      |                    |
              +      +--------------------+ 0x7fee8dcdd000

The core dump analysis under gdb provides the following location for the crash: gdb下的核心转储分析提供了以下崩溃位置:

$ gdb a.out core
[...]
(gdb) where
#0  0x00005594eb9461a0 in function (arg=<error reading variable: Cannot access memory at address 0x7fe95459ded8>) at p.c:56
#1  0x00007fe95879d609 in start_thread (arg=<optimized out>) at pthread_create.c:477
#2  0x00007fe9586c4293 in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
(gdb) disas /m
Dump of assembler code for function function:
56  void* function(void* arg){
   0x00005594eb946189 <+0>: endbr64 
   0x00005594eb94618d <+4>: push   %rbp
   0x00005594eb94618e <+5>: mov    %rsp,%rbp
   0x00005594eb946191 <+8>: lea    -0x4000000(%rsp),%r11
   0x00005594eb946199 <+16>:    sub    $0x1000,%rsp
=> 0x00005594eb9461a0 <+23>:    orq    $0x0,(%rsp)
   0x00005594eb9461a5 <+28>:    cmp    %r11,%rsp
   0x00005594eb9461a8 <+31>:    jne    0x5594eb946199 <function+16>
   0x00005594eb9461aa <+33>:    sub    $0x20,%rsp
   0x00005594eb9461ae <+37>:    mov    %rdi,-0x4000018(%rbp)
   0x00005594eb9461b5 <+44>:    mov    %fs:0x28,%rax
   0x00005594eb9461be <+53>:    mov    %rax,-0x8(%rbp)
   0x00005594eb9461c2 <+57>:    xor    %eax,%eax

57    int picture[4096][4096];
58  }

The above disassembly code of the thread entry point shows that gcc generates stack accesses every 4 KB (memory page size).上述线程入口点的反汇编代码显示, gcc每 4 KB(内存页大小)生成一次堆栈访问。 It first sets R11 register with the address of the beginning of the local table ( 0x4000000 is 4096x4096xsizeof(int) = 67108864 bytes ):它首先将R11寄存器设置为本地表开头的地址( 0x40000004096x4096xsizeof(int) = 67108864 字节):

   0x00005594eb946191 <+8>: lea    -0x4000000(%rsp),%r11

Then, it loops "oring" the content of the stack with 0 every 4096 bytes (0x1000):然后,它每 4096 个字节(0x1000)循环“oring”堆栈的内容为 0:

   0x00005594eb946199 <+16>:    sub    $0x1000,%rsp
=> 0x00005594eb9461a0 <+23>:    orq    $0x0,(%rsp)
   0x00005594eb9461a5 <+28>:    cmp    %r11,%rsp
   0x00005594eb9461a8 <+31>:    jne    0x5594eb946199 <function+16>

Hence, the crash because at some point, the orq instruction occurs in the guard page of the stack!因此,崩溃是因为在某些时候, orq指令出现在堆栈的保护页中!

NB :注意

  • The reason for the "apparently useless" generated code is the protection against the Stack Clash class of vulnerabilities as explained in this answer “显然无用”生成代码的原因是针对堆栈冲突 class 漏洞的保护,如本答案中所述
  • Of course, compiling the same code with an optimization option would not trigger any crash as function() would not contain any code:当然,使用优化选项编译相同的代码不会触发任何崩溃,因为function()不会包含任何代码:
$ gcc p.c -lpthread -O2
$ ./a.out

The optimized disassembly code of function() is a simple "return": function()的优化反汇编代码是一个简单的“返回”:

$ objdump -S a.out
[...]
00000000000011f0 <function>:
    11f0:   f3 0f 1e fa             endbr64 
    11f4:   c3                      retq   
    11f5:   66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
    11fc:   00 00 00 
    11ff:   90                      nop

How to set a bigger stack for the thread如何为线程设置更大的堆栈

As seen above, by default, the GLIBC/pthread library allocates a default stack of 8 MB.如上所示,默认情况下, GLIBC/pthread库分配 8 MB 的默认堆栈。 But it also provides the ability to set a stack allocated by the user or simply define the stack size with the following steps:但它也提供了设置用户分配的堆栈的能力,或者通过以下步骤简单地定义堆栈大小:

Here is an enhanced version of the program which defines a stack of 65 MB for the thread:这是该程序的增强版本,它为线程定义了 65 MB 的堆栈:

#include <stdio.h>
#include <pthread.h>

void* function(void* arg)
{
  int picture[4096][4096];    // 4096*4096*sizeof(int) = 67108864 bytes = 64 MB
}

int main(void)
{
  pthread_t pids[10];
  pthread_attr_t attr;

  pthread_attr_init(&attr);
  pthread_attr_setstacksize(&attr, 65*1024*1024);
  pthread_create(&pids[0], &attr, function, NULL);
  pthread_join(pids[0], NULL);
  pthread_attr_destroy(&attr);

  return 0;
}

Build and execution:构建和执行:

$ gcc p2.c -lpthread
$ ./a.out

There is no crash.没有崩溃。 With strace , we can verify the behavior:使用strace ,我们可以验证行为:

$ strace ./a.out
[...]
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
mmap(NULL, 68161536, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7fe55afd3000
mprotect(0x7fe55afd4000, 68157440, PROT_READ|PROT_WRITE) = 0
brk(NULL)                               = 0x55b9d7ade000
brk(0x55b9d7aff000)                     = 0x55b9d7aff000
clone(child_stack=0x7fe55f0d2fb0, flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID, parent_tid=[5199], tls=0x7fe55f0d3700, child_tidptr=0x7fe55f0d39d0) = 5199
futex(0x7fe55f0d39d0, FUTEX_WAIT, 5199, NULL) = 0
munmap(0x7fe55afd3000, 68161536)        = 0
exit_group(0)                           = ?
+++ exited with 0 +++

We can see in the above traces:我们可以在上面的痕迹中看到:

  • A call to mmap() of 65 MB + 4KB = 66564 KB = 68161536 bytes (ie 65 MB + 4 KB of guard page rounded up to the greater 4 KB page boundary)mmap()的调用为65 MB + 4KB = 66564 KB = 68161536 字节(即65 MB + 4 KB的保护页面向上舍入到更大的 4 KB 页面边界)
    mmap(NULL, 68161536, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_STACK, -1, 0) = 0x7fe55afd3000
  • A call to mprotect() on the first 68157440 bytes to set the guard page in the remaining 4KB在前 68157440 个字节上调用mprotect()以在剩余的 4KB 中设置保护页
    mprotect(0x7fe55afd4000, 68157440, PROT_READ|PROT_WRITE) = 0

Hence the new memory space layout:因此,新的 memory 空间布局:

              +      +--------------------+ 0x7fe55afd3000
              |      |                    |
      4 KB    |      |      RED ZONE      |
   (PROT_NONE)|      |                    |              
              +      +--------------------+ 0x7fe55afd4000
              |      |                    |
              |      |                    |
              |      |          ^         |
   66560 KB   |      |          |         |
(PROT_READ/WRITE)    |        Stack       |
              |      |          |         |
              |      |          |         |
              |      +--------------------+ 0x7fe55f0d2fb0
              |      |                    |
              |      |     TCB + TLS      |
              |      |                    |
              +      +--------------------+ 0x7FE55F0D4000

Conclusion结论

From a simple program ending into a strange crash, we took the opportunity to study the thread's stack layout in the GLIBC/pthread library as well as the protection mechanism against the stack overflows and the stack size configuration.从一个简单的程序结束到一个奇怪的崩溃,我们借此机会研究了GLIBC/pthread库中线程的堆栈布局以及堆栈溢出的保护机制和堆栈大小配置。
However, from a program design point of view , we should never allocate so huge variables in the stack.但是,从程序设计的角度来看,我们不应该在堆栈中分配这么大的变量。 In the current program, the table should be dynamically allocated or defined as a global variable (in Thread Local Storage) for examples.在当前程序中,表应该是动态分配或定义为全局变量(在线程本地存储中)的例子。 But it is another story...但这是另一个故事……

I generated core dump file.我生成了核心转储文件。 I ran the core dump file.我运行了核心转储文件。 It gave me the following:它给了我以下信息:

#0  0x00005643352ba745 in function (arg=<error reading variable: Cannot access memory at address 0x7fe80b054ed8>) at Pthred_kk.c:5
        picture = <error reading variable picture (value requires 67108864 bytes, which is more than max-value-size)>
#1  0x00007fe80f6526db in start_thread (arg=0x7fe80f055700) at pthread_create.c:463
        pd = 0x7fe80f055700
        now = <optimized out>
        unwind_buf = {cancel_jmp_buf = {{jmp_buf = {140634661148416, 8554578219241222147, 140634661146560, 0, 0, 140724934020640, 
                -8545604918547140605, -8545605192128745469}, mask_was_saved = 0}}, priv = {pad = {0x0, 0x0, 0x0, 0x0}, data = {prev = 0x0, 
              cleanup = 0x0, canceltype = 0}}}
        not_first_call = <optimized out>
#2  0x00007fe80f37b88f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

picture = <error reading variable picture (value requires 67108864 bytes, which is more than max-value-size)图片=<错误读取变量图片(值需要67108864字节,大于max-value-size)

In linux, the maxmum stack size for a thread is about 8MB.在 linux 中,线程的最大堆栈大小约为 8MB。
As you can see the size (67108864 bytes) of picture is more than the maximum size (8MB = 8 * 1024 *1024 = 8388608).如您所见, picture的大小(67108864 字节)大于最大大小(8MB = 8 * 1024 *1024 = 8388608)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM