Makefile管理工具与GDB调试工具学习笔记

一、Makefile项目管理工具:

1、用途:

①项目代码编译管理(分模块管理)
②节省编译项目时间(更新的需要重新编译)
③一次编写终身受益

2、规则记录:

(1)、三要素:
目标、依赖、命令,格式如下:

目标:依赖(条件)
	命令
/*注意:命令前有一个Tab键。*/

(2)、基本实现:
以加减乘除计算的四个函数为例:

all:add.c sub.c dive.c mul.c main.c 
	gcc add.c sub.c dive.c mul.c main.c -o app

解析:为了达成all目标(目标名自定义,一般以目标生成文件名为目标名),依赖于一系列“.c”文件。如何达成目标?就是执行命令“gcc add.c sub.c dive.c mul.c main.c -o app”。

(3)、工程编译中的细化:

app:add.o sub.o dive.o mul.o main.o
	gcc add.o sub.o dive.o mul.o main.o -o app
add.o:add.c
	gcc -c add.c
sub.o:sub.c
	gcc -c sub.c
dive.o:dive.c
	gcc -c dive.c
mul.o:mul.c
	gcc -c mul.c
main.o:main.c
	gcc -c main.c

关系图如下:
这里写图片描述

Makefile工作步骤:
建立关系树(树根节点为需要生成的目标可执行文件)->根据关系树从底到上执行命令->根据依赖文件的最后更改时间是否比目标新,来确定是否需要执行命令进行更新->如果目标不依赖任何文件,则执行对应命令,以示更新。若目标不存在,则认为依赖比目标新。

clean:
	-rm -f *.o
	-rm -f app

对于上面这个目标指令一般存在于Makefile文件最后,make clean(只执行clean目标的命令,“make + 目标名”只执行目标对应的命令),注意:rm前的“-”表示即使该条命令出错也会继续向后执行。

(4)、make clean的问题:
由于clean无依赖关系所以加上:“.PHONY:clean”用来生成一个伪目标,否则如果Makefile文件所在文件夹中有一个无关的名为clean文件,make clean就会出错,事与愿违。如下:

.PHONY:clean
clean:
	-rm -f *.o
	-rm -f app

(5)、“@”符号:

不显示命令本身,只显示结果eg:

test:
	echo "hello"
	@echo "hello" 

make test看一看结果:
不加@符号:
这里写图片描述
加@符号:
这里写图片描述
(6)、更高级的Makefile(#表示注释):

#$表示索引
#三个重要的变量:"$@"表示目标,"$^"表示所有依赖,"$<"表示依赖中的第一个

obj=add.o sub.o dive.o mul.o main.o
app:$(obj)
	gcc $(obj) -o app
	
#采用内建语法规则去编译(有的Makefile下面默认没有,需要自己写)
%.o:%.c
	gcc -c $< -o $@
	
.PHONY:clean
clean:
	-rm -f *.o
	-rm -f app

(7)、借助Makefile的函数去优化:

#wildcard函数找到当前目录下的所有.c文件,赋给变量src
src = $(wildcard *.c)
#patsubst函数将匹配到的所有.c转换成.o赋给变量obj
obj = $(patsubst %.c,%.o,$(src))
#为目标文件重命名
target = app

$(target):$(obj)
	gcc $^ -o $@
%.o:%.c
	gcc -c $< -o $@
.PHONY:clean
clean:
	-rm -f *.o
	-rm -f app

(8)、借助变量去优化(变量名固定):

#指定头文件位置在./include目录中
CPPFLAGS= -I ./include
#指定编译参数
CFLAGS= -g -Wall
#指定链接库位置path与名字,无连接库不赋值即可
#LDFLAGS=
LDFLAGS= -L path -lmycalc
#指定编译器
CC=gcc

#用于Linux嵌入式编译的编译器
#CC=arm-linux-gcc

src = $(wildcard *.c)
obj = $(patsubst %.c,%.o,$(src))
target = app

$(target):$(obj)
	$(CC) $^ $(LDFLAGS) -o $@
%.o:%.c
	$(CC) -c $< $(CFLAGS) $(CPPFLAGS) -o $@
.PHONY:clean
clean:
	-rm -f *.o
	-rm -f app

(9)、思考:
根目录下有一个Makefile,下一级目录home中有一个Makefile,如何在根目录下make实现home目录下的项目编译?
实现:在根目录下的Makefile中写:

#-C参数指定进入的路径,即在根目录下执行make时,先读取根目录下的Makefile文件即执行make -C /home/:先进入/home再读取/home下的Makfile文件。

all:
	make -C /home/

(10)、关于头文件的依赖关系:

此外注意:以上规则仅针对了源文件和目标文件,并没有指定头文件如果发生改变,如何?即使头文件发生变化(主要针对自定义头文件,标准库/系统库的头文件我们默认是不会发生改变的),规则也不会更新执行。为此我们需要为目标文件添加除原文件之外的头文件的依赖,比如:

main.o:main.c stalib.c mylib.c string.c ......

但是这样添加会永无止境,每增加一个自定义头文件或删除一个头文件,都需要修改Makefile,所以gcc有-M(生成依赖关系,并打印到屏幕上,同时阻止编译过程)、-MD、-MF等参数对此问题进行解决,主要使用"-MD"(生成.d依赖文件的同时不阻止编译任务,.d文件名默认和.c文件名除后缀外一致)、"-MMD"(过滤掉标准库和系统库头文件,-MM对于-M也是同样效果)、"-MF"(指定输出.d文件名);其他参数具体的内容参照:Linux Makefile 生成 *.d 依赖文件及 gcc -M -MF -MP 等相关选项说明

简单测试如下:

Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ ls
include  led.c  Makefile  start.S

#采用 -MD -MF参数
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ gcc -o led.exe -nostartfiles -e led_light -MD -MF led_md.d -I ./include/soc_s3c2440/ led.c 

#采用-MMD -MF参数
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ gcc -o led.exe -nostartfiles -e led_light -MMD -MF led_mmd.d -I ./include/soc_s3c2440/ led.c 

#很明显,led_mmd.d比led_md.d要小的多,因为缺少系统库和标准库头文件
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ ll
总用量 30
drwxrwxrwx 1 root root  4096 4月   5 21:13 ./
drwxrwxrwx 1 root root 12288 4月   5 21:03 ../
drwxrwxrwx 1 root root     0 4月   5 21:03 include/
-rwxrwxrwx 1 root root  3348 4月   5 21:05 led.c*
-rwxrwxrwx 1 root root  5856 4月   5 21:13 led.exe*
-rwxrwxrwx 1 root root  1696 4月   5 21:12 led_md.d*
-rwxrwxrwx 1 root root   146 4月   5 21:13 led_mmd.d*
-rwxrwxrwx 1 root root   857 4月   5 21:03 Makefile*
-rwxrwxrwx 1 root root   795 4月   5 21:03 start.S*

#-MD -MF led_md.d
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ cat led_md.d 
led.exe: led.c /usr/include/stdc-predef.h /usr/include/stdio.h \
 /usr/include/features.h /usr/include/x86_64-linux-gnu/sys/cdefs.h \
 /usr/include/x86_64-linux-gnu/bits/wordsize.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs.h \
 /usr/include/x86_64-linux-gnu/gnu/stubs-64.h \
 /usr/lib/gcc/x86_64-linux-gnu/5/include/stddef.h \
 /usr/include/x86_64-linux-gnu/bits/types.h \
 /usr/include/x86_64-linux-gnu/bits/typesizes.h /usr/include/libio.h \
 /usr/include/_G_config.h /usr/include/wchar.h \
 /usr/lib/gcc/x86_64-linux-gnu/5/include/stdarg.h \
 /usr/include/x86_64-linux-gnu/bits/stdio_lim.h \
 /usr/include/x86_64-linux-gnu/bits/sys_errlist.h /usr/include/unistd.h \
 /usr/include/x86_64-linux-gnu/bits/posix_opt.h \
 /usr/include/x86_64-linux-gnu/bits/environments.h \
 /usr/include/x86_64-linux-gnu/bits/confname.h /usr/include/getopt.h \
 /usr/include/stdlib.h /usr/include/x86_64-linux-gnu/bits/waitflags.h \
 /usr/include/x86_64-linux-gnu/bits/waitstatus.h /usr/include/endian.h \
 /usr/include/x86_64-linux-gnu/bits/endian.h \
 /usr/include/x86_64-linux-gnu/bits/byteswap.h \
 /usr/include/x86_64-linux-gnu/bits/byteswap-16.h \
 /usr/include/x86_64-linux-gnu/sys/types.h /usr/include/time.h \
 /usr/include/x86_64-linux-gnu/sys/select.h \
 /usr/include/x86_64-linux-gnu/bits/select.h \
 /usr/include/x86_64-linux-gnu/bits/sigset.h \
 /usr/include/x86_64-linux-gnu/bits/time.h \
 /usr/include/x86_64-linux-gnu/sys/sysmacros.h \
 /usr/include/x86_64-linux-gnu/bits/pthreadtypes.h /usr/include/alloca.h \
 /usr/include/x86_64-linux-gnu/bits/stdlib-float.h \
 include/soc_s3c2440/soc_s3c2440_reg_operator.h \
 include/soc_s3c2440/soc_s3c2440_reg.h \
 include/soc_s3c2440/soc_s3c2440_field.h
 
 #-MMD -MF led_mmd.d
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ cat led_mmd.d 
led.exe: led.c include/soc_s3c2440/soc_s3c2440_reg_operator.h \
 include/soc_s3c2440/soc_s3c2440_reg.h \
 include/soc_s3c2440/soc_s3c2440_field.h
Krj@VM:/mnt/hgfs/workspace/S3C2440/1stQuarter_BarBoard/04_make_file/test$ 

二、Makefile的函数使用与规则适用测试:

测试用例如下所示:

A = a b c.pl aaea af bag ia j kka n.txt b.txt c.jpg

#过滤符合格式的参数"$(filter va1 var2 var3 ...,list)"#
B = $(filter %a,$(A))

#过滤掉不符合格式的参数:"$(filter-out va1 var2 var3 ...,list)"#
C = $(filter-out %a %.txt %.jpg %.pl,$(A))

#对列表中的参数执行指定操作:"$(foreach var,list,operator)"#
D_HEAD = $(foreach f,$(C),$(f).h $(f).hpp)
D_SOURCE = $(foreach f,$(C),$(f).c $(f).cpp $(f).i $(f).s $(f).asm $(f).hpp)
D_TARGET = $(foreach f,$(C),$(f).bin $(f).o $(f).obj $(f).exe $(f).pck $(f).sta)
D += $(D_HEAD)
D += $(D_SOURCE)
D += $(D_TARGET)

#获取指定文件下的指定类型文件:"$(wildcard file1 file2 file3...)"#
SRC = $(wildcard *.c *.cpp *.s)
HEAD = $(wildcard *.h *.hpp)

#对指定类型文件名进行修改:"$(patsubst format1 format2 ...,format_trget,list)"#
C_OBJ = $(patsubst %.c,%.o,$(SRC))
CPP_OBJ = $(patsubst %.cpp,%.o,$(SRC))
S_OBJ = $(patsubst %.s %asm,%.o,$(SRC))

first:target_B target_C target_file format_file
	@echo A = $(A)

.PHONY:target_B
target_B:
	@echo B = $(B)	

.PHONY:target_C
target_C:
	@echo C = $(C)

.PHONY:target_file
target_file:
	@echo D = $(D)	
	@touch $(D)

.PHONY:target_choose
target_choose:
	@echo $1 $2 $3
	@touch ${$3}
	
.PHONY:format_file
format_file:
	@echo SRC = $(SRC)
	@echo HEAD = $(HEAD)
	@echo C_OBJ = $(C_OBJ)
	@echo CPP_OBJ = $(CPP_OBJ)
	@echo S_OBJ = $(S_OBJ)

.PHONY:clean
clean:
	rm -f *.*

.PHONY:clean_head
clean_head:
	rm -f *.h *.hpp

.PHONY:clean_source
clean_source:
	rm -f *.c *.cpp *.s *.asm *.i

.PHONY:clean_target
clean_target:
	rm -f *.o *.bin *.exe *.sta *.pck *.obj

在这里插入图片描述
三、关于CMAKE:

不再赘述,有需要时参见:CMake 入门实战

四、GDB调试工具:

1、首先编译的时候必须先加-g(不加-g无法调试)

eg:gcc -o test1 test.c(×)
gcc -o test2 -g test.c(√)

我们发现加了-g的链接文件比不加-g的大一些。因为加-g的包含调试信息:

这里写图片描述
2、GDB命令:调试时输入GDB test2进入GDB

(括号中的字母均是简写)
help(h):查看常用命令类,“help + 类名”查看该类的具体包含的命令;
quit(q):退出;
run(r):全速运行;
start:启动程序,单步执行;
list:查看程序代码,一次显示一段,连续list则按顺序一段一段显示(回车可重复上次命令),“list + 函数名”可查看指定函数;
next(n):逐过程,执行下一步(回车可重复上次命令),不进入语句调用的函数中
step(s):逐语句,执行下一步(回车可重复上次命令),会进入语句调用的函数中
print(p):加变量名可以打印变量内容(eg:print i、print &i等);

break(b):设置断点,"break + 行号"为文件中某行设置断点
continue(c):继续全速运行

info(i):查看GDB内部局部变量的值
info breakpoints:查看设置的断点信息
delete(d) breakpoints 编号:删除指定编号断点

backtrace(bt):显示当前的所在位置的函数调用关系
frame:切换栈帧(frame + 行号),暂时切换到指定行(eg:当前在100行,要查看其他函数的局部变量(假设在第十行):frame 10,在第十行的函数中查看局部变量的值,查看完之后next回到101行,next能回到101行是因为frame(f):是切换栈帧,就是这个意思)

finsh:结束当前函数,返回函数调用点
set:设置变量值(eg:set var n=100,set var buf[2]='x',...)
run argv[1] argv[2]...:调试时命令行传参

display:设置观察点(eg:display num)
undisplay:取消观察点设置(undisplay 观察点编号)
enable breakpoints:启用所有断点
disable breakpoints:禁用所有断点

x:查看内存(eg:x/20x buf,20个字节,按十六进制显示)
watch:被设置观察点的变量被修改时,会打印显示(与display区别)
(info)i watch:显示观察点

core:核心(日志文件)
ulimit -a:查看core文件大小,默认是0
ulimit -c 1024:将core文件大小调节成指定的1024
gdb ./a.out core:便可查看程序bug出现时的信息。

set follow-fork-mode child:跟踪子进程
set follow-fork-mode parent:跟踪父进程
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值