gdb 下调试多线程
[ ]The content is recoverd from Wordpress Blog, for more details please check HERE
August 27, 2015
最近接触的工作需要多线程的一些知识才能完成,而以前没有多线程相关知识,现在进行一些简单的学习和研究.本文内容偏向实践,对于想要了解原理的请去看计算机线程方面的资料和书籍.
1.gdb下调试多进程代码的注意事项 The content is recoverd from Wordpress Blog, for more details please check HERE
最近在调试这样一段代码的时候, 由于我不具备任何多线程的知识,导致我在找错误的时候出现了严重的问题,代码如下:
/***********************************************************************
* This is a STC that causes the following error on my test machine:
* NtCreateEvent(lock): 0xC0000035
*
* It tries to use flock() for file locking. It creates a temporary
* file, the uses fork to spawn a number of children. Each child opens
* the file, then repeatedly uses flock to lock and unlock it.
*
* This test was extracted from the APR test suite.
*
* Compile: gcc -Wall -o stc-flock-fork stc-flock-fork.c
***********************************************************************/
#include <sys/types.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define MAX\_ITER 2000
#define CHILDREN 6
/* A temporary file used for flock. */
char tmpfilename[] = "/tmp/flocktstXXXXXX";
/* Fork and use flock to lock and unlock the file repeatedly in the child. */
void make\_child(int trylock, pid\_t *pid)
{
if ((*pid = fork()) < 0) {
perror("fork failed");
exit(1);
}
else if (*pid The content is recoverd from Wordpress Blog, for more details please check [HERE](recover-my-blog) 0) {
int fd2 = open(tmpfilename, O\_RDONLY);
if (fd2 < 0) {
perror("child open");
exit(1);
}
int rc;
int i;
for (i=0; i<MAX\_ITER; ++i) {
do {
rc = flock(fd2, LOCK\_EX);
} while (rc < 0 && errno The content is recoverd from Wordpress Blog, for more details please check [HERE](recover-my-blog) EINTR);
if (rc < 0) {
perror("lock");
exit(1);
}
do {
rc = flock(fd2, LOCK\_UN);
} while (rc < 0 && errno The content is recoverd from Wordpress Blog, for more details please check [HERE](recover-my-blog) EINTR);
if (rc < 0) {
perror("unlock");
exit(1);
}
}
exit(0);
}
}
/* Wait for the child to finish. */
void await\_child(pid\_t pid)
{
pid\_t pstatus;
int exit\_int;
do {
pstatus = waitpid(pid, &exit\_int, WUNTRACED);
} while (pstatus < 0 && errno The content is recoverd from Wordpress Blog, for more details please check [HERE](recover-my-blog) EINTR);
}
int main(int argc, const char * const * argv, const char * const *env)
{
pid\_t child[CHILDREN];
int n;
int fd;
/* Create the temporary file. */
fd = mkstemp(tmpfilename);
if (fd < 0) {
perror("open failed");
exit(1);
}
close(fd);
/* Create the children. */
for (n = 0; n < CHILDREN; n++)
make\_child(0, &child[n]);
/* Wait for them to finish. */
for (n = 0; n < CHILDREN; n++)
await\_child(child[n]);
/* Clean up. */
unlink(tmpfilename);
return 0;
}
调试环境为在Linux下的Wine下运行的MSYS2(32bit) 在MSYS2内运行的gdb, 这段代码在运行到
if ((*pid = fork()) < 0)
这一行的时候 就出现了错误,导致我认为是fork出了问题,不过实际上不是这样的, 在我了解fork的具体操作后,才明白了为什么错误不发生在fork这一行但是却在fork上报错. 下面将会解释上面这个问题 首先要从fork的应用来说.
通过查看 man 2 fork我们可以了解到fork的使用方法和返回值的含义, fork的作用是在某一个程序运行到fork()这里的时候 ,新开一个线程,这个线程与原来的线程独立,不过线程内的变量值等信息都与原来的线程一致, (他们不共用内存地址) , 这个线程就称为子进程, 原来的进程就叫做父进程. fork()的返回值为一个进程号, 返回的进程号为当前父进程的子进程的进程号,子进程的这个返回值为0(注意子进程是相对父进程而言) .
另外 在使用gdb调试的时候 ,如果出现了多线程的代码 ,那么不进行任何设置的情况下,gdb不会阻止(控制)子进程的执行,也就是说,就算你在用step in 或者next 对代码进行调试 ,但是遇到了 fork()之后 ,子进程会自动执行从 fork开始到代码末尾的所有语句,不会受gdb控制, 因而 ,如果是在fork之后的代码出了问题, 那么在用没有配置的gdb进行单步调试的时候,代码会在父进程fork之后报错,这样就不便于定位错误的位置. 下面用一段代码来验证一下我的这个说法:
/*************************************************************************
> File Name: fork\_test.c
> Author: VOID\_133
> ###################
> Mail: ###################
> Created Time: Thu 27 Aug 2015 05:46:19 PM CST
************************************************************************/
#include<stdio.h>
#include <unistd.h>
int main(void)
{
printf("Now will fork a process from Parentn");
int a = fork();
printf("Forked PID is %dn",a);
return 0;
}
上面这段代码做的事情很简单 ,我就不再多解释 ,将这段代码用 gdb -g -O0 编译之后 在gdb下调试
首先使用 start > tmpfile来启动调试进程, 这样所有的输出都会被重定向到一个文件 ,不会因为gdb的原因导致输出延迟, 然后 使用 next指令 执行完 fork这一行, 然后立即查看tmpfile的内容 你会发现 tmpfile的内容为:
Now will fork a process from Parent
Forked PID is 0
我们明明还没有执行到下一个printf,但是却输出了forked PID is 0 , 通过上文我们知道了 PID为0 意味着打印这个信息的进程为子进程 这也就说明了 gdb没有阻拦子进程的执行 ,让子进程执行完毕了,但是父进程依旧在被gdb控制,因此,如果是fork之后的代码出现了问题,由于未经配置的gdb不能监视子进程,子进程自动执行到出错的地方然后扔出错误信息,但是我们在gdb上看到的现象却是执行fork的时候报错了,这样就导致了判断不出来错误的所在.
2.用gdb调试多线程 The content is recoverd from Wordpress Blog, for more details please check HERE
那么遇到多线程的代码的时候应该如何调试呢, 官方文档给出了三种方法 https://sourceware.org/gdb/onlinedocs/gdb/Forks.html
相关信息已经有很好的列出,在这里就不多解释了. 不过需要注意一点 ,就是在无法使用 follow-fork-mode 和 follow-exec-mode的时候 (比如kernel版本不对,平台不对之类的问题) 那么就要在代码里插入sleep来进行调试, 这个方法是通用的一个方法
3.依旧存在疑惑的地方 The content is recoverd from Wordpress Blog, for more details please check HERE
上面那段代码 ,如果直接执行(将信息输出到stdout上) 那么结果为:
Now will fork a process from Parent
Forked PID is 22873
Forked PID is 0
这样三行内容 而如果重定向到文件或者管道的话 ,内容则变为:
Now will fork a process from Parent
Forked PID is 22882
Now will fork a process from Parent
Forked PID is 0
四行, 具体一为什么会出现这一现象的原因有待深入学习fork和重定向的相关知识
Linux, Threads and Process, wine, 计算机系统原理 C. Linux, kernel, Laravel, PHP, Python, Shell, Web, wine
Historical Comments
Post navigation ————— NEXT
mingw 和 MSYS 的区别 PREVIOUS CSAPP Linking 学习总结