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>
:顯示動態符號表。
這些工具可以幫助你快速檢索某個共享庫是否定義了特定的符號。