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(符號) 信息。

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

  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 文件的模板
 
 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"
  1. 鏈接
    1. 鏈接的時候需要帶上這幾個庫:bfd iberty dl z
    2. 例如,假設 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 程序會使用的途徑,包括 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

 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 符號,使它變回源代碼中找到的用戶級的名字。如果指定類型,會為如下類型之一:autognulucidarmhpedggnu-v3javagnat 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

顯示版本信息,然后退出

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM