03_makefile_gdb

代码: https://github.com/WHaoL/study/tree/master/00_06_Linux_SystemCode_and_SocketCode

代码: https://gitee.com/liangwenhao/study/tree/master/00_06_Linux_SystemCode_and_SocketCode

1. Makefile

在这里插入图片描述

什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。

因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Visual C++的nmake,Linux下GNU的make。

1.1 文件命名

  • 两种书写格式:
    • Makefile
    • makefile

1.2 makefile中的规则

# 有了makefile, 这个文件中有多个规则, 执行make构建命令, 生成的是第一条规则的目标
gcc a.c b.c c.c -o app
# make的构建规则
目标 ... : 依赖 ... 
	命令		# 需要有一个tab缩进
	...

# 目标: 最终要生成的文件, 一般写一个, 可以有多个, 通过命令生成的
# 依赖: 生成目标需要使用的文件, 在命令中被使用, 依赖可以有多个
# 命令: 一般是一个gcc命令, 通过对依赖进行编译生成目标, 命令可以有多个

# 参考:
$ gcc a.c b.c c.c -o app
# 改成一个规则
app:a.c b.c c.c
	gcc a.c b.c c.c -o app

###################### 例子 #################
app:a.o b.o c.o
	gcc a.o b.o c.o -o app

a.o:a.c
	gcc -c a.c
b.o:b.c
	gcc -c b.c
c.o:c.c
	gcc -c c.c

1.3 makefile中的知识点

1.3.1 工作原理

  • 命令在执行之前会检测依赖是否存在
    • 如果依赖不存在:
      • make会向下检测其他的规则, 看有没有一条规则是用来生成这个依赖的
      • 如果找到了这个规则, 执行规则中的命令, 得到了需要的依赖
    • 依赖存在就不向下检测了
  • 关于文件更新 (不适用于伪目标)
    • 执行make命令, 规则中的依赖和目标这些文件会进行一个文件时间的比较
      • 如果目标文件不存在
        • 目标文件被生成 -> 执行对应的命令
      • 如果依赖时间早, 目标时间晚 -> 正常
        • 目标不需要重写更新, 不需要重新生成, 使用旧的就可以
      • 依赖时间晚, 目标的时间早 -> 不正常
        • 说明依赖被重新修改 -> 目标需要重新生成
          • 对应的命令就被执行了

1.3.2 变量

  • 自定义变量

    # 没有类型
    # 自定义变量, 变量名小写
    变量名=变量值
    # 举例:
    var=a.c b.c c.c
    # 取变量的值
    $(变量名)
    
  • 默认的自带变量

    # 内部变量, 变量名大写
    CC
    # 取值, gcc
    var=$(CC)
    
  • 自动变量

    # 只能在规则中的命令中使用
    $@: 规则中的目标
    $<: 规则中的第一个依赖
    $^: 规则中的所有的依赖
    # 例子
    app:a.o b.o c.o
    	gcc a.o b.o c.o -o app
    
    # 改进
    app:a.o b.o c.o
    	gcc $^ -o $@
    # 例子	
    a.o:a.c
    	gcc -c a.c
    # 改进
    a.o:a.c
    	gcc -c $< -o $@
    	gcc -c $^ -o $@
    

1.3.3 模式匹配

app:add.o  div.o  main.o  mult.o  sub.o                                      
    gcc add.o  div.o  main.o  mult.o  sub.o -o app
    
# 语法
%.o:%.c
	gcc -c $< -o $@

%: 通配符, 前后对应的是同一字符串

在这里插入图片描述

1.3.4 函数

makefile中所有的函数都是有返回值的, 因此所有函数的样式:

$(函数名 参数1, 参数2, 参数3, ...)

  • 获取指定目录下指定类型的文件列表 – wildcard

    $(wildcard PATTERN...) 
    
    • 功能:
      • PATTERN 指的是某个或多个目录下的对应的某种类型的文件
      • 如果有多个目录,一般使用空格间隔
    • 返回值:
      • 得到的若干个文件的文件列表, 文件名之间使用空格间隔
    • 示例:$(wildcard *.c ./sub/*.c)
      • 返回值格式: a.c b.c c.c d.c e.c f.c ./sub/aa.c ./sub/bb.c
  • 模式字符串替换函数——patsubst

    $(patsubst <pattern>,<replacement>,<text>)
    # 举例: 
    # % 通配符 和第二个参数的 % 对应, 代表的是同一个字符串
    $(patsubst %.c, %.o, a.c b.c d.c e.c)
    # 得到的返回值:
    a.o b.o d.o e.o
    
    • 功能:
      • 查找 中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式,如果匹配的话,则以模式替换。
      • 这里,可以包括通配符%,表示任意长度的字串。如果中也包含%,那么,中的这个%将是中的那个%所代表的字串。(可以用“\”来转义,以\%来表示真实含义的%字符)
    • 返回值:
      • 函数返回被替换过后的字符串。
    • 示例: $(patsubst %.c, %.o, x.c.c bar.c)
      • 把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o]
      • 返回结果是“x.c.o bar.o”

1.4 makefile的编写

# 环境准备:
.
├── add.c
├── div.c
├── head.h
├── main.c
├── mult.c
└── sub.c

版本1

# 如果对其中某个.c进行修改, 需要重新编译, 会编译多少个文件: 全部
# 效率很低
app:add.c  div.c  main.c  mult.c  sub.c
	gcc add.c  div.c  main.c  mult.c  sub.c -o app 
# 改进方案: 将这个唯一的规则拆分为若干个

版本2

# 这个版本只适合文件比较少的情况, 太冗余
# 使用变量进行精简
app:add.o  div.o  main.o  mult.o  sub.o                                      
    gcc add.o  div.o  main.o  mult.o  sub.o -o app

add.o:add.c
    gcc -c add.c

div.o:div.c
    gcc -c div.c
    
main.o:main.c
    gcc -c main.c

mult.o:mult.c
    gcc -c mult.c

sub.o:sub.c
    gcc -c sub.c

版本3

# 缺点: 如果源文件比较多, 指定.o时候很麻烦
# 改进: 自动搜索指定目录下的源文件(.c), 将.c -> .o文件就可以了
# 需要使用makefile中的函数
target=app                                                                                                             
objs=add.o  div.o  main.o  mult.o  sub.o
$(target):$(objs)
    gcc $(objs) -o $(target)

%.o:%.c
    gcc -c $< -o $@

版本4

# 缺点: 功能不完善, 只能编译代码, 不能删除生成的目标
# 改进: 添加一个规则用来删除文件
target=app                                                                 
# 搜索指定目录下的指定格式的文件 .c
src=$(wildcard ./*.c)
# 将得到的文件列表中文件的后缀.c -> .o
objs=$(patsubst %.c, %.o, $(src))

$(target):$(objs)
    gcc $(objs) -o $(target)

%.o:%.c
    gcc -c $< -o $@

版本5

target=app
# 搜索指定目录下的指定格式的文件 .c
src=$(wildcard ./*.c)
# 将得到的文件列表中文件的后缀.c -> .o
objs=$(patsubst %.c, %.o, $(src))

$(target):$(objs)
    gcc $(objs) -o $(target)

%.o:%.c
    gcc -c $< -o $@

# 将clean声明为伪目标
.PHONY:clean                                                                   
clean:
    rm $(objs) $(target)
    
##############################################################
# 伪目标: 这个目标在磁盘上没有实体, 不对应一个磁盘文件, 不会进行依赖和目标的时间检测
# 需要关键字: 
.PHONY:伪目标文件的名字

2. GDB调试

gdb 是由 GNU 软件系统社区提供的调试器,同 gcc 配套组成了一套完整的开发环境,可移植性很好,支持非常多的体系结构并被移植到各种系统中(包括各种类 Unix 系统与 Windows 系统里的 MinGW 和 Cygwin )。此外,除了 C 语言之外,gcc/gdb 还支持包括 C++、Objective-C、Ada 和 Pascal 等各种语言后端的编译和调试。 gcc/gdb 是 Linux 和许多类 Unix 系统中的标准开发环境,Linux 内核也是专门针对 gcc 进行编码的。

gdb 的吉祥物是专门捕杀 bug 的射手鱼:

在这里插入图片描述

For a fish, the archer fish is known to shoot down bugs from low hanging plants by spitting water at them.

2.1 调试准备

通常,在为调试而编译时,我们会(在尽量不影响程序行为的情况下)关掉编译器的优化选项(-O), 并打开调试选项(-g)。另外,-Wall选项打开所有 warning,也可以发现许多问题,避免一些不必要的 bug:

$ gcc -g -Wall program.c -o program 

-g选项的作用是在可执行文件中加入源代码的信息,比如可执行文件中第几条机器指令对应源代码的第几行,但并不是把整个源文件嵌入到可执行文件中,所以在调试时必须保证gdb能找到源文件。

2.2 gdb调试命令

1.gdb启动、传参、和退出

# 1.启动, shell命令
gdb 可执行程序
# 2.给程序传递参数
(gdb) set args 10 9
(gdb) show args
Argument list to give program being debugged when it is started is "10 9".
# 3.退出gdb
(gdb) quit //q == quit

2.查看代码

1.当前文件

# 1.从默认位置显示代码
(gdb) l
(gdb) list
# 2.从指定的行显示
(gdb) l 行号
# 3.从指定的函数开始显示
(gdb) l 函数名

2.非当前文件

# 1.指定文件的指定的行
(gdb) l 文件名:行号
# 2.指定文件的指定的函数
(gdb) l 文件名:函数名

3.设置显示的行数

# 1.查看默认显示的行数
# list == listsize
(gdb) show list
Number of source lines gdb will list by default is 10.
(gdb) show listsize
Number of source lines gdb will list by default is 10.
# 2.更改显示的函数
(gdb) set list 行数
(gdb) set listsize 行数

3.断点操作

1.设置断点

break == b
# 在当前文件中设置
(gdb) b 行号
(gdb) b 函数名
# 在非当前文件中设置
(gdb) b 文件名:行号
(gdb) b 文件名:函数名

# 1.在当前文件设置断点
(gdb) b 行号
Breakpoint 1 at 0x400c23: file test.cpp, line 13.
# b 函数名
(gdb) b main
Breakpoint 2 at 0x400bb8: file test.cpp, line 10.

# 2.在非当前文件设置断点
# b 文件名:行号
(gdb) b insert.cpp:15
Breakpoint 3 at 0x400991: file insert.cpp, line 15
# b 文件名:函数名
(gdb) b insert.cpp:insertionSort
Breakpoint 4 at 0x400951: file insert.cpp, line 7.

2.查看断点

# i == info
# 查看设置的断点信息
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400c23 in main() at test.cpp:13
2       breakpoint     keep y   0x0000000000400bb8 in main() at test.cpp:10
3       breakpoint     keep y   0x0000000000400991 in insertionSort(int*, int) at insert.cpp:15
4       breakpoint     keep y   0x0000000000400951 in insertionSort(int*, int) at insert.cpp:7

3.删除断点

# d = del = delete
# d 断点的编号Num
(gdb) d 4

4.设置断点无效

# dis == disable
# dis 断点编号
(gdb) dis 1
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x0000000000400c23 in main() at test.cpp:13
2       breakpoint     keep y   0x0000000000400bb8 in main() at test.cpp:10
3       breakpoint     keep y   0x0000000000400991 in insertionSort(int*, int) at insert.cpp:15

5.无效断点生效

# ena == enable
# ena 断点的编号
(gdb) ena 2
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep n   0x0000000000400c23 in main() at test.cpp:13
2       breakpoint     keep y   0x0000000000400bb8 in main() at test.cpp:10
3       breakpoint     keep y   0x0000000000400991 in insertionSort(int*, int) at insert.cpp:15

6.设置条件断点

# 一般用在循环的位置
# b 行号 if 变量==某个值
(gdb) b 18 if i==6    
Breakpoint 5 at 0x400c52: file test.cpp, line 18.

4.调试命令

1.运行gdb程序

# 通过gdb工具将应用程序运行起来

# 1. start, 程序只运行一行
# 通过start, 只运一行, 就停止了
(gdb) start

# 2. run == r, 程序一直运行, 直到遇到断点停止
# 通过run, 一直运行, 直到遇到了断点
(gdb) run

2.从当前断点的位置继续运行, 停到下一个断点的位置

# 程序停到了断点的位置, 继续运行, 运行到下一个断点停止
# c = continue
(gdb) c

3.向下单步调试

1.进入函数体
# s == step向下执行一行, 遇到函数, 进入函数体内部
(gdb) s
1.跳出函数体
# 要跳出的函数中不能有断点, 如果有要删除或者设置为无效的断点
(gdb) finish
2.不进入函数体
# next == n 向下执行一行, 遇到函数, 不进入函数体内部(直接把函数执行完)
(gdb) n

4.打印变量

1.打印变量
# 1.p == print 变量名
(gdb) print i
$1 = 6

# 2.进制转换: p/x p/o 
# p/x -> 16进制  p/o -> 八进制  p/d -> 十进制
(gdb) print/x i
$1 = 0x6
2.打印变量类型
# ptype 变量名

(gdb) ptype array
type = int [12]

(gdb) ptype i
type = int
3.设置变量等于某一个值
# 一般在循环中用的比较多
# set var 变量名=变量值
(gdb) set var i=9
4.变量自动显示
# 1.自动打印指定变量的值
# 语法: display 变量名
(gdb) display j

# 2.查看追踪的变量
# 语法: i display
i == info
(gdb) i display
Auto-display expressions now in effect:
Num Enb Expression
1:   y  i
2:   y  j

# 3.取消自动追踪变量不打印变量值
# 语法: undisplay 变量编号(通过 i display 查看的)
(gdb) undisplay 1

5.跳出循环

# until
# 要跳出的循环代码块中不能有断点, 如果有要删除或者设置为无效的断点
# 执行完循环体的最后一行 == 循环的开始位置 才能跳出

猜你喜欢

转载自blog.csdn.net/liangwenhao1108/article/details/107436587
今日推荐