I started a thread here three days ago about altering the VTable of a class at runtime:
https://groups.google.com/forum/#!topic/comp.lang.c++/q_QY4zNnLJ4
That thread has over a hundred posts now going in various directions so I've created this new thread to specifically look at the following sample program. Note that I'm editing the VTable for the class, rather than altering the pointer-to-Vtable which exists inside any given object.
First of all, here's a sample program for printing incrementing numbers to the screen:
#include <chrono> // milliseconds
#include <thread> // this_thread::sleep_for
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
struct NumberPrinter {
long unsigned i;
virtual void Print(void) = 0;
};
struct DecimalNumberPrinter : NumberPrinter {
void Print(void) { cout << std::dec << i++ << endl; }
};
struct HexadecimalNumberPrinter : NumberPrinter {
void Print(void) { cout << "0x" << std::hex << i++ << endl; }
};
auto main(void) -> int
{
NumberPrinter *p;
cout << "Enter 1 for Decimal, or 2 for Hexadecimal: " << std::flush;
unsigned choice;
cin >> choice;
if ( 2 == choice )
p = new HexadecimalNumberPrinter;
else
p = new DecimalNumberPrinter;
https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
p->i = 0;
for ( ; /* ever */ ; )
{
p->Print();
std::this_thread::sleep_for(std::chrono::milliseconds(100u));
}
}
And so next I want to add code to the loop in 'main' to simulate the firing of an interrupt (which then synchronously calls the function Interrupt_Routine and then hands control back to main).
The following code compiles and runs properly on Linux using the GNU compiler, and also on MS-Windows using the Visual C++ compiler.
I _do_ have an understanding of how different compilers handle pointers to methods, as explained here:
https://www.codeproject.com/Articles/7150/Member-Function-Pointers-and-the-Fastest-Possible
However I _don't_ fully understand how and why I was able to get the code to work for MS-Visual (for instance I don't know why it worked when I added the magic number 88 to the address). So if anyone can look further into this with me and help me understand then I'd greatly appreciate it!
On the website, "
rextester.com", this code works properly for GNU and VC++. It fails for 'clang' because the two method pointers are equal in value (I got this same behaviour on VC++ and had to work around it as you can see).
When you run this program, if you select (1) for decimal, then it should print 5 numbers in decimal format, the it will switch to hexadecimal for the next 5, and then switch back to decimal. It will keep alternating like that for every 5 iterations.
extern bool Check_For_Interrupt(void); // Defined below main
extern void Interrupt_Routine(void); // Defined below main
#include <chrono> // milliseconds
#include <thread> // this_thread::sleep_for
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
struct NumberPrinter {
long unsigned i;
virtual void Print(void) = 0;
};
struct DecimalNumberPrinter : NumberPrinter {
void Print(void) { cout << std::dec << i++ << endl; }
};
struct HexadecimalNumberPrinter : NumberPrinter {
void Print(void) { cout << "0x" << std::hex << i++ << endl; }
};
auto main(void) -> int
{
NumberPrinter *p;
cout << "Enter 1 for Decimal, or 2 for Hexadecimal: " << std::flush;
unsigned choice;
cin >> choice;
if ( 2 == choice )
p = new HexadecimalNumberPrinter;
else
p = new DecimalNumberPrinter;
p->i = 0;
for ( ; /* ever */ ; )
{
p->Print();
std::this_thread::sleep_for(std::chrono::milliseconds(100u));
if ( Check_For_Interrupt() )
Interrupt_Routine();
}
}
bool Check_For_Interrupt(void)
{
// Interrupt after every 5 iterations
static unsigned i = 0u;
if ( i++ > 3u )
{
i = 0u;
return true;
}
return false;
}
struct VTable {
void (*funcptr[1u])(void);
};
#include <cstdint> // uintptr_t, uint32_t, uint64_t
#ifdef _WIN32
extern "C" int VirtualProtect(
uint64_t lpAddress,
uint64_t dwSize,
uint32_t flNewProtect,
uint32_t *lpflOldProtect
);
struct SYSTEM_INFO {
char stuff[4];
uint32_t dwPageSize;
char more_stuff[128];
};
extern "C" void GetSystemInfo(
uint64_t lpSystemInfo
);
#else
extern "C" uint32_t sysconf(int32_t name);
extern "C" int32_t mprotect(uint64_t addr, uint64_t len, int32_t prot);
#endif
void Set_Writeability_Of_Memory(void (**const p)(void), bool const writeable)
{
union {
void *p_start_of_page;
std::uintptr_t i_start_of_page;
};
p_start_of_page = p;
static std::uintptr_t page_size;
#ifdef _WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo((uint64_t)&sysinfo);
page_size = sysinfo.dwPageSize;
//cout << "Page size == " << page_size << endl;
i_start_of_page -= (i_start_of_page % page_size);
uint32_t old_perms;
VirtualProtect(i_start_of_page, page_size, 0x04 /*PAGE_READWRITE*/, &old_perms);
#else
// Linux
page_size = sysconf( 30 /*_SC_PAGE_SIZE*/);
i_start_of_page -= (i_start_of_page % page_size);
mprotect(i_start_of_page, page_size, 1 /*PROT_READ*/ | (writeable ? 2 /*PROT_WRITE*/ : 0u));
#endif
}
bool Try_Replace_Entry_In_VTable(VTable *const pvtable, void (*const before)(void), void (*const after)(void))
{
unsigned const how_many_pointers_to_try = 5u;
for (unsigned i = 0; i != how_many_pointers_to_try; ++i)
{
if ( before == pvtable->funcptr[i] )
{
Set_Writeability_Of_Memory(&pvtable->funcptr[i], true);
pvtable->funcptr[i] = after;
Set_Writeability_Of_Memory(&pvtable->funcptr[i], false);
return true;
}
}
return false;
}
void Interrupt_Routine(void)
{
static bool alternator = false;
// cout << "Entered Interrupt Routine" << endl;
alternator = !alternator;
DecimalNumberPrinter obj;
#ifdef _WIN32
// This bit is complicated with the MS-Visual compiler
void (DecimalNumberPrinter::* mfp_address_of_decimal_func)(void) = &DecimalNumberPrinter::Print;
void (HexadecimalNumberPrinter::* mfp_address_of_hexadecimal_func)(void) = &HexadecimalNumberPrinter::Print;
char *addr_dec_alpha = (char*)&(void*&)mfp_address_of_decimal_func;
char *addr_hex_alpha = (char*)&(void*&)mfp_address_of_hexadecimal_func;
char *addr_dec_beta = (char*)(void*&)mfp_address_of_decimal_func;
char *addr_hex_beta = (char*)(void*&)mfp_address_of_hexadecimal_func;
addr_dec_beta += 0x14;
addr_hex_beta += 0x14;
addr_hex_beta += addr_hex_alpha - addr_dec_alpha + (11u * 8u);
void (*address_of_decimal_func)(void) = (void (*)(void))addr_dec_beta;
void (*address_of_hexadecimal_func)(void) = (void (*)(void))addr_hex_beta;
#else
// This bit is easy with the GNU compiler
void (*const address_of_decimal_func)(void) = reinterpret_cast<void(*)(void)>(&DecimalNumberPrinter::Print);
void (*const address_of_hexadecimal_func)(void) = reinterpret_cast<void(*)(void)>(&HexadecimalNumberPrinter::Print);
#endif
void (*const before)(void) = (alternator ? address_of_decimal_func : address_of_hexadecimal_func);
void (*const after)(void) = (alternator ? address_of_hexadecimal_func : address_of_decimal_func);
//cout << "Before: " << before << endl;
//cout << "After: " << after << endl;
if ( before == after )
cout << "Why the hell are these equal?" << endl; // I needed this when debugging MS-Visual and clang
// - - - - - - - - Pointer to vtable might be at the beginning of the object
if ( sizeof(obj) < sizeof(void(*)(void)) )
return;
VTable *pvtable = reinterpret_cast<VTable*>( *reinterpret_cast<void**>(&obj) );
if ( Try_Replace_Entry_In_VTable(pvtable, before, after) )
{
return;
}
// - - - - - - - - Or let's try at the end
if ( sizeof(obj) < sizeof(void(*)(void))+1u )
return;
pvtable = reinterpret_cast<VTable*>( reinterpret_cast<char*>(&obj + 1u) - sizeof(void*) );
// Watch out for unalligned memory access in the next line
if ( Try_Replace_Entry_In_VTable(pvtable, before, after) )
{
return;
}
// - - - - - - - - Or at the end but before the final padding bytes
if ( sizeof(obj) < sizeof(void(*)(void))+2u )
return;
for (unsigned i = 0; i != (sizeof(obj) - 1u - sizeof(void(*)(void))); ++i)
{
pvtable = reinterpret_cast<VTable*>( reinterpret_cast<char*>(pvtable) - 1u );
// Watch out for unalligned memory access in the next line
if ( Try_Replace_Entry_In_VTable(pvtable, before, after) )
{
return;
}
}
}