软件调试的艺术笔记:GDB

10/19 10:46
阅读数 16

很久之前,在https://blog.csdn.net/fengbingchun/article/details/41413381中简单整理过gdb中常用的一些命令,不齐全,这里按照《软件调试的艺术》一书中关于gdb的介绍再做次整理。《软件调试的艺术》于2009年由人民邮电出版社出版。

1. 预备知识

GDB:Unix程序员最常用的调试工具,是由Richard Stallman开发的GNU项目调试器(GNU Project Debugger)。

DDD:Data Display Debugger,数据显示调试器,GDB的GUI(图形用户界面)前端:用户通过GUI发出命令,GUI将这些命令传递给GDB。

Eclipse:IDE(集成开发环境)。

GDB的命令允许在不产生歧义的情况下使用缩写

从版本6.1以来,GDB已经以名为TUI(Terminal User Interface, 终端用户界面)的模式提供了基于文本交互和图形用户交互之间的折中方法。在这一模式中,GDB将终端屏幕划分为类似于DDD的源文本窗口和控制台的多个子窗口:可以在类似于源文本窗口的子窗口中跟踪程序执行的进展过程,同时在类似于控制台的子窗口中发出GDB命令。也可以使用另一个程序CGDB,该程序也提供了类似的功能。

为了以TUI模式运行GDB,可以在调用GDB时在命令行上指定-tui选型,如gdb -tui main,或者处于非TUI模式时在GDB中使用Ctrl+X+A组合键。如果当前处于TUI模式,后一种命令方式就会使你离开TUI模式。TUI模式中,GDB窗口划分为两个子窗口:一个用于输入GDB命令,而另一个用于查看源代码。需要注意的是,在一些情况下TUI可能不能按照用户所需要的方式运作。

1.5 主要调试器操作

单步调试源代码:

(1).断点:调试工具会在指定断点处暂停程序的执行,在GDB中是通过break命令及其行号完成的。

(2).单步调试:GDB的next命令让GDB执行下一行,然后暂停。step命令的作用与此类似,只是在函数调用时step命令会进入函数,而next导致程序执行的暂停出现在下次调用函数时。

(3).恢复操作:在GDB中,continue命令通知调试器恢复执行并继续,直到遇到断点为止。

(4).临时断点:在GDB中,tbreak命令与break相似,但是这一命令设置的断点的有效期限只到首次到达指定行时为止。GDB中还有创建特殊类型的一次性断点的命令until和finish。

检查变量:当调试器暂停了程序的执行后,可以执行一些命令来显示程序变量的值。这些变量可以是局部变量、全局变量、数组的元素和C语言的struct、C++类中的成员变量等。如果发现某个变量有一个出乎意料的值,那往往是找出某个程序错误的位置和性质的重要线索。最基本的变量显示类型是仅仅输出当前值。在GDB中使用print命令输出当前值,如查看变量j的当前值执行”(gdb) print j”。

设置监视点以应对变量值的改变:监视点结合了断点和变量检查的概念。最基本形式的监视点通知调试器,每当指定变量的值发生变化时都暂停程序的执行。例如,在程序执行期间,假设要在变量z改变值时查看程序的状态,在GDB中,可以执行如下命令:”(gdb) watch z”,当运行程序时,每当z的值发生变化,GDB都会暂停执行。更好的方法是,可以基于条件表达式来设置监视点。例如,假设要查找程序执行期间z的值大于28的第一个位置,在GDB中,输入”(gdb) watch (z>28)”。监视点对局部变量的用途一般没有对作用域更宽的变量的用途大,因为一旦变量超出作用域(即当在其中定义变量的函数结束时),在局部变量上设置的监视点就会被取消。

上下移动调用栈:在函数调用期间,与调用关联的运行时信息存储在称为栈帧(stack frame)的内存区域中。帧中包含函数的局部变量的值、其形参,以及调用该函数的位置的记录。每次发生函数调用时,都会创建一个新帧,并将其推到一个系统维护的栈上;栈最上方的帧表示当前正在执行的函数,当函数退出时,这个帧被弹出栈,并且被释放。在GDB中可用用如下命令查看以前的帧:“(gdb) frame 1”。当执行GDB的frame命令时,当前正在执行的函数的帧被编号为0,其父帧(即该函数的调用者的栈帧)被编号为1,父帧的父帧被编号为2,以此类推。GDB的up命令将你带到调用栈中的下一个父帧(例如,从帧0到帧1),down则引向相反反向。这样的操作非常有用,因为根据以前的一部分栈帧中的局部变量的值,可能发现一些关于引起程序错误的代码的线索。遍历调用栈不会修改执行路径,但是它确实允许查看帧的祖先帧,因此可以检查通向当前帧的函数调用的局部变量的值。GDB的backtrace命令会显示整个栈,即当前存在的所有帧的集合

在GDB中,可以通过help命令访问文档。例如,”(gdb) help breakpoints”,将显示关于断点的文档。不带参数的GDB命令help提供了一个可用来作为help的参数的命令类别的菜单。

1.7 初涉调试会话:

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

与接受行号(或函数名)的break命令不同,condition接受断点号。总是可以用命令info break来查询要查找的断点的编号。用break if可以将break和condition命令组合成一个步骤,如下所示”(gdb) break 30 if num_y==1”,然后用run命令再次运行程序。如果要重用老的命令行参数,就不必再次指定命令行参数,可以简单地输入run

我们在重新编译程序之前仍然不必退出GDB。这样做提供了很大的方便,第一,不需要重新指出命令行参数,只要键入run重新运行程序即可。其次,GDB保留了你设置的断点,因此不需要再次键入它。同样,在调试会话期间不要退出再重启文本编辑器,这件事也会分心而且浪费时间。只要将文本编辑器放在一个窗口中,GDB放在另一个窗口中,用第三个窗口调试程序即可。

当运行程序试图访问不允许访问的内存时发生了段错误。原因通常是由于数组索引超出了边界,或者采用了错误的指针值。段错误也可能由每月显式地包含指针或数组变量的内存引用产生。

1.8 启动文件的使用:

在重新编译代码时,最好不要退出GDB。这样,你的断点和建立的其它各种动作都会保留。要是退出GDB,就不得不再次重复键入所有这些内容为了不丢失它们,可以将断点和设置的其它命令放在一个GDB启动文件中,然后每次启动GDB时会自动加载它们

GDB的启动文件默认名为.gdbinit。可以将一个文件放在主目录中用于一般用途,另一个文件放在包含该项目特有用途的特定项目的目录中。GDB在加载可执行文件之前会读取主目录中的启动文件。在调用GDB时可以指定启动文件,例如”$ gdb -command=z x”,表示要在可执行文件x上运行GDB,首先要从文件z中读取命令。

2. 停下来环顾程序

2.1 暂停机制:有3种方式可以通知GDB暂停程序的执行:

(1).断点:通知GDB在程序中的特定位置暂停执行。

(2).监视点:通知GDB当特定内存位置(或者涉及一个或多个位置的表达式)的值发生变化时暂停执行。

(3).捕获点:通知GDB当特定事件发生时暂停执行。

2.2 断点概述:在程序中的特定”位置”设置断点,当到达那一点时,调试器会暂停程序的执行(在GDB这样的基于文本的调试器的情况下,会出现命令行提示符)。GDB中关于”位置”的含义是非常灵活的,它可以指各种源代码行、代码地址、源代码文件中的行号或者函数的入口等。

GDB命令行提示符上面显式的语句信息是将要执行的代码行,而不是GDB最后执行的代码行,如下图所示,接下来将要执行的是Messy_Test.cpp的第6行。GDB的工作针对的是机器语言指令,而不是源代码行,一行代码可能对应于数行机器语言。GDB之所以可以使用源代码,是因为可执行文件中包含了额外的信息。

2.3 跟踪断点:程序员创建的每个断点(包括断点、监视点和捕获点)都被标识为从1开始的唯一整数标识符。这个标识符用来执行该断点上的各种操作。调试器还包括一种列出所有断点及其属性的方式。当创建断点时,GDB会告知你分配给该断点的编号。如果忘记了分配给哪个断点的编号是什么,可以使用”(gdb) info breakpoints”命令来提示。通过使用delete命令以及断点标识符,可以删除断点、监视点及捕获点。

2.4 设置断点:GDB中有许多指定断点的方式,下面是一些最常见的方法:

(1).break funciton:在函数function的入口(第一行可执行代码)处设置断点。

(2).break line_number:在当前活动源代码文件的line_number处设置断点

(3).break filename:line_number:在源代码文件filename的line_number处设置断点。如果filename不在当前工作目录中,则可以给出相对路径名或者完全路径名来帮助GDB查找该文件

(4).break filename:function:在文件filename中的函数function的入口处设置断点。重载函数或者使用同名静态函数的程序可能需要使用这种形式。

当设置一个断点时,该断点的有效性会持续到删除、禁用或退出GDB时。然而,临时断点是首次到达后就会被自动删除的断点。临时断点使用tbreak命令设置,它与break采用相同类型的参数GDB实际设置断点的位置可能与请求将断点放置的位置不同,当打开优化来编译程序时,这个问题可能变得更糟糕。GDB最终使用的是机器指令。在调试完成前不应当优化代码。

当GDB使用多个断点中断一行源代码时,它只会中断一次。换言之,当它到达该行代码时,如果恢复执行,会忽略恰好在同一行上的其它断点。事实上,GDB知道是哪个断点”触发”了程序停止执行。在具有多个断点的代码行上,触发中断的断点将是标识符编号最小的断点。

2.5 展开GDB示例:

启动调试会话时在main()中设置断点是非常普遍的。这一操作在该函数的第一行上设置断点:”(gdb) break main”。

在任何给定时间,GDB都有一个焦点,可以将它看作当前”活动”文件。这意味着除非对命令做了限定,否则都是在具有GDB的焦点的文件上执行命令。默认情况下,具有GDB的初始焦点的文件是包含main()函数的文件,但是当发生如下任一动作时,焦点会转移到不同的文件上:

(1).向不同的源文件应用list命令。

(2).进入位于不同的源代码文件中的代码。

(3).当在不同的源代码文件中执行代码时GDB遇到断点。

使用quit命令离开GDB。

2.6 断点的持久性:如果在修改和重新编译代码时没有退出GDB,那么在下次执行GDB的run命令时,GDB会感知到代码已修改,并自动重新加载新版本。然而要注意,断点是会”移动”的。

2.7 删除和禁用断点:这里提到的一切方法都同样适用于监视点。

GDB中有两个用来删除断点的命令:delete和clear。delete命令用来基于标识符删除断点。clear命令使用与创建断点相同的语法删除断点。

(1).delete breakpoint_list:删除断点使用数值标识符。断点可以是一个数字,比如delete 2删除第二个断点;也可以是数字列表,比如delete 2 4删除第二个和第四个断点。

(2).delete:删除所有断点。

(3).clear:清除GDB将执行的下一个指令处的断点。这种方法适用于要删除GDB已经到达的断点的情况

(4).clear function、clear filename:funciton、clear line_number和clear filename:line_number:这些命令根据位置清除断点,工作方式与对应的break命令相似。

每个断点都可以被启用或禁用。只有当GDB遇到启用的断点时,才会暂停程序的执行;它会忽略禁用的断点。默认情况下,断点的生命期从启用时开始。在调试会话期间,会遇到大量断点。对于经常重复的循环结构或函数,这种情况使得调试极不方便。如果要保留断点以便以后使用,暂时又不希望GDB停止执行,可以禁用它们,在以后需要时再启用。

使用disable breakpoint-list命令禁用断点,使用enable breakpoint-list命令启用断点,其中breakpoint-list是使用空格分隔的列表,其中有一个或多个断点标识符。不带任何参数地执行disable命令将禁用所有现有断点。类似地,不带参数地执行enable命令会启用所有现有断点。还有一个enable once命令”enable once breakpoint-list”,在断点下次引起GDB暂停执行后被禁用。这个命令与tbreak命令非常类似,但是当遇到断点时,它是禁用断点,而不是删除断点。

2.8 进一步介绍浏览断点属性:每个断点都有各种属性----行号、加在断点上的条件(如果有的话)、当前启用/禁用状态等。

创建的每个断点都被赋予了唯一的整数标识符。设置的第一个断点被赋予”1”,其后的每个断点都被赋予所赋的上一个标识符加1.每个断点也有一些控制调整其行为的属性。使用唯一标识符,可以分别调整各个断点的属性。可以使用”(gdb) info breakpoints”命令(简写为”i b”)来获得设置的所有断点的清单,以及它们的属性。info breakpoints的输出看上去或多或少类似于如下所示:

(1).标识符(Num):断点的唯一标识符。

(2).类型(Type):这个字段指出该断点是断点、监视点还是捕获点。

(3).部署(Disp):每个断点都有一个部署,指示断点下次引起GDB暂停程序的执行后该断点上会发生什么事情。可能的部署有3种:

a.保持(keep):下次到达断点后不改变断点。这是新建断点的默认部署。

b.删除(del):下次到达断点后删除该断点。使用tbreak命令创建的任何断点都是这样的断点。

c.禁用(dis):下次到达断点时会禁用该断点。这是使用enable once命令设置的断点。

(4).启用状态(Enb):这个字段说明断点当前是启用还是禁用的。

(5).地址(Address):这是内存中设置断点的位置。它主要用于汇编语言程序员,或者试图调试没有用扩充的符号表编译的可执行文件的人。

(6).位置(What):各个断点位于源代码中的特定行上。What字段显示了断点所在位置的行号和文件号。

2.9 恢复执行:方法有3类:

(1).使用step和next”单步”调试程序,仅执行代码的下一行然后再次暂停。一旦GDB在断点处停止,可以使用next(简写为n)和step(简写为s)命令来单步调试代码。当触发了断点,并且GDB暂停后,可以使用next和step来执行紧接着的下一行代码。当执行了这一行后,GDB会再次暂停,并给出一个命令行提示符。这两个命令的区别是它们如何处理函数调用:next执行函数,不会在其中暂停,然后在调用之后的第一条语句处暂停。step在函数中的第一个语句处暂停。next将函数调用看作一行代码,并在一个操作中执行整个函数,这称为单步越过(stepping over)函数。next和step命令都采用一个可选的数值参数,表示要使用next或step执行的额外行数。换言之,next 3与在一行中键入next三次(或者键入next一次,然后按下ENTER键两次)的作用相同。如果是在没有函数调用的那部分代码中,那么使用next还是step并没有关系,在这种情况下,两个命令是完全相同的

(2).由使用continue组成,使GDB无条件地恢复程序的执行,直到它遇到另一个断点或者程序结束。简写为c,与仅执行一行代码的step和next相反,这个命令使GDB恢复程序的执行,直到触发断点或者程序结束。continue命令可以接受一个可选的整数参数n,这个数字要求GDB忽略下面n个断点。

(3).涉及条件,用finish或until命令恢复。在这种情况下,GDB会恢复执行,程序继续运行直到遇到某个预先确定的条件(比如,到达函数的末尾),到达另一个断点,或者程序完成。

使用finish恢复程序执行:想返回到单步进入被调用函数之前GDB所在的调用函数,这时应该使用finish命令finish命令(简写为fin)指示GDB恢复执行,直到恰好在当前栈帧完成之后为止。也就是说,这意味着如果你在一个不是main的函数中,finish命令会导致GDB恢复执行,直到恰好在函数返回之后为止。finish的另一个常见用途是当不小心单步进入原本希望单步越过的函数时(换言之,当需要使用next时使用了step),在这种情况下,使用finish可以将你正好放回到使用next会位于的位置。

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

使用until恢复程序执行:finish命令在不进一步在函数中暂停(除了中间断点)的情况下完成当前函数的执行。类似地,until命令(简写为u)通常用来在不进一步在循环中暂停(除了循环中的中间断点)的情况下完成正在执行的循环,使用until会执行循环的其余部分,让GDB在循环后面的第一行代码处暂停。如果在循环的末尾,执行until命令导致回跳到循环顶部,这时只要再次执行until,就可以离开当前的循环。until命令也可以接受源代码中的位置作为参数,事实上,该命令接受的参数与break命令相同。

2.10 条件断点:这类似于监视点的工作方式,但是有一个重要区别。如果怀疑某个变量得到了一个伪值,那么条件断点相当适用于监视点。每当该变量的值发生变化时,监视点都会中断。条件断点只会在怀疑有问题的代码处当变量呈现该怀疑值时才中断。设置条件断点的语法为”break break-args if (condition)”。其中break-args是可以传递给break以指定断点位置的任何参数,condition是定义的布尔表达式。括着condition的圆括号是可选的。如”(gdb) break main if argc > 1”:说明了如果用户向程序中键入一些命令行参数,则在main()处中断。条件中断极其有用,尤其适用于索引变量的特定值出了问题的循环结构。条件中断也极其灵活,不仅可以测试相等或不相等的变量。在有效的C条件语句中几乎可以使用任何表达式。无论使用什么表达式,都需要具有布尔值,即真(非0)或假(0)

(1).相等、逻辑和不相等运算符(<、<=、==、!=、>、>=、&&、||等),如”(gdb) break 180 if string==nullptr && i < 0”

(2).按位和移位运算符(&、|、^、>>、<<等),如”(gdb) break test.c:34 if (x & y) == 1”

(3).算术运算符(+、-、*、/、%),如”(gdb) break myfunc if i % (j + 3) != 0”

(4).你自己的函数,只要它们被链接到程序中,如”(gdb) break test.c:myfunc if ! check_variable_sanity(i)”

(5).库函数,只要该库被链接到代码中,例如:”(gdb) break 44 if strlen(mystring) == 0”

由于优先级的次序规则在起作用,因此可能需要使用圆括号将一些结构括起来。此外,如果在GDB表达式中使用库函数,而该库不是用调试符号编译的(几乎肯定是这种情况),那么唯一能在断点条件中使用的返回值类型为int。换言之,如果没有调试信息,GDB会假设函数的返回值是int类型。当这种假设不正确时,函数的返回值会被曲解。

可以对正常断点设置条件以将它们转变为条件断点。例如,如果设置了断点3作为无条件断点,但是现在希望添加条件i==3,只要键入”(gdb) cond 3 i == 3”,如果以后要删除条件,但是保持该断点,只要键入”(gdb) cond 3”。

2.11 断点命令列表:使用”断点命令列表”可以让GDB在每次到达某个断点时自动执行一组命令,从而自动完成反复查看相同的变量这一过程。使用commands命令设置命令列表。逐条输入命令,然后键入end表示输入命令完毕,从那以后,每当GDB在这个断点处中断时,它都会执行你输入的任何命令。

可以使用GDB的define命令创建宏。可以将宏保存在.gdbinit文件中,以便将它用于其它程序。键入”(gdb) show user”可以得到所有宏的列表。

2.12 监视点:是一种特殊类型的断点,它类似于正常断点,是要求GDB暂停程序执行的指令。区别在于监视点没有”住在”某一行源代码中,取而代之的是,监视点是指示GDB每当某个表达式改变了值就暂停执行的命令,如”(gdb) watch (i | j > 12) && i > 24 && strlen(name) > 6”。可以将监视点看作被”附加”在表达式上,当表达式的值改变时,GDB会暂停程序的执行。

虽然管理监视点和断点的方式相同,但是两者之间有一个重要区别。断点与源代码中的一个位置关联。只要代码没有改变,就不存在某行代码”超出作用域”的风险。因为C语言有严格的作用域规则,所以只能监视存在且在作用域内的变量。一旦变量不再存在于调用栈的任何帧中(当包含局部变量的函数返回时),GDB会自动删除监视点。GDB只能监视单个线程中的变量

当变量var存在且在作用域中时,可以通过使用如下命令来设置监视点:”(gdb) watch var”,该命令会导致每当var改变值时GDB都中断。GDB实际上是在var的内存位置改变值时中断。监视点不仅仅限于监视变量,事实上,可以监视涉及变量的表达式,每当表达式修改值时,GDB都会中断。

GDB中的表达式可以包含:

(1).GDB方便变量(convenience variables)。

(2).程序中的任何在作用域内的变量。

(3).任何种类的字符串、数值或字符常量。

(4).预处理器宏(如果程序被编译为包括预处理器调试信息)。

(5).条件、函数调用、类型强制转换和所用语言定义的运算符。

3. 检查和设置变量

3.2 变量的高级检查和设置:

GDB的display命令(简写为disp)在执行中每次有暂停(由于有断点,使用next或step命令等)时就输出指定条目的值,只有当变量在作用域中时才生效。

在GDB中可以输出整个数组的值,如”(gdb) p x”或”(gdb) *pointer@number_of_elements”。

在GDB中,可以通过调用info locals命令得到当前栈帧中所有局部变量的值列表

在有些情况下,可能希望检查给定地址的内存,而不是通过变量的名称。GDB为这种目的提供了x命令。

print和display命令允许指定可选的格式,如”(gdb) p/x y”,会以十六进制格式显示变量,而不是十进制格式。其它常用的格式为c表示字符(character),s表示字符串(string),f表示浮点(floating-point)。

可以临时禁用某个显示项,例如”(gdb) dis disp 1”,临时禁用显示列表中的条目1.如果不知道条目号,可以通过info disp命令检查。要重新启用条目,使用enable,例如”(gdb) enable disp 1”。要完全删除显示的条目,使用undisplay,例如”(gdb) undisp 1”。

3.3 设置变量:”(gdb) set x = 12”,会将x的当前值改成12。可以通过GDB的set args命令设置程序的命令行参数,如”(gdb) set args 1 52 19 11”,并不会立即将argv[1]改成1,argv[2]改成52,等等,直到下次执行run命令时才会发生这些变化。GDB有用来检查当前函数参数的info args命令

3.4 GDB自己的变量:

(1).使用值历史:GDB的print命令的输出值被标为$1、$2等,这些值统称为值历史。在将来执行print命令时使用这样的值历史会比较方便,如”(gdb) p *$1”。

(2).方便变量(convenience variable):会根据C规则改变值。

4. 程序崩溃处理

4.2 核心文件:如果在程序运行期间创建了核心文件(core file,俗称转储核心),则可以打开调试器(比如GDB)上的该文件,然后开始常规GDB操作。

核心文件包含程序崩溃时对程序状态的详细描述:栈的内容(或者,如果程序是多线程的,则是各个线程的栈),CPU寄存器的内容(同样,如果程序是多线程的,则是每个线程上的一组寄存器值),程序的静态分配变量的值(全局与static变量),等等。

大多数现代shell都会在一开始就防止编写核心文件。在bash中,可以使用ulimit命令控制核心文件的创建。”$ ulimit -c n”,其中n是核心文件的最大大小,以千字节为单位。超过nKB的任何核心文件都不会被编写。如果没有指定n,shell就会显示核心文件上的当前限制。如果允许任意大小的核心文件,可以使用:”$ ulimit -c unlimited”。

如果程序的源代码不可用,或者可执行文件不是用增强的符号表编译的,甚或当我们没有计划调试可执行文件,核心文件可能都不会有太大的用处。

core file的使用示例可参考:https://blog.csdn.net/fengbingchun/article/details/97113451

4.3 扩展示例:

在调试会话期间,修改代码时永远不要退出GDB。这样就不必费时间来启动,可以保留我们的断点,等等。类似地,我们要保持文本编辑器打开。在调试时的两次编译之间留在同一个编辑器会话中,我们可以充分利用编辑器的”撤销”功能。例如,调试过程中的一个常见策略是临时删除部分代码,以便集中精力于你认为存在程序错误的余下部分。完成检查后,只要使用编辑器的撤销功能恢复被删除的代码行即可。因此,在屏幕上我们通常有一个GDB窗口,以及一个编辑器窗口。我们还打开了第三个窗口用于执行编译器命令,甚至最好是通过编辑器执行命令。

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

5. 多活动上下文中的调试

5.1 调试客户/服务器网络程序:socket

5.2 调试多线程代码:

查看指定线程的栈,如线程3,可执行”(gdb) thread 3”,再执行”(gdb) bt”。多线程程序的运行有一定的随机性。

下面是与线程相关的GDB命令用法汇总:

(1).info threads:给出关于当前所有线程的信息,带有”*”号表示当前线程;

(2).thread 3:改成线程3;

(3).break 88 thread 3:当线程3到达源代码行88时停止执行;

(4).break 88 thread 3 if x == y:当线程3到达源代码行88,并且变量x和y相等时停止执行。

5.3 调试并行应用程序:共享内存和消息传递、MPI

5.4 扩展示例:OpenMP

6. 特殊主题

6.1 根本无法编译或加载:

6.2 调试GUI程序:

7. 其它工具

8. 对其它语言使用GDB

GitHubhttps://github.com/fengbingchun/Messy_Test

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部