一.gdb调试工具
命令 | 简写 | 作用 |
help | h | 按模块列出命令类 |
help class | 查看某一类型的具体命令 | |
lsit | l | 查看代码,可跟行号和函数名 |
quit | q | 退出gdb |
run | r | 全速运行程序 |
start | 单步执行,运行程序,停在第一行执行语句 | |
next | n | 逐过程执行 |
step | s | 逐语句执行,遇到函数,跳到函数内执行 |
backtrace | bt | 查看函数的调用的栈帧和层级关系 |
info | i | 查看GDB内部局部变量的数值,info breakpoints |
frame | f | 切换函数的栈帧 |
finish | 结束当前函数,返回到函数调用点 | |
set | 设置变量的值 set var n=100 | |
run argv[1] argvp[2] | 调试时命令行传参 | |
| p | 打印变量和地址 |
break | b | 设置断点,可根据行号和函数名 |
delete | d | 删除渐点 d breakpoints NUM |
display | 设置观察变量 | |
continue | c | 取消观察变量 |
enable breakpoints | 继续全速运行剩下的代码 | |
disable breakpoints | 启用断点禁用断点 | |
x | 查看内存x / 20xw 显示20个单元,16进制,4字节每单元 | |
watch | 被设置观察点的变量发生修改时,打印显示 | |
i watch | 显示观察点 | |
core文件 | ulimit -c 1024 开启core文件,调试时 gdb a.out core |
前置工作:
mkdir -p ~/linux_1/gdb_test
cd ~/linux_1/gdb_test
touch gdbtest.c
vim gdbtest.c 往里面随意写入要调试的代码后退出
gcc gdbtest.c -o gdbtest
./gdbtst运行查看结果gcc gdbtest.c -o a.out -g使编译后的gdbtest.c产生的可执行为文件是包含调试信息的
ll 比较gdbtest和a.out的文件大小
1.基础指令
gdb a.out进入调试模式当中,接下来的命令行前都会显示给(gdb)
//l
//list
// 这两种能将要调试的源码列出,但有可能不是从第一行开始显示
list 1
l从第一行源码开始显示,然后依次点 l 会继续往下显示b 行号设置断点
r运行,也可以写成run,然后程序就会停止在设置断点的行号
nnext下一个,如果断点处是个函数,不会进去
sstep单步,如果遇到自定义函数调用会进去继续单步调试如果遇到系统函数,就要按 n ,否则会出错
util 行号如果遇到循环,调试的时候一个循环一个循环的进行,跳过循环内部util 加上循环结束的行号,然后接下来持续按 n 就可以一次n结束一个循环,直到整个for循环结束
p 变量名 查看变量所对应的值
continue不想继续调试了,直接运行剩下的
quit退出当前调试
2.其它指令
1.进入gdb后,使用run去查找段错误出现位置char *p = “hello”;p[3] = ‘M’;对常量进行了赋值会出现段错误
2.strat进入gdb后,不想 b 设置断点然后 run 执行调试,直接从main函数开始调式可以用 start 替代:b main函数所在行号、run
3.finsh结束当前函数的调式
4.set args 参数设置main函数中形参的值,然后start,看代码块A
5.run 字符串1 字符串2 字符串3...和4一样设置main函数中的形参但是要先设置好断点
6.info b查看设置了哪些断点,断点是可以设多个的
7.b 行号 if 条件条件断点,比如fori循环中:b for所在行号 if i = 4在for循环执行到i=4的时候断点才会生效
8.ptype 变量名查看变量的类,ptype i;ptype arr[k]知识点扩展看下面(扩展1)
9.bt或者backtrace,查找函数的调用的栈帧和层级关系会显示出每个栈帧的信息以及编号
10.frame根据栈帧的编号切换栈帧看扩展1
11.display 变量名设置观察变量,一直跟踪该变量的情况,每n一下都会自动显示该变量值会自带编号
12.undisplay 编号取消设置跟踪变量,使用跟踪变量的编号
2.1 set命令行读取输入
--------代码块A
gdbtest.c中:int main(int argc, char *argv[]){printf("%s %s %s \n",argv[1],argv[2],argv[3]); }保存退出[.....]# gcc gdbtest.c -o a.out
[.....]# ./a.out aa bb cc dd会输出aa bb cc 也就是int main函数中的参数会将命令行中的参数给读进去调式的时候,如何往里面添这aa bb cc dd?
[.....]# gcc gdbtest.c -o a.out -g
[.....]# gdb a.out
(gdb) set args aa bb cc dd
(gdb) start
2.2 栈帧:
-----代码块B
void init_arr(int *arr,int len){int i;......
}
void print_arr(....){.....
}
int main(void){char *p = "hello";int len;int arr[k];init_arr(arr,len); -- (A)print_arr(arr,N); -- (B)
}---以上为hello.c中的文件
gcc hello.c -o hello -g
gdb hello
b 1
n
n
s//进入到init_arr函数当中
ptype p
:No symbol "p" int current context//涉及到作用域的问题,p在main栈帧里,不在init_arr栈帧中//那么调式到init_arr函数时如何查看main栈帧中p的类型呢//不采用向init_arr函数中传入p的方式
bt 查看函数的调用的栈帧和层级关系,会显示出每个栈帧的信息栈帧前有对应的数字n,可以通过frame切换栈帧
frame n 切换到main函数的栈帧
ptype p这时候就能查到p的类型
栈帧:随着函数调用而在stack上开辟的一片内存空间,用户存放函数调用时产生的局部变量和临时值
局部变量---函数被调用的时候,会在该函数所在的栈帧上存放局部变量以及传入函数的形参,当函数调用结束后,该栈帧就会被释放掉
临时值---函数的栈帧从哪个位置开始又从哪个位置消失再开始产生栈帧就和临时值有关,比如函数内fori循环,i达到什么条件循环结束,函数结束,i此时就是所谓临时值
如何在栈帧init_arr(调式到了函数init_arr)中去访问到main栈帧中的p呢?
这时候就可以用到指令 backtrace / bt 以及 frame
这就解决了没有向栈帧B(init_arr)中传入所想要查看的函数print_arr中的变量i的时候,在函数B(init_arr)中无法查看函数栈帧A(print_arr)中变量i的值的问题
二.Makefile项目管理
专门管理项目
1.规则
基本原则:
若想生成目标,检查规则中的依赖条件是否存在,如不存在,则寻找是否有规则用来
检查规则中的目标是否需要更新,必须先检查它的所有依赖,依赖中有任一个被更新,则目标必须更新
- 分析各个目标和依赖之间的关系
- 根据依赖关系自底向上执行命令
- 根据修改时间比目标新,确定更新
- 如果目标不依赖任何条件,则执行对应命令,以示更新
命名:makefile/Makefile只能命名这两个,否则无法去使用一些默认的命令目标:依赖条件(一个tab缩进)命令如:
add.o:add.cgcc -Wall -g -c add.c -o add.o
目标:要生成的目标文件
依赖:目标文件由哪些文件生成
命令:通过执行该命令由依赖文件生成目录
1.1 分步骤
[....]# mkdir ~/linux_1/make_test_1
[....]# cd ~/linux_1/make_test_1
[....]# vim ./hello.c写入一部分代码
[....]# touch ./makefile命名只能用makefile或者Makefile------1-----
[....]# vim ./makefile打开makefile向里面去写入所谓的命名规则 hello:hello.cgcc hello.c -o hello:wq
[....]# makemakefile中的依赖条件存在,运行规则
>>gcc hello.c -o hello
[....]# ls
>>hello.c hello makefile------2------
[....]# vim ./makefile打开makefile修改规则,想要让hello.c生成可执行文件需要先-c生成.o,在gcc生成.out文件,hello是最终目标hello:hello.ogcc -c hello.o -o hellohello.o:hello.cgcc -c hello.c -o hello.o:wq
[....]# rm ./hello
[....]# make
>>gcc -c hello.c -o hello.o
>>gcc -c hello.o -o hello
[....]# ls
>>hello hello.o hello.c makefile
1.2 修改编译、最终目标
[....]# cd ~/linux_1/make_test_2/
[....]# touch add.c div1.c hello.c Makefile sub.cadd div1 sub中分别写入函数定义,能够返回各自的值
[....]# vim hello.c写入:int add(int a,int b);......int main(int argc, char *argv[]){int a = 10;int b = 5;printg("%d+%d=%d",a,b,add(a,b));......return 0; }
[....]# gcc hello.c add.c sub.c div1.c -o a.out
[....]# ./a.out
>>10+5=15
>>.....*****使用Makefile*******
[....]# rm a.out
[....]# vim Makefilegcc hello.c add.c sub.c div1.c -o a.out:wq
[....]# make
>>gcc hello.c add.c sub.c div1.c -o a.out//比如对add.c做出修改,但另外两个不用做出修改,这时候要//重新make一下得到修改后得到的可执行文件a.out//但是,sub和div1明明没修改却也还要跟着重新编译一次//就像一个团队中因为一人的原因导致另外的人也要跟着一起加班//因为gcc四步骤中编译最耗时,那么就可以先让.c编程成.o文件
[....]# rm Makefile
[....]# vim Makefilea.out:hello.o add.o sub.o div1.ogcc hello.o add.o sub.o div1.o -o a.outhello.o:hello.cgcc -c hello.c -o hello.oadd.o:add.cgcc -c add.c -o add.o........:wq
[....]# rm ./*.o
[....]# make
>>gcc -c hello.c -o hello.o
>>gcc -c add.c -o add.o
>>.....
>>gcc hello.o add.o sub.o div1.o -o a.out
[....]# ./a.out
>>10+5=10
>>.....
[....]# vim add.c 做出修改return a+b+1:wq
[....]# make
>>gcc -c add.c -o add.o
>>gcc hello.o add.o sub.o div1.o -o a.out//可以发现,这时候只对add.c做出修改,make后//只有add.c重新编译成add.o//然后gcc链接生成可执行文件,div1和sub都没有重新编译//见原理1*****注意******
[....]# rm ./*.o
[....]# rm Makefile
[....]# vim Makefilehello.o:hello.cgcc -c hello.c -o hello.oadd.o:add.cgcc -c add.c -o add.o........a.out:hello.o add.o sub.o div1.ogcc hello.o add.o sub.o div1.o -o a.out:wq
[....]# make
>>gcc -c hello.c -o hello.o//makefile会查看你写的makefile文件,把它碰到的第一组规则作为//最终目标,一旦最终目标执行了,规则都不在执行//指定终极目标见原理1
[....]# make -f m
# 如果创建的makefile不在当前目录,而在m目录中,用-f指明
原理:
因为add.c的时间是比add.o的时间早的,也就是现有add.c后又add.o,而这时候add.c一旦做出修改,那么其时间就比add.o的时间晚了,此时makefile辨别到,就会对add.c重新编译生成新的add.o
也就是依赖条件 add.c 时间比生成目标 add.o 时间要早
可以通过ALL来指定终极目标
2.函数
Makefile函数
src = $(wildcard *.c)
# 找到当前目录下所有后缀为.c的文件,将文件名提取出来作为一个列表赋值给src
# 此时src就是所谓的依赖列表obj = $(patsubst %.c,%.o,$(src))
把src变量里所有后缀位.c的文件替换成.o,同时作为新列表存储到obj
%是匹配等价的意思,比如从src取出来的是./src/add.c,那么%.c中的%就是./src/add
然后会等价给%.o,是通配符,有锁定的意思
运用》》》
clean函数
vim ./makefileclean:-rm -rf $(obj) a.out# –: “-”此条命令出错,make也会继续执行后续的命令。如:“-rm main.o”:wqmake clean -n# 检查要执行的命令是否正确,只打印要执行的命令
make clean # 确认没问题后再去执行
如果目录下已经被创建了ALL或者是clean文件,那么可能就不会去执行clean命令,可以通过:
.PHONY:clean ALL
生成伪目标,不管条件满足与否,它这个目标都会被执行
Makefile函数new
a. $(foreach var, list, text) # 对于list中的每个值var都执行text,然后用=赋值存到起来
b. $(filter pattern..., text) # 在text中取出符合pattern格式的值$(filter-out pattern..., text) # 在text中取出不符合pattern格式的值
c. $(wildcard pattern) # 在patter定义了文件名的格式,wildcard取出其中存在的文件
d. $(pastubst pattern, replacement, text)# 在test列表中符合pattern的将其替换成replacement,然后将替换后的列表传出
# a
A = a b c
B = $(foreach f, &(A), &(f).o) # b
C = a b c d/
D = $(filter %/, $(C))
E = $(filter-out %/, $(C)) # c
F = $(wildcard *.c)
F2 = a.c b.c c.c d.c
F3 = $(wildcard $(F2)) # 看F2中哪些文件是真实存在makefile所在目录下的# d
G = $(pastubst %.c, %.o, $(F)) # 将F中的.c都换成.o文件,然后将列表赋值到GALL:echo B = $(B) # echo B = a.o b.o c.oecho D = $(D) # echo D = d/echo E = $(E) # echo E = a b c echo F = $(F) # 输出的是和makefile同个目录下的.c文件echo F3 = $(F3)# 输出的makefile目录下和F2同时拥有的.c文件 echo G = $(G) # a.o b.o c.o d.o
3.自动变量
$@:在规则的命令中,表示规则中的目标
$<:在规则的命令中,表示规则中的第一个条件,如果将该变量
应用在模式规则当中,它可将依赖条件列表中的依赖依次取
出,套用模式规则
$^:在规则的命令中,表示规则中的所有条件,组成一个列表,
以空格隔开,如果这个列表中有重复的项则消除重复项。
》》》》
add.o依赖于add.c,也就是add.c($<,条件)决定add.o($@,目标)
模式规则
.o 依赖于 .c
静态模式规则
$(obj):%.o:%.c
gcc -c $< -o %@
# 表示对哪一个依赖条件 $(obj) 去套用这个规则
4.赋值方法
:= # 即使变量 A := XXX -> A的值即可确定,在定义时即确定
= # 延时变量 B = XXX -> B的值使用到时才确定
?= # 延时变量,如果是第1次定义才起效,如果在前面该变量已定义,则忽略这句
+= # 附加,它是即时变量还是延时变量却决于前面的定义
********eg:
A = $(C)
B := $(c)
D = 100ask
D ?= weiALL:echo A = $(C) # 空echo B = $(C) # abcecho D = $(D) # 100ask
C = abc# A的值在定义的时候就已经确定了,也就是为空(C为空),即使后面给C赋值了abc,A也还是为空
# 在echo B,B的值才被确认,ALL是最终目标,C先被赋值了abc,echo B 使用了B,B的值确定为abc
# D第一次已经被定义,D ?= wei不会赋值到D上,D的值还是第一次赋值的100ask
5.合并
经过上面合并可以得出最终的模板:
1、2:src存放的是make后面的多个.c文件的列表,obj则是将src中的.c文件列表变成.o列表赋值到obj
11、12:挨个去生产.o文件(E预处理.i、S编译.s、c预处理.o)
8、9:差不多是链接,collect2将obj中对应的所有.o文件都链接成a.out目标文件
17:将clean ALL判断为假想目标,这样就不会去判断其是否存在
other:
objs := main.o sub.o
test : $(objs)
gcc -o test $^# 需要判断是否存在依赖文件
# .main.o.d .sub.o.d
dep_files := $(foreach f, $(objs), .$(f).d)
dep_files := $(wildcard $(dep_files))# 把依赖文件包含进来
ifneq ($(dep_files),)include $(dep_files)
endif%.o : %.c
gcc -Wp,-MD,.$@.d -c -o $@ $<clean:
rm *.o test -fdistclean:
rm $(dep_files) *.o test -f
6.运用
head.h文件:
hello.c:
# obj目录中存放.o文件,src目录中存放.c文件,makefile放规则
# inc目录存放.h文件
# 对makefile中的规则重新设置,指定路径
[....]# cd /StudyTest/maketest
[....]# touch ./inc
[....]# cd ./inc
[....]# vim head.h[....]# cd .. # 返回上一级目录,也就是maketest[....]# ls ./src
>>add.c div1.c hello.c mul.c sub.c[....]# ls
>>inc obj src makefile[....]# gcc -c ./src/add.c -o ./obj/add.o -I ./inc# 不使用make规则来实现。。。。。
[....]# gcc add.o sub.o ... hello.o -o a.out# 链接生成可执行文件# 使用make规则来写:
[....]# vim makefilesrc=$(wildcard ./src/*.c)obj=$(patsubst ./src/%.c ./obj/%*.o, $(src))# 用%代替add、sub等名字inc_path=./inc # 指定头文件所在目录myArgs= -Wall -gALL:a.outa.out:$(obj)gcc $^ -o $@ $(myArgs)$(obj):./obj/%.o:./src/%.cgcc -c $< -o $@ $(myArgs) -I $(inc_path)# 头文件的处理是在预处理阶段的,不是在链接阶段# 因此 -I 得加在这里clean:-rm -rf $(obj) a.out.PHONY:clean ALL
7.选项参数
-n模拟执行make,make clean命令我们可以使用“-f”选项指定文件,不再使用名为“Makefile”的文件,比
如:make -f Makefile.build
我们可以使用“-C” and “-f”选项指定目录,切换到其他目录里去执行指定的make文件,比如:make -C a/ -f Makefile.build
我们可以指定目标,不再默认生成第一个目标:make -C a/ -f Makefile.build other_target
8.通用Makefile
顶层目录Makefile.build:
PHONY := __build
# 第一个目标
__build:
# obj-y是Makegile中定义过的,expert导入Makefile后有Makegile中已经被赋值上的列表
obj-y :=subdir-y :=
EXTRA_CFLAGS :=# 将目录下的Makefile文件包含进来 -- 类似于合并
# 在顶层目录去执行Makefile去执行就包含进顶层的Makefile
# -C指定了去子目录执行Makefile就包含进子目录下的Makefile
include Makefile# obj-y := main.o sub.o a/ c/ d/
# $(filter %/, $(obj-y)) : a/ c/ d/
# __subdir-y : a c d
# subdir-y : a c d,存放的是子目录的录名
# 在顶层去执行Makefilebuild:将子目录的(例如a/ c/ d/)的a b c目录名取出来,附加到即时变量subdir-y
# 如果是在子目录去执行Makefilebuild,则下面两个列表都是为空(子目录无子目录)
__subdir-y:= $(patsubst %/,%,$(filter %/, $(obj-y)))
subdir-y+= $(__subdir-y)# 各子目录中的.c文件编译后打包起来的.o列表:a/built-in.o c/built-in.o d/built-in.o
# 这个是在子目录中执行完Makefilebuild后的假想打包起来的.o列表,在子目录未提前存好打包的列表
subdir_objs := $(foreach f,$(subdir-y),$(f)/built-in.o)# 顶层目录中的.c文件的编译后的想要取名的.o列表:main.o sub.o
# 如果是去子目录中执行的Makefilebuild,挑选出的就是a下的sub2.o sub3.o
cur_objs := $(filter-out %/, $(obj-y))# 加上.d后缀挑出编译后目录中存在的.d存起来,用于distclean清除文件
dep_files := $(foreach f,$(cur_objs),.$(f).d)
dep_files := $(wildcard $(dep_files))
# 包含进Makefilebuild文件中
ifneq ($(dep_files),)include $(dep_files)
endifPHONY += $(subdir-y)# 第一个目标,__build,在第3行指明过,依赖未形成,去执行依赖
__build : $(subdir-y) built-in.o# 编译子目录,进入子目录编译,TOPDIR是Makefile中的,是该Makefilebuild的目录位置
# 就是$@指定了make要去子目录下make,执行的makefile文件是顶层目录的Makefile.build
# 而include则是包含了$@子目录下的Makefile -- a/中也有自己的Makefile文件
$(subdir-y):
make -C $@ -f $(TOPDIR)/Makefile.build# 当前目录下的.o 和子目录下的 built-in.o() 要打包起来,全都打包进built-in.o
built-in.o : $(cur_objs) $(subdir_objs)
$(LD) -r -o $@ $^# 存放依赖文件
dep_file = .$@.d# 假想的各自的.o
# 编译当前目录中的文件
# 编译参数
# 额外的编译选项(可以不设置,是设置了makefile的文件的所属目录才有该参数的功能)-- 子目录中makefile设置了,子目录下的.c有该参数功能
# 额外给xxx.c设置它自己的编译选项(是设置了的makefile文件所属目录下的指定.c才有该参数的功能) -- 子目录中的makefile设置了,子目录下指定的.c有该参数功能
%.o : %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$@) -Wp,-MD,$(dep_file) -c -o $@ $<.PHONY : $(PHONY)
顶层Makefile:
CROSS_COMPILE =
AS= $(CROSS_COMPILE)as
LD= $(CROSS_COMPILE)ld
CC= $(CROSS_COMPILE)gcc
CPP= $(CC) -E
AR= $(CROSS_COMPILE)ar
NM= $(CROSS_COMPILE)nmSTRIP= $(CROSS_COMPILE)strip
OBJCOPY= $(CROSS_COMPILE)objcopy
OBJDUMP= $(CROSS_COMPILE)objdump# 该变量的值在所有目录中都可以见
export AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP# 编译参数
CFLAGS := -Wall -O2 -g
# 库的目录,头文件h的路径
CFLAGS += -I $(shell pwd)/include LDFLAGS := export CFLAGS LDFLAGS# 当前目录,该变量的值在所有目录中都可见(export)
TOPDIR := $(shell pwd)
export TOPDIR# 链接后要产生的指定的可执行文件的名
TARGET := test# obj-y存放的是顶层目录的.o以及子目录a/等
# 添加本目录下的main.c(main.o)和sub.c(sub.o)编进进obj-y
obj-y += main.o
obj-y += sub.o
# 指定子目录(存放其它.c文件的子目录) -- 添加进列表
# 表示要进入subdir这个子目录下去寻找文件来编进程序里,是哪些文件由subdir日录下的Makefile决定
obj-y += a/# 目标是all,依赖是start_recursive_build和$(TARGET),还有,产生
all : start_recursive_build $(TARGET)
@echo $(TARGET) has been built! # 可执行文件已经创建# 执行本本目录下的Makefile.build处理顶层目录(Makefilebuild本目录)
# $(TOPDIR)存放的是前面shell pwd获得的Makefilebuild的路径
# -C后面跟的是目录,表示make要执行哪个文件的makefile
# 用-f来指定make执行的是Makefilebuild,不添加该参数make还是执行本Makefile
start_recursive_build:
make -C ./ -f $(TOPDIR)/Makefile.build# 顶层 Makefile 中把顶层目录的 built-in.o 链接成 APP,生成可执行文件
$(TARGET) : built-in.o
$(CC) -o $(TARGET) built-in.o $(LDFLAGS)clean:
rm -f $(shell find -name "*.o")
rm -f $(TARGET)distclean:
rm -f $(shell find -name "*.o")
rm -f $(shell find -name "*.d")
rm -f $(TARGET)# 顶层目录的Makefile:
# 它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,
# 主要是定义工具链前缀CROSS COMPILE,定义编译参数CFLAGS,
# 定义链接参数LDFLAGS,这些参数就是文件中用export导出的各变量。
子目录Makefile:EXTRA_CFLAGS := -D DEBUG
CFLAGS_sub3.o := -D DEBUG_SUB3obj-y += sub2.o
obj-y += sub3.o
说明:
本程序的Makefile分为3类:
1. 顶层目录的Makefile
2. 顶层目录的Makefile.build
3. 各级子目录的Makefile一、各级子目录的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设置它自己的编译选项, 可以不设置注意:
1. "subdir/"中的斜杠"/"不可省略
2. 顶层Makefile中的CFLAGS在编译任意一个.c文件时都会使用
3. CFLAGS EXTRA_CFLAGS CFLAGS_xxx.o 三者组成xxx.c的编译选项二、顶层目录的Makefile:它除了定义obj-y来指定根目录下要编进程序去的文件、子目录外,主要是定义工具链前缀CROSS_COMPILE,定义编译参数CFLAGS,定义链接参数LDFLAGS,这些参数就是文件中用export导出的各变量。三、顶层目录的Makefile.build:这是最复杂的部分,它的功能就是把某个目录及它的所有子目录中、需要编进程序去的文件都编译出来,打包为built-in.o详细的讲解请看视频。
四、怎么使用这套Makefile:
1.把顶层Makefile, Makefile.build放入程序的顶层目录在各自子目录创建一个空白的Makefile2.确定编译哪些源文件修改顶层目录和各自子目录Makefile的obj-y : obj-y += xxx.o
obj-y += yyy/
这表示要编译当前目录下的xxx.c, 要编译当前目录下的yyy子目录3. 确定编译选项、链接选项修改顶层目录Makefile的CFLAGS,这是编译所有.c文件时都要用的编译选项;修改顶层目录Makefile的LDFLAGS,这是链接最后的应用程序时的链接选项;修改各自子目录下的Makefile:"EXTRA_CFLAGS", 它给当前目录下的所有文件(不含其下的子目录)设置额外的编译选项, 可以不设置"CFLAGS_xxx.o", 它给当前目录下的xxx.c设置它自己的编译选项, 可以不设置4. 使用哪个编译器?修改顶层目录Makefile的CROSS_COMPILE, 用来指定工具链的前缀(比如arm-linux-)5. 确定应用程序的名字:修改顶层目录Makefile的TARGET, 这是用来指定编译出来的程序的名字6. 执行"make"来编译,执行"make clean"来清除,执行"make distclean"来彻底清除