Date: Sat, 6 Apr 2024 21:04:24 +0100
Earlier this week, I wrote:
> Before I go any further with this, I have to see if it can be
> implemented with the Microsoft compiler. Because if I can get it
> working with Microsoft, then I'll have the top 4 compilers.
Implementing this on the Microsoft compiler without compiler support
has been tricky.
On the GNU, LLVM and Intel compilers, you only need a 'type_info' in
order to throw and catch an exception. However on Microsoft, when you
throw an exception, they have a 'ThrowInfo' struct which contains an
array of pointers to 'type_info' structs for all the base classes of
the type being thrown.
So on Microsoft, if you throw a 'std::runtime_error', you have a
'ThrowInfo' struct which contains pointers to:
typeid(std::exception)
typeid(std::runtime_error)
Since I don't have compiler support to find the ThrowInfo's address
relative to the type_info's address, I had to write a function that
does a little gymnastics moving back and forth in memory to find it
(see my comments in the below code).
So then inside my 'base' function, I convert the supplied 'type_info'
to a 'ThrowInfo', I iterate throw all the contained type_info's, and
if there's a match, I adjust the 'this' pointer and return it. In
order to adjust the 'this' pointer, I use the "_PMD::mdisp" field
however I'm not sure if this is correct (I see that there's also a
'pdisp' and a 'vdisp' but I don't know what's what).
The following code is tested and working on 32-Bit and 64-Bit x86
MS-Windows 10. In order to make it work properly with complex virtual
inheritance, maybe you need to also factor in "_PMD::pdisp" and
"_PMD::vdisp". I don't know.
I went to the bother of coding this pretty much just as a 'proof of
concept' -- because realistically, if a compiler vendor were to
implement this, they'd probably just re-use the code they have for
'dynamic_cast'. Anyway here's the code:
#include <cstdint> // int32_t
#include <typeinfo> // type_info
#include <type_traits> // conditional, is_const
// The following struct is used on both 32-Bit and 64-Bit
// Microsoft Visual C++ compilers for holding info needed
// to catch C++ exceptions.
// On 32-Bit machines, each member is a pointer.
// On 64-Bit machines, each member is a 32-Bit offset which
// is to be added onto the program's
// base address (see 'addr0' below).
struct ThrowInfo {
std::int32_t attributes,
pmfnUnwind,
pForwardCompat,
pCatchableTypeArray;
};
#ifdef _WIN64
extern "C" char *GetModuleHandleA(char const*);
char const *const addr0 = GetModuleHandleA(nullptr);
#else
char const *const addr0 = nullptr;
#endif
// In the following function, I do a bit of gymnastics
// to try find the address of the 'ThrowInfo' relative
// to the address of the 'type_info'.
// See the comments inside the function.
ThrowInfo const *TypeInfo_To_ThrowInfo(std::type_info const *const pti)
{
using std::int32_t;
// Step 1: Moving backwards, find the address of
// the type_info inside the CatchableType
int32_t const n0 = (char*)pti - addr0, *p0 = (int32_t*)pti;
for (;;)
{
if ( n0 != *--p0 ) continue;
//cout << "Found the address of the type_info inside the
CatchableType: " << (void*)p0 << endl;
// Step 2: Calculate the address of the CatchableType
// (it's 4 bytes behind the address of the type_info);
char const *const pCatchableType = (char*)(p0 - 1);
// Step 3: Moving forwards, find the address of the
// CatchableType inside the CatchableTypeArray
int32_t const n1 = pCatchableType - addr0, *p1 =
(int32_t*)pCatchableType;
for (;;)
{
if ( n1 != *++p1 ) continue;
//cout << "Found the address of the CatchableType inside
the CatchableTypeArray" << (void*)p1 << endl;
for (;;)
{
// Step 4: Calculate the address of the CatchableTypeArray
// by moving backward until we find a small number
if ( *--p1 > 16 ) continue;
//cout << "Found the address of the
CatchableTypeArray: " << (void*)p1 << endl;
// Step 4: Calculate the address of the _ThrowInfo by
// subtracting 16 bytes (32-Bit) or 32 bytes (64-Bit)
ThrowInfo *const pthrowinfo = (ThrowInfo*)(p1 - sizeof(void*));
//cout << "Found the address of the ThrowInfo: " <<
(void*)pthrowinfo << endl;
return pthrowinfo;
}
}
}
}
template<class Base>
Base *base(std::conditional_t<std::is_const_v<Base>,void const,void>
*const parg, std::type_info const &ti)
{
ThrowInfo const *const pthi = TypeInfo_To_ThrowInfo(&ti);
struct CatchableTypeArray { int32_t nCatchableTypes,
arrayOfCatchableTypes[]; };
struct CatchableType { int32_t properties, pType; _PMD
thisDisplacement; };
CatchableType const *p = nullptr;
CatchableTypeArray const *const pcta = (CatchableTypeArray*)(addr0
+ pthi->pCatchableTypeArray);
for ( unsigned i = 0u; i < pcta->nCatchableTypes; ++i )
{
CatchableType const *const pct = (CatchableType *)(addr0 +
pcta->arrayOfCatchableTypes[i]);
std::type_info const *const pti = (std::type_info*)(addr0 + pct->pType);
if ( &ti == pti )
{
p = pct;
break;
}
}
if ( nullptr == p ) return nullptr;
return (Base*) ((char*)parg + p->thisDisplacement.mdisp); // Or
pdisp? Or vdisp? I don't know.
}
// ========================== Here comes the test code
#include <cassert> // assert
#include <any> // any
#include <iostream> // cout, endl
#include <stdexcept> // exception, runtime_error
using std::cout, std::endl;
void *pointer_from_any(std::any const &a)
{
if ( false == a.has_value() ) return nullptr;
return (void*)&a;
}
char const *get_what(std::any const &a)
{
void *const pderived = pointer_from_any(a);
if ( nullptr == pderived ) return "<no info>";
auto *const pbase = base<std::exception>( pderived, a.type() );
if ( nullptr == pbase ) return "<no info>";
cout << "Address of base inside function 'get_what': " <<
(void*)pbase << endl;
return pbase->what();
}
int main(void)
{
std::any a = std::runtime_error("Frogs don't have wings");
auto &re = std::any_cast<std::runtime_error&>(a);
cout << "The following three pointers should be equal:\n";
cout << pointer_from_any(a) << endl;
cout << (void*)&re << endl;
cout << (void*)static_cast<std::exception*>(&re) << endl;
cout << get_what(a) << endl;
// The invocation of 'base' on the next line should fail
// because 'logic_error' isn't a base of 'runtime_error'
std::logic_error *const ple = base<std::logic_error>(&re, a.type());
assert( nullptr == ple );
// The next line is need to ensure that the compiler
// writes the ThrowInfo for std::runtime_error into
// the final executable file.
try { throw std::runtime_error("frog"); } catch(...){}
}
> Before I go any further with this, I have to see if it can be
> implemented with the Microsoft compiler. Because if I can get it
> working with Microsoft, then I'll have the top 4 compilers.
Implementing this on the Microsoft compiler without compiler support
has been tricky.
On the GNU, LLVM and Intel compilers, you only need a 'type_info' in
order to throw and catch an exception. However on Microsoft, when you
throw an exception, they have a 'ThrowInfo' struct which contains an
array of pointers to 'type_info' structs for all the base classes of
the type being thrown.
So on Microsoft, if you throw a 'std::runtime_error', you have a
'ThrowInfo' struct which contains pointers to:
typeid(std::exception)
typeid(std::runtime_error)
Since I don't have compiler support to find the ThrowInfo's address
relative to the type_info's address, I had to write a function that
does a little gymnastics moving back and forth in memory to find it
(see my comments in the below code).
So then inside my 'base' function, I convert the supplied 'type_info'
to a 'ThrowInfo', I iterate throw all the contained type_info's, and
if there's a match, I adjust the 'this' pointer and return it. In
order to adjust the 'this' pointer, I use the "_PMD::mdisp" field
however I'm not sure if this is correct (I see that there's also a
'pdisp' and a 'vdisp' but I don't know what's what).
The following code is tested and working on 32-Bit and 64-Bit x86
MS-Windows 10. In order to make it work properly with complex virtual
inheritance, maybe you need to also factor in "_PMD::pdisp" and
"_PMD::vdisp". I don't know.
I went to the bother of coding this pretty much just as a 'proof of
concept' -- because realistically, if a compiler vendor were to
implement this, they'd probably just re-use the code they have for
'dynamic_cast'. Anyway here's the code:
#include <cstdint> // int32_t
#include <typeinfo> // type_info
#include <type_traits> // conditional, is_const
// The following struct is used on both 32-Bit and 64-Bit
// Microsoft Visual C++ compilers for holding info needed
// to catch C++ exceptions.
// On 32-Bit machines, each member is a pointer.
// On 64-Bit machines, each member is a 32-Bit offset which
// is to be added onto the program's
// base address (see 'addr0' below).
struct ThrowInfo {
std::int32_t attributes,
pmfnUnwind,
pForwardCompat,
pCatchableTypeArray;
};
#ifdef _WIN64
extern "C" char *GetModuleHandleA(char const*);
char const *const addr0 = GetModuleHandleA(nullptr);
#else
char const *const addr0 = nullptr;
#endif
// In the following function, I do a bit of gymnastics
// to try find the address of the 'ThrowInfo' relative
// to the address of the 'type_info'.
// See the comments inside the function.
ThrowInfo const *TypeInfo_To_ThrowInfo(std::type_info const *const pti)
{
using std::int32_t;
// Step 1: Moving backwards, find the address of
// the type_info inside the CatchableType
int32_t const n0 = (char*)pti - addr0, *p0 = (int32_t*)pti;
for (;;)
{
if ( n0 != *--p0 ) continue;
//cout << "Found the address of the type_info inside the
CatchableType: " << (void*)p0 << endl;
// Step 2: Calculate the address of the CatchableType
// (it's 4 bytes behind the address of the type_info);
char const *const pCatchableType = (char*)(p0 - 1);
// Step 3: Moving forwards, find the address of the
// CatchableType inside the CatchableTypeArray
int32_t const n1 = pCatchableType - addr0, *p1 =
(int32_t*)pCatchableType;
for (;;)
{
if ( n1 != *++p1 ) continue;
//cout << "Found the address of the CatchableType inside
the CatchableTypeArray" << (void*)p1 << endl;
for (;;)
{
// Step 4: Calculate the address of the CatchableTypeArray
// by moving backward until we find a small number
if ( *--p1 > 16 ) continue;
//cout << "Found the address of the
CatchableTypeArray: " << (void*)p1 << endl;
// Step 4: Calculate the address of the _ThrowInfo by
// subtracting 16 bytes (32-Bit) or 32 bytes (64-Bit)
ThrowInfo *const pthrowinfo = (ThrowInfo*)(p1 - sizeof(void*));
//cout << "Found the address of the ThrowInfo: " <<
(void*)pthrowinfo << endl;
return pthrowinfo;
}
}
}
}
template<class Base>
Base *base(std::conditional_t<std::is_const_v<Base>,void const,void>
*const parg, std::type_info const &ti)
{
ThrowInfo const *const pthi = TypeInfo_To_ThrowInfo(&ti);
struct CatchableTypeArray { int32_t nCatchableTypes,
arrayOfCatchableTypes[]; };
struct CatchableType { int32_t properties, pType; _PMD
thisDisplacement; };
CatchableType const *p = nullptr;
CatchableTypeArray const *const pcta = (CatchableTypeArray*)(addr0
+ pthi->pCatchableTypeArray);
for ( unsigned i = 0u; i < pcta->nCatchableTypes; ++i )
{
CatchableType const *const pct = (CatchableType *)(addr0 +
pcta->arrayOfCatchableTypes[i]);
std::type_info const *const pti = (std::type_info*)(addr0 + pct->pType);
if ( &ti == pti )
{
p = pct;
break;
}
}
if ( nullptr == p ) return nullptr;
return (Base*) ((char*)parg + p->thisDisplacement.mdisp); // Or
pdisp? Or vdisp? I don't know.
}
// ========================== Here comes the test code
#include <cassert> // assert
#include <any> // any
#include <iostream> // cout, endl
#include <stdexcept> // exception, runtime_error
using std::cout, std::endl;
void *pointer_from_any(std::any const &a)
{
if ( false == a.has_value() ) return nullptr;
return (void*)&a;
}
char const *get_what(std::any const &a)
{
void *const pderived = pointer_from_any(a);
if ( nullptr == pderived ) return "<no info>";
auto *const pbase = base<std::exception>( pderived, a.type() );
if ( nullptr == pbase ) return "<no info>";
cout << "Address of base inside function 'get_what': " <<
(void*)pbase << endl;
return pbase->what();
}
int main(void)
{
std::any a = std::runtime_error("Frogs don't have wings");
auto &re = std::any_cast<std::runtime_error&>(a);
cout << "The following three pointers should be equal:\n";
cout << pointer_from_any(a) << endl;
cout << (void*)&re << endl;
cout << (void*)static_cast<std::exception*>(&re) << endl;
cout << get_what(a) << endl;
// The invocation of 'base' on the next line should fail
// because 'logic_error' isn't a base of 'runtime_error'
std::logic_error *const ple = base<std::logic_error>(&re, a.type());
assert( nullptr == ple );
// The next line is need to ensure that the compiler
// writes the ThrowInfo for std::runtime_error into
// the final executable file.
try { throw std::runtime_error("frog"); } catch(...){}
}
Received on 2024-04-06 20:04:37