Author Topic: SetUnhandledExceptionFilter  (Read 286 times)

JK

  • Member
  • **
  • Posts: 132
SetUnhandledExceptionFilter
« on: September 14, 2021, 05:55:34 AM »
While in 32 bit this works as expected, it doesn´t in 64 bit:
Code: [Select]
include <windows.inc>
includelib KERNEL32.LIB
includelib USER32.LIB


.data

caption db "Test", 0
txt     db "inside handler", 0


.code

gpf_handler proc x:ptr
;***************************************************************************
; (simpilfied) gpf handling here
;***************************************************************************


  MessageBox(0, addr txt, addr caption, 0)
  ExitProcess(1)
 

gpf_handler endp


;***************************************************************************


start proc
;***************************************************************************
; main
;***************************************************************************


  SetUnhandledExceptionFilter(offset gpf_handler)

  xor eax, eax
  mov eax, [eax]                                      ;gpf


start endp


;***************************************************************************


end start

That is, in 32 bit the exception is caught (i get the MessageBox), while in 64 bit the gpf_handler is never reached (the debugger pops up instead at: mov eax, [eax]). I suspect i don´t have the correct commandline options for ASMC and Linker (link.exe from MS). This is, what i use currently (64 bit):
Code: [Select]
asmc.exe /Fl /c /Zp16 /win64 /Cp /W0 /I ...
LINK.EXE /SUBSYSTEM:WINDOWS /RELEASE /VERSION:4.0 /MACHINE:X64 /LIBPATH: ...


What are the correct options for 64 bit? I once had a similar problem in C with the GNU compiler/linker, adding a linker option ("unwind-tables" or something like that) solved it.

Thanks

JK



nidud

  • Member
  • *****
  • Posts: 2215
    • https://github.com/nidud/asmc
Re: SetUnhandledExceptionFilter
« Reply #1 on: September 14, 2021, 08:28:00 AM »
There's a problem there as the option frame:auto is set by the include file (if you use the Asmc includes that is).

The command line switch /frame adds unwind information for all proc's. However, this sets the frame value to 3 (same as option frame:add) but this is reset by the option above (auto = 1).

Note that OFFSET should not be used in 64-bit, at least not in INVOKE, so use ADDR there instead. As for the sample it should work by adding option frame:add (after include) or add FRAME to the proc.

start proc FRAME

  SetUnhandledExceptionFilter(ADDR gpf_handler)

You may also add the function as an argument to FRAME. The sample below creates an excepton if argument(s) are used.
Code: [Select]
include windows.inc

catch proto signo:int_t {
__except:
    mov eax,signo
    .if eax
        lea rax,@F
        mov [r8].CONTEXT._Rip,rax
        xor eax,eax
        retn
        @@:
        mov eax,1
    .endif
    }

.code

main proc frame:__except argc:dword

    xor eax,eax
    .if ecx > 1
        mov eax,[rax] ; gpf
    .endif
    MessageBox(0, "No Exception", "Catch", 0)

    .if catch(0)

        MessageBox(0, "Exception error: argc used", "Catch error", 0)
    .endif
    ExitProcess(0)
    ret

main endp

    end

nidud

  • Member
  • *****
  • Posts: 2215
    • https://github.com/nidud/asmc
Re: SetUnhandledExceptionFilter
« Reply #2 on: September 14, 2021, 10:29:45 PM »
Added a fix for the frame issue, made the command line option "sticky" so it will override the frame:auto option.

This means you don't need to add FRAME to a proc unless additional arguments is added. A custom handler may then be used like this.
Code: [Select]
include windows.inc

define CUSTOM_CODE 0x87654321

    .code

CustomFilter proc pointers:ptr EXCEPTION_POINTERS

    mov rcx,[rcx]
    .if ( [rcx].EXCEPTION_RECORD.ExceptionCode == CUSTOM_CODE )

        MessageBox(0, "Handling custom exception", "Inside exception filter", 0)
       .return EXCEPTION_CONTINUE_EXECUTION
    .endif
    MessageBox(0, "Allowing handler to execute", "Inside exception filter", 0)
   .return EXCEPTION_EXECUTE_HANDLER

CustomFilter endp

wWinMain proc hInstance:HINSTANCE, hPrevInstance:HINSTANCE,
        lpCmdLine:LPTSTR, nShowCmd:SINT

    SetUnhandledExceptionFilter(&CustomFilter)
    RaiseException(CUSTOM_CODE, 0, 0, NULL)
    ret

wWinMain endp

wWinStart proc

    ExitProcess(wWinMain(NULL, NULL, NULL, 0))

wWinStart endp

    end wWinStart

This will work with LINK and LINKW but the internal linker (/pe) don't handle FRAME so it is somewhat convenient to have a command line switch for this.

asmc64 /ws /frame test.asm
link /libpath:%AsmcDir%\lib\x64 /machine:x64 test.obj

JK

  • Member
  • **
  • Posts: 132
Re: SetUnhandledExceptionFilter
« Reply #3 on: September 15, 2021, 01:18:50 AM »
Thanks nidud for the fix and the explanation!

It works like a charm now - really nice.

BTW asmc.chm coming from your repo seems to be outdated, because in your repo i finally found docs about option frame, but in asmc.chm i couldn´t.

JK

JK

  • Member
  • **
  • Posts: 132
Re: SetUnhandledExceptionFilter
« Reply #4 on: September 15, 2021, 02:52:19 AM »
Alas, nesting it one level deeper, it stops working - why?
Code: [Select]
include <windows.inc>
includelib KERNEL32.LIB
includelib USER32.LIB

option frame:add

.data

caption db "Test", 0
txt     db "inside handler", 0


.code

gpf_handler proc x:ptr
;***************************************************************************
; (simpilfied) gpf handling here
;***************************************************************************


  MessageBox(0, addr txt, addr caption, 0)
  ExitProcess(1)
 

gpf_handler endp


;***************************************************************************


start proc
;***************************************************************************
; main
;***************************************************************************


  SetUnhandledExceptionFilter(addr gpf_handler)

  xor eax, eax
  mov eax, [eax]                                      ;gpf


start endp


;***************************************************************************

do_start proc
;***************************************************************************
;
;***************************************************************************
 
  invoke start
 

do_start endp


;***************************************************************************


;end start                                             ;-> this works
end do_start                                          ;-> this doesn´t

nidud

  • Member
  • *****
  • Posts: 2215
    • https://github.com/nidud/asmc
Re: SetUnhandledExceptionFilter
« Reply #5 on: September 15, 2021, 03:43:55 AM »
Try this:

  invoke start
  ret

JK

  • Member
  • **
  • Posts: 132
Re: SetUnhandledExceptionFilter
« Reply #6 on: September 15, 2021, 04:31:04 AM »
Yes, of course "ret" is missing, but adding it in the appropriate places doesn´t help either - sorry

JK

  • Member
  • **
  • Posts: 132
Re: SetUnhandledExceptionFilter
« Reply #7 on: September 15, 2021, 04:50:35 AM »
This one works:
Code: [Select]
do_start proc uses RSI

Simply adding a "uses" clause to the entry procedure makes it work again - strange

nidud

  • Member
  • *****
  • Posts: 2215
    • https://github.com/nidud/asmc
Re: SetUnhandledExceptionFilter
« Reply #8 on: September 15, 2021, 05:00:06 AM »
If you don't use the Asmc includes option win64:3 should be added.
Code: [Select]
include <windows.inc>
includelib KERNEL32.LIB
includelib USER32.LIB

option win64:3
option frame:add

.data

caption db "Test", 0
txt     db "inside handler", 0

.code

gpf_handler proc x:ptr

  MessageBox(0, addr txt, addr caption, 0)
  ExitProcess(1)

gpf_handler endp


start proc

  SetUnhandledExceptionFilter(addr gpf_handler)

  xor eax,eax
  mov eax,[rax] ;gpf
  ret

start endp

do_start proc

  invoke start
  ret

do_start endp

end do_start

I also added [rax] and ret to start (works without thought).

asmc64 test2.asm
link /libpath:\asmc\lib\x64 /subsystem:windows /machine:x64 test2.obj

JK

  • Member
  • **
  • Posts: 132
Re: SetUnhandledExceptionFilter
« Reply #9 on: September 15, 2021, 07:19:38 AM »
Thanks for your help!

I am using the ASMC includes and option win64:5. This setup seems to require an "uses" clause. With option win64:3 it indeed works without "uses".

For certain reasons i would sometimes like to avoid including option 2 in win64, but with win64:3 it is included. Is there a specific reason, why win64:5 obviously needs "uses" for option frame:add to work properly and win64:3 doesn´t?

So as a workaround for me (win64:5) i could just always add "uses" to the main procedure, is this safe then?

JK

nidud

  • Member
  • *****
  • Posts: 2215
    • https://github.com/nidud/asmc
Re: SetUnhandledExceptionFilter
« Reply #10 on: September 15, 2021, 08:21:16 AM »
I am using the ASMC includes and option win64:5. This setup seems to require an "uses" clause. With option win64:3 it indeed works without "uses".

The FRAME attributes demands a balanced stack so it wont work otherwise.

Quote
For certain reasons i would sometimes like to avoid including option 2 in win64, but with win64:3 it is included. Is there a specific reason, why win64:5 obviously needs "uses" for option frame:add to work properly and win64:3 doesn´t?

So as a workaround for me (win64:5) i could just always add "uses" to the main procedure, is this safe then?

No, it's just luck. If you add another register, argument, local, or another level the stack will be misaligned again. The shadow space will most likely be wrong (or missing) and thus the unwind information will also be wrong.

So there's a lot of things to keep track of if the stack is not balanced, especially when using frame attributes as the implementation itself, assuming a balanced stack, makes stack adjustments based on this assumption.

JK

  • Member
  • **
  • Posts: 132
Re: SetUnhandledExceptionFilter
« Reply #11 on: September 16, 2021, 12:01:07 AM »
Sorry, i should have given more information. My options in such a case are:
Code: [Select]
  OPTION STACKBASE : RBP
  option win64:5
  option cstack:on
  option frame:auto 

I don´t use a custom Prologue/Epilogue, but at procedure entry after defining locals the stack is 16 bit aligned (and rsp, -16). This works perfectly well so far.

I know and accept, that this is not the best approach, but for reasons i don´t want to discuss here, i want it that way in some cases.

The only drawback so far i had, is that SEH isn´t working even if i change option frame:auto to option frame:add. It works, if i add a uses clause (e.g. uses rsi) to the main procedure (the one called with the end statement).

So having or not having "uses" there makes it work or not.

This raises two questions:
- As a workaround for me (win64:5) i could just always add "uses" to the main procedure, is this safe then (with the options mentioned above)?

- Why does having or not having "uses" make a difference at all? Shouldn´t it work without "uses" in both cases (win64:5/win64:3)?


My ultimate goal is not to be able to actually unwind stackframes in the gpf_handler procedure, i just want to be able to catch GPFs throughout the program and signal this by leaving with ExitProcess(1) instead of ExitProcess(0), which signals a graceful termination.

Thanks

JK


added later: further testing shows, that "uses" is indeed not reliable, but at least adding one local to the main procedure does the trick!
« Last Edit: September 16, 2021, 03:11:21 AM by JK »

nidud

  • Member
  • *****
  • Posts: 2215
    • https://github.com/nidud/asmc
Re: SetUnhandledExceptionFilter
« Reply #12 on: September 16, 2021, 03:33:08 AM »
I don´t use a custom Prologue/Epilogue, but at procedure entry after defining locals the stack is 16 bit aligned (and rsp, -16). This works perfectly well so far.

I know and accept, that this is not the best approach, but for reasons i don´t want to discuss here, i want it that way in some cases.

Yes, that's a very bad idea.

Quote
The only drawback so far i had, is that SEH isn´t working even if i change option frame:auto to option frame:add. It works, if i add a uses clause (e.g. uses rsi) to the main procedure (the one called with the end statement).

Well, this works both with and without USES on my end.

Code: [Select]
include windows.inc

option stackbase:rbp
option win64:5
option frame:add
option cstack:on

.data

caption db "Test", 0
txt     db "inside handler", 0

.code

gpf_handler proc x:ptr

  MessageBox(0, addr txt, addr caption, 0)
  ExitProcess(1)

gpf_handler endp


start proc

  SetUnhandledExceptionFilter(addr gpf_handler)

  xor eax,eax
  mov eax,[rax] ;gpf
  ret

start endp

do_start proc ;uses rdi

  invoke start
  ret

do_start endp

end do_start

JK

  • Member
  • **
  • Posts: 132
Re: SetUnhandledExceptionFilter
« Reply #13 on: September 16, 2021, 04:08:14 AM »
Further testing shows, that "uses" is indeed not reliable, but at least adding one local to the main procedure does the trick!

I assume, that having a local changes the necessary stack calculations in ASMC so "FRAME" (or option frame:add) works properly then (even with my settings). Without a local this calculation can be skipped and the result is different, which causes "FRAME" not to work anymore (with my setting). It is a problem, which happens only with the main procedure (the one invoked by "end ..."). All others don´t matter.

Thanks for your help!

JK

nidud

  • Member
  • *****
  • Posts: 2215
    • https://github.com/nidud/asmc
Re: SetUnhandledExceptionFilter
« Reply #14 on: September 16, 2021, 05:34:47 AM »
That assumption was wrong so it does fail without the push.

Further testing shows, that "uses" is indeed not reliable, but at least adding one local to the main procedure does the trick!

I assume, that having a local changes the necessary stack calculations in ASMC so "FRAME" (or option frame:add) works properly then (even with my settings). Without a local this calculation can be skipped and the result is different, which causes "FRAME" not to work anymore (with my setting). It is a problem, which happens only with the main procedure (the one invoked by "end ..."). All others don´t matter.

Why it "works" doing this is a mystery but you may get lucky sometimes adjusting the stack manually. The unwind information will be wrongly calculated without the AUTO option so it have to be done manually in that case.