交叉编译链安装及工具集(gcc、readelf、objdump、objcopy和strip等)使用方法
主要解决编译链程序和目标程序运行环境不同的问题,如在x86环境上使用编译工具进行编译汇编链接,而生成的程序需要运行在ARM开发板上。
目录
1.为什么要有交叉编译?
主要解决编译链程序和目标程序运行环境不同的问题,如在x86环境上使用编译工具进行编译汇编链接,而生成的程序需要运行在ARM开发板上。
2.如何安装交叉编译链?
下载交叉编译链压缩包,下载链接:
https://developer.arm.com/open-source/gnu-toolchain
https://releases.linaro.org/components/toolchain/binaries/
使用如下命令解压:
//解压到根目录
sudo tar -xvf gcc-arm-11.2-2022.02-x86_64-arm-none-linux-gnueabihf.tar.xz -C /opt
注意:不同的压缩格式,命令也不同。
.tar.gz 格式解压为 tar -zxvf xx.tar.gz
.tar.bz2 格式解压为 tar -jxvf xx.tar.bz2
最后编辑 vim ~/.bashrc 或者 sudo vim /etc/environment,在文件末尾追加PATH环境变量:
export PATH=$PATH:/opt/gcc-arm-11.2-2022.02-x86_64-arm-none-linux-gnueabihf/bin
使用 source ~/.bashrc 或者 source /etc/environment 使得新的PATH环境变量在当前shell生效。
或者通过sudo vi /etc/environment编辑环境变量,将交叉编译链的bin文件夹路径添加到环境变量中,通过source /etc/environment重新加载环境变量。
若交叉编译工具在32bit系统下生产的,需要在64bit下运行,则需要安装32bit对应的库。使用以下方法:
sudo apt-get install ia32-libs
//或者
sudo apt-get install libc6:i386
//或者
sudo yum install libXtst.i686
//可能还需要安装
sudo apt-get install lib32z1
安装完后使用gcc -v查看是否正常显示。
[root@localhost bin]# ./arm-none-linux-gnueabihf-gcc -v
Using built-in specs.
COLLECT_GCC=./arm-none-linux-gnueabihf-gcc
COLLECT_LTO_WRAPPER=/opt/gcc-arm-11.2-2022.02-x86_64-arm-none-linux-gnueabihf/bin/../libexec/gcc/arm-none-linux-gnueabihf/11.2.1/lto-wrapper
Target: arm-none-linux-gnueabihf
Configured with: /data/jenkins/workspace/GNU-toolchain/arm-11/src/gcc/configure --target=arm-none-linux-gnueabihf --prefix= --with-sysroot=/arm-none-linux-gnueabihf/libc --with-build-sysroot=/data/jenkins/workspace/GNU-toolchain/arm-11/build-arm-none-linux-gnueabihf/install//arm-none-linux-gnueabihf/libc --with-bugurl=https://bugs.linaro.org/ --enable-gnu-indirect-function --enable-shared --disable-libssp --disable-libmudflap --enable-checking=release --enable-languages=c,c++,fortran --with-gmp=/data/jenkins/workspace/GNU-toolchain/arm-11/build-arm-none-linux-gnueabihf/host-tools --with-mpfr=/data/jenkins/workspace/GNU-toolchain/arm-11/build-arm-none-linux-gnueabihf/host-tools --with-mpc=/data/jenkins/workspace/GNU-toolchain/arm-11/build-arm-none-linux-gnueabihf/host-tools --with-isl=/data/jenkins/workspace/GNU-toolchain/arm-11/build-arm-none-linux-gnueabihf/host-tools --with-arch=armv7-a --with-fpu=neon --with-float=hard --with-mode=thumb --with-arch=armv7-a --with-pkgversion='GNU Toolchain for the Arm Architecture 11.2-2022.02 (arm-11.14)'
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 11.2.1 20220111 (GNU Toolchain for the Arm Architecture 11.2-2022.02 (arm-11.14))
3.交叉编译工具集介绍
常用的工具有gcc、readelf、size、nm、strip、strings、objdump和addr2line。可以使用-h选项查看各个命令的使用方法。下面我们对各个工具逐一介绍。
使用简单的helloworld作为示例。
#include <stdlib.h>
#include <stdio.h>
int g_val = 12;
int g_uninit;
const char * str = "who am I?";
static char s_uninit;
int main()
{
printf("hello world!\n");
int* p = malloc(sizeof(int));
static int s_tmp = 0;
free(p);
return 0;
}
3.1 gcc命令
3.1.1 常用编译命令选项
-
无选项编译链接
用法:#gcc main.c
作用:将main.c预处理、汇编、编译并链接形成可执行文件。这里未指定输出文件,默认输出为a.out。 -
选项 -o
用法:#gcc main.c -o main
作用:将main.c预处理、汇编、编译并链接形成可执行文件main。-o选项用来指定输出文件的文件名。 -
选项 -E
用法:#gcc -E main.c -o main.i
作用:将main.c预处理输出main.i文件。 -
选项 -S
用法:#gcc -S main.i
作用:将预处理输出文件main.i汇编成main.s文件。 -
选项 -c
用法:#gcc -c main.s
作用:将汇编输出文件main.s编译输出main.o文件。 -
无选项链接
用法:#gcc main.o -o main
作用:将编译输出文件main.o链接成最终可执行文件main。 -
选项-O
用法:#gcc -O1 main.c -o main
作用:使用编译优化级别1编译程序。级别为1~3,级别越大优化效果越好,但编译时间越长。
3.1.2 多源文件的编译方法
如果有多个源文件,基本上有两种编译方法:
[假设有两个源文件为main.c和testfun.c]
-
多个文件一起编译
用法:#gcc testfun.c main.c -o main
作用:将testfun.c和main.c分别编译后链接成main可执行文件。 -
分别编译各个源文件,之后对编译后输出的目标文件链接。
用法:
#gcc -c testfun.c //将testfun.c编译成testfun.o
#gcc -c main.c //将main.c编译成main.o
#gcc -o testfun.o test.o -o main //将testfun.o和test.o链接成main
以上两种方法相比较,第一中方法编译时需要所有文件重新编译,而第二种方法可以只重新编译修改的文件,未修改的文件不用重新编译。
3.1.3 库文件链接
开发软件时,完全不使用第三方函数库的情况是比较少见的,通常来讲都需要借助许多函数库的支持才能够完成相应的功能。从程序员的角度看,函数库实际上就是一些头文件(.h)和库文件(so、或lib、dll)的集合。。虽然Linux下的大多数函数都默认将头文件放到/usr/include/目录下,而库文件则放到/usr/lib/目录下;Windows所使用的库文件主要放在Visual Stido的目录下的include和lib,以及系统文件夹下。但也有的时候,我们要用的库不再这些目录下,所以GCC在编译时必须用自己的办法来查找所需要的头文件和库文件。
例如我们的程序test.c是在linux上使用c连接mysql,这个时候我们需要去mysql官网下载MySQL Connectors的C库,下载下来解压之后,有一个include文件夹,里面包含mysql connectors的头文件,还有一个lib文件夹,里面包含二进制so文件libmysqlclient.so
其中inclulde文件夹的路径是/usr/dev/mysql/include,lib文件夹是/usr/dev/mysql/lib
(1) 编译成可执行文件
首先我们要进行编译main.c为目标文件,这个时候需要执行
gcc –c –I /usr/dev/mysql/include main.c –o main.o
(2) 链接最后我们把所有目标文件链接成可执行文件:
gcc –L /usr/dev/mysql/lib –lmysqlclient main.o –o main
Linux下的库文件分为两大类分别是动态链接库(通常以.so结尾)和静态链接库(通常以.a结尾),二者的区别仅在于程序执行时所需的代码是在运行时动态加载的,还是在编译时静态加载的。
(3) 强制链接时使用静态链接库
默认情况下, GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要的话可以在编译时加上-static选项,强制使用静态链接库。
在/usr/dev/mysql/lib目录下有链接时所需要的库文件libmysqlclient.so和libmysqlclient.a,为了让GCC在链接时只用到静态链接库,可以使用下面的命令:
gcc –L /usr/dev/mysql/lib –static –lmysqlclient main.o –o main
静态库链接时搜索路径顺序:
- ld会去找GCC命令中的参数-L
- 再找gcc的环境变量LIBRARY_PATH
- 再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的
动态链接时、执行时搜索路径顺序:
- 编译目标代码时指定的动态库搜索路径
- 环境变量LD_LIBRARY_PATH指定的动态库搜索路径
- 配置文件/etc/ld.so.conf中指定的动态库搜索路径
- 默认的动态库搜索路径/lib
- 默认的动态库搜索路径/usr/lib
有关环境变量:
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径
这里,我们使用简单的编译命令。
[root@localhost toolchains]# arm-none-linux-gnueabihf-gcc -g main.c -o main
[root@localhost toolchains]# ll
总用量 14
-rwxrwxrwx 1 root root 13116 7月 2 23:00 main
-rwxrwxrwx 1 root root 242 7月 2 22:59 main.c
[root@localhost toolchains]# file main
main: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 3.2.0, with debug_info, not stripped
3.2 readelf命令
显示可执行程序的elf文件信息。
常用的选项:
-S --section-headers Display the sections’ header
-e --headers Equivalent to: -h -l -S
-s --syms Display the symbol table
-x --hex-dump=<number|name>
Dump the contents of section <number|name> as bytes
-p --string-dump=<number|name>
Dump the contents of section <number|name> as strings
[root@localhost toolchains]# arm-none-linux-gnueabihf-readelf -h main
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: ARM
Version: 0x1
Entry point address: 0x1039d
Start of program headers: 52 (bytes into file)
Start of section headers: 11636 (bytes into file)
Flags: 0x5000400, Version5 EABI, hard-float ABI
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 9
Size of section headers: 40 (bytes)
Number of section headers: 37
Section header string table index: 36
有ultraEdit软件打开可执行程序main,可以看到文件首部内容和readelf打印对应。
查看程序各个段头:
[root@localhost toolchains]# arm-none-linux-gnueabihf-readelf -S main
There are 37 section headers, starting at offset 0x2d74:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00010154 000154 000019 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 00010170 000170 000020 00 A 0 0 4
[ 3] .hash HASH 00010190 000190 000030 04 A 5 0 4
[ 4] .gnu.hash GNU_HASH 000101c0 0001c0 000034 04 A 5 0 4
[ 5] .dynsym DYNSYM 000101f4 0001f4 000070 10 A 6 1 4
[ 6] .dynstr STRTAB 00010264 000264 000058 00 A 0 0 1
[ 7] .gnu.version VERSYM 000102bc 0002bc 00000e 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 000102cc 0002cc 000030 00 A 6 1 4
[ 9] .rel.dyn REL 000102fc 0002fc 000008 08 A 5 0 4
[10] .rel.plt REL 00010304 000304 000030 08 AI 5 21 4
[11] .init PROGBITS 00010334 000334 00000c 00 AX 0 0 4
[12] .plt PROGBITS 00010340 000340 00005c 04 AX 0 0 4
[13] .text PROGBITS 0001039c 00039c 0000e0 00 AX 0 0 4
[14] .fini PROGBITS 0001047c 00047c 000008 00 AX 0 0 4
[15] .rodata PROGBITS 00010484 000484 000140 00 A 0 0 4
[16] .ARM.exidx ARM_EXIDX 000105c4 0005c4 000008 00 AL 13 0 4
[17] .eh_frame PROGBITS 000105cc 0005cc 000004 00 A 0 0 4
[18] .init_array INIT_ARRAY 00020f08 000f08 000004 04 WA 0 0 4
[19] .fini_array FINI_ARRAY 00020f0c 000f0c 000004 04 WA 0 0 4
[20] .dynamic DYNAMIC 00020f10 000f10 0000f0 08 WA 6 0 4
[21] .got PROGBITS 00021000 001000 000028 04 WA 0 0 4
[22] .data PROGBITS 00021028 001028 000010 00 WA 0 0 4
[23] .bss NOBITS 00021038 001038 000010 00 WA 0 0 4
[24] .comment PROGBITS 00000000 001038 000057 01 MS 0 0 1
[25] .ARM.attributes ARM_ATTRIBUTES 00000000 00108f 000035 00 0 0 1
[26] .debug_aranges PROGBITS 00000000 0010c8 0000c8 00 0 0 8
[27] .debug_info PROGBITS 00000000 001190 000673 00 0 0 1
[28] .debug_abbrev PROGBITS 00000000 001803 000274 00 0 0 1
[29] .debug_line PROGBITS 00000000 001a77 000346 00 0 0 1
[30] .debug_frame PROGBITS 00000000 001dc0 000034 00 0 0 4
[31] .debug_str PROGBITS 00000000 001df4 000482 01 MS 0 0 1
[32] .debug_line_str PROGBITS 00000000 002276 000025 01 MS 0 0 1
[33] .debug_rnglists PROGBITS 00000000 00229b 000038 00 0 0 1
[34] .symtab SYMTAB 00000000 0022d4 000700 10 35 87 4
[35] .strtab STRTAB 00000000 0029d4 000241 00 0 0 1
[36] .shstrtab STRTAB 00000000 002c15 00015f 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
D (mbind), y (purecode), p (processor specific)
查看某个符号(函数或者变量等)的地址:
arm-sgmstar-gnueabihf-9.1.0-readelf -s main
3.3 objdump命令
显示程序信息,函数汇编等,常用于调试。
-D, --disassemble-all Display assembler contents of all sections
-S, --source Intermix source code with disassembly
把反汇编内容输出到main.txt中:
[root@localhost toolchains]# arm-none-linux-gnueabihf-objdump -D main > main.txt
[root@localhost toolchains]# ll
总用量 82
-rwxrwxrwx 1 root root 13152 7月 2 23:42 main
-rwxrwxrwx 1 root root 255 7月 2 23:30 main.c
-rwxrwxrwx 1 root root 69373 7月 2 23:53 main.txt
3.4 size命令
显示可执行程序中各个段占用的大小。
[root@localhost toolchains]# arm-none-linux-gnueabihf-size main
text data bss dec hex filename
1143 304 16 1463 5b7 main
我们在main.c中增加全局变量int aaa = 2;,重新编译后可以看到data段增加了4个字节:
[root@localhost toolchains]# arm-none-linux-gnueabihf-gcc -g main.c -o main
[root@localhost toolchains]# arm-none-linux-gnueabihf-size main
text data bss dec hex filename
1143 308 16 1467 5bb main
3.5 nm命令
该命令主要用于显示可执行程序的符号。
[root@localhost toolchains]# arm-none-linux-gnueabihf-nm main
00010170 r __abi_tag
U abort@GLIBC_2.4
00010488 r all_implied_fbits
00010534 r all_implied_fbits
00021048 B __bss_end__
00021048 B _bss_end__
00021038 B __bss_start
00021038 B __bss_start__
000103c0 t call_weak_fn
00021038 b completed.0
00021028 D __data_start
00021028 W data_start
000103e4 t deregister_tm_clones
00010434 t __do_global_dtors_aux
00020f0c d __do_global_dtors_aux_fini_array_entry
0002102c D __dso_handle
00020f10 d _DYNAMIC
00021038 D _edata
00021048 B __end__
00021048 B _end
0001047c T _fini
0001044c t frame_dummy
00020f08 d __frame_dummy_init_array_entry
000105cc r __FRAME_END__
U free@GLIBC_2.4
00021000 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0002103c B g_uninit
00021030 D g_val
00010334 T _init
00010484 R _IO_stdin_used
U __libc_start_main@GLIBC_2.34
00010450 T main
U malloc@GLIBC_2.4
U puts@GLIBC_2.4
00010408 t register_tm_clones
0001039c T _start
00021044 b s_tmp.0
00021034 D str
00021040 b s_uninit
00021038 D __TMC_END__
符号说明:
对于每一个符号来说,其类型如果是小写的,则表明该符号是local的。大写则表明该符号是global(external)的
A:该符号的值是绝对的,在以后的链接过程中,不允许改变。这样的符号,常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置。
B:该符号的值出现在非初始化数据段BSS中。例如,一个文件中定义全局 static int s_int。则符号s_int 类型为b,位于bss section中。其值表示该符号在bss段的偏移。一般而言,bss段分配于RAM中。
C:该符号为common。common symbol是未初始化数据段。该符号没有包含于一个普通section中。只有在链接过程中才进行分配。符号的值表示要分配的字节数。例如,在一个c文件中,定义int g_no_init,并且该符号在别的地方会被引用,则该符号类型就是C,否则为B。
D:该符号位于初始化数据段中。一般来说,分配到data section中。比如,全局变量 int g_init = 2;
G:该符号也位于初始化数据段。主要用于small object,提高访问small data object的一种方式。
I:该符号是对另一个符号的间接引用。
N:该符号是一个debugging符号
R:该符号位于只读数据区。比如,全局变量 const int const_int = 0; 如果在一个函数中定义 const char* test = “abc”; const int a = 2;使用nm都不会得到符号信息。但是字符串"abc"分配于只读存储器中,test 在rodata section中,大小为4
S:符号位于非初始化数据区,用于 small object
T:符号位于代码区 text section
U:符号在当前文件中是未定义的,即该符号的定义在别的文件中。比如,当前文件中调用另一个文件中的函数,在这个本目标文件中,函数就是未定义的。但是在定义它的文件中,类型为T。但是对于全局变量来说,在定义它的文件中,符号类型是C,在使用它的文件中,类型是U。
V:该符号是一个weak object
?:该符号类型没有定义
3.6 addr2line命令
将函数在可执行程序文件中的地址转换成源代码文件名和行数。
命令:
arm-none-linux-gnueabihf-addr2line -e main -psfC addr地址
[root@localhost toolchains]# arm-none-linux-gnueabihf-gcc -g main.c -o main
[root@localhost toolchains]# arm-none-linux-gnueabihf-readelf -s main|grep main
69: 00000000 0 FILE LOCAL DEFAULT ABS main.c
107: 00010451 44 FUNC GLOBAL DEFAULT 13 main
[root@localhost toolchains]# arm-none-linux-gnueabihf-addr2line -e main -psfC 0x10451
main at main.c:11
[root@localhost toolchains]# cat -n main.c
1 #include <stdlib.h>
2 #include <stdio.h>
3
4 int g_val = 12;
5 int g_uninit;
6 int aaa = 2;
7 const char * str = "who am I?";
8 static char s_uninit;
9
10 int main()
11 {
12 printf("hello world!\n");
13 int* p = malloc(sizeof(int));
14 static int s_tmp = 0;
15 free(p);
16 return 0;
17 }
3.7 objcopy命令
将目标文件的一部分或者全部内容拷贝到另外一个目标文件中,或者实现目标文件的格式转换。通过指定输入目标为二进制文件(例如-O binary),objcopy可以生成原始格式的二进制文件。当objcopy生成一个原始格式的二进制文件的时候,它会生成输入的目标文件的基本内存拷贝,然后所有的标号和可重定位信息都会被去掉。内存拷贝开始于最低段的加载地址,拷贝到输出文件。
常用的选项有:
[root@localhost test]# objcopy -O srec main main.srec #将文件转换成S-record格式
[root@localhost test]# objcopy -O binary main main.bin #将文件转换成rawbinary 格式
[root@localhost test]# objcopy -S main main.stripall #生成一个不含重定位以及标号目标文件
[root@localhost test]# objcopy -R .comment main main.remove #去掉指定名称的节
[root@localhost test]# objcopy --add-section mysection=hello_text main main.add #添加一个自定义的节到可执行文件并将一个文件内容添加到其中
[root@localhost test]# objcopy -j mysection main.add section_hello #将指定的段拷贝出来
[root@localhost test]# objcopy --only-keep-debug main.debug main.debuginfo # 生成调试信息文件
[root@localhost test]# objcopy --strip-debug main.debug main.stripdebug #生成 不含调试信息的可执行文件
[root@localhost test]# objcopy --add-gnu-debuglink=main.debuginfo main.stripdebug #为不含调试信息的可执行文件添加调试信息
3.8 stings命令
显示可执行程序中能打印出来的字符串。可以看到代码里面写的常量字符串“who am I?”、“hello world!”等等。
[root@localhost toolchains]# arm-none-linux-gnueabihf-strings main
/lib/ld-linux-armhf.so.3
malloc
__libc_start_main
puts
free
abort
libc.so.6
GLIBC_2.4
GLIBC_2.34
__gmon_start__
F{`xh
who am I?
hello world!
GCC: (GNU Toolchain for the Arm Architecture 11.2-2022.02 (arm-11.14)) 11.2.1 20220111
aeabi
.shstrtab
.interp
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rel.dyn
.rel.plt
.init
.text
.fini
.rodata
.ARM.exidx
.eh_frame
.init_array
.fini_array
.dynamic
.got
.data
.bss
.comment
.ARM.attributes
3.9 strip命令
该命令主要用于剥离可执行程序中的符号表。可以看到,执行strip后可执行程序变小很多。嵌入式开发往往会用到strip命令节省flash空间。
[root@localhost toolchains]# ll
总用量 14
-rwxrwxrwx 1 root root 13116 7月 2 23:00 main
-rwxrwxrwx 1 root root 242 7月 2 22:59 main.c
[root@localhost toolchains]# arm-none-linux-gnueabihf-strip main
[root@localhost toolchains]# ll
总用量 6
-rwxrwxrwx 1 root root 5600 7月 2 23:01 main
-rwxrwxrwx 1 root root 242 7月 2 22:59 main.c
4. 总结
本文简要介绍了交叉编译链及常见的工具集的使用方法。由于水平有限,如有纰漏,请指正。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)