IanB
12th August 2004, 07:11
Well after many torturous hours with the debugger and a disassembler I think I have finally found the cause of the infamous "Evaluate: Unrecognized exception!" in release versions of AviSynth. It appears to be an optimiser bug in the compiler. In the generated Structured Exception Handling code the compiler forgets to initialize a stack data location on entry. When no exceptions are thrown it doesn't matter, the main line code doesn't reference this local variable (probably why the optimizer omitted the code). However when processing an exception, the SEH code references the uninitialized data from the stack and misbehaves in some fashion. If it causes an access violation you get "Evaluate: Unrecognized exception!" or if you are really lucky it quietly scribbles randomly in memory and leaves a nice little bomb waiting to detonate.
How to fix it, and be sure, well that's a problem. Doing almost anything makes the problem hide. Add some debugging code, change the order or size of variables, shuffle the code. It seems debug version code is immune, so I have been testing the various compiler optimiser switches /O[awgipstyx] and disabling "Frame-Pointer Omission" with /Oy- seems to do the trick but as the generated code is so drasticly different, it just may be hiding the issue again.
Below is a sample of some dud code clipped from an ASM compiler output listing. This is how I now classify and identify the problem.
This first bit is the entry sequence, it is normal code generated into the "_TEXT" code segment. Note the stack offsets for parameters and local variables declared at the top. It is sparse, unreferenced elements are not listed, particularly note there is no -16 entry. The entry code is typical, it sets EBP as a frame pointer, chains itself onto the exception list and bumps the stack pointer to make room for the local variables and initialize them (well some at least)._TEXT SEGMENT
_clip$ = 12
_target_width$ = 16
_target_height$ = 20
_args$ = 24
_f$ = 28
_env$ = 32
___$ReturnUdt$ = 8
_vi$ = -56
_subrange_left$ = -32
_subrange_top$ = -24
_subrange_width$ = -48
_subrange_height$ = -40
_result$ = -64
_V$ = -65
$T120214 = -56
$T120215 = -56
$T120216 = -56
$T120217 = -56
$T120218 = -56
$T120219 = -56
$T120220 = -56
$T120221 = -56
$T120233 = -52
$T120348 = -40
$T120386 = -40
__$EHRec$ = -12
?CreateResize@@YA?AVPClip@@V1@HHPBVAVSValue@
@PAVResamplingFunction@@PAVIScriptEnvironment@
@@Z PROC NEAR ; CreateResize, COMDAT
; 1602 : {
push ebp
mov ebp, esp
and esp, -8 ; fffffff8H
push -1
push $L120656
mov eax, DWORD PTR fs:__except_list
push eax
mov DWORD PTR fs:__except_list, esp
sub esp, 56 ; 00000038H
push ebx
push esi
push edi
mov DWORD PTR $T120233[esp+80], 0
....This next bit is exception handling code, it is in the special "text$x" code segment.
For each object created on the stack there is an entry that fetches the appropriate "this" address into the ECX register and then jumps to its destructor. Each is called in turn from ___CxxFrameHandler as it parses the exception data table.
Note the two references to [ebp-16], the location that was never initialized this causes the destructor to do its thing on random memory, PClip's like to decrement words, just how evil is that. :devil:
text$x SEGMENT
$L120222:
mov ebp, DWORD PTR [ebp-16]
lea ecx, DWORD PTR _clip$[ebp-4]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120223:
lea ecx, DWORD PTR _result$[ebp]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120225:
lea ecx, DWORD PTR $T120215[ebp]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120227:
lea ecx, DWORD PTR $T120217[ebp]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120229:
lea ecx, DWORD PTR $T120219[ebp]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120231:
lea ecx, DWORD PTR $T120221[ebp]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120232:
mov ebp, DWORD PTR [ebp-16]
mov eax, DWORD PTR $T120233[ebp]
and eax, 1
test eax, eax
je $L120234
mov ecx, DWORD PTR ___$ReturnUdt$[ebp-4]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120234:
ret 0
$L120656:
mov eax, OFFSET FLAT:$T120615
jmp ___CxxFrameHandler
text$x ENDS
This is the exception data table, it is in the special "xdata$x" data segment. It starts at $T120615 which has a magic number. (Why is May 20, 1993 special) :D It points to 7 entries at $T120664.
xdata$x SEGMENT
$T120664 DD 0ffffffffH
DD FLAT:$L120232
DD 00H
DD FLAT:$L120222
DD 01H
DD FLAT:$L120223
DD 02H
DD FLAT:$L120225
DD 02H
DD FLAT:$L120227
DD 02H
DD FLAT:$L120229
DD 02H
DD FLAT:$L120231
$T120615 DD 019930520H
DD 07H
DD FLAT:$T120664
DD 2 DUP(00H)
DD 2 DUP(00H)
xdata$x ENDSWell where to from here.
Do we recommend /Oy- on all avisynth and filter builds?
How can we be sure /Oy- actually fixes the problem not just hides it?
IanB :confused:
How to fix it, and be sure, well that's a problem. Doing almost anything makes the problem hide. Add some debugging code, change the order or size of variables, shuffle the code. It seems debug version code is immune, so I have been testing the various compiler optimiser switches /O[awgipstyx] and disabling "Frame-Pointer Omission" with /Oy- seems to do the trick but as the generated code is so drasticly different, it just may be hiding the issue again.
Below is a sample of some dud code clipped from an ASM compiler output listing. This is how I now classify and identify the problem.
This first bit is the entry sequence, it is normal code generated into the "_TEXT" code segment. Note the stack offsets for parameters and local variables declared at the top. It is sparse, unreferenced elements are not listed, particularly note there is no -16 entry. The entry code is typical, it sets EBP as a frame pointer, chains itself onto the exception list and bumps the stack pointer to make room for the local variables and initialize them (well some at least)._TEXT SEGMENT
_clip$ = 12
_target_width$ = 16
_target_height$ = 20
_args$ = 24
_f$ = 28
_env$ = 32
___$ReturnUdt$ = 8
_vi$ = -56
_subrange_left$ = -32
_subrange_top$ = -24
_subrange_width$ = -48
_subrange_height$ = -40
_result$ = -64
_V$ = -65
$T120214 = -56
$T120215 = -56
$T120216 = -56
$T120217 = -56
$T120218 = -56
$T120219 = -56
$T120220 = -56
$T120221 = -56
$T120233 = -52
$T120348 = -40
$T120386 = -40
__$EHRec$ = -12
?CreateResize@@YA?AVPClip@@V1@HHPBVAVSValue@
@PAVResamplingFunction@@PAVIScriptEnvironment@
@@Z PROC NEAR ; CreateResize, COMDAT
; 1602 : {
push ebp
mov ebp, esp
and esp, -8 ; fffffff8H
push -1
push $L120656
mov eax, DWORD PTR fs:__except_list
push eax
mov DWORD PTR fs:__except_list, esp
sub esp, 56 ; 00000038H
push ebx
push esi
push edi
mov DWORD PTR $T120233[esp+80], 0
....This next bit is exception handling code, it is in the special "text$x" code segment.
For each object created on the stack there is an entry that fetches the appropriate "this" address into the ECX register and then jumps to its destructor. Each is called in turn from ___CxxFrameHandler as it parses the exception data table.
Note the two references to [ebp-16], the location that was never initialized this causes the destructor to do its thing on random memory, PClip's like to decrement words, just how evil is that. :devil:
text$x SEGMENT
$L120222:
mov ebp, DWORD PTR [ebp-16]
lea ecx, DWORD PTR _clip$[ebp-4]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120223:
lea ecx, DWORD PTR _result$[ebp]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120225:
lea ecx, DWORD PTR $T120215[ebp]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120227:
lea ecx, DWORD PTR $T120217[ebp]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120229:
lea ecx, DWORD PTR $T120219[ebp]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120231:
lea ecx, DWORD PTR $T120221[ebp]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120232:
mov ebp, DWORD PTR [ebp-16]
mov eax, DWORD PTR $T120233[ebp]
and eax, 1
test eax, eax
je $L120234
mov ecx, DWORD PTR ___$ReturnUdt$[ebp-4]
jmp ??1PClip@@QAE@XZ ; PClip::~PClip
$L120234:
ret 0
$L120656:
mov eax, OFFSET FLAT:$T120615
jmp ___CxxFrameHandler
text$x ENDS
This is the exception data table, it is in the special "xdata$x" data segment. It starts at $T120615 which has a magic number. (Why is May 20, 1993 special) :D It points to 7 entries at $T120664.
xdata$x SEGMENT
$T120664 DD 0ffffffffH
DD FLAT:$L120232
DD 00H
DD FLAT:$L120222
DD 01H
DD FLAT:$L120223
DD 02H
DD FLAT:$L120225
DD 02H
DD FLAT:$L120227
DD 02H
DD FLAT:$L120229
DD 02H
DD FLAT:$L120231
$T120615 DD 019930520H
DD 07H
DD FLAT:$T120664
DD 2 DUP(00H)
DD 2 DUP(00H)
xdata$x ENDSWell where to from here.
Do we recommend /Oy- on all avisynth and filter builds?
How can we be sure /Oy- actually fixes the problem not just hides it?
IanB :confused: