GCC编译器原理(一)03------GCC 工具:gprof、ld、libbfd、libiberty 和libopcodes
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 |
显示使用次数为零的例程(按照调用计数和累积时间计算)。 |
例子:
1 #include <stdio.h> 2 #include <stdlib.h> 3 int a(void) 4 { 5 int i=0,g=0; 6 while(i++ < 100000) 7 { 8 g+=i; 9 } 10 11 return g; 12 } 13 14 int b(void) 15 { 16 int i=0,g=0; 17 18 while(i++ < 400000) 19 { 20 g +=i; 21 } 22 23 return g; 24 } 25 26 int main(int argc, char** argv) 27 { 28 int iterations; 29 30 if(argc != 2) 31 { 32 printf("Usage %s <No of Iterations>\n", argv[0]); 33 exit(-1); 34 } 35 else 36 iterations = atoi(argv[1]); 37 printf("No of iterations = %d\n", iterations); 38 39 while(iterations--) 40 { 41 a(); 42 b(); 43 } 44 }
应用程序包括两个函数: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(符号) 信息。
使用此工具需要注意的地方:
-
头文件包含
- 程序使用bfd,需要包含bfd.h头文件。但是,在包含 bfd.h 之前,还需要包含 config.h。即代码中需要有如下形式的文件包含:
- #include "config.h"
#include <bfd.h> -
config.h 不是系统的头文件,也不是bfd库的头文件,而是应用程序自己的头文件。
- 采用GNU autotools的项目,在编译前一般都会执行一下 configure 脚本,生成 Makefile 及 config.h文 件。
- 对于没有使用 GNU autotools 的应用,可以采用如下格式得到 config.h 文件,这个文件的内容,相当于是使用 GNU autotools 开发一个 hello world 项目而得到的 config.h,下面就是 config.h 文件的模板
1 /* config.h. Generated from config.h.in by configure. */ 2 /* config.h.in. Generated from configure.ac by autoheader. */ 3 4 /* Name of package */ 5 #define PACKAGE "hello" 6 7 /* Define to the address where bug reports for this package should be sent. */ 8 #define PACKAGE_BUGREPORT "bug-report@address" 9 10 /* Define to the full name of this package. */ 11 #define PACKAGE_NAME "hello" 12 13 /* Define to the full name and version of this package. */ 14 #define PACKAGE_STRING "hello 1.0" 15 16 /* Define to the one symbol short name of this package. */ 17 #define PACKAGE_TARNAME "hello" 18 19 /* Define to the home page for this package. */ 20 #define PACKAGE_URL "" 21 22 /* Define to the version of this package. */ 23 #define PACKAGE_VERSION "1.0" 24 25 /* Version number of package */ 26 #define VERSION "1.0"
-
链接
- 链接的时候需要带上这几个库:bfd iberty dl z
- 例如,假设 hello.c 是一个完整的使用 bfd 库的程序,则他的编译方法如:gcc hello.c -lbfd -liberty -ldl -lz
例子如下:
1 #include <stdio.h> 2 #include <stdint.h> 3 #include "config.h" 4 #include <bfd.h> 5 #include <string.h> 6 #include <malloc.h> 7 #include <sys/unistd.h> 8 #include <linux/elf.h> 9 10 /* 11 这里定义 3 个 static 变量,并把他们放到一个单独的 section 中。 12 后面,我们通过 bfd 找出这个 section,并得到这 3 个变量的内容。 13 同时,我们还通过符号查找操作,找到 a_haha 这个 static 变量的信息。 14 */ 15 static uint64_t a_haha __attribute__((section ("my_test_sec"))) =3; 16 static uint64_t b __attribute__((section ("my_test_sec"))) =7; 17 static uint64_t c __attribute__((section ("my_test_sec"))) =8; 18 19 /* 获取当前进程自己的elf文件路径 */ 20 int get_self_path(char *buf, int buf_len) 21 { 22 int ret = readlink("/proc/self/exe", buf, buf_len); 23 buf[ret]='\0'; 24 return ret; 25 } 26 27 void section_proc(bfd *abfd, asection *sect, PTR obj) 28 { 29 if (strcmp(sect->name, "my_test_sec")==0) 30 printf("section %s exists\n", sect->name); 31 } 32 33 void search_a_given_symbol(bfd *ibfd, const char *name) 34 { 35 long storage_needed; 36 asymbol **symbol_table; 37 long number_of_symbols; 38 long i; 39 symbol_info symbolinfo ; 40 41 storage_needed = bfd_get_symtab_upper_bound(ibfd); 42 43 symbol_table = (void *)(unsigned long)malloc(storage_needed); 44 number_of_symbols = bfd_canonicalize_symtab (ibfd, symbol_table); 45 46 printf("Scanning %ld symbols\n", number_of_symbols); 47 for(i=0;i<number_of_symbols;i++) 48 { 49 if (symbol_table[i]->section==NULL) continue; 50 51 bfd_symbol_info(symbol_table[i], &symbolinfo); 52 if (strcmp(name, symbolinfo.name)) continue; 53 54 printf("Section %s ", symbol_table[i]->section->name); 55 printf("Symbol \"%s\" value 0x%lx\n", symbolinfo.name, symbolinfo.value); 56 } 57 } 58 59 int main() 60 { 61 char our_self_path[1024]; 62 bfd *ibfd; 63 char **matching; 64 65 asection *psection; 66 67 bfd_init(); 68 69 get_self_path(our_self_path, sizeof(our_self_path)); 70 printf("our elf file path:%s\n", our_self_path); 71 72 ibfd = bfd_openr(our_self_path, NULL); 73 bfd_check_format_matches(ibfd, bfd_object, &matching); 74 75 printf("number of sections = %d\n", bfd_count_sections(ibfd)); 76 77 /* 遍历所有 section,让 section_proc 对每一个 section 进行处理 */ 78 bfd_map_over_sections(ibfd, section_proc, NULL); 79 80 /* 查找特定名称的 section ,打印出其信息 */ 81 psection = bfd_get_section_by_name(ibfd, "my_test_sec"); 82 printf("section name=%s; start_address=0x%lx; size=%ld\n", psection->name, psection->vma, psection->size); 83 84 /* 打印出my_test_sec section中的 3 个 uint64_t 变量的值 */ 85 { 86 uint64_t *pu64 = (void *) psection->vma; 87 printf("%lu %lu %lu \n", pu64[0], pu64[1], pu64[2]); 88 } 89 90 printf("address of a_haha=%p\n", &a_haha); 91 92 /* 遍历所有符号,以找出名称为 a_haha 的符号 */ 93 search_a_given_symbol(ibfd, "a_haha"); 94 return 0; 95 }
编译:gcc test.c -lbfd -liberty -ldl -lz
执行如下:
1.3.11 libiberty
包含多个 GNU 程序会使用的途径,包括 getopt、obstack、strerror、strtol 和 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
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void bye(void) 5 6 { 7 8 printf("good bye!\n"); 9 10 }
hello.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void hello(void) 5 { 6 printf("hello!\n"); 7 }
main.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(int argc, char *argv[]) 5 { 6 hello(); 7 bye(); 8 return 0; 9 }
执行命令: 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 |
显示版本信息,然后退出 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)