gdb之线程

来源:互联网 发布:java static是在类加载 编辑:程序博客网 时间:2024/06/05 17:55
gdb如何调试多线程呢??之前写了一个同步异步的例子,因为对异步来说通常采用的机制有两种,一是轮询,就是说每隔一段时间就过来询问一下,如果有就调用,对于这个机制,epoll比较适合,另一个就是回调,也就是说当我准备好了后直接通知你,基于这个,今天就顺着昨天的例子增加一些函数来理解异步回调,同时学习基础的gdb调试多线程。
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <pthread.h>typedef void (*CBK_FUN)();struct CBK_CALL{    void *data;    CBK_FUN fn;};void printSync(const char *str){    sleep(1);   // assume this will do lots of thing    puts(str);    sleep(1);   // assume this also will do lots of thing}void *thread_proc(void *data){    struct CBK_CALL *cbk_call = data;    sleep(1);    puts((char *)(cbk_call->data));    sleep(1);    cbk_call->fn(data);    return (void *)0;}pthread_t tid;void printAsync(const char *str, CBK_FUN fn){    struct CBK_CALL cbk_call = {(void *)str, fn};    pthread_create(&tid, NULL, thread_proc, (void *)(&cbk_call));    }void callback(){    puts("After Sync Call");}int main(){    printSync("I am Sync call");    printAsync("I am a Async call", callback);    puts("end");    pthread_join(tid, NULL);    return 0;}
程序比较简单,基本思想就是先执行先同步输出, 接着printAsync会异步执行,且执行完后会回调callback函数,可是由于粗心大意,编译运行发现一直不成功,没办法,上gdb
# gdb a.out
(gdb) b printAsync             // 函数断点, 输入print后按tab和Linux效果一样
Breakpoint 1 at 0x400696: file error.c, line 31.
(gdb) s                              // 进入函数
(gdb) n                            
(gdb) p cbk_call                // 打印cbk_call的值,<和预期的一样>, 执行 p cbk_call.data,   p (char *)cbk_call.data可以打印更多信息
$1 = {data = 0x400837, fn = 0x4006c3 <callback>}
(gdb) p cbk_call.fn()         // gdb 直接打印函数执行的结果,    <和预期的一样>
After Sync Call
$2 = void
(gdb) p &cbk_call     
$3 = (struct CBK_CALL *) 0x7fffffffe590
(gdb) n                             // 下一步执行就会创建一个新的线程
[New Thread 0x7ffff7fe9710 (LWP 1716)]
(gdb) info thread             // 打印当前进程的线程信息, <注,Thread前面标记*表示当前gdb正在调试>
  2 Thread 0x7ffff7fe9710 (LWP 1716)  0x0000003c0f2e1501 in clone () from /lib64/libc.so.6
* 1 Thread 0x7ffff7feb700 (LWP 1702)  printAsync (str=0x400837 "I am a Async call", fn=0x4006c3 <callback>) at error.c:33
(gdb) thread 2                // 切换到标记为2的线程上去执行,
[Switching to thread 2 (Thread 0x7ffff7fe9710 (LWP 1716))]#0  0x0000003c0f2e1501 in clone () from /lib64/libc.so.6
(gdb) bt                         // 打印函数调用堆栈
#0  0x0000003c0f2e1501 in clone () from /lib64/libc.so.6
#1  0x0000003c0f607710 in ?? () from /lib64/libpthread.so.0
#2  0x00007ffff7fe9710 in ?? ()
#3  0x0000000000000000 in ?? ()
(gdb) thread apply all bt                // 打印所以线程的函数调用堆栈
Thread 2 (Thread 0x7fb0d2a47710 (LWP 2136)):
#0  0x0000003c0f2e1501 in clone () from /lib64/libc.so.6
#1  0x0000003c0f607710 in ?? () from /lib64/libpthread.so.0
#2  0x00007ffff7fe9710 in ?? ()
#3  0x0000000000000000 in ?? ()

Thread 1 (Thread 0x7fb0d2a49700 (LWP 2135)):
#0  0x0000003c0f60803d in pthread_join () from /lib64/libpthread.so.0
#1  0x00000000004006cc in main ()
(gdb) s
(gdb) s                          // 进入到线程<就是函数>
(gdb) p data                 // 打印传进来的参数,    并没有发生改变,还没有找到错误的地方???
$4 = (void *) 0x7fffffffe590
(gdb) p *(struct CBK_CALL*)data              // 期望和之前 p cbk_call的内容一样, 结果发现不同, 说明参数传进来后所指向地址的内容发生了变化
$6 = {data = 0x400520, fn = 0x7fffffffe690}
(gdb) q                        // 问题找到了, 退出调试
为什么会发生变化呢, 原来是因为main函数调用printAsync函数,而在printAsync函数的局部变量cbk_call在printAsync函数返回后由于栈空间被释放了所以值就不在了,而拷贝传给线程的只是一个指针,指针指向的内容却已经被重写了。
解决办法, 将cbk_call声明称全局变量.


pstack脚本分析
#!/bin/sh
### 参数判断, 如果不是 #pstack pid 形式则打印使用方式并退出if test $# -ne 1; then    echo "Usage: `basename $0 .sh` <process-id>" 1>&2    exit 1fi
### $1为传进来的pid, 如果/proc/$pid这个目录不存在则打印进程没有找到的信息
### 当一个进程运行时,在/proc/$pid目录下会生成很多和该进程相关的文件,可以cd 到对应的进程该目录下查看,可以确定,进程执行时一定有该目录if test ! -r /proc/$1; then    echo "Process $1 not found." 1>&2    exit 1fi# GDB doesn't allow "thread apply all bt" when the process isn't# threaded; need to peek at the process to determine if that or the# simpler "bt" should be used.
### gdb中打印堆栈的命令btbacktrace="bt"
### 如果是多线程可以看到/proc/$pid/task目录下有多个目录,且一个线程对应一个目录
### 就是判断这一时刻进程中是否有多个线程在执行来决定是否需要将bt替换为"thread apply all bt"if test -d /proc/$1/task ; then    # Newer kernel; has a task/ directory.    if test `/bin/ls /proc/$1/task | /usr/bin/wc -l` -gt 1 2>/dev/null ; thenbacktrace="thread apply all bt"    fielif test -f /proc/$1/maps ; then    # Older kernel; go by it loading libpthread.
    ### 同样,但此刻有多线程执行时,可以grep到调用了libpthread线程库    if /bin/grep -e libpthread /proc/$1/maps > /dev/null 2>&1 ; thenbacktrace="thread apply all bt"    fifi
### 如果GDB有值则不变,否则赋值为/usr/bin/gdb    类似   GDB=$GDB?$GDB:/usr/bin/gdbGDB=${GDB:-/usr/bin/gdb}if $GDB -nx --quiet --batch --readnever > /dev/null 2>&1; then    readnever=--readneverelse    readnever=fi# Run GDB, strip out unwanted noise.$GDB --quiet $readnever -nx /proc/$1/exe $1 <<EOF 2>&1 | set width 0set height 0set pagination no$backtraceEOF/bin/sed -n \    -e 's/^\((gdb) \)*//' \    -e '/^#/p' \    -e '/^Thread/p'

执行 bash -x /usr/bin/pstack $pid 可以看到shell脚本执行的全部过程

0 0
原创粉丝点击