Enabling C++ exceptions and RTTI in kernel-mode code

425 views
Skip to first unread message

Gary Nebbett

unread,
Jul 20, 2000, 3:00:00 AM7/20/00
to
There are a number of sources of information on using C++ language features in Windows NT/2000
kernel-mode code, amongst which are:

"C++ Run Time Support for NT/WDM Kernel Mode Drivers"
(http://www.numega.com/drivercentral/examples/c++support.shtml)
"C++ Runtime Support for the NT DDK" (http://www.osr.com/ntinsider/1999/global.htm)

However both of these sources stop short of explaining how to enable use of C++ exceptions and Run
Time Type Information. This message presents one technique of enabling these features. The first
step is to compile cpprt.cpp and then to build a library from cpprt.obj and selected runtime support
object modules provided with Visual C++.

The batch commands:

@echo off
if "%1" == "" exit /b
setlocal
set lib=%1\crt\src\intel\st_lib
link/lib /nologo %lib%\ehprolog.obj %lib%\throw.obj %lib%\frame.obj %lib%\trnsctrl.obj
%lib%\unhandld.obj %lib%\validate.obj %lib%\exsup.obj %lib%\lowhelpr.obj %lib%\hooks.obj
%lib%\stdexcpt.obj %lib%\ehvecdtr.obj %lib%\ehvecctr.obj %lib%\ehveccvb.obj %lib%\user.obj
%lib%\typinfo.obj %lib%\typname.obj %lib%\rtti.obj %lib%\undname.obj cpprt.obj /out:cpprt.lib
endlocal

collect the necessary modules into the library (where %1 is the directory containing the Visual C++
installation). The st_lib (single threaded library) modules are used; the only threading issue (of
which I am aware) is the use of two global variables to hold pointers to an exception record and a
context record whilst searching for a handler and calling its catch block. It would be possible to
address this issue by using the multi-threaded (mt_lib) or dll (dll_lib) object modules and
implementing the function _getptd() suitably.

The source of cpprt.cpp is:
------------------------------------------------------------------------------
extern "C" {
#include <ntddk.h>
}
#include "cpprt.h"

#pragma comment(linker, "-ignore:4049") // locally defined symbol "<symbol>" imported

#pragma data_seg(".CRT$XIA")
void (*_xi_a[])() = {0};
#pragma data_seg(".CRT$XIZ")
void (*_xi_z[])() = {0};
#pragma data_seg(".CRT$XCA")
void (*_xc_a[])() = {0};
#pragma data_seg(".CRT$XCZ")
void (*_xc_z[])() = {0};
#pragma data_seg(".CRT$XPA")
void (*_xp_a[])() = {0};
#pragma data_seg(".CRT$XPZ")
void (*_xp_z[])() = {0};
#pragma data_seg(".CRT$XTA")
void (*_xt_a[])() = {0};
#pragma data_seg(".CRT$XTZ")
void (*_xt_z[])() = {0};
#pragma data_seg()

extern "C"
NTSYSAPI
VOID
NTAPI
RtlRaiseException(
IN PEXCEPTION_RECORD ExceptionRecord
);

class AtExitCall {
public:
AtExitCall(void (__cdecl *func)()) : func(func), next(list) { list = this; }
~AtExitCall() { func(); list = next; }
static AtExitCall* list;
private:
void (__cdecl *func)();
AtExitCall* next;
};

AtExitCall* AtExitCall::list = 0;

extern "C" int __cdecl atexit(void (__cdecl *func)())
{
return (new (PagedPool) AtExitCall(func) == 0) ? 1 : 0;
}

extern "C" void * __cdecl malloc(size_t n)
{
return ExAllocatePool(NonPagedPool, n);
}

extern "C" void __cdecl free(void * p)
{
if (p) ExFreePool(p);
}

extern "C" void __cdecl abort()
{
KeBugCheck(0x1E);
}

extern "C" void __stdcall
RaiseException(ULONG Code, ULONG Flags, ULONG NumberParameters, const ULONG * Information)
{
EXCEPTION_RECORD er = {Code, Flags, 0, RaiseException, NumberParameters};

for (ULONG i = 0; i < NumberParameters; i++) er.ExceptionInformation[i] = Information[i];

RtlRaiseException(&er);
}

int probe(void * p, size_t n, LOCK_OPERATION op)
{
PMDL mdl = IoAllocateMdl(p, n, FALSE, FALSE, 0);

__try {
MmProbeAndLockPages(mdl, KernelMode, op);
MmUnlockPages(mdl);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
IoFreeMdl(mdl);
return 1;
}

IoFreeMdl(mdl);
return 0;
}

extern "C" int __stdcall IsBadReadPtr(const void * p, size_t n)
{
return probe(const_cast<void *>(p), n, IoReadAccess);
}

extern "C" int __stdcall IsBadWritePtr(void * p, size_t n)
{
return probe(p, n, IoWriteAccess);
}

extern "C" int __stdcall IsBadCodePtr(const void * p)
{
return IsBadReadPtr(p, 1);
}

extern "C" void * SetUnhandledExceptionFilter(void *)
{
return 0;
}

void CpprtStartup()
{
void (**p)();

for (p = _xi_a; p < _xi_z; p++) if (*p) (*p)();

for (p = _xc_a; p < _xc_z; p++) if (*p) (*p)();
}

void CpprtRundown()
{
while (AtExitCall::list) delete AtExitCall::list;

void (**p)();

for (p = _xp_a; p < _xp_z; p++) if (*p) (*p)();

for (p = _xt_a; p < _xt_z; p++) if (*p) (*p)();
}
------------------------------------------------------------------------------
and the source of cpprt.h is:
------------------------------------------------------------------------------
#ifndef __CPPRT__
#define __CPPRT__

inline void * __cdecl operator new(size_t n)
{
return n ? ExAllocatePool(NonPagedPool, n) : 0;
}

inline void * __cdecl operator new(size_t n, POOL_TYPE pooltype)
{
return n ? ExAllocatePool(pooltype, n) : 0;
}

inline void __cdecl operator delete(void * p)
{
if (p) ExFreePool(p);
}

inline void __cdecl operator delete[](void * p)
{
if (p) ExFreePool(p);
}

inline void __cdecl operator delete(void * p, POOL_TYPE)
{
if (p) ExFreePool(p);
}

void CpprtStartup();

void CpprtRundown();

#endif // __CPPRT__
------------------------------------------------------------------------------

When building the code, it is necessary to specify the appropriate compiler options (/GR, /EHa,
etc.), reference library cpprt.lib and to heed the warnings about code placement in pageable
sections given in the NuMega and OSR documents. Since there is no exception handler at the top of
every kernel-mode stack, the terminate routine is not called if a C++ exception is not caught - the
system BugChecks instead! set_terminate can be called but it has no effect. The structured exception
translation function does work, so C++ handlers can also catch system exceptions.

The following sample code demonstrates some of the features:
------------------------------------------------------------------------------
extern "C" {
#include <ntddk.h>
}
#include "cpprt.h"
#include <typeinfo>

struct OsException {
OsException(unsigned int code, PEXCEPTION_POINTERS ep = 0) : code(code), ep(ep) {}
unsigned int code;
PEXCEPTION_POINTERS ep;
};

void __cdecl translator(unsigned int code, PEXCEPTION_POINTERS ep)
{
throw OsException(code, ep);
}

struct GlobalTest {
GlobalTest() { DbgPrint("%s constructor\n", typeid(*this).name()); }
~GlobalTest() { DbgPrint("%s destructor\n", typeid(*this).name()); }
} gt[2];

extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT, PUNICODE_STRING)
{
CpprtStartup();

_set_se_translator(translator);

try {
struct Test {
Test() { DbgPrint("%s constructor\n", typeid(*this).name()); }
~Test() { DbgPrint("%s destructor\n", typeid(*this).name()); }
} t[2];

volatile int *x = 0, y = *x;
}
catch (OsException e) {
DbgPrint("Caught test exception %lX\n", e.code);
}
catch (...) {
DbgPrint("Caught exception in catch-all clause\n");
}

KIRQL SaveIrql;

KeRaiseIrql(DISPATCH_LEVEL, &SaveIrql);

try {
struct Test {
Test() { DbgPrint("%s constructor\n", typeid(*this).name()); }
~Test() { DbgPrint("%s destructor\n", typeid(*this).name()); }
} t[2];

throw OsException(0xdead);
}
catch (OsException e) {
DbgPrint("Caught test exception %lX\n", e.code);
}
catch (...) {
DbgPrint("Caught exception in catch-all clause\n");
}

KeLowerIrql(SaveIrql);

CpprtRundown();

return STATUS_UNSUCCESSFUL;
}
------------------------------------------------------------------------------

Gary


Demyn Plantenberg

unread,
Jul 20, 2000, 3:00:00 AM7/20/00
to

"Gary Nebbett" <gary.n...@cp.novartis.com> wrote in message
news:3976...@guardhouse.chbs...

> There are a number of sources of information on using C++ language
features in Windows NT/2000
> kernel-mode code, amongst which are:

Has anyone got STL working in the kernel? Your code was gives us containers
(e.g. vector, map...) and some other stuff, but not strings or streams.


Demyn Plantenberg

unread,
Jul 21, 2000, 3:00:00 AM7/21/00
to
"Gary Nebbett" <gary.n...@cp.novartis.com> wrote in message
news:3976...@guardhouse.chbs...

> extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT, PUNICODE_STRING)
> {
> CpprtStartup();
>
> _set_se_translator(translator);

Does this call need to be added to each dispatch routine, callback, etc.?


(BTW, thanks for your posting. I was about to ask about getting better C++
support in drivers.)
Demyn


Maxim S. Shatskih

unread,
Jul 22, 2000, 3:00:00 AM7/22/00
to
> Has anyone got STL working in the kernel? Your code was gives us
containers
> (e.g. vector, map...) and some other stuff, but not strings or streams.

Rtl generic table is OK for container (for map namely).
Strings are present too :-)

Max

Gary Nebbett

unread,
Jul 24, 2000, 3:00:00 AM7/24/00
to
"Demyn Plantenberg" <de...@almaden.ibm.com> wrote in message news:8l9rh7$e...@fox.almaden.ibm.com...

> "Gary Nebbett" <gary.n...@cp.novartis.com> wrote in message
> news:3976...@guardhouse.chbs...
>
> > extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT, PUNICODE_STRING)
> > {
> > CpprtStartup();
> >
> > _set_se_translator(translator);
>
> Does this call need to be added to each dispatch routine, callback, etc.?

CpprtStartup just needs to be called as early as possible after the driver is loaded (e.g. be the
first routine that DriverEntry calls). CpprtRundown just needs to be called as late possible before
the driver is unloaded (e.g. be the last routine called by the driver unload routine or be the last
routine called by DriverEntry before it returns STATUS_UNSUCCESSFUL).

Gary


Jack Cheng

unread,
Jul 24, 2000, 3:00:00 AM7/24/00
to
Thanx for the info, however, I can not find the directory or object files
mentioned in the batch
commands.

In my installation, I saw crt\src without any further subdir.

Is it on the Visual studio CD ?

Jack

Gary Nebbett wrote in message <3976...@guardhouse.chbs>...

Demyn Plantenberg

unread,
Jul 24, 2000, 3:00:00 AM7/24/00
to

"Gary Nebbett" <gary.n...@cp.novartis.com> wrote in message
news:397b...@guardhouse.chbs...

> "Demyn Plantenberg" <de...@almaden.ibm.com> wrote in message
news:8l9rh7$e...@fox.almaden.ibm.com...
> > "Gary Nebbett" <gary.n...@cp.novartis.com> wrote in message
> > news:3976...@guardhouse.chbs...
> >
> > > extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT, PUNICODE_STRING)
> > > {
> > > CpprtStartup();
> > >
> > > _set_se_translator(translator);
> >
> > Does this call need to be added to each dispatch routine, callback,
etc.?
>
> CpprtStartup just needs to be called as early as possible after the driver
is loaded (e.g. be the
> first routine that DriverEntry calls). CpprtRundown just needs to be
called as late possible before
> the driver is unloaded (e.g. be the last routine called by the driver
unload routine or be the last
> routine called by DriverEntry before it returns STATUS_UNSUCCESSFUL).

Sorry about not being clear. I was wondering if
_set_se_translator(translator) needs to be called whenever we are in an
arbitrary thread?

Demyn.


dave porter

unread,
Jul 24, 2000, 3:00:00 AM7/24/00
to
The CRT source is only installed if
you ask for the CRT source to be installed.

(i.e., do a 'custom' installation of VC++)

--
remove 'z' from my email address


"Jack Cheng" <uch...@lexis-nexis.com> wrote in message
news:8li194$csj$1...@mailgate2.lexis-nexis.com...

Jack Cheng

unread,
Jul 25, 2000, 3:00:00 AM7/25/00
to
Indeed it is there. After I installed it, it compiled without problem. Now I
can
go to next step of experiment !

Thanks,

Jack
dave porter wrote in message <8li3jd$jfv$1...@bob.news.rcn.net>...

Jack Cheng

unread,
Jul 25, 2000, 3:00:00 AM7/25/00
to
Hi all,

I need one more help. At end of "build -cef", it gave me infamous unresolved
external symbol in regard to _set_se_translator.

Am I missing any library ?

Also where should I put the the mentioned compiler flag /GR & /EHa. I asked
this because in the sources file, I have xxxx.rc file. The rc compiler seems
doesn't like these two options.

Thanx,

Jack

Jack Cheng wrote in message <8lk4ah$evl$1...@mailgate2.lexis-nexis.com>...

Reply all
Reply to author
Forward
0 new messages