gdb调试查看CALL指令的压栈情况

gdb调试查看CALL指令的压栈情况

CALL指令时压栈

有这样一段代码,功能是输出字符串,代码如下:

section .data
strHello db "Hello World!", 0Ah
STRLEN equ $-strHello

section .text

global _start
_start:
    push STRLEN
    push strHello
    call print

mov ebx, 0
mov eax, 1
int 0x80

print:
	mov ecx, [esp+4] ; 为什么是4,为什么不直接pop ecx
    mov edx, [esp+8] ; 为什么是8,为什么不pop edx
	
	; 发起sys_write()系统调用,输出信息
    mov ebx, 1  
    mov eax, 4
    int 0x80
    ret

上述代码,把输出字符串的代码放到一个子程序print中,然后在调用print子程序时,字符串的地址和字符串的长度通过栈来传递。

粗略地一看很简单也没毛病,在call print前,push STRLENpush strHello是把字符串长度和字符串的地址使用压栈的方式来传递参数,然后在print子程序中从栈中取出参数,就完成了参数的传递。栈内存就如下分部:

image-20210317233437275

但看到print子程序代码时,不禁一想,参数压栈了,取参数STRLENstrHello时为什么不直接pop ecxpop edx呢?

然后又看了一下取参数的下标,代码中是mov edx, [esp+8]mov ecx, [esp+4]strHello不是应该在栈顶吗,不应该是直接取吗?

当查看了课本之后,才发现,在使用call指令调用子程序时,相当于修改了程序走向,要修改ip的值,因此需要把原来ip的值压栈保存,当子程序调用完毕后在把ip的值恢复。

image-20210317233831607

所以,在call print前,ip会指向mov ebx, 0(call指令的下一个指令),那么当执行call指令,就会把这个地址压栈,也就是栈空间分布其实是这样的:

image-20210317234604703

可是书上,写的是“相当于”,是真的就是把ip的值压栈吗?是指SS:SP那个栈吗?

所以我想在程序运行中验证下栈的数据,确认下当call指令执行完,栈顶是不是那个原先的mov ebx, 0(call指令的下一个指令)的地址。

使用gdb调试验证CALL指令压栈情况

这里再贴一边代码:

section .data
strHello db "Hello World!", 0Ah
STRLEN equ $-strHello

section .text
global _start
_start:
    push STRLEN
    push strHello
    call print

mov ebx, 0
mov eax, 1
int 0x80

print:
	mov ecx, [esp+4] ; 为什么是4,为什么不直接pop ecx
    mov edx, [esp+8] ; 为什么是8,为什么不pop edx
	
	; 发起sys_write()系统调用,输出信息
    mov ebx, 1  
    mov eax, 4
    int 0x80
    ret

将上述的汇编代码生成可执行程序(可调试的,使用参数-g指定):

image-20210317235622118

输入gdb hello进入gdb调试,对hello进行调试。

进入gdb调试页面,输入b _startb print,在标号为_startprint的地方打上断点,如下图:

image-20210317235945972

然后查看输入r,表示run,然后程序会执行到断点_start处停止,如下图:

image-20210318000155750

查看_start标号断点执行前的情况
查看反汇编代码

此时,就可以查看_start标号的子程序代码块的内容了,输入disassemble,将此处内存的机器码反汇编,查看每一条汇编指令所在的内存地址。

image-20210318000443740

上图的反汇编代码对应的源代码就是:

image-20210318000742609

可以看到:

  1. STRLENstrHelloprint等标号都被替换成了具体的数值。
  2. 每一行指令的地址

可知:

  1. STRLEN = 0xd = 13,数一数代码中字符个数也正好是13;这个数在调用print时会被压栈。
  2. strHello表示字符串的地址,该地址是0x804a000,这个地址数值在调用print时会被压栈。
  3. call指令的下一条指令(mov ebx, 0)的地址是0x0804900c
查看print标号断点执行前的情况

执行到print标号处,是call print指令已经执行完毕了,已经进入print子程序了,ip已经指向了print子程序第一条指令,该指令欲执行。

也就是此时,进入print子程序了,_start程序中的下面的三条指令都执行完毕:

push STRLEN
push strHello
call print
查看栈段的内容

此时查看栈段的值,要想知道栈的内容,要先知道esp的值:

image-20210318002536727

上图可知,栈顶指针的地址是0xffffce94,由于栈是往下走的,因此栈顶在低地址,栈底在高地址。

所以,查看地址0xffffce94处连续2个word(1 word = 4 byte)和1个h(1 h = 2 byte),因为在压栈时,STRLEN应该是2个字节,strHello是一个地址占4个字节,压入旧的eip的值也是4个字节。所以预想的栈内存分布图应该如下:

image-20210318003716880

所以接下来只需验证下栈的内容和图中预想是不是一样的即可。

gdb中查看栈的内容(这里为了方便,直接看栈顶连续三个word的内容算了):

x/3xw 0xffffce94

注:0xfffce94是栈顶指针ESP的值,上面图中有。

栈中内容显示结果如下:

image-20210318004419371

可知,此时实际内存中,栈的内容应该是这样的:

image-20210318004827233

在看看上面提到的_start标号处汇编指令执行过程中的地址情况:

image-20210318000443740

栈中的内容,和我们对CALL指令压栈的猜想完全一致。

结论

所以最终结论是,在调用call 子程序指令时,会把call执行的下一行指令的地址压栈保存,并且该栈就是进行数据操作的栈,所以在操作栈时要小心,不光自己使用pushpop指令会修改栈的内容,在使用call指令也会修改栈的内容

因此在CALL指令调用子程序,使用栈传递参数时,入参可以push 参数,取参数时可不能直接pop,因为栈顶并不是刚才传进去的参数,而是原始ip的值!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值