Add stack guard page to all platforms.

This commit is contained in:
Kuba Sunderland-Ober 2023-08-06 22:50:02 +00:00 committed by Mark Roszko
parent b993311d47
commit f7fe411cf5
1 changed files with 78 additions and 59 deletions

View File

@ -43,6 +43,7 @@
#include <libcontext.h>
#include <functional>
#include <optional>
#include <memory>
#include <advanced_config.h>
@ -50,8 +51,10 @@
#include <wx/log.h>
#ifdef _WIN32
#include <optional>
#include <windows.h>
#else // Linux, BSD, MacOS
#include <unistd.h> // getpagesize
#include <sys/mman.h> // mmap, mprotect, munmap
#endif
/**
@ -192,9 +195,6 @@ public:
* Create a coroutine from a delegate object.
*/
COROUTINE( std::function<ReturnType( ArgType )> aEntry ) :
#ifdef _WIN32
m_stack( nullptr ),
#endif
m_func( std::move( aEntry ) ),
m_running( false ),
m_args( nullptr ),
@ -223,11 +223,6 @@ public:
if( m_callee.ctx )
libcontext::release_fcontext( m_callee.ctx );
#ifdef _WIN32
if( m_stack )
::VirtualFree( m_stack, 0, MEM_RELEASE );
#endif
}
public:
@ -382,71 +377,34 @@ private:
m_args = &aArgs;
assert( m_stack == nullptr );
size_t stackSize = m_stacksize;
std::size_t stackSize = m_stacksize;
void* sp = nullptr;
#ifndef LIBCONTEXT_HAS_OWN_STACK
#ifdef _WIN32
// kind of a hack to avoid calling into GetSystemInfo constantly for page size
static std::optional<std::size_t> system_page_size;
if( !system_page_size.has_value() )
{
// For Windows, we want to use VirtualAlloc and VirtualProtect which let us
// trap stack overflows with the guard page, it'll still crash but this
// has a chance of preserving stack in dumps
SYSTEM_INFO si;
::GetSystemInfo( &si );
assert( !m_stack );
system_page_size = static_cast<std::size_t>( si.dwPageSize );
}
// this shouldn't happen but just incase
if( m_stack )
::VirtualFree( m_stack, 0, MEM_RELEASE );
const std::size_t systemPageSize = SystemPageSize();
// calculate the correct number of pages to allocate based on request stack size
std::size_t pages = ( m_stacksize + system_page_size.value() - 1 ) / system_page_size.value();
std::size_t pages = ( m_stacksize + systemPageSize - 1 ) / systemPageSize;
// we allocate an extra page for the guard
std::size_t alloc_size = ( pages + 1 ) * system_page_size.value();
stackSize = ( pages + 1 ) * systemPageSize;
m_stack = ::VirtualAlloc( 0, alloc_size, MEM_COMMIT, PAGE_READWRITE );
if( !m_stack )
throw std::bad_alloc();
m_stack.reset( static_cast<char*>( MapMemory( stackSize ) ) );
m_stack.get_deleter().SetSize( stackSize );
// now configure the first page (by only specifying a single page_size from vp)
// that will act as the guard page
// the stack will grow from the end and hopefully never into this guarded region
DWORD old_prot; // dummy var since the arg cannot be NULL
const BOOL res = ::VirtualProtect( m_stack, system_page_size.value(),
PAGE_READWRITE | PAGE_GUARD, &old_prot );
GuardMemory( m_stack.get(), systemPageSize );
#ifdef NDEBUG
// Avoid compil warning (unused variable 'res')
(void) res;
#else
assert( res != false );
#endif
stackSize = alloc_size;
sp = static_cast<char*>( m_stack ) + stackSize;
#else
// fixme: Clean up stack stuff. Add a guard
m_stack.reset( new char[stackSize] );
// align to 16 bytes
sp = (void*)((((ptrdiff_t) m_stack.get()) + stackSize - 0xf) & (~0x0f));
// correct the stack size
stackSize -= size_t( ( (ptrdiff_t) m_stack.get() + stackSize ) - (ptrdiff_t) sp );
sp = static_cast<char*>( m_stack.get() ) + stackSize;
#ifdef KICAD_USE_VALGRIND
m_valgrind_stack = VALGRIND_STACK_REGISTER( sp, m_stack.get() );
#endif
#endif
#endif
#ifdef KICAD_SANITIZE_THREADS
// Create a new fiber to go with the new context
@ -465,6 +423,69 @@ private:
return jumpIn( aInvArgs );
}
#ifndef LIBCONTEXT_HAS_OWN_STACK
///< A functor that frees the stack
struct STACK_DELETER
{
#ifdef _WIN32
void SetSize( std::size_t ) {}
void operator()( void* aMem ) noexcept { ::VirtualFree( aMem, 0, MEM_RELEASE ); }
#else
std::size_t m_size = 0;
void SetSize( std::size_t aSize ) { m_size = aSize; }
void operator()( void* aMem ) noexcept { ::munmap( aMem, m_size ); }
#endif
};
///< The size of the mappable memory page size
static inline size_t SystemPageSize()
{
static std::optional<size_t> systemPageSize;
if( !systemPageSize.has_value() )
{
#ifdef _WIN32
SYSTEM_INFO si;
::GetSystemInfo( &si );
systemPageSize = static_cast<size_t>( si.dwPageSize );
#else
int size = getpagesize();
systemPageSize = static_cast<size_t>( size );
#endif
}
return systemPageSize.value();
}
///< Map a page-aligned memory region into our address space.
static inline void* MapMemory( size_t aAllocSize )
{
#ifdef _WIN32
void* mem = ::VirtualAlloc( 0, aAllocSize, MEM_COMMIT, PAGE_READWRITE );
if( !mem )
throw std::bad_alloc();
#else
void* mem = ::mmap( 0, aAllocSize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 );
if( mem == (void*) -1 )
throw std::bad_alloc();
#endif
return mem;
}
///< Change protection of memory page(s) to act as stack guards.
static inline void GuardMemory( void* aAddress, size_t aGuardSize )
{
#ifdef _WIN32
DWORD old_prot; // dummy var since the arg cannot be NULL
BOOL res = ::VirtualProtect( aAddress, aGuardSize,
PAGE_READWRITE | PAGE_GUARD, &old_prot );
#else
bool res = ( 0 == ::mprotect( aAddress, aGuardSize, PROT_NONE ) );
#endif
if( !res )
wxLogTrace( kicadTraceCoroutineStack, wxT( "COROUTINE::GuardMemory has failes" ) );
}
#endif // LIBCONTEXT_HAS_OWN_STACK
INVOCATION_ARGS* doResume( INVOCATION_ARGS* args )
{
return jumpIn( args );
@ -532,11 +553,9 @@ private:
}
}
#ifndef LIBCONTEXT_HAS_OWN_STACK
///< coroutine stack
#ifdef _WIN32
void* m_stack;
#else
std::unique_ptr<char[]> m_stack;
std::unique_ptr<char[], struct STACK_DELETER> m_stack;
#endif
int m_stacksize;