1.3.7 gprof:性能分析工具

参考文档:https://www.cnblogs.com/andashu/p/6378000.html

gprof是GNU profile工具,可以运行于linux、AIX、Sun等操作系统进行C、C++、Pascal、Fortran程序的性能分析,用于程序的性能优化以及程序瓶颈问题的查找和解决。通过分析应用程序运行时产生的 "flat profile",可以得到每个函数的调用次数,每个函数消耗的处理器时间,也可以得到函数的 "调用关系图" ,包括函数调用的层次关系,每个函数调用花费了多少时间。

  • Gprof具有以下优缺点:
    • 优点:
      • GNU工具,人手一个;
      • 混合方法采集信息。
    • 缺点:
      • 需要编译选项支持:
        • 使用 gcc/cc 或 g++ 编译和链接时需要加入 -pg 选项;
          • 例如:gcc -pg -o test test.cpp ,编译器会自动在目标代码中插入用于性能测试的代码片断,这些代码在程序运行时采集并记录函数的调用关系和调用次数,并记录函数自身执行时间和被调用函数的执行时间。
          • 执行编译后的可执行程序,如:./test。该步骤运行程序的时间会稍慢于正常编译的可执行程序的运行时间。程序运行结束后,会在程序所在路径下生成一个缺省文件名为 gmon.out 的文件,这个文件就是记录程序运行的性能、调用关系、调用次数等信息的数据文件。
          • 使用 gprof 命令来分析记录程序运行信息的 gmon.out 文件,如:gprof test gmon.out 则可以在显示器上看到函数调用相关的统计、分析信息。上述信息也可以采用 gprof test gmon.out > gprofresult.txt 重定向到文本文件以便于后续分析。
        • 使用 ld 链接时需要用 /lib/gcrt0.o 代替 crt0.o 作为第一个 input 文件
        • 如果要调试 libc 库需要使用 -lc_p代替 -lc 参数
      • 调试多线程程序只能统计主线程的信息(所以不能用于 kingbase)。

命令行选项如下:

选项

描述

-b

不再输出统计图表中每个字段的详细描述。

-q

只输出函数的调用图(Call graph的那部分信息)。

-p

只输出函数的时间消耗列表。

-e Name

不再输出函数 Name 及其子函数的调用图(除非它们有未被限制的其它父函数)。可以给定多个 -e 标志。一个 -e 标志只能指定一个函数。

-E Name

不再输出函数 Name 及其子函数的调用图,此标志类似于 -e 标志,但它在总时间和百分比时间的计算中排除了由函数 Name 及其子函数所用的时间。

-f Name

输出函数 Name 及其子函数的调用图。可以指定多个 -f 标志。一个 -f 标志只能指定一个函数。

-F Name

输出函数 Name 及其子函数的调用图,它类似于 -f 标志,但它在总时间和百分比时间计算中仅使用所打印的例程的时间。可以指定多个 -F 标志。一个 -F 标志只能指定一个函数。-F 标志覆盖 -E 标志。

-z

显示使用次数为零的例程(按照调用计数和累积时间计算)。

例子:

 #include <stdio.h>
#include <stdlib.h>
int a(void)
{
int i=,g=;
while(i++ < )
{
g+=i;
} return g;
} int b(void)
{
int i=,g=; while(i++ < )
{
g +=i;
} return g;
} int main(int argc, char** argv)
{
int iterations; if(argc != )
{
printf("Usage %s <No of Iterations>\n", argv[]);
exit(-);
}
else
iterations = atoi(argv[]);
printf("No of iterations = %d\n", iterations); while(iterations--)
{
a();
b();
}
}
 

应用程序包括两个函数:a 和 b,它们通过运行不同次数的循环来消耗不同的CPU时间。

main 函数中采用了一个循环来反复调用这两个函数。函数 b 中循环的次数是 a 函数的 4 倍,因此我们期望通过 gprof 的分析结果可以观察到大概有 20% 的时间花在了 a 函数中,而 80% 的时间花在了 b 函数中。

编译程序:gcc test.c -pg -o test -O2 -lc

运行并传入参数:./test 50000

程序运行完之后,会在目录下生成一个 gmon.out 文件:

使用 gprof 命令分析分析 gmon.out 文件gprof test gmon.out -p

程序运行时间太短,所以 gprof 无效,若是大程序即可使用此来分析。

上面那些参数得含义如下:

名称

含义

%time

函数以及衍生函数(函数内部再次调用的子函数)所占的总运行时间的百分比

cumulative seconds

函数累计执行的时间

self seconds

函数执行占用的时间

calls

函数的调用次数

self ms/call

每一次调用函数花费的时间microseconds,不包括衍生函数的运行时间

total ms/call

每一次调用函数花费的时间microseconds,包括衍生函数的运行时间

name

函数名称

1.3.8 ld:GNU 链接器

ld 是 GNU 工具链中的一个软件,主要用于将 obj 文件链接成可执行文件。同时可以使用自己的脚本来控制 ld 的行为,可以通过 -T 选项选择自己的脚本而不是默认的。

选项

描述

-static

静态链接

-l<libname>

指定链接某个库

-e name

指定 name 为程序入口

-r

合并目标文件,不进行最终链接

-L <directory>

指定链接时查找路径,多个路径之间用冒号隔开

-M

将链接时的符号和地址输出成一个映射文件

-o

指定输出的文件名

-s

清除输出文件中的符号信息

-shared

链接器生成一个 Linux 上使用的动态库

-S

清除输出文件中的调试信息

-T <scriptfile>

指定链接脚本文件

-Ttext <address>

指定 text 段的地址

-version-script <file>

指定符号版本脚本文件

-soname <name>

指定输出动态库的 SONAME

-export-dynamic

将全局符号全部导出

-verbose

链接时输出详细信息

-rpath <path>

指定链接时库查找路径

--help

查看链接器的帮助信息

1.3.9 libbfd:二进制文件描述器

参考文档:https://blog.csdn.net/crazycoder8848/article/details/51456297

libbfd 工具不会在安装 binutils 的时候自动安装,需要在 binutils 安装包的 bfd 文件夹下单独安装。

在安装完 binutils 工具之后就可以看到此工具。安装完成后,会生成如下文件:

  • /usr/local/include/bfd.h
  • /usr/local/lib/libbfd.a

可以利用此工具获取 elf 可执行文件的 section(节) 及 symbol(符号) 信息。

使用此工具需要注意的地方:

  1. 头文件包含
    1. 程序使用bfd,需要包含bfd.h头文件。但是,在包含 bfd.h 之前,还需要包含 config.h。即代码中需要有如下形式的文件包含:
    2. #include "config.h"
          #include <bfd.h>
    3. config.h 不是系统的头文件,也不是bfd库的头文件,而是应用程序自己的头文件。
      1. 采用GNU autotools的项目,在编译前一般都会执行一下 configure 脚本,生成 Makefile 及 config.h文 件。
      2. 对于没有使用 GNU autotools 的应用,可以采用如下格式得到 config.h 文件,这个文件的内容,相当于是使用 GNU autotools 开发一个 hello world 项目而得到的 config.h,下面就是 config.h 文件的模板
 
 /* config.h.  Generated from config.h.in by configure.  */
/* config.h.in. Generated from configure.ac by autoheader. */ /* Name of package */
#define PACKAGE "hello" /* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "bug-report@address" /* Define to the full name of this package. */
#define PACKAGE_NAME "hello" /* Define to the full name and version of this package. */
#define PACKAGE_STRING "hello 1.0" /* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "hello" /* Define to the home page for this package. */
#define PACKAGE_URL "" /* Define to the version of this package. */
#define PACKAGE_VERSION "1.0" /* Version number of package */
#define VERSION "1.0"
  1. 链接
    1. 链接的时候需要带上这几个库:bfd iberty dl z
    2. 例如,假设 hello.c 是一个完整的使用 bfd 库的程序,则他的编译方法如:gcc hello.c -lbfd -liberty -ldl -lz

例子如下:

 #include <stdio.h>
#include <stdint.h>
#include "config.h"
#include <bfd.h>
#include <string.h>
#include <malloc.h>
#include <sys/unistd.h>
#include <linux/elf.h> /*
这里定义 3 个 static 变量,并把他们放到一个单独的 section 中。
后面,我们通过 bfd 找出这个 section,并得到这 3 个变量的内容。
同时,我们还通过符号查找操作,找到 a_haha 这个 static 变量的信息。
*/
static uint64_t a_haha __attribute__((section ("my_test_sec"))) =;
static uint64_t b __attribute__((section ("my_test_sec"))) =;
static uint64_t c __attribute__((section ("my_test_sec"))) =; /* 获取当前进程自己的elf文件路径 */
int get_self_path(char *buf, int buf_len)
{
int ret = readlink("/proc/self/exe", buf, buf_len);
buf[ret]='\0';
return ret;
} void section_proc(bfd *abfd, asection *sect, PTR obj)
{
if (strcmp(sect->name, "my_test_sec")==)
printf("section %s exists\n", sect->name);
} void search_a_given_symbol(bfd *ibfd, const char *name)
{
long storage_needed;
asymbol **symbol_table;
long number_of_symbols;
long i;
symbol_info symbolinfo ; storage_needed = bfd_get_symtab_upper_bound(ibfd); symbol_table = (void *)(unsigned long)malloc(storage_needed);
number_of_symbols = bfd_canonicalize_symtab (ibfd, symbol_table); printf("Scanning %ld symbols\n", number_of_symbols);
for(i=;i<number_of_symbols;i++)
{
if (symbol_table[i]->section==NULL) continue; bfd_symbol_info(symbol_table[i], &symbolinfo);
if (strcmp(name, symbolinfo.name)) continue; printf("Section %s ", symbol_table[i]->section->name);
printf("Symbol \"%s\" value 0x%lx\n", symbolinfo.name, symbolinfo.value);
}
} int main()
{
char our_self_path[];
bfd *ibfd;
char **matching; asection *psection; bfd_init(); get_self_path(our_self_path, sizeof(our_self_path));
printf("our elf file path:%s\n", our_self_path); ibfd = bfd_openr(our_self_path, NULL);
bfd_check_format_matches(ibfd, bfd_object, &matching); printf("number of sections = %d\n", bfd_count_sections(ibfd)); /* 遍历所有 section,让 section_proc 对每一个 section 进行处理 */
bfd_map_over_sections(ibfd, section_proc, NULL); /* 查找特定名称的 section ,打印出其信息 */
psection = bfd_get_section_by_name(ibfd, "my_test_sec");
printf("section name=%s; start_address=0x%lx; size=%ld\n", psection->name, psection->vma, psection->size); /* 打印出my_test_sec section中的 3 个 uint64_t 变量的值 */
{
uint64_t *pu64 = (void *) psection->vma;
printf("%lu %lu %lu \n", pu64[], pu64[], pu64[]);
} printf("address of a_haha=%p\n", &a_haha); /* 遍历所有符号,以找出名称为 a_haha 的符号 */
search_a_given_symbol(ibfd, "a_haha");
return ;
}
 

编译:gcc test.c -lbfd -liberty -ldl -lz

执行如下:

1.3.11 libiberty

包含多个 GNU 程序会使用的途径,包括 getoptobstackstrerrorstrtol 和 strtoul。

这只是一个库文件,具体用法需要看源码。

Ubuntu下执行 sudo apt-get install libiberty-dev 安装此库

1.3.12 libopcodes

用来处理 opcodes("可读文本格式的")处理器操作指令的库, 在生成一些应用程序的时候也会用到它,比如objdump

1.3.13 nlmconv

1.3.14 nm:列出目标文件中的符号

nm用来列出目标文件中的符号,可以帮助程序员定位和分析执行程序和目标文件中的符号信息和它的属性。利用命令行选项,可以根据符号的地址、尺寸或名字组织这些符号,而且可以按照很多方式格式化该输出结果。符号也可以被demangled,产生的结果和源代码中的一样。

如果没有目标文件作为参数传递给nm,nm 假定目标文件为 a.out。

来个例子:

bye.c

 #include <stdio.h>
#include <stdlib.h> void bye(void) { printf("good bye!\n"); }

hello.c

 #include <stdio.h>
#include <stdlib.h> void hello(void)
{
printf("hello!\n");
}

main.c

 #include <stdio.h>
#include <stdlib.h> int main(int argc, char *argv[])
{
hello();
bye();
return ;
}

执行命令: gcc -Wall -c main.c hello.c bye.c

gcc 生成 main.o,hello.o,bye.o 三个目标文件(这里没有声明函数原型,加了-Wall,gcc会给出警告)

执行命令:nm main.o hello.o bye.o

  • 结合这些输出结果,以及程序代码,可以知道:
    • 对于main.o, bye和hello未被定义, main被定义了
    • 对于hello.o,hello被定义了,puts未被定义
    • 对于bye.o, bye被定义了,puts未被定义
  • 几个值得注意的问题:
    • "目标文件" 指 .o文件, 库文件, 最终的可执行文件
      • .o : 编译后的目标文件,即含有最终编译出的机器码,但它里面所引用的其他文件中函数的内存位置尚未定义.
    • 如果用 nm 查看可执行文件,输出会比较多,仔细研究输出,可以对 nm 用法有更清醒的认识。
    • 在上述 hello.c,bye.c 中,调用的是 printf(),而 nm 输出中显示调用的是 puts(),说明最终程序实际调用的 puts(),如果令 hello.c 或 bye.c 中的 printf() 使用格式化输出,则 nm 显示调用 printf()。( 如: printf("%d", 1); )

选项

描述

-A

同选项 --print-file-name

-a

同选项 --debug-syms

-B

同选项 --format=bsd。这是默认设置

-C [type]

同选项 --demangle

-D

同选项 –dynamic

--debug-syms

显示调试器使用的符号。通常不会显示这些符号

--demangle[=type]

demangles 符号,使它变回源代码中找到的用户级的名字。如果指定类型,会为如下类型之一:auto、gnu、lucid、arm、hp、edg、gnu-v3、java、gnat 或 compaq

--dynamic

对于动态目标,例如共享库,该选项可以显示动态符号而不是普通符号

--extern-only

显示定义为外部的符号

-f fmt

同选项 --format

--format=fmt

使用指定的输出格式显示符号。可选的格式包括 bsd、sysv 和 posix,其中 bsd 为默认格式

-g

同选项 --extern-only

-h

显示选项列表,然后退出

--help

显示选项列表,然后退出

-l

同选项 --line-numbers

--line-numbers

使用文件中保存的调试信息来确定文件名和每个符号的行号

-n

同选项 --numeric-sort

--no-sort

指出不要将符号排序

--numeric-sort

按照符号地址的数值排序

-o

同选项 --print-file-name

-p

同选项 --no-sort

-P

同选项 --format=posix

--portability

同选项 --format=posix

--print-armap

在列举静态库成员符号时,该选项包含了模块的索引信息及其他信息,正是该模块含有所列符号

--print-file-name

用源文件的名字标记每个符号,而不是只在文件头命名源文件

-r

同选项 --reverse-sort

--radix=base

指出打印符号值的数字的进制。可选的为 d 的十进制、o 的八进制、或 x 的十六进制

--reverse-sort

反序排列,不论字母还是数字均可

-s

同选项 --print-armap

--size-sort

按照尺寸进行符号排序。计算尺寸取的是下一个符号的最高地址和本符号的地址的差。输出列出的是尺寸而不是通常的地址

-t base

同选项 --radix

--target=bfdname

bfdname 是目标文件格式的名字,它不是当前机器的名字。为得到已知格式名字列表,键入命令 objdump –i

-u

同选项--undefined-only

--undefined-only

只显示文件引用但未定义的符号

-V

同选项 --version

--version

显示版本信息,然后退出

GCC编译器原理(一)03------GCC 工具:gprof、ld、libbfd、libiberty 和libopcodes的更多相关文章

  1. GCC编译器原理(二)------编译原理一:目标文件

    一.目标文件 在 UNIX® 和 Linux® 中,任何事物都是文件.UNIX 和 Linux 编程实际上是编写处理各种文件的代码.系统由许多类型的文件组成,但目标文件具有一种特殊的设计,提供了灵活和 ...

  2. GCC编译器原理(一)------交叉编译器制作和GCC组件及命令

    1.1 交叉编译器制作 默认安装的 GCC 编译系统所产生的代码适用于本机,即运行 GCC 的机器,但也可将 GCC 安装成能够生成其他的机器代码.安装一些必须的模块,就可产生多种目标机器代码,而且可 ...

  3. GCC编译器原理(三)------编译原理三:编译过程(2-2)---编译之语法分析

    2.2 语法分析 语法分析器(Grammar Parser)将对由扫描器产生的记号进行语法分析,从而产生语法树(Syntax Tree).整个分析过程采用了上下文无关语法(Context-free G ...

  4. GCC编译器原理(一)------GCC 工具:addr2line、ar、as、c++filt和elfedit

    1.3 GCC 工具 1.3.1 binutils 工具集 工具 描述 addr2line 给出一个可执行文件的内部地址,addr2line 使用文件中的调试信息将地址翻译成源代码文件名和行号. ar ...

  5. GCC编译器原理(一)04------GCC 工具:nlmconv、nm、objcopy、objdump和 ranlib

    1.3.13 nlmconv nlmconv 将可重定位的对象文件(Infile)转换为 NetWare 可加载模块(outfile),并可选择读取头文件信息获取 NLM 头信息. 选项,描述 -I ...

  6. GCC编译器原理(一)05------GCC 工具:readelf、size、strings、strip和 windres

    1.3.18 readelf:elf 文件格式分析工具 这个工具和 objdump 命令提供的功能类似,但是它显示的信息更为具体,并且它不依赖 BFD 库( BFD 库是一个 GNU 项目,它的目标就 ...

  7. GCC编译器原理(三)------编译原理三:编译过程(3)---编译之汇编以及静态链接【2】

    4.1.2 符号解析与重定位 (1)重定位 在完成空间和地址的分配步骤之后,链接器就进入了符号解析和重定位的步骤,这是静态链接的核心部分. 先看看 a.o 的反汇编文件: objdump -d a.o ...

  8. GCC编译器原理(三)------编译原理三:编译过程(2-1)---编译之词法分析

    二.编译 引用文档:https://blog.csdn.net/chdhust/article/details/9040647 编译过程就是把预处理完的文件进行一系列词法分析.语法分析.语义分析及优化 ...

  9. GCC编译器原理(三)------编译原理三:编译过程---预处理

    Gcc的编译流程分为了四个步骤: 预处理,生成预编译文件(.文件):gcc –E hello.c –o hello.i 编译,生成汇编代码(.s文件):gcc –S hello.i –o hello. ...

随机推荐

  1. Visible Trees HDU - 2841(容斥)

    对于已经满足条件的(x1,y1),不满足条件的点就是(n*x1,n*y1),所以要求的就是满足点(x,y)的x,y互质,也就是gcd(x,y) == 1,然后就可以用之前多校的方法来做了 另f[i] ...

  2. AHOI中国象棋(dp)

    大力dp题. 每行每列最多放两个,考虑用行作为dp阶段. dp[i][j][k]表示i行,有一个的有j列,有两个的有k列. 然后就是分类讨论. 一个都不放,放一个在0出,放一个在1出,放两个在0,放两 ...

  3. [luogu2296][寻找道路]

    直接赋题目..... 题目描述 在有向图G 中,每条边的长度均为1 ,现给定起点和终点,请你在图中找一条从起点到终点的路径,该路径满足以下条件: 1 .路径上的所有点的出边所指向的点都直接或间接与终点 ...

  4. react-native中的style

    在 React Native 中,你并不需要学习什么特殊的语法来定义样式.我们仍然是使用 JavaScript 来写样式. 所有的核心组件都接受名为style的属性.这些样式名基本上是遵循了 web ...

  5. 都是分号惹的祸 ORA-00911

    使用JMeter连接oracle数据库,访问JDBC 请求,执行结果提示:ORA-00911: ??Ч??? 意思为无效的字符错误 说明了在执行的的SQL语句中出现了无效字符,所以在AQL语句无法通过 ...

  6. latex 导入pdf

    pdflatex \includepdf[addtotoc={1,section,1,something would show in catalog,cc},pages=-,offset=0cm 0. ...

  7. Eclipse搭建SpringBoot之HelloWorld

    你的eclipse需要先安装 Spring Tool Suite™ 第一种方法(不建议,之所以贴上是因为探索的过程) 首先新建Maven工程 勾选第一个按钮,第三个是选择working set ,你可 ...

  8. Unity 光照着色器

    光照着色器需要考虑光照的分类,一般分为漫反射和镜面反射. 漫反射计算基本光照: float brightness=dot(normal,lightDir)    将法线和光的入射方向进行点积运算,求出 ...

  9. fork()、vfork()、clone()的区别

    因为生活的复杂,这是一个并行的世界,在同一时刻,会发生很多奇妙的事情,北方下雪,南方下雨,这里在吃饭,那边在睡觉,有人在学习,有人在运动,所以这时一个多彩多姿的世界,每天都发生着很多事情,所以要想很好 ...

  10. Luogu P3868 [TJOI2009]猜数字

    题目链接 \(Click\) \(Here\) 中国剩余定理的板子.小心取模. #include <bits/stdc++.h> using namespace std; const in ...