概述
第一步:使用vim编写一个简单的C程序helloworld.c
第二步:使用gcc命令编译文件
这里可以使用cc helloworld.c生成一个默认的a.out文件
(若要自定义生成文件名称,使用gcc -o filename helloworld.c,如下图,其他操作完全相同)
我们运行一下a.out文件,看一下效果
第三步:开始调试
1>在编译选项中加入-g属性重新编译,使生成的a.out文件能够支持调试
2>执行gdb(GDB, the GNU Project debugger, allows you to see what is going on `inside' another program while it executes)
执行完后会有一段提示:
显示(gdb)开头就说明进入了调试模式
生词:warranty(a written guarantee, issued to the purchaser of an article by its manufacturer, promising to repair or replace it if necessary within a specified period of time.)
3>设置断点:
有两种写法
写法1:对函数进行调试
这里又有两种格式:
格式1: break function_name
格式2: break filename : function_name
写法2:对某一行进行调试(需要在vim中的命令模式下设置set nu命令显示行号)
这里也有两种格式:
格式1: break line_number
格式2: break filename : line_number
这里采用使用行号的方式添加断点:
首先退出调试模式,进入hello.c查看行号
vim hello.c进入源文件,使用set nu命令显示行号,找到我们要调试的某一行
4>设置断点,在这里,尝试在第2,3行设置断点
发现提示只有在for,while,utill循环中,break设置断点才有意义
vim打卡hello.c,加一个for循环
之后使用cc -g指令重新编译hello.c(重复步骤1-4)
再经过以上重复步骤之后,可以设置断点在第4行处
5>执行调试,输入r [args] 开始调试(args为可选参数)
6>监视变量值,使用 print variable_name 语句监视相应的变量
7>后续操作(常用)
类型 对应指令
print variable/address/register_name(打印变量/地址/寄存器名) p expression / print expression
step over(执行下一行,如果是函数,执行完该函数) n / next
step in(单步进入,进入函数体内部,一条条语句执行) s / step
continue(快进,跳转到下一个断点) c / continue
show code for 10 lines(从第1行开始,每调用一次展示10行代码) l / list
exit(退出调试) q / quit
help(显示帮助) h / help
show stack info(展示程序运行栈信息) bt / backtrack
delete breakpoint(删除断点) clear lineNumber
寄存器级别监视:
info register(显示寄存器信息) i f / info register (不是if 是 i f)
examine n data in format f of size s(查看内存,n条结果,f进制,每条s字节) x / nsf address (一条完整的命令, / 并不表示或)
x nfs address 命令参数说明
n : 一个正整数: 例如 1, 4, 6, 9 ...
f : 一个代表进制的符号 : 支持2/8/10/16进制 以及浮点数/字符格式
s : 一个代表每次读取几个字节的符号 : 有bwhg四种
nfs顺序问题: n必须放在前面,fs的顺序随意
参数f(format)的详细说明 :
16进制(hex):
x/a : 16进制显示变量
u : 16进制显示无符号整型
10进制(decimal):
d : 10进制显示变量
8进制(octal):
o : 8进制格式显示变量
2进制(binary):
t : 2进制格式显示变量(个人猜测是tree的缩写,而tree最常见的就是binary tree)
字符格式(char):
c : 字符格式显示变量
浮点格式(float):
f : 浮点数格式显示变量
参数s(size)的详细说明:
s对应c语言中一个很著名的内置函数sizeof,s代表的就是字节数bytes,一个字节byte规定是8个位bit
如果不指明,则默认为4bytes
参数列表:
b : 8 bits 单字节(就是一个字节)
w : 4 bytes 四字节(刚好四根线,形象)
h : 2 bytes 双字节
g : 8 bytes 八字节
使用示例:
创建一个字符数组进行检验:
x / 4bt &charStr[0] (当然这里写成charStr效果相同)
命令解释:
x / 开始读取内存操作
参数解释:
从charStr地址[参数&charStr]开始 向后每次读取半个字节(4 bits)[参数b] 以2进制方式显示[参数t] 一共读取4次[参数4]
按照上面的步骤1-4进行编译,添加断点(这里发现不是循环的行也可以添加断点,不知道之前为什么报错)
编译:
gdb命令 list显示代码 break n 在代码第n行添加断点
添加断点,在第3,4行,然后输入r开始Debug
这里已经按照 x/nsf address的格式对内容进行了输出,从地址0xbffff327开始向后 读取4个单位 每个单位一个字节 以2进制输出
可以使用16进制输出,使得计算分析更加方便,即将x/4bt address 转化为 x/4bx address
可以从上图看到四个连续字节内存对应数值分别为:
0x00 -> 0
0x99->153
0x84->132
0x04->4
这显然不符合"abcd"四个字符中的任何一个的ascii码,'a'的ascii码位97,'b'的ascii码位98,'c'的ascii码为99,'d'的ascii码位100
原因很简单,断点到这里,还没有运行,这里面的值是系统开始随机分配的值
执行(gdb) n进入下一行指令,执行当前指令,就可以完成指令
所以执行n命令之后,就可以看到赋值完成:
0x61->97 -->ascii 'a'
0x62->98 -->ascii 'b'
0x63->99 -->ascii 'c'
0x64->100 -->ascii 'd'
为了更直观,可以采用直接以字符的形式输出(f参数使用c)
比较容易弄混的是小端规则"高高低低"在这里貌似不成立,明明是从0xbffff327(低地址)开始到地址0xbffff32a(高地址),按照小端机器的"高高低低"的规则("高高低低"的解释:对于一个数据,例如0x12345 (16) => 10进制 1*16^4 + 2*16^3 + 3*16^2 + 4*16^1 + 5*16^0 认为左边权值大,所以是数据位的高位,右边的数据为低位)应该是高地址放高数据位数据(在字符数组中,数据高位应该是最左边的'a'字符),也就是应该在0xbffff32a这个地方放字符'a',但是使用gdb调试输出却可以发现内存的高位0xbffff32a处存放的确是字符'd'
有这种想法,其实是"高高低低"的规则没有真正理解,"高高低低"是对于一个基本数据类型而言的,在char字符数组中,基本的数据类型是char型,每个字符是一个独立的意群,自然不存在这个问题,但如果在一个int类型的内存中写入四个字符'a','b','c','d',就需要考虑这个问题,因为此时机器解码的时候,翻译自然以int整型这样一个数据类型来翻译,如果是个人电脑(小端机器),此时按照"高高低低",高地址放高有效位数据(如果想要输出的效果是"abcd"那么'a'对应最高有效位,'d'对应最低有效位),此时就应该将'a'字符放在高内存中,将'd'字符放在低内存中,也就是应该写成下面这样:
int arr[2] = {(('d')<<24+('c')<<16+('b')<<8+('a')),('