【问题标题】:can run in gdb, segmentation fault when run directly可以在gdb中运行,直接运行时出现分段错误
【发布时间】:2013-03-12 16:59:52
【问题描述】:

我的程序正常运行时出现分段错误。但是,如果我使用 gdb run,它就可以正常工作。此外,当我在philo函数中增加睡眠时间时,分段错误的比率会增加。我正在使用 ubuntu 12.04。任何帮助或指点表示赞赏。这是我的代码

#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sched.h>

#include <signal.h>
#include <sys/wait.h>
#include <time.h>
#include <semaphore.h>
#include <errno.h>

#define STACKSIZE 10000
#define NUMPROCS 5
#define ROUNDS 10

int ph[NUMPROCS];
//cs[i] is the chopstick between philosopher i and i+1
sem_t cs[NUMPROCS], dead;

int philo() {
    int i = 0;
    int cpid = getpid();
    int phno;

    for (i=0; i<NUMPROCS; i++)
        if(ph[i] == cpid) phno = i;

    for (i=0; i < ROUNDS ; i++){
        // Add your entry protocol here
        if (sem_wait(&dead) != 0) {
            perror(NULL);
            return 1;
        }
        if (sem_wait(&cs[phno]) != 0) {
            perror(NULL);
            return 1;
        }
        if (sem_wait(&cs[(phno-1+NUMPROCS) % NUMPROCS]) != 0){
            perror(NULL);
            return 1;
        }

        // Start of critical section -- simulation of slow n++
        int sleeptime = 20000 + rand()%50000;
        printf("philosopher %d is eating by chopsticks %d and %d\n",phno,phno,(phno-1+NUMPROCS)%NUMPROCS);
        usleep(sleeptime) ;
        // End of critical section

        // Add your exit protocol here
        if (sem_post(&dead) != 0) {
            perror(NULL);
            return 1;
        }
        if (sem_post(&cs[phno]) != 0) {
            perror(NULL);
            return 1;
        }
        if (sem_post(&cs[(phno-1+NUMPROCS) % NUMPROCS]) != 0){
            perror(NULL);
            return 1;
        }
    }
    return 0;
}

int main( int argc, char ** argv){
    int i;
    void* stack[NUMPROCS];
    srand(time(NULL));

    //initialize semaphores
    for (i=0; i<NUMPROCS; i++) {
        if (sem_init(&cs[i],1,1) != 0){
            perror(NULL);
            return 1;
        }
    }
    if (sem_init(&dead,1,4) != 0){
        perror(NULL);
        return 1;
    }

    for (i = 0; i < NUMPROCS; i++){
        stack[i] = malloc(STACKSIZE) ;
        if ( stack[i] == NULL ) {
            printf("Error allocating memory\n") ;
            exit(1) ;
        }

        // create a child that shares the data segment
        ph[i] = clone(philo, stack[i]+STACKSIZE-1, CLONE_VM|SIGCHLD, NULL) ;
        if (ph[i] < 0) {
            perror(NULL) ;
            return 1;
        }
    }

    for (i=0; i < NUMPROCS; i++) wait(NULL);
    for (i=0; i < NUMPROCS; i++) free(stack[i]);

    return 0 ;
}

【问题讨论】:

  • 如果你用-static构建程序,它还会崩溃吗?在通过 PLT 的第一次调用(即gettpid())期间,我观察到子进程中的 glibc 动态加载器符号解析崩溃了(即gettpid()

标签: gdb segmentation-fault clone


【解决方案1】:

典型的 Heisenbug:如果你看它,它就会消失。根据我的经验,仅在 gdb 之外获得 segv 或反之亦然是使用未初始化内存或依赖实际指针地址的标志。通常运行valgrind 在检测这些方面非常准确。不幸的是(我的)valgrind 无法在pthread 上下文之外处理您的clone

目视检查表明这不是内存问题。只有堆栈分配在堆上,它们的使用看起来没问题。除非你用 void * 指针对待它们,然后向它添加一些东西,这在标准 C(GNU 扩展)中是不允许的。正确的做法是使用 char *,但 GNU 扩展可以满足您的需求。

从堆栈的顶部地址中减去 1 可能不是必需的,并且可能会在 clone 的简单实现中导致对齐错误,但我再次认为这不是问题,因为 clone 很可能会对齐再次堆叠顶部。诚然,clone 的手册页对地址的确切位置不是很清楚:“内存空间的最高地址”。

只是等待孩子的状态改变并假设它死了有点草率,然后拿走它的堆栈可能会导致分段错误,但我再次认为这不是问题,因为你可能并不疯狂向您的哲学家发送信号。

如果我运行你的应用程序,哲学家可以在 gdb 内外不受干扰地完成他们的晚餐,所以以下是猜测。让我们将克隆哲学家的父进程称为“桌子”。克隆哲学家后,该表将返回的 pid 存储在 ph 中,例如将该数字分配给椅子。哲学家做的第一件事就是寻找他的椅子。如果他没有找到他的椅子,他将有一个未初始化的phno,用于访问他的信号量。现在这很可能会导致分段错误。

该实现假设在哲学家开始之前将控制权返回给表。我在手册页中找不到这样的保证,我实际上希望这不是真的。克隆接口也有可能将进程 ID 放置在子进程和父进程共享的内存中,这表明这是一个公认的问题(参见参数 pidctid)。如果使用了这些,pid 将在表或刚刚克隆的哲学家获得控制之前写入。

这个错误很有可能解释了gdb 内部和外部之间的差异,因为gdb 非常了解在其监督下生成的进程,并且可能会以不同于操作系统的方式对待它们。

或者,您可以为表分配一个信号量。所以没有人坐在桌子旁,直到桌子说出来,显然是在它分配了所有椅子之后。这将更好地使用信号量dead

顺便说一句。您当然完全清楚,您的解决方案的设置确实允许所有哲学家最终每个人都有一个叉子(嗯,筷子)并饿死等待另一个的情况。幸运的是,发生这种情况的机会非常渺茫。

【讨论】:

    【解决方案2】:
    ph[i] = clone(philo, stack[i]+STACKSIZE-1, CLONE_VM|SIGCHLD, NULL) ;
    

    这会创建一个执行线程,而 glibc 对此一无所知。因此,glibc 不会创建它需要的任何特定于线程的内部结构,例如动态符号解析。

    通过这样的设置,从您的philo 函数调用任何 glibc 函数会调用未定义的行为,并且您有时会崩溃(因为动态加载器将使用主线程的私有数据来执行符号解析,并且因为加载器假定每个线程有自己的私有区域,但您违反了这个假设,创建了clones,它在“glibc 背后”共享单个私有区域。

    如果您查看核心转储,实际崩溃很有可能发生在 ld.so,这将证实我的猜测。

    永远不要直接使用clone(除非您知道自己在做什么)。请改用pthread_create

    这是我在刚刚得到的核心中看到的(这正是我描述的问题):

    Program terminated with signal 4, Illegal instruction.
    #0  _dl_x86_64_restore_sse () at ../sysdeps/x86_64/dl-trampoline.S:239
    239             vmovdqa %fs:RTLD_SAVESPACE_SSE+0*YMM_SIZE, %ymm0
    (gdb) bt
    #0  _dl_x86_64_restore_sse () at ../sysdeps/x86_64/dl-trampoline.S:239
    #1  0x00007fb694e1dc45 in _dl_fixup (l=<optimized out>, reloc_arg=<optimized out>) at ../elf/dl-runtime.c:127
    #2  0x00007fb694e0dee5 in _dl_runtime_resolve () at ../sysdeps/x86_64/dl-trampoline.S:42
    #3  0x00000000004009ec in philo ()
    #4  0x00007fb69486669d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:112
    

    【讨论】:

      猜你喜欢
      • 2013-05-10
      • 2011-09-26
      • 2017-02-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多