使用GDB
概述
学习汇编的过程中利用GDB了解运行中的状态是必要的,了解实际运作中做了什么事而变成了什么,对于熟悉有巨大的帮助。
另外现在已经有发达的LLM,问ai各种为什么的问题和网络搜索也同样重要。
说明
GDB的作用是将可执行文件反汇编来做分析,是编写汇编过程中良好的调试工具。
使用GDB
对着我们的第一个程序打开gdb
gdb filename
常用指令:
b 设定断点r 运行disas 显示当前位置代码i r 显示当前所有寄存器的内容i r rax 显示rax寄存器的内容n 执行高级语言源文件一行不进入函数ni 执行汇编语言一行不进入函数s 执行高级语言源文件一行si 执行汇编语言一行x/gx 查看内存内容 8bytes hex格式
首先设定断点,linux的程序进入点默认是_start函数
接着执行到中断处,并显示当前位置代码内容
b _startrdisas
可以看到显示了跟我们写的相似的代码,不过指令是gdb用的风格
另外有些寄存器被改成32位元的e开头而不是64位元的r,是nasm处理后的结果
查看寄存器
首先第一行
=> mov rax, 1
在运行前我们可以先用i r rax
看看它原本的值
由于这一段原本就是汇编语言,所以执行下一行可以用si或s都一样
再看看rax改变后的值
查看内存
接着把mov rsi, 0x402000这行运行完,再看看rsi的内容
i r rsi
这行原本是将hello的内存地址放到rsi
也就是0x402000存着Hello World!\n
使用
x/gx 0x402000或x/gx $rsi
存取它的内存地址
x/gx $rsi就像是對指針做*rsi取值,在GDB代入暫存器要以$開頭
w , o l l e H0x402000: 0x57202c6f6c6c6548
再看看下面单个byte
x/bx $rsi也可以按enter自动递增x/bx $rsi+1...x/bx $rsi+7
然后把下一段8bytes一起印出来
x/2gx $rsi
w , o l l e H \n! d l r o0x402000: 0x57202c6f6c6c6548 0x00000a21646c726f
x/的用法是x/数量长度格式 地址
取得该内存地址的内容
长度
b 1 byteh 2 bytesw 4 bytesg 8 bytes
单位
x hexd decimalu unsign decimalo octalt binaryc chars stringi asm code
记不住没关系,数字类全用x/gx,字串则用x/s
现在试试x/s $rsi
,这行会取字串直到出现0 (00, \0, ascii 0, NUL 同义)
但是hello并没有以0结尾,所以在gdb x/s可能显示多余的内容,而实际运行结果并不影响
如何阅读内存
1 byte = 2 hex
一次看两个hex,两两一组,一条8 bytes之中每组hex从右边看到左边,每条8 bytes由左到右上到下
x/gx 一次显示8 bytes也就是8组hex,刚好是一个寄存器的完整大小
以hex格式输出的原因是易于阅读,如果用二进制输出0和1在当前64位环境难以阅读
补充资料
现代cpu的地址总线48 bits,数据总线64 bits
这表示一个指令操作比如mov,最多可以搬动8 bytes大小,刚好是rax长度
不过设计上也有别的cpu数据总线跟寄存器不一致,实际动作可能就要分两次
地址总线48 bits等于6 bytes等于12个hex
理论内存范围是0~0x0000FFFFFFFFFFFF,但这部份完全取决于操作系统
在linux用户可用的就到0x7FFFFFFFFFFF,再往上是内核使用的空间