编译全部流程

news/2024/10/17 23:30:32/

文章目录

  • Make
    • 概述
    • 标准文件构成与工作流程
        • 伪目标
    • 标准工程
  • Cmake
    • 安装目录
    • 添加搜索路径
    • FindXXX.cmake文件
  • gcc
    • 概述
      • Binutils工具集
      • glibc库
    • gdb调试
      • 远程调试
    • 交叉编译
  • OpenOCD
    • 概述
    • 指令
    • GDB配合

Make

概述

  Makefile文件中所描述的模块与模块、模块与源代码文件之间的依赖关系,将源代码文件编译成obj文件,再将obj文件链接成库文件或可执行程序文件的过程。
  但软件项目中,模块之间的依赖关系一般比较复杂,想要通过一条命令gcc命令完成项目的编译非常困难,并且不利于扩展和维护。
  以下图所示的工程为例,利用makefile快速编写编译脚本

test1.c
test1.
libtest.a
test2.c
test2.
test
main.c
main.o
# test依赖于libtest.a main.o
test: libtest.a main.ogcc main.o -ltest -L. -o test
# main.o依赖于main.c
main.o:main.cgcc main.c -c -o main.o
# libtest.a依赖于test1.o test2.o
libtest.a: test1.o test2.oar -r libtest.a test1.o test2.o
# test1.o依赖于test1.c
test1.o:test1.cgcc test1.c -c -o test1.o
# test2.o依赖于test2.c
test2.o:test2.cgcc test2.c -c -o test2.o

标准文件构成与工作流程

  makefile的规则,也就是makefile中最核心的内容如下所示。

target ... : prerequisites ...command

  target可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。prerequisites:生成该target所依赖的文件和/或target。command:该target要执行的命令(任意的shell命令)。
  makefile可以使用变量。例如使用变量添加文件,如下所示。

objects = main.o kbd.o command.o display.o \insert.o search.o files.o utils.o

  make能够自动推导。比如make看到一个 .o 文件,它就会自动的把 .c 文件加在依赖关系中,如果make找到一个whatever.o ,那么 whatever.c 就会是 whatever.o 的依赖文件。并且cc -c whatever.c 也会被推导出来,
  在Makefile使用 include 关键字可以把别的Makefile包含进来。在 include 前面可以有一些空字符,但是绝不能是 Tab 键开始。 include 和< filename > 可以用一个或多个空格隔开。举个例子,你有这样几个Makefile:a.mk、b.mk 、 c.mk ,还有一个文件叫 foo.make ,以及一个变量 $(bar) ,其包含了 e.mk 和 f.mk ,那么,下面的语句相互等价:

include foo.make *.mk $(bar)
include foo.make a.mk b.mk c.mk e.mk f.mk

  如果make执行时,有 -I 或-include-dir 参数,那么make就会在这个参数所指定的目录下去寻找。
  make支持三个通配符:* , ? 和 ~ 。波浪号( ~ )字符在文件名中也有比较特殊的用途。如果是 ~/test ,这就表示当前用户的 $HOME 目录下的test目录。而 ~hchen/test 则表示用户hchen的宿主目录下的test目录。
  要让通配符在变量中展开,也就是让objects的值是所有 .o 的文件名的集合。

objects := $(wildcard *.c)

  特殊变量VPATH,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当前目录找不到的情况下,到所指定的目录中去找寻文件了。

VPATH = src:../headers

  上面的定义指定两个目录,“src”和“…/headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)
  “vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:

vpath <pattern> <directories> # 为符合模式的文件指定搜索目录。
vpath <pattern> # 清除符合模式的文件的搜索目录。
vpath # 清除所有已被设置好了的文件搜索目录。
vpath %.h ../headers # 该语句表示,要求make在“../headers”目录下搜索所有以 .h 结尾的文件。

  “.PHONY”来显式地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。

.PHONY : clean
clean :rm *.o temp

伪目标

  伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:

all : prog1 prog2 prog3
.PHONY : allprog1 : prog1.o utils.occ -o prog1 prog1.o utils.oprog2 : prog2.occ -o prog2 prog2.oprog3 : prog3.o sort.o utils.occ -o prog3 prog3.o sort.o utils.o

  目标也可以成为依赖。所以,伪目标同样也可成为依赖。

.PHONY : cleanall cleanobj cleandiffcleanall : cleanobj cleandiffrm programcleanobj :rm *.ocleandiff :rm *.diff

标准工程

##########################################################################################################################
# File automatically-generated by tool: [projectgenerator] version: [3.18.0-B7] date: [Mon Apr 03 14:51:26 CST 2023] 
########################################################################################################################### ------------------------------------------------
# Generic Makefile (based on gcc)
#
# ChangeLog :
#	2017-02-10 - Several enhancements + project update mode
#   2015-07-22 - first version
# ------------------------------------------------######################################
# target
######################################
TARGET = XXX######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og#######################################
# paths
#######################################
# Build path
BUILD_DIR = build_make######################################
# source
######################################
# C sources
C_SOURCES =  \
main.c # ASM sources
# ASM_SOURCES =  #######################################
# binaries
#######################################
#GCC_PATH = D:/Download/gcc-arm-none-eabi-10.3-2021.10/bin
#PREFIX = arm-none-eabi-
# The gcc compiler bin path can be either defined in make command via GCC_PATH variable (> make GCC_PATH=xxx)
# either it can be added to the PATH environment variable.
ifdef GCC_PATH
CC = $(GCC_PATH)/$(PREFIX)gcc
AS = $(GCC_PATH)/$(PREFIX)gcc -x assembler-with-cpp
CP = $(GCC_PATH)/$(PREFIX)objcopy
SZ = $(GCC_PATH)/$(PREFIX)size
else
CC = $(PREFIX)gcc
AS = $(PREFIX)gcc -x assembler-with-cpp
CP = $(PREFIX)objcopy
SZ = $(PREFIX)size
endif
HEX = $(CP) -O ihex
BIN = $(CP) -O binary -S#######################################
# CFLAGS
######################################## macros for gcc
# AS defines
AS_DEFS = # C defines
# C_DEFS =  # ifeq ($(DEBUG), 1)
# C_DEFS += \
# -DDEBUG \
# -DGCC_ARM 
# endif# C includes
C_INCLUDES =  \
-ICore/Inc # compile gcc flags
ASFLAGS = $(MCU) $(AS_DEFS) $(AS_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sectionsCFLAGS += $(MCU) $(C_DEFS) $(C_INCLUDES) $(OPT) -Wall -fdata-sections -ffunction-sectionsifeq ($(DEBUG), 1)
CFLAGS += -g -gdwarf-2
endif# Generate dependency information
CFLAGS += -MMD -MP -MF"$(@:%.o=%.d)"#######################################
# LDFLAGS
#######################################
# link script
LDSCRIPT = STM32F103ZETx_FLASH.ld# libraries
LIBS = -lc -lm -lnosys 
LIBDIR = 
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) -Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref -Wl,--gc-sections# default action: build all
all: $(BUILD_DIR)/$(TARGET).elf $(BUILD_DIR)/$(TARGET).hex $(BUILD_DIR)/$(TARGET).bin#######################################
# build the application
#######################################
# list of objects
OBJECTS = $(addprefix $(BUILD_DIR)/,$(notdir $(C_SOURCES:.c=.o)))
vpath %.c $(sort $(dir $(C_SOURCES)))
# list of ASM program objects
OBJECTS += $(addprefix $(BUILD_DIR)/,$(notdir $(ASM_SOURCES:.s=.o)))
vpath %.s $(sort $(dir $(ASM_SOURCES)))$(BUILD_DIR)/%.o: %.c Makefile | $(BUILD_DIR) $(CC) -c $(CFLAGS) -Wa,-a,-ad,-alms=$(BUILD_DIR)/$(notdir $(<:.c=.lst)) $< -o $@$(BUILD_DIR)/%.o: %.s Makefile | $(BUILD_DIR)$(AS) -c $(CFLAGS) $< -o $@$(BUILD_DIR)/$(TARGET).elf: $(OBJECTS) Makefile$(CC) $(OBJECTS) $(LDFLAGS) -o $@$(SZ) $@$(BUILD_DIR)/%.hex: $(BUILD_DIR)/%.elf | $(BUILD_DIR)$(HEX) $< $@$(BUILD_DIR)/%.bin: $(BUILD_DIR)/%.elf | $(BUILD_DIR)$(BIN) $< $@	$(BUILD_DIR):mkdir $@		#######################################
# clean up
#######################################
clean:-rm -fR $(BUILD_DIR)#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)# *** EOF ***

Cmake

安装目录

  安装目录,可执行文件,动态库等可通过以下变量设置。
  安装目录可通过 CMAKE_INSTALL_PREFIX 变量指定。

变量注释
CMAKE_ARCHIVE_OUTPUT_DIRECTORY默认存放静态库的文件夹位置 .a
CMAKE_LIBRARY_OUTPUT_DIRECTORY默认存放动态库的文件夹位置 .so
LIBRARY_OUTPUT_PATH默认存放库文件的位置,如果产生的是静态库并且没有指定CMAKE_ARCHIVE_OUTPUT_DIRECTORY则存放在该目录下,动态库也类似
CMAKE_RUNTIME_OUTPUT_DIRECTORY存放可执行软件的目录
mkdir build && cd build/ && cmake .. -DCMAKE_INSTALL_PREFIX=/home/ubuntu/Disk/ProgrameFiles -DLIBRARY_OUTPUT_PATH=/home/ubuntu/Disk/share/lib -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/home/ubuntu/Disk/share/bin # 通用配置

添加搜索路径

  可以通过 PKG_CONFIG_PATH 指定路径pc文件路径。通过 CMAKE_PREFIX_PATH 指定安装目录文件,CMAKE_PREFIX_PATH 变量与 CMAKE_INSTALL_PREFIX 变量值一致。
  例如/home/ubuntu/Disk/programFiles/share/pkgconfig/eigen3.pc,可以指定为/home/ubuntu/Disk/programFiles/share/pkgconfig/
  由于PKG_CONFIG_PATH需要其他配置,所以可以自己编写 XXXXXConfig.cmake 文件。文件构成如下:

set(Module_Name "XXXXX")
find_path(${Module_Name}_INCLUDE_DIR /usr/include/ /usr/local/include ${CMAKE_SOURCE_DIR}/ModuleMode)
find_library(${Module_Name}_LIBRARY NAMES add PATHS /usr/lib/add /usr/local/lib/add ${CMAKE_SOURCE_DIR}/ModuleMode)if (${Module_Name}_INCLUDE_DIR AND ${Module_Name}_LIBRARY)set(${Module_Name}_FOUND TRUE)
endif (${Module_Name}_INCLUDE_DIR AND ${Module_Name}_LIBRARY)

FindXXX.cmake文件

  在Cmake中使用find_package时,会尝试查找FindXXX.cmake 文件。该文件一般存在CMAKE_MODULE_PATH变量中。

gcc

概述

  GCC(GNU Compiler Collection)是由 GNU 开发的编程语言编译器。 GCC最初代表“GNU C Compiler”,当时只支持C语言。 后来又扩展能够支持更多编程语言,包括 C++、Fortran 和 Java 等。 因此,GCC也被重新定义为“GNU Compiler Collection”,成为历史上最优秀的编译器, 其执行效率与一般的编译器相比平均效率要高 20%~30%。GCC编译工具链(toolchain),是指以GCC编译器为核心的一整套工具。它主要包含以下三部分内容:

  • gcc-core:即GCC编译器,用于完成预处理和编译过程,把C代码转换成汇编代码。
  • Binutils :除GCC编译器外的一系列小工具包括了链接器ld,汇编器as、目标文件格式查看器readelf等。
  • glibc:包含了主要的 C语言标准函数库,C语言中常常使用的打印函数printf、malloc函数就在glibc 库中。

  在很多场合下会直接用GCC编译器来指代整套GCC编译工具链。
  GCC 编译工具链在编译一个C源文件时需要经过以下 4 步:

预处理 -E
编译 -S
汇编 -C
链接
hello.c
hello.i
hello.s
hello.o
hello

  最后将每个源文件对应的目标.o文件链接起来,就生成一个可执行程序文件,这是链接器ld完成的工作。例如一个工程里包含了A和B两个代码文件,在链接阶段, 链接过程需要把A和B之间的函数调用关系理顺,也就是说要告诉A在哪里能够调用到fun函数, 建立映射关系,所以称之为链接。若链接过程中找不到fun函数的具体定义,则会链接报错。
  链接分为两种:

  • 动态链接:GCC编译时的默认选项。动态是指在应用程序运行时才去加载外部的代码库,不同的程序可以共用代码库。 所以动态链接生成的程序比较小,占用较少的内存。
  • 静态链接:链接时使用选项 “–static”,它在编译阶段就会把所有用到的库打包到自己的可执行程序中。 所以静态链接的优点是具有较好的兼容性,不依赖外部环境,但是生成的程序比较大。

在Ubuntu下,可以使用 ldd 工具查看动态文件的库依赖,尝试执行如下命令:

ldd hello # 动态文件会显示依赖库
ldd hello_static # 静态文件会显示 不是动态可执行文件

  gcc/g++一些常用指令如下表所示:

指令说明
-c生成目标文件,不进行链接
-o指定生成的文件名
-g在目标文件中添加调试信息,便于gdb调试或objdump反汇编
-Wall显示所有的警告信息
-Werror视警告为错误,出现警告即放弃编译
-w不显示任何警告信息
-v显示编译步骤
-On(n=0,1,2,3) 设置编译器优化等级,O0为不优化,O3为最高等级优化,O1为默认优化等级
-L指定库文件的搜索目录
-l(小写的L)链接某一库
-I(大写的i)指定头文件路径
-D定义宏,例如-DAAA=1,-DBBBB
-U取消宏定义,例如-UAAA

Binutils工具集

  在进行程序开发的时候通常不会直接调用这些工具,而是在使用GCC编译指令的时候由GCC编译器间接调用。下面是其中一些常用的工具:

  • as:汇编器,把汇编语言代码转换为机器码(目标文件)。
  • ld:链接器,把编译生成的多个目标文件组织成最终的可执行程序文件。
  • readelf:可用于查看目标文件或可执行程序文件的信息。
  • nm : 可用于查看目标文件中出现的符号。
  • objcopy: 可用于目标文件格式转换,如.bin 转换成 .elf 、.elf 转换成 .bin等。
  • objdump:可用于查看目标文件的信息,最主要的作用是反汇编。
  • size:可用于查看目标文件不同部分的尺寸和总尺寸,例如代码段大小、数据段大小、使用的静态内存、总大小等。

glibc库

  glibc库是GNU组织为GNU系统以及Linux系统编写的C语言标准库,因为绝大部分C程序都依赖该函数库,该文件甚至会直接影响到系统的正常运行,例如常用的文件操作函数read、write、open,打印函数printf、动态内存申请函数malloc等。

gdb调试

  可执行文件要能够被gdb调试,必须在编译时加上调试信息,也即是加上-g选项。生成之后可以通过gdb命令进行调试。

gcc -g hello.c -o hello # 生成带调试信息的可执行文件
gdb hello   # 调用GDB调试

  启动之后可以看到命令行提示符为(gdb),接着我们就可以在这个gdb的命令行提示符上面输入各种gdb的调试命令了(补充:这里也可以在shell中输入gdb,然后回车,这样直接进入到gdb的调试命令行,之后可以通过file hellp)。
  载入待调试的可执行文件之后,在gdb的命令行中输入list或者其简写l可以查看到程序的源码以及行号,输入l之后,默认会显示10行源代码,按回车之后会显示接下来的10行,直到文件的末尾。
  在gdb下添加断点使用命令break或简写 b,有下面几个常见用法:

b 函数名
b 行号
b 文件名:行号
b 行号 if条件

  加上断点之后,我们可以通过 info break 命令查看断点的信息。通过 disable < number > 来禁用指定Num的断点,通过 enable < number > 可以来解禁断点,用 delete < number > 命令来删除掉一个断点。
  可以使用run命令或者简写r来启动程序的执行,如果函数带有参数可以在 run 的收带上参数,也可以 set [ args ] XXXp < name >/print < name > 可以查看某一个变量的当前值。 next 命令或者n可以单步执行。
  我在 func() 函数调用行加上了断点,然后r开始执行程序,之后程序在断点处停住,此时我执行 step 命令或其简写 s 来跳入func()函数内部调试,在内部依然像执行外部调试一样,如果要从函数跳出则执行 finished ,这时会导致函数执行完毕,并且打印出一些函数的返回信息,并且程序停在函数后的第一条语句处。 quit(简写 q ) 退出gdb调试 。
  使用 watch < name > 命令可以实现监控变量,使用 info watch 命令可以查看监控的变量。同时 break 所拥有的 enable,disable,delete 等动词对于 watch 依然使用,且用法大同小异。

远程调试

   targeet remote < ip > < port > ,根据官方文档可以通过串行总线或者网络连接。连接方法分别如下所示:

gdb hello # 调试可执行文件
gdb hello -tui # 带显示的调试可执行文件
target remote /dev/ttya
targeet remote IP:PORT

交叉编译

  交叉编译器与本地编译器使用起来并没有多大区别。同样的C代码文件,使用交叉编译器编译后,生成的hello已经变成了ARM平台的可执行文件,可以通过readelf工具来查看具体的程序信息。

arm-linux-gnueabihf-gcc hello.c –o hello_arm # 编译
readelf -a hello_arm # 查看程序具体信息

  编译器还有很多版本,如arm-linux-gnueabi-gcc,本地编译器gcc全名为x86_64-linux-gnu-gcc,这些编译器是有一定的命名规则的:
arch [-os] [-(gnu)eabi(hf)] -gcc
  其中的各字段如下表所示:

字段含义
arch目标芯片
os操作系统
gnuC标准库类型
eabi应用二进制接口
hf浮点模式

OpenOCD

概述

  OpenOCD(Open On-Chip Debugger)开源片上调试器,是一款开源软件,最初是由Dominic Rath同学还在大学期间发起的(2005年)项目。OpenOCD旨在提供针对嵌入式设备的调试、系统编程和边界扫描功能。
  OpenOCD的功能是在仿真器的辅助下完成的,仿真器是能够提供调试目标的电信号的小型硬件单元。仿真器是必须的,因为调试主机(运行OpenOCD的主机)通常不具备这种电信号的直接解析功能。
  仿真器支持一个或多个传输协议,每个协议涉及不同的电信号,且使用不同的协议栈进行消息传递。市面上有很多种仿真器,并且这些仿真器的命名没有统一的规律。
  仿真器有时候会被封装成独立的加密狗,这种称为硬件接口加密狗。一些开发板上面直接集成了硬件接口加密狗,这样可以使开发板通过USB直接连到主机上进行调试。
  以STMMCU为例,使用仿真器调试框架如下图所示:

PC
HARDWARE
STLinkDriver
OpenOCD
GDB
GUI
MCU
STLink

指令

指令缩减指令说明示例
--help-h显示帮助
--version-v显示openocd版本-f config1.cfg
--file-f使用配置文件,可以有多个 -f
--serach-s在该路径下搜寻配置文件或者脚本
--debug-d设置仿真等级
--log_output-l重定向输出到文件
--command-c运行指令

GDB配合

终端1,用于打开OpenOCD

openocd -f board/stm32f429discovery.cfg -c "program Yourtarget.bin verify reset exit 0x08000000"

终端2,用于打开GDB

gdb Yourtarget.elf # 开启GDB
target remote localhost:3333 # 连接openocd服务
monitor reset # 复位MCU,从而让MCU处于确定的状态
load # 往MCU中加载调试文件,也就是常见的烧录过程

http://www.ppmy.cn/news/653165.html

相关文章

直流屏控母和合母有什么区别

1、含义不同 控母指控制电源输出直流220伏、110伏、48伏等&#xff0c;以稳定电压。 合母则是控制高压柜合闸用电源。 3、电压要求不同 控母电压一般为220VDC&110VDC&#xff0c;合母电压稍高一些&#xff0c;为240VDC&125VDC。用于控制或信号指示的所有控制回路总…

交流电、交流信号、直流电、直流信号

交流电是正电流和负电流交替起伏的电流&#xff0c;大小和方向都随着时间变化而做周期性变化的电流&#xff08;我们的市电220v50Hz表示一秒内波形起伏50次&#xff09;交流电包含正直流电和负直流电&#xff08;参考点还是地面电压零伏&#xff09; 交流信号是小的交流电流和小…

String、StringBuffer和StringBuilder的区别(面试题)

目录 一、介绍String、StringBuffer和StringBuilder三大类 1.String类 2.StringBuffer类 3.StringBuilder类 4.什么是字符串常量池 4.StringBuilder类为什么不需要同步进行同步操作 二、关于String、StringBuffer和StringBuilder常见的面试题 1.为什么String是不可变的…

计算机机房双电源供电,直流屏是否需要双电源供电?

直流屏和UPS的区别是&#xff1a; 1、应用场合不同&#xff1b;直流屏主要是为开关柜&#xff0c;综合保护装置等提供操作电源或者控制电源&#xff1b;UPS主要是给机房&#xff0c;计算机等提供后备电源。 2、原理不同&#xff1b;直流屏是将交流电整流为直流电&#xff0c;为…

直流-直流(DC-DC)变换电路

直流-直流&#xff08;DC-DC&#xff09;变换电路&#xff0c;可以将一种直流电源经过变换电路后输出另一种具有不同输出特性的直流电源&#xff0c;可以是一种固定电压或可调电压的直流电。按照电路拓扑结构的不同&#xff0c;DC-DC变换电路可以分成两种形式&#xff0c;不带隔…

TFT-LCD电路设计之电源电路(Power IC)

POWER IC REVIEWS Power IC 利用经系统的输入电压生成5种工作电压&#xff0c;一般外界电压&#xff0c;NB为3.3V&#xff0c;Monitor为5V&#xff0c;TV一般为12V&#xff1b; ①VDD&#xff1a;各种逻辑IC电路工作电压&#xff0c;约3.3V左右&#xff0c;一般采用低压差线性…

ADC直流参数

输入电容&#xff1a;采样模式60pF, 保持模式4pF 输入漏电流&#xff1a;典型值1uA&#xff0c;最大为3倍标准差值。 输入阻抗&#xff1a;动态阻抗&#xff0c;最大为3倍标准差值。输入漏电流和输入电容开关充放电的结果。 输入参考电压 DNL: 差分非线性 NMC&#xff1a; N…

LCD屏幕参数

最近调屏参时&#xff0c;对屏幕相关参数做了简单整理&#xff1a; 像素(pixel)&#xff1a;整个显示图像是由一个个的像素组成的。 点时钟速率(VCLK/LCD_CLK)&#xff1a;像素时钟信号。每发出一个脉冲&#xff0c;表示新的一个点图像数据开始传送。视频硬件在显示器上绘制像…