Linux 应用开发基础01-编译工具
交叉编译工具为 ARM 编译程序
- gcc 编译可执行程序 hello_gcc
$ gcc -o hello_gcc hello.c
$ ls
hello hello.c hello_gcc
$ file hello_gcc
hello_gcc: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=18032f92c07c12275cdaf06e541562db6fa7021d, for GNU/Linux 3.2.0, not stripped
x86-64上的可执行程序
- 在开发板上运行
[root@imx6ull:/mnt]# ./hello_gcc
-bash: ./hello_gcc: cannot execute binary file: Exec format error
架构不同,运行出错 cannot execute binary file: Exec format error
- 使用交叉编译工具编译,并在开发板上运行(开发板通过 NFS和开发板文件夹共享)
$ arm-buildroot-linux-gnueabihf-gcc -o hello hello.c
$ ls
hello hello.c hello_gcc
$ file hello
hello: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 4.9.0, not stripped
可以正常运行
[root@imx6ull:/mnt]# ./hello
Hello, world!
GCC 编译器使用
常用命令示例
gcc hello.c // 输出一个名为 a.out 的可执行程序,然后可以执行./a.out gcc -o hello hello.c // 输出名为 hello 的可执行程序,然后可以执行./hello gcc -o hello hello.c -static // 静态链接gcc -c -o hello.o hello.c // 先编译(不链接) gcc -o hello hello.o // 再链接
编译过程
![嵌入式 Linux 架构相关-GCC编译过程.drawio](https://img2022.cnblogs.com/blog/2689124/202207/2689124-20220726174207852-528679538.png)
通过在 gcc 编译命令后面添加 “-v” 标志,可以查看编译过程,“-o”包含编译的三个过程:编译、汇编和链接过程。编译命令在第 12、33 和 38行。
![](https://img2022.cnblogs.com/blog/2689124/202207/2689124-20220726174207289-1488534136.jpg)
简化的过程如下:
cc1 main.c -o /tmp/ccXCx1YG.s as -o /tmp/ccZfdaDo.o /tmp/ccXCx1YG.s cc1 sub.c -o /tmp/ccXCx1YG.s as -o /tmp/ccn8Cjq6.o /tmp/ccXCx1YG.s collect2 -o test /tmp/ccZfdaDo.o /tmp/ccn8Cjq6.o ....
每个过程也可以用单独命令实现:
gcc -E -o hello.i hello.c
gcc -S -o hello.s hello.i
gcc -c -o hello.o hello.s
常用编译选项
参数 | 描述 | |
---|---|---|
-E | 预处理。开发过程想快速确定某个宏,可以使用"-E -dM" | compiler |
-c | 编译、汇编指定的源文件(也就是编译源文件),但是不进行链接 | compiler |
-o | 用来指定输出文件 | output |
-I | 指定头文件目录 | |
-L | 为 gcc 增加一个搜索链接库的目录 | |
-l | 用来指定程序要链接的库 | link |
编译多个文件
- 一起编译、链接:
gcc -o test main.c sub.c
- 分开编译,统一链接:
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c
gcc -o test main.o sub.o
制作使用动态库
- 制作、编译:
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c
gcc -shared -o libsub.so sub.o sub2.o sub3.o(可以使用多个.o 生成动态库)
gcc -o test main.o -lsub -L /libsub.so/所在目录/
- 运行
- 先把 libsub.so 放到 Ubuntu 的/lib 目录,然后就可以运行 test 程序。
- 如果不想把 libsub.so 放到/lib,也可以放在某个目录比如/a,然后如下执行:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a ./test
制作使用静态库
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c
ar crs libsub.a sub.o sub2.o sub3.o(可以使用多个.o 生成静态库)
gcc -o test main.o libsub.a (如果.a 不在当前目录下,需要指定它的绝对或相对路径
运行: 不需要把静态库 libsub.a 放到板子上。
注意:执行 arm-linux-gnueabihf-gcc -c -o sub.o sub.c 交叉编译需要在最后面加上 -fPIC 参数
其他有用的选项
gcc -E main.c // 查看预处理结果,比如头文件是哪个
gcc -E -dM main.c > 1.txt // 把所有的宏展开,存在 1.txt 里
gcc -Wp,-MD,abc.dep -c -o main.o main.c // 生成依赖文件 abc.dep,后面 Makefile 会用
echo 'main(){}'| gcc -E -v - // 它会列出头文件目录、库目录(LIBRARY_PATH)
选项详细说明见第四篇2.3-2.9节
Makefile 的使用
Makefile 效果
gcc -o test a.c b.c
缺点:对所有文件都在处理一次,应该分别编译最后链接
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -o test a.o b.o
通过 makefile 编译
test: a.o b.o
gcc -o test a.o b.o
a.o: a.c
gcc -c -o a.o a.c
b.o: b.c
gcc -c -o b.o b.c
修改a.c,编译过程如下:
编译目标 test,由于 a.c 修改,需要执行gcc -c -o a.o a.c
命令,编译最新的 a.o。b.c 未修改,因此保留 b.o,最后gcc -o test a.o b.o
将 test 编译出来。
Makefile 简介
参考【第4篇】嵌入式Linux应用开发基础知识 3-1 到 3-6 章节,相关资料点这里下载。
更详细的教程可以参考这里。
通用 Makefile 使用
适用范围
参考Linux内核的Makefile编写了一个通用的Makefile,它可以用来编译应用程序:
① 支持多个目录、多层目录、多个文件;
② 支持给所有文件设置编译选项;
③ 支持给某个目录设置编译选项;
④ 支持给某个文件单独设置编译选项;
⑤ 简单、好用。
组成和介绍
本程序的Makefile分为3类:
- 各级子目录的Makefile:
它最简单,形式如下:EXTRA_CFLAGS := CFLAGS_file.o := obj-y += file.o obj-y += subdir/
说明
- "obj-y += file.o" 表示把当前目录下的 file.c 编进程序里,
- "obj-y += subdir/" 表示要进入 subdir 这个子目录下去寻找文件来编进程序里,是哪些文件由subdir目录下的Makefile决定。
- "EXTRA_CFLAGS",它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
- "CFLAGS_xxx.o",它给当前目录下的 xxx.c 设置它自己的编译选项, 可以不设置
注意:
- "subdir/"中的斜杠"/"不可省略
- 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用
- CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者组成xxx.c的编译选项
-
顶层目录的Makefile:
它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,
主要是定义工具链前缀CROSS_COMPILE,
定义编译参数CFLAGS,
定义链接参数LDFLAGS,
这些参数就是文件中用export导出的各变量。 -
层目录的Makefile.build:
这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o
详细的讲解请看视频。
怎么使用这套Makefile
-
把顶层Makefile, Makefile.build放入程序的顶层目录
在各自子目录创建一个空白的Makefile
例子: 在 a 子目录下创建空白 Makefile├── a │ ├── Makefile │ ├── sub2.c │ └── sub3.c ├── include │ ├── sub2.h │ ├── sub3.h │ └── sub.h ├── main.c ├── Makefile ├── Makefile.build └── sub.c
-
确定编译哪些源文件
修改顶层目录和各自子目录Makefile的obj-y :
obj-y += xxx.o
obj-y += yyy/
这表示要编译当前目录下的xxx.c, 要编译当前目录下的yyy子目录例子:主目录下 Makefile 添加编译编译目标。根据 main.c 和 sub.c 添加 obj_y。
obj-y += main.o obj-y += sub.o obj-y += a/
同样在子目录 Makefile 下,添加如下内容:
obj-y += sub2.o obj-y += sub3.o
-
确定编译选项、链接选项
修改顶层目录Makefile的CFLAGS,这是编译所有.c文件时都要用的编译选项;
修改顶层目录Makefile的LDFLAGS,这是链接最后的应用程序时的链接选项;CFLAGS := -Wall -O2 -g CFLAGS += -I $(shell pwd)/include LDFLAGS :=
修改各自子目录下的Makefile:
"EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置
"CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置例子:a/sub2.c,有一个条件编译选项,DEBUG
#include <stdio.h> #include <sub2.h> void sub2_fun(void) { printf("Sub2 fun, B = %d!\n", B); #ifdef DEBUG printf("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); #endif }
a/sub3.c,有两个条件编译选项,DEBUG 和 DEBUG_SUB3
#include <stdio.h> #include <sub3.h> void sub3_fun(void) { printf("Sub3 fun, C = %d!\n", C); #ifdef DEBUG printf("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); #endif #ifdef DEBUG_SUB3 printf("It is only debug info for sub3.\n"); #endif }
添加 EXTRA_CFLAGS
EXTRA_CFLAGS := DEBUG obj-y += sub2.o obj-y += sub3.o
编译运行查看结果
Main fun! Sub fun, A = 1! Sub2 fun, B = 2! sub2.c sub2_fun line 8 Sub3 fun, C = 3! sub3.c sub3_fun line 9
sub2.c 和 sub3.c 中编译选项打开,打印日志记录
添加 CFLAGS_xxx.o
EXTRA_CFLAGS := -D DEBUG CFLAGS_sub3.o := -D DEBUG_SUB3 obj-y += sub2.o obj-y += sub3.o
编译运行查看结果
编译运行查看结果
Main fun! Sub fun, A = 1! Sub2 fun, B = 2! sub2.c sub2_fun line 8 Sub3 fun, C = 3! sub3.c sub3_fun line 9
-
使用哪个编译器?
修改顶层目录Makefile的CROSS_COMPILE, 用来指定工具链的前缀(比如arm-linux-) -
确定应用程序的名字:
修改顶层目录Makefile的TARGET, 这是用来指定编译出来的程序的名字 -
执行"make"来编译,执行"make clean"来清除,执行"make distclean"来彻底清除
通用 Makefile 的解析
1. 主要知识点
序号 | 说明 |
---|---|
1. | “-f”选项指定文件 make -f Makefile.build “-C”选项指定目录,切换到其他目录里去 make -C a/ -f Makefile.build 指定目标,不再默认生成第一个目标 make -C a/ -f Makefile.build other_target |
2. | A = xxx // 延时变量 B ?= xxx // 延时变量,只有第一次定义时赋值才成功;如果曾定义过,此赋值无效 C := xxx // 立即变量 D += yyy // 如果 D 在前面是延时变量,那么现在它还是延时变量; // 如果 D 在前面是立即变量,那么现在它还是立即变量 |
3. | 变量的导出(export) 切换目录,执行其他目录下的Makefile,要让某个变量的值在所有目录中可见,则要 export 出来。 |
4. | TOPDIR := $(shell pwd) TOPDIR 等于 shell 命令 pwd 的结果。 |
5. | 假象目标 .PHONY : clean 把“clean”这个目标,设置为“假想目标” ,这样可以确保执行“make clean”时那些删除命令肯定可以得到执行。 |
6. | 常用函数 \((foreach var,list,text) <br />\)(wildcard pattern) \((filter pattern...,text)<br />\)(filter-out pattern...,text) $(patsubst pattern,replacement,text) |
2. 通用 Makefile 的设计思想
![makefile编译-文件结构.drawio](https://img2022.cnblogs.com/blog/2689124/202207/2689124-20220726174207123-395199476.png)
![](https://img2022.cnblogs.com/blog/2689124/202207/2689124-20220726174207161-1674438659.jpg)
#end