万变不离其宗——程序动态分析(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%)

    深入理解:

    可以参考<>或者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




本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部