zoukankan      html  css  js  c++  java
  • Linux程序分析工具:ldd和nm

         ldd和nm是Linux下两个非常实用的程序分析工具。其中,ldd是用来分析程序运行时需要依赖的动态链接库的工具,nm是用来查看指定程序中的符号表信息的工具。


    1 ldd

    格式:ldd [options] file   

    功能:列出file运行所需的共享库

    参数:

          -d    执行重定位并报告所有丢失的函数

          -r    执行对函数和对象的重定位并报告丢失的任何函数或对象

    tanghuaming@Thm:~/Documents/sys_programming$ whereis ldd
    ldd: /usr/bin/ldd /usr/share/man/man1/ldd.1.gz
    tanghuaming@Thm:~/Documents/sys_programming$ ll /usr/bin/ldd
    -rwxr-xr-x 1 root root 5420  3月 26 14:59 /usr/bin/ldd*

        ldd能够显示可执行模块的dependency,其原理是通过设置一系列的环境变量,如下:LD_TRACE_LOADED_OBJECTS、LD_WARN、LD_BIND_NOW、LD_LIBRARY_VERSION、LD_VERBOSE等。当LD_TRACE_LOADED_OBJECTS环境变量不为空时,任何可执行程序在运行时,它都会只显示模块的dependency,而程序并不真正执行。要不你可以在shell终端测试一下,如下:

    (1) export LD_TRACE_LOADED_OBJECTS=1

    (2) 再执行任何的程序,如ls等,看看程序的运行结果。

        ldd显示可执行模块的dependency的工作原理,其实质是通过ld-linux.so(elf动态库的装载器)来实现的。我们知道,ld-linux.so模块会先于executable模块程序工作,并获得控制权,因此当上述的那些环境变量被设置时,ld-linux.so选择了显示可执行模块的dependency。

        实际上可以直接执行ld-linux.so模块,如:/lib/ld-linux.so.2 --list program(这相当于ldd program)ldd命令使用方法(摘自ldd --help)

    我们选择一段待测试的应用程序,代码如下:

    //@file tooltest.c
    //@brief resource sharing between parent-process and sub-process
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int global = 1; /*global variable, stored at data section*/
    
    int main(void)
    {
        pid_t pid;//to store pid value
        int   stack = 1;//local variable, stored at stack
        int  *heap;//pointer to a heap variable
    
        heap = (int *)malloc(sizeof(int));
        *heap = 2;//set the heap value to 2
    
        pid = fork();//create a new process
        if (pid < 0)
        {
            //error
            perror("fail to fork");
            exit(-1);
        }
        else if (pid == 0)
        {
            //sub-process, change values
            global++;
            stack++;
            (*heap)++;
            //print all values
            printf("In sub-process, global: %d, stack: %d, heap: %d
    ", global, stack, *heap);
            exit(0);
        }
        else
        {
            //parent process
            sleep(2);//sleep 2 secends to make sure the sub-process runs first
            printf("In parent-process, global: %d, stack: %d, heap: %d
    ", global, stack, *heap);
        }
    
        return 0;
    }

    然后,我们编译并运行ldd命令:

    xiaomanon@xiaomanon-machine:~/Documents/c_code$ ldd tooltest
        linux-gate.so.1 =>  (0xb775b000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7595000)
        /lib/ld-linux.so.2 (0xb775c000)

    我们可以将ldd的输出结果分为3列来看:

    ■ 第一列:程序需要依赖什么库

    ■ 第二列:系统提供的与程序需要的库对应的库名称

    ■ 第三列:依赖库加载的开始地址

    通过上面的这些信息,我们可以总结出下面的用途:

    (1) 通过对比第一列和第二列,我们可以知道程序需要的动态链接库和系统实际提供的是否相比配。

    (2) 通过第三列,我们可以知道当前动态链接库中的符号在进程地址空间中的起始位置。


    2 nm

    格式:nm [options] file   

    功能:列出file中的所有符号

    参数:

         -C   将符号转化为用户级的名字

         -s   当用于.a文件即静态库时,输出把符号名映射到定义该符号的模块或成员名的索引

         -u   显示在file外定义的符号或没有定义的符号

         -l   显示每个符号的行号,或为定义符号的重定义项

    下面是运行nm命令的输出结果:

    xiaomanon@xiaomanon-machine:~/Documents/c_code$ nm tooltest
    0804a038 B __bss_start
    0804a038 b completed.6590
    0804a02c D __data_start
    0804a02c W data_start
    08048450 t deregister_tm_clones
    080484c0 t __do_global_dtors_aux
    08049f0c t __do_global_dtors_aux_fini_array_entry
    0804a030 D __dso_handle
    08049f14 d _DYNAMIC
    0804a038 D _edata
    0804a03c B _end
             U exit@@GLIBC_2.0
    08048674 T _fini
             U fork@@GLIBC_2.0
    08048688 R _fp_hw
    080484e0 t frame_dummy
    08049f08 t __frame_dummy_init_array_entry
    080487e0 r __FRAME_END__
    0804a034 D global
    0804a000 d _GLOBAL_OFFSET_TABLE_
             w __gmon_start__
    08048354 T _init
    08049f0c t __init_array_end
    08049f08 t __init_array_start
    0804868c R _IO_stdin_used
             w _ITM_deregisterTMCloneTable
             w _ITM_registerTMCloneTable
    08049f10 d __JCR_END__
    08049f10 d __JCR_LIST__
             w _Jv_RegisterClasses
    08048670 T __libc_csu_fini
    08048600 T __libc_csu_init
             U __libc_start_main@@GLIBC_2.0
    0804850d T main
             U malloc@@GLIBC_2.0
             U perror@@GLIBC_2.0
             U printf@@GLIBC_2.0
    08048480 t register_tm_clones
             U sleep@@GLIBC_2.0
    08048410 T _start
    0804a038 D __TMC_END__
    08048440 T __x86.get_pc_thunk.bx

    上面便是tooltest这个程序中所有的符号,首先介绍一下上面输出内容的格式:

    ■ 第一列:当前符号的地址。

    ■ 第二列:当前符号的类型(关于类型的说明,可以查看手册页man nm详细阅读)。

    ■ 第三列:当前符号的名称。

    使用nm主要有一下几个方面的帮助:

    (1) 判断指定的程序中有没有指定的符号,比较常用的方式为:nm –C program | grep symbol

    (2) 解决程序编译时undefined reference的错误,以及multiple definition的错误。

    (3) 查看某个符号的地址,以及在进程空间的大概位置(.bss, .data, .text段,具体可以通过第二列的类型来判断)。

    部分符号类型说明

    A : 该符号的值是绝对的,在以后的链接过程中,不允许进行改变。这样的符号值,常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置。

    B : 该符号的值出现在非初始化数据段(.bss)中。例如,在一个文件中定义全局static int test。则该符号test的类型为b,位于bss section中。其值表示该符号在bss段中的偏移。一般而言,bss段分配于RAM中 。

    C : 该符号为common。common symbol是未初始话数据段。该符号没有包含于一个普通section中。只有在链接过程中才进行分配。符号的值表示该符号需要的字节数。例如在一个c文件中,定义int test,并且该符号在别的地方会被引用,则该符号类型即为C。否则其类型为B。

    D : 该符号位于初始化数据段中。一般来说,分配到.data section中。例如定义全局int baud_table[5] = {9600, 19200, 38400, 57600, 115200},则会分配于初始化数据段中。

    G : 该符号也位于初始化数据段中。主要用于small object提高访问small data object的一种方式。

    I : 该符号是对另一个符号的间接引用。

    N : 该符号是一个debugging符号。

    R : 该符号位于只读数据段。例如定义全局const int test[] = {123, 123};则test就是一个只读数据区的符号。注意在cygwin下如果使用gcc直接编译成MZ格式时,源文件中的test对应_test,并且其符号类型为D,即初始化数据段中。但是如果使用m6812-elf-gcc这样的交叉编译工具,源文件中的test对应目标文件的test,即没有添加下划线,并且其符号类型为R。一般而言,位于rodata section。值得注意的是,如果在一个函数中定义const char *test = “abc”, const char test_int = 3。使用nm都不会得到符号信息,但是字符串“abc”分配于只读存储器中,test在rodata section中,大小为4。

    S : 符号位于非初始化数据段,用于small object。

    T : 该符号位于代码段text section。

    U : 该符号在当前文件中是未定义的,即该符号的定义在别的文件中。例如,当前文件调用另一个文件中定义的函数,在这个被调用的函数在当前就是未定义的;但是在定义它的文件中类型是T。但是对于全局变量来说,在定义它的文件中,其符号类型为C,在使用它的文件中,其类型为U。

    V : 该符号是一个weak object。

    W : The symbol is a weak symbol that has not been specifically tagged as a weak object symbol.

    - : 该符号是a.out格式文件中的stabs symbol。

    ? : 该符号类型没有定义。

  • 相关阅读:
    数据分析2 numpy(ndarray数组,属性,创建,索引切片,运算,函数,随机数), Pandas(Series[创建,series数据对齐(相加),缺失值处理,特性,索引[整数索引loc和iloc]],DataFrame[索引与切片, 常用属性,数据对齐(相加),缺失值处理,常用方法],时间对象,时间序列), dateutil时间处理
    数据分析1 ipython, jupyter notebook(快捷键), anaconda软件
    CMDB4 总结CMDB,数据展示前后端不分离(xadmin第二种安装方法),前后端分离(vue-element-admin,iview-admin), 画图工具(highcharts,echarts,antv)
    CMDB3 完善采集端代码(ssh方案的多线程采集), 异常处理, 服务端目录结构的设计(django的app), API数据分析比对入库
    CMDB2 采集客户端目录架构设计, 高级配置文件, 可插拔式的采集
    CentOS, CMDB1 Linux命令补充(netstat,ps,kill,service,systemctl,top,wget,Irzsz,vim,磁盘使用情况,cpu情况,tree,history),linux常见的面试题, CMDB
    CentOS centos7的目录结构, 文件系统常用命令(pwd,cd,mkdir,touch,ls,cat,echo,cp,mv,rm), vim编辑器
    CentOS VMware安装CentOS7,配置网卡文件,Xshell5连接,快照,克隆,修改主机名
    flask框架4 表单验证, 表单查询wtforms,flask-migrate
    flask框架3 local对象补充,偏函数,请求上下文,g对象,flask-session组件,flask的信号,自定义信号(了解),用命令启动flask项目(flask-script)
  • 原文地址:https://www.cnblogs.com/xiaomanon/p/4203671.html
Copyright © 2011-2022 走看看