文章目录
- 一、示例代码
- 二、gdb汇编指令
- 2.1 step/stepi
- 2.2 next/nexti
- 2.3 info registers
- 2.4 set
- 2.5 x
- 2.6 rip寄存器
- 2.7 rip 寄存器
- 参考资料
一、示例代码
(1)
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int main()
{
int a = 3;
int b = 5;
int c = add(a, b);
printf("c = %d\n", c);
return 0;
}
main,add函数下断点:
main函数调用 call add函数时,将下一条指令的地址0x400566压入栈中,然后跳转到函数add的地址 0x40052d处去执行。
call 指令等价于:
push 0x400566
jmp 0x40052d(add)
进入到 add 函数,在执行 ret 指令之前,然后 rsp 寄存器的内存地址中的值是 0x400566,即main函数调用add函数压入栈中的地址,执行 stepi指令,add函数执行 ret 指令,pop出该地址 0x400566 ,然后跳转到 0x400566 地址去执行指令,rip寄存器的值也是 0x400566。
ret 指令等效于:
pop 0x400566
jmp 0x400566
(2)
接下来修改一下这个返回地址,调用 add时,add返回地址应该是 0x400566,我们将其修改为0x40056c。
可以看到当在 add 调用 ret 指令后,将要执行的下一条指令的地址存放在 0x40056c 中而不是 call add 的下一条指令的地址。
二、gdb汇编指令
2.1 step/stepi
(1)step:用于单步执行程序并停在下一个源代码行,会进入函数。
使用 step 命令时,GDB 会执行当前行的代码,如果当前行包含函数调用,GDB 会跳进函数并停在函数内的第一行代码。如果当前行不包含函数调用,GDB 会停在下一行代码处。如果程序在当前行内包含多条语句,GDB 会执行完当前行内的所有语句并停在下一行代码处。
(2)stepi:以单步执行程序的汇编指令,汇编指令call 函数时,会进入函数。
用于单步执行一条汇编指令并停止。与 step 命令不同的是,step 命令会将当前源代码行(比如C源代码行)作为一个单独的步骤来执行,而 stepi 命令会将汇编指令作为执行的最小单位。
使用 stepi 命令时,GDB 会执行当前指令,并将 PC 寄存器指向下一条指令,然后停止程序的执行。如果当前指令是跳转指令,那么 stepi 命令会跳转到跳转指令的目标地址继续执行。如果当前指令是函数调用指令,那么 stepi 命令会跳转到被调用函数的入口地址,等待下一个命令。
stepi 命令是按照汇编指令执行的,而不是按照源代码行执行的。
2.2 next/nexti
next:用于单步执行程序并停在下一行源代码,不会进入函数。
使用 next 命令时,GDB 会执行当前行的代码,如果当前行包含函数调用,GDB 会跳过函数并停在函数调用的下一行代码。如果当前行不包含函数调用,GDB 会停在下一行代码处。如果程序在当前行内包含多条语句,GDB 只会执行当前行的第一条语句并停在下一行代码处。
nexti:用于执行下一条汇编指令并停止。
使用 nexti 命令时,GDB 会执行当前指令,并将 PC 寄存器指向下一条指令,然后停止程序的执行。如果当前指令是跳转指令,那么 nexti 命令会跳转到跳转指令的目标地址继续执行。如果当前指令是函数调用指令,那么 nexti 命令会将函数作为一个整体执行,并在函数返回时停止。
nexti 命令是按照汇编指令执行的,而不是按照源代码行执行的。
2.3 info registers
info registers 用于查看当前程序的寄存器状态,包括通用寄存器、浮点寄存器、协处理器寄存器等。
使用 info registers 命令时,GDB 会列出当前程序的寄存器状态,每个寄存器的名称、值和格式(十六进制或十进制)都会被显示出来。通常情况下,一般只需要查看一部分寄存器的值,可以使用 info registers 命令后面跟上寄存器名称的方式来查看指定寄存器的值。比如:
(gdb) info registers rsp
可以使用 set $register=value 命令来设置寄存器的值,以便进行调试和测试。
2.4 set
用于设置变量、内存地址、寄存器或者其他 GDB 配置选项的值。使用 set 命令可以在调试过程中修改变量的值或者更改 GDB 的配置选项,以便更好地进行调试和分析。
2.5 x
显示内存中的内容,格式如下:
x/[n][f][size] address:其中,n表示要显示的数据的数量,f表示要显示的数据的格式,size表示每个数据的大小,address表示要显示的内存地址。
f 选项可以用于指定要显示的数据的格式。下面是一些常见的数据格式:
x: 以十六进制格式显示数据。
d: 以十进制格式显示数据。
u: 以十进制无符号整数格式显示数据。
o: 以八进制格式显示数据。
t: 以二进制格式显示数据。
a: 以地址格式显示数据。
c: 以字符格式显示数据。
f: 以浮点数格式显示数据。
s: 以字符串格式显示数据。
i: 以指令格式显示数据。
size 选项可以用于指定要显示的每个数据的大小。下面是一些常见的数据大小选项:
b: 每个数据的大小为1字节(即8位)。
h: 每个数据的大小为2字节(即16位)。
w: 每个数据的大小为4字节(即32位)。
g: 每个数据的大小为8字节(即64位)。
2.6 rip寄存器
rsp 寄存器是 x86_64 架构中的一个 64 位寄存器,用于存储栈指针(Stack Pointer,SP)。栈指针是一个指向当前栈顶的内存地址的指针,用于指示下一个入栈或出栈操作的位置。
在函数调用时,rsp 寄存器会被用于存储当前函数的栈帧信息,包括函数的返回地址、参数、局部变量等。在函数返回时,rsp 寄存器会被重新设置为之前保存的值,以便恢复上一个函数的栈帧信息。
在程序执行过程中,rsp 寄存器的值会随着栈的变化而不断改变。当程序执行入栈操作时,rsp 寄存器的值会减小,指向新的栈顶位置。当程序执行出栈操作时,rsp 寄存器的值会增加,指向上一个栈顶位置。
在 GDB 中,可以使用 info registers 命令或 print $rsp 命令来查看 rsp 寄存器的值。比如:
(gdb) print $rip
$1 = (void (*)()) 0x400537 <add+10>
(gdb) info registers rip
rip 0x400537 0x400537 <add+10>
同时,可以使用 x 命令来查看栈内存的内容,例如:
(gdb) x/8xg $rsp
这样就可以以十六进制格式查看栈顶的 8 个 64 位数据。
2.7 rip 寄存器
rip 寄存器是 x86_64 架构中的一个 64 位寄存器,用于存储指令指针(Instruction Pointer,IP)。指令指针是一个指向下一条将要执行的指令的地址的指针,用于指示程序的执行位置。
在程序执行过程中,rip 寄存器的值会随着指令的执行而不断改变。当程序执行一条指令时,rip 寄存器的值会自动增加,指向下一条指令的地址。如果程序遇到跳转指令,rip 寄存器的值会被修改为跳转目标的地址,以便程序跳转到指定位置继续执行。
在 GDB 中,可以使用 info registers 命令或 print $rip 命令来查看 rip 寄存器的值。
参考资料
https://cloud.tencent.com/developer/article/1646414