3

I'm experimenting with measuring CPU's instructions latency and throughput on P and E cores using RDPMC on Win 11, something like that:

 MOV ECX, 0x40000000 ; Instructions Counter RDPMC ; Read Performance-Monitoring Counter SHL RDX, 32 OR RAX, RDX ; Value in RAX ; ... instruction to be tested and so on 

Everything works like a charm except for one minor but annoying issue.

If I forget to set bit 8 of the CR4 register (which I currently configure via my own Windows driver), I get an EXCEPTION_PRIV_INSTRUCTION (0xC0000096) at RDPMC, as expected in a user-mode application.

Question: Is there any elegant way to catch and handle this exception directly in assembly code?

Ironically, I can't read and check the CR4 register before executing RDPMC because reading CR4 requires privileged execution at Ring 0 as well.

But for example, this code works fine:

#include <intrin.h> #include <stdio.h> #include <windows.h> int main() { unsigned int ecx = 0x40000000; // Counter index __try { unsigned __int64 value = __readpmc(ecx); // RDPMC printf("PMC[0x%X] value: %llu\n", ecx, value); } __except (EXCEPTION_EXECUTE_HANDLER) { printf("Exception occurred while reading RDPMC\n"); } return 0; } 

However, I would like to achieve the same "try/except" behavior directly in assembly in simple way and without involving SEH, ideally in an OS-independent manner. Is that possible, or is it fundamentally unavoidable?

1
  • 3
    It certainly won't be OS-independent. CPU exceptions are always handled first by the kernel, then passed to userspace in an OS-specific manner. Linux for instance uses signals instead of C++ throw/catch. Commented Oct 21 at 19:21

1 Answer 1

2

Well, now I've got this (thanks to Nate Eldredge). If CPU exceptions are first handled by the kernel, then they should be propagated to user space in an OS-specific way. I had hoped it could be as simple as I did in the past with the PDP-11 and RT-11, about 35+ years ago, using a simple trap. But, well, the world nowadays is slightly more complicated.

The OS I'm actually targeting is Windows x64, so this answer is specific to that, using SEH.

After digging through old forums and documentation, I was able to get things done using FASM.

Here is a minimal working example with an SEH handler:

format PE64 CONSOLE entry start include 'seh64.inc' start: SUB RSP,8*(4+1) LEA RCX,[hello] call [printf] .try handler MOV ECX,0x40000000 ; Instructions Counter RDPMC ; Read Performance-Monitoring Counter .end safe_place: LEA RCX, [finish] call [printf] XOR ECX,ECX call [ExitProcess] handler: SUB RSP,8*(4+1) MOV qword [R8+CONTEXT64.Rip],safe_place LEA RCX,[exception] call [printf] XOR EAX,EAX ADD RSP,8*(4+1) retn section '.data' data readable writeable hello db "Hello, SEH!",10,0 finish db "Sucessfully finished",10,0 exception db "Instruction caused exception",10,0 data seh end data section '.idata' import data readable library kernel32,'KERNEL32.DLL', msvcrt, 'msvcrt.dll' import kernel32, ExitProcess,'ExitProcess' import msvcrt, printf, 'printf' 

Now exception on RDPMC will not cause application crash, and will be landed into my hands in handler:

>sehtest.exe Hello, SEH! Instruction caused exception Sucessfully finished 

Of course, this will work for any kind of exception as well, for example:

.try handler XOR EAX,EAX MOV dword [EAX], 0 .end 

or

.try handler XOR EBX,EBX DIV EBX .end 

Content of seh64.inc:

include 'INCLUDE\win64a.inc' macro enqueue list,item { match any,list \{ list equ list,item \} match ,list \{ list equ item \} } macro dequeue list,item { done@dequeue equ match first=,rest,list \{ item equ first list equ rest restore done@dequeue \} match :m,done@dequeue:list \{ item equ m list equ restore done@dequeue \} match ,done@dequeue \{ item equ restore done@dequeue \} } macro queue list,index,item { local copy copy equ list rept index+1 \{ dequeue copy,item \} } macro data directory { done@data equ match =3,directory \{ local l_infos,_info,_end l_infos equ align 4 match list,l_handlers \\{ irp _handler,list \\\{ local rva$ rva$ = rva $ enqueue l_infos,rva$ db 19h,0,0,0 dd _handler,0 \\\} \\} data 3 match list,l_begins \\{ irp _begin,list \\\{ dequeue l_ends,_end dequeue l_infos,_info dd _begin dd _end dd _info \\\} \\} restore done@data \} match ,done@data \{ data directory restore done@data \} } l_begins equ l_ends equ l_handlers equ macro .try handler { local ..try __TRY equ ..try local ..end __END equ ..end local ..catch __CATCH equ ..catch __TRY: if ~ handler eq virtual at handler __CATCH: end virtual end if } macro .catch { jmp __END __CATCH: } macro .end { __END: enqueue l_begins,rva __TRY enqueue l_ends,rva __END enqueue l_handlers,rva __CATCH restore __TRY restore __END restore __CATCH } seh equ 3 CONTEXT64.Rip = 0F8h 

Be very careful with this code above, it is just "quick and dirty" (but at least functional) example.

Update

And as suggested by Margaret Bloom, here is a code snippet that uses AddVectoredExceptionHandler. This time EuroAssembler:

EUROASM AutoSegment=ON, CPU=X64, SIMD=AVX2 %^SourceName PROGRAM Format=PE, Width=64, Model=Flat, IconFile=, Entry=main INCLUDE winscon.htm, winabi.htm, cpuext64.htm helloMsg D "Hello, Exception!",0 finishMsg D "Sucessfully finished",0 exceptionMsg D "Instruction caused exception",0 Handle DQ 0 main: nop StdOutput helloMsg, Eol=Yes, Console=Yes WinABI AddVectoredExceptionHandler, 1, VectoredHandler MOV [Handle],RAX MOV ECX, 0x40000000 ; Counter RDPMC ; 2 bytes (0F33) EXCEPTION_PRIV_INSTRUCTION WinABI RemoveVectoredExceptionHandler, [Handle] StdOutput finishMsg, Eol=Yes, Console=Yes TerminateProgram VectoredHandler PROC PUSH RBX PUSH RSI PUSH RDI MOV RBX, RCX ; EXCEPTION_POINTERS MOV RDI, [RBX + 8] ; CONTEXT MOV RSI, [RBX] ; EXCEPTION_RECORD MOV EAX, [RSI] ; ExceptionCode CMP EAX, 0C0000096h ; EXCEPTION_PRIV_INSTRUCTION JNE continue_search ADD [RDI + 0F8h], 2, DATA=Q ; Rip += 2 (adjust for RDPMC) StdOutput exceptionMsg, Eol=Yes, Console=Yes MOV EAX, 0FFFFFFFFh ; EXCEPTION_CONTINUE_EXECUTION JMP return continue_search: MOV EAX,1 ; EXCEPTION_CONTINUE_SEARCH return: POP RDI POP RSI POP RBX RET ENDP ENDPROGRAM 

and it works as well:

>vecdemo.exe Hello, Exception! Instruction caused exception Sucessfully finished 
Sign up to request clarification or add additional context in comments.

2 Comments

You can ditch the 64-bit table based SEH and use AddVectoredExceptionHandler since you are only using a single, global, exception handler.
Thank you, Margaret, for pointing to this function! I have added a simple example using AddVectoredExceptionHandler, and it works.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.