gdb控制程序行为
-
环境变量
(gdb) set environment VAR=value
多线程
相关命令
-
查看线程列表:在
gdb
中可以使用以下命令查看当前所有线程的列表:(gdb) info threads
这将列出所有线程及其状态,包括它们的 LWP ID 和线程标识符。
-
切换到特定线程:如果你想切换调试上下文到某个特定的线程,可以使用:
(gdb) thread <thread_number>
其中
<thread_number>
是gdb
为线程分配的编号(可以从info threads
命令中获取)。
-
查看所有线程的调用栈(批量查看每个线程的栈帧):
(gdb) thread apply all bt
这会列出所有线程的调用栈信息,方便你快速发现哪个线程挂掉或崩溃。
使用 pstack 工具查看线程栈
如果你只想快速查看某个进程的所有线程的栈,而不需要全面调试,可以使用 pstack 工具(需要安装)。它会显示所有线程的调用栈(包括 LWP ID)。
通过 ps
或 top
查看线程状态
在 gdb
之外,你也可以通过系统命令查看线程的状态:
-
使用
ps
查看线程状态:bash
Copy
ps -eLf | grep <PID>
该命令会列出指定进程的所有线程,并显示线程的状态、PID、LWP 等信息。你可以通过
S
列查看线程的状态:R
:正在运行。S
:休眠中。D
:不可中断的睡眠(通常是 I/O 操作)。Z
:僵尸线程。
-
使用
top
查看线程状态:启动
top
命令并按H
键,可以查看某个进程的所有线程及其状态。bash
Copy
top -H -p <PID>
这将显示该进程的所有线程及其 CPU 使用率、状态等信息。
总结
- 使用
gdb
中的info threads
命令可以查看所有线程的状态。 - 使用
thread <id>
切换到特定线程,使用backtrace
查看其调用栈。 - 使用
thread apply all bt
可以一次性查看所有线程的调用栈,快速定位问题线程。 - 通过调用栈可以识别线程是否因段错误、死锁、或其他系统调用而挂起。
- 使用系统工具如
ps
或top
也可以查看线程的状态,辅助判断哪个线程可能挂掉了。
信号
SIGABRT
信号通常在以下情景下触发:
- 显式调用
abort()
。 - 未通过
assert
检查。 - 内存管理错误(如非法释放内存或越界访问)。
- 未捕获的异常(在 C++ 中,抛出异常但未捕获时会调用
std::terminate()
,进而触发SIGABRT
)。 - 手动发送
SIGABRT
信号。
情境问题
如何知道一个文件被哪些进程的哪些函数读取过?
要知道一个文件被哪些进程的哪些函数读取过,这个问题涉及两部分:
- 哪些进程读取了该文件:这是系统级别的问题,通过跟踪文件系统调用可以获取相关信息。
- 哪些函数读取了该文件:这是进程内部的问题,需要了解进程内的调用栈。
要实现这两部分的监控,通常需要结合系统工具和调试工具来完成。以下是几种常见的方式:
1. 使用 strace
跟踪文件访问系统调用
strace
是 Linux 系统中的一个工具,可以跟踪进程的系统调用,包括 open()
, read()
, write()
等文件操作的系统调用。通过 strace
,你可以知道某个文件被哪些进程读取过。
步骤:
-
跟踪所有进程对文件的访问:
你可以通过
strace
跟踪所有进程的文件系统调用,并过滤出对特定文件的访问。例如,假设你想跟踪/path/to/file
文件的访问,可以使用以下命令:sudo strace -f -e trace=open,read,write -p $(pgrep -d, .) 2>&1 | grep "/path/to/file"
解释:
-f
:跟踪子进程。-e trace=open,read,write
:只跟踪open()
,read()
,write()
系统调用。-p $(pgrep -d, .)
:跟踪所有进程(pgrep -d, .
会输出所有进程的 PID)。
-
跟踪特定进程的文件访问:
如果你知道某个进程的 PID,你可以使用
strace
直接跟踪该进程:sudo strace -f -e trace=open,read,write -p <PID>
这将显示该进程打开、读取、写入的文件。你可以结合
grep
过滤出你感兴趣的文件。 -
输出示例:
假设有个进程在读取
/path/to/file
,strace
的输出可能如下:open("/path/to/file", O_RDONLY) = 3 read(3, "file contents...", 1024) = 1024
这表明进程通过文件描述符 3 打开了文件
/path/to/file
并读取了 1024 字节的内容。
总结
strace
:可以用来跟踪进程对文件的系统调用,知道哪些进程访问了文件。lsof
:可以实时查看哪些进程打开了文件。auditd
:可以对文件访问进行审计,记录哪些进程访问了文件。gdb
或ptrace
:可以用来跟踪进程内部的函数调用栈,了解哪些函数读取了文件。LD_PRELOAD
:可以通过插桩方式重载文件操作函数,捕捉调用栈信息。
通过结合这些工具,你可以追踪文件访问的进程和函数调用栈。
使用 lsof
查看文件被哪些进程打开
lsof
是一个用于列出打开文件的工具。你可以使用它来查看某个文件当前被哪些进程打开。
步骤:
lsof /path/to/file
这将输出所有当前打开该文件的进程信息,包括进程 ID、进程名称、打开的文件描述符等。例如:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
cat 1234 user 3r REG 8,1 1234 5678 /path/to/file
这里,cat
进程(PID 1234)以只读模式(3r
)打开了 /path/to/file
。
3. 使用 auditd
进行文件访问审计
auditd
是 Linux 的审计框架,可以对文件访问进行详细的日志记录,包括进程访问文件的情况。
步骤:
-
安装
auditd
:如果未安装
auditd
,可以使用以下命令安装:sudo apt install auditd
-
添加审计规则:
使用
auditctl
添加审计规则,跟踪某个文件的读取:sudo auditctl -w /path/to/file -p r -k file_read
解释:
-w /path/to/file
:监控文件/path/to/file
。-p r
:监控读取操作。-k file_read
:为此规则设置一个标识符file_read
。
-
查看审计日志:
审计日志会存储在
/var/log/audit/audit.log
文件中。你可以使用ausearch
命令查看与文件访问相关的日志:sudo ausearch -k file_read
这将显示哪些进程读取了
/path/to/file
,包括详细的进程信息。
4. 使用 gdb
或 ptrace
获取函数调用栈
strace
和 auditd
可以告诉你哪些进程访问了某个文件,但它们无法告诉你进程内部的函数调用栈。如果你想知道进程内部的哪些函数读取了该文件,则需要使用调试器(如 gdb
)或 ptrace
进行更深入的分析。
步骤:
-
使用
gdb
附加到进程:假设你已经知道某个进程(PID 为
<PID>
)在访问文件,你可以使用gdb
附加到该进程并设置断点在open()
,read()
, 或fopen()
等文件操作函数上:gdb -p <PID>
-
设置断点:
在
gdb
中设置断点,例如你想捕捉open()
系统调用:(gdb) break open
如果使用 C 标准库的
fopen()
函数:(gdb) break fopen
-
继续执行并捕捉调用栈:
继续执行进程,直到遇到断点:
(gdb) continue
当进程命中断点时,你可以使用
backtrace
命令查看当前的函数调用栈:(gdb) backtrace
这将显示当前函数及其父函数,帮助你确定哪些函数调用了
open()
或read()
。 -
输出示例:
假设调用栈如下:
#0 open (filename="/path/to/file", flags=O_RDONLY) at open.c:34 #1 0x00007ffff7abc123 in read_file() at file_reader.c:54 #2 0x00007ffff7abc456 in main() at main.c:12
这表示
main()
调用了read_file()
,而read_file()
又调用了open()
打开了/path/to/file
。
5. 自定义动态库插桩 (LD_PRELOAD)
可以通过插桩(interpose)标准库函数 open
, read
, fopen
等,来记录下文件访问的详细信息。你可以编写一个自定义的动态库,重载这些函数,在函数被调用时记录调用栈或其他信息。
步骤:
-
编写一个 C 文件,实现你要重载的函数:
#define _GNU_SOURCE #include <dlfcn.h> #include <stdio.h> #include <unistd.h> #include <execinfo.h> typedef int (*orig_open_f_type)(const char *pathname, int flags); int open(const char *pathname, int flags) { // 打印调用栈 void *buffer[10]; int nptrs = backtrace(buffer, 10); backtrace_symbols_fd(buffer, nptrs, STDERR_FILENO); // 调用原始的 open 函数 orig_open_f_type orig_open; orig_open = (orig_open_f_type)dlsym(RTLD_NEXT, "open"); return orig_open(pathname, flags); }
-
编译成共享库:
gcc -shared -fPIC -o myopen.so myopen.c -ldl
-
使用
LD_PRELOAD
运行目标程序:LD_PRELOAD=./myopen.so ./your_program
这样,每次
open
函数被调用时,你的自定义函数会被调用,并打印出调用栈。
gdb假设没有加载符号表 是否能断点一些特定函数呢?
在 GDB 中,如果程序没有加载符号表(例如,编译时未使用 -g
选项),调试器将无法识别函数名或变量名。这是因为符号表包含了可执行文件中函数的名称、变量的名称及其对应的内存地址等调试信息。没有符号表的情况下,GDB 只能看到程序的机器代码和内存地址,而无法直接断点到特定的函数名。
然而,即使没有符号表,仍然有一些方法可以对特定的函数或代码位置设置断点,具体方法取决于情况:
1. 使用内存地址设置断点
如果你知道目标函数的内存地址,你可以直接在该地址设置断点。即使没有符号表,GDB 仍然可以在指定的内存地址处暂停程序执行。
步骤:
-
启动 GDB 并加载程序:
gdb ./your_program
-
运行程序或使用
start
命令让程序执行到某个位置。 -
如果你知道目标函数的地址(例如,通过反汇编工具或
nm
命令获取),你可以直接在该地址设置断点。例如:break *0x4005d0
这里
0x4005d0
是目标函数的内存地址。*
表示在该地址处设置断点。 -
运行程序,GDB 会在该地址处暂停。
如何获取函数地址:
-
你可以使用
nm
或objdump
等工具来列出程序中的函数地址。例如:nm ./your_program | grep some_function
如果存在
some_function
,它会显示类似以下的输出:00000000004005d0 T some_function
其中
0x4005d0
是some_function
的地址。 -
另一种方法是使用
objdump
来反汇编可执行文件并找到目标函数:objdump -d ./your_program | less
然后在反汇编输出中查找你感兴趣的函数地址。
2. 使用汇编指令设置断点
如果你知道某个函数的汇编指令或知道特定的汇编指令模式(如函数的入口处通常是 push
或 mov
指令),你可以通过反汇编来找到这个函数的地址,并在该地址设置断点。
步骤:
-
反汇编程序的入口点或特定代码段:
disassemble main
如果没有符号表,GDB 也可以反汇编出汇编指令(即使没有函数名),例如:
disassemble 0x4005d0
-
找到感兴趣的汇编指令后,设置断点:
break *0x4005d0
3. 使用 GDB 的自动分析功能
GDB 有一些自动分析功能,即使没有符号表,它也可以尝试自动获取某些函数的地址。例如,info functions
可以列出程序中找到的所有函数地址(尽管没有符号名)。
info functions
这将列出程序中的所有已知函数,即使没有符号表,它也可能找到一些与标准库函数相关的符号。
4. 使用共享库中函数的符号
如果你调试的程序动态链接了共享库,即使主程序没有符号表,GDB 仍然可以识别共享库中的符号。例如,如果程序使用了标准 C 库函数 printf
,你可以使用以下命令为 printf
设置断点:
break printf
因为共享库通常会加载自己的符号表,因此 GDB 可以识别这些库中的函数。
5. 使用条件断点
如果你不知道函数的内存地址,也没有符号表,但你知道某个特定位置的条件(比如某个寄存器的值或内存内容),你可以使用条件断点来捕捉该条件。例如,如果你知道某个函数的返回地址或特定的寄存器值,可以这样设置断点:
break *0x4005d0 if $rax == 5
这表示在地址 0x4005d0
处设置断点,但仅当寄存器 rax
的值为 5
时才触发断点。
小结
- 没有符号表:GDB 无法直接通过函数名设置断点,但你可以使用目标函数的内存地址来设置断点。
- 获取地址:可以通过工具如
nm
,objdump
,或者使用 GDB 的反汇编功能来获取函数的入口地址。 - 共享库函数:即使没有符号表,动态链接库中的符号仍然可以被识别,允许为标准库函数设置断点。
例如,通过命令 break *<address>
,你可以在任何特定的内存地址处设置断点。
假如一个线程因为抛了一个异常终止了 如何能知道哪个函数抛异常呢?在gdb中
当一个线程因为抛出异常而终止时,你可以使用 GDB 来调试并确定哪个函数抛出了异常。GDB 提供了多种工具来捕获异常和检查堆栈,从而帮助你找出异常的来源。
步骤概述:
- 设置断点捕获异常抛出。
- 使用回溯(backtrace)查看调用栈,找出哪个函数抛出了异常。
1. 在 GDB 中捕获异常抛出
GDB 提供了一个特殊的命令可以捕获 C++ 异常抛出。当有异常抛出时,程序会暂停,这样你就可以在抛出异常的时刻检查调用栈。
你可以使用以下命令捕获所有 C++ 异常的抛出:
catch throw
这个命令会告诉 GDB,在异常被抛出的时候暂停程序的执行。
捕获异常的具体步骤:
-
启动 GDB,并加载你的程序:
gdb ./your_program
-
设置捕捉异常的断点:
catch throw
-
运行程序:
run
-
当程序抛出异常时,GDB 会暂停,并显示类似以下的信息:
Catchpoint 1 (throw)
2. 查看调用栈(Backtrace)
当程序由于抛出异常而暂停时,你可以使用 backtrace
(或简写 bt
)命令查看调用栈,找出哪个函数抛出了异常。
backtrace
这将显示当前的调用栈,列出从最底层到最顶层的函数调用顺序。通过这个调用栈,你可以找到抛出异常的函数。
示例:
假设你运行了程序并且捕捉到了异常抛出:
(gdb) catch throw
Catchpoint 1 (throw)
(gdb) run
Starting program: ./your_program
Catchpoint 1 (throw), __cxa_throw () at /build/glibc-23423....
现在你可以使用 backtrace
来查看调用栈:
(gdb) backtrace
#0 __cxa_throw () at /build/glibc-23423....
#1 0x00000000004015ae in some_function() at main.cpp:10
#2 0x0000000000401234 in main () at main.cpp:20
如上所示,some_function()
函数在 main.cpp
第 10 行抛出了异常。
3. 捕获异常处理开始(可选)
除了捕获异常的抛出,你还可以捕获异常的处理过程(即捕获 catch
处理异常的时刻),方法是使用:
catch catch
这会在异常被捕获处理的时候暂停程序。如果你想调试异常处理过程,这个命令非常有用。
4. 其他有用的 GDB 命令
info threads
:查看所有线程的状态。如果你调试的是多线程程序,可以使用这个命令来查看线程情况。thread apply all bt
:如果程序有多个线程,这个命令可以显示所有线程的调用栈,帮助你确定哪个线程抛出了异常。
5. 使用调试符号
为了确保你能够看到函数名称、行号等详细信息,建议在编译程序时启用调试符号(使用 -g
选项)。例如:
g++ -g -o your_program your_program.cpp
如果没有调试符号,GDB 可能只会显示内存地址,而不会显示函数名称和代码行号。
6. 通过反汇编查看异常(无调试符号时)
如果你调试的程序没有调试符号,调用栈可能只会显示内存地址而不是函数名。在这种情况下,你可以使用 GDB 的 disassemble
命令来查看抛出异常的汇编代码,并手动确定抛出异常的位置。
例如:
disassemble 0x4005d0
这会显示内存地址 0x4005d0
处的汇编指令。你可以通过反汇编和代码分析,推测哪个函数抛出了异常。
总结
- 捕获异常:使用 GDB 的
catch throw
命令捕获所有 C++ 异常。 - 查看调用栈:使用
backtrace
命令查看调用栈,找到抛出异常的函数。 - 多线程支持:如果你的程序是多线程的,使用
info threads
和thread apply all bt
来查看各线程的状态和调用栈。 - 调试符号:确保程序在编译时加入了调试符号(
-g
),这将帮助你在 GDB 中看到更多的调试信息。
通过这些步骤,你应该能够在 GDB 中轻松找到哪个函数抛出了异常,即使是在多线程环境下。
我在第二个栈帧 我能拿到什么上下文呢?
小结
当你进入某个特定的栈帧(例如第二个栈帧)时,能够获取的上下文信息包括:
- 局部变量和函数参数:使用
info locals
和info args
。 - 调用栈:使用
backtrace
或bt
查看调用栈的上下文。 - 源代码:使用
list
或frame
查看当前帧对应的源代码。 - 寄存器状态:使用
info registers
查看寄存器的值。 - 内存内容:使用
x
命令查看任意内存地址的内容。 - 全局变量:使用
print
或info variables
查看全局变量的值。 - 线程信息:使用
info threads
查看当前线程的状态。 - 返回地址:使用
info frame
查看当前帧的返回地址。 - 动态类型:使用
print
或ptype
查看 C++ 对象的动态类型。
如何检索一个so里 是否定义了某种符号呢?
要检索一个共享库(.so
文件)中是否定义了某个符号(函数、变量等),你可以使用以下几种工具:
1. nm
命令
nm
是一个常用的工具,用来列出对象文件中的符号。你可以使用它来查看 .so
文件中定义的符号。
使用方法:
nm -D <your_library.so> | grep <symbol_name>
-D
:表示显示动态符号(即只显示共享库中的符号)。grep <symbol_name>
:用于过滤你想要查找的符号。
示例:
nm -D libexample.so | grep my_function
如果库中定义了 my_function
,则会显示类似以下的输出:
0000000000001234 T my_function
其中:
T
表示该符号在库中定义(T
表示符号在代码段中)。U
表示该符号是未定义的(即该共享库引用了该符号,但并未定义它)。- 其他符号类型也存在(如
B
表示 BSS 段中的符号,D
表示数据段中的符号等)。
2. readelf
命令
readelf
是另一个工具,可以显示 ELF 文件的详细信息(ELF 是 Linux 和其他类 Unix 系统中常用的可执行文件格式)。
使用方法:
readelf -Ws <your_library.so> | grep <symbol_name>
-W
:表示不折行输出,显示完整的符号信息。-s
:表示显示符号表。
示例:
readelf -Ws libexample.so | grep my_function
同样,如果库中定义了 my_function
,你会看到类似下面的输出:
Num: Value Size Type Bind Vis Ndx Name
33: 0000000000012345 100 FUNC GLOBAL DEFAULT 12 my_function
FUNC
表示这是一个函数。GLOBAL
表示这是一个全局符号。DEFAULT
表示默认可见性。
3. objdump
命令
objdump
是另一个用于分析二进制文件的工具,也可以列出共享库中的符号。
使用方法:
objdump -T <your_library.so> | grep <symbol_name>
-T
:表示显示动态符号表。
示例:
objdump -T libexample.so | grep my_function
输出类似于:
0000000000012345 g DF .text 00000000000000a4 Base my_function
g
表示全局符号。DF
表示这是一个函数符号。.text
表示函数位于代码段。
4. ldd
或 ldconfig
如果你不确定是否包含在某个共享库中,ldd
或 ldconfig
可以帮助你查看共享库的依赖关系,确认共享库实际被加载。
例如,使用 ldd
可以列出可执行文件所依赖的共享库:
ldd <your_executable>
这不会直接列出符号,但可以帮助你确认共享库是否被正确加载。
5. objdump
+ grep
自动化查找符号(可选)
你可以通过简单地结合 objdump
和 grep
来自动化查找符号:
for lib in $(ldconfig -p | awk '{print $4}'); do
echo "Checking $lib"
objdump -T $lib | grep my_function
done
这将遍历系统中的所有共享库,检查它们是否包含 my_function
符号。
总结
要查找一个 .so
文件中是否定义了某个符号,你可以使用以下工具:
nm -D <your_library.so> | grep <symbol_name>
:列出动态符号。readelf -Ws <your_library.so> | grep <symbol_name>
:查看 ELF 文件的符号表。objdump -T <your_library.so> | grep <symbol_name>
:显示动态符号表。
这些工具可以帮助你快速检索某个共享库是否定义了特定的符号。
标签:调用,函数,查看,gdb,线程,断点 From: https://www.cnblogs.com/jaqennnnn/p/18449364