News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

RETRY last function

Started by fearless, April 21, 2009, 01:04:18 AM

Previous topic - Next topic

fearless

Ive been trying to code a function in asm that is similar to the RETRY command in Visual Foxpro. The RETRY command in VFP allows the programmer to execute the previous command that last failed. This can be called within the error handler (VFP sets limits to amount of retries as nAttempts or nSeconds to try again)

Ive tried to implement this as a global error handler in my library code which allows the user to specify there own user error handler which passes back to the global one if nothing is processed.  So i though about saving the state of the registers, then if the user invokes XRetry in their error handler, i could simply load the info back into the registers and call the original function to give it a go again. If the count of nested errors is too high (triggered by successive retries) then i finally quit with a message.

Ive done some searching on stacks, ebp and esp as i think the main problem is with these settings. So im wondering if anyone has any ideas on the following:

Can i save all reg info at start of a function? (ive tried with a macro with partial success - ebp, esp not same as before function call)
Will saving the reg info be enough? (as i suspect i shouldnt be saving the stack info in any case)
Am i going about calling the original function in the correct manner or is there something i have overlooked and/or not taken into account?

Any help or ideas on where i might be going wrong would be greatly appreciated.

How it works:

InvokeXOnError, Offset MyErrorHandler user calls this in their code to set their error handler, and has a error handler proc like so:

ErrHandler PROC ErrorNo:DWORD, ErrMessage:DWORD

PrintDec ErrorNo

.IF ErrorNo == 3 ; File in use
Invoke MessageBox, NULL, CTEXT("We caught a file in use error"), CTEXT("ErrHandler"),MB_OK
Invoke XRetry
.ELSE

.ENDIF

mov eax, FALSE
ret

ErrHandler endp


Here is my code so far for the error handler:

The ERRSAVERETRYINFO macro would be placed inside a function at start, and say when a file is opened exclusively in that function and is already open by another program exclusively, and we have received INVALID_HANDLE_VALUE we call _XE_GlobalErrorHandler with our error no and message and if it should kill program if critical error, or maybe give user a chance to filter through with their error handler first.


;=====================================================================================
; Error 'X' procedures
;=====================================================================================

XOnError PROTO :DWORD
XRetry PROTO

;=====================================================================================
; Error Internal Procedures
;=====================================================================================
_XE_GlobalErrorHandler PROTO :DWORD, :DWORD, :DWORD


ERRSAVERETRYINFO MACRO
push eax
call @F
@@:
pop eax
sub eax, 5 ;value of eip in eax - i think this is wrong

mov _OnErrorRetry.RegEip, eax
pop eax

mov _OnErrorRetry.RegEax, eax
mov _OnErrorRetry.RegEbx, ebx
mov _OnErrorRetry.RegEcx, ecx
mov _OnErrorRetry.RegEdx, edx
mov _OnErrorRetry.RegEdi, edi
mov _OnErrorRetry.RegEsi, esi

PrintText 'ERRSAVERETRYINFO'
PrintDec _OnErrorRetry.RegEax
PrintDec _OnErrorRetry.RegEbx
PrintDec _OnErrorRetry.RegEcx
PrintDec _OnErrorRetry.RegEdx
PrintDec _OnErrorRetry.RegEdi
PrintDec _OnErrorRetry.RegEsi
PrintDec _OnErrorRetry.RegEip
ENDM


.CONST

XBASEONERRORRETRY STRUC
RegEax dd 0
RegEbx dd 0
RegEcx dd 0
RegEdx dd 0
RegEdi dd 0
RegEsi dd 0
RegEbp dd 0
RegEsp dd 0
RegEip dd 0
RegFlags dd 0
XBASEONERRORRETRY ENDS

.DATA
_OnErrorHandler dd 0 ; location of users ON ERROR handler
_InErrorHandler dd 0 ; Count for nested error handling calls
_OnErrorRetry XBASEONERRORRETRY <>
ERR_NESTED_ERRORS db "Too Many Errors Generated In User Defined Error Handler. Quitting.",0

.DATA?

.CODE

XOnError PROC lptrOnErrorHandler:DWORD
mov eax, lptrOnErrorHandler
mov _OnErrorHandler, eax
ret
XOnError endp


_XE_GlobalErrorHandler PROC ErrorNo:DWORD, ErrorMessage:DWORD, lCriticalShutdown:DWORD

PrintDec _OnErrorHandler

.IF _OnErrorHandler == 0 ; Use default global error handler
invoke MessageBox, NULL, ErrorMessage, ADDR _XbaseLibrary, MB_OK+MB_ICONSTOP+MB_TASKMODAL+MB_TOPMOST
.IF lCriticalShutdown == TRUE ; If we need to stop running program due to critical nature
Invoke ExitProcess, 0
.ENDIF
.ELSE

; Get users errorhandler and call it
inc _InErrorHandler
.IF _InErrorHandler > 10
invoke MessageBox, NULL, Addr ERR_NESTED_ERRORS, ADDR _XbaseLibrary, MB_OK+MB_ICONSTOP+MB_TASKMODAL+MB_TOPMOST
Invoke ExitProcess, 0
ret
.endif
mov eax, _OnErrorHandler
PrintText 'Calling ErrorHandler'

push ErrorMessage
push ErrorNo
call eax ; call users error handler

.IF eax == TRUE ; if user returned true then try to continue onwards
dec _InErrorHandler
mov eax, TRUE
ret
.ELSE ; else we are forced to show default message and close down

PrintText 'Final message before quitting'
invoke MessageBox, NULL, ErrorMessage, ADDR _XbaseLibrary, MB_OK+MB_ICONSTOP+MB_TASKMODAL+MB_TOPMOST
Invoke ExitProcess, 0
ret
.endif
.ENDIF
ret
_XE_GlobalErrorHandler endp


XRetry PROC

.IF _InErrorHandler > 10
PrintText 'Not Retrying, too many errors, so fall out and let Global handler show message and quit'
mov eax, FALSE
ret

.ELSE
PrintText 'Retry'
; recall saved retry info
mov eax, _OnErrorRetry.RegEax
mov ebx, _OnErrorRetry.RegEbx
mov ecx, _OnErrorRetry.RegEcx
mov edx, _OnErrorRetry.RegEdx
mov edi, _OnErrorRetry.RegEdi
mov esi, _OnErrorRetry.RegEsi
PrintDec _OnErrorRetry.RegEip
call _OnErrorRetry.RegEip ; call the original function that we want to retry again.
.endif
mov eax, TRUE
ret
XRetry endp

ƒearless

fearless

Managed to make some progress.

I was able to save the stack information, and most importantly the variables as passed to the original procedure and store them in a data structure, then when the retry function is called i was able to recall them and push them onto the stack in the correct order and then call the original functions offset. The global handler then triggered again until it reached its max reprocess level (10 currently) and then defaulted to the original error message before quitting.

Im still concerned that perhaps by saving the stack information and pushing additional information onto the stack before a call that i might later on in the program cause a crash - further testing will show me i suppose if this is the correct way to proceed. a SEH for stack overflow is probably my next bit of research - just in case  :wink
ƒearless

Tedd

Nice idea, but it only works if you deny global state information (or save everything, which is nuts.)
If any of the functions you call (or functions they call, etc) modify any global variables, then you're in for trouble. And you have to consider any other state information on the stack (e.g. other functions' local stack variables.)
No snowflake in an avalanche feels responsible.

fearless

Thats true Tedd, ive got it working to the point of triggering and catching as outlined above, but i have that nagging thought that something would be lost in a future call which would mean that program would throw out unexpected results or crash. I will continue to work on it, to test out the impact of this possiblity and include it in my library once im happy with it, or not if it looks too problematic.
ƒearless