Back to home page

DOS ain't dead

Forum index page

Log in | Register

Back to the board
Thread view  Mix view  Order
RayeR

Homepage

CZ,
24.07.2020, 13:15
 

User exception handler in C (Developers)

Hi, is it possible/how to install user exception handler in user program in DJGPP (DOS) and MinGW (Windows) to catch CPU exception before OS/program will crash? Does DJGPP have it's own hander or is it catched by DPMI server or other memory manager in v86 mode like JEMM? Is it possible to resume from exception and continue normally? Eg. in case of RDMSR of nonexisting MSR exception is generated and it leads to crash or BSOD in Windows. There was some discussion about CPU-Z in Win9x that from VXD the exception cannot be catched and Windows will BSOD...

---
DOS gives me freedom to unlimited HW access.

Japheth

Homepage

Germany (South),
24.07.2020, 18:07

@ RayeR

User exception handler in C

> Is it possible to resume from exception and continue normally?
> Eg. in case of RDMSR of nonexisting MSR exception is generated ...

Yes. However, I'd recommend to use a small assembly routine for MSR access that is protected by a temporarily modified exception vector 0x0D (GPF).

This does not even require protected-mode, this small real-mode assembly prog reads MSRs and won't crash if a certain MSR can't be read:


        .286
        .model small
        .dosseg
        .stack 2048

DGROUP group _TEXT

lf      equ 10

CStr macro text:vararg
local sym
        .const
sym  db text,0
        .code
        exitm <offset sym>
endm

DStr macro text:vararg
local sym
        .const
sym  db text,0
        .data
        exitm <offset sym>
endm

        .data

msrs label dword
        dd 10h
        dd 1Bh
        dd 0e7h,0e8h
        dd 0feh
        dd 174h,175h,176h
        dd 179h,17ah,17bh
        dd 1D9h
        dd 1DBh,1DCh,1DDh,1DEh

        dd 200h,201h,202h,203h
        dd 204h,205h,206h,207h
        dd 208h,209h,20ah,20bh
        dd 20ch,20dh,20eh,20fh

        dd 250h,258h,259h
        dd 268h,269h,26Ah,26bh
        dd 26ch,26dh,26eh,26fh
        dd 277h
        dd 2ffh

        dd 0C0000080h,0C0000081h,0C0000082h,0C0000083h,0C0000084h
        dd 0C0000100h,0C0000101h,0C0000102h
        dd 0C0001019h,0C000101Ah,0C000101Bh,0C0001027h  ;DRX_ADDR_MASK
        dd 0C0010010h   ;SYSCFG
        dd 0C001001Ah
        dd 0C001001Bh
        dd 0C0010030h, 0C0010031h, 0C0010032h
        dd 0C0010033h, 0C0010034h, 0C0010035h
        dd 0C0010111h, 0C0010112h, 0C0010113h   ;SMBASE, SMM_ADDR, SMM_MASK
        dd 0C0010114h, 0C0010115h, 0C0010116h   ;VM_CR, IGNNE, SMM_CTL
        dd 0C0010117h, 0C0010118h, 0C0010119h   ;VM_HSAVE_PA, SVM_KEY_MSR, SMM_KEY_MSR
        dd 0

names label word
        dw DStr("TSC")
        dw DStr("APIC Base Addr Register")
        dw DStr("MPERF"), DStr("APERF")
        dw DStr("MTRRCap")
        dw DStr("SysEnterCS"),DStr("SysEnterESP"),DStr("SysEnterEIP")
        dw DStr("MCG_Cap"),DStr("MCG_Status"),DStr("MCG_Ctl")
        dw DStr("DebugCtl")
        dw DStr("LastBranchFromIP"),DStr("LastBranchToIP"),DStr("LastIntFromIP"),DStr("LastIntToIP")

        dw DStr("MtrrBase0"),DStr("MtrrMask0"),DStr("MtrrBase1"),DStr("MtrrMask1")
        dw DStr("MtrrBase2"),DStr("MtrrMask2"),DStr("MtrrBase3"),DStr("MtrrMask3")
        dw DStr("MtrrBase4"),DStr("MtrrMask4"),DStr("MtrrBase5"),DStr("MtrrMask5")
        dw DStr("MtrrBase6"),DStr("MtrrMask6"),DStr("MtrrBase7"),DStr("MtrrMask8")

        dw DStr("MtrrFix64k_00000"),DStr("MtrrFix16k_80000"),DStr("MtrrFix16k_A0000")
        dw DStr("MtrrFix4k_C0000"),DStr("MtrrFix4k_C8000"),DStr("MtrrFix4k_D0000"),DStr("MtrrFix4k_D8000")
        dw DStr("MtrrFix4k_E0000"),DStr("MtrrFix4k_E8000"),DStr("MtrrFix4k_F0000"),DStr("MtrrFix4k_F8000")
        dw DStr("PAT")
        dw DStr("MtrrdefType")

        dw DStr("EFER"),DStr("STAR"),DStr("LSTAR"),DStr("CSTAR"),DStr("FMASK")
        dw DStr("FSBase"),DStr("GSBase"),DStr("KrnlGSBase")
        dw DStr("DR1_ADDR_MASK"),DStr("DR2_ADDR_MASK"),DStr("DR3_ADDR_MASK"),DStr("DR0_ADDR_MASK")
        dw DStr("SYSCFG")
        dw DStr("TOP_MEM")
        dw DStr("TOP_MEM2")
        dw DStr("CpuNameString"),DStr("CpuNameString"),DStr("CpuNameString")
        dw DStr("CpuNameString"),DStr("CpuNameString"),DStr("CpuNameString")
        dw DStr("SMBASE"),DStr("SMM_ADDR"), DStr("SMM_MASK")
        dw DStr("VM_CR"),DStr("IGNNE"), DStr("SMM_CTL")
        dw DStr("VM_HSAVE_PA"),DStr("SVM_KEY_MSR"), DStr("SMM_KEY_MSR")

orgint0d dw offset exc0D,seg exc0D
wSP     dw 0

        .code

        assume ds:DGROUP

        .386
        include printf.inc

        .586p

exc0D proc far
        sti
        pop ax
        pop dx
        cmp ax,msrlbl
        jz @F
        invoke printf, CStr(<"Exc 0Dh occured, cs:ip=%X:%X, ecx=%lX",lf>),dx,ax,ecx
        mov sp,wSP
        jmp done
@@:
        invoke printf, CStr(<"%lX(%s): undefined (access caused an Exc 0Dh)",lf>),ecx,bx
        mov sp,wSP
        jmp nextitem

exc0D endp

xchgint0d proc
        push 0
        pop es
        mov eax,dword ptr [orgint0d]
        xchg eax,es:[4*0Dh]
        mov dword ptr orgint0d,eax
        ret
xchgint0d endp

main proc
        mov wSP,sp
        smsw ax
        test al,1
        jz @F
        invoke printf,CStr(<"cannot run in V86 mode.",lf>)
        jmp exit
@@:
        call xchgint0d
        mov si,offset msrs
        mov di,offset names
nextitem::
        lodsd
        and eax,eax
        jz done
        mov ecx,eax
        xchg di,si
        lodsw
        mov bx,ax
        xchg di,si
msrlbl::
        rdmsr
        invoke printf, CStr(<"%lX(%s): %08lX-%08lX",lf>),ecx,bx,edx,eax
        jmp nextitem
done::
        call xchgint0d
exit:
        ret
main endp

start:
        push cs
        pop ds
        mov ax,ss
        mov dx,cs
        sub ax,dx
        shl ax,4
        push cs
        pop ss
        add sp,ax
        call main
        mov ax,4c00h
        int 21h

        end start

---
MS-DOS forever!

Rugxulo

Homepage

Usono,
24.07.2020, 18:39

@ RayeR

User exception handler in C

> Hi, is it possible/how to install user exception handler in user program in
> DJGPP (DOS) and MinGW (Windows) to catch CPU exception before OS/program
> will crash? Does DJGPP have it's own hander or is it catched by DPMI server
> or other memory manager in v86 mode like JEMM? Is it possible to resume
> from exception and continue normally?

DJGPP's CvsWeb interface is down, so I can't link directly. But perhaps you want something like uclock.c (RDTSC) that catches SIGILL? (If not, sorry, I'm no hardware expert.)

tkchia

Homepage

24.07.2020, 20:47

@ RayeR

User exception handler in C

Hello RayeR,

> Hi, is it possible/how to install user exception handler in user program in
> DJGPP (DOS) and MinGW (Windows) to catch CPU exception before OS/program
> will crash? Does DJGPP have it's own hander or is it catched by DPMI server
> or other memory manager in v86 mode like JEMM? Is it possible to resume
> from exception and continue normally? Eg. in case of RDMSR of nonexisting

Apparently DJGPP supports the POSIX-ish signal(...) and sigaction(...) interface for catching exceptions such as these --- see http://www.delorie.com/djgpp/doc/libc/libc_724.html . (Myself though, I have only tried using it on Linux, not on DJGPP.)

If it works, a way to use it would be to do a sigsetjmp(...) before doing the RDMSR. Meanwhile you also arrange for your signal handler to do a siglongjmp(...) if it is invoked. The place where the program siglongjmp(...)'s to can then handle the failed RDMSR.

Thank you!

---
https://gitlab.com/tkchia · https://codeberg.org/tkchia · 😴 "MOV AX,0D500H+CMOS_REG_D+NMI"

Guti

Homepage

27.07.2020, 08:22

@ RayeR

User exception handler in C

> Hi, is it possible/how to install user exception handler in user program in
> DJGPP (DOS) and MinGW (Windows) to catch CPU exception before OS/program
> will crash? Does DJGPP have it's own hander or is it catched by DPMI server
> or other memory manager in v86 mode like JEMM? Is it possible to resume
> from exception and continue normally? Eg. in case of RDMSR of nonexisting
> MSR exception is generated and it leads to crash or BSOD in Windows. There
> was some discussion about CPU-Z in Win9x that from VXD the exception cannot
> be catched and Windows will BSOD...

You can use com0com0 to map a real Windows port to a virtual DOS one. Indeed I use it for debugging my DOS Watcom applications under Windows 10.

https://www.javiergutierrezchamorro.com/depurar-aplicaciones-dos-con-watcom-c-y-openwatcom-c/

---
Visit my personal blog at https://www.javiergutierrezchamorro.com

RayeR

Homepage

CZ,
30.07.2020, 13:18

@ tkchia

User exception handler in C

Thanks, it led me to this example:
http://www.delorie.com/djgpp/doc/libc/libc_641.html

It seems DJGPP have good tools for exception handling.
CPU context is probably saved automatically in __djgpp_exception_state structure from where I can check the opcode caused exception, read an manipulate registers and also skip "bad" opcode and resume program execution by longjmp(__djgpp_exception_state, 0);
I'm not sure what all functions I can call from the handler, I would need just printf.

I would also need to implement this mechanism in my windows kernel driver because it's anoying to let exception BSOD and restart whole computer. I don't know if kernel winapi have support of similar function to hook exception handler or would I have to do it in assembler.

Also I'm thinking about an idea of using exception handler to emulate missing CMOV instruction on some CPUs that caused lot of problems as many libs and programs are compiled with CMOV (without recompiling all code). But the problem is that every program replaces/installs it's own default exception handler. It would need to make some TSR that will monitor that exception vector has changed and it would hook it again to be the 1st handler to be executed to properly emulate CMOV. Or could me make as a patch for DJGPP binaries that will find default exception handler code and inject CMOV handling code there but it would need extra space for additional code in binary, more uneversal and cleaner would be the TSR way...

---
DOS gives me freedom to unlimited HW access.

tom

Homepage

Germany (West),
30.07.2020, 23:54

@ RayeR

User exception handler in C

> I'm not sure what all functions I can call from the handler, I would need
> just printf.
(f)printf requires a FILE object, which is at exception time possibly in use or not available.
use sprintf(), and your own logging to file, memory, or (windows) debug print monitor.



> I would also need to implement this mechanism in my windows kernel driver
> because it's annoying to let exception BSOD and restart whole computer. I
> don't know if kernel winapi have support of similar function to hook
> exception handler or would I have to do it in assembler.

most exceptions can be handled by
__try {...}
__except {...}

in userspace.

those that can't, often can't in the kernel. IMO the kernel/driver is supposed to check the arguments for correctness before executing on it.

AFAIK there is no 'catch all, log and ignore' method in Windows

Tom

tkchia

Homepage

31.07.2020, 19:36

@ RayeR

User exception handler in C

Hello RayeR,

> Thanks, it led me to this example:
> http://www.delorie.com/djgpp/doc/libc/libc_641.html
> It seems DJGPP have good tools for exception handling.
> CPU context is probably saved automatically in __djgpp_exception_state
> structure from where I can check the opcode caused exception, read an
> manipulate registers and also skip "bad" opcode and resume program
> execution by longjmp(__djgpp_exception_state, 0);

Hmm... this is interesting. One worry I have with using
longjmp(__djgpp_exception_state, 0);
is that a longjmp(, ) will always alter eax, because that is how longjmp(, ) works. This may be problematic if your code doing the RDMSR (or whatever) happens to rely on eax not changing at that particular point.

I guess I will need to learn more about the finer details of how __djgpp_exception_state works. But for now, I suspect that it might be safer for you to set up your own jmp_buf (using sigsetjmp) and then jump to it.

Thank you!

---
https://gitlab.com/tkchia · https://codeberg.org/tkchia · 😴 "MOV AX,0D500H+CMOS_REG_D+NMI"

RayeR

Homepage

CZ,
01.08.2020, 02:13

@ tom

User exception handler in C

> (f)printf requires a FILE object, which is at exception time possibly in
> use or not available.

I experimented with printf and it worked in my case.

> those that can't, often can't in the kernel. IMO the kernel/driver is
> supposed to check the arguments for correctness before executing on it.

I do rd/wrmsr in kernel driver so I should handle exception in kernel mode. I know that some windows tool/driver can do it. Checking arguments is no go here as I don't know what MSRs are valid on current CPU and exception handling should prevent falling to BSOD.

---
DOS gives me freedom to unlimited HW access.

RayeR

Homepage

CZ,
01.08.2020, 02:16

@ tkchia

User exception handler in C

I probably just print some warning about reading invalid MSR and let the program continue, where returned RDMSR value can be zero or something else, not so important...

---
DOS gives me freedom to unlimited HW access.

Laaca

Homepage

Czech republic,
16.08.2020, 21:27

@ RayeR

User exception handler in C

The use of function "Signal" is very interresting.
It would be also cool to test in this way the SSE instruction.
The SSE must be supported by hardware (can be obtained with CPUID) but also switched on by software by privileged instruction (MOV CR4,???)

So the elegant way how to obtain the SSE is to check the CPUID status and then set the exception handler and just to try to call some.

But my question is - how to set the Signal back to original handler?

The setting the my handler is in the source: "signal(SIGILL, catch_rdtsc)"
but it would be nice to call something like "signal(SIGILL, orig_handler)"

---
DOS-u-akbar!

Laaca

Homepage

Czech republic,
16.08.2020, 22:38

@ Laaca

User exception handler in C

Aha, it was not so hard.

My implementation in FreePascal:
(not the mentioned SSE check but a very simple example how to catch out the division by zero and how to revert the handler to original state)


uses dpmiexcp;

var was_excp:boolean;

Function SignalHandler(i:longint):longint;cdecl;
var pexp:PException_State;
    jmp_rec:dpmi_jmp_buf;
    eip:longint;
begin
pexp:=djgpp_exception_state;
eip:=pexp^.__eip;
if {eip=$310f} 1=1 then
   begin
   was_excp:=true;
   Move(pexp^,jmp_rec,sizeof(dpmi_jmp_buf));
   jmp_rec.eip:=pexp^.__eip+2;
   jmp_rec.edx:=0;
   dpmi_LongJmp(jmp_rec,0);
   end;
end;


Procedure Do_Something;
var a:byte;
begin
a:=0;
writeln(2 div a); {I can't write just "2 div 0" because of the FPC sanity check :-)}
end;

{=========== main ===========}
begin
was_excp:=false;

Signal(SIGFPE,@SignalHandler);
Do_Something;
Signal(SIGFPE,@SIG_DFL);

writeln(was_excp);
end.

---
DOS-u-akbar!

Rugxulo

Homepage

Usono,
17.08.2020, 22:26

@ Laaca

User exception handler in C

> It would be also cool to test in this way the SSE instruction.
> The SSE must be supported by hardware (can be obtained with CPUID) but also
> switched on by software by privileged instruction (MOV CR4,???)

Let the OS (or DOS extender, in this case) enable it for you. That means DOS/32A, Causeway 4.x, HDPMI32, or CWSDPMI (r5 2008 or r7). Or whatever Win9x or WinNT versions.

Honestly, it may not be any faster than plain MMX, so it may not be worth your time. (Depends on cpu.)

> So the elegant way how to obtain the SSE is to check the CPUID status and
> then set the exception handler and just to try to call some.


sse_supported:
  fxsave [xmm_save]
  mov dword ptr [xmm_save+160],"C0DE"
  fxrstor [xmm_save]
  mov dword ptr [xmm_save+160],"D00D"
  fxsave [xmm_save]
  cmp dword ptr [xmm_save+160],"C0DE"
  mov eax,0
  jnz sse_supported_bye
  inc al                              ; if OSFXSR not turned on,
sse_supported_bye:                    ;    XMM* are not saved
  ret


So you can indeed know if running under old CWSDPMI (r5 2000) without SSE support and then fallback to MMX only. (Unless your BIOS enables it for you. One guy said his did, but none of mine did.)

Rugxulo

Homepage

Usono,
17.08.2020, 22:35

@ Laaca

User exception handler in C

> Aha, it was not so hard.
>
> My implementation in FreePascal:
> (not the mentioned SSE check but a very simple example how to catch out the
> division by zero and how to revert the handler to original state)

Okay, but technically, just to determine something like SSE support, you should probably use unit mmx (is_sse2_cpu) instead.

A few other things were mentioned on FASM's forum a while back, e.g. int6.

marcov

18.08.2020, 12:23

@ Rugxulo

User exception handler in C

> > Aha, it was not so hard.
> >
> > My implementation in FreePascal:
> > (not the mentioned SSE check but a very simple example how to catch out
> the
> > division by zero and how to revert the handler to original state)
>
> Okay, but technically, just to determine something like SSE support, you
> should probably use unit
> mmx
> (is_sse2_cpu) instead.

Undocumented unit cpu has some functions too, and might be more maintained.

Laaca

Homepage

Czech republic,
18.08.2020, 20:15

@ marcov

User exception handler in C

> Undocumented unit cpu has some functions too, and might be more maintained.

I think that undocumented unit CPU has a potential bug.

function cr0 : longint;assembler;
      asm
         DB 0Fh,20h,0C0h
         { mov eax,cr0
           special registers are not allowed in the assembler
                parsers }
      end;


Problem is that "mov eax,cr0" is a privileged instruction. Sure, some DPMI managers let you to call it anyway but it is not sure.
And I am not sure about other OSes.

Better is to use the instruction SMSW which is non-privileged.

function cr0 : longint;assembler;
asm
mov eax,0
smsw ax 
end;

---
DOS-u-akbar!

tkchia

Homepage

19.08.2020, 18:52

@ Laaca

User exception handler in C

Hello Laaca,

> The setting the my handler is in the source: "signal(SIGILL, catch_rdtsc)"
> but it would be nice to call something like "signal(SIGILL, orig_handler)"

The POSIX function sigaction(...) allows one to retrieve the originally active signal handler and action information. Apparently Free Pascal calls it fpSigAction: https://www.freepascal.org/docs-html/rtl/baseunix/fpsigaction.html .

And yes, if you are sure that no other routine in your program is trying to hook SIGILL at that point, you can probably get away with signal(SIGILL, SIG_DFL). :-)

Thank you!

---
https://gitlab.com/tkchia · https://codeberg.org/tkchia · 😴 "MOV AX,0D500H+CMOS_REG_D+NMI"

Rugxulo

Homepage

Usono,
19.08.2020, 19:44

@ Laaca

User exception handler in C

> Better is to use the instruction SMSW which is non-privileged.
>
> function cr0 : longint;assembler;
> asm
> mov eax,0
> smsw ax 
> end;
>


I think you rather meant LMSW here since SMSW only reads from it.

Laaca

Homepage

Czech republic,
19.08.2020, 21:50

@ Rugxulo

User exception handler in C

> I think you rather meant
> LMSW here since
> SMSW only reads from it.

No. SMSW is correct.

1) LMSW is privilegged, SMSW not
2) SMSW AX is MOV AX,CR0 and LMSW AX is MOV CR0,AX

---
DOS-u-akbar!

marcov

20.08.2020, 00:00

@ tkchia

User exception handler in C

> The POSIX function sigaction(...) allows one to retrieve the originally
> active signal handler and action information. Apparently Free Pascal calls
> it fpSigAction:
> https://www.freepascal.org/docs-html/rtl/baseunix/fpsigaction.html .


Note the unitname "baseunix"; FreePascal only does posix on unixy targets, iow this is not available for go32v2/dos.

Though there used to be a signal() routine for djgpp interoperation (unit signals?)

Laaca

Homepage

Czech republic,
20.08.2020, 15:31

@ marcov

User exception handler in C

> Though there used to be a signal() routine for djgpp interoperation (unit
> signals?)

No, unit DPMIEXCP.

---
DOS-u-akbar!

Rugxulo

Homepage

Usono,
23.08.2020, 17:32

@ Laaca

User exception handler in C

> No. SMSW is correct.
>
> 1) LMSW is privilegged, SMSW not

Those date from 286 era, no? So before CR0 was made user-accessible.

> 2) SMSW AX is MOV AX,CR0 and LMSW AX is MOV CR0,AX

Your "mov eax,0" confused me. Apparently you're clearing the top bits of the return value in advance, right?

FPC started as a 386 compiler for DOS (GO32V1), so this is probably some relic of that history. In FPC's case, I don't think CR0 is very useful overall, at least not directly.

Japheth

Homepage

Germany (South),
23.08.2020, 20:36

@ Rugxulo

User exception handler in C

> Your "mov eax,0" confused me. Apparently you're clearing the top bits of
> the return value in advance, right?

I assume yes. It's not necessary, however, because you can use the 0x66 prefix:


     db 66h         ;makes smsw copy all 32 bits to EAX
     smsw ax           


with LMSW, this trick won't work.

On newer cpus, the UMIP flag in CR4 can be set, which will make SMSW a privileged instruction as well.

---
MS-DOS forever!

Back to the board
Thread view  Mix view  Order
22049 Postings in 2034 Threads, 396 registered users, 68 users online (0 registered, 68 guests)
DOS ain't dead | Admin contact
RSS Feed
powered by my little forum