Loops Can';t从递归循环堆栈溢出返回主进程
我试图编写一个简单的循环,它调用自己并跟踪循环次数。当我一步一步通过它时,一旦Loops Can';t从递归循环堆栈溢出返回主进程,loops,assembly,recursion,masm,irvine32,Loops,Assembly,Recursion,Masm,Irvine32,我试图编写一个简单的循环,它调用自己并跟踪循环次数。当我一步一步通过它时,一旦ECX在recursive PROC内点击0,它就会跳转到RET在L1内-我想在0之后它会返回到主程序 我的理解是,当调用一个过程时,它会将指令指针推到堆栈上,然后当调用RET时,它会将其弹出并返回到该点 ; Calls a recursive procedure ; INCLUDE Irvine32.inc .data constant DWORD 5 count DWORD ? .code main PRO
ECX
在recursive PROC
内点击0,它就会跳转到RET
在L1
内-我想在0之后它会返回到主程序
我的理解是,当调用一个过程时,它会将指令指针推到堆栈上,然后当调用RET
时,它会将其弹出并返回到该点
; Calls a recursive procedure
;
INCLUDE Irvine32.inc
.data
constant DWORD 5
count DWORD ?
.code
main PROC
mov eax, 0
mov ecx, constant
CALL recursive
main ENDP
recursive PROC
add eax, 1
loop L1
ret
L1:
CALL recursive
ret
recursive ENDP
END main
当我将
RET
从L1
中取出时,我会看到Visual S 2013中出现的“无可用源代码”页面。如果我在调用recursive
之后立即在main
中打印eax
的值,我会得到5
,这样您的代码就会返回到main
。问题是您的main
缺少ret
。即应改为:
main PROC
mov eax, 0
mov ecx, constant
CALL recursive
ret
main ENDP
由于您的程序应该在那里结束,因此您也可以使用Win32函数调用ExitProcess,0而不是
ret
初始答案的问题是,递归调用函数后,返回地址已被递归函数推送到堆栈上而隐藏。每次执行调用时,包含过程的返回地址都被推送到堆栈上,ESP被递减。如果我们一直递归地调用一个函数而不从中返回,堆栈将包含所有这些重复的地址和指向最后一次调用的ESP点
为了使ret指令有效,您需要从堆栈中弹出所有这些递归函数调用。您可以使用调试器验证此行为:
.386
.model flat,stdcall
.stack 4096
includelib Irvine32.lib
includelib User32.lib
include Irvine32.inc
.code
main PROC
mov eax,0
mov ecx,5 ; # calls to perform
call recurs
call WriteInt
INVOKE ExitProcess,0
main ENDP
; input - ecx, parameter for # function calls to perform
; uses ebx as workspace to dump junk from the stack
recurs PROC
loop L1
mov ecx,eax ; we need to keep track of recursive call count.
L2: pop ebx
loop L2
inc eax ; add 1 for the initial call to made to recurs
ret ; ret pops the address currently pointed to by
L1: inc eax ; by ESP into EIP.
call recurs
recurs ENDP
END main
要返回到
main
,您必须执行与调用CALL
s一样多的RET
s。如果继续单步执行会发生什么情况?如果您只想执行ECX pop操作,lea esp,[esp+ECX*4]
而不是在循环中执行pop
。但这不是真正的递归;您基本上是在将其优化回循环中,但仍然使用call
而不是jmp
。它不是真正的递归,除非你用ret
而不是pop
通过ret
返回一堆返回地址。(结构化编程,而不是一个longjmp
一步退出所有这些范围。)此外,必须硬编码main传递到递归函数的递归深度这一事实完全使这种方法无效:它只适用于一个汇编时间常量输入值。如果您有一个包装器函数,它将初始参数保存在某个地方供以后使用,则可以认为这是有效的。此外,您的函数版本不会在函数条目上推送任何内容,只会推送返回地址,因此我认为RECR\u COUNT*2
是不正确的;我不得不说,这是对循环
-转发和调用
的巧妙滥用,使递归和循环之间的某种东西在某种程度上起作用。在函数末尾有一个ret
,这样调用recurse
实际上可以返回而不会从末尾掉下来,向前的循环
跳转实际上可以用作正确递归函数中的减量和基本大小写检查。谢谢Peter,是的,我在RECR\u COUNT*2上犯了一个错误,我有一个uses指令试图保存寄存器状态,因为已删除。好的,现在可以了,但它仍然不是递归。从堆栈中弹出一个返回地址而不返回它并不算。您将增加EAX以跟踪递归深度(而ECX将向基本情况倒计时),但是您没有通过递归返回,而是运行了一个愚蠢的循环,而不是lea esp,[esp+eax*4}
放弃而不是使用返回地址。这就像接受异常或执行longjmp
以从多个级别的嵌套函数调用中退出,有点像回到父范围。