1.指令地址在32位上是32位,在64位上是64位

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(1)

上面是32位的,这里的x/20xw = x/20x = x/20 $esp, 都是一样的效果

再来看一下64位的

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(2)

可以看到这里虽然是0x556f7a4ab5a1,但是实际上他把前置0给隐藏了,真正的结果是0x0000556f7a4ab5a1, 可以看到是8个字节即64位,当然,如果你这里要打印内存内容的话,记得需要用x/20gx

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(3)

2.寄存器的变化

从32位的esp -> rsp(当前的栈地址), ebp -> rbp(栈基址,用来让你根据这个地址来reslove他对应的local变量以及函数参数), eip -> rip(下一个指令),需要注意每个寄存器的实际长度也从32位到了64位

3.Calling convention

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(4)

可以看到,我们打印了rsp,从栈的生长方向是从大到小,我们的函数本身是add_number(1,2),可以看到先压了1,然后压了2.这个跟32bit也是不同的,按照32bit的calling convention,会先压2,再压1

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(5)

实际上64位和32位在参数压栈这块是相同的,之前结论错在我们都用x/20gx进行打印,这个默认会按照64位8个字节进行排序,实际上,对于int来说他只有4个字节,我们需要用w来打印

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(6)

在这种情况下,我们可以看到按照栈生长方向从高到低的顺序,他先压了2,再压了1,这样就跟32bit是一样的了

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(7)

可以看到,上面的555190就是表示此时函数的return address, 因为我们在main.cpp中调用了add_number(),因此最终还是会回到main函数上的这个位置

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(8)

按照32位的说明

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(9)

会先压最后一个参数 -> 最后第二个参数 -> ... -> 第一个参数 -> return address -> current EBP -> 本函数内部创建的local variables. 64bit的可以按照这个效仿,但是参数顺序还是以第一个开始. 当把current EBP压栈之后,需要立即把rsp(esp)赋值给rbp(ebp)方便之后在拿function arguments以及local variables的时候进行查找

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(10)

也就是说压完function arguments之后,压return address,最后立马压rbp, 随后把rsp赋值给rbp, 同时把rsp(esp)进行自减扩展来继续压对应的local variables.

gdb在调试32位和64位需要注意的一些细节(gdb在调试32位和64位需要注意的一些细节)(11)

这边就比较好的反应了上面总计的规律:

  1. 标准的开栈
  2. 压rbp 将rsp给rbp 压参数 减rsp
  3. 基于rbp的相对位置进行赋值(注意增长顺序是地址越来越小)
,