Date: Tue, 30 Jul 2024 00:36:53 +0100
On Mon, Jul 29, 2024 at 9:49 PM Thiago Macieira wrote:
>
>
> You missed my point of reentrancy: what happens if this function is reentered
> on the same thread while it's already running?
>
> Why would it do that? Well, why wouldn't it do that? You want to contemplate
> this as a Standard feature, so it needs to contemplate the use-case when the
> function is recursive.
>
> int OpenSocket(int af, std::string_view address)
> {
> if (af == AF_INET)
> return OpenSocket(AF_INET6, std::string("::ffff:") + address);
> return OpenSocket_inner(address);
> }
I am loath to be a one trick pony and so I've switched operating
system and also switched compiler.
Here's your recursive interceptor function running on MS-Windows
x86_32 with the VC++ compiler:
https://godbolt.org/z/f3rfhdshx
And here it is copy-pasted:
#include <cassert> // assert
#include <cstdint> // uintptr_t
#include <iostream> // cout, endl
#include <mutex> // recursive_mutex
extern "C" void* __stdcall LoadLibraryA(char const*);
extern "C" void* __stdcall GetProcAddress(void*, char const*);
// ===============================================
// To change the library name or function name,
// only edit the following two lines:
#define MACRO_FILE kernel32.dll
#define MACRO_FUNC EnumResourceTypesA
// -----------------------------------------------
// Don't edit the next five lines:
#define Q(x) #x
#define QUOTE(x) Q(x)
extern "C" void MACRO_FUNC(void);
constexpr char str_func[] = QUOTE(MACRO_FUNC);
constexpr char str_file[] = QUOTE(MACRO_FILE);
// ===============================================
std::recursive_mutex m;
// A thread_local variable is used to store
// the original return address because we
// can neither use a caller-saved nor a
// callee-saved register for this purpose
constexpr unsigned max_recursion = 16u;
thread_local unsigned count_recursion = -1;
thread_local void (*original_return_address[max_recursion])(void);
// The "Lock" function does 4 things:
// (1) Lock the mutex
// (2) Store the original return address
// inside a thread_local variable
// (3) Log the calling of the function
// (4) Return the address of the original
// function
extern "C" auto __cdecl Inwards( void (*const arg)(void) ) -> void(*)(void)
{
++count_recursion;
m.lock();
std::cout << "Mutex is locked\n";
assert( count_recursion < max_recursion );
original_return_address[count_recursion] = arg;
std::cout << "Function called: '" << str_func << " (recursion
depth = " << count_recursion << ")\n";
auto const h = LoadLibraryA(str_file);
assert( nullptr != h );
auto const pf = GetProcAddress(h, str_func);
assert( nullptr != pf );
return (void(*)(void))pf;
}
// The "Unlock" function does 3 things:
// (1) Unlock the mutex
// (2) Log the return of the original function
// (3) Return the original return address
extern "C" auto __cdecl Outwards(void) -> void(*)(void)
{
std::cout << "Function '" << str_func << "' has returned from
recursive call " << count_recursion << "\n";
m.unlock();
std::cout << "Mutex has been unlocked\n";
return original_return_address[count_recursion--];
}
void __declspec(naked) MACRO_FUNC(void)
{
__asm {
mov eax, [esp] // save original return address
pushf
push ebp
push ebx
push edi
push esi
push eax // set 1st argument = return address
call Inwards
add esp, 4 // remove 1st argument from stack
pop esi
pop edi
pop ebx
pop ebp
popf
add esp, 4 // remove return address from top of stack
lea edx, come_back_here
push edx // set the new return address at top of stack
jmp eax // jump to original function
come_back_here:
pushf
push ebp
push ebx
push edi
push esi
call Outwards
pop esi
pop edi
pop ebx
pop ebp
popf
jmp eax // jump back to caller
}
}
// ======================================================
// ======================================================
// ======================================================
// ======================================================
// Now let's test it out:
int __stdcall MyCallback( void *const h, char const *const s, void
(*const arg)(void) )
{
if ( 0u == ((uintptr_t)s & 0xFFFF0000) )
{
if ( LoadLibraryA("user32.dll") != h )
{
auto const pf = (int(__stdcall*)(void*, void*, void*))arg;
pf( LoadLibraryA("user32.dll"), MyCallback, &EnumResourceTypesA );
}
return 0;
}
if ( s && *s ) std::cout << "Resource Type: " << s << std::endl;
return 1;
}
int main(void)
{
std::cout << "First line in main.\n";
auto const pf = (int(__stdcall*)(void*, void*, void*))&EnumResourceTypesA;
pf( LoadLibraryA("shell32.dll"), MyCallback, &EnumResourceTypesA );
std::cout << "Last line in main.\n";
}
>
>
> You missed my point of reentrancy: what happens if this function is reentered
> on the same thread while it's already running?
>
> Why would it do that? Well, why wouldn't it do that? You want to contemplate
> this as a Standard feature, so it needs to contemplate the use-case when the
> function is recursive.
>
> int OpenSocket(int af, std::string_view address)
> {
> if (af == AF_INET)
> return OpenSocket(AF_INET6, std::string("::ffff:") + address);
> return OpenSocket_inner(address);
> }
I am loath to be a one trick pony and so I've switched operating
system and also switched compiler.
Here's your recursive interceptor function running on MS-Windows
x86_32 with the VC++ compiler:
https://godbolt.org/z/f3rfhdshx
And here it is copy-pasted:
#include <cassert> // assert
#include <cstdint> // uintptr_t
#include <iostream> // cout, endl
#include <mutex> // recursive_mutex
extern "C" void* __stdcall LoadLibraryA(char const*);
extern "C" void* __stdcall GetProcAddress(void*, char const*);
// ===============================================
// To change the library name or function name,
// only edit the following two lines:
#define MACRO_FILE kernel32.dll
#define MACRO_FUNC EnumResourceTypesA
// -----------------------------------------------
// Don't edit the next five lines:
#define Q(x) #x
#define QUOTE(x) Q(x)
extern "C" void MACRO_FUNC(void);
constexpr char str_func[] = QUOTE(MACRO_FUNC);
constexpr char str_file[] = QUOTE(MACRO_FILE);
// ===============================================
std::recursive_mutex m;
// A thread_local variable is used to store
// the original return address because we
// can neither use a caller-saved nor a
// callee-saved register for this purpose
constexpr unsigned max_recursion = 16u;
thread_local unsigned count_recursion = -1;
thread_local void (*original_return_address[max_recursion])(void);
// The "Lock" function does 4 things:
// (1) Lock the mutex
// (2) Store the original return address
// inside a thread_local variable
// (3) Log the calling of the function
// (4) Return the address of the original
// function
extern "C" auto __cdecl Inwards( void (*const arg)(void) ) -> void(*)(void)
{
++count_recursion;
m.lock();
std::cout << "Mutex is locked\n";
assert( count_recursion < max_recursion );
original_return_address[count_recursion] = arg;
std::cout << "Function called: '" << str_func << " (recursion
depth = " << count_recursion << ")\n";
auto const h = LoadLibraryA(str_file);
assert( nullptr != h );
auto const pf = GetProcAddress(h, str_func);
assert( nullptr != pf );
return (void(*)(void))pf;
}
// The "Unlock" function does 3 things:
// (1) Unlock the mutex
// (2) Log the return of the original function
// (3) Return the original return address
extern "C" auto __cdecl Outwards(void) -> void(*)(void)
{
std::cout << "Function '" << str_func << "' has returned from
recursive call " << count_recursion << "\n";
m.unlock();
std::cout << "Mutex has been unlocked\n";
return original_return_address[count_recursion--];
}
void __declspec(naked) MACRO_FUNC(void)
{
__asm {
mov eax, [esp] // save original return address
pushf
push ebp
push ebx
push edi
push esi
push eax // set 1st argument = return address
call Inwards
add esp, 4 // remove 1st argument from stack
pop esi
pop edi
pop ebx
pop ebp
popf
add esp, 4 // remove return address from top of stack
lea edx, come_back_here
push edx // set the new return address at top of stack
jmp eax // jump to original function
come_back_here:
pushf
push ebp
push ebx
push edi
push esi
call Outwards
pop esi
pop edi
pop ebx
pop ebp
popf
jmp eax // jump back to caller
}
}
// ======================================================
// ======================================================
// ======================================================
// ======================================================
// Now let's test it out:
int __stdcall MyCallback( void *const h, char const *const s, void
(*const arg)(void) )
{
if ( 0u == ((uintptr_t)s & 0xFFFF0000) )
{
if ( LoadLibraryA("user32.dll") != h )
{
auto const pf = (int(__stdcall*)(void*, void*, void*))arg;
pf( LoadLibraryA("user32.dll"), MyCallback, &EnumResourceTypesA );
}
return 0;
}
if ( s && *s ) std::cout << "Resource Type: " << s << std::endl;
return 1;
}
int main(void)
{
std::cout << "First line in main.\n";
auto const pf = (int(__stdcall*)(void*, void*, void*))&EnumResourceTypesA;
pf( LoadLibraryA("shell32.dll"), MyCallback, &EnumResourceTypesA );
std::cout << "Last line in main.\n";
}
Received on 2024-07-29 23:37:02