News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests

Main Menu

Exceptions, runtime errors and debugging in Masm32

Started by jj2007, June 02, 2012, 03:28:40 AM

Previous topic - Next topic

jj2007

;   This is a mini-tutorial showing how to handle exceptions and runtime errors in Masm32 using MasmBasic.
;
;   The include below adds the MasmBasic library to the Masm32 SDK. Most if not all examples in
;   the folder \masm32\examples work just fine if you replace the header lines or the default header
;   ( include \masm32\include\masm32rt.inc ) with this single line:

include \masm32\MasmBasic\MasmBasic.inc   ; download
.data
dummy   db "data lines before the Init statement are allowed", 0

; the next line initialises the library and the error handlers. Use instead of .code and start: label.
   Init tc, con[/b]   ; install the try/catch handler, output to console; see TryCatchEnd at the end
; the con is optional; no arg or box would show MessageBoxes for each error, while key would wait for a keypress after each error.

; the ErrLines macro adds some extra code in case you want to know which line triggered a runtime error. It is not needed for exceptions.
   ErrLines   ; blank or on means use them, off means don't

; we have a suspicion that the next para might be somewhat illegal. Let's try nonetheless:
  Try         ; set the start label
   xor ecx, ecx   ; you should go to jail for accessing address zero, but we'll be kind
   inc dword ptr [ecx]   ; RichMasm will select this line after exiting the program
  Catch         ; set the end label
   PrintLine "Incrementing (0) is illegal, but nobody can stop us!!", CrLf$

; so exceptions are no problem - what about runtime errors?
   call CatchMyRunTimeError   ; let's test the TryRTE macro, too

; once again, an exception, but this time we add code that is triggered only in case of an exception:
  Try         ; set the start label
     mov ecx, 4
     .Repeat
      Print Str$("44444444/%i = ", ecx)
      mov eax, 44444444
      cdq   ; extend the sign of eax to edx ->edx=0
      SetErrLine
      div ecx
      PrintLine Str$(eax)
      dec ecx
   .Until Sign?
  Catch only      ; "only" means skip this section if there are no problems
     mov eax, 31415926   ; ... otherwise, change your results here...
     PrintLine "undefined"   ; ... or issue a warning, etc.
  Finally         ; needed if "only" was used above
   .if LastEx(code)   ; we better give some extra info
        PrintLine Str$("\nOuch, we had an exception in source line %i at\nAddress\t", LastEx(line)), Hex$(LastEx(addr)), CrLf$, "Code", Tb$, Hex$(LastEx(code)), CrLf$
        PrintLine "The OS reports:", CrLf$, LastEx(info)
   .else
      PrintLine Err$()   ; no exceptions, but maybe a Windows error?
   .endif
   PrintLine CrLf$, "That was cute, now we crash the FPU:"
   FpuSet MbNear64, exall   ; set rounding=near, 64 bits precision, all flags set except precision flag

; one more exception, now with coder-defined into:
  Try "fdiv zero is not a good idea"   ; you can pass your own message with try
   fldpi   ; push 3.14
   fldz      ; push 0

; the deb macro can display all kinds of numerical values (reg8/16/32, immediate constants, xmm regs, fpu regs etc
; you can use it with the console (deb 4), with messageboxes (deb 1-3) and even with logfiles (deb 5)
   deb 4, "First, let's have a look at the FPU:", ST(0), ST(1)

   PrintLine CrLf$, "And now the illegal fdiv instruction:"
   fdiv      ; 3.1415/0 -> sets Z flag but does not yet trigger the exception
   nop      ; line 46
   nop      ; line 47
   fstp st   ; triggers exception (note this is line 69, the OS rightly says the fault happened in line 66)
  Catch only
   PrintLine "LastEx=  ", Tb$, Hex$(LastEx(code))
   PrintLine "Address=  ", Tb$, Hex$(LastEx(addr))
   PrintLine Str$("Source line=\t%i", LastEx(line))
   .if LastEx(user)
      PrintLine "Your coder says ", eax
   .endif
     PrintLine CrLf$, "The OS reports:", CrLf$, LastEx(info)
  Finally   ; this line will always be executed

; OK, game over - the last exception will be fatal. All files opened with Open "x", #1 etc will be closed before exiting.
   Print CrLf$, "Let's try one more, outside the Try/Catch block:", CrLf$
   mov ecx, 1234h   ; you won't be allowed to write to that address
   inc dword ptr [ecx]   ; this exception not handled by Try/Catch, so the program stops here
   Inkey "Can you see this text?"   ; no, you can't... we have a non-continuable exception before ;-)

   Exit         ; this won't be used, but the last crash will provide for an orderly Exit

CatchMyRunTimeError proc
   TryRTE RecallFailed   ; options: con means errors to console, key = con+wait for a keypress; default is box
   Let esi=FileRead$("NoSuchFile.dat")   ; trigger a runtime error
   Recall "NoSuchFile.dat", MyRec$()   ; try the impossible...
   TryRTE off
   Print Str$("%i strings read\n", eax)   ; in case Recall succeeds ;-)
   ; xor edx, edx   ; if there is a chance that edx could equal the label, zero it before the label
RecallFailed:
   cmp edx, $   ; simple error check
   .if Zero?
      Print CrLf$, "There was a runtime error, check your code or your files", CrLf$, CrLf$
   .else
      PrintLine "Rec 0=", MyRec$(0)
   .endif
   ret
CatchMyRunTimeError endp

   TryCatchEnd   ; activate the handler - must be the last instruction before "end start"

end start

MasmBasic's preferred editor RichMasm will select the source line that triggered the first exception, provided you use the
right assembler and linker as shown below:
  OPT_Assembler   mlv615         ; only ml.exe 6.15 and  JWasm work ok (ml 8+ don't do /Zd any more)
  OPT_DebugA   /Zd         ; generate info for mapinfo:lines
  OPT_DebugL   /map /mapinfo:lines      ; recent ml.exe and link.exe versions can't do that
  OPT_Linker   link         ; you need the Masm32 version, i.e. the old 1998 link.exe
  OPT_Wait   0         ; optional: don't wait for a key after exiting (i.e. you need an Inkey somewhere)

And here is the complete output of the proggie. It is partly Italian because my NtDll.dll is Italian. If you want to see it in your language: source and executable are attached.

Incrementing (0) is illegal, but nobody can stop us

Could not open
NoSuchFile.dat
for Recall, FileRead$ etc.

There was a runtime error, check your code or your files

44444444/4 = 11111111
44444444/3 = 14814814
44444444/2 = 22222222
44444444/1 = 44444444
44444444/0 = undefined

Ouch, we had an exception in source line 36 at
Address 0040108D
Code    C0000094

The OS reports:
{ERRORE DI EXCEPTION}
Divisione intera per zero.
EIP     0040108D
Code    C0000094

That was cute, now we crash the FPU:

First, let's have a look at the FPU:
ST(0)           0.0
ST(1)           3.14159265358979324

And now the illegal fdiv instruction:
LastEx=         C000008E
Address=        00401460
Source line=    63
Your coder says fdiv zero is not a good idea

The OS reports:
{ERRORE DI EXCEPTION}
Divisione a virgola mobile per zero.
EIP     00401460
Code    C000008E

Let's try one more, outside the Try/Catch block:
L'istruzione a "00401696" ha fatto riferimento alla memoria a "00001234". La memoria non poteva essere "written".