使用 strace 偵錯軟體部署

使用 strace 偵錯軟體部署

我的日常工作主要是軟體部署,這意味著我花了很多時間試圖回答以下問題:

  • 該軟體適用於開發人員,但不適用於我。 為什麼?
  • 昨天這個軟體對我有用,但今天不行了。 為什麼?

這是一種與常規軟體調試略有不同的調試。 常規調試是關於程式碼的邏輯,而部署調試是關於程式碼與環境之間的互動。 即使問題的根源是邏輯錯誤,但一切都在一台電腦上運行而不是在另一台電腦上運行的事實意味著問題在某種程度上存在於環境中。

因此,不要使用通常的調試工具,例如 數據庫 我有一套不同的工具用於調試部署。 我最喜歡的工具用於處理「為什麼這個軟體不適合我?」之類的問題。 被稱為 痕跡.

什麼是 strace?

痕跡 是一個「系統呼叫追蹤」的工具。 它最初是為 Linux 創建的,但可以使用其他系統的工具來完成相同的調試技巧(DTrace的追蹤).

基本應用程式非常簡單。 您只需要使用任何命令運行 strace,它就會轉儲所有系統呼叫(儘管首先您可能需要自己安裝它) 痕跡):

$ strace echo Hello
...Snip lots of stuff...
write(1, "Hellon", 6)                  = 6
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

這些系統呼叫是什麼? 這有點像是作業系統核心的 API。 曾幾何時,軟體可以直接存取其運作的硬體。 例如,如果它需要在螢幕上顯示某些內容,它會使用視訊裝置的連接埠或記憶體映射暫存器進行播放。 當多任務計算機系統變得流行時,由於各種應用程式爭奪硬件,混亂出現了。 一個應用程式中的錯誤可能會導致其他應用程式(甚至整個系統)癱瘓。 然後CPU中出現了特權模式(或稱為「環保護」)。 核心成為最有特權的:它獲得了對硬體的完全訪問權限,從而產生了特權較低的應用程序,這些應用程式已經必須向核心請求訪問權限才能透過系統呼叫與硬體進行互動。

在二進位級別,系統呼叫與簡單的函數呼叫略有不同,但大多數程式都使用標準庫中的包裝器。 那些。 POSIX C 標準函式庫包含函數調用 寫(),其中包含系統呼叫的所有特定於體系結構的程式碼 .

使用 strace 偵錯軟體部署

簡而言之,應用程式與其環境(電腦系統)之間的任何互動都是透過系統呼叫進行的。 因此,當軟體在一台機器上運行但在另一台機器上運行失敗時,最好查看系統呼叫追蹤結果。 更具體地說,這裡列出了可以使用系統呼叫追蹤進行分析的典型點:

  • 控制台輸入/輸出
  • 網路輸入/輸出
  • 檔案系統存取和檔案 I/O
  • 管理行程執行緒的生命週期
  • 低階記憶體管理
  • 存取特定設備驅動程式

何時使用 strace?

理論上, 痕跡 與使用者空間中的任何程式一起使用,因為使用者空間中的任何程式都必須進行系統呼叫。 它可以更有效地處理已編譯的低階程序,但如果您可以消除來自執行時間和解釋器的額外噪音,它也可以處理 Python 等高階語言。

在所有的輝煌中 痕跡 在調試在一台機器上運行良好的軟體時,它會突然停止在另一台機器上運行,產生有關文件、權限的模糊訊息,或嘗試執行某些命令或其他內容失敗...很遺憾,但事實並非如此與證書驗證錯誤等高級問題結合得很好。 通常這需要組合 痕跡, 有時 追蹤 和更高層級的工具(如命令列工具 OpenSSL的 調試證書)。

我們將使用獨立伺服器作為範例,但係統呼叫追蹤通常可以在更複雜的部署平台上完成。 您只需要選擇正確的工具。

簡單調試範例

假設您想要運行令人驚嘆的伺服器應用程式 foo,這就是您最終得到的結果:

$ foo
Error opening configuration file: No such file or directory

顯然它找不到你寫的設定檔。 發生這種情況是因為有時當套件管理器編譯應用程式時,它們會覆蓋預期的檔案位置。 如果您遵循一個發行版的安裝指南,那麼在另一個發行版中您會發現檔案與您預期的完全不同。 如果錯誤訊息告訴我們在哪裡查找配置文件,那麼問題可能會在幾秒鐘內解決,但事實並非如此。 那麼要去哪裡看呢?

如果您有權存取原始程式碼,您可以閱讀它並找出所有內容。 一個好的備份計劃,但不是最快的解決方案。 您可以使用逐步偵錯器,例如 數據庫 並查看程式的作用,但使用專門設計用於顯示與環境互動的工具會更有效: 痕跡.

產量 痕跡 可能看起來多餘,但好消息是大部分內容都可以安全地忽略。 使用 -o 運算子將追蹤結果儲存到單獨的檔案通常很有用:

$ strace -o /tmp/trace foo
Error opening configuration file: No such file or directory
$ cat /tmp/trace
execve("foo", ["foo"], 0x7ffce98dc010 /* 16 vars */) = 0
brk(NULL)                               = 0x56363b3fb000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=25186, ...}) = 0
mmap(NULL, 25186, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f2f12cf1000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF2113 3 > 1 260A2 "..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1824496, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f2f12cef000
mmap(NULL, 1837056, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f2f12b2e000
mprotect(0x7f2f12b50000, 1658880, PROT_NONE) = 0
mmap(0x7f2f12b50000, 1343488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f2f12b50000
mmap(0x7f2f12c98000, 311296, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x16a000) = 0x7f2f12c98000
mmap(0x7f2f12ce5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b6000) = 0x7f2f12ce5000
mmap(0x7f2f12ceb000, 14336, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2f12ceb000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f2f12cf0500) = 0
mprotect(0x7f2f12ce5000, 16384, PROT_READ) = 0
mprotect(0x56363b08b000, 4096, PROT_READ) = 0
mprotect(0x7f2f12d1f000, 4096, PROT_READ) = 0
munmap(0x7f2f12cf1000, 25186)           = 0
openat(AT_FDCWD, "/etc/foo/config.json", O_RDONLY) = -1 ENOENT (No such file or directory)
dup(2)                                  = 3
fcntl(3, F_GETFL)                       = 0x2 (flags O_RDWR)
brk(NULL)                               = 0x56363b3fb000
brk(0x56363b41c000)                     = 0x56363b41c000
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x8), ...}) = 0
write(3, "Error opening configuration file"..., 60) = 60
close(3)                                = 0
exit_group(1)                           = ?
+++ exited with 1 +++

大約輸出整個第一頁 痕跡 - 這通常是發射的低級準備。 (很多電話 MMAP, 保護, BRK 用於檢測低階記憶體和顯示動態庫之類的事情。)實際上,在偵錯輸出期間 痕跡 最好從最後開始閱讀。 下面會有一個挑戰 ,它會顯示一條錯誤訊息。 我們看上面,看到第一個錯誤的系統呼叫──調用 開放,這會引發錯誤 埃諾特 (“未找到檔案或目錄”)嘗試開啟 /etc/foo/config.json。 這是設定檔應該所在的位置。

這只是一個例子,但我想說 90% 的時間我都在使用 痕跡,沒有什麼比這更難做到的了。 以下是完整的分步調試指南:

  • 由於程式中出現有關係統錯誤的模糊訊息而感到不安
  • 重新啟動程式 痕跡
  • 在trace結果中找到錯誤訊息
  • 走得更高,直到遇到第一個失敗的系統調用

步驟 4 中的系統呼叫很可能會揭示出了什麼問題。

提示

在向您展示更複雜的調試範例之前,我將向您展示一些有效使用的技巧 痕跡:

男人是你的朋友

在許多 *nix 系統上,可以透過執行以下命令來取得對核心的系統呼叫的完整列表 人系統調用。 你會看到類似的東西 布爾克(2),這意味著可以透過運行獲得更多信息 男子 2 布魯克.

小耙子: 人2叉子 顯示 shell 的頁面 叉子() в GNU 函式庫,事實證明,這是使用呼叫來實現的 克隆()。 調用語義 如果您使用以下方式編寫程序,則保持不變 叉子(),然後運行跟踪 - 我找不到任何調用 ,而不是他們將會有 克隆()。 如果您開始將來源與輸出進行比較,這樣的耙子只會讓您感到困惑 痕跡.

使用 -o 將輸出儲存到文件

痕跡 可以產生大量輸出,因此將追蹤結果儲存在單獨的檔案中通常很有用(如上例所示)。 這也有助於避免程式輸出與輸出混淆 痕跡 在控制台中。

使用 -s 查看更多參數數據

您可能已經注意到,錯誤訊息的後半部沒有顯示在上面的範例追蹤中。 這是因為 痕跡 預設僅顯示字串參數的前 32 個位元組。 如果您想查看更多內容,請添加類似內容 -s 128 打電話 痕跡.

-y 可以更輕鬆地追蹤檔案、套接字等。

「全部都是檔案」表示 *nix 系統使用檔案描述子執行所有 I/O,無論這適用於檔案、網路或進程間管道。 這對於程式設計來說很方便,但是當您看到常見的情況時,很難追蹤到底發生了什麼 閱讀 и 在系統呼叫追蹤結果中。

透過新增運算符 ÿ,你會強迫 痕跡 使用其指向的註解來註解輸出中的每個檔案描述符。

使用 -p** 附加到已執行的進程

正如您將從下面的範例中看到的,有時您需要追蹤已經在運行的程式。 如果知道它正在作為進程 1337 運行(例如,從輸出 ps),那麼你可以像這樣追蹤它:

$ strace -p 1337
...system call trace output...

您可能需要 root 權限。

使用 -f 監視子進程

痕跡 預設情況下,它僅追蹤一個進程。 如果該進程產生子進程,則可以看到生成子進程的系統調用,但不會顯示子進程的系統調用。

如果您認為錯誤出現在子進程中,請使用下列語句 -f,這將啟用其追蹤。 這樣做的缺點是輸出會讓你更困惑。 什麼時候 痕跡 追蹤一個進程或一個線程,它顯示單一呼叫事件流。 當它同時追蹤多個進程時,您可能會看到呼叫的開始被訊息中斷 ,然後 - 對其他執行分支的一堆調用,然後 - 第一個執行分支結束 <...foocall 恢復>。 或將所有追蹤結果拆分到不同的檔案中,也使用運算符 -ff (詳細資訊在 領導痕跡).

使用 -e 過濾跟踪

正如您所看到的,追蹤的結果是所有可能的系統呼叫的真實堆積。 旗幟 -e 您可以過濾追蹤(請參閱 領導痕跡)。 主要優點是運行過濾追蹤比執行完整追蹤更快 grep的`在。 老實說,我幾乎總是不在乎。

並非所有錯誤都是壞事

一個簡單且常見的範例是程式同時在多個位置尋找文件,就像 shell 尋找包含執行檔的目錄一樣:

$ strace sh -c uname
...
stat("/home/user/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/uname", 0x7ffceb817820) = -1 ENOENT (No such file or directory)
stat("/usr/bin/uname", {st_mode=S_IFREG|0755, st_size=39584, ...}) = 0
...

像「報告錯誤之前最後一次失敗的請求」這樣的啟發式方法可以很好地發現相關錯誤。 不管怎樣,從最後開始是合乎邏輯的。

C 程式教學可以幫助您理解系統呼叫。

對 C 函式庫的標準調用不是系統調用,而只是一個薄薄的表面層。 因此,如果您至少了解一點 C 語言的操作方法和用途,您將更容易理解系統呼叫追蹤的結果。 例如,你在調試網路系統的呼叫時遇到麻煩,看看同樣經典的 Bija 的網路程式設計指南.

更複雜的調試範例

我已經說過,簡單調試的範例是我在使用時最需要處理的範例 痕跡。 然而,有時需要進行真正的調查,因此這裡有一個更高級調試的現實範例。

克隆 - 任務處理調度程序,*nix 守護程序的另一個實現 cron的。 它安裝在伺服器上,但是當有人嘗試編輯時間表時,會發生以下情況:

# crontab -e -u logs
bcrontab: Fatal: Could not create temporary file

好吧,這意味著 克隆 嘗試寫入某個文件,但沒有成功,而且他不承認原因。 揭秘 痕跡:

# strace -o /tmp/trace crontab -e -u logs
bcrontab: Fatal: Could not create temporary file
# cat /tmp/trace
...
openat(AT_FDCWD, "bcrontab.14779.1573691864.847933", O_RDONLY) = 3
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
read(3, "#Ansible: logsaggn20 14 * * * lo"..., 8192) = 150
read(3, "", 8192)                       = 0
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
socket(AF_UNIX, SOCK_STREAM, 0)         = 3
connect(3, {sa_family=AF_UNIX, sun_path="/var/run/bcron-spool"}, 110) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f82049b4000
write(3, "156:Slogs #Ansible: logsaggn20 1"..., 161) = 161
read(3, "32:ZCould not create temporary f"..., 8192) = 36
munmap(0x7f82049b4000, 8192)            = 0
close(3)                                = 0
write(2, "bcrontab: Fatal: Could not creat"..., 49) = 49
unlink("bcrontab.14779.1573691864.847933") = 0
exit_group(111)                         = ?
+++ exited with 111 +++

接近尾聲時出現錯誤訊息 ,但這次有些不同。 首先,沒有相關的系統呼叫錯誤,這種錯誤通常發生在這之前。 其次,很明顯地有人已經在某個地方閱讀了錯誤訊息。 看起來真正的問題在其他地方,並且 定時任務表 只是回放該訊息。

如果你看 男人2讀,您可以看到第一個參數 (3) 是一個檔案描述符,*nix 將其用於所有 I/O 處理。 如何找出文件描述符 3 代表什麼? 在這種特殊情況下,您可以運行 痕跡 與操作符 ÿ (見上文)它會自動告訴你,但要弄清楚這樣的事情,了解如何讀取和解析追蹤結果是很有用的。

檔案描述符的來源可以是許多系統呼叫之一(這完全取決於描述符的用途 - 控制台、網路套接字、檔案本身或其他東西),但無論如何,我們都會尋找透過返回3 進行呼叫(即我們在追蹤結果中尋找“= 3”)。 在此結果中有 2 個: 開放 在最頂部和 插座 在中間。 開放 打開文件但是 完成交易(3)然後將顯示它再次關閉。 (Rake:檔案描述子在開啟和關閉時可以重複使用)。 稱呼 插座() 合適,因為這是之前的最後一篇 讀(),事實證明 bcrontab 透過套接字進行某些操作。 下一行顯示文件描述符與 unix 域套接字 在途中 /var/run/bcron-spool.

因此,我們需要找到與 Unix套接字 另一方面。 為此,有一些巧妙的技巧,它們對於調試伺服器部署都很有用。 第一個是使用 netstat命令 或更新的 ss (套接字狀態)。 兩個指令都顯示系統的活動網路連線並採取語句 -l 描述監聽套接字以及操作符 -p 顯示作為客戶端連接到套接字的程式。 (還有許多更有用的選項,但這兩個選項足以完成此任務。)

# ss -pl | grep /var/run/bcron-spool
u_str LISTEN 0   128   /var/run/bcron-spool 1466637   * 0   users:(("unixserver",pid=20629,fd=3))

這表示監聽器是命令 UNIX伺服器,以進程 ID 20629 運行。(巧合的是,它使用檔案描述符 3 作為套接字。)

用於尋找相同資訊的第二個真正有用的工具稱為 。 它列出了系統上所有開啟的檔案(或檔案描述符)。 或者您可以獲得有關某一特定文件的資訊:

# lsof /var/run/bcron-spool
COMMAND   PID   USER  FD  TYPE  DEVICE              SIZE/OFF  NODE    NAME
unixserve 20629 cron  3u  unix  0x000000005ac4bd83  0t0       1466637 /var/run/bcron-spool type=STREAM

進程 20629 是一個長期存在的伺服器,因此您可以將其附加到 痕跡 使用類似的東西 strace -o /tmp/trace -p 20629。 如果您在另一個終端機中編輯 cron 作業,您將收到帶有錯誤的追蹤輸出。 這是結果:

accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21181
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21181, si_uid=998, si_status=0, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WNOHANG|WSTOPPED, NULL) = 21181
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL)                   = 4
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21200
close(4)                                = 0
accept(3, NULL, NULL)                   = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=21200, si_uid=998, si_status=111, si_utime=0, si_stime=0} ---
wait4(0, [{WIFEXITED(s) && WEXITSTATUS(s) == 111}], WNOHANG|WSTOPPED, NULL) = 21200
wait4(0, 0x7ffe6bc36764, WNOHANG|WSTOPPED, NULL) = -1 ECHILD (No child processes)
rt_sigaction(SIGCHLD, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, {sa_handler=0x55d244bdb690, sa_mask=[CHLD], sa_flags=SA_RESTORER|SA_RESTART, sa_restorer=0x7faa47ab9840}, 8) = 0
rt_sigreturn({mask=[]})                 = 43
accept(3, NULL, NULL

(最後的 接受() 追蹤時不會完成。)同樣,不幸的是,這個結果不包含我們正在尋找的錯誤。 我們沒有看到 bcrontag 向套接字發送或從套接字接收的任何訊息。 相反,完整的過程控制(克隆, 等待4, 號誌燈 等等)此進程會產生一個子進程,正如您可能猜到的那樣,該子進程執行真正的工作。 如果您需要追蹤她的蹤跡,請加入通話 strace-f。 這是我們用strace在新結果中搜尋錯誤訊息時會發現的 -f -o /tmp/trace -p 20629:

21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

現在,就是這樣。 進程 21470 嘗試在路徑建立檔案時收到「存取被拒絕」錯誤 tmp/spool.21470.1573692319.854640 (與目前工作目錄相關)。 如果我們只知道目前工作目錄,我們也會知道完整路徑,並且能夠找出進程無法在其中建立臨時檔案的原因。 不幸的是,該進程已經退出,所以你不能只使用 lsof -p 21470 為了找到當前目錄,但您可以朝相反的方向工作 - 尋找更改目錄的 PID 21470 系統呼叫。 (如果沒有,PID 21470一定是從它的父級繼承了它們,這已經通過 lsof-p 找不到。)這個系統呼叫是 目錄 (借助現代線上搜尋引擎很容易找到)。 這是根據追蹤結果反向搜尋的結果,一直到伺服器PID 20629:

20629 clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7faa47c44810) = 21470
...
21470 execve("/usr/sbin/bcron-spool", ["bcron-spool"], 0x55d2460807e0 /* 27 vars */) = 0
...
21470 chdir("/var/spool/cron")          = 0
...
21470 openat(AT_FDCWD, "tmp/spool.21470.1573692319.854640", O_RDWR|O_CREAT|O_EXCL, 0600) = -1 EACCES (Permission denied) 
21470 write(1, "32:ZCould not create temporary f"..., 36) = 36
21470 write(2, "bcron-spool[21470]: Fatal: logs:"..., 84) = 84
21470 unlink("tmp/spool.21470.1573692319.854640") = -1 ENOENT (No such file or directory)
21470 exit_group(111)                   = ?
21470 +++ exited with 111 +++

(如果你迷路了,你可能想閱讀我之前的文章 關於 *nix 進程管理和 shell.)因此,伺服器PID 20629沒有收到在路徑上建立檔案的權限 /var/spool/cron/tmp/spool.21470.1573692319.854640。 最有可能的原因是經典檔案系統權限設定。 讓我們檢查:

# ls -ld /var/spool/cron/tmp/
drwxr-xr-x 2 root root 4096 Nov  6 05:33 /var/spool/cron/tmp/
# ps u -p 20629
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
cron     20629  0.0  0.0   2276   752 ?        Ss   Nov14   0:00 unixserver -U /var/run/bcron-spool -- bcron-spool

那是埋狗的地方! 伺服器以使用者cron運行,但只有root有寫入目錄的權限 /var/spool/cron/tmp/。 簡單的指令 chown cron /var/spool/cron/tmp/ 將迫使 克隆 正常工作。 (如果這不是問題,那麼下一個最有可能的嫌疑是像 SELinux 或 AppArmor 這樣的內核安全模組,所以我會檢查內核訊息日誌 dmesg的.)

在總

系統呼叫追蹤對於初學者來說可能會讓人不知所措,但我希望我已經證明它們是調試一整類常見部署問題的快速方法。 想像嘗試調試多進程 克隆使用逐步調試器。

沿著系統呼叫鏈向後解析追蹤結果需要技巧,但正如我所說,幾乎總是使用 痕跡,我只是獲取跟踪結果並從末尾開始查找錯誤。 反正, 痕跡 幫我節省大量調試時間。 我希望它對你也有用。

來源: www.habr.com

添加評論