一,gdb调试基础
(1)用gdb编译程序时,首先在编译程序时加-g
gcc test.c -o gdb_test -g
(2)运行程序
- gdb + 编译后的文件名
- 在命令行中先输入gdb,按回车,再输入file + 编译后的文件
(3)run命令就可以将程序运行起来了,简写 r
(4)断点
- 可以对某一行进行打断点 例:对程序的第三行进行打断点 b 3
- 多个文件,可以对某个文件的某一行打断点, 例:对test.c的第三行打断点 b test.c:3
- 可以对某个函数进行打断点 例:对程序中函数打断点 b func
- 也可以对多个文件中的某一个文件的函数打断点,例: b test.c:func
- 还可以以条件表达式设置断点, 例: break n if 条件
(5)暂停某个断点:
- 如果不需要程序在该断点暂停时,有两种方法,一种是使该断点失效,一种是直接删除该断点
- enable和disable break启用和暂停某个断点
- delete break 删除所有的断点
- delete break n 删除某个断点 n为断点号
- clear 行号 删除设在某一行的断点
(6)查看断点信息
-
info b 查看所有断点信息
-
info b n 查看第n个断点的信息
(7)打印变量值print,简化p
(8)程序继续执行continue,简化c
(9)显示程序list,简化l
- list 5,10 显示第5行到第10行的代码;
- list func 显示func函数周围的代码,显示范围和list参数有关;
- list test.c:5,10 显示源文件test.c第5行到第10行的代码,一般用于调试含多个源文件的程序。
(10)清屏shell clear
(11)next和step
next,继续执行下一条语句;还有一条命令step,与之类似,不同的是,当下一条语句遇到函数调用的时候,next不会跟踪进入函数,而是继续执行下面的语句,而step命令则会跟踪进入函数内部。
函数返回可以使用return和finish命令
(12)退出gdb调试 quit
二,函数
(1)直接执行函数,使用“call
”或“print
”命令
#include <stdio.h>
int global = 1;
int func(void)
{
return (++global);
}
int main(void)
{
printf("%d\n", global);
return 0;
}
使用gdb调试程序时,可以使用“call
”或“print
”命令直接调用函数执行。以上面程序为例:
(gdb) start
Temporary breakpoint 1 at 0x4004e3: file a.c, line 12.
Starting program: /data2/home/nanxiao/a
Temporary breakpoint 1, main () at a.c:12
12 printf("%d\n", global);
(gdb) call func()
$1 = 2
(gdb) print func()
$2 = 3
(gdb) n
3
13 return 0;
可以看到执行两次func
函数后,global
的值变成3
。
(2)选择函数堆栈帧“frame n
”命令
#include <stdio.h>
int func1(int a)
{
return 2 * a;
}
int func2(int a)
{
int c = 0;
c = 2 * func1(a);
return c;
}
int func3(int a)
{
int c = 0;
c = 2 * func2(a);
return c;
}
int main(void)
{
printf("%d\n", func3(10));
return 0;
}
用gdb调试程序时,当程序暂停后,可以用“frame n
”命令选择函数堆栈帧,其中n
是层数。以上面程序为例:
(gdb) b test.c:5
Breakpoint 1 at 0x40053d: file test.c, line 5.
(gdb) r
Starting program: /home/nanxiao/test
Breakpoint 1, func1 (a=10) at test.c:5
5 return 2 * a;
(gdb) bt
#0 func1 (a=10) at test.c:5
#1 0x0000000000400560 in func2 (a=10) at test.c:11
#2 0x0000000000400586 in func3 (a=10) at test.c:18
#3 0x000000000040059e in main () at test.c:24
(gdb) frame 2
#2 0x0000000000400586 in func3 (a=10) at test.c:18
18 c = 2 * func2(a);
可以看到程序断住后,最内层的函数帧为第0
帧。执行frame 2
命令后,当前的堆栈帧变成了fun3
的函数帧。
也可以用“frame addr
”命令选择函数堆栈帧,其中addr
是堆栈地址。仍以上面程序为例:
(gdb) frame 2
#2 0x0000000000400586 in func3 (a=10) at test.c:18
18 c = 2 * func2(a);
(gdb) i frame
Stack level 2, frame at 0x7fffffffe590:
rip = 0x400586 in func3 (test.c:18); saved rip = 0x40059e
called by frame at 0x7fffffffe5a0, caller of frame at 0x7fffffffe568
source language c.
Arglist at 0x7fffffffe580, args: a=10
Locals at 0x7fffffffe580, Previous frame's sp is 0x7fffffffe590
Saved registers:
rbp at 0x7fffffffe580, rip at 0x7fffffffe588
(gdb) frame 0x7fffffffe568
#1 0x0000000000400560 in func2 (a=10) at test.c:11
11 c = 2 * func1(a);
使用“i frame
”命令可以知道0x7fffffffe568
是func2
的函数堆栈帧地址,使用“frame 0x7fffffffe568
”可以切换到func2
的函数堆栈帧。
(3)用gdb调试程序时,当程序暂停后,可以用“up n
”或“down n
”命令向上或向下选择函数堆栈帧,其中n
是层数。
(4)退出正在调试的函数“finish
”命令或者“return
”命令退出
三,断点
(1)在程序入口处打断点
获取程序入口
方法一:
$ strip a.out
$ readelf -h a.out
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x400440
Start of program headers: 64 (bytes into file)
Start of section headers: 4496 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 9
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28
方法二:
$ gdb a.out
>>> info files
Symbols from "/home/me/a.out".
Local exec file:
`/home/me/a.out', file type elf64-x86-64.
Entry point: 0x400440
0x0000000000400238 - 0x0000000000400254 is .interp
0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
0x0000000000400298 - 0x00000000004002b4 is .gnu.hash
0x00000000004002b8 - 0x0000000000400318 is .dynsym
0x0000000000400318 - 0x0000000000400355 is .dynstr
0x0000000000400356 - 0x000000000040035e is .gnu.version
0x0000000000400360 - 0x0000000000400380 is .gnu.version_r
0x0000000000400380 - 0x0000000000400398 is .rela.dyn
0x0000000000400398 - 0x00000000004003e0 is .rela.plt
0x00000000004003e0 - 0x00000000004003fa is .init
0x0000000000400400 - 0x0000000000400440 is .plt
0x0000000000400440 - 0x00000000004005c2 is .text
0x00000000004005c4 - 0x00000000004005cd is .fini
0x00000000004005d0 - 0x00000000004005e0 is .rodata
0x00000000004005e0 - 0x0000000000400614 is .eh_frame_hdr
0x0000000000400618 - 0x000000000040070c is .eh_frame
0x0000000000600e10 - 0x0000000000600e18 is .init_array
0x0000000000600e18 - 0x0000000000600e20 is .fini_array
0x0000000000600e20 - 0x0000000000600e28 is .jcr
0x0000000000600e28 - 0x0000000000600ff8 is .dynamic
0x0000000000600ff8 - 0x0000000000601000 is .got
0x0000000000601000 - 0x0000000000601030 is .got.plt
0x0000000000601030 - 0x0000000000601040 is .data
0x0000000000601040 - 0x0000000000601048 is .bss
当调试没有调试信息的程序时,直接运行start
命令是没有效果的:
(gdb) start
Function "main" not defined.
如果不知道main在何处,那么可以在程序入口处打断点。先通过readelf
或者进入gdb,执行info files
获得入口地址,然后:
(gdb) b *0x400440
(gdb) r
(2)保存已经设置的断点
在gdb中,可以使用如下命令将设置的断点保存下来:
(gdb) save breakpoints file-name-to-save
下此调试时,可以使用如下命令批量设置保存的断点:
(gdb) source file-name-to-save
(gdb) info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x00000000005a7af0 in gdb_main at /home/xmj/project/binutils-trunk/gdb/main.c:1061
2 breakpoint keep y 0x00000000005a6bd0 in captured_main at /home/xmj/project/binutils-trunk/gdb/main.c:310
3 breakpoint keep y 0x00000000005a68b0 in captured_command_loop at /home/xmj/project/binutils-trunk/gdb/main.c:
(3)设置临时断点tbreak命令
#include <stdio.h>
#include <pthread.h>
typedef struct
{
int a;
int b;
int c;
int d;
pthread_mutex_t mutex;
}ex_st;
int main(void) {
ex_st st = {1, 2, 3, 4, PTHREAD_MUTEX_INITIALIZER};
printf("%d,%d,%d,%d\n", st.a, st.b, st.c, st.d);
return 0;
}
在使用gdb时,如果想让断点只生效一次,可以使用“tbreak”命令(缩写为:tb)。以上面程序为例: