编译是将高级编程语言编写的源代码转换为可执行程序的一系列步骤。对每一个*.cpp文件、*.c文件都会生成一个对应的目标文件(*.o结尾),再经过链接操作,生成最终的可执行程序。以test.cpp为例,介绍编译过程中的四部分:
#include
int main()
{
std::cout << "Hello World" << std::endl;
}
在这个阶段,预处理器会根据预处理指令对源代码进行处理。预处理指令以#
开头,常见的指令包括:
(1)#include
:将指定的头文件内容插入到当前位置。例如,#include
会将标准输入输出头文件的内容包含进来,以便程序可以使用其中定义的函数和变量。
(2)#define
:定义宏。宏可以是常量、表达式或代码片段的替换。例如,#define PI 3.14159
定义了一个名为PI
的宏,在后续的代码中,只要出现PI
,就会被替换为3.14159
。
(3)#ifdef
、#ifndef
、#endif
:用于条件编译。根据特定的条件决定是否包含某些代码。例如,可以使用这些指令来确保某些代码只在特定的平台或配置下被编译。
预处理后的结果是一个经过扩展和替换的源代码文件,通常以.i
为扩展名。
[root@abcd /aaaa/]#
[root@abcd /aaaa/]# g++ -E test.cpp -o a.i
[root@abcd /aaaa/]#
编译阶段将预处理后的源代码转换为汇编语言代码。编译器会对源代码进行语法分析、语义分析和优化,以确保代码的正确性和高效性。
(1)语法分析:检查代码是否符合编程语言的语法规则。如果发现语法错误,编译器会报告错误信息,编译过程停止。
(2)语义分析:检查代码的语义是否正确,例如变量的类型是否匹配、函数的调用是否合法等。如果发现语义错误,编译器也会报告错误信息。
(3)优化:编译器会对代码进行优化,以提高程序的执行效率。优化的方式包括去除不必要的代码、合并重复的计算、调整指令顺序等。
编译后的结果是汇编语言代码文件,通常以.s
为扩展名。
[root@abcd /aaaa/]#
[root@abcd /aaaa/]# g++ -S a.i -o a.s
[root@abcd /aaaa/]#
汇编阶段将汇编语言代码转换为机器语言代码。汇编器会将汇编指令转换为对应的机器指令,并生成目标文件。
目标文件包含了机器语言代码、数据和符号表等信息。符号表记录了程序中的变量、函数和标签等符号的名称和地址。
目标文件通常以.o
为扩展名。
[root@abcd /aaaa/]#
[root@abcd /aaaa/]# g++ -c a.s -o a.o
[root@abcd /aaaa/]#
链接阶段将多个目标文件和库文件组合成一个可执行程序。链接器会解决目标文件之间的引用关系,将它们合并成一个完整的程序。
(1)符号解析:链接器会解析目标文件中的符号引用,将它们与其他目标文件或库文件中的定义进行匹配。如果找不到符号的定义,链接器会报告错误信息。
(2)重定位:链接器会调整目标文件中的地址,以确保程序在运行时能够正确地访问内存中的数据和代码。例如,变量的地址可能会在链接过程中被确定,链接器会将对变量的引用调整为正确的地址。
链接后的结果是一个可执行程序文件,可以在操作系统上运行。
[root@abcd /aaaa/]#
[root@abcd /aaaa/]# ./a
Hello World
[root@abcd /aaaa/]#
[root@abcd /aaaa/]#
[root@abcd /aaaa/]#
[root@abcd /aaaa/]# g++ test.cpp -o a -v
Using built-in specs.
COLLECT_GCC=g++
............
............
COMPILER_PATH=..................
LIBRARY_PATH=...................
............
............
[root@abcd /aaaa/]# ./a
Hello World
[root@abcd /aaaa/]#
在整个编译过程中,会出现不同的编译报错。
错误描述 | 阶段 | 描述 |
---|---|---|
头文件找不到 | 第1阶段:预处理 | |
语法错误 | 第2阶段:编译 | |
符号未定义 | 第4阶段:链接 | |
符号重定义 | 第4阶段:链接 | |
recompile with -fPIC | 第4阶段:链接 |
3.1、readelf
命令 readelf
是一个在 Linux 系统中非常实用的命令行工具,主要用于显示 ELF(Executable and Linkable Format)格式文件的详细信息。ELF 格式是 Linux 下可执行文件、目标文件、共享库等的标准文件格式。
1、readelf -h
显示帮助信息。
[root@shell]
[root@shell]
[root@shell] readelf -h
readelf: Warning: Nothing to do.
Usage: readelf
2、readelf -s xxxx.so
readelf --syms xxxx.so
readelf --syms xxxx.so | grep "function_name"
此命令会显示 ELF 文件的符号表信息,符号表包含了文件中定义和引用的符号(如函数名、变量名等)。
[root@shell]
[root@shell]
[root@shell] readelf -s test.so
Symbol table '.dynsym' contains 5123 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit@LIBC (2)
2: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize@LIBC (2)
3: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_memclr8
4: 00000000 0 FUNC GLOBAL DEFAULT UND __stack_chk_fail@LIBC (2)
5: 00000000 0 OBJECT GLOBAL DEFAULT UND __stack_chk_guard@LIBC (2)
6: 00000000 0 FUNC GLOBAL DEFAULT UND __vsnprintf_chk@LIBC (2)
7: 00000000 0 OBJECT GLOBAL DEFAULT UND __sF@LIBC (2)
8: 00000000 0 FUNC GLOBAL DEFAULT UND fputs@LIBC (2)
9: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_memcpy
10: 00000000 0 FUNC GLOBAL DEFAULT UND __android_log_print
11: 00000000 0 FUNC GLOBAL DEFAULT UND __strlen_chk@LIBC (2)
12: 00000000 0 FUNC GLOBAL DEFAULT UND ceilf@LIBC (3)
13: 00000000 0 FUNC GLOBAL DEFAULT UND memchr@LIBC (2)
14: 00000000 0 FUNC GLOBAL DEFAULT UND memcmp@LIBC (2)
15: 00000000 0 FUNC GLOBAL DEFAULT UND strlen@LIBC (2)
16: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_memclr4
17: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_memcpy4
18: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_memmove
19: 00000000 0 FUNC GLOBAL DEFAULT UND sscanf@LIBC (2)
20: 00000000 0 FUNC GLOBAL DEFAULT UND vsnprintf@LIBC (2)
21: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_memclr
22: 00000000 0 FUNC GLOBAL DEFAULT UND log@LIBC (3)
23: 00000000 0 FUNC GLOBAL DEFAULT UND logf@LIBC (3)
24: 00000000 0 FUNC GLOBAL DEFAULT UND expf@LIBC (3)
25: 00000000 0 FUNC GLOBAL DEFAULT UND log1pf@LIBC (3)
26: 00000000 0 FUNC GLOBAL DEFAULT UND strcmp@LIBC (2)
..................
[root@shell]
[root@shell]
3、readelf -d xxxx.so
该命令会显示 ELF 文件的动态符号表信息,动态符号表主要用于动态链接过程。显示连接了哪些动态库文件。
[root@shell]
[root@shell]
[root@shell] readelf -d test.so
Dynamic section at offset 0x20545c contains 29 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [liblog.so]
0x00000001 (NEEDED) Shared library: [libm.so]
0x00000001 (NEEDED) Shared library: [libdl.so]
0x00000001 (NEEDED) Shared library: [libc.so]
0x0000000e (SONAME) Library soname: [test.so]
0x0000001e (FLAGS) SYMBOLIC BIND_NOW
0x6ffffffb (FLAGS_1) Flags: NOW
0x00000011 (REL) 0x72c34
0x00000012 (RELSZ) 35184 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffa (RELCOUNT) 4387
0x00000017 (JMPREL) 0x87bdc
0x00000002 (PLTRELSZ) 1424 (bytes)
0x00000003 (PLTGOT) 0x206924
0x00000014 (PLTREL) REL
0x00000006 (SYMTAB) 0x210
0x0000000b (SYMENT) 16 (bytes)
0x00000005 (STRTAB) 0x28b58
0x0000000a (STRSZ) 303321 (bytes)
0x6ffffef5 (GNU_HASH) 0x16aa8
0x00000004 (HASH) 0x1eb38
0x00000019 (INIT_ARRAY) 0x2062f4
0x0000001b (INIT_ARRAYSZ) 360 (bytes)
0x0000001a (FINI_ARRAY) 0x2062ec
0x0000001c (FINI_ARRAYSZ) 8 (bytes)
0x6ffffff0 (VERSYM) 0x14240
0x6ffffffe (VERNEED) 0x16a48
0x6fffffff (VERNEEDNUM) 3
0x00000000 (NULL) 0x0
[root@shell]
[root@shell]
nm
是一个在 Unix 和类 Unix 系统(如 Linux)中常用的命令行工具,主要用于列出目标文件(包括可执行文件、目标文件、共享库等)中的符号表信息。符号表记录了文件中定义和引用的符号,如函数名、变量名等
1、nm -h
显示帮助信息。
[root@shell]
[root@shell]
[root@shell] nm -h
Usage: nm [option(s)] [file(s)]
List symbols in [file(s)] (a.out by default).
The options are:
-a, --debug-syms Display debugger-only symbols
-A, --print-file-name Print name of the input file before every symbol
-B Same as --format=bsd
-C, --demangle[=STYLE] Decode low-level symbol names into user-level names
The STYLE, if specified, can be `auto' (the default),
`gnu', `lucid', `arm', `hp', `edg', `gnu-v3', `java'
or `gnat'
--no-demangle Do not demangle low-level symbol names
-D, --dynamic Display dynamic symbols instead of normal symbols
--defined-only Display only defined symbols
-e (ignored)
-f, --format=FORMAT Use the output format FORMAT. FORMAT can be `bsd',
`sysv' or `posix'. The default is `bsd'
-g, --extern-only Display only external symbols
-l, --line-numbers Use debugging information to find a filename and
line number for each symbol
-n, --numeric-sort Sort symbols numerically by address
-o Same as -A
-p, --no-sort Do not sort the symbols
-P, --portability Same as --format=posix
-r, --reverse-sort Reverse the sense of the sort
--plugin NAME Load the specified plugin
-S, --print-size Print size of defined symbols
-s, --print-armap Include index for symbols from archive members
--size-sort Sort symbols by size
--special-syms Include special symbols in the output
--synthetic Display synthetic symbols as well
-t, --radix=RADIX Use RADIX for printing symbol values
--target=BFDNAME Specify the target object format as BFDNAME
-u, --undefined-only Display only undefined symbols
-X 32_64 (ignored)
@FILE Read options from FILE
-h, --help Display this information
-V, --version Display this program's version number
nm: supported targets: elf64-x86-64 elf32-i386 elf32-iamcu elf32-x86-64 a.out-i386-linux pei-i386 pei-x86-64 elf64-l1om elf64-k1om elf64-little elf64-big elf32-little elf32-big plugin srec symbolsrec verilog tekhex binary ihex
Report bugs to .
[root@shell]
[root@shell]
[root@shell]
[root@shell]
[root@shell] readelf test.so --syms -W | grep simple
2025: 00140093 112 FUNC GLOBAL DEFAULT 14 _ZN4test_hh13HyStudClass19simpleERKNS_10VectorBaseIfEERS2_ia
3860: 00140129 56 FUNC GLOBAL DEFAULT 14 _ZN4test_hh13HyStudClass19simpleERKNS_10MatrixBaseIfEERNS_10VectorBaseIfEEia
[root@shell]
[root@shell]
每行内容包含以下几个部分:
序号: 地址 大小 类型 绑定信息 可见性 所在节索引 符号名称
(1)序号
如 2025、3860 等,这是符号在符号表中的索引,用于唯一标识该符号在符号表中的位置。
(2)地址
像 00140093、00140129 这类,代表符号在内存中的地址。在程序加载到内存后,该地址指向符号(如函数、变量)实际存储的位置。
(3)大小
例如 112、56 等,指的是符号所占用的内存字节数。对于函数而言,就是函数代码的大小;对于变量,就是变量占用的存储空间大小。
(4)类型
FUNC 表示该符号是一个函数。除了 FUNC,常见的类型还有:
OBJECT:代表变量或者数据对象。
NOTYPE:表示无法确定类型或者没有类型信息。
(5)绑定信息
GLOBAL 表明该符号是全局可见的。全局符号可以在不同的目标文件或者库中被引用,与之相对的是 LOCAL 符号,LOCAL 符号只能在定义它的文件内部使用。
(6)可见性
DEFAULT 是默认的可见性设置,意味着该符号遵循标准的链接和可见性规则。
(7)所在节索引
14 表示该符号位于目标文件的第 14 个节(section)中。节是 ELF 文件里的基本组成单元,不同的节存储不同类型的数据,例如 .text 节存储代码,.data 节存储已初始化的数据等。
(8)符号名称
像 _ZN4test_hh13HyStudClass19simpleERKNS_10VectorBaseIfEERS2_ia 这类,这是经过 C++ 名称修饰(Name Mangling)后的符号名。C++ 为了支持函数重载和命名空间等特性,会对函数名和类名进行修饰,从而保证符号名的唯一性。你可以使用 c++filt 工具对这些修饰后的名称进行还原。如下:
[root@shell]
[root@shell] echo "_ZN4test_hh13HyStudClass19simpleERKNS_10VectorBaseIfEERS2_ia" | c++filt
test_hh::HyStudClass::simple(esis::VectorBase const&, test_hh::VectorBase&, int, signed char)
[root@shell]
[root@shell]