0%

less - 少就是多

Linux系统如果希望查阅文件,有三个命令,是在命令行里面,如果GUI界面,请自行绕过,选择太多了。

  • cat 入门级的
  • more 文件内容一屏幕装不下的时候使用的
  • less 可以简单地认为是more的升级版 , 首推

我首推less命令的原因是该命令可以往回卷动浏览已经看过的部分,但是more是不可以的。或者可以认为less是查看模式下的vim

首先看看为什么用less命令吧。

If the file is longer than the size of Terminal window then it will be not easy to read or view all the content of the file easily. But there is a tweak, you can use less with cat command. It will give user an ability to scroll forward and backward through the content of the files using PgUp and PgDn keys or Up and Down Arrow keys on the keyboard.

如题,在文件内容足够多的时候,屏幕足够不大的时候,就会出现上面描述的问题,这就出现了less命令。

Linux系统可以说把少就是多这个哲学用到了极致,恰如小巧优美的C语言,不该有的功能坚决不给你提供,应该有的也不给你提供,哈哈,比如内存的管理,程序员就是神,你就是神。

命令简介

less - opposite of more # 我觉得这是废话

我嘞个去,什么鬼?这是什么意思,我也知道少的反义词是多,大的反义词是小。

别急,那就看看more的含义吧,不会是 opposite of less 吧。OMG

more - file perusal filter for crt viewing

什么意思,淡定,听我说,在Linux系统中有三种命令可以用来查阅全部的文件,分别是catmoreless命令,关于more的解释主要针对在上古年代的计算机,你不理解crt也没有关系,毕竟现在已经是Retina的年代了。

一起看看下面的实例吧。

命令格式

1
less [参数] 文件

与其他命令类似,直接跟上文件名即可。

接下来依旧使用/etc/services来进行示例。

-m 显示类似more命令的百分比

这个是more命令比较好用的一个功能,可以显示目前浏览的百分比。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ less -m /etc/services

auditd 48/udp # Digital Audit Daemon
la-maint 51/tcp # IMP Logical Address Maintenance
la-maint 51/udp # IMP Logical Address Maintenance
xns-time 52/tcp # XNS Time Protocol
xns-time 52/udp # XNS Time Protocol
xns-ch 54/tcp # XNS Clearinghouse
xns-ch 54/udp # XNS Clearinghouse
isi-gl 55/tcp # ISI Graphics Language
isi-gl 55/udp # ISI Graphics Language
xns-auth 56/tcp # XNS Authentication
xns-auth 56/udp # XNS Authentication
xns-mail 58/tcp # XNS Mail
xns-mail 58/udp # XNS Mail
ni-mail 61/tcp # NI MAIL
ni-mail 61/udp # NI MAIL
5%

此时可以在左下角看到,有个百分比。

-N 显示行号

使用-N可以实现cat中-n的效果,显示行号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 $ less -N /etc/services
1 # /etc/services:
2 # $Id: services,v 1.55 2013/04/14 ovasik Exp $
3 #
4 # Network services, Internet style
5 # IANA services version: last updated 2013-04-10
6 #
7 # Note that it is presently the policy of IANA to assign a single well-known
8 # port number for both TCP and UDP; hence, most entries here have two entries
9 # even if the protocol doesn't support UDP operations.
10 # Updated from RFC 1700, ``Assigned Numbers'' (October 1994). Not all ports
11 # are included, only the more common ones.
12 #
13 # The latest IANA port assignments can be gotten from
14 # http://www.iana.org/assignments/port-numbers
15 # The Well Known Ports are those from 0 through 1023.
16 # The Registered Ports are those from 1024 through 49151

搜索字符串

在less中,可以比较容易的搜索字符串,比如可以:

  • /字符串:向下搜索“字符串”的功能
  • ?字符串:向上搜索“字符串”的功能
  • n:重复前一个搜索(与 / 或 ? 有关)
  • N:反向重复前一个搜索(与 / 或 ? 有关)

其实这些功能或者热键与vim相同。

在用less打开文件后,可以直接输入/number来搜索nubmer这个字符串,回车后可以看到该字符串高亮显示,这个也是优于more的一点;同样?number可以反向搜索number字符串。

可以通过-i选项来忽略搜索时的大小写

设置缓冲区的大小

可以通过-b <缓冲区大小> 设置缓冲区的大小,这个一般用于文件很大、巨大、不是一般大的时候,此时你的内容可能不足以承载打开整个文件,比如4G的内存,而你却要打开10G的文件,此时可以通过该选项来设置,默认单位为KB,比如

1
$ less -b 1024 filename

即打开1024KB的文件缓冲

编辑less浏览的文件

要编辑一个正在用less浏览的文件,可以按下v。你就可以用变量$EDITOR所指定的编辑器来编辑了: 按下v键来编辑文件,退出编辑器后,你可以继续用less浏览了。

移动

我比较喜欢less的原因是对于该命令的很多操作都是与vim相同,而我是一个重度vimer,so 推荐less

说几个比较简单的移动:

  • j 向下移动
  • k 向上移动
  • g 移动到第一行
  • G 移动到最后一行
  • b 向后翻一页
  • d 向后翻半页
  • u 向前滚动半页
  • y 向前滚动一行
  • 空格键 滚动一行
  • 回车键 滚动一页

识别文件类型的file

file鉴别大神

file的官方解释为:

1
file - determine file type

也就是说可以识别文件类型的意思,也可用来辨别一些文件的编码格式。它是通过查看文件的头部信息来获取文件类型,而不是像Windows通过扩展名来确定文件类型的,所以加不加后缀真的无所谓,谁会爱上谁,说起Windows吗,啥也不说了。

下面看几个比较使用的例子。

实例一 :默认

file后直接跟文件,得到如下所示信息

1
2
3
4
$ file book.pdf
delete.pdf: PDF document, version 1.3
➜ file book
delete: PDF document, version 1.3

可以看出加不加后缀都是没有关系的。

实例二:不显示名称

1
2
$ file -b book.pdf
PDF document, version 1.3

加上-b参数,是brief的含义,将只显示文件辨识结果,不显示文件名称了,这个其实对于很多文件而言,不是很友好。

实例三:输出易懂信息

1
2
$ file -i  delete.pdf
delete.pdf: application/pdf; charset=binary

加上-i参数,是mime类型的含义,我也不懂是啥意思,但是我能刚方便地读懂我想知道的文件类型的含义。这就够了,不是吗,毕竟我们是来是用file命令的。

实例四:查看文件中的文件名的文件信息

1
2
3
4
$ cat hello.txt
sunset.jpg
$ file -f hello.txt
sunset.jpg: JPEG image data, JFIF standard 1.01

这个咋听着这么拗口,其实很简单,其实并不难,加上·-f·参数,是file-from类型的含义,到底是几个意思呢,也就是你想查看文件的类型信息的文件名在一个文件里面,从这个文件里面读取文件的信息。

实例五:好看的鸡肋

1
2
$ file -F " === " sunset.jpg
sunset.jpg === JPEG image data, JFIF standard 1.01

这个功能说实话,没搞明白有什么作用,默认的:感觉挺好用的,当然这个应该属于定制型的,就是默认替换掉一些提示信息。

实例六:查看软链接的文件信息

1
2
3
4
$ file a.jpg
a.jpg: symbolic link to `sunset.jpg'
$ file -L a.jpg
a.jpg: JPEG image data, JFIF standard 1.01

默认情况下,如果没有-L参数,只能得到这个文件是软链接的信息,如果加上这个参数,就能看到源文件的文件信息,这个功能还是很赞的。

识别文件类型的file

file的官方解释为:

1
file - determine file type

也就是说可以识别文件类型的意思,也可用来辨别一些文件的编码格式。它是通过查看文件的头部信息来获取文件类型,而不是像Windows通过扩展名来确定文件类型的,所以加不加后缀真的无所谓,谁会爱上谁,说起Windows吗,啥也不说了。

下面看几个比较使用的例子。

实例一 :默认

file后直接跟文件,得到如下所示信息

1
2
3
4
file book.pdf
delete.pdf: PDF document, version 1.3
file book
delete: PDF document, version 1.3

可以看出加不加后缀都是没有关系的。

实例二:不显示名称

1
2
file -b book.pdf
PDF document, version 1.3

加上-b参数,是brief的含义,将只显示文件辨识结果,不显示文件名称了,这个其实对于很多文件而言,不是很友好。

实例三:输出易懂信息

1
2
file -i  delete.pdf
delete.pdf: application/pdf; charset=binary

加上-i参数,是mime类型的含义,我也不懂是啥意思,但是我能刚方便地读懂我想知道的文件类型的含义。这就够了,不是吗,毕竟我们是来是用file命令的。

实例四:查看文件中的文件名的文件信息

1
2
3
4
➜  cat hello.txt
sunset.jpg
➜ file -f hello.txt
sunset.jpg: JPEG image data, JFIF standard 1.01

这个咋听着这么拗口,其实很简单,其实并不难,加上·-f·参数,是file-from类型的含义,到底是几个意思呢,也就是你想查看文件的类型信息的文件名在一个文件里面,从这个文件里面读取文件的信息。

实例五:好看的鸡肋

1
2
➜  file -F " === " sunset.jpg
sunset.jpg === JPEG image data, JFIF standard 1.01

这个功能说实话,没搞明白有什么作用,默认的:感觉挺好用的,当然这个应该属于定制型的,就是默认替换掉一些提示信息。

实例六:查看软链接的文件信息

1
2
3
4
➜  file a.jpg
a.jpg: symbolic link to `sunset.jpg'
➜ file -L a.jpg
a.jpg: JPEG image data, JFIF standard 1.01

默认情况下,如果没有-L参数,只能得到这个文件是软链接的信息,如果加上这个参数,就能看到源文件的文件信息,这个功能还是很赞的。

创建、修改、更新文件的touch

linuxtcpdump命令不常用,一般用来修改文件时间戳(可更改文件或目录的日期时间,包括存取时间和更改时间)或者新建一个不存在的文件。

命令格式

1
$ touch [选项]... 文件...

其中选项如下所示:

  • -a 只更改存取时间。
  • -c 或–no-create  不建立任何文档。
  • -d  使用指定的日期时间,而非现在的时间。
  • -m 只更改变动时间。
  • -r  把指定文件或目录的日期时间,统统设成和参考文件或目录的日期时间相同。
  • -t  使用指定的日期时间,而非现在的时间。

创建不存在的文件

1
2
3
4
5
6
$ ls

$ touch a.txt b.txt

$ ls
a.txt b.txt

更新b.txt 的时间和 a.txt 时间戳相同

1
2
# 将文件b.txt的时间戳与a.txt保持一致
$ touch -r a.txt b.txt

设定文件的时间戳

1
2
3
4
5
# 设定filename的时间戳为2012年05月06日13时14分15秒
$ touch -t 201205061314.15 filename

$ ls -l
-rw-rw-r--. 1 user user 0 May 6 2012 filename

其中-t time 使用指定的时间值 time 作为指定文件相应时间戳记的新值.此处的 time的形式如下为: [[CC]YY]MMDDhhmm[.SS]

其中秒及年可以省略。

创建、修改、更新文件的touch

linuxtouch命令不常用,一般用来修改文件时间戳(可更改文件或目录的日期时间,包括存取时间和更改时间)或者新建一个不存在的文件。

命令格式

1
$ touch [选项]... 文件...

其中选项如下所示:

  • -a 只更改存取时间。
  • -c 或–no-create  不建立任何文档。
  • -d  使用指定的日期时间,而非现在的时间。
  • -m 只更改变动时间。
  • -r  把指定文件或目录的日期时间,统统设成和参考文件或目录的日期时间相同。
  • -t  使用指定的日期时间,而非现在的时间。

创建不存在的文件

1
2
3
4
5
6
$ ls

$ touch a.txt b.txt

$ ls
a.txt b.txt

更新b.txt 的时间和 a.txt 时间戳相同

1
2
# 将文件b.txt的时间戳与a.txt保持一致
$ touch -r a.txt b.txt

设定文件的时间戳

1
2
3
4
5
# 设定filename的时间戳为2012年05月06日13时14分15秒
$ touch -t 201205061314.15 filename

$ ls -l
-rw-rw-r--. 1 user user 0 May 6 2012 filename

其中-t time 使用指定的时间值 time 作为指定文件相应时间戳记的新值.此处的 time的形式如下为: [[CC]YY]MMDDhhmm[.SS]

其中秒及年可以省略。

创建、修改、更新文件的touch

linuxtouch命令不常用,一般用来修改文件时间戳(可更改文件或目录的日期时间,包括存取时间和更改时间)或者新建一个不存在的文件。

命令格式

1
$ touch [选项]... 文件...

其中选项如下所示:

  • -a 只更改存取时间。
  • -c 或–no-create  不建立任何文档。
  • -d  使用指定的日期时间,而非现在的时间。
  • -m 只更改变动时间。
  • -r  把指定文件或目录的日期时间,统统设成和参考文件或目录的日期时间相同。
  • -t  使用指定的日期时间,而非现在的时间。

创建不存在的文件

1
2
3
4
5
6
$ ls

$ touch a.txt b.txt

$ ls
a.txt b.txt

更新b.txt 的时间和 a.txt 时间戳相同

1
2
# 将文件b.txt的时间戳与a.txt保持一致
$ touch -r a.txt b.txt

设定文件的时间戳

1
2
3
4
5
# 设定filename的时间戳为2012年05月06日13时14分15秒
$ touch -t 201205061314.15 filename

$ ls -l
-rw-rw-r--. 1 user user 0 May 6 2012 filename

其中-t time 使用指定的时间值 time 作为指定文件相应时间戳记的新值.此处的 time的形式如下为: [[CC]YY]MMDDhhmm[.SS]

其中秒及年可以省略。

其他工具

精通调试代码并不是说学会使用GDB这样的调试器就行了—这只是开端。

为了增强调试技能,初学者程序员最好学会其中的集中调试工具,了解那种工具适合于调试那种程序错误,并识别发生程序错误时使用其中那个工具可以节省时间和精力。

充分利用文本编辑器

最好的调试方法是一开始就不要有编程错误,而简单地充分利用一种支持编程的编辑器就可以达到这个效果。因为:

  • 精通强大的编辑器可以缩短编写代码所需的时间,具有自动缩排、单词补充和全文代码查询等特殊功能的编辑器对程序员是非常有利的;
  • 优秀的编辑器确实可以帮到编码者在编写代码时捕获某些类型的程序错误。

Mark:Makefile中可以使用patsubst关键字来进行文本查找和替换命令。

从vim中调用make非常方便,不需要退出编辑器,直接:make 即可,同时vim可以捕获编译器发出的所有消息,编辑器理解GCC输出内容的语法,知道何时发生编辑器警告或错误。发生错误时可以直接定位到第一个错误,然后使用:cnext或者:cprevious来显示下一个或上一个错误或者警告。

在Vim中可以使用K来查询man页面中的函数。

充分利用编译器

如果说编辑器是对抗程序错误的第一个武器,那么编译器就是第二个武器了,所有编译器都有能力扫描代码并查找常见错误,但是通常必须通过调用适当的选项来启用这种错误检查。

对于GCC而言,如果不适用-Wall,就几乎没有必要使用GCC了,所以任何编译最好都加上-Wall选项。

C语言中的错误报告

C语言中的错误报告是使用名为errno的老式机制完成的。系统与库调用的失败通常是由于设置了名为errno的全局定义整数变量,在大多数GNU/Linux系统上,error是在/usr/include/errno.h上声明的,因此,只要包含了这个头文件,就不必在源代码中声明extern int errno了。

当一个系统或库调用失败时,它将errno设置为一个指示失败类型的值。检查errno的值,并采取适当的动作可以判断错误类型。比如可以使用函数perror或者strerror。

errno可以通过任何库函数或系统调用设置,无论它是成功还是失败。因为即便成功的函数调用能够设置errno,让人不能依赖errno告诉你是否发生了错误。因此使用errno最安全的方式为:

  • 执行对库或者系统函数的调用;
  • 使用函数的返回值判断时候发生了某个错误;
  • 如果发生了某个错误,使用errno确定为什么发生这个错误。

库函数和系统调用的区别

库函数是更高级别的,完全在用户空间里运行,并未程序员提供了更方便的做实际工作的函数接口。

系统调用代表用户以内核模式工作,由操作系统本身的内核提供。

库函数printf看上去类似于一般输出函数,但是它实际上只是格式化你提供给字符串的数据,并用低级系统调用write编写字符串数据,然后将数据发送到一个与终端的标准输出关联的文件中。

更好地使用strace和ltrace

strace跟踪系统调用而ltrace跟踪库调用,这两个使用程序,有时比深度调试代码还快。

静态代码检查器:lint与其衍生

静态代码检查器:扫描代码的工具,不编译代码,仅仅警告错误、可能的错误和与严格C语言编码标准的差距。

衍生版本为splint:该软件的目标是帮助编写大部分有防御性、安全和尽可能少出错的程序,当然,改程序对组成有效代码的内容非常挑剔。

很多程序员将splint没有报告警告看做一种极大的荣誉。当出现这种情况是,代码被声明为无lint的。

调试动态分配的内存

动态分配的内存(Dynamically Allocated Memory, DAM)是程序用malloc和calloc这样的函数从堆中请求的内存。

查找DAM问题分麻烦,分为以下几类:

  • 没有释放动态分配的内存;—内存泄漏
  • 对malloc的调用失败;
  • 想DAM段之外的地址执行读写操作;—访问错误
  • 释放DAM段之后对DAM区域中的内存进行读写操作;—访问错误
  • 对动态内存的同一段调用两次free。—重复释放double free

Electric Fence

Bruce Perens在1988年写的,当EFence链接到代码中时,导致程序在发生下列情况之一时立即发生段错误并转出核心:

  • 在DAM边界之外执行读写操作;
  • 对已经释放的DAM执行读写操作;
  • 对没有指向malloc分配的DAM的指针执行free,包括重复释放的特殊情况。

比如,

1
$ gcc –g3 –Wall –std=c99 test.c –o test_with_efence outOfBound_with_efence  –lefence

and

1
$ gcc –g3 –Wall –std=c99 test.c –o test_with_efence outOfBound_without_efence  -lefence

可以对一个内存泄露但是没有编译失败的程序做测试。

用GNU C库工具调试DAM问题

  • 在调用任何与堆有关的函数钱调用函数mcheck;
  • 使用mtrace函数捕获内存泄露和重复释放;

程序崩溃处理

有人说C语言是低级语言,这有一部分原因是因为应用程序的内存管理大部分需要由程序员来实现。虽然这种方法非常有用,但是也给程序员添加了很多的麻烦。

也有人说,C语言是相对较小且容易学习的语言,然而,只有不考虑标准C语言库的典型实现时,C语言才比较小,这个库相当庞大,很多程序员认为C语言是易用语言,那是因为他们还没有遇到指针。

一般而言,程序错误会导致下面两件事情的发生:

  • 导致程序做一些程序员没有打算做的事情;
  • 导致程序崩溃

相信很多调试过程序的兄弟都碰到过段错误即segmentation fault,则合格主要原因是试图在未经允许的情况下访问一个内存单元。硬件会感知这件事并执行对操作系统的跳转。

堆区域

调用malloc函数分配的内存;

栈区域

用来动态分配数据的空间,函数调用的数据(包括参数、局部变量和返回地址)都存储在栈上。

查看程序在Linux上的精确内存布局情况

可以通过使用info proc mappings来详细查看该程序在Linux上的精确内存布局情况,例如:

此时我们还可以看到这个进程号为14455,所以我们还可以通过文件/proc/14455/maps来查看该信息。通过这些信息,我们有可能看到文本和数据区域,以及堆和栈。

分配页策略

操作系统不会将不完整的页分配给程序,例如,如果要运行的程序总共大约有10000字节,如果完全加载,会占用3个内存页(一个页占4096个字节),它不会仅占用2.5个页,因为页是虚拟内存系统能够操作的最小内存单元,这是调试时要着重了解的情况,这也导致了程序的一些错误内存访问不会触发段错误,换言之,在调试会话期间,没有引起段错误并不能直接说明代码是没有问题的。

页的角色细节

当程序执行时,它会连续访问程序中的各个区域,导致硬件按照以下几种情况所示处理页表:

  • 每次程序使用全局变量时,需要具有对数据区域的读写访问权限;
  • 每次程序访问局部变量时,程序会访问栈,需要对栈区域具有读写访问权限;
  • 每次程序进入或离开函数时,对该栈进行一次或多次访问,需要对栈区具有读写访问权限;
  • 每次程序访问通过调用malloc或者new创建的存储器时,都会发生堆访问,也需要读写访问权限;
  • 程序执行的每个机器指令是从文本区域取出的,因此需要具有读和执行文件;

信号

这里需要注意的是,进程抛出的信号,实际上没有任何内容发送给进程。所发生的事情只不过是操作系统将信号记录到进程表中,以便下次进程接收信号时得到CPU上的时间片,执行恰当的信号处理程序。

自定义信号的复杂性

使用GDB/DDD/Eclipse调试时,自定义信号处理程序可能会使程序变得复杂,无论是直接使用还是通过DDD GUI,每当发出任何信号时,GDB都会停止进程,所以,有可能意味着GDB会因为与调试无关的工作而频繁的停止,此时可以使用handle命令告诉GDB在某些信号发生时不要停止。

总线错误的原因

  • 访问不存在的物理地址;
  • 在很多架构上,要求访问32位量的机器指令要求字对齐,而导致视图在奇数号地址上访问具有4字节的数的指针错误可能引起总线错误。

总线错误是处理器层的异常,导致在Unix系统上发出SIGBUS信号,默认情况下,SIGBUS会导致转储内存并终止。

核心文件

有些信号表示让某个进程继续是不妥当的,甚至是不可能的,在这些情况中,默认动作是提前终止进程,并编写一个名为核心文件core file的文件,俗称转储核心。

核心文件包含程序崩溃时对程序状态的详细描述:栈的内容、CPU寄存器的内容、程序的静态分配变量的值。

我们可以通过file命令来查看文件的详细信息。

为什么需要核心文件

  • 只有在运行了一段长时间后才发生段错误,所以在调试器中无法重新创建该错误;
  • 程序的行为取决于随机的环境事件,因此再次运行程序可能不会再现段错误;
  • 当新手用户运行程序时发生的段错误,需要发送核心文件给开发人员。

重载功能

当GDB注意到重新编译了程序后,它会自动加载新的可执行文件,因此不需要退出和重启GDB。

调试设计的内容

  • 确认原则;
  • 使用核心文件进行崩溃进程的“死后”分析;
  • 纠正、编译并重新运行程序后,甚至不需要退出GDB;
  • Printf()风格调试的不足之处;
  • 利用你的智慧,这是无可替代的;
  • 如果你过去使用printf风格调试的,就会发现使用printf跟踪这些程序错误中的部分错误原来有多难,虽然在调试中使用printf诊断代码有一定的好处,但是作为一种通用目的的工具,它远远不足以用来跟踪实际代码中发生的大部分程序错误。

调试器不仅能够运行程序,还可以通知它暂停程序的运行,暂停以后,调试器提供了检查变量、跟踪执行路径的机会。

暂停机制

有3中方式可以通过GDB暂停程序的执行:

  • 断点:通知GDB在程序中的特定位置暂停执行;
  • 监视点:通知GDB当特定内存位置(或者设计一个或多个位置的表达式)的值发生变化时暂停执行;
  • 捕获点:通知GDB当特定事件发生时暂停执行;

在GDB中删除断点

  • delete breakpoint_list
  • delete
  • clear
  • clear function、clear filename:function、clear line_number、clear filename:line_number

在GDB中禁用断点

在调试会话期间,会遇到大量断点,对于经常重复的循环结构或函数,这种情况使得调试极不方便。如果要保留断点以便以后使用,暂时又不希望GDB停止执行,可以禁用它们,在以后需要时再启用。此时我们可以使用disable/enable breakpoint_list来禁用和启用断点。

DDD

  • 可以直接拖拽断点,很方便;

  • 还有一个优秀的功能Undo/Redo;

GDB中恢复执行的方法

  • 使用step和next单步调试程序;
  • 使用continue使GDB无条件地恢复程序的执行,知道它遇到另一个断点或者程序结束;
  • 用finish或until命令恢复。

next和step的区别

next执行函数,不会在其中暂停,然后在调用之后的第一条语句处暂停;

step在函数中的第一个语句处暂停;

使用continue恢复程序执行

continue与执行一行代码的step和next相反,这个命令使GDB恢复程序的执行,直到触发断点或者程序结束。

continue命令可以接受一个可选的整数参数n,这个数字要求GDB忽略下面n个断点。

使用finish恢复程序执行

命令finish指示GDB恢复执行,直到恰好在当前栈帧完成之后位置,这意味着如果你在一个不是main的函数中,finish命令会导致GDB恢复执行,直到恰好在函数返回之后为止,例如:

如果在一个递归函数中,finish只会将你带到递归的上一层,这是因为每次调用都被看做在它自己权限内的函数调用,因为每个函数都有自己的栈帧,如果要在递归层次较高时完全退出递归函数,那么更适合使用临时断点及continue,或者使用until命令。

使用until恢复程序执行

命令until执行程序,直到到达当前循环体外的下一行源代码。

设置条件断点的方法

break break-args if (condition)

监视点

监视点是一种特殊类型的断点,它类似于正常断点,是要求GDB暂停程序执行的指令。区别在于监视点是没有“住在”某一行源码中,取而代之的是,监视点是指示GDB每当某个表达式改变了值就暂停执行的指令。

预备知识

处于TUI模式的GDB

加上-tui选项可以在调用GDB时使用TUI模式运行,或者当处于非TUI模式时在GDB中使用Ctrl+A+X组合键就可以在TUI模式和非TUI模式中跳转。

在GUI模式中,GDB窗口划分为两个子窗口:

  • 用于输入GDB命令的窗口
  • 用于查看源码的窗口

通过使用上下方向键可以在TUI模式中移动到代码的其他部分。如果没有处于TUI模式中,就可以使用箭头键来浏览以前的GBD命令,从而修改或重复执行这些命令。

在TUI模式中,箭头键用于滚动源代码子窗口,可以使用Ctrl+P和Ctrl+N组合键来浏览以前的GDB命令。

常用的命令

  • backtrace:显示程序的当前位置和表示如何到达当前位置的栈跟踪(同义词:where)。
  • breakpoint:在程序中设置一个断点。
  • cd:改变当前工作目录。
  • clear:删除刚才停止处的断点。
  • commands:命中断点时,列出将要执行的命令。
  • continue:从断点处开始继续执行。
  • delete:删除一个断点或监测点,也可与其他命令一起使用。
  • display:程序停止时显示变量和表达式。
  • down:下移栈帧,使得另一个函数成为当前函数。
  • frame:选择下一条continue命令的帧。
  • info:显示与该程序有关的各种信息。
  • info break:显示当前断点清单,包括到达断点处的次数等。
  • info files:显示被调试文件的详细信息。
  • info func:显示所有的函数名称。
  • info local:显示当前函数中的局部变量信息。
  • info prog:显示被调试程序的执行状态。
  • info var:显示所有的全局和静态变量名称。
  • jump:在源程序中的另一点开始运行。
  • kill:异常终止在gdb 控制下运行的程序。
  • list:列出相应于正在执行的程序的源文件内容。
  • next:执行下一个源程序行,从而执行其整体中的一个函数。
  • print:显示变量或表达式的值。
  • pwd:显示当前工作目录。
  • pype:显示一个数据结构(如一个结构或C++类)的内容。
  • quit:退出gdb。
  • reverse-search:在源文件中反向搜索正规表达式。
  • run:执行该程序。
  • search:在源文件中搜索正规表达式。
  • set variable:给变量赋值。
  • signal:将一个信号发送到正在运行的进程。
  • step:执行下一个源程序行,必要时进入下一个函数。
  • undisplay display:命令的反命令,不要显示表达式。
  • until:结束当前循环。
  • up:上移栈帧,使另一个函数成为当前函数。
  • watch:在程序中设置一个监测点(即数据断点)。
  • whatis:显示变量或函数类型。

CGDB

CGDB是一个很友好的GDB界面,当然是相对于GDB的TUI界面,CGDB也是GDB的前端,虽然CGDB类似于基于终端的TUI的概念,但其在色彩方面特别有吸引力,而且可以浏览源代码子窗口,并直接在子窗口中设置断点,并且CGDB处理屏幕刷新的能力似乎也比GDB的TUI强。

CGDB的基本命令与约定:

  • 按下ESC键可以从基本命令转到源代码窗口,按下i键返回;
  • 当光标在源代码窗口中时,可以使用箭头键或者类似vi的操作在源码中随意移动,j下k上/查找;
  • 要执行的下一行用箭头标记;
  • 为了在通过光标突出显示的当前代码行上设置断点,只要按下空格键即可;
  • 断点行的行号用红色突出显示

编译:gcc -g -Wall -o gdb_app test.c

Attention:其中的-g选项可以让编译器将符号表(即对应程序的变量和代码行的内存地址列表)保存在生成的可执行文件。这是一个绝对必要的步骤,这样才能在调试会话过程中引用源代码中的变量名和行号。