pwndbg(GDB)

gdb简记

传送门

官网:GDB: The GNU Project Debugger (sourceware.org)

百科:gdb_百度百科 (baidu.com)

教程大全:gdb入门教程大全(清晰明了)_我是标同学的博客-CSDN博客_gdb 教程

声音超甜的b站教程:【小神仙讲 GDB】 通俗易懂版教程 | 一小时入门GDB | Debug | c/c++程序员必备 | 佩雨小神仙 _哔哩哔哩_bilibili

知乎:GDB调试入门指南 - 知乎 (zhihu.com)

啥是个gdb

Summary of GDB

GDB可以做四种主要的事情(加上支持这些事情的其他事情)来帮助您捕获bug:

•启动你的程序,指定任何可能影响它行为的东西。

•让你的程序在特定条件下停止。

•当你的程序停止时,检查发生了什么。

•修改程序中的内容,这样你就可以尝试纠正一个错误的影响,并继续学习另一个错误。

咋配置gdb

安装gdb并检查是否成功安装

1
2
$ yum install gdb
$ gdb --version

GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2 Copyright (C) 2020 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law.

装起来了

说明

因为之前一些工作需要逆向,所以我直接下载的是pwndbg,pwndbg专门针对pwn题调试添加了额外的功能。

就是说,是个gdb的插件,用来进行逆向工程调试的。

如下是pwndbg的安装命令:

$ git clone https://github.com/pwndbg/pwndbg

$ cd pwndbg

$ ./setup.sh

当然,如果只是debug的话,gdb足够,且装了pwndbg之后进行gdb,命令没有变化,只是会在gdb基础上展示出逆向进程。

在此附说明是因为:下述示例中的展示皆基于pwndbg插件下的gdb。

quickstart

先vim一个简单的c程序

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(){
int arr[4] = {1, 2, 3, 4};
for(int i = 0; i < 4; i++){
printf("%d\n",arr[i]);
}
return 0;
}

gcc编译gcc -g start.c,通过gdb进入./a.out执行文件gdb ./a.out

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ gcc -g start.c
$ gdb ./a.out
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 197 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from ./a.out...
pwndbg>

如此进入到gdb指令交互。

gdb指令

男人一下

1
$ man gdb

Here are some of the most frequently needed GDB commands:

  • break [file:]function Set a breakpoint at function (in file).

  • run [arglist] Start your program (with arglist, if specified).

  • bt Backtrace: display the program stack.

  • print expr Display the value of an expression.

  • c Continue running your program (after stopping, e.g. at a breakpoint).

  • next Execute next program line (after stopping); step over any function calls in the line.

  • list [file:]function type the text of the program in the vicinity of where it is presently stopped.

  • step Execute next program line (after stopping); step into any function calls in the line.

  • help [name] Show information about GDB command name, or general information about using GDB.

  • quit Exit from GDB.

基本命令应用

r/run: 润

1
2
3
4
5
6
7
pwndbg> r
Starting program: /home/Mo01iHt/Desktop/a.out
1
2
3
4
[Inferior 1 (process 134817) exited normally]

b/break: 打断点

b 函数名字

在函数处装b(指打断点:

1
2
pwndbg> b main
Breakpoint 1 at 0x555555555169: file start.c, line 3.
b 行号

可搭配list指令(查看源代码),查看代码对应行数,在对应行装b

1
2
3
4
5
6
7
8
9
10
11
12
13
pwndbg> list
1 #include <stdio.h>
2
3 int main(){
4 int arr[4] = {1, 2, 3, 4};
5 for (int i=0;i<4;i++)
6 {
7 printf("%d\n", arr[i]);
8 }
9 return 0;
10 }
pwndbg> b 7
Breakpoint 2 at 0x5555555551a9: file start.c, line 7.
info b: 康康刚在哪装了b
1
2
3
4
pwndbg> info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000555555555169 in main at start.c:3
2 breakpoint keep y 0x00005555555551a9 in main at start.c:7

润一下r

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
pwndbg> r
Starting program: /home/Mo01iHt/Desktop/a.out

Breakpoint 1, main () at start.c:3
3 int main(){
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────[ REGISTERS ]───────────────────────────────────────
RAX 0x555555555169 (main) ◂— endbr64
RBX 0x5555555551f0 (__libc_csu_init) ◂— endbr64
RCX 0x5555555551f0 (__libc_csu_init) ◂— endbr64
RDX 0x7fffffffdf68 —▸ 0x7fffffffe2e3 ◂— 'SHELL=/bin/bash'
RDI 0x1
RSI 0x7fffffffdf58 —▸ 0x7fffffffe2c4 ◂— '/home/Mo01iHt/Desktop/a.out'
R8 0x0
R9 0x7ffff7fe0d60 (_dl_fini) ◂— endbr64
R10 0x0
R11 0x7ffff7f6c7c0 (intel_02_known) ◂— 0x200000200406
R12 0x555555555080 (_start) ◂— endbr64
R13 0x7fffffffdf50 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x0
RSP 0x7fffffffde68 —▸ 0x7ffff7de1083 (__libc_start_main+243) ◂— mov edi, eax
RIP 0x555555555169 (main) ◂— endbr64
────────────────────────────────────────[ DISASM ]─────────────────────────────────────────
► 0x555555555169 <main> endbr64
0x55555555516d <main+4> push rbp
0x55555555516e <main+5> mov rbp, rsp
0x555555555171 <main+8> sub rsp, 0x30
0x555555555175 <main+12> mov rax, qword ptr fs:[0x28]
0x55555555517e <main+21> mov qword ptr [rbp - 8], rax
0x555555555182 <main+25> xor eax, eax
0x555555555184 <main+27> mov dword ptr [rbp - 0x20], 1
0x55555555518b <main+34> mov dword ptr [rbp - 0x1c], 2
0x555555555192 <main+41> mov dword ptr [rbp - 0x18], 3
0x555555555199 <main+48> mov dword ptr [rbp - 0x14], 4
─────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────
In file: /home/Mo01iHt/Desktop/start.c
1 #include <stdio.h>
2
► 3 int main(){
4 int arr[4] = {1, 2, 3, 4};
5 for (int i=0;i<4;i++)
6 {
7 printf("%d\n", arr[i]);
8 }
─────────────────────────────────────────[ STACK ]─────────────────────────────────────────
00:0000│ rsp 0x7fffffffde68 —▸ 0x7ffff7de1083 (__libc_start_main+243) ◂— mov edi, eax
01:0008│ 0x7fffffffde70 —▸ 0x7ffff7ffc620 (_rtld_global_ro) ◂— 0x50f3c00000000
02:0010│ 0x7fffffffde78 —▸ 0x7fffffffdf58 —▸ 0x7fffffffe2c4 ◂— '/home/Mo01iHt/Desktop/a.out'
03:0018│ 0x7fffffffde80 ◂— 0x100000000
04:0020│ 0x7fffffffde88 —▸ 0x555555555169 (main) ◂— endbr64
05:0028│ 0x7fffffffde90 —▸ 0x5555555551f0 (__libc_csu_init) ◂— endbr64
06:0030│ 0x7fffffffde98 ◂— 0x457649a3c538cc92
07:0038│ 0x7fffffffdea0 —▸ 0x555555555080 (_start) ◂— endbr64
───────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────
► f 0 0x555555555169 main
f 1 0x7ffff7de1083 __libc_start_main+243
───────────────────────────────────────────────────────────────────────────────────────────

n/next: 单步运行

p/print: 打印变量值

调试过程中打印出目标的值。

1
2
3
4
5
6
pwndbg> p arr[0]
$1 = 0
pwndbg> p &arr[0]
$2 = (int *) 0x7fffffffde40
pwndbg> p &arr[1]
$3 = (int *) 0x7fffffffde44

显然验证整型数组每个元素占1word,即4bits

No symbol “xxx” in current context 的解决

在进行gdb调试时(包括使用core dump文件调试),经常出现GDB No symbol “xxx” in current context问题,常见的原因如下:

使用gcc编译的时候未使用-g选项。或者加了-g选项,同时使用了-o选项。建议编译的时候使用-g选项的时候,同时使用-Og可以关闭编译优化(-O选项较低gcc版本不支持)。

出现GDB No symbol “xxx” in current context的几种原因分析_春日绿野的博客-CSDN博客_no symbol in current context

s/step: 步入函数

为了方便引例:cp start.c start1.c 提醒自己要有备份文件的习惯 ~~(rm -rf /*~~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

void hello(){
printf("hello echo~\n");
}

int main(){
int arr[4] = {1, 2, 3, 4};
for (int i=0;i<4;i++)
{
printf("%d\n", arr[i]);
}
hello();
return 0;
}

进入gdb进行debug,并在对hello()函数的引用处打断点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
pwndbg> list
11 printf("%d\n", arr[i]);
12 }
13 hello();
14 return 0;
15 }
pwndbg> b 13
Breakpoint 1 at 0x555555555206: file start1.c, line 13.
pwndbg> r
Starting program: /home/Mo01iHt/Desktop/a.out
1
2
3
4

Breakpoint 1, main () at start1.c:13
13 hello();
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────[ REGISTERS ]───────────────────────────────────────
RAX 0x2
RBX 0x555555555230 (__libc_csu_init) ◂— endbr64
RCX 0x0
RDX 0x0
RDI 0x7ffff7fab7e0 (_IO_stdfile_1_lock) ◂— 0x0
RSI 0x5555555592a0 ◂— 0xa34 /* '4\n' */
R8 0x0
R9 0x2
R10 0x555555556012 ◂— 0x483b031b01000a /* '\n' */
R11 0x246
R12 0x5555555550a0 (_start) ◂— endbr64
R13 0x7fffffffdf50 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffde60 ◂— 0x0
RSP 0x7fffffffde30 —▸ 0x7ffff7fae2e8 (__exit_funcs_lock) ◂— 0x0
RIP 0x555555555206 (main+102) ◂— mov eax, 0
────────────────────────────────────────[ DISASM ]─────────────────────────────────────────
► 0x555555555206 <main+102> mov eax, 0
0x55555555520b <main+107> call hello <hello>

0x555555555210 <main+112> mov eax, 0
0x555555555215 <main+117> mov rdx, qword ptr [rbp - 8]
0x555555555219 <main+121> xor rdx, qword ptr fs:[0x28]
0x555555555222 <main+130> je main+137 <main+137>

0x555555555224 <main+132> call __stack_chk_fail@plt <__stack_chk_fail@plt>

0x555555555229 <main+137> leave
0x55555555522a <main+138> ret

0x55555555522b nop dword ptr [rax + rax]
0x555555555230 <__libc_csu_init> endbr64
─────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────
In file: /home/Mo01iHt/Desktop/start1.c
8 int arr[4] = {1, 2, 3, 4};
9 for (int i=0;i<4;i++)
10 {
11 printf("%d\n", arr[i]);
12 }
► 13 hello();
14 return 0;
15 }
─────────────────────────────────────────[ STACK ]─────────────────────────────────────────
00:0000│ rsp 0x7fffffffde30 —▸ 0x7ffff7fae2e8 (__exit_funcs_lock) ◂— 0x0
01:0008│ 0x7fffffffde38 ◂— 0x455555230
02:0010│ 0x7fffffffde40 ◂— 0x200000001
03:0018│ 0x7fffffffde48 ◂— 0x400000003
04:0020│ 0x7fffffffde50 —▸ 0x7fffffffdf50 ◂— 0x1
05:0028│ 0x7fffffffde58 ◂— 0x8fee5fb1f1bbc000
06:0030│ rbp 0x7fffffffde60 ◂— 0x0
07:0038│ 0x7fffffffde68 —▸ 0x7ffff7de1083 (__libc_start_main+243) ◂— mov edi, eax
───────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────
► f 0 0x555555555206 main+102
f 1 0x7ffff7de1083 __libc_start_main+243
───────────────────────────────────────────────────────────────────────────────────────────

一个s步入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
pwndbg> s
hello () at start1.c:3
3 void hello(){
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────[ REGISTERS ]───────────────────────────────────────
*RAX 0x0
RBX 0x555555555230 (__libc_csu_init) ◂— endbr64
RCX 0x0
RDX 0x0
RDI 0x7ffff7fab7e0 (_IO_stdfile_1_lock) ◂— 0x0
RSI 0x5555555592a0 ◂— 0xa34 /* '4\n' */
R8 0x0
R9 0x2
R10 0x555555556012 ◂— 0x483b031b01000a /* '\n' */
R11 0x246
R12 0x5555555550a0 (_start) ◂— endbr64
R13 0x7fffffffdf50 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7fffffffde60 ◂— 0x0
*RSP 0x7fffffffde28 —▸ 0x555555555210 (main+112) ◂— mov eax, 0
*RIP 0x555555555189 (hello) ◂— endbr64
────────────────────────────────────────[ DISASM ]─────────────────────────────────────────
► 0x555555555189 <hello> endbr64
0x55555555518d <hello+4> push rbp
0x55555555518e <hello+5> mov rbp, rsp
0x555555555191 <hello+8> lea rdi, [rip + 0xe6c]
0x555555555198 <hello+15> call puts@plt <puts@plt>

0x55555555519d <hello+20> nop
0x55555555519e <hello+21> pop rbp
0x55555555519f <hello+22> ret

0x5555555551a0 <main> endbr64
0x5555555551a4 <main+4> push rbp
0x5555555551a5 <main+5> mov rbp, rsp
─────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────
In file: /home/Mo01iHt/Desktop/start1.c
1 #include <stdio.h>
2
► 3 void hello(){
4 printf("hello echo~\n");
5 }
6
7 int main(){
8 int arr[4] = {1, 2, 3, 4};
─────────────────────────────────────────[ STACK ]─────────────────────────────────────────
00:0000│ rsp 0x7fffffffde28 —▸ 0x555555555210 (main+112) ◂— mov eax, 0
01:0008│ 0x7fffffffde30 —▸ 0x7ffff7fae2e8 (__exit_funcs_lock) ◂— 0x0
02:0010│ 0x7fffffffde38 ◂— 0x455555230
03:0018│ 0x7fffffffde40 ◂— 0x200000001
04:0020│ 0x7fffffffde48 ◂— 0x400000003
05:0028│ 0x7fffffffde50 —▸ 0x7fffffffdf50 ◂— 0x1
06:0030│ 0x7fffffffde58 ◂— 0x8fee5fb1f1bbc000
07:0038│ rbp 0x7fffffffde60 ◂— 0x0
───────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────
► f 0 0x555555555189 hello
f 1 0x555555555210 main+112
f 2 0x7ffff7de1083 __libc_start_main+243
───────────────────────────────────────────────────────────────────────────────────────────

可以看到步入到了hello()函数处

c/continue: 继续执行到下一个断点

我们可能打了多处断点,或者断点打在循环内,这个时候,想跳过这个断点,甚至跳过多次断点继续执行该怎么做呢?可以使用continue命令(可简写为c)或者fg,它会继续执行程序,直到再次遇到断点处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> b main
Breakpoint 1 at 0x1169: file start.c, line 3.
pwndbg> b 7
Breakpoint 2 at 0x11a9: file start.c, line 7.
pwndbg> r
Starting program: /home/Mo01iHt/Desktop/a.out

Breakpoint 1, main () at start.c:3
3 int main(){
pwndbg> c #继续运行,直到下一次断住
Continuing.

Breakpoint 2, main () at start.c:7
7 printf("%d\n", arr[i]);
pwndbg> c 3 #跳过三次
Will ignore next 2 crossings of breakpoint 2. Continuing.
1
2
3

Breakpoint 2, main () at start.c:7
7 printf("%d\n", arr[i]);

u/until: 继续运行到指定位置

例如我们在main函数起始处打断停住,想要运行到第9行时再次停住,则:

1
2
3
4
5
6
7
8
9
10
11
pwndbg> b main
Breakpoint 1 at 0x1169: file start.c, line 3.
pwndbg> r
pwndbg> until 9
1
2
3
4

main () at start.c:9
9 return 0;

gdb一些骚操作

允许执行终端命令: shell xxx

例如shell cat start.c

1
2
3
4
5
6
7
8
9
10
11
pwndbg> shell cat start.c
#include <stdio.h>

int main(){
int arr[4] = {1, 2, 3, 4};
for (int i=0;i<4;i++)
{
printf("%d\n", arr[i]);
}
return 0;
}

日志功能: set logging on

可以生成一个.txt文档记录输出日志

如执行如下命令set logging in

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
pwndbg> set logging on
Copying output to gdb.txt.
Copying debug output to gdb.txt.
pwndbg> b hello
Breakpoint 2 at 0x555555555189: file start1.c, line 3.
pwndbg> r
Starting program: /home/Mo01iHt/Desktop/a.out
1
2
3
4

Breakpoint 1, main () at start1.c:13
13 hello();
pwndbg> n

Breakpoint 2, hello () at start1.c:3
3 void hello(){
pwndbg> n
4 printf("hello echo~\n");
pwndbg> n
hello echo~
5 }
pwndbg> n
main () at start1.c:14
14 return 0;
pwndbg> n
15 }
pwndbg> n
__libc_start_main (main=0x5555555551a0 <main>, argc=1, argv=0x7fffffffdf58, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdf48) at ../csu/libc-start.c:342
342 ../csu/libc-start.c: No such file or directory.

退出后在终端ll查看:

1
2
3
4
5
6
$ ll
total 28
-rwxrwxr-x 1 Mo01iHt Mo01iHt 16824 11月 22 00:06 a.out*
-rw-rw-r-- 1 Mo01iHt Mo01iHt 25268 11月 22 00:01 gdb.txt
-rw-rw-r-- 1 Mo01iHt Mo01iHt 181 11月 21 23:41 start1.c
-rw-rw-r-- 1 Mo01iHt Mo01iHt 128 11月 21 22:18 start.c

可以看到生成了一个叫做gdb.txt的文件。vim进来看看:

算了懒得搬了。

观察点watchpoints & 捕捉点catchpoints

啥,除了berakpoints还有watchpoints和catchpoints??

Breakpoints, watchpoints and catchpoints

插个眼: watch

watchpoints的主要用途就是在动态调试过程中观察变量的变化。

如若给变量 i 插眼:

记录下变量 i 地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
pwndbg> list
1 #include <stdio.h>
2
3 void hello(){
4 printf("hello echo~\n");
5 }
6
7 int main(){
8 int arr[4] = {1, 2, 3, 4};
9 for (int i=0;i<4;i++)
10 {
pwndbg> b 9
Breakpoint 1 at 0x11d7: file start1.c, line 9.
pwndbg> r
Starting program: /home/Mo01iHt/Desktop/a.out

Breakpoint 1, main () at start1.c:9
9 for (int i=0;i<4;i++)
pwndbg> p &i
$1 = (int *) 0x7fffffffde3c

插个眼watch *0x7fffffffde3c

1
2
3
4
5
pwndbg> watch *0x7fffffffde3c
Hardware watchpoint 2: *0x7fffffffde3c
pwndbg> info watchpoints
Num Type Disp Enb Address What
2 hw watchpoint keep y *0x7fffffffde3c

(可利用info watchpoints查看插眼史

然后在接下来n单步调试可以看到watchpoints的变化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pwndbg> n

Hardware watchpoint 2: *0x7fffffffde3c

Old value = 21845
New value = 0
main () at start1.c:9
9 for (int i=0;i<4;i++)
pwndbg> n
11 printf("%d\n", arr[i]);
pwndbg> n
1
9 for (int i=0;i<4;i++)
pwndbg> n

Hardware watchpoint 2: *0x7fffffffde3c

Old value = 0
New value = 1
0x0000555555555200 in main () at start1.c:9
9 for (int i=0;i<4;i++)

在变量的值变化时,会触发Hardware watchpoint x: *0x0123456789ab,并会显示出old value与new value,便于对照。

禁用或启动断点、断点清除

有些断点暂时不想使用,但又不想删除,可以暂时禁用或启用。例如:

disable #禁用所有断点

disable bnum #禁用标号为bnum的断点

enable #启用所有断点

enable bnum #启用标号为bnum的断点

enable delete bnum #启动标号为bnum的断点,并且在此之后删除该断点

断点清除主要用到clear和delete命令。常见使用如下:

clear #删除当前行所有breakpoints

clear function #删除函数名为function处的断点

clear filename:function #删除文件filename中函数function处的断点

clear lineNum #删除行号为lineNum处的断点

clear f:lename:lineNum #删除文件filename中行号为lineNum处的断点

delete #删除所有breakpoints,watchpoints和catchpoints delete bnum #删除断点号为bnum的断点

按照特定格式打印变量

对于简单的数据,print默认的打印方式已经足够了,它会根据变量类型的格式打印出来,但是有时候这还不够,我们需要更多的格式控制。常见格式控制字符如下:

  • x 按十六进制格式显示变量。
  • d 按十进制格式显示变量。
  • u 按十六进制格式显示无符号整型。
  • o 按八进制格式显示变量。
  • t 按二进制格式显示变量。
  • a 按十六进制格式显示变量。
  • c 按字符格式显示变量。
  • f 按浮点数格式显示变量。

还是以辅助程序来说明,正常方式打印字符数组c:

1
2
pwndbg> p c
$1 = "hello"

但是如果我们要查看它的十六进制格式打印呢?

1
2
pwndbg> p/x c
$2 = {0x68, 0x65, 0x6c, 0x6c, 0x6f}

查看内存内容

examine(简写为x)可以用来查看内存地址中的值。语法如下:

x/[n][f][u] addr

其中:

  • n 表示要显示的内存单元数,默认值为1
  • f 表示要打印的格式,前面已经提到了格式控制字符
  • u 要打印的单元长度
  • addr 内存地址

单元类型常见有如下:

  • b 字节
  • h 半字,即双字节
  • w 字,即四字节
  • g 八字节

我们通过一个实例来看,假如我们要把float变量e按照二进制方式打印,并且打印单位是一字节:

1
2
pwndbg> x/4tb &e
0x7fffffffdbd4: 00000000 00000000 00001000 01000001

可以看到,变量e的四个字节都以二进制的方式打印出来了。

自动显示变量内容

假设我们希望程序断住时,就显示某个变量的值,可以使用display命令。

1
2
pwndbg> display e
1: e = 8.5

那么每次程序断住时,就会打印e的值。要查看哪些变量被设置了display,可以使用:

1
2
3
4
5
pwndbg> into display
Auto-display expressions now in effect:
Num Enb Expression
1: y b
2: y e

如果想要清除可以使用

delete display num #num为前面变量前的编号,不带num时清除所有。

或者去使能:

disable display num #num为前面变量前的编号,不带num时去使能所有

查看寄存器内容

1
2
3
4
5
6
7
8
9
pwndbg> info registers
rax 0x0 0
rbx 0x0 0
rcx 0x7ffff7dd1b00 140737351850752
rdx 0x0 0
rsi 0x7ffff7dd1b30 140737351850800
rdi 0xffffffff 4294967295
rbp 0x7fffffffdc10 0x7fffffffdc10
(内容过多未显示完全)

调试core文件

前述是针对a.out是调试一个binary文件,

下述调试宕掉的程序。

写一个error.c:

1
2
3
4
5
6
7
#include <stdio.h>

int main(){
int* temp = NULL;
*temp = 10;
return 0;
}

显然给一个空指针赋值,会触发段错误:

1
2
3
$ gcc -g error.c
$ ./a.out
Segmentation fault (core dumped)

对于出现内存越界错误的程序,我们通常会查看生成的core文件来进行进一步分析。

这里提到了一个core文件,那么:

什么是core文件?

core是unix系统的内核,或者说是内存的映像。当程序出现内存越界的时候,操作系统会中止你的进程,并将当前内存状态倒出到core文件中,以便进一步分析。

inux系统下执行代码,不能正常运行,程序会core,或者直接离开,不会core。 bug和操作系统或硬件的保护机制都会导致程序异常终止,操作系统会kill掉这些进程并产生core文件。程序员可以通过core文件来找出问题所在。 它记录了程序挂掉时详细的状态描述。

开发和使用 Unix程序时, 有时程序莫名其妙的宕(down?)了, 却没有任何的提示(有时候会提示core dumped)。 这时候可以查看一下有没有形如core.进程号的文件生成,这个文件便是操作系统把程序宕掉时的内存内容扔出来生成的,它可以做为调试程序的参考。

开启core dump

然而在Linux的默认限制中,core文件不会默认生成,需要手动修改资源设置的限制以开启core文件的生成(使用命令ulimit -a查看配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15258
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15258
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

(可以看到core file size 默认为0,即不会自动生成。使用指令ulimit -c unlimited开启

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ ulimit -c unlimited
$ ulimit -a
core file size (blocks, -c) unlimited
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15258
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15258
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

设置好后再次运行可执行文件

1
2
3
$ gcc -g error.c
$ ./a.out
Segmentation fault (core dumped)

显而易见,程序给你抛出了一个core(core dumped

(我在这里出现了个问题,在如上配置好之后仍然没有生成core文件,csdn了一下,找到解决方案:解决:Linux下C++段错误不产生core文件_zsiming的博客-CSDN博客
查看相关设置:
$ cat /proc/sys/kernel/core_pattern 
|/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -- %E
就是说,文件的结果会被Linux的一个进程apport吞掉,拿去检查是不是系统的bug,所以看不到core文件了。
解决方法:
$ sudo service apport stop
)

ll一下可以看到同级目录下生成了一个core文件:

1
2
3
4
5
6
7
8
9
$ ll
total 1072
-rwxrwxr-x 1 Mo01iHt Mo01iHt 18984 11月 22 22:57 a.out*
-rw------- 1 Mo01iHt Mo01iHt 253952 11月 22 23:10 core
-rw-rw-r-- 1 Mo01iHt Mo01iHt 77 11月 22 22:35 error.c
-rw------- 1 Mo01iHt Mo01iHt 1335 11月 22 22:30 .gdb_history
-rw-rw-r-- 1 Mo01iHt Mo01iHt 25268 11月 22 00:22 gdb.txt
-rw-rw-r-- 1 Mo01iHt Mo01iHt 181 11月 22 00:30 start1.c
-rw-rw-r-- 1 Mo01iHt Mo01iHt 128 11月 21 22:18 start.c

进入gdb调试

带着core文件来gdb,执行命令$ gdb a.out core

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ gdb a.out core
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 197 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from a.out...
[New LWP 173306]
Core was generated by `./a.out'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000056088e42013d in main () at error.c:5
5 *temp = 10;
Exception occurred: Error: maximum recursion depth exceeded in comparison (<class 'RecursionError'>)
For more info invoke `set exception-verbose on` and rerun the command
or debug it by yourself with `set exception-debugger on`
Python Exception <class 'RecursionError'> maximum recursion depth exceeded in comparison:

gdb启动是真的罗里吧嗦啊)

可以看到带着core文件进入到gdb里会直接爆出fault以及产生抛出错误的代码行。

调试一个正在运行的文件

先写一个程序start2.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void test(){
}

void test1(){
int i = 0;
i++;
}
int main(){
for(;;){
test();
test1();
}
return 0;
}

这个程序程序无限循环并一直call给函数test()和test1(),但过程中i一直自加。

gcc编译之后让他挂到后台运行(./a.out &

1
2
3
$ gcc -g start2.c
$ ./a.out &
[1] 7514

给我们抛了一个pid(进程识别号):7514

pid进程还可以通过ps命令$ ps -ef|grep 进程名或者$ pidof 进程名找到:

1
2
3
4
5
6
$ ps -ef|grep ./a.out
Mo01iHt 4548 4176 71 00:04 pts/0 00:30:16 ./a.out
Mo01iHt 7514 4176 19 00:17 pts/0 00:05:46 ./a.out
Mo01iHt 14018 4176 0 00:46 pts/0 00:00:00 grep --color=auto ./a.out
$ pidof ./a.out
7514 4548

那么怎么利用pid通过gdb调试一个正在运行的gdb呢?

man一下gdb($ man gdb

  • You can run “gdb” with no arguments or options; but the most usual way to start GDB is with one argument or two, specifying an executable program as the argument:

    gdb program

  • You can also start with both an executable program and a core file specified:

    gdb program core

  • You can, instead, specify a process ID as a second argument or use option “-p”, if you want to debug a running process:

    gdb program 1234

    gdb -p 1234

    would attach GDB to process 1234. With option -p you can omit the program filename.

于是乎$ gdb -p 7514

或者

$ gdb

pwndgb> attach pid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
$ gdb -p 7514
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
pwndbg: loaded 197 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Attaching to process 7514
Reading symbols from /home/Mo01iHt/Desktop/a.out...
Reading symbols from /lib/x86_64-linux-gnu/libc.so.6...
Reading symbols from /usr/lib/debug/.build-id/18/78e6b475720c7c51969e69ab2d276fae6d1dee.debug...
Reading symbols from /lib64/ld-linux-x86-64.so.2...
Reading symbols from /usr/lib/debug/.build-id/45/87364908de169dec62ffa538170118c1c3a078.debug...
0x0000559272c30133 in test () at start2.c:4
4 }
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]─────────────────────────────────
RAX 0x0
RBX 0x559272c30170 (__libc_csu_init) ◂— endbr64
RCX 0x559272c30170 (__libc_csu_init) ◂— endbr64
RDX 0x7ffd166c9ea8 —▸ 0x7ffd166cb33e ◂— 'SHELL=/bin/bash'
RDI 0x1
RSI 0x7ffd166c9e98 —▸ 0x7ffd166cb336 ◂— 0x74756f2e612f2e /* './a.out' */
R8 0x0
R9 0x7f7f22a4ed60 (_dl_fini) ◂— endbr64
R10 0x0
R11 0x7f7f229e07c0 (intel_02_known) ◂— 0x200000200406
R12 0x559272c30040 (_start) ◂— endbr64
R13 0x7ffd166c9e90 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7ffd166c9da0 ◂— 0x0
RSP 0x7ffd166c9d98 —▸ 0x559272c3015c (main+18) ◂— mov eax, 0
RIP 0x559272c30133 (test+10) ◂— ret
──────────────────────────────────[ DISASM ]───────────────────────────────────
► 0x559272c30133 <test+10> ret <0x559272c3015c; main+18>

0x559272c3015c <main+18> mov eax, 0
0x559272c30161 <main+23> call test1 <test1>

0x559272c30166 <main+28> jmp main+8 <main+8>

0x559272c30168 nop dword ptr [rax + rax]
0x559272c30170 <__libc_csu_init> endbr64
0x559272c30174 <__libc_csu_init+4> push r15
0x559272c30176 <__libc_csu_init+6> lea r15, [rip + 0x2c73] <0x559272c32df0>
0x559272c3017d <__libc_csu_init+13> push r14
0x559272c3017f <__libc_csu_init+15> mov r14, rdx
0x559272c30182 <__libc_csu_init+18> push r13
───────────────────────────────[ SOURCE (CODE) ]───────────────────────────────
In file: /home/Mo01iHt/Desktop/start2.c
1 #include <stdio.h>
2
3 void test(){
► 4 }
5
6 void test1(){
7 int i = 0;
8 i++;
9 }
───────────────────────────────────[ STACK ]───────────────────────────────────
00:0000│ rsp 0x7ffd166c9d98 —▸ 0x559272c3015c (main+18) ◂— mov eax, 0
01:0008│ rbp 0x7ffd166c9da0 ◂— 0x0
02:0010│ 0x7ffd166c9da8 —▸ 0x7f7f22855083 (__libc_start_main+243) ◂— mov edi, eax
03:0018│ 0x7ffd166c9db0 —▸ 0x7f7f22a6a620 (_rtld_global_ro) ◂— 0x50f4000000000
04:0020│ 0x7ffd166c9db8 —▸ 0x7ffd166c9e98 —▸ 0x7ffd166cb336 ◂— 0x74756f2e612f2e /* './a.out' */
05:0028│ 0x7ffd166c9dc0 ◂— 0x100000000
06:0030│ 0x7ffd166c9dc8 —▸ 0x559272c3014a (main) ◂— endbr64
07:0038│ 0x7ffd166c9dd0 —▸ 0x559272c30170 (__libc_csu_init) ◂— endbr64
─────────────────────────────────[ BACKTRACE ]─────────────────────────────────
► f 0 0x559272c30133 test+10
f 1 0x559272c3015c main+18
f 2 0x7f7f22855083 __libc_start_main+243
───────────────────────────────────────────────────────────────────────────────

可以看到进程执行到test()处。

(很难受的是我在这里又寄乐,报错如下:Could not attach to process. If your uid matches the uid of the target process, check the setting of /proc/sys/kernel/yama/ptrace_scope, or try again as the root user. For more details, see /etc/sysctl.d/10-ptrace.conf ptrace: Operation not permitted.,cn一下:使用GDB调试时attach ID不被允许 - longyuan-z - 博客园 (cnblogs.com)
$ cat  /etc/sysctl.d/10-ptrace.conf查看配置文件。最后一行默认是kernel.yama.ptrace_scope = 1,这个值不允许用户使用普通账户使用attach ID连接程序进行调试,需要使用超级用户权限才能连接。
解决方法:sudo vim /etc/sysctl.d/10-ptrace.conf将最后一行改为 kernel.yama.ptrace_scope = 0,保存退出。重启系统后,普通用户就可以使用attach ID连接程序调试了。
)

简单调试观察一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pwndbg> n
main () at start2.c:13
13 test1();
pwndbg> s
test1 () at start2.c:6
6 void test1(){
pwndbg> n
7 int i = 0;
pwndbg> n
8 i++;
pwndbg> p i
$1 = 0
pwndbg> n
9 }
pwndbg> p i
$2 = 1
pwndbg> n
main () at start2.c:12
12 test();