Linux 应用开发基础01-编译工具

交叉编译工具为 ARM 编译程序

  1. 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上的可执行程序

  1. 在开发板上运行
[root@imx6ull:/mnt]# ./hello_gcc
-bash: ./hello_gcc: cannot execute binary file: Exec format error

架构不同,运行出错 cannot execute binary file: Exec format error

  1. 使用交叉编译工具编译,并在开发板上运行(开发板通过 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
嵌入式 Linux 架构相关-GCC编译过程.drawio

通过在 gcc 编译命令后面添加 “-v” 标志,可以查看编译过程,“-o”包含编译的三个过程:编译、汇编和链接过程。编译命令在第 12、33 和 38行。

简化的过程如下:

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

编译多个文件

  1. 一起编译、链接:
    gcc -o test main.c sub.c
  2. 分开编译,统一链接:
gcc -c -o main.o  main.c 
gcc -c -o sub.o sub.c 
gcc -o test main.o sub.o

制作使用动态库

  1. 制作、编译:
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/所在目录/
  1. 运行
    1. 先把 libsub.so 放到 Ubuntu 的/lib 目录,然后就可以运行 test 程序。
    2. 如果不想把 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类:

  1. 各级子目录的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的编译选项
  1. 顶层目录的Makefile:

    它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,
    主要是定义工具链前缀CROSS_COMPILE,
    定义编译参数CFLAGS,
    定义链接参数LDFLAGS,
    这些参数就是文件中用export导出的各变量。

  2. 层目录的Makefile.build:
    这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o
    详细的讲解请看视频。

怎么使用这套Makefile

  1. 把顶层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
    
  2. 确定编译哪些源文件
    修改顶层目录和各自子目录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
    
  3. 确定编译选项、链接选项
    修改顶层目录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
    
  4. 使用哪个编译器?
    修改顶层目录Makefile的CROSS_COMPILE, 用来指定工具链的前缀(比如arm-linux-)

  5. 确定应用程序的名字:
    修改顶层目录Makefile的TARGET, 这是用来指定编译出来的程序的名字

  6. 执行"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
makefile编译-文件结构.drawio

#end

posted @ 2022-07-26 17:42  Oddpage  阅读(150)  评论(0编辑  收藏  举报