萬變不離其宗——程序動態分析(gdb)



    萬劍歸宗是無名的招數,但是它卻道出一個道理。不管劍招多麼花哨,多麼厲害,最終還是需要回歸正宗與樸實。程序也是一樣,不管代碼如何實現,不論語言如何,技巧如何,最終也是要能夠被正確,有效,可靠的運行,纔是本真。所以我們需要耐心的去了解與分析程序的運行過程,能夠去調試之,驗證自己程序的性能。

當然有些其他的知識技巧被總結於此。以下從5個方面做出介紹(參考文檔到我的資源中下載即可):


一)自動獲取編譯路徑的工具:pkg-config介紹

功能介紹:

自動配置編譯與鏈接路徑,在編譯時需要引入別人已經編譯好的庫與頭文件(-I,-L,-l參數)的自動配置,一般在configue時自動生成。

使用說明:

1)編譯時的參數(zlib爲例): pkg-config --cflags zlib

輸出爲-I/home/yiye/yiye_run/include 

2)鏈接時的參數:pkg-config --libs zlib 

輸出爲:-L/home/yiye/yiye_run/lib -lz

3)直接編譯使用zlib的程序:gcc zlib-program.c $(pkg-config cflags --libs zlib ).

4)查詢有哪些庫可以被找到:pkg-config list-all

5)查詢程序庫版本:pkg-config modversion zlib

輸出爲1.2.8

實現原理:

遍歷搜索路徑下的所有*.pc文件,然後輸出相關信息;默認的搜索路徑:

/usr/lib/pkgconfig, /usr/share/pkgconfig, /usr/local/lib/pkgconfig and /usr/local/share/pkg

       config;客製化路徑,修改環境變量,可以PKG_CONFIG_PATH

簡單分析*.pc文件(zlib.pc爲例)

[yiye@yiye pkgconfig]$ cat zlib.pc 

prefix=/home/yiye/yiye_run-----------configue時的prefix路徑,以下爲定義的相關變量

exec_prefix=${prefix}

libdir=${exec_prefix}/lib

sharedlibdir=${libdir}

includedir=${prefix}/include

 

Name: zlib-------------庫的名稱

Description: zlib compression library---------功能描述

Version: 1.2.8---------------版本信息

 

Requires:

Libs: -L${libdir} -L${sharedlibdir} -lz-----------鏈接參數

Cflags: -I${includedir}---------------編譯參數

深入瞭解:

   pkg-config help/man pkg-config



二)加載動態鏈接庫細節

功能介紹:

現在的應用程序大部分都依賴於動態鏈接,所以需要知道編譯出來的程序依賴哪些動態鏈接庫,同時,這些庫是如何被使用的。

使用說明:

1.查看應用程序動態庫的依賴關係:

ldd a.out

輸出:

linux-vdso.so.1 (0x00007fff73888000)

libadd.so => ./libadd.so (0x00007ff841e3e000)

libc.so.6 => /lib64/libc.so.6 (0x00007ff841a64000)

/lib64/ld-linux-x86-64.so.2 (0x000055cfc1243000)

2.修改運行加載的動態庫:

export LD_LIBRARY_PATH=lib-path:$LD_LIBRARY_PATH

得到程序解釋器——動態鏈接器:readelf -l /bin/ls|grep -n interpreter|sed 's/.*:\(.*\)]/\1/'。正常輸出爲 /lib/ld-linux.so.2Android被定製化了爲/system/bin/linker

3.添加動態鏈接庫到系統中:

a.添加動態庫路徑到/etc/ld.so.conf

b.執行ldconfig,將路徑寫入到/etc/ld.so.cache.

實現原理:

鏈接器在以下位置搜索庫:

    1)是否 dynamic 段有一個稱爲 DT_RPATH 的表項,它是由分號分隔開的可以搜索庫的目錄列表。

它可以通過一個命令行參數或者在程序鏈接時常規(非動態)鏈接器的環境變量來添加。它經

常會被諸如數據庫類這樣需要加載一系列程序並可將庫放在單一目錄的子系統使用,

設置DT_RPATH的方法——詳情可以參考ld的官方文檔2.1Command Line Options

a)通過gcc傳遞DT_RPATH給鏈接器rpath參數:gcc -Wl-rpath DT_RPATH

b)在編譯時設置LD_RUN_PATH環境變量。

查看可執行文件的DT_RPATH設置:

a)readelf -d a.out |grep RPATH

b)ldd a.out

 

2)是否有一個環境符號 LD_LIBRARY_PATH ,它可以是由分號分隔開的可供鏈接器搜索庫的目錄

列表。這就可以讓開發者創建一個新版本的庫並將它放置在 LD_LIBRARY_PATH 的路徑中,

樣既可以通過已存在的程序來測試新的庫,或用來監測程序的行爲。(因爲安全原因,如果程序設置了 set-uid ,那麼這一步會被跳過)

 

3)鏈接器查看庫緩衝文件 /etc/ld.so.conf ,其中包含了庫文件名和路徑的列表。如果要查找的

庫名稱存在於其中,則採用文件中相應的路徑。大多數庫都通過這種方法被找到(路徑末尾的

文件名稱並不需要和所搜索的庫名稱精確匹配,詳細請參看下面的庫版本章節)

修改/etc/ld.so.conf 緩衝的內容之後,需要ldconfig來將修改導入到/etc/ld.so.cache,保證程序能夠被引用。

 

4)如果所有的都失敗了,就查找缺省目錄 /usr/lib ,如果在這個目錄中仍沒有找到,就打印錯

誤信息,並退出執行

 

深入理解:

可參考《鏈接器與加載器.pdf》,ld鏈接文檔——在binutils的文檔中

三)Prelink的介紹

功能介紹:

預鏈接ELF動態鏈接庫,加速程序加載速度;在嵌入式linux系統中得到廣泛的使用,比如:Android;爲了解決應用程序,加載動態鏈接庫慢的問題——加載動態鏈接庫時,需要修改所有需要重定向的動態庫地址。

使用說明——一定需要root權限:

 1.prelink elf文件:prelink elf-file

2.prelink所有文件——默認文件/etc/prelink.conf 當然也可以制定文件-C: prelink -avmR

3.查看所有prelink的結果——prelink -p

4.嵌入式系統中使用它:fakeroot prelink –root=PATH elf-file

5.prelink.conf配置:

[root@localhost Supernova]# cat /etc/prelink.conf

# This config file contains a list of directories both with binaries

# and libraries prelink should consider by default.

# If a directory name is prefixed with `-l ', the directory hierarchy

# will be walked as long as filesystem boundaries are not crossed.

# If a directory name is prefixed with `-h ', symbolic links in a

# directory hierarchy are followed.——符號鏈接被prelink

# Directories or files with `-b ' prefix will be blacklisted.

# `-c ' is used to source additional config file snippets.

-c /etc/prelink.conf.d/*.conf——添加新的config文件

-b *.la----黑名單,這些文件不被prelink

-b *.png

-b *.py

-b *.pl

-b *.pm

-b *.sh

-b *.xml

-b *.xslt

-b *.a

-b *.js

-b /lib/modules

-b /usr/lib/locale

-l /bin--------------prelink目錄下所有文件

-l /usr/bin

-l /sbin

-l /usr/sbin

-l /usr/kerberos/bin

-l /usr/games

-l /usr/libexec

-l /var/ftp/bin

-l /lib{,64}

-l /usr/lib{,64}

-l /var/ftp/lib{,64}

 

實現原理:

在編譯時,將需要鏈接的動態庫,進行重定向修改,保證加載動態庫時,直接被訪問即可。

以如下代碼爲例add.c:

int add(int a,int b){return a+b};

a)編譯成libadd.so:gcc -fpic -shared -o libadd.so add.c

b)直接讀取libadd.so的動態鏈接庫加載信息:

Elf file type is DYN (Shared object file)

Entry point 0x310

There are 5 program headers, starting at offset 52

 

Program Headers:

  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align

  LOAD           0x000000 0x00000000 0x00000000 0x00448 0x00448 R E 0x1000

  LOAD           0x000448 0x00001448 0x00001448 0x000f8 0x00100 RW  0x1000

  DYNAMIC        0x000460 0x00001460 0x00001460 0x000c0 0x000c0 RW  0x4

  NOTE           0x0000d4 0x000000d4 0x000000d4 0x00024 0x00024 R   0x4

  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

c)然後prelink 之後的lib:prelink libadd.so,再讀取libadd.so的動態鏈接庫加載信息:

Elf file type is DYN (Shared object file)

Entry point 0x2fb2310

There are 5 program headers, starting at offset 52

 

Program Headers:

  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align

  LOAD           0x000000 0x02fb2000 0x02fb2000 0x00448 0x00448 R E 0x1000

  LOAD           0x000448 0x02fb3448 0x02fb3448 0x000f8 0x00100 RW  0x1000

  DYNAMIC        0x000460 0x02fb3460 0x02fb3460 0x000c0 0x000c0 RW  0x4

  NOTE           0x0000d4 0x02fb20d4 0x02fb20d4 0x00024 0x00024 R   0x4

  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

由此可見,地址已經被重定向過了,當該庫被加載到內存中時,就不用重新計算這些地址,而是直接被引用,從而加速程序加載運行。

d)查看prelink的結果:prelink -p|grep libadd

/work1/yiye/SPI_IWAT/Supernova/libadd.so [0xd493bb66] 0x02fb2000-0x02fb3548:

e)檢測prelink的運行效果——需要與沒有prelink過的程序進行比較然後得出結果:LD_DEBUG=statistics exe-file

  2248:     runtime linker statistics:

      2248:       total startup time in dynamic loader: 953296 clock cycles

      2248:                 time needed for relocation: 58792 clock cycles (6.1%)

      2248:                      number of relocations: 0

      2248:           number of relocations from cache: 70

      2248:             number of relative relocations: 0

      2248:                time needed to load objects: 687448 clock cycles (72.1%)

    深入理解:

    可以參考<<prelink.pdf>>或者man prelink

四)程序調試——gdb與coredump介紹

   功能介紹:

   程序調試工具,能夠動態追蹤程序運行,查看程序運行狀態;它是每個程序開發者都需要熟練使用的工具,值得反覆去使用,直到爐火純青爲止。

   使用說明:

 A)gdb的使用:

 1.生成帶debug信息的可執行文件
      編譯時,需要加入-g參數。
     2.本地調試與遠程調試(a到d步驟需要)的方法
     a)設備執行>gdbserver :port --attach pid
     b)在編譯環境中執行>cross-gdb 進入gdb命令行模式
     c)設置gdb的代碼路徑
     gdb>set solib-absolute-prefix symbols-path(模擬設備中的執行root根目錄)
     gdb>set solib-search-path debug-target-path(設備中執行命令庫的路徑)
     d)連接到設備中gdb>target remote ip:port
     f)加載動態鏈接庫gdb>add-symbols-file symbols addr
     g)設置斷點 b line/file(文件行設置斷點):line/function(函數設置斷點) *addr(給地址設置斷點)
     h)運行 r/c
     i)反彙編 disassemble start,+d
     j)查看寄存器的值 info regxxx
     l)查看堆棧的值 bt
     m)查看內存的值 x /32w
     n)查看變量的值 p var
     o)幫助 help xx yy zz
     p)單指令執行:ni/si n
     q)設置調試構架: set architecture i8086
    3.gdb dump 內存到文件

gdb >dump binary memory filename start end
    4.內存空間與對應的動態鏈接庫的對應關係
    a)通過readelf -s 讀取對應的函數的地址(funcname,func_addr)
    b)查看進程的內存映射 cat /proc/pid/maps。得到動態庫與程序對應的起始(p_start)與結束的地址。
    c)dump出進程的某個動態庫或者程序的某個段,將會看到readelf -S 所得到的段已經被加載。
    d)函數執行的地址爲:p_start+func_addr
    f)加載動態鏈接庫地址:p_start+addr(.text)
    5.gdb啓動執行init指令
    a)在本地目錄下創建.gdbinit
    b)可以在運行gdb之後,source .gdbinit
    c)可以以如下方式執行gdb:
    gdb -q -iex 'add-auto-load-safe-path .' .gdbinit

B)coredump的介紹:

1.core文件的生成開關和大小限制
    1)使用ulimit -c命令可查看core文件的生成開關。若結果爲0,則表示關閉了此功能,不會生成core文件。
    2)使用ulimit -c filesize命令,可以限制core文件的大小(filesize的單位爲kbyte)。若ulimit -c unlimited,則表示core文件的大小不受限制。如果生成的信息超過此大小,將會被裁剪,最終生成一個不完整的core文件。在調試此core文件的時候,gdb會提示錯誤。


    2.core文件的名稱和生成路徑
    core文件生成路徑:
    輸入可執行文件運行命令的同一路徑下。
    若系統生成的core文件不帶其它任何擴展名稱,則全部命名爲core。新的core文件生成將覆蓋原來的core文件。

        1)/proc/sys/kernel/core_uses_pid可以控制core文件的文件名中是否添加pid作爲擴展。文件內容爲1,表示添加pid作爲擴展名,生成的core文件格式爲core.xxxx;爲0則表示生成的core文件同一命名爲core。
可通過以下命令修改此文件:
echo "1" > /proc/sys/kernel/core_uses_pid

       2)proc/sys/kernel/core_pattern可以控制core文件保存位置和文件名格式。
可通過以下命令修改此文件:
echo "/corefile/core-%e-%p-%t" > core_pattern,可以將core文件統一生成到/corefile目錄下,產生的文件名爲core-命令名-pid-時間戳
以下是參數列表:
    %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 添加命令名

  深入理解:

     《單步調試Android系統源碼的方法》,gdb官方文檔——gdb-html,coredump分析——《core.pdf

  五)追蹤程序與內核交互(strace),分析程序內存使用(valgrind)

     功能介紹:

     strace分析可執行文件的所有系統調用;valgrind動態分析程序內存使用。它們都是很有用而功能強大的開發調試工具,能夠很方便調試程序運行,需要在實際使用過程中反覆使用。

使用說明:

跟蹤程序執行流程,與內核進行交互的細節。

strace exe-file

檢測是否內存泄漏與內存分析:

valgrind [options] prog-and-args

實例:

valgrind --tool=memcheck --leak-check=full --show-reachable=yes ./testM

深入理解:

詳情《strace.pdf/man strace;valgrind_manual.pdf



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章