陈皓:用GDB调试程序css
GDB概述
————html
GDB是GNU开源组织发布的一个强大的UNIX下的程序调试工具。或许,各位比较喜欢那种图形界面方式的,像VC、BCB等IDE的调试,但若是你是在UNIX平台下作软件,你会发现GDB这个调试工具备比VC、BCB的图形化调试器更强大的功能。所谓“寸有所长,尺有所短”就是这个道理。node
通常来讲,GDB主要帮忙你完成下面四个方面的功能:mysql
一、启动你的程序,能够按照你的自定义的要求为所欲为的运行程序。
二、可以让被调试的程序在你所指定的调置的断点处停住。(断点能够是条件表达式)
三、当程序被停住时,能够检查此时你的程序中所发生的事。
四、动态的改变你程序的执行环境。linux
从上面看来,GDB和通常的调试工具没有什么两样,基本上也是完成这些功能,不过在细节上,你会发现GDB这个调试工具的强大,你们可能比较习惯了图形化的调试工具,但有时候,命令行的调试工具却有着图形化工具所不能完成的功能。让咱们一一看来。c++
一个调试示例
——————程序员
1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
21 }
22
23 printf("result[1-100] = %d /n", result );
24 printf("result[1-250] = %d /n", func(250) );
25 }redis
编译生成执行文件:(Linux下)
hchen/test> cc -g tst.c -o tst算法
使用GDB调试:sql
hchen/test> gdb tst <---------- 启动GDB
GNU gdb 5.1.1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-suse-linux"...
(gdb) l <-------------------- l命令至关于list,从第一行开始例出原码。
1 #include <stdio.h>
2
3 int func(int n)
4 {
5 int sum=0,i;
6 for(i=0; i<n; i++)
7 {
8 sum+=i;
9 }
10 return sum;
(gdb) <-------------------- 直接回车表示,重复上一次命令
11 }
12
13
14 main()
15 {
16 int i;
17 long result = 0;
18 for(i=1; i<=100; i++)
19 {
20 result += i;
(gdb) break 16 <-------------------- 设置断点,在源程序第16行处。
Breakpoint 1 at 0x8048496: file tst.c, line 16.
(gdb) break func <-------------------- 设置断点,在函数func()入口处。
Breakpoint 2 at 0x8048456: file tst.c, line 5.
(gdb) info break <-------------------- 查看断点信息。
Num Type Disp Enb Address What
1 breakpoint keep y 0x08048496 in main at tst.c:16
2 breakpoint keep y 0x08048456 in func at tst.c:5
(gdb) r <--------------------- 运行程序,run命令简写
Starting program: /home/hchen/test/tst
Breakpoint 1, main () at tst.c:17 <---------- 在断点处停住。
17 long result = 0;
(gdb) n <--------------------- 单条语句执行,next命令简写。
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) n
18 for(i=1; i<=100; i++)
(gdb) n
20 result += i;
(gdb) c <--------------------- 继续运行程序,continue命令简写。
Continuing.
result[1-100] = 5050 <----------程序输出。
Breakpoint 2, func (n=250) at tst.c:5
5 int sum=0,i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p i <------- 打印变量i的值,print命令简写。从这里能够看到,n是显示下一条要运行的语句,但还没运行
$1 = 134513808
(gdb) n
8 sum+=i;
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$2 = 1
(gdb) n
8 sum+=i;
(gdb) p i
$3 = 2
(gdb) n
6 for(i=1; i<=n; i++)
(gdb) p sum
$4 = 3
(gdb) bt <--------------------- 查看函数堆栈。backtrace 打印当前的函数调用栈的全部信息。
#0 func (n=250) at tst.c:5
#1 0x080484e4 in main () at tst.c:24
#2 0x400409ed in __libc_start_main () from /lib/libc.so.6
(gdb) finish <--------------------- 退出函数。
Run till exit from #0 func (n=250) at tst.c:5
0x080484e4 in main () at tst.c:24
24 printf("result[1-250] = %d /n", func(250) );
Value returned is $6 = 31375
(gdb) c <--------------------- 继续运行。
Continuing.
result[1-250] = 31375 <----------程序输出。
Program exited with code 027. <--------程序退出,调试结束。
(gdb) q <--------------------- 退出gdb。
hchen/test>
好了,有了以上的感性认识,仍是让咱们来系统地认识一下gdb吧。
使用GDB
————
通常来讲GDB主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,咱们必需要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数能够作到这一点。如:
> cc -g hello.c -o hello
> g++ -g hello.cpp -o hello
若是没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。当你用-g把调试信息加入以后,并成功编译目标代码之后,让咱们来看看如何用gdb来调试他。
启动GDB的方法有如下几种:
一、gdb <program>
program也就是你的执行文件,通常在固然目录下。
二、gdb <program> core
用gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件。
三、gdb <program> <PID>
若是你的程序是一个服务程序,那么你能够指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索获得。
GDB启动时,能够加上一些GDB的启动开关,详细的开关能够用gdb -help查看。我在下面只例举一些比较经常使用的参数:
-symbols <file>
-s <file>
从指定文件中读取符号表。
-se file
从指定文件中读取符号表信息,并把他用在可执行文件中。
-core <file>
-c <file>
调试时core dump的core文件。
-directory <directory>
-d <directory>
加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径。
转自:http://blog.csdn.net/haoel/article/details/2879
技巧汇总:
输出格式
通常来讲,GDB会根据变量的类型输出变量的值。但你也能够自定义GDB的输出的格
式。例如,你想输出一个整数的十六进制,或是二进制来查看这个整型变量的中的
位的状况。要作到这样,你可使用GDB的数据显示格式:
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。
(gdb) p i
$21 = 101
(gdb) p/a i
$22 = 0x65
(gdb) p/c i
$23 = 101 'e'
(gdb) p/f i
$24 = 1.41531145e-43
(gdb) p/x i
$25 = 0x65
(gdb) p/t i
$26 = 1100101
程序变量
查看文件中某变量的值:
file::variable
function::variable
能够经过这种形式指定你所想查看的变量,是哪一个文件中的或是哪一个函数中的。例如,查看文件f2.c中的全局变量x的值:
gdb) p 'f2.c'::x
查看数组的值
有时候,你须要查看一段连续的内存空间的值。好比数组的一段,或是动态分配的数据的大小。你可使用GDB的“@”操
做符,“@”的左边是第一个内存的地址的值,“@”的右边则你你想查看内存的长度。例如,你的程序中有这样的语句:
int *array = (int *) malloc (len * sizeof (int));
因而,在GDB调试过程当中,你能够以以下命令显示出这个动态数组的取值:
p *array@len
二维数组打印
p **array@len
若是是静态数组的话,能够直接用print数组名,就能够显示数组中全部数据的内容了。
http://www.cppblog.com/chaosuper85/archive/2009/08/04/92123.html
*启动gdb调试指定程序app:
$gdb app
这样就在启动gdb以后直接载入了app可执行程序,须要注意的是,载入的app程序必须在编译的时候有gdb调试选项,例如'gcc -g app app.c',注意,若是修改了程序的源代码,可是没有编译,那么在gdb中显示的会是改动后的源代码,可是运行的是改动前的程序,这样会致使跟踪错乱的。
*启动程序以后,再用gdb调试:
$gdb <program> <PID>
这里,<program>是程序的可执行文件名,<PID>是要调试程序的PID.若是你的程序是一个服务程序,那么你能够指定这个服务程序运行时的进程ID。gdb会自动attach上去,并调试他。program应该在PATH环境变量中搜索获得。
*启动程序以后,再启动gdb调试:
$gdb <PID>
这里,程序是一个服务程序,那么你能够指定这个服务程序运行时的进程ID,<PID>是要调试程序的PID.这样gdb就附加到程序上了,可是如今还无法查看源代码,用file命令指明可执行文件就能够显示源代码了。
gdb断点设置
http://sourceware.org/gdb/current/onlinedocs/gdb
2、断点设置
gdb断点分类:
以设置断点的命令分类:
breakpoint
能够根据行号、函数、条件生成断点。
watchpoint
监测变量或者表达式的值发生变化时产生断点。
catchpoint
监测信号的产生。例如c++的throw,或者加载库的时候。
gdb中的变量从1开始标号,不一样的断点采用变量标号同一管理,能够 用enable、disable等命令管理,同时支持断点范围的操做,好比有些命令接受断点范围做为参数。
例如:disable 5-8
一、break及break变种详解:
相关命令有break,tbreak,rbreak,hbreak,thbreak,后两种是基于硬件的,先不介绍。
>>break 与 tbreak
break,tbreak能够根据行号、函数、条件生成断点。tbreak设置方法与break相同,只不过tbreak只在断点停一次,事后会自动将断点删除,break须要手动控制断点的删除和使能。
break 可带以下参数:
linenum 本地行号,即list命令可见的行号
filename:linenum 制定个文件的行号
function 函数,可以是自定义函数也但是库函数,如open
filename:function 制定文件中的函数
condtion 条件
(
5.2 多文件设置断点
在进入指定函数时停住:
C++中可使用class::function或function(type,type)格式来指定函数名。若是有名称空间,可使用namespace::class::function或者function(type,type)格式来指定函数名。
break filename:linenum
在源文件filename的linenum行处停住
break filename:function
在源文件filename的function函数的入口处停住break class::function或function(type,type) (我的感受这个比较方便,b 类名::函数名,执行后会提示如:
>>b GamePerson::update
Breakpoint 1 at 0x46b89e: file GamePerson.cpp, line 14.
在类class的function函数的入口处停住
break namespace::class::function
在名称空间为namespace的类class的function函数的入口处停住
)
*address 地址,但是函数,变量的地址,此地址能够经过info add命令获得。
例如:
break 10
break test.c:10
break main
break test.c:main
break system
break open
若是想在指定的地址设置断点,好比在main函数的地址出设断点。
可用info add main 得到main的地址如0x80484624,而后用break *0x80484624.
条件断点就是在如上述指定断点的同时指定进入断点的条件。
例如:(假若有int 类型变量 index)
break 10 if index == 3
tbreak 12 if index == 5
8)单步运行 (gdb) n
9)程序继续运行 (gdb) c
使程序继续往下运行,直到再次遇到断点或程序结束;
break + 设置断点的行号 break n 在n行处设置断点
tbreak + 行号或函数名 tbreak n/func 设置临时断点,到达后被自动删除
until
until line-number 继续运行直到到达指定行号,或者函数,地址等。 (这个颇有用)
until line-number if condition
bt: 显示当前堆栈的追踪,当前所在的函数。
(1)如何打印变量的值?(print var)
(2)如何打印变量的地址?(print &var)
(3)如何打印地址的数据值?(print *address)
(4)如何查看当前运行的文件和行?(backtrace)
(5)如何查看指定文件的代码?(list file:N)
(6)如何当即执行完当前的函数,可是并非执行完整个应用程序?(finish)
(7)若是程序是多文件的,怎样定位到指定文件的指定行或者函数?(list file:N)
(8)若是循环次数不少,如何执行完当前的循环?(until)
4.调试运行环境相关命令
set args set args arg1 arg2 设置运行参数
show args show args 参看运行参数
set width + 数目 set width 70 设置GDB的行宽
cd + 工做目录 cd ../ 切换工做目录
run r/run 程序开始执行
step(s) s 进入式(会进入到所调用的子函数中)单步执行,进入函数的前提是,此函数被编译有debug信息
next(n) n 非进入式(不会进入到所调用的子函数中)单步执行
finish finish 一直运行到函数返回并打印函数返回时的堆栈地址和返回值及参数值等信息
until + 行数 u 3 运行到函数某一行
continue(c) c 执行到下一个断点或程序结束
return <返回值> return 5 改变程序流程,直接结束当前函数,并将指定值返回
call + 函数 call func 在当前位置执行所要运行的函数
查看堆栈信息
info stack
用这条指令你能够看清楚程序的调用层次关系,这个挺有用。3. 执行一行程序. 若呼叫函数, 则将该包含该函数程序代码视为一行程序 (next 指令可简写为 n)。
(gdb) next
4. 执行一行程序. 若呼叫函数, 则进入函数逐行执行 (step 指令可简写为 s)。
(gdb) step5. 执行一行程序,若此时程序是在 for/while/do loop 循环的最后一行,则一直执行到循环结束后的第一行程序后中止 (until 指令可简写为 u)。
(gdb) until6. 执行现行程序到回到上一层程序为止。
(gdb) finish
*执行下一步:
(gdb) next
这样,执行一行代码,若是是函数也会跳过函数。这个命令能够简化为n.
*执行N次下一步:
(gdb) next N
*执行上次执行的命令:
(gdb) [Enter]
这里,直接输入回车就会执行上次的命令了。
*单步进入:
(gdb) step
这样,也会执行一行代码,不过若是遇到函数的话就会进入函数的内部,再一行一行的执行。
*执行完当前函数返回到调用它的函数:
(gdb) finish
这里,运行程序,直到当前函数运行完毕返回再中止。例如进入的单步执行若是已经进入了某函数,而想退出该函数返回到它的调用函数中,可以使用命令finish.
*指定程序直到退出当前循环体:
(gdb) until
或(gdb) u
这里,发现须要把光标中止在循环的头部,而后输入u这样就自动执行所有的循环了。
*列出指定区域(n1到n2之间)的代码:
(gdb) list n1 n2
这样,list能够简写为l,将会显示n1行和n2行之间的代码,若是使用-tui启动gdb,将会在相应的位置显示。若是没有n1和n2参数,那么就会默认显示当前行和以后的10行,再执行又下滚10行。另外,list还能够接函数名。
通常来讲在list后面能够跟如下这们的参数:
<linenum> 行号。
<+offset> 当前行号的正偏移量。
<-offset> 当前行号的负偏移量。
<filename:linenum> 哪一个文件的哪一行。
<function> 函数名。
<filename:function> 哪一个文件中的哪一个函数。
<*address> 程序运行时的语句在内存中的地址
有时候n输不出代码信息,上传缺失的那个文件,而后directory . 表示指定当前目录。 而后就能够看到了。
>>rbreak
rbreak 能够跟一个规则表达式。rbreak + 表达式的用法与grep + 表达式类似。即在全部与表达式匹配的函数入口都设置断点。
rbreak list_* 即在全部以 list_ 为开头字符的函数地方都设置断点。
rbreak ^list_ 功能与上同。
>>查看断点信息
info break [break num ]
info break 可列出全部断点信息,info break 后也可设置要查看的break num如:
info break 1 列出断点号是1的断点信
Linux编程基础——GDB(设置断点)
http://www.cnblogs.com/ggjucheng/archive/2011/12/14/2288004.html
有时候在用gdb调试程序的时候,发现gdb找不到源码。用list命令无效。
记住: gdb的调试信息中并不包含源码,只是包含了怎样去寻找源码,可是由于某种缘由,好比你的源码转移了位置或者别的缘由。你须要告诉gdb到哪里去寻找源码。这个经过directory命令来实现。
要查看当前gdb寻找源码的路径:
show directories
添加一个新的路径到查找路径:
dir dirname
添加多个时,个dirname用: 分开。
详细见 : http://ftp.gnu.org/old-gnu/Manuals/gdb-5.1.1/html_node/gdb_48.html
3、源文件
GDB时,提示找不到源文件。
须要作下面的检查:
编译程序员是否加上了 -g参数 以包含debug信息。
路径是否设置正确了。
使用GDB的directory命令来设置源文件的目录。
下面给一个调试/bin/ls的示例(ubuntu下)
$ apt-get source coreutils
$ sudo apt-get install coreutils-dbgsym
$ gdb /bin/ls
GNU gdb (GDB) 7.1-ubuntu
(gdb) list main
1192 ls.c: No such file or directory.
in ls.c
(gdb) directory ~/src/coreutils-7.4/src/
Source directories searched: /home/hchen/src/coreutils-7.4:$cdir:$cwd
(gdb) list main
1192 }
1193 }
1194
http://blog.csdn.net/liujiayu2/article/details/50008131
http://blog.chinaunix.net/uid-9525959-id-2001805.html
https://wenku.baidu.com/view/504eaffa02768e9951e738c8.html
gdb打印STL容器
GDB中print方法并不能直接打印STL容器中保存的变量,
(gdb) p vec
$1 = std::vector of length 1, capacity 1 = {2}
(
其实只要http://www.yolinux.com/TUTORIALS/src/dbinit_stl_views-1.03.txt这个文件保存为~/.gdbinit 就可使用它提供的方法方便调试容器
如今我使用这个脚本提供的plist方法打印
下载 http://www.yolinux.com/TUTORIALS/src/dbinit_stl_views-1.03.txt 2. #cat dbinit_stl_views-1.03.txt >> ~/.gdbinit 3. 若正处于gdb中,运行命令: (gdb) source ~/.gdbinit
详细查看list中的元素信息
脚本文件以下:把下列内容拷贝到一份txt文件里,而后重命名 .gdbinit(有点号,隐藏文件),而后拷贝到用户主目录下,~/.gdbinit,调用gdb就能够查看容器内容了。
# include < vector >
using namespace std ;
int main( )
{
vector < int > vec;
vec. push_back( 2) ;
vec. push_back( 3) ;
vec. push_back( 4) ;
return 0;
}
编译:#g+ + - o bugging - g bugging. cpp
Breakpoint 1, main ( ) at bugging. cpp: 6
6 vector < int > vec;
( gdb) n
7 vec. push_back( 2) ;
( gdb)
8 vec. push_back( 3) ;
( gdb) pvector
Prints std : : vector < T> information.
Syntax: pvector < vector > < idx1> < idx2>
Note: idx, idx1 and idx2 must be in acceptable range [ 0. . < vector > . size( ) - 1] .
Examples:
pvector v - Prints vector content, size, capacity and T typedef
pvector v 0 - Prints element[ idx] from vector
pvector v 1 2 - Prints elements in range [ idx1. . idx2] from vector
( gdb) pvector vec
elem[ 0] : $1 = 2
Vector size = 1
Vector capacity = 1
Element type = int *
( gdb) n
9 vec. push_back( 4) ;
( gdb)
10 return 0;
( gdb) pvector vec
elem[ 0] : $2 = 2
elem[ 1] : $3 = 3
elem[ 2] : $4 = 4
Vector size = 3
Vector capacity = 4
Element type = int *
( gdb)
5. 默认状况下gdb不能用[]查看stl容器的数据元素,提示以下错误:
( gdb) print vec[ 0]
One of the arguments you tried to pass to operator [ ] could not be converted to what the function wants.
Gdb保存断点:
1. 保存断点
先用info b 查看一下目前设置的断点,使用save breakpoint命令保存到指定的文件,这里我使用了和进程名字后面加bp后缀,你能够按你的喜爱取名字。
我使用的是save breakpoint fig8.3.bp ,在当前目录下,会生成一个fig8.3.bp的文件,里面存储的就是断点的命令。
2. 读取断点
注意,在gdb已经加载进程的过程当中,是不可以读取断点文件的,必须在gdb加载文件的命令中指定断点文件,具体就是使用-x参数。例如,我须要调试fig8.3这个文件,指定刚才保存的断点文件fig8.3.bp。
我使用的是gdb fig8.3 -x fig8.3.bp
我又仔细看了一下上面的对于“save breakpoints”的注释,在另外一个gdb session中可使用“source”命令restore(恢复,还原)它们(断点)。测试一下:先删除全部断点,而后使用source恢复全部保存的断点,以下:
该命令能够改变一个变量的值。
set variable varname = value
varname是变量名称,value是变量的新值。
(1)修改变量值:
a. printv=value: 修改变量值的同时,把修改后的值显示出来
b. set [var]v=value: 修改变量值,须要注意若是变量名与GDB中某个set命令中的关键字同样的话,前面加上var关键字
条件断点
设置一个条件断点,条件由cond指定;在gdb每次执行到此
断点时,cond都被计算。当cond的值为非零时,程序在断点处中止。
用法:
break [break-args] if (condition)
例如:
break main if argc > 1
break 180 if (string == NULL && i < 0)
break test.c:34 if (x & y) == 1
break myfunc if i % (j+3) != 0
break 44 if strlen(mystring) == 0
b 10 if ((int)$gdb_strcmp(a,"chinaunix") == 0)
b 10 if ((int)aa.find("dd",0) == 0)
condition
能够在咱们设置的条件成立时,自动中止当前的程序,先使用break(或者watch也能够)设置断点,
而后用condition来修改这个断点的中止(就是断)的条件。
用法:
condition <break_list> (conditon)
例如:
cond 3 i == 3
condition 2 ((int)strstr($r0,".plist") != 0)
ignore
若是咱们不是想根据某一条件表达式来中止,而是想断点自动忽略前面多少次的中止,从某一次开始
才中止,这时ignore就颇有用了。
用法:
ignore <break_list> count
上面的命令行表示break_list所指定的断点号将被忽略count次。
例如:
ignore 1 100,表示忽略断点1的前100次中止
Watchpoints相关命令:(Watchpoint的做用是让程序在某个表达式的值发生变化的时候中止运行,达到‘监视’该表达式的目的)
(1)设置watchpoints:
a. watch expr: 设置写watchpoint,当应用程序写expr,修改其值时,程序中止运行
b. rwatch expr: 设置读watchpoint,当应用程序读表达式expr时,程序中止运行
c. awatch expr: 设置读写watchpoint, 当应用程序读或者写表达式expr时,程序都会中止运行
(2)info watchpoints:查看当前调试的程序中设置的watchpoints相关信息
(3)watchpoints和breakpoints很相像,都有enable/disabe/delete等操做,使用方法也与breakpoints的相似
对断点的控制除了创建和删除外,还能够经过使能和禁止来控制,后一种方法更灵活。
断点的四种使能操做:
enable [breakpoints] [range...] 彻底使能
enable //激活全部断点
enable 4 //激活4断点
enable 5-6 //激活5~6断点
disable [breakpoints] [range...] 禁止
用法举例:
diable //禁止全部断点
disble 2 //禁止第二个断点
disable 1-5 //禁止第1到第5个断点
enable once [breakpoints] [range...] 使能一次,触发后禁止
enable delete [breakpoints] [range...]使能一次,触发后删除
一、程序运行参数。
set args 可指定运行时参数。(如:set args -f 20 -t 40)
show args 命令能够查看设置好的运行参数。
二、运行环境。
path 可设定程序的运行路径。
show paths 查看程序的运行路径。
set environment varname [=value] 设置环境变量。如:set env USER=user
show environment [varname] 查看环境变量。
三、工做目录。
cd 至关于shell的cd命令。
pwd 显示当前的所在目录。
http://blog.csdn.net/tonywearme/article/details/41447007
http://blog.csdn.net/lxl584685501/article/details/45575717
GDB的全称是GNU project debugger,是类Unix系统上一个十分强大的调试器。这里经过一个简单的例子(插入算法)来介绍如何使用gdb进行调试,特别是如何经过中断来高效地找出死循环;咱们还能够看到,在修正了程序错误并从新编译后,咱们仍然能够经过原先的GDB session进行调试(而不须要重开一个GDB),这避免了一些重复的设置工做;同时,在某些受限环境中(好比某些实时或嵌入式系统),每每只有一个Linux字符界面可供调试。这种状况下,可使用job在代码编辑器、编译器(编译环境)、调试器之间作到无缝切换。这也是高效调试的一个方法。
先来看看这段插入排序算法(a.cpp),里面有一些错误。
// a.cpp #include <stdio.h> #include <stdlib.h> int x[10]; int y[10]; int num_inputs; int num_y = 0; void get_args(int ac, char **av) { num_inputs = ac - 1; for (int i = 0; i < num_inputs; i++) x[i] = atoi(av[i+1]); } void scoot_over(int jj) { for (int k = num_y-1; k > jj; k++) y[k] = y[k-1]; } void insert(int new_y) { if (num_y = 0) { y[0] = new_y; return; } for (int j = 0; j < num_y; j++) { if (new_y < y[j]) { scoot_over(j); y[j] = new_y; return; } } } void process_data() { for (num_y = 0; num_y < num_inputs; num_y++) insert(x[num_y]); } void print_results() { for (int i = 0; i < num_inputs; i++) printf("%d\n",y[i]); } int main(int argc, char ** argv) { get_args(argc,argv); process_data(); print_results(); return 0; }
码就不分析了,稍微花点时间应该就能明白。你能发现几个错误?
使用gcc编译:
gcc -g -Wall -o insert_sort a.cpp
"-g"告诉gcc在二进制文件中加入调试信息,如符号表信息,这样gdb在调试时就能够把地址和函数、变量名对应起来。在调试的时候你就能够根据变量名查看它的值、在源代码的某一行加一个断点等,这是调试的先决条件。“-Wall”是把全部的警告开关打开,这样编译时若是遇到warning就会打印出来。通常状况下建议打开全部的警告开关。
运行编译后的程序(./insert_sort),才发现程序根本停不下来。上调试器!(有些bug可能一眼就能看出来,这里使用GDB只是为了介绍相关的基本功能)
TUI模式
如今版本的GDB都支持所谓的终端用户接口模式(Terminal User Interface),就是在显示GDB命令行的同时能够显示源代码。好处是你能够随时看到当前执行到哪条语句。之因此叫TUI,应该是从GUI抄过来的。注意,能够经过ctrl + x + a来打开或关闭TUI模式。
gdb -tui ./insert_sort
死循环
进入GDB后运行run命令,传入命令行参数,也就是要排序的数组。固然,程序也是停不下来:
为了让程序停下来,咱们能够发送一个中断信号(ctrl + c),GDB捕捉到该信号后会挂起被调试进程。注意,何时发送这个中断有点技巧,彻底取决于咱们的经验和程序的特色。像这个简单的程序,正常状况下几乎马上就会执行完毕。若是感受到延迟就说明已经发生了死循环(或其余什么),这时候发出中断确定落在死循环的循环体中。这样咱们才能经过检查上下文来找到有用信息。大型程序若是正常状况下就须要跑个几秒钟甚至几分钟,那么你至少须要等到它超时后再去中断。
此时,程序暂停在第44行(第44行还未执行),TUI模式下第44行会被高亮显示。咱们知道,这一行是某个死循环体中的一部分。
由于暂停的代码有必定的随机性,能够多运行几回,看看每次停留的语句有什么不一样。后面执行run命令的时候能够不用再输入命令行参数(“12 5”),GDB会记住。还有,再执行run的时候GDB会问是否重头开始执行程序,固然咱们要从头开始执行。
基本肯定位置后(如上面的44行),由于这个程序很小,能够单步(step)一条条语句查看。不难发现问题出在第24行,具体的步骤就省略了。
无缝切换
在编码、调试的时候,除非你有集成开发环境,通常你会须要打开三个窗口:代码编辑器(好比不少人用的VIM)、编译器(新开的窗口运行gcc或者make命令、执行程序等)、调试器。集成开发环境固然好,但某些倒闭的场合下你没法使用任何GUI工具,好比一些仅提供字符界面的嵌入式设备——你只有一个Linux命令行可使用。显然,若是在VIM中修改好代码后须要先关闭VIM才能敲入gcc的编译命令,或者调试过程当中发现问题须要先关闭调试器才能从新打开VIM修改代码、编译、再从新打开调试器,那么不言而喻,这个过程太痛苦了!
好在能够经过Linux的做业管理机制,经过ctrl + z把当前任务挂起,返回终端作其余事情。经过jobs命令能够查看当前shell有哪些任务。好比,当我暂停GDB时,jobs显示个人VIM编辑器进程与GDB目前都处于挂起状态。
如下是些相关的命令,比较经常使用
fg %1 // 打开VIM,1是VIM对应的做业号
fg %2 // 打开GDB
bg %1 // 让VIM到后台运行
kill %1 && fg // 完全杀死VIM进程
GDB的“在线刷新”
好了,刚才介绍了无缝切换,那咱们能够在不关闭GDB的状况下(注意,ctrl + z不是关闭GDB这个进程,只是挂起)切换到VIM中去修改代码来消除死循环(把第24行的“if (num_y = 0)" 改为"if (num_y == 0)")。动做序列能够是:
ctrl + z // 挂起GDB
jobs // 查看VIM对应的做业号,假设为1
fg %1 // 进入VIM,修改代码..
ctrl + z // 修改完后挂起VIM
gcc -g -Wall -o insert_sort a.cpp // 从新编译程序
fg %2 // 进入GDB,假设GDB的做业号为2
如今,咱们又返回GDB调试界面了!但在调试前还有一步,如何让GDB识别新的程序(由于程序已经从新编译)?只要再次运行run就能够了。由于GDB没有关闭,因此以前设置的断点、运行run时传入的命令行参数等还保留着,不须要从新输入。很好用吧!
gdb Tui窗口:
TUI模式
如今版本的GDB都支持所谓的终端用户接口模式(Terminal User Interface),就是在显示GDB命令行的同时能够显示源代码。好处是你能够随时看到当前执行到哪条语句。之因此叫TUI,应该是从GUI抄过来的。注意,能够经过ctrl + x + a来打开或关闭TUI模式。
gdb -tui ./insert_sort
方法1:使用gdb -tui
方法二: 直接使用gdb调试代码,在须要的时候使用切换键 ctrl+x a
调出gdbtui。
http://beej.us/guide/bggdb/#compiling
简单来讲就是在以往的gdb开始的时候添加一个-tui选项.有的版本已经有gdbtui这个程序了
在linux自带的终端里是正常显示的,可是在securecrt里面,可能因为编码的问题,边缘会有些乱码,不过不影响使用(若是你的程序有错误输出,会扰乱整个界面,因此在调试的时候,建议添加2>/dev/null,这样的话基本可用)
启动gdb以后,上面是src窗口,下面是cmd窗口,默认focus在src窗口的,这样的话上下键以及pagedown,pageup都是在移动显示代码,并不显示上下的调试命令.这个时候要切换focus,具体可简单参见
(gdb) info win 查看当前focus
SRC (36 lines) <has focus>
CMD (18 lines)
(gdb) fs next 切换focus Focus set to CMD window. (gdb) info win SRC (36 lines) CMD (18 lines) <has focus> (gdb) fs SRC 切换指定focus Focus set to SRC window. (gdb)
(Window names are case in-sensitive.)
To start in neato and highly-recommended GUI mode, start the debugger with gdb -tui. (For many of the examples, below, I show the output of gdb's dumb terminal mode, but in real life I use TUI mode exclusively.)
And here is a screenshot of what you'll see, approximately:
gdb设置程序参数:
有三种方法能够指定程序运行的参数,第一种方法是在命令行上直接指定;第二种方法是经过run命令提供程序运行时的参数;第三种方法是经过set args命令指定程序的参数
第一种方法:为程序传递参数5
root@guo-virtual-machine:~/debug# gdb --args factorial 5
第二种方法:为程序传递参数5
(gdb) run 5
第三种方法:为程序传递参数5
(gdb) set args 5 (gdb) run Starting program: /root/debug/factorial 5 warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000 Factorial of 5 is 120
查看指定给程序的参数经过show args
由于暂停的代码有必定的随机性,能够多运行几回,看看每次停留的语句有什么不一样。后面执行run命令的时候能够不用再输入命令行参数(“12 5”),GDB会记住。还有,再执行run的时候GDB会问是否重头开始执行程序,固然咱们要从头开始执行。
在使用gdb调试时,常常要用到查看堆栈信息,特别是在内核调试时,这(gdb) show args Argument list to give program being debugged when it is started is "5". (gdb)
set print object on
gdb中查看字符串,地址的操做,数据类型
比始有一个int型的变量i,相要知道他的相关信息,能够
(gdb) print i
打印出变量i的当前值
(gdb)x &i
与上面的命令等价。
若是有x命令看时,须要看一片内存区域,(若是某个地方的值为0,用x时会自动截断了)
(gdb) x/16bx address
单字节16进制打印address地址处的长度为16的空间的内存,16表示空间长度,不是16进制,x表示16进制,b表示byte单字节
gdb看变量是哪一个数据类型
(gdb) whatis i (whatis和ptype相似)
便可知道i是什么类型的变量
gdb调试中,对于动态建立的数组,好比 int * x; 这个时候不能像静态数组那样经过p x 就能够打印出整个数组。若是想要打印出整个数组,能够经过建立一我的工数组来解决这个问题。其通常形式为:
gdb还容许在适当的时候使用类型强制转换,好比:
(gdb) p (int[25]*x
gdb的ptype命令能够很方便的快速浏览类或结构体的结构。
(gdb) info locals命令获得当前栈中全部局部变量的值列表。
print和display的高级选项
p /x y 会以十六进制的格式显示变量,而不是十进制的形式。其它经常使用的格式为c表示字符,s表示字符串,f表示浮点。
能够临时禁用某个显示项。例如
(gdb) dis disp 1
查看条目好,命令是
(gdb) info disp
从新启用条目,命令是
(gdb) enable disp 1
彻底删除显示的条目,命令是
(gdb) undisp 1
在gdb中设置变量
在单步调试程序的中间使用调试器设置变量的值是很是有用的。命令是
(gdb) set x = 12 ( 前提是x必须在程序中已经声明了)
set var a=3
能够经过gdb的set args 命令设置程序的命令行参数。
设置“方便变量”,命令是
(gdb) set $q = p
用来记录特定节点的历史,这个变量q不会去改变本身的地址。
(
9.GDB环境变量
你能够在GDB的调试环境中定义本身的变量,用来保存一些调试程序中的运行数据。要定义一个GDB的变量很简单只需。使用GDB的set命令。GDB的环境变量和UNIX同样,也是以$起头。如:
set $foo = *object_ptr
使用环境变量时,GDB会在你第一次使用时建立这个变量,而在之后的使用中,则直接对其賦值。环境变量没有类型,你能够给环境变量定义任一的类型。包括结构体和数组。
show convenience
该命令查看当前所设置的全部的环境变量。
这是一个比较强大的功能,环境变量和程序变量的交互使用,将使得程序调试更为灵活便捷。例如:
set $i = 0
print bar[$i++]->contents
因而,当你就没必要,print bar[0]->contents, print bar[1]->contents地输入命令了。输入这样的命令后,只用敲回车,重复执行上一条语句,环境变量会自动累加,从而完成逐个输出的功能。
)
在调用gdb时,能够指定“启动文件”。例如:
gdb –command=z x
表示要在可执行文件x上运行gdb,首先要从文件z中读取命令。
(gdb) jump 34
程序直接跳到34行。
使用strace命令,能够跟踪系统作过的全部系统调用。
进程和线程的主要区别是:与进程同样,虽然每一个线程有本身的局部变量,可是多线程环境中父程序的全局变量被全部线程共享,并做为在线程之间通讯的主要方法。
能够经过命令 ps axH 来查看系统上当前的全部进程和线程。
ptype 命令多是我最喜好的命令。它告诉你一个 C 语言表达式的类型。
(gdb) ptype i
C 语言中的类型能够变得很复杂,可是好在 ptype 容许你交互式地查看他们。
调试已运行的程序
两种方法:
(1)在UNIX下用ps查看正在运行的程序的PID(进程ID),而后用gdb PID格式挂接正在运行的程序。
(2)先用gdb 关联上源代码,并进行gdb,在gdb中用attach命令来挂接进程的PID。并用detach来取消挂接的进程。
detach:
当你调试结束以后,可使用该命令断开进程与gdb的链接(结束gdb对进程的控制),在这个命令执行以后,你所调试的那个进程将继续运行;
内存查看命令
可使用examine命令(简写是x)来查看内存地址中的值。x命令的语法以下所示:
x/<n/f/u> <addr>
其中,n是一个正整数,表示须要显示的内存单元的个数。
f 表示显示的格式:x 按十六进制格式显示变量
d 按十进制格式显示变量
u 按十六进制格式显示无符号整型
o 按八进制格式显示变量
t 按二进制格式显示变量
c 按字符格式显示变量
f 按浮点数格式显示变量
u 表示从当前地址日后请求的字节数,若是不指定的话,GDB默认是4个bytes。u参数能够用下面的字符来代替,b表示单字节,h表示双字节,w表示四字 节,g表示八字节。当咱们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其看成一个值取出来。
命令:x/3uh 0x54320 表示,从内存地址0x54320读取内容,h表示以双字节为一个单位,3表示输出三个单位,u表示按十六进制显示。
http://blog.jobbole.com/87482/
https://www.cnblogs.com/muhe221/articles/4846680.html
watch
watch [-l|-location] expr [thread threadnum] [mask maskvalue]
-l 与 mask没有仔细研究,thread threadnum 是在多线程的程序中限定只有被线程号是threadnum的线程修改值后进入断点。
常常用到的以下命令:
watch <expr>
为表达式(变量)expr设置一个观察点。变量量表达式值有变化时,立刻停住程序。
表达式能够是一个变量
例如:watch value_a
表达式能够是一个地址:
例如:watch *(int *)0x12345678 能够检测4个字节的内存是否变化。
表达式能够是一个复杂的语句表达式:
例如:watch a*b + c/d
watch 在有些操做系统支持硬件观测点,硬件观测点的运行速度比软件观测点的快。若是系统支持硬件观测的话,当设置观测点是会打印以下信息:
Hardware watchpoint num: expr
watch两个变种 rwatch,awatch,这两个命令只支持硬件观测点若是系统不支持硬件观测点会答应出不支持这两个命令的信息
rwatch <expr>
当表达式(变量)expr被读时,停住程序。
awatch <expr>
当表达式(变量)的值被读或被写时,停住程序。
info watchpoints
列出当前所设置了的全部观察点。
watch 所设置的断点也能够用控制断点的命令来控制。如 disable、enable、delete等。
1:定位某变量/内存地址 什么时候被修改a为待观察的变量gdb> watch *(long*)a
gdb> watch *(long*)(a+4)
gdb> watch *(long*)(a+8)
2:查看数组的值。编程时:array[i]用GDB查看时,用 p array+i便可。3:善于使用$http://blog.chinaunix.net/uid-20801390-id-3236840.htmlGDB内存断点(Memory break)的使用举例
本文是一篇使用GDB设置内存断点的例子。
1. 源程序
文件名:testMemBreak.c
#include <stdio.h>
#include <string.h>
int main()
{
int i,j;
char buf[256]={0};
char* pp = buf;
printf("buf addr= 0x%x/r/n",buf);
for(i=0;i<16;i++)
{
printf("addr = 0x%x ~ 0x%x/r/n",pp+i*16,pp+i*16+15);
for(j=0;j<16;j++)
*(pp+i*16+j)=i*16+j;
}
printf("ASCII table:/n");
for(i=0;i<16;i++)
{
for(j=0;j<16;j++)
printf("%c ", *(pp+i*16+j));
printf("/n");
}
return 0;
}
即完成ASCII码的初始化并打印出来。
要使用的GDB命令,以下。
(gdb) help watch
Set a watchpoint for an expression.
A watchpoint stops execution of your program whenever the value of
an expression changes.
(gdb) help rwatch
Set a read watchpoint for an expression.
A watchpoint stops execution of your program whenever the value of
an expression is read.
(gdb) help awatch
Set a watchpoint for an expression.
A watchpoint stops execution of your program whenever the value of
an expression is either read or written.
(gdb)
2. 调试过程
2.1 设置断点并启动程序完成初始化
启动程序对其进行初始化,并使用display自动显示buf的地址。
$ gcc -g -o membreak testMemBreak.c
$ gdb ./membreak.exe
GNU gdb 6.3.50_2004-12-28-cvs (cygwin-special)
Copyright 2004 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i686-pc-cygwin"...
(gdb) l
1 #include <stdio.h>
2 #include <string.h>
3
4 int main()
5 {
6 int i,j;
7 char buf[256]={0};
8 char* pp = buf;
9
10 printf("buf addr= 0x%x/r/n",buf);
(gdb)
11 for(i=0;i<16;i++)
12 {
13 printf("addr = 0x%x ~ 0x%x/r/n",pp+i*16,pp+i*16+15);
14 for(j=0;j<16;j++)
15 *(pp+i*16+j)=i*16+j;
16 }
17
18 printf("ASCII table:/n");
19 for(i=0;i<16;i++)
20 {
(gdb)
21 for(j=0;j<16;j++)
22 printf("%c ", *(pp+i*16+j));
23 printf("/n");
24 }
25
26 return 0;
27 }
(gdb) b 10
Breakpoint 1 at 0x4010ae: file testMemBreak.c, line 10.
(gdb) r
Starting program: /cygdrive/e/study/programming/linux/2009-12-28 testMemBreak/membreak.exe
Breakpoint 1, main () at testMemBreak.c:10
10 printf("buf addr= 0x%x/r/n",buf);
(gdb) step
buf addr= 0x22cb70
11 for(i=0;i<16;i++)
(gdb) p buf
$1 = '/0' <repeats 255 times>
(gdb) p &buf
$2 = (char (*)[256]) 0x22cb70
(gdb) display &buf
1: &buf = (char (*)[256]) 0x22cb70
(gdb) p/x *0x22cb70@64
$3 = {0x0 <repeats 64 times>}
(gdb) x/64w 0x22cb70
0x22cb70: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cb80: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cb90: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cba0: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cbb0: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cbc0: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cbd0: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cbe0: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cbf0: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc00: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc10: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc20: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc30: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc40: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc50: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc60: 0x00000000 0x00000000 0x00000000 0x00000000
p/x *0x22cb70@64:以16进制方式显示0x22cb70开始的64个双字组成的数组,实际上就是256个字节的数组,只是默认以双字显示。
能够看见,buf内存块中的全部数据被初始化为0。
2.2 在buf+80处设置内存断点
设置断点后,运行程序,使之停在对该内存写的动做上。
(gdb) watch *(int*)0x22cbc0
Hardware watchpoint 2: *(int *) 2280384
(gdb) c
Continuing.
addr = 0x22cb70 ~ 0x22cb7f
addr = 0x22cb80 ~ 0x22cb8f
addr = 0x22cb90 ~ 0x22cb9f
addr = 0x22cba0 ~ 0x22cbaf
addr = 0x22cbb0 ~ 0x22cbbf
addr = 0x22cbc0 ~ 0x22cbcf
Hardware watchpoint 2: *(int *) 2280384
Old value = 0
New value = 80
main () at testMemBreak.c:14
14 for(j=0;j<16;j++)
1: &buf = (char (*)[256]) 0x22cb70
(gdb) p i
$4 = 5
(gdb) p j
$5 = 0
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x004010ae in main at testMemBreak.c:10
breakpoint already hit 1 time
2 hw watchpoint keep y *(int *) 2280384
breakpoint already hit 1 time
(gdb) delete 1
(gdb) delete 2
(gdb) b 18
Breakpoint 3 at 0x40113b: file testMemBreak.c, line 18.
(gdb) info breakpoints
Num Type Disp Enb Address What
3 breakpoint keep y 0x0040113b in main at testMemBreak.c:18
(gdb) p/x *0x22cb70@64
$6 = {0x3020100, 0x7060504, 0xb0a0908, 0xf0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x23222120,
0x27262524, 0x2b2a2928, 0x2f2e2d2c, 0x33323130, 0x37363534, 0x3b3a3938, 0x3f3e3d3c, 0x43424140, 0x47464544,
0x4b4a4948, 0x4f4e4d4c, 0x50, 0x0 <repeats 43 times>}
(gdb) x/64w 0x22cb70
0x22cb70: 0x03020100 0x07060504 0x0b0a0908 0x0f0e0d0c
0x22cb80: 0x13121110 0x17161514 0x1b1a1918 0x1f1e1d1c
0x22cb90: 0x23222120 0x27262524 0x2b2a2928 0x2f2e2d2c
0x22cba0: 0x33323130 0x37363534 0x3b3a3938 0x3f3e3d3c
0x22cbb0: 0x43424140 0x47464544 0x4b4a4948 0x4f4e4d4c
0x22cbc0: 0x00000050 0x00000000 0x00000000 0x00000000
0x22cbd0: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cbe0: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cbf0: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc00: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc10: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc20: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc30: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc40: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc50: 0x00000000 0x00000000 0x00000000 0x00000000
0x22cc60: 0x00000000 0x00000000 0x00000000 0x00000000
查看buf内存块,能够看见,程序按咱们但愿的在运行,并在buf+80处停住。此时,程序正试图向该单元即0x22cbc0写入80。
2.3 使用continue执行程序直至结束
(gdb) c
Continuing.
addr = 0x22cbd0 ~ 0x22cbdf
addr = 0x22cbe0 ~ 0x22cbef
addr = 0x22cbf0 ~ 0x22cbff
addr = 0x22cc00 ~ 0x22cc0f
addr = 0x22cc10 ~ 0x22cc1f
addr = 0x22cc20 ~ 0x22cc2f
addr = 0x22cc30 ~ 0x22cc3f
addr = 0x22cc40 ~ 0x22cc4f
addr = 0x22cc50 ~ 0x22cc5f
addr = 0x22cc60 ~ 0x22cc6f
Breakpoint 3, main () at testMemBreak.c:18
18 printf("ASCII table:/n");
1: &buf = (char (*)[256]) 0x22cb70
(gdb) p/x *0x22cb70@64
$7 = {0x3020100, 0x7060504, 0xb0a0908, 0xf0e0d0c, 0x13121110, 0x17161514, 0x1b1a1918, 0x1f1e1d1c, 0x23222120,
0x27262524, 0x2b2a2928, 0x2f2e2d2c, 0x33323130, 0x37363534, 0x3b3a3938, 0x3f3e3d3c, 0x43424140, 0x47464544,
0x4b4a4948, 0x4f4e4d4c, 0x53525150, 0x57565554, 0x5b5a5958, 0x5f5e5d5c, 0x63626160, 0x67666564, 0x6b6a6968,
0x6f6e6d6c, 0x73727170, 0x77767574, 0x7b7a7978, 0x7f7e7d7c, 0x83828180, 0x87868584, 0x8b8a8988, 0x8f8e8d8c,
0x93929190, 0x97969594, 0x9b9a9998, 0x9f9e9d9c, 0xa3a2a1a0, 0xa7a6a5a4, 0xabaaa9a8, 0xafaeadac, 0xb3b2b1b0,
0xb7b6b5b4, 0xbbbab9b8, 0xbfbebdbc, 0xc3c2c1c0, 0xc7c6c5c4, 0xcbcac9c8, 0xcfcecdcc, 0xd3d2d1d0, 0xd7d6d5d4,
0xdbdad9d8, 0xdfdedddc, 0xe3e2e1e0, 0xe7e6e5e4, 0xebeae9e8, 0xefeeedec, 0xf3f2f1f0, 0xf7f6f5f4, 0xfbfaf9f8,
0xfffefdfc}
(gdb) x/64w 0x22cb70
0x22cb70: 0x03020100 0x07060504 0x0b0a0908 0x0f0e0d0c
0x22cb80: 0x13121110 0x17161514 0x1b1a1918 0x1f1e1d1c
0x22cb90: 0x23222120 0x27262524 0x2b2a2928 0x2f2e2d2c
0x22cba0: 0x33323130 0x37363534 0x3b3a3938 0x3f3e3d3c
0x22cbb0: 0x43424140 0x47464544 0x4b4a4948 0x4f4e4d4c
0x22cbc0: 0x53525150 0x57565554 0x5b5a5958 0x5f5e5d5c
0x22cbd0: 0x63626160 0x67666564 0x6b6a6968 0x6f6e6d6c
0x22cbe0: 0x73727170 0x77767574 0x7b7a7978 0x7f7e7d7c
0x22cbf0: 0x83828180 0x87868584 0x8b8a8988 0x8f8e8d8c
0x22cc00: 0x93929190 0x97969594 0x9b9a9998 0x9f9e9d9c
0x22cc10: 0xa3a2a1a0 0xa7a6a5a4 0xabaaa9a8 0xafaeadac
0x22cc20: 0xb3b2b1b0 0xb7b6b5b4 0xbbbab9b8 0xbfbebdbc
0x22cc30: 0xc3c2c1c0 0xc7c6c5c4 0xcbcac9c8 0xcfcecdcc
0x22cc40: 0xd3d2d1d0 0xd7d6d5d4 0xdbdad9d8 0xdfdedddc
0x22cc50: 0xe3e2e1e0 0xe7e6e5e4 0xebeae9e8 0xefeeedec
0x22cc60: 0xf3f2f1f0 0xf7f6f5f4 0xfbfaf9f8 0xfffefdfc
(gdb) c
Continuing.
ASCII table:
! " # $ % & ' ( ) * + , - . /
0 1 2 3 4 5 6 7 8 9 : ; < = > ?
@ A B C D E F G H I J K L M N O
P Q R S T U V W X Y Z [ / ] ^ _
` a b c d e f g h i j k l m n o
p q r s t u v w x y z { | } ~
€ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
Program exited normally.
(gdb)
至此,咱们已经看到watch查看内存的做用了,只要被查看的单元会被修改(读/写),均会断在此处,以方便咱们调试程序
https://blog.csdn.net/livelylittlefish/article/details/5110234
gdb 内存断点watch 的使用