您的位置:首页 > 其它

gdb之线程

2015-08-18 23:39 183 查看
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 1
fi
### $1为传进来的pid, 如果/proc/$pid这个目录不存在则打印进程没有找到的信息
### 当一个进程运行时,在/proc/$pid目录下会生成很多和该进程相关的文件,可以cd 到对应的进程该目录下查看,可以确定,进程执行时一定有该目录
if test ! -r /proc/$1; then
echo "Process $1 not found." 1>&2
exit 1
fi

# 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中打印堆栈的命令bt
backtrace="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 ; then
backtrace="thread apply all bt"
fi
elif 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 ; then
backtrace="thread apply all bt"
fi
fi
### 如果GDB有值则不变,否则赋值为/usr/bin/gdb    类似   GDB=$GDB?$GDB:/usr/bin/gdb
GDB=${GDB:-/usr/bin/gdb}

if $GDB -nx --quiet --batch --readnever > /dev/null 2>&1; then
readnever=--readnever
else
readnever=
fi

# Run GDB, strip out unwanted noise.
$GDB --quiet $readnever -nx /proc/$1/exe $1 <<EOF 2>&1 |
set width 0
set height 0
set pagination no
$backtrace
EOF
/bin/sed -n \
-e 's/^\((gdb) \)*//' \
-e '/^#/p' \
-e '/^Thread/p'


执行 bash -x /usr/bin/pstack $pid 可以看到shell脚本执行的全部过程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: