linux/C++笔记-查看线程详情-pstree/pstack/gdb调试多线程
参考资料
线程的查看以及利用gdb调试多线程 https://blog.csdn.net/zhangye3017/article/details/80382496
GDB教程 http://c.biancheng.net/view/8232.html
next step : https://blog.csdn.net/www_dong/article/details/117374370
简介
本文给出了常用的gdb命令解释,以及使用示例
查看主线程和新线程的关系
pstree -p 主线程id
查看线程栈结构
pstack pid
gdb命令目录
命令 | 用法 | 示例 |
---|---|---|
info threads | 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,操作线程时可使用此ID。 前面有*的是当前调试的线程 |
|
thread ID(1,2,3…) | 切换当前调试的线程为指定ID的线程 | |
break thread_test.c:123 thread all | 普通断点: 在所有线程中相应的行上设置断点 | 1. break pthread_run1 在进入pthread_run1函数时停住 2. break 20 在第20行停住 3. break +/-offset 在当前行号的前/后的offset行停住 4. break filename:linenum 在源文件filename的linenum行处停住。 |
l | list的简写, 查看当前调试的代码上下文, 显示当前context的代码 | |
i b | info breakpoint 的简写查看断点(包括编号) | |
delete | 删除所有断点 delete [breakpoints num] [range...] | delete 1 或 delete 1-10 |
d 1 | 删除指定编号的断点, 这里与delete 1 功能相同,指删除编号1的断点 | |
n | next的简写, 单步调试, 不过当遇到包含调用函数的语句时,无论函数内部包含多少行代码,next 指令都会一步执行完 | 例: next 1 单步执行1行代码 |
s | step的简写, 单步调试,和next的区别: 当 step 命令所执行的代码行中包含函数时,会进入该函数内部,并在函数第一行代码处停止执行 | |
pring val_1 | 打印val_1的值 | |
pring val_1=10 | 修改val_1的值 | |
display val_1 | 与print类似,display 用于调试阶段查看某个变量或表达式的值 区别: 使用 display 命令查看变量或表达式的值,每当程序暂停执行(例如单步执行)时,GDB 调试器都会自动打印 而 print只打印一次 |
|
watch val_1 | 观察val_1的值,当有变化时,停止 | |
clear | 删除所在行的断点 | 删除函数的所有断点: clear list_insert 删除文件:函数的所有断点: clear list.c:list_delet 删除行号的所有断点: clear 12 删除文件:行号的所有断点: clear list.c:12 |
thread apply ID1 ID2 command | 让一个或者多个线程执行GDB命令command | thread apply 2 n 表示让线程2继续执行 |
thread apply all command | 让所有被调试线程执行GDB命令command | |
set scheduler-locking 选项 command | 设置线程是以什么方式来执行命令 | |
set scheduler-locking off | 不锁定任何线程,也就是所有线程都执行,这是默认值 | |
set scheduler-locking on | 只有当前被调试程序会执行 | |
set scheduler-locking on step | 在单步的时候,除了next过一个函数的情况 (熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)以外, 只有当前线程会执行 |
示例
代码
#include <stdio.h>
#include <unistd.h>
#include <thread>
#include <stdlib.h>
#include <string.h>
void my_print1(int i) {
printf("Thread1,ID: %d, value:%d\n",pthread_self(), i);
}
void my_print2(int i) {
printf("Thread2,ID: %d, value:%d\n",pthread_self(), i);
}
void thread_run1() {
int i = 0;
while (1) {
my_print1(i);
i++;
sleep(1);
}
}
void thread_run2() {
int i = 0;
while (1) {
my_print2(i);
i++;
sleep(1);
}
}
int main() {
std::thread t1(thread_run1);
std::thread t2(thread_run1);
printf("main thread\n");
t1.join();
t2.join();
return 0;
}
编译
g++ -g test_thread_gdb.cpp -o test -pthread
命令
ps -aux | grep test
gdb attach pid // pid为你的test的进程id
1. 线程信息
info thread // 查看全部线程
thread 2 // 切换到线程2
bt //
2. 设置断点
b thread_run1 // thread_run1设置断点
i b // 查看当前断点 包括编号
3. 单步调试
n 1
n
next // 以上可用于持续的单步调试
print i // 打印变量 i 的值
4. 打印上下文代码
l
list // 查看代码上下文
5. 变量打印
display i // 之后每次单步调试都会显示i的变量值
6. 观察变量
watch i // 观察i的值,当有变化时,停止
7. 输出到文件
set print elements 0
来关闭掉显示长度限制。再使用print 就可以完全显示变量内容了。
set print repeats 0
对重复的字符进行打印
(gdb) set logging file gdb.log
(gdb) set logging on
(gdb) disassemble
(gdb) set logging off
扩展:https://zhuanlan.zhihu.com/p/594423089
gdb frame
** 参考资料 http://c.biancheng.net/view/8282.html **
任何一个被调用的函数,执行时都会生成一个存储必要信息的栈帧。对于 C、C++ 程序而言,其至少也要包含一个函数,即 main() 主函数,这意味着程序执行时至少会生成一个栈帧。
frame 命令的常用形式有 2 个:
根据栈帧编号或者栈帧地址,选定要查看的栈帧
语法格式如下:
(gdb) frame your_spec
该命令可以将 your_spec 参数指定的栈帧选定为当前栈帧。your_spec 参数的值,常用的指定方法有 3 种:
- 通过栈帧的编号指定。0 为当前被调用函数对应的栈帧号,最大编号的栈帧对应的函数通常就是 main() 主函数;
(gdb) frame 0
- 借助栈帧的地址指定。栈帧地址可以通过 info frame 命令(后续会讲)打印出的信息中看到;
- 通过函数的函数名指定。注意,如果是类似递归函数,其对应多个栈帧的话,通过此方法指定的是编号最小的那个栈帧。
查看程序栈内容
设置好栈帧后,就可以使用命令查看程序栈的内容
info frame 查看当前程序栈信息
info args 查看当前程序栈的参数
info locals 查看当前程序栈的局部变量
info registers 查看寄存器的值
info all-registers 查看寄存器的值(包括浮点寄存器)
info catch 查看程序栈的exception handlers
常见问题
问题1: No symbol table is loaded. Use the "file" command
使用list命令时,报以上错误
编译时加上 -g参数
g++编译
g++ -g test.cpp -o test
bazel编译
bazel build ... --compilation_mode=dbg