Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/assembly/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Recursion 额外的推送会对装配程序产生什么影响?_Recursion_Assembly_X86 64_Callstack_Att - Fatal编程技术网

Recursion 额外的推送会对装配程序产生什么影响?

Recursion 额外的推送会对装配程序产生什么影响?,recursion,assembly,x86-64,callstack,att,Recursion,Assembly,X86 64,Callstack,Att,鉴于: 以及以下在树中搜索值的汇编代码。(与代码相同) 我想知道这一变化会产生什么影响,以及它是否会导致计划无法按预期工作: 继续后立即添加推送%rdi 据我所知,这将导致一个问题,因为我们正在将一些额外的值推送到堆栈中,因此此迭代的调用方可能会弹出错误的%rdi值。例如,在这种情况下,调用方: .section .text .global _start _start: mov $8, %esi mov $A, %rdi call func movq $60,

鉴于:

以及以下在树中搜索值的汇编代码。(与代码相同)

我想知道这一变化会产生什么影响,以及它是否会导致计划无法按预期工作:

继续后立即添加
推送%rdi

据我所知,这将导致一个问题,因为我们正在将一些额外的值推送到堆栈中,因此此迭代的调用方可能会弹出错误的%rdi值。例如,在这种情况下,调用方:

.section .text
.global _start

_start:
    mov $8, %esi
    mov $A, %rdi
    call func
    movq $60, %rax
    movq $0, %rdi
    syscall
    
func:
    pushq %rbp
    movq %rsp, %rbp
    cmp (%rdi), %esi
    jne continue
    mov $1, %eax
    jmp finish
    
continue: # go left
    cmpq $0, 4(%rdi)
    je next
    pushq %rdi # 3
    mov 4(%rdi), %rdi
    call func
    pop %rdi # 4
    cmp $1, %eax
    je finish

next: # go right
    cmpq $0, 12(%rdi)
    je fail
    pushq %rdi # 1
    mov 12(%rdi), %rdi
    call func
    pop %rdi # 2
    cmp $1, %eax
    je finish
   
fail:
    mov $0, %rax

finish:
    leave
    ret
可能会弹出12+%rdi而不是弹出%rdi,但我运行了许多测试,它们似乎都在RAX中返回了正确的值,这是为什么


注意:此行是否也会导致堆栈溢出?我想答案可能是肯定的。

请注意尾声中的提示:它在
ret
之前恢复RSP,撤消任何额外的推送。如果没有,额外的推送会使函数崩溃(例如,将RDI的副本弹出到RIP中),无法成功返回,但RSP指向错误的位置


可能会弹出12+%rdi而不是弹出%rdi

不,那是不可能的。推送发生在RDI被修改之前,因此您正在推送原始值,而pop会将其读回<代码>呼叫对RSP没有净影响,即RSP保留呼叫。(如果你破坏了你的堆栈,
ret
会弹出错误的东西,而不是返回地址,因此你会崩溃而不是返回。因此,除非你有意将返回地址复制到其他地方,否则你无法在修改RSP的情况下返回)

此外,RDI+12处的内存内容与RDI+12不同。如果你的字面意思是
pop12(%rdi)
(即弹出到内存中),那么显然不是
pop%rdi
始终写入寄存器,无论弹出什么数据


正如我刚才在你发布这篇文章时所评论的那样,在结束语中掩盖了任何不平衡推送的问题(无论是添加额外推送,还是删除pop),只要实际需要将正确的值放入正确寄存器的推送/弹出操作仍然存在。(在第一次
呼叫时保存RDI
)。您的函数在第二次
调用之后不需要再次使用节点指针,因此这是毫无意义的,最好是
离开
/
mov 12(%rdi),%rdi
/
jmp func
。但是在第二次呼叫之前的任何额外推送都无关紧要

唯一会出现问题的是一个额外的
pop
,它会在最后一次
调用之前从堆栈中删除您的返回地址,因此它会被覆盖。(最后一次调用后的不平衡
pop
将使您的返回地址位于RSP下方,但在x86-64 System V ABI的用户空间中这是安全的,因为它保证RSP下方有一个红色区域,可以安全地避免被信号处理程序等异步攻击。因此
leave
仍将RSP指向它应该位于的位置,让
ret
弹出返回地址

记住,
leave
相当于
mov%rbp,%rsp
/
pop%rbp
,因此它会为自身和以后的堆栈操作重置rsp。

递归
调用之前的任何额外推送都意味着每个堆栈深度使用8个额外字节的堆栈空间,但Linux用户空间堆栈默认为8MiB,因此需要相当深的树才能接近实际溢出


在调试器(如GDB)中单步执行代码,以查看其运行方式

使用
display/x*(long(*)[5])$rsp
在每一步之后从堆栈中转储前5个qwords(通过将其转换为指向数组的指针并取消引用)。然后
stepi
浏览您的代码,看看它是如何变化的,尤其是在
离开时。请注意尾声中的注意:它在
ret
之前恢复RSP,撤消任何额外的推送。如果没有,额外的推送会使您的函数崩溃(例如,将RDI的副本弹出到RIP中),未成功返回,但RSP指向错误的位置


可能会弹出12+%rdi而不是弹出%rdi

不,这是不可能的。推送发生在修改RDI之前,因此您正在推送原始值,然后pop将其读回。
调用
对RSP没有净影响,即RSP保留调用。(如果你破坏了你的堆栈,
ret
会弹出错误的东西,而不是返回地址,因此你会崩溃而不是返回。因此,除非你有意将返回地址复制到其他地方,否则你无法在修改RSP的情况下返回)

此外,RDI+12处的内存内容与RDI+12不同。如果您的字面意思是
pop 12(%RDI)
(即pop into memory),则显然不是这样;
pop%RDI
总是写入寄存器,而不管弹出什么数据


正如我刚才在你发布这篇文章时所评论的那样,在结束语中掩盖了任何不平衡推送的问题(无论是添加额外推送,还是删除pop),只要实际需要将正确的值放入正确寄存器的推送/弹出操作仍然存在。(在第一次
调用前后保存RDI
)。在第二次
调用之后,您的函数不需要再次使用节点指针,因此这是毫无意义的,最好只
离开
/
mov 12(%RDI),%RDI
/
jmp func
。但在第二次
调用之前的任何额外推送都无关紧要

唯一会出现问题的是一个额外的
pop
,它会在最后一次
调用之前从堆栈中删除您的返回地址,因此它会被覆盖。(最后一次呼叫后的不平衡
pop
会使您的回信地址低于RSP,但在x86-64系统V ABI的用户空间中这是安全的,因为它保证RSP下方的红色区域是安全的。)
.section .text
.global _start

_start:
    mov $8, %esi
    mov $A, %rdi
    call func
    movq $60, %rax
    movq $0, %rdi
    syscall
    
func:
    pushq %rbp
    movq %rsp, %rbp
    cmp (%rdi), %esi
    jne continue
    mov $1, %eax
    jmp finish
    
continue: # go left
    cmpq $0, 4(%rdi)
    je next
    pushq %rdi # 3
    mov 4(%rdi), %rdi
    call func
    pop %rdi # 4
    cmp $1, %eax
    je finish

next: # go right
    cmpq $0, 12(%rdi)
    je fail
    pushq %rdi # 1
    mov 12(%rdi), %rdi
    call func
    pop %rdi # 2
    cmp $1, %eax
    je finish
   
fail:
    mov $0, %rax

finish:
    leave
    ret
    pushq %rdi # 1
    mov 12(%rdi), %rdi
    call func
    pop %rdi # 2