GDB深入研究

一、GDB代码调试

(一)GDB调试实例

在终端中编译一个示例C语言小程序,保存到文件 gdb-sample.c 中,用GCC编译之

#include <stdio.h>

int nGlobalVar = ;

int tempFunction(int a, int b)
{
printf("tempFunction is called, a = %d, b = %d /n", a, b);
return (a + b);
} int main()
{
int n;
n = ;
n++;
n--; nGlobalVar += ;
nGlobalVar -= ; printf("n = %d, nGlobalVar = %d /n", n, nGlobalVar); n = tempFunction(, );
printf("n = %d", n); return ;
}

在上面的命令行中,使用 -o 参数指定了编译生成的可执行文件名为 gdb-sample,使用参数 -g 表示将源代码信息编译到可执行文件中。如果不使用参数 -g,会给后面的GDB调试造成不便。

下面输入“gdb”命令启动GDB,将首先显示GDB说明:

下面使用“file”命令载入被调试程序 gdb-sample(这里的 gdb-sample 即前面 GCC 编译输出的可执行文件)

上图中最后一行“(gdb) ”为GDB内部命令引导符,等待用户输入GDB命令。

上图倒数第二行提示已经加载成功。

下面使用“r”命令执行(Run)被调试文件,因为尚未设置任何断点,将直接执行到程序结束

之后使用“b”命令在 main 函数开头设置一个断点(Breakpoint)

之后一行提示已经成功设置断点,并给出了该断点信息:在源文件 gdb-sample.c 第14行处设置断点;这是本程序的第一个断点(序号为1);断点处的代码地址为 0x8048418。向上看源代码,第14行中的代码为“n = 1”,恰好是 main 函数中的第一个可执行语句(因为前面的“int n;”为变量定义语句,并非可执行语句)。

之后, 再次使用“r”命令执行(Run)被调试程序:

程序中断在gdb-sample.c第14行处,即main函数是第一个可执行语句处。 上面最后一行信息为:下一条将要执行的源代码为“n = 1;”,它是源代码文件gdb-sample.c中的第14行。

下面使用“s”命令(Step)执行下一行代码(即第14行“n = 1;”):

上面的信息表示已经执行完“n = 1;”,并显示下一条要执行的代码为第15行的“n++;”。

既然已经执行了“n = 1;”,即给变量 n 赋值为 1,那我们用“p”命令(Print)看一下变量 n 的值是不是 1 :

果然是 1。($1表示这是第一次使用“p”命令——再次执行“p n”将显示“$2 = 1”。)

下面我们分别在第21行打印处、tempFunction 函数开头各设置一个断点(分别使用命令“b 21”“b tempFunction”):

使用“c”命令继续(Continue)执行被调试程序,程序将中断在第二个断点(21行),此时全局变量 nGlobalVar 的值应该是 88;再一次执行“c”命令,程序将中断于第三个断点(7行,tempFunction 函数开头处),此时tempFunction 函数的两个参数 a、b 的值应分别是 1 和 2:

再一次执行“c”命令(Continue),因为后面再也没有其它断点,程序将一直执行到结束:

(二)GDB常用命令

 命令  解释  示例
file <文件名> 加载被调试的可执行程序文件。
因为一般都在被调试程序所在目录下执行GDB,因而文本名不需要带路径。
(gdb) file gdb-sample
r Run的简写,运行被调试的程序。
如果此前没有下过断点,则执行完整个程序;如果有断点,则程序暂停在第一个可用断点处。
(gdb) r
c Continue的简写,继续执行被调试程序,直至下一个断点或程序结束。 (gdb) c
b <行号>
b <函数名称>
b *<函数名称>
b *<代码地址>

d [编号]

b: Breakpoint的简写,设置断点。两可以使用“行号”“函数名称”“执行地址”等方式指定断点位置。
其中在函数名称前面加“*”符号表示将断点设置在“由编译器生成的prolog代码处”。如果不了解汇编,可以不予理会此用法。

d: Delete breakpoint的简写,删除指定编号的某个断点,或删除所有断点。断点编号从1开始递增。

(gdb) b 8
(gdb) b main
(gdb) b *main
(gdb) b *0x804835c

(gdb) d

s, n s: 执行一行源程序代码,如果此行代码中有函数调用,则进入该函数;
n: 执行一行源程序代码,此行代码中的函数调用也一并执行。

s 相当于其它调试器中的“Step Into (单步跟踪进入)”;
n 相当于其它调试器中的“Step Over (单步跟踪)”。

这两个命令必须在有源代码调试信息的情况下才可以使用(GCC编译时使用“-g”参数)。

(gdb) s
(gdb) n
si, ni si命令类似于s命令,ni命令类似于n命令。所不同的是,这两个命令(si/ni)所针对的是汇编指令,而s/n针对的是源代码。 (gdb) si
(gdb) ni
p <变量名称> Print的简写,显示指定变量(临时变量或全局变量)的值。 (gdb) p i
(gdb) p nGlobalVar
display ...

undisplay <编号>

display,设置程序中断后欲显示的数据及其格式。
例如,如果希望每次程序中断后可以看到即将被执行的下一条汇编指令,可以使用命令
“display /i $pc”
其中 $pc 代表当前汇编指令,/i 表示以十六进行显示。当需要关心汇编代码时,此命令相当有用。

undispaly,取消先前的display设置,编号从1开始递增。

(gdb) display /i $pc

(gdb) undisplay 1

i Info的简写,用于显示各类信息,详情请查阅“help i”。 (gdb) i r
q Quit的简写,退出GDB调试环境。 (gdb) q
help [命令名称] GDB帮助命令,提供对GDB名种命令的解释说明。
如果指定了“命令名称”参数,则显示该命令的详细说明;如果没有指定参数,则分类显示所有GDB命令,供用户进一步浏览和查询。
(gdb) help display

 

二、CGDB代码调试

cgdb可以看作gdb的界面增强版,cgdb主要功能是在调试时进行代码的同步显示,这增加了调试的方便性,提高了调试效率。其他功能则与gdb一样,可使用其常用命令。所以这里只做简单介绍,常用命令等参见gdb。

主要功能介绍:

  1. 相比GDB,增加了语法加亮的代码窗口,显示在GDB窗口的上部,随GDB的调试位置代码同步显示。

  2. 断点设置可视化 。

  3. 在代码窗口中可使用GDB常用命令 。

  4. 在代码窗口可进行代码查找,支持正则表达式 。

界面及使用说明

  1. 代码窗口

    调试时同步显示被调试程序源代码,自动标记出程序运行到的位置。当焦点在代码窗口时,可以浏览代码、查找代码以及执行命令 ,操作方式同vi 。常用命令如下:

     i : 焦点切换到GDB窗口 。
    o :打开文件选择框,可选择要显示的代码文件 。
    空格 :设置/取消断点 。
    k:向上移动
    j:向下移动
    /:查找
  2. 状态条窗口

    同vi的状态条,一般显示当前打开的源文件名,当代码窗口进入命令状态时,显示输入的命令等信息

  3. GDB窗口

    CGDB的操作界面,同GDB ,按ESC键则焦点切换到代码窗口 。

    启动&退出——启动:cgdb;退出:在代码窗口或GDB窗口,执行quit命令 。

代码实现:

  1. “(gdb)”表示GDB已经启动,等待我们输入命令。此时程序并未开始运行,输入“run”开始运行程序。这种方式在GDB内部运行程序:

  2. List n,m表示显示n到m行的代码

  3. 设置断点,break n,用step单步执行(这里break 21,结果首先打印出 “hello!”,再次s,打印出“Who are you ?”):

三、汇编代码调试

汇编级的调试或跟踪,需要用到display命令“display /i $pc”,如上表所示,

“display /i $pc”
其中 $pc 代表当前汇编指令,/i 表示以十六进行显示。当需要关心汇编代码时,此命令相当有用。
undispaly,取消先前的display设置,编号从1开始递增。

看到了汇编代码,“n = 1;”对应的汇编代码是“movl $0x1,0x1c(%esp)”。

并且以后程序每次中断都将显示下一条汇编指定(“si”命令用于执行一条汇编代码——区别于“s”执行一行C代码)

接下来我们试一下命令“b *<函数名称>”。 为了更简明,有必要先删除目前所有断点(使用“d”命令——Delete breakpoint)

当被询问是否删除所有断点时,输入“y”并按回车键即可。

下面使用命令“b *main”在 main 函数的 prolog 代码处设置断点(prolog、epilog,分别表示编译器在每个函数的开头和结尾自行插入的代码):

此时可以使用“i r”命令显示寄存器中的当前值———“i r”即“Infomation Register”,

也可以输入“i r 寄存器名”显示任意一个指定的寄存器值:

最后输入命令“q”,退出(Quit)GDB调试环境

四、DDD代码调试

(一)DDD简介

DDD,全称是Data Display Debugger,对于Linux系统中的编程人员来说,它就是windows系统下面的visual studio ,功能强大,是Linux世界中少数有图形界面的程序调试工具。DDD是命令行调试器的图形前端,除了一般的程序调试功能以外,还具有交互式图形数据显示的功能。它在嵌入式应用开发中也十分出色。DDD最初源于1990年Andreas Zeller编写的VSL结构化语言,后来经过一些程序员的努力,演化成今天的模样。DDD的功能非常强大,可以调试用C\C++、Ada、 Fortran、Pascal、Modula-2和Modula-3编写的程序;可以超文本方式浏览源代码;能够进行断点设置、回溯调试和历史纪录编辑;具有程序在终端运行的仿真窗口,并在远程主机上进行调试的能力;图形数据显示功能(Graphical Data Display)是创建该调试器的初衷之一,能够显示各种数据结构之间的关系,并将数据结构以图形化形式显示;具有GDB/DBX/XDB的命令行界面,包括完全的文本编辑、历史纪录、搜寻引擎。

(二)DDD调试过程

首先,我们制作一个程序文档,作为我们后面调试的对象。

打开终端命令行窗口,输入命令vi testddd.c,建立testddd.c文件:

在testddd.c文件中输入一些C语言的程序数据,DDD工具可以调试很多种程序设置基于的代码,本次调试以C语言作为说明对象。

把testddd.c文件编译成可以执行的文件testddd,命令:gcc -g -o testddd testddd.c,注意一定要带-g参数,否则生成的可执行文件中没有必要的调试信息,最终使用DDD工具不能调试。

运行DDD调试工具,直接输入命令ddd就可以打开DDD工具。

DDD工具打开后如下图所示,上面较大空白部分为代码区,和工具区,分割线下面是调试生成信息区。

点击菜单栏上的“文件”----->“打开程序”,准备打开我们上面准备的testddd.c文件

在打开程序框中,定位到我们要调试的程序的目录下,在Files列表下选择我们要调试 信息,之后点击左下方的打开按钮。

调试程序打开后,在代码区可以看到我们的代码,右边的一些按钮是我们调试要用的工具。

在代码区点鼠标右键,会弹出如图所示的菜单:

我们可以给程序设置断点等,点击工具区里面的Run按钮,可以执行程序,在下面的调试信息区可以看到程序的执行结果。

如上图所示:在鼠标右键点击的地方设置了断点,在下方调试信息生成区显示了程序运行的输入信息。

PS:也可以在Terminal中输入ddd 文件名来直接打开ddd调试该文件的界面:

在怀疑程序哪个变量为可疑变量时,可以在控制台输入如下命令

或者在主窗口原程序中点击某个变量如sum选中该变量,右击后选择display sum 选项就会看到该变量的值在主窗口的上方。 接着往下单步运行,多次点击工具栏中的“Step”按钮,观察变量sum的结果。

如果问题出在count上。这时点击命令工具栏上的“Kill”按钮将程序断掉,把初始化sum的那一句改正确。重新运行之后,发现结果正确,调试过程完毕。

(三)常用命令简介

run 执行程序

step 单步调试

kill 杀死正在运行的程序

interrupt 退出此次调试回到原始状态

DDD的数据显示功能非常强大。

对于固定大小的数组,用鼠标选中数组名,点击plot按钮即可画出图形。

对于变长数组,可以使用graph plot数组名[起始索引] @ 数组大小的命令来显示。

对于复杂的数据结构,DDD也可以用图形方式解析: DDD有一个detect aliases的选项,可以智能的判别数据是否会被重复显示。这种方式通过内存地址的检测来实现的。

五、段错误

  1. 定义:段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址等等情况。
  2. 段错误产生的原因

    (1) 访问不存在的内存地址

    (2) 访问系统保护的内存地址

    (3) 访问只读的内存地址

    (4) 栈溢出

下面以原因一访问不存在的内存地址为例,进行实践。

(一)使用gcc和gdb(对于简单代码)

首先,编写一段代码,访问不存在内存地址。编译后进入CGDB,运行程序(我这里使用CGDB,可以看到源代码,更加方便。):

从输出中可以看出,程序收到SIGSEGV信号,触发段错误,并提示0x080483c4、调用main报的错,在Derro.c中23行。并且在代码窗口第23行被标记出来。

适用场景

  1. 仅当能确定程序一定会发生段错误的情况下使用。

  2. 当程序的源码可以获得的情况下,使用-g参数编译程序。

  3. 一般用于测试阶段,生产环境下gdb会有副作用:使程序运行减慢,运行不够稳定,等等。

  4. 即使在测试阶段,如果程序过于复杂,gdb也不能处理。

(二)使用core文件和gdb

提到段错误会触发SIGSEGV信号,通过man 7 signal,可以看到SIGSEGV默认的handler会打印段错误出错信息,并产生core文件,由此我们可以借助于程序异常退出时生成的core文件中的调试信息,使用gdb工具来调试程序中的段错误。

查看core文件发现不存在:

查看系统core文件的大小限制,发现为0,这样不会自动生成core文件。把大小设置为1000。运行程序后再次查看可看到存在core文件:

加载core文件,使用gdb工具进行调试。从输出中可以看出同样的段错误信息:

六、多进程与多线程

(一)多进程

1、进程的基本概念

进程定义了一个计算的基本单元,可以认为是一个程序的一次运行。它是一个动态实体,是独立的任务。它拥有独立的地址空间、执行堆栈、文件描述符等。 每个进程拥有独立的地址空间,进程间正常情况下,互不影响,一个进程的崩溃不会造成其他进程的崩溃。 当进程间共享某一资源时,需注意两个问题:同步问题和通信问题。

2、创建进程

父进程通过调用fork函数来创建一个新的运行子进程。fork函数定义如下:

#include <sys/types.h>
#include <unistd.h> pid_t fork(void);

fork函数只被调用一次,但是会返回两次:父进程返回子进程的PID,子进程返回0.如果失败返回-1。

fork后,子进程和父进程继续执行fork()函数后的指令。子进程是父进程的副本。子进程拥有父进程的数据空间、堆栈的副本。但父、子进程并不共享这些存储空间部分。如果代码段是只读的,则父子进程共享代码段。如果父子进程同时对同一文件描述字操作,而又没有任何形式的同步,则会出现混乱的状况; 父进程中调用fork之前打开的所有描述字在函数fork返回之后子进程会得到一个副本。fork后,父子进程均需要将自己不使用的描述字关闭,有两方面的原因:(1)以免出现不同步的情况;(2)最后能正常关闭描述字

在BSD3.0中开始出现,主要为了解决fork昂贵的开销。它是完全共享的创建,新老进程共享同样的资源,完全没有拷贝。 两者的基本区别在于当使用vfork()创建新进程时,父进程将被暂时阻塞,而子进程则可以借用父进程的地址空间。这个奇特状态将持续直到子进程退出或调用execve()函数,至此父进程才继续执行。

3、终止进程

进程的终止存在两个可能: 父进程先于子进程终止(init进程领养) 子进程先于主进程终止 对于后者,系统内核为子进程保留一定的状态信息:进程ID、终止状态、CPU时间等;当父进程调用wait或waitpid函数时,获取这些信息; 当子进程正常或异常终止时,系统内核向其父进程发送SIGCHLD信号;缺省情况下,父进程忽略该信号,或者提供一个该信号发生时即被调用的函数。

#include <stdlib.h>
void exit(int status);

本函数终止调用进程。关闭所有子进程打开的描述符,向父进程发送SIGCHLD信号,并返回状态。

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *stat_loc);

返回:终止子进程的ID-成功;-1-出错;statloc存储子进程的终止状态(一个整数);

如果没有终止的子进程,但是有一个或多个正在执行的子进程,则该函数将堵塞,直到有一个子进程终止或者wait被信号中断时,wait返回。 当调用该系统调用时,如果有一个子进程已经终止,则该系统调用立即返回,并释放子进程所有资源。

pidt waitpid(pidt pid, int *statloc, int options);

返回:终止子进程的ID-成功;-1-出错;statloc存储子进程的终止状态;

当pid=-1,option=0时,该函数等同于wait,否则由参数pid和option共同决定函数行为,其中pid参数意义如下:

-:要求知道任何一个子进程的返回状态(等待第一个终止的子进程);
>:要求知道进程号为pid的子进程的状态;
<-: wait for any child process whose process group ID is equal to the absolute value of pid.

Options最常用的选项是WNOHANG,它通知内核在没有已终止进程时不要堵塞。

调用wait或waitpid函数时,正常情况下,可能会有以下几种情况:

阻塞(如果其所有子进程都还在运行);
获得子进程的终止状态并立即返回(如果一个子进程已终止,正等待父进程存取其终止状态);
出错立即返回(如果它没有任何子进程)

4、调试进程

一般情况下,父进程fork一个子进程,gdb只会继续调试父进程而不会管子进程的运行。如果想跟踪子进程进行调试,可以使用set follow-fork-mode mode来设置fork跟随模式。

set follow-fork-mode 所带的mode参数可以是以下的一种:

    parent

        gdb只跟踪父进程,不跟踪子进程,这是默认的模式。

    child

        gdb在子进程产生以后只跟踪子进程,放弃对父进程的跟踪。

进入gdb以后,我们可以使用show follow-fork-mode来查看目前的跟踪模式。

可以看到目前使用的模式是parent。

然而,有时,我们想同时调试父进程和子进程,以上的方法就不能满足了。Linux提供了set detach-on-fork mode命令来供我们使用。其使用的mode可以是以下的一种:

    on

        只调试父进程或子进程的其中一个(根据follow-fork-mode来决定),这是默认的模式。

    off

        父子进程都在gdb的控制之下,其中一个进程正常调试(根据follow-fork-mode来决定)

另一个进程会被设置为暂停状态。

同样,show detach-on-fork显示了目前是的detach-on-fork模式,如上图所示。

以上是调试fork产生子进程的情况,但是如果子进程使用exec系统函数而装载了新程序执行,我们就使用set follow-exec-mode mode提供的模式来跟踪这个exec装载的程序。mode可以是以下的一种:

 new 当发生exec的时候,如果这个选项是new,则新建一个inferior给执行起来的子进程,而父进程的inferior仍然保留,当前保留的inferior的程序状态是没有执行。

    same 当发生exec的时候,如果这个选项是same(默认值),因为父进程已经退出,所以自动在执行exec的inferior上控制子进程。 

(二)多线程

  1. 线程:运行在单一进程上下文中的逻辑流,由内核进行调度,共享同一进程的虚拟地址空间。

基于线程的并发编程

线程由内核自动调度,每个线程都有它自己的线程上下文(thread context),包括一个惟一的整数线程ID(Thread ID,TID),栈,栈指针,程序计数器,通用目的寄存器和条件码。每个线程和其他线程一起共享进程上下文的剩余部分,包括整个用户的虚拟地址空间,它是由只读文本(代码),读/写数据,堆以及所有的共享库代码和数据区域组成的,还有,线程也共享同样的打开文件的集合。

线程不像进程那样,不是按照严格的父子层次来组织的。和一个进程相关的线程组成一个对等线程池,独立于其他线程创建的线程。进程中第一个运行的线程称为主线程。对等(线程)池概念的主要影响是,一个线程可以杀死它的任何对等线程,或者等待它的任意对等线程终止;进一步来说,每个对等线程都能读写相同的共享数据。

线程是可执行代码的可分派单元。这个名称来源于“执行的线索”的概念。在基于线程的多任务的环境中,所有进程有至少一个线程,但是它们可以具有多个任务。这意味着单个程序可以并发执行两个或者多个任务。

简而言之,线程就是把一个进程分为很多片,每一片都可以是一个独立的流程。这已经明显不同于多进程了,进程是一个拷贝的流程,而线程只是把一条河流截成很多条小溪。它没有拷贝这些额外的开销,但是仅仅是现存的一条河流,就被多线程技术几乎无开销地转成很多条小流程,它的伟大就在于它少之又少的系统开销。

linux提供的多线程的系统调用:
  1. 函数pthread_create用来创建一个线程,它的原型为:

    extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,void *(*__start_routine) (void *), void *__arg));

    第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。

  2. 函数pthread_join用来等待一个线程的结束。函数原型为:

    2extern int pthread_join __P ((pthread_t __th, void **__thread_return));

    第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。

  3. 一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。它的函数原型为:

    extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));

    唯一的参数是函数的返回代码,只要pthread_ join中的第二个参数thread_ return不是NULL,这个值将被传递给 thread_return。

    最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH。

Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork。

下面代码示例:

主线程做自己的事情,生成2个子线程,task1为分离,任其自生自灭,而task2还是继续送外卖,需要等待返回。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h> void* task1(void*);
void* task2(void*); void usr();
int p1,p2; int main()
{
usr();
getchar();
return 1;
} void usr()
{
pthread_t pid1, pid2;
pthread_attr_t attr;
void *p;
int ret=0;
pthread_attr_init(&attr); //初始化线程属性结构
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置attr结构为分离
pthread_create(&pid1, &attr, task1, NULL); //创建线程,返回线程号给pid1,线程属性设置为attr的属性,线程函数入口为task1,参数为NULL
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&pid2, &attr, task2, NULL);
//前台工作 ret=pthread_join(pid2, &p); //等待pid2返回,返回值赋给p
printf("after pthread2:ret=%d,p=%d/n", ret,(int)p); } void* task1(void *arg1)
{
printf("task1/n");
//艰苦而无法预料的工作,设置为分离线程,任其自生自灭
pthread_exit( (void *)1); } void* task2(void *arg2)
{
int i=0;
printf("thread2 begin./n");
//继续送外卖的工作
pthread_exit((void *)2);
}
编译运行:

多次运行发现结果并不相同,这是不同的线程抢占CPU的结果。

七、心得体会

本次GDB深入研究实践项目是我报名的第一个小组实践项目,刚开始抱着gdb调试会不会很难,相关的内容自己研究会不会学不懂的心态,不过当我和队友开始动手实践时,我才觉得The fear of imagining is far greater than doing. 我们是分工合作,我主要负责实践GDB、DDD、汇编代码调试以及多进程部分,其余部分我向队友学习。我们之前在课上已经讲解并实践过GDB调试以及汇编代码调试的相关内容,我参照以前的学习资料与经验,并查询了网络上的一些资料,很顺利的完成GDB代码调试等内容,这次实践让我觉得曾经学过的部分在脑中有了进一步加深的认识,之前不曾学到的相关知识也因为基础在较快地理解了。多进程涉及到书上第八章的知识,我刚刚学习完第八章,有了一些理解,所以这次并没有很晦涩难懂,还是较快的理解了。DDD调试对我来说是一个陌生的知识,网上的资料也比较少,我看了老师推荐的书《软件调试的艺术》关于DDD的相关内容,结合自己的理解,完成了代码调试的部分,DDD就是比GDB更深入,有图形界面的,更清晰明了的调试工具,基本操作与命令都与GDB差不多,它更具特色之处在于可视化显示数据结构(单个结构体、二叉树、链表等)和绘制数据集等。这些功能与操作我在一篇使用DDD调试linux软件的学术论文找到了答案,深感网络真的对我们来说用处很大。在之前的学习生活中,由于接触计算机也比较少,并没有觉得网络在学术方面的用处,上大学以来,通过娄老师鼓励我们自主学习,翻转课堂的教学方式,我渐渐掌握了这种高效的学习方法,将老师课上讲的与课下自己学的结合起来。这次实践与队友合作完成也让我体会了小组合作的重要性,我明白“你有一种思想,我有一种思想,我们交换便有了两种思想。”的道理。这次实践让我收获很多,感悟很多,我会继续应用这种学习方法,继续努力在实践项目中巩固、探索这门课的相关知识。

GDB深入研究——20135308芦畅的更多相关文章

  1. GDB深入研究

    GDB深入研究 一.GDB代码调试 (一)GDB调试实例 在终端中编译一个示例C语言小程序,保存到文件 gdb-sample.c 中,用GCC编译之 #include <stdio.h> ...

  2. 2014-2015-2 《Java程序设计》课程学生博客列表

    20135101 曹钰晶 20135103 王海宁 20135104 刘 帅 20135105 王雪铖 20135109 高艺桐 20135111 李光豫 20135114 王朝宪 20135116 ...

  3. Linux内核分析——第一周学习笔记20135308

    第一周 计算机是如何工作的 第一节 存储程序计算机工作模型 1.冯·诺依曼结构模型:冯·诺依曼结构也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构.程序指令存储地址和数据存储 ...

  4. 信息安全系统设计基础实验一:Linux开发环境的配置和使用

    北京电子科技学院(BESTI) 实验报告 课程:信息安全系统设计基础    班级:1353 姓名:芦畅 傅冬菁 学号:20135308 20135311 成绩:       指导教师:娄家鹏      ...

  5. UNIX下的LD_PRELOAD环境变量

    UNIX下的LD_PRELOAD环境变量 也许这个话题并不新鲜,因为LD_PRELOAD所产生的问题由来已久.不过,在这里,我还是想讨论一下这个环境变量.因为这个环境变量所带来的安全问题非常严重,值得 ...

  6. Java实验报告五:Java网络编程及安全

    Java实验报告五:Java网络编程及安全                                                                               ...

  7. GDB的深入研究

    GDB的深入研究 一.GDB代码调试 (一)GDB调试实例 在终端中编译一个示例C语言小程序,保存为文件 gdblianxi.c 中,用GCC编译. 在上面的命令行中,使用-o参数指定了编译生成的可执 ...

  8. 调试多线程 & 查死锁的bug & gcore命令 & gdb对多线程的调试 & gcore & pstack & 调试常用命令

    gdb thread apply all bt 如果你发现有那么几个栈停在 pthread_wait 或者类似调用上,大致就可以得出结论:就是它们几个儿女情长,耽误了整个进程. 注意gdb的版本要高于 ...

  9. GDB配置与.gdbinit的编写

    GDB配置与.gdbinit的编写 当 GDB(即 GNU Project Debugger)启动时,它在当前用户的主目录中寻找一个名为 .gdbinit 的文件:如果该文件存在,则 GDB 就执行该 ...

随机推荐

  1. Oracle定义varchar2()类型存储汉字的长度问题

    varchar2最大是4000字节,那么就看你的oracle字符集:(select userenv('language') from dual;)如果字符集是16位编码的,ZHS16GBK,那么每个字 ...

  2. Effective Java 20 Prefer class hierarchies to tagged classes

    Disadvantage of tagged classes 1. Verbose (each instance has unnecessary irrelevant fields). 2. Erro ...

  3. Effective Java 69 Prefer concurrency utilities to wait and notify

    Principle Use the higher-level concurrency utilities instead of wait and notify for easiness. Use Co ...

  4. 【windows环境下】RabbitMq的安装和监控插件安装

    RabbitMq的安装: RabbitMQ是基于Erlang的,所以必须先配置Erlang环境. 下载Erlang,地址:http://www.erlang.org/download/otp_win3 ...

  5. 最完整PHP.INI中文版

    ;;;;;;;;;;;;;;;;;;; 关于php.ini ;;;;;;;;;;;;;;;;;;;; 这个文件必须命名为'php.ini'并放置在httpd.conf中PHPINIDir指令指定的目录 ...

  6. 超链接的那些事(三): 属性target

    a标签的属性之一 target 1. 定义     规定在何处打开链接文档. 如果a标签中有target属性,浏览器将会载入和显示用这个标签的 href 属性命名的.名称与这个目标吻合的框架或者窗口中 ...

  7. uniq

    -c, --count 在每行前加上表示相应行目出现次数的前缀编号-d, --repeated 只输出重复的行-D, --all-repeated[=delimit-method 显示所有重复的行de ...

  8. 在Windows2012下配置Mercurial

    所需的安装文件: xampp-win32-1.8.3-4-VC11-installer.exe python-2.7.7.amd64.msi tortoisehg-3.0.1-x64.msi merc ...

  9. JAVA基础之对象的初始化

    本文主要记录JAVA中对象的初始化过程,包括实例变量的初始化和类变量的初始化以及 final 关键字对初始化的影响.另外,还讨论了由于继承原因,探讨了引用变量的编译时类型和运行时类型 一,实例变量的初 ...

  10. python如何控制数据库?

    http://www.w3cschool.cc/python/python-mysql.html 通过利用MySQLdb可以操作数据库 实例: 以下实例链接Mysql的TESTDB数据库: # enc ...