gcc在链接C++代码时没有自动链接C++标准库,因此链接失败。
可以添加-lstdc++
gcc -o helloworldplus helloworldplus.cpp -lstdc++
#include <stdio.h>
int main(int argv,char *args[]){
printf("argv = %d\n",argv);
for (int i=0;i<argv;i++){
printf("%s\n",args[i]);
}
return 0;
}
将上面代码保存为helloworld.c
,使用gcc -g -o helloworld helloworld.c
编译成可执行程序。
使用gdb helloworld
进入gdb调试窗口。
在运行前使用set args
后跟命令行参数就可以。之后使用run
命令运行程序就可以了
直接在在run
后跟命令行参数就可以。
上图中也有一行Starting program: /root/gdb_study/helloworld a b c
,从这里也能看到我们的命令行参数设置是生效的。
使用gdb attach pid
可以附加到一个正在运行的程序上
gdb中的断点可以分为好几个种类,比如普通断点、函数断点、条件断点等。
break 文件名:行号
在指定文件指定行设置断点。b helloworld.c:4
在helloworld.c
第4行设置断点。
输入list
(缩写为l
)来查看断点附近的代码,输入print
(缩写为p
)来查看变量的值,
break 函数名
在指定函数设置断点
b main
在main
函数设置断点
正则表达式设置函数断点
rb 正则表达式
rb main*
在所有以main
开头的函数添加断点
通过偏移量设置断点
b +偏移量
b -偏移量
当前代码执行到某一行时,如果要为当前代码行的前面某一行或者后面某一行设置断点,就可以通过偏移量来达到快速设置断点的目的。
设置条件断点
b 断点 条件
b main:4 if argv==3
当argv==3时在main
方法第4行设置断点
使用p
命令查看变量的值
代码如下:
#include <stdio.h>
int main(){
int a = 0x1234;
char b[] = "abcdefg";
double c = 10.1;
getchar();
return 0;
}
执行
gcc -g -o memoryview memoryview.c #编译成可执行程序
gdb memoryview
#进入gdb后执行run命令,程序会停在第7行的位置,这时我们就可以查看内存。
命中断点时,使用x
命令来查看各个变量的内存信息。x
命令的语法如下:
x /选项 地址
各种选项
d 按十进制格式显示变量
x 按十六进制格式显示变量
a 按十六进制格式显示变量
u 按十六进制格式显示无符号整型
o 按八进制格式显示变量
t 按二进制格式显示变量
c 按字符格式显示变量
f 按浮点数格式显示变量
执行x /7x b
,以十六进制显示内存信息,/7x
中的7表示显示7个字节
(gdb) x /7x b
0xfffffffff198: 0x61 0x62 0x63 0x64 0x65 0x66 0x67
字符串b
值是abcdefg
,
命令x /s str
还可以以字符串的形式输出。
(gdb) x /s b
0xfffffffff198: "abcdefg"
命令x /d str
还可以以十进制方式显示、按照单个字节的方式显示,同时可以指定显示的字节数量。
(gdb) x /d b
0xfffffffff198: 97
(gdb) x /4d b
0xfffffffff198: 97 98 99 100
对于数字变量,变量名不是地址,使用 &a的方式获取变量地址,然后再显示。
(gdb) x /4t &a
0xfffffffff1ac: 00110100 00010010 00000000 00000000
(gdb) x /2x &a
0xfffffffff1ac: 0x34 0x12
(gdb) x /2d &a
0xfffffffff1ac: 52 18
函数地址、变量地址,还是其他地址,只要地址合法并且可以访问,就可以使用x命令来查看。
在gdb中,通用寄存器会存储一些变量的值、函数的参数以及函数的返回值等。
x86和ARM寄存器的对应关系
- x86寄存器:
EAX
、EBX
、ECX
、EDX
:数据寄存器,用于存储计算结果和中间值。ESP
、EBP
:指针寄存器,用于存储堆栈指针。EIP
:指令指针寄存器,存储当前执行的指令地址。EFlags
:标志寄存器,用于存储状态标志位。- ARM寄存器:
R0
-R15
:通用寄存器,用于存储数据和地址。PC
:程序计数器,存储当前执行的指令地址。SP
:堆栈指针寄存器。LR
:链接寄存器,存储子程序的返回地址。CPSR
(在ARMv7架构中):条件码寄存器,存储当前的条件标志位。
查看寄存器的命令是
info registers
#可以简写成 i r
info all-registers
命令就会显示所有寄存器的值,包括浮点寄存器等。
可以使用i r 寄存器名
查看指定寄存器的值
(gdb) i r pc
pc 0x4101dc 0x4101dc <main+52>
struct t{
int a;
char* b;
} ;
要查看结构体的值,比如上面的结构体,当前有一个st
的结构体 ,a=10,b="abc"
,
(gdb) p st
$1 = {a = 10, b = 0x420010 "abc"}
可以执行set print pretty
使打印更漂亮
(gdb) set print pretty
(gdb) p st
$2 = {
a = 10,
b = 0x420010 "abc"
}
对于数组,可以使用
set print array on
#include<stdio.h>
int b(int n){
return n+1;
}
int a(int n){
return b(n+1);
}
int main(){
int n=0;
n=a(n+1);
return 0;
}
将上面的代码保存成framedemo.c
。执行gcc -g -c framedemo framedemo.c
编程。
执行gdb framedemo
进入gdb窗口,执行b framedemo.c:4
在b
函数中设置断点,执行run
命令运行,程序就会在b
函数中停住。
这时可以使用bt
、bt n
命令查看调用栈帧。n代表现在最上层的栈帧个数。
(gdb) bt
#0 b (n=2) at framedemo.c:4
#1 0x00000000004101d8 in a (n=1) at framedemo.c:8
#2 0x00000000004101f8 in main () at framedemo.c:12
(gdb) bt 1
#0 b (n=2) at framedemo.c:4
(More stack frames follow...)
(gdb) bt 2
#0 b (n=2) at framedemo.c:4
#1 0x00000000004101d8 in a (n=1) at framedemo.c:8
(More stack frames follow...)
frame
(简写f
)默认情况下,我们只能查看当前栈帧的变量,变量的值也只能显示当前栈帧的值。
当前代码停在了第4行,也就是b函数中 return n+1;
的位置,由于当前n+1
还没执行,此时n的值是2。
如果我们想要看下a
函数中变量n
,就可以执行frame 1
切换到a
函数的栈帧,这时使用p
命令就能查看当前栈帧中的变量值。
(gdb) bt
#0 b (n=2) at framedemo.c:4
#1 0x00000000004101d8 in a (n=1) at framedemo.c:8
#2 0x00000000004101f8 in main () at framedemo.c:12
(gdb) p n
$1 = 2
(gdb) frame 1
#1 0x00000000004101d8 in a (n=1) at framedemo.c:8
8 return b(n+1);
(gdb) p n
$2 = 1
可以使用info locals来查看当前帧所包含的所有局部变量的值,也可以使用info args来查看当前帧包含的所有函数参数.
#当前栈帧在b函数,只有一个参数n,值是2。当前没有局部变量。
(gdb) info args
n = 2
(gdb) info locals
No locals.
还可以使用up命令和down命令来切换帧,up命令和down命令都是基于当前帧来计数的。比如,当前帧号为1,使用up 1则会切换到2号帧,使用down 1则会切换到0号帧,
(gdb) up
#1 0x00000000004101d8 in a (n=1) at framedemo.c:8
8 return b(n+1);
(gdb) up
#2 0x00000000004101f8 in main () at framedemo.c:12
12 n=a(n+1);
(gdb) down
#1 0x00000000004101d8 in a (n=1) at framedemo.c:8
8 return b(n+1);
(gdb) down
#0 b (n=2) at framedemo.c:4
4 return n+1;
可以使用info frame
(简写 i f
)查看栈帧的详细信息,默认是当前栈帧,可以后跟数字入info frame 1
查看编号1的栈帧。
(gdb) info frame
Stack level 0, frame at 0xfffffffff170:
pc = 0x4101b0 in b (framedemo.c:4); saved pc = 0x4101d8
called by frame at 0xfffffffff190
source language c.
Arglist at 0xfffffffff160, args: n=2
Locals at 0xfffffffff160, Previous frame's sp is 0xfffffffff170
(gdb) info frame 1
Stack frame at 0xfffffffff190:
pc = 0x4101d8 in a (framedemo.c:8); saved pc = 0x4101f8
called by frame at 0xfffffffff1b0, caller of frame at 0xfffffffff170
source language c.
Arglist at 0xfffffffff170, args: n=1
Locals at 0xfffffffff170, Previous frame's sp is 0xfffffffff190
Saved registers:
x29 at 0xfffffffff170, x30 at 0xfffffffff178
ptype可选参数 变量或者类型
可以显示变量的类型
其中,可选参数用来控制显示信息,变量或者类型可以是任意的变量,也可以是定义的数据类型,比如类、结构体、枚举等。
ptype
命令的可选参数如下所示。
/r:以原始数据的方式显示,不会代替一些typedef定义。
/m:查看类时,不显示类的方法,只显示类的成员变量。
/M:与/m相反,显示类的方法(默认选项)。
/t:不打印类中的typedef数据。
/o:打印结构体字段的偏移量和大小。
以下面的代码为例
#include <stdio.h>
struct t{
int a;
char* b;
} ;
int main(){
struct t st;
st.a=10;
st.b="abc";
int n=10;
printf("%d\t%s\n",st.a,st.b);
return 0;
}
让代码暂停在12行,printf
函数的位置,执行ptype
输出n
,st
类型信息:
(gdb) ptype n
type = int
(gdb) ptype /m st
type = struct t {
int a;
char *b;
}
(gdb) ptype /o st
/* offset | size */ type = struct t {
/* 0 | 4 */ int a;
/* XXX 4-byte hole */
/* 8 | 8 */ char *b;
/* total size (bytes): 16 */
}
whatis,可以用来查看变量或者表达式的类型,只是相对比较简单。
(gdb) whatis n
type = int
(gdb) whatis st
type = struct t
gdb
可以同时显示几个窗口,比如命令窗口、源代码窗口、汇编窗口、寄存器窗口等。
● 命令窗口:gdb命令输入和结果输出的窗口,该窗口始终是可见的。
● 源代码窗口:显示程序源代码的窗口,会随着代码的执行自动显示代码对应的行。
● 汇编窗口:汇编窗口也会随着代码的执行而变化,显示代码对应的汇编代码行。
● 寄存器窗口:显示寄存器的值。
可以使用layout命令来控制窗口的显示。layout命令可以设置显示哪个窗口、是否切分窗口等,主要命令如下所示。
显示下一个窗口:layout next
显示前一个窗口:layout prev
只显示源代码窗口:layout src
只显示汇编窗口:layout asm
显示源代码窗口和汇编窗口:layout split
显示寄存器窗口,与源代码窗口和汇编窗口一起显示:layout regs
设置窗口为活动窗口,以便能够响应上下滚动键:focus next | prev | src | asm | regs | split
更新源代码窗口:refresh
更新源代码窗口:update
要关闭这些窗口(除命令窗口以外),可以执行tui disable
命令。
在
gdb
中直接输入info
,可以查到当前支持的查看的信息的所有命令。比如我们想查看命令
p
的详细信息,可以在gdb
中输入help p
,就能显示p
命令的具体用法。