31.GDB介绍及简单使用


欢迎访问个人网络日志🌹🌹知行空间🌹🌹


GDBGNU Debugger的缩写,是GNU软件系统中的标准调试器, 很多类UNIX系统都可以使用GDBGDB主要用来调试C/C++编写的程序,也可以用来用来调试Pascal/FORTRAN/D等语言编写的程序。GDB最早由犹太裔美国人理查德·马修·斯托曼于1988年编写,后基于GNU GPL`协议公开发布。

基本用法

要使用GDB需要在编译c/c++程序时带上编译参数-g,如:

g++ -g foo.cpp -o foo

然后通过gdb命令来运行程序,如:

gdb [executable]

gdb命令可以直接运行程序,也可以调试程序,在运行程序时,可以通过run命令运行程序,在调试程序时,可以通过break设置断点,然后通过continue命令运行程序,当程序运行到断点处时,程序会自动停止。常用调试命令如下:

  • run 运行程序
  • list 查看源码信息
  • break 设置断点
  • disable 禁用断点,如disable break_point1
  • enable启用断点,如enable break_point1
  • delete删除断点等,如delete breakpoint 1
  • continue 运行程序,直到遇到断点
  • next 运行下一行
  • step 进入函数内部
  • finish/fin 执行直到当前函数运行结束,如何有返回值会打印返回值
  • backtrace 查看调用栈
  • info args 查看参数
  • info locals 查看局部变量
  • print 打印变量值/寄存器值
  • disassemble 查看源代码相关的汇编代码
  • quit 退出gdb

常用命令示例:

# 查看某个寄存器的值:
p $rdx

# 设置某个寄存器的值
set $rdx = 0x10

# 查看环境变量
show environment [KEY]

查看汇编代码

  • 直接输入命令disassemble
> disassemble

查看当前函数的汇编代码

  • 查看特定地址的汇编代码
# x命令是用来查看内存地址的值,i命令是用来查看指令的
# > x/FMT ADDDRESS

> x/i 0xaddress
  • 查看特定源文件对应函数的汇编代码
disassemble /m 'source.cpp'::add

Text User Interface(TUI)

使用GDB的时候可以借助文本用户界面Text User Interface来提升代码调试的体验。使用TUI可以同时查看源码,汇编代码和控制调试指令输入。

  • 可以通过以下指令启动gdbTUI模式
> gdb -tui
  • 使用layout next命令来启动TUI,同时展示汇编和源码窗口
> layout next
  • 使用layout split命令来启动TUI,同时展示汇编和源码窗口
> layout split
  • 使用layout regs命令来查看寄存器信息
> layout regs
  • 使用layout asm命令来查看汇编代码
> layout asm
  • 使用layout src命令来查看源代码
> layout src

窗口切换的快捷键:

  • 只显示源代码窗口
Ctrl + X 1
  • 打开汇编代码窗口
Ctrl + X 2

refernece

1.https://www.sourceware.org/gdb/documentation/

<think>嗯,用户让我介绍如何用GDB运行一个Hello World程序,并通过修改寄存器或内存,让程序输出“hello 学号”。首先,我需要确认用户的需求是什么。可能他们是在学习逆向工程或者调试技术,想要通过实践来理解程序在运行时的内存和寄存器状态如何影响输出结果。 首先,我应该考虑用户可能已经有的知识。他们应该知道基本的C语言,因为要编写Hello World程序,以及会用GCC编译。可能还需要了解GDB的基本命令,比如断点设置、查看寄存器、修改内存等。不过,用户可能需要一步步的指导,所以得详细说明每个步骤。 接下来,我需要思考如何让程序输出不同的字符串。通常,Hello World程序中的字符串是存储在只读数据段(.rodata)中的,程序运行时将该字符串的地址加载到寄存器中,然后传递给puts或printf函数。如果用户想修改输出的字符串,可能需要找到该字符串在内存中的位置,并修改其内容。不过,如果字符串是常量,存放在只读段的话,直接修改可能会引发段错误。所以可能需要编译时去掉某些保护选项,比如禁用ASLR,或者让可执行段可写。 或者,用户可能需要修改寄存器中的值,例如在调用printf之前,修改指向字符串的指针寄存器,使其指向另一个内存区域,其中包含修改后的字符串。这需要找到程序调用输出函数的位置,并在该点之前修改相应的寄存器或内存内容。 具体步骤可能包括:编写Hello World程序,编译时加上-g选项以便调试,然后使用GDB在调用puts或printf的地方设置断点,检查当前寄存器和内存中的字符串地址,然后修改内存中的内容为“hello 学号”对应的ASCII码,或者修改寄存器指向新的内存地址。但如何将新的字符串写入内存呢?可能需要事先在程序中预留空间,或者在GDB使用命令写入内存。 或者,考虑直接在原字符串的内存位置覆盖内容。例如,原字符串是“Hello, World!\n”,可能长度不足以放下“hello 学号”,所以可能需要扩展长度,或者在原地址写入更长的字符串,但这可能会溢出,导致不可预知的结果。或者,如果用户学号较短,可能刚好替换原字符串的内容而不溢出。 例如,原字符串是“Hello, World!\n”,假设学号是8位数字,那么“hello 12345678”可能刚好占用差不多的长度,需要覆盖原内存位置的内容,并确保字符串以空字符结尾。或者,如果原字符串后面有其他数据,覆盖可能导致问题,但作为教学示例可能可以忽略。 接下来,我需要具体步骤: 1. 编写C程序,比如: #include <stdio.h> int main() { printf("Hello, World!\n"); return 0; } 2. 编译时使用-g选项:gcc -g -o hello hello.c 3. 启动GDBgdb ./hello 4. 在printf或puts处设置断点。因为编译器可能会将printf替换为puts,所以需要先反汇编main函数,找到调用点。 5. 运行程序,直到断点处,此时检查参数寄存器(如RDI在x86_64中存放第一个参数),找到字符串地址。 6. 查看该地址处的字符串,确认是原内容。 7. 修改该地址处的内存内容为“hello 学号”,注意字符串长度和编码。例如,中文字符可能需要UTF-8编码,每个汉字占3字节。学号如果是数字,直接ASCII码即可。 例如,原字符串“Hello, World!\n”占14字节(包括换行和空字符?或者原字符串是否以空结尾?因为printf需要空终止符)。所以需要确保替换后的字符串长度不超过原空间,否则可能覆盖其他数据。或者,如果原字符串后面有足够的空间,可以覆盖更多,但风险较大。 假设原字符串是“Hello, World!\n”,长度13字节(H e l l o , W o r l d ! \n),共13个字符,加上空终止符共14字节。如果用户需要替换成“hello 学号”,比如“hello 12345”,长度可能刚好,但如果有中文,则需要注意字节数。 例如,“hello 学号”转换为UTF-8的话,“hello ”是6字节,每个汉字(如“学”是E5 AD A6,号是E5 8F B7),所以两个汉字共6字节,总长度是6+6=12字节,加上换行或者空终止符,可能需要13或14字节,可能刚好适合原字符串的空间,假设原字符串后面有空余空间。否则可能需要调整。 如果替换的字符串比原字符串长,可能导致溢出,破坏其他数据,所以可能需要找一个更长的空间或者在程序的其他地方写入新字符串,并修改指针指向那里。 或者,在GDB使用命令如`set {char [n]} address = "hello 学号"`,其中n是新字符串的长度,确保不超过分配的空间。 另一个方法是,在程序运行到断点时,直接修改RDI寄存器(x86_64)的值,使其指向另一个内存地址,比如在栈上分配空间写入新字符串,然后将RDI指向这个地址。这需要了解当前栈的布局,以及如何写入数据到栈中。 例如,在GDB中,可以在栈上分配空间,比如使用命令`print (char*)malloc(20)`来分配堆内存,但可能复杂。或者直接找到栈指针的位置,然后写入字符串内容到栈的某个位置,然后修改RDI指向该地址。 这可能更灵活,因为栈通常有可写权限,而且可以容纳更长的字符串。但需要注意字符串的构造和正确终止。 所以可能的步骤: - 在调用printf之前设置断点。 - 运行到断点处,查看RDI的值,得到当前字符串地址。 - 查看该地址的内容,确认原字符串。 - 计算需要的新字符串的字节,比如“hello 学号”转换为UTF-8的十六进制。 例如,“hello ”的十六进制是68 65 6C 6C 6F 20,“学”的UTF-8是E5 AD A6,“号”是E5 8F B7,所以整个字符串的十六进制是: 68 65 6C 6C 6F 20 E5 AD A6 E5 8F B7 00(假设需要空终止符)。 总长度是6 + 6 +1 =13字节?或者算每个字符的字节数:“hello ”是6字节(包括空格),学号各3字节,所以“学号”是6字节,总长度6+6=12,加空终止符是13字节。如果原字符串是14字节,可能足够。 然后,在GDB使用`set`命令修改内存: set {char [13]} 0x原地址 = {0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0xE5, 0xAD, 0xA6, 0xE5, 0x8F, 0xB7, 0x00} 但需要确认原地址是否有足够的空间,否则可能会覆盖其他数据。 或者,如果原字符串后面没有足够空间,可能需要将新字符串写入其他地方,比如栈上,然后修改RDI寄存器指向该地址。 例如: 1.GDB中,当程序中断在printf调用前,可以找到当前栈的位置。 2. 使用例如`x/gx $rsp`查看栈指针的位置,找一个可用的栈地址。 3. 将新字符串写入栈中,例如从地址$rsp-0x20开始。 4. 使用`set`命令将每个字节写入,例如: set {char} ($rsp - 0x20) = 0x68 set {char} ($rsp - 0x20 +1) = 0x65 依此类推,直到整个字符串写入。 5. 然后修改RDI寄存器的值为该栈地址:set $rdi = $rsp - 0x20 6. 继续执行,此时printf会使用新的字符串。 但这种方法需要手动计算每个字符的十六进制值,可能比较繁琐,尤其是包含中文字符时。 另外,用户可能希望使用他们的学号,例如“hello 20210001”这样的ASCII字符串,这样会更简单,不需要处理UTF-8编码。但题目中的示例是“hello 学号”,所以可能需要处理中文。 所以,用户需要注意,如果他们的学号中有非ASCII字符,需要正确转换为字节序列。 总结步骤: 1. 编写并编译带调试信息的程序。 2.GDB中找到要修改的字符串地址或调用点。 3. 修改内存或寄存器,使字符串变为目标内容。 可能的挑战包括:字符串长度限制,内存区域的写权限,正确计算字节编码。 因此,在回答时,需要详细指导如何找到字符串地址,如何修改内存,如何处理中文字符编码,以及可能的替代方法(如修改寄存器指向栈上的新字符串)。 另外,需要注意编译选项,例如是否启用优化,这可能会影响寄存器的使用和代码结构。因此,建议编译时不使用优化(-O0),保持代码的直观性。 最后,验证修改后的程序是否输出预期的字符串,可能需要继续执行程序,观察输出。</think>要通过GDB修改寄存器或内存使程序输出“hello 学号”,可以按以下步骤操作: --- ### 1. **编写并编译程序** 创建一个简单的 `hello.c` 文件: ```c #include <stdio.h> int main() { printf("Hello, World!\n"); return 0; } ``` 编译时保留调试信息(禁用优化和栈保护): ```bash gcc -g -O0 -no-pie -o hello hello.c ``` - `-no-pie` 关闭地址随机化,方便定位内存地址。 --- ### 2. **启动GDB并设置断点** ```bash gdb ./hello ``` 在GDB中设置断点: ```gdb (gdb) b main # 在 main 函数处断点 (gdb) run # 运行程序 ``` --- ### 3. **定位字符串地址** 反汇编 `main` 函数,找到 `printf` 调用前的参数地址: ```gdb (gdb) disas main ``` 输出示例: ``` 0x0000000000401136 <+8>: lea rdi, [rip+0xec7] # 加载字符串地址到 rdi 0x000000000040113d <+15>: call 0x401030 <puts@plt> ``` 记录字符串地址(例如 `0x402000`,具体值需根据实际输出调整)。 --- ### 4. **查看并修改内存** #### **查看原字符串内容** ```gdb (gdb) x/s 0x402000 # 替换为你的地址 输出示例: 0x402000: "Hello, World!\n" ``` #### **写入新字符串** 将目标字符串转为十六进制(如 "hello 123456"): ```python # 手动转换(注意末尾加 '\0') "hello 123456" 的十六进制: 68 65 6C 6C 6F 20 31 32 33 34 35 36 00 ``` 在GDB中逐字节修改内存: ```gdb (gdb) set {char [13]} 0x402000 = {0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x00} ``` - 如果包含中文(如 "学号"),需使用UTF-8编码(例如 `0xE5ADA6` 表示“学”)。 --- ### 5. **验证并运行** 继续执行程序: ```gdb (gdb) continue ``` 程序会输出修改后的字符串: ``` hello 123456 ``` --- ### 替代方法:修改寄存器指向新地址 如果原字符串空间不足,可在栈上写入新字符串并修改寄存器: ```gdb (gdb) set $rdi = $rsp - 0x20 # 假设栈空间可用 (gdb) set {char [20]} ($rsp-0x20) = "hello 学号\x00" ``` --- ### 注意事项 1. **字符串长度**:新字符串长度不要超过原内存分配。 2. **编码问题**:中文字符需转为UTF-8(例如“学”=`0xE5ADA6`)。 3. **权限问题**:确保目标内存可写(编译时用 `-no-pie` 避免地址随机化)。 通过上述步骤,即可动态修改程序输出为自定义内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值