Linux 後臺開發經常使用調試工具

來源:李海彬
https://urlify.cn/VF7V7v

01 總覽

編譯階段

  • nm                 獲取二進制文件包含的符號信息
  • strings           獲取二進制文件包含的字符串常量
  • strip               去除二進制文件包含的符號
  • readelf           顯示目標文件詳細信息
  • objdump         儘量反彙編出源代碼
  • addr2line        根據地址查找代碼行

運行階段

  • gdb                強大的調試工具
  • ldd                 顯示程序須要使用的動態庫和實際使用的動態庫
  • strace            跟蹤程序當前的系統調用
  • ltrace             跟蹤程序當前的庫函數
  • time               查看程序執行時間、用戶態時間、內核態時間
  • gprof              顯示用戶態各函數執行時間
  • valgrind          檢查內存錯誤
  • mtrace           檢查內存錯誤

其餘

  • proc文件系統
  • 系統日誌

02 編譯階段

nm(獲取二進制文件裏面包含的符號)

符號:函數、變量python

參數:linux

  • -C           把C++函數簽名轉爲可讀形式
  • -A           列出符號名的時候同時顯示來自於哪一個文件。
  • -a           列出全部符號(這將會把調試符號也列出來。默認狀態下調試符號不會被列出)
  • -l            列出符號在源代碼中對應的行號(指定這個參數後,nm將利用調試信息找出文件名以及符號的行號。對於一個已定義符號,將會找出這個符號定義的行號,對於未定義符號,顯示爲空)
  • -n           根據符號的地址來排序(默認是按符號名稱的字母順序排序的)
  • -u           只列出未定義符號

strings(獲取二進制文件裏面的字符串常量)

功能:網絡

獲取二進制文件裏面的字符串常量app

用途:函數

比較重要的是檢查KEY泄露工具

eg:strings <your_proc> | grep '^.\{16\}$' 性能

查找<your_proc>中是否存在一行有16個字符的行,並顯示出來。測試

選項:ui

  • -a 不僅是掃描目標文件初始化和裝載段, 而是掃描整個文件。
  • -f 在顯示字符串以前先顯示文件名。
  • -n min-len打印至少min-len字符長的字符串.默認的是4。
#strings /lib/tls/libc.so.6 | grep GLIBC
GLIBC_2.0
GLIBC_2.1
GLIBC_2.1.1……

這樣就能看到glibc支持的版本。this

strip(去除二進制文件裏面包含的符號)

用途:

可執行程序減肥(一般只在已經調試和測試過的生成模塊上,由於不能調試了)

反編譯、反跟蹤

readelf(顯示目標文件詳細信息)

nm 程序可用於列舉符號及其類型和值,可是,要更仔細地研究目標文件中這些命名段的內容,須要使用功能更強大的工具。其中兩種功能強大的工具是objdump和readelf。

readelf工具使用來顯示一個或多個ELF格式文件信息的GNU工具。使用不一樣的參數能夠查看ELF文件不一樣的的信息。

readelf <option> <elffile>

  • -a           顯示全部ELF文件的信息
  • -h           顯示ELF文件的文件頭
  • -l            顯示程序頭(program-header)和程序段(segment)和段下面的節
  • -S           顯示較爲詳細的節信息(section)
  • -s           顯示符號信息,
  • -n           顯示標識信息(若是有)
  • -r            顯示重定位信息(若是有)
  • -u           顯示展開函數信息(若是有)
  • -d           顯示動態節信息,通常是動態庫的信息

objdump(儘量反彙編出源代碼)objdump –S <exe>

儘量反彙編出源代碼,尤爲當編譯的時候指定了-g參數時,效果比較明顯。

addr2line(根據地址查找代碼行)

當某個進程崩潰時,日誌文件(/var/log/messages)中就會給出附加的信息,包括程序終止緣由、故障地址,以及包含程序狀態字(PSW)、通用寄存器和訪問寄存器的簡要寄存器轉儲。

eg:Mar 31 11:34:28 l02 kernel: failing address: 0

若是可執行文件包括調試符號(帶-g編譯的),使用addr2line,能夠肯定哪一行代碼致使了問題。

eg:addr2line –e exe addr

其實gdb也有這個功能,不過addr2line的好處是,不少時候,bug很難重現,咱們手上只有一份crash log。這樣就能夠利用addr2line找到對應的代碼行,很方便。

注意:

  1. 該可執行程序用-g編譯,使之帶調試信息。
  2. 若是crash在一個so裏面,那addr2line不能直接給出代碼行。

參數:

  • -a     在顯示函數名或文件行號前顯示地址
  • -b     指定二進制文件格式
  • -C     解析C++符號爲用戶級的名稱,可指定解析樣式
  • -e     指定二進制文件
  • -f      同時顯示函數名稱
  • -s     僅顯示文件的基本名,而不是完整路徑
  • -i      展開內聯函數
  • -j      讀取相對於指定節的偏移而不是絕對地址
  • -p     每一個位置都在一行顯示

03 運行階段

調試程序的常見步驟:

一、肯定運行時間主要花在用戶態仍是內核態(比較土的一個方法:程序暫時屏蔽daemon()調用,hardcode收到n個請求後exit(0),time一下程序……)。

二、若是是用戶態,則使用gprof進行性能分析。

三、若是是內核態,則使用strace進行性能分析,另外可使用其餘工具(好比ltrace等)輔助。

ldd(顯示程序須要使用的動態庫和實際使用的動態庫)

# ldd /bin/lslinux-gate.so.1 => (0xbfffe000)librt.so.1 => /lib/librt.so.1 (0xb7f0a000)libacl.so.1 => /lib/libacl.so.1 (0xb7f04000)libc.so.6 => /lib/libc.so.6 (0xb7dc3000)libpthread.so.0 => /lib/libpthread.so.0 (0xb7dab000)/lib/ld-linux.so.2 (0xb7f1d000)libattr.so.1 => /lib/libattr.so.1 (0xb7da6000)

第一欄:須要用什麼庫;第二欄:實際用哪一個庫文件;第三欄:庫文件裝載地址。

若是缺乏動態庫,就會沒有第二欄。

strace(跟蹤當前系統調用)

結果默認輸出到2。

  • -p <pid> attach到一個進程
  • -c 最後統計各個system call的調用狀況
  • -T 打印system call的調用時間
  • -t/-tt/-ttt 時間格式
  • -f/-F 跟蹤由fork/vfork調用所產生的子進程
  • -o <file>,將strace的輸出定向到file中。

如:strace -f -o ~/<result_file> <your_proc>

  • -e expr 指定一個表達式,用來控制如何跟蹤,格式以下:
  • -e open等價於-e trace=open,表示只跟蹤open調用

使用 strace –e open ./prg 來看程序使用了哪些配置文件或日誌文件,很方便。

  • -e trace=<set> 只跟蹤指定的系統調用

例如:-e trace=open,close,rean,write 表示只跟蹤這四個系統調用.

  • -e trace=file只跟蹤有關文件操做的系統調用
  • -e trace=process只跟蹤有關進程控制的系統調用
  • -e trace=network跟蹤與網絡有關的全部系統調用
  • -e strace=signal 跟蹤全部與系統信號有關的系統調用
  • -e trace=ipc跟蹤全部與進程通信有關的系統調用

ltrace(跟蹤當前庫函數)

參數和strace很接近

time(查看程序執行時間、用戶態時間、內核態時間)

# time ps aux | grep 'hi'
1020 21804 0.0 0.0 1888 664 pts/6 S+ 17:46 0:00 grep hi
real 0m0.009s
user 0m0.000s
sys 0m0.004s

注意:

time只跟蹤父進程,因此不能fork

gprof(顯示用戶態各函數執行時間)

gprof原理:

在編譯和連接程序的時候(使用 -pg 編譯和連接選項),gcc在你應用程序的每一個函數中都加入了一個名爲mcount(or「_mcount」, or「__mcount」)的函數,也就是說-pg編譯的應用程序裏的每個函數都會調用mcount, 而mcount會在內存中保存一張函數調用圖,並經過函數調用堆棧的形式查找子函數和父函數的地址。這張調用圖也保存了全部與函數相關的調用時間,調用次數等等的全部信息。

使用步驟:

一、使用 -pg 編譯和連接應用程序

gcc -pg -o exec exec.c

若是須要庫函數調用狀況:

gcc -lc_p -gp -o exec exec.c

二、執行應用程序使之生成供gprof 分析的數據gmon.out

三、使用gprof 程序分析應用程序生成的數據

gprof exec gmon.out > profile.txt

注意:

程序必須經過正常途徑退出(exit()、main返回),kill無效。對後臺常駐程序的調試——個人比較土方法是,屏蔽daemon()調用,程序hardcode收到n個請求後exit(0)。

有時不太準。

只管了用戶態時間消耗,沒有管內核態消耗。

gdb core exec (gdb查看core文件) 準備生成core:

啓動程序前,ulimit -c unlimited,設置core文件不限制大小。(相反,ulimit -c 0,能夠阻止生成core文件)

默認在可執行程序的路徑,生成的是名字爲core的文件,新的core會覆蓋舊的。

設置core文件名字:

/proc/sys/kernel/core_uses_pid 能夠控制產生的core文件的文件名中是否添加pid做爲擴展,1爲擴展,不然爲0。

proc/sys/kernel/core_pattern 能夠設置格式化的core文件保存位置或文件名,好比原來文件內容是core,能夠修改成:

echo "/data/core/core-%e-%p-%t" > core_pattern

如下是參數列表:

  • %p - insert pid into filename 添加pid
  • %u - insert current uid into filename 添加當前uid
  • %g - insert current gid into filename 添加當前gid
  • %s - insert signal that caused the coredump into the filename 添加致使產生core的信號
  • %t - insert UNIX time that the coredump occurred into filename 添加core文件生成時的unix時間
  • %h - insert hostname where the coredump happened into filename 添加主機名
  • %e - insert coredumping executable name into filename 添加命令名

使用gdb查看core:

gdb <program> <core文件>

opprofile (查看CPU耗在哪)

經常使用命令

使用oprofile進行cpu使用狀況檢測,須要通過初始化、啓動檢測、導出檢測數據、查看檢測結果等步驟,如下爲經常使用的oprofile命令。

初始化

  • opcontrol --no-vmlinux : 指示oprofile啓動檢測後,不記錄內核模塊、內核代碼相關統計數據
  • opcontrol --init : 加載oprofile模塊、oprofile驅動程序

檢測控制

  • opcontrol --start : 指示oprofile啓動檢測
  • opcontrol --dump : 指示將oprofile檢測到的數據寫入文件
  • opcontrol --reset : 清空以前檢測的數據記錄
  • opcontrol -h : 關閉oprofile進程

查看檢測結果

  • opreport : 以鏡像(image)的角度顯示檢測結果,進程、動態庫、內核模塊屬於鏡像範疇
  • opreport -l : 以函數的角度顯示檢測結果
  • opreport -l test : 以函數的角度,針對test進程顯示檢測結果
  • opannotate -s test : 以代碼的角度,針對test進程顯示檢測結果
  • opannotate -s /lib64/libc-2.4.so : 以代碼的角度,針對libc-2.4.so庫顯示檢測結果
linux # opreport
CPU: Core 2, speed 2128.07 MHz (estimated) Counted CPU_CLK_UNHALTED events (Clock cycles when not halted) with a unit mask of 0x00 (Unhalted core cycles) count 100000CPU_CLK_UNHAL
T.........| samples | %| ------------------------ 31645719     87.6453      no-vmlinux 4361113     10.3592      libend.so 7683      0.1367      libpython2.4.so.1.0        7046      0.1253      op_test

valgrind(檢查內存錯誤)

使用步驟:

一、官網下載並安裝valgrind。

二、-g編譯的程序均可以使用。

官網的示例代碼test.c

#include <stdlib.h>  
void f(void)  
{  
 int* x = malloc(10 * sizeof(int));  
 x[10] = 0; // problem 1: heap block overrun  
} // problem 2: memory leak -- x not freed  
  
int main(void)  
{  
 f();  
 return 0;  
}

編譯程序gcc -Wall -g -o test test.c

三、valgrind啓動程序,屏幕輸出結果。

valgrind --tool=memcheck --leak-check=full ./test

注意:

valgrind只能查找堆內存的訪問錯誤,對棧上的對象和靜態對象沒辦法。

valgrind會影響進程性能,聽說可能慢20倍,因此在性能要求高的狀況下,只能使用mtrace這種輕量級的工具了(可是mtrace只能識別簡單的內存錯誤)。

若是程序生成的core的堆棧是錯亂的,那麼基本上是stackoverflow了。這種狀況,能夠經過在編譯的時候,加上 –fstack-protector-all-D_FORTIFY_SOURCE=2 來檢測。Stack-protector-all 會在每一個函數里加上堆棧保護的代碼,並在堆棧上留上指紋。(記錄下,沒用過)

由於valgrind 查不了棧和靜態對象的內存訪問越界,這類問題,能夠經過使用gcc的-fmudflap –lmudflap 來檢測。(記錄下,沒用過)

全局變量的類型不一致的問題,如今還找到比較好的方法,這從另外一個方面說明全局對象不是個好的設計,這給調試帶來了麻煩。

mtrace(檢查內存錯誤)

mtrace是glibc內提供的工具,原理很簡單,就是把你程序中malloc()和free()的位置所有下來,最後兩輛配對,沒有配對到的就是memory leak。

使用的步驟以下:

一、代碼中添加mtrace()

#include <stdio.h>  
#include <stdlib.h>  
int main(void)  
{  
 int *p;  
 int i;  
#ifdef DEBUG  
 setenv("MALLOC_TRACE", "./memleak.log", 1);  
 mtrace();  
#endif  
 p=(int *)malloc(1000);  
 return 0;  
}

這段代碼malloc了一個空間,卻沒有free掉。咱們添加9-12行的mtrace調用。

二、編譯gcc -g -DDEBUG -o test1 test1.c

三、執行./test1,在目錄裏會發現./memleak.log

四、使用mtrace <your_proc> memleak.log 查看信息。

# mtrace test1 memleak.log  
- 0x0804a008 Free 3 was never alloc'd 0xb7e31cbe  
- 0x0804a100 Free 4 was never alloc'd 0xb7ec3e3f  
- 0x0804a120 Free 5 was never alloc'd 0xb7ec3e47  
  
Memory not freed:  
-----------------  
Address Size Caller  
0x0804a4a8 0x3e8 at /home/illidanliu/test1.c:14

能夠看到test1.c沒有對應的free()。

04 其餘

proc文件系統

內核的窗口。

proc文件系統是一個僞文件系統,它存在內存當中,而不佔用外存空間。

用戶和應用程序能夠經過proc獲得系統的信息,並能夠改變內核的某些參數。

proc/目錄結構(部分):

  • cmdline                 內核命令行
  • cpuinfo                  關於Cpu信息
  • devices                 能夠用到的設備(塊設備/字符設備)
  • filesystems           支持的文件系統
  • interrupts               中斷的使用
  • ioports                  I/O端口的使用
  • kcore                    內核核心映像
  • kmsg                    內核消息
  • meminfo     內存信息
  • mounts                 加載的文件系統
  • stat                       全面統計狀態表
  • swaps                   對換空間的利用狀況
  • version                  內核版本
  • uptime                  系統正常運行時間
  • net                        網絡信息
  • sys                       可寫,能夠經過它來訪問或修改內核的參數

proc/<pid>/目錄結構(部分):

  • cmdline                 命令行參數
  • environ                  環境變量值
  • fd                         一個包含全部文件描述符的目錄
  • mem                     進程的內存被利用狀況
  • stat                       進程狀態
  • status                   Process status in human readable form
  • cwd                      當前工做目錄的連接
  • exe                       Link to the executable of this process
  • maps                    內存映像
  • statm                    進程內存狀態信息
  • root                       連接此進程的root目錄

系統日誌

/var/log/下的日誌文件:

  • /var/log/messages     總體系統信息,其中也包含系統啓動期間的日誌。此外,mail、cron、daemon、kern和auth等內容也記錄在var/log/messages日誌中。
  • /var/log/auth.log           系統受權信息,包括用戶登陸和使用的權限機制等。
  • /var/log/boot.log           系統啓動時的日誌。
  • /var/log/daemon.log      各類系統後臺守護進程日誌信息。
  • /var/log/lastlog          記錄全部用戶的最近信息。這不是一個ASCII文件,所以須要用lastlog命令查看內容。
  • /var/log/user.log           記錄全部等級用戶信息的日誌。
  • /var/log/cron                每當cron進程開始一個工做時,就會將相關信息記錄在這個文件中。
  • /var/log/wtmp或utmp    登陸信息。
  • /var/log/faillog           用戶登陸失敗信息。此外,錯誤登陸命令也會記錄在本文件中。

image

相關文章
相關標籤/搜索