Я разработал небольшую программу, которая проверяет производительность 32-битной Windows обработки структурированных исключений. Чтобы минимизировать издержки по сравнению с остальными, я написал код, генерирующий фильтрацию исключения в сборке.
Это код C ++:
#include <Windows.h>
#include <iostream>
using namespace std;
bool __fastcall getPointerFaultSafe( void *volatile *from, void **to );
int main()
{
auto getThreadTimes = []( LONGLONG &kt, LONGLONG &ut )
{
union
{
FILETIME ft;
LONGLONG ll;
} creationTime, exitTime, kernelTime, userTime;
GetThreadTimes( GetCurrentThread(), &creationTime.ft, &exitTime.ft, &kernelTime.ft, &userTime.ft );
kt = kernelTime.ll;
ut = userTime.ll;
};
LONGLONG ktStart, utStart;
getThreadTimes( ktStart, utStart );
size_t const COUNT = 100'000;
void *pv;
for( size_t c = COUNT; c; --c )
getPointerFaultSafe( nullptr, &pv );
LONGLONG ktEnd, utEnd;
getThreadTimes( ktEnd, utEnd );
double ktNsPerException = (ktEnd - ktStart) * 100.0 / COUNT,
utNsPerException = (utEnd - utStart) * 100.0 / COUNT;
cout << "kernel-time per exception: " << ktNsPerException << "ns" << endl;
cout << "user-time per exception: " << utNsPerException << "ns" << endl;
return 0;
}
Это сборка- код:
.686P
PUBLIC ?getPointerFaultSafe@@YI_NPCRAXPAPAX@Z
PUBLIC sehHandler
.SAFESEH sehHandler
sehHandler PROTO
_DATA SEGMENT
byebyeOffset dd 0
_DATA ENDS
exc_ctx_eax = 0b0h
exc_ctx_eip = 0b8h
_TEXT SEGMENT
?getPointerFaultSafe@@YI_NPCRAXPAPAX@Z PROC
ASSUME ds:_DATA
push OFFSET sehHandler
push dword ptr fs:0
mov dword ptr fs:0, esp
mov byebyeOffset, OFFSET byebye - OFFSET mightfail
mov al, 1
mightfail:
mov ecx, dword ptr [ecx]
mov dword ptr [edx], ecx
byebye:
mov edx, dword ptr [esp]
mov dword ptr fs:0, edx
add esp, 8
ret 0
?getPointerFaultSafe@@YI_NPCRAXPAPAX@Z ENDP
sehHandler PROC
mov eax, dword ptr [esp + 12]
mov dword ptr [eax + exc_ctx_eax], 0
mov edx, byebyeOffset
add [eax + exc_ctx_eip], edx
mov eax, 0
ret 0
sehHandler ENDP
_TEXT ENDS
END
- Как получить asm-модуль моей программы / SAFESEH-совместимый?
- Почему эта программа потребляет так много пользовательского ЦП-времени? Библиотечный код, вызываемый операционной системой после начала обработки исключения, должен сохранять только все регистры в структуре CONTEXT, заполнять структуру EXCEPTION_RECORD, вызывать самый верхний фильтр исключений, который - в данном случае - сдвигает выполнение еще на две инструкции, и при возврате он в моем случае восстановит все регистры для продолжения выполнения в соответствии с тем, что я возвратил в EAX. Это не должно быть так много времени, что почти треть процессорного времени будет потрачено в пользовательском пространстве. Это около 2,3 мс, т.е. когда мой старый Ryzen 1800X работает на одном ядре с 4 ГГц, около 5 200 тактов.
- Я использую переменную byebyeOffset в своем коде для переноса расстояния между небезопасными инструкция, которая может сгенерировать нарушение доступа и код безопасности впоследствии. Я инициализирую эту переменную перед небезопасной инструкцией. Но было бы неплохо иметь это смещение статически как немедленное в точке, где я добавляю его в EIP в функции фильтра исключений sehHandler; но смещения ограничены getPointerFaultSafe. Конечно, сохранение смещения и выборка его из переменной занимают незначительную часть общего времени вычислений, но было бы лучше иметь чистое решение.