mirror of
https://github.com/Ed94/raddebugger.git
synced 2026-10-01 21:48:41 +00:00
Add a shared multi-file background writer with lazy worker startup. Represent PDB output as a file artifact, keep PDB output state internal to the builder, and overlap deferred PDB writers with the file maps release.
1981 lines
58 KiB
C
1981 lines
58 KiB
C
// Copyright (c) Epic Games Tools
|
|
// Licensed under the MIT license (https://opensource.org/license/mit/)
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Modern Windows SDK Functions
|
|
//
|
|
// (We must dynamically link to them, since they can be missing in older SDKs)
|
|
|
|
typedef HRESULT W32_SetThreadDescription_Type(HANDLE hThread, PCWSTR lpThreadDescription);
|
|
global W32_SetThreadDescription_Type *w32_SetThreadDescription_func = 0;
|
|
typedef BOOL W32_InitializeSynchronizationBarrier_Type(W32_SYNCHRONIZATION_BARRIER *lpBarrier, LONG lTotalThreads, LONG lSpinCount);
|
|
global W32_InitializeSynchronizationBarrier_Type *w32_InitializeSynchronizationBarrier_func = 0;
|
|
typedef BOOL W32_DeleteSynchronizationBarrier_Type(W32_SYNCHRONIZATION_BARRIER *lpBarrier);
|
|
global W32_DeleteSynchronizationBarrier_Type *w32_DeleteSynchronizationBarrier_func = 0;
|
|
typedef BOOL W32_EnterSynchronizationBarrier_Type(W32_SYNCHRONIZATION_BARRIER *lpBarrier, DWORD dwFlags);
|
|
global W32_EnterSynchronizationBarrier_Type *w32_EnterSynchronizationBarrier_func = 0;
|
|
global RIO_EXTENSION_FUNCTION_TABLE w32_rio_functions = {0};
|
|
|
|
////////////////////////////////
|
|
//~ rjf: File Info Conversion Helpers
|
|
|
|
internal FilePropertyFlags
|
|
w32_file_property_flags_from_dwFileAttributes(DWORD dwFileAttributes)
|
|
{
|
|
FilePropertyFlags flags = 0;
|
|
if(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
flags |= FilePropertyFlag_IsFolder;
|
|
}
|
|
return flags;
|
|
}
|
|
|
|
internal void
|
|
w32_file_properties_from_attribute_data(FileProperties *properties, WIN32_FILE_ATTRIBUTE_DATA *attributes)
|
|
{
|
|
properties->size = Compose64Bit(attributes->nFileSizeHigh, attributes->nFileSizeLow);
|
|
w32_dense_time_from_file_time(&properties->created, &attributes->ftCreationTime);
|
|
w32_dense_time_from_file_time(&properties->modified, &attributes->ftLastWriteTime);
|
|
properties->flags = w32_file_property_flags_from_dwFileAttributes(attributes->dwFileAttributes);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Time Conversion Helpers
|
|
|
|
internal void
|
|
w32_date_time_from_system_time(DateTime *out, SYSTEMTIME *in)
|
|
{
|
|
out->year = in->wYear;
|
|
out->mon = in->wMonth - 1;
|
|
out->wday = in->wDayOfWeek;
|
|
out->day = in->wDay;
|
|
out->hour = in->wHour;
|
|
out->min = in->wMinute;
|
|
out->sec = in->wSecond;
|
|
out->msec = in->wMilliseconds;
|
|
}
|
|
|
|
internal void
|
|
w32_system_time_from_date_time(SYSTEMTIME *out, DateTime *in)
|
|
{
|
|
out->wYear = (WORD)(in->year);
|
|
out->wMonth = in->mon + 1;
|
|
out->wDay = in->day;
|
|
out->wHour = in->hour;
|
|
out->wMinute = in->min;
|
|
out->wSecond = in->sec;
|
|
out->wMilliseconds = in->msec;
|
|
}
|
|
|
|
internal void
|
|
w32_dense_time_from_file_time(DenseTime *out, FILETIME *in)
|
|
{
|
|
SYSTEMTIME systime = {0};
|
|
FileTimeToSystemTime(in, &systime);
|
|
DateTime date_time = {0};
|
|
w32_date_time_from_system_time(&date_time, &systime);
|
|
*out = dense_time_from_date_time(date_time);
|
|
}
|
|
|
|
internal U32
|
|
w32_sleep_ms_from_endt_us(U64 endt_us)
|
|
{
|
|
U32 sleep_ms = 0;
|
|
if(endt_us == max_U64)
|
|
{
|
|
sleep_ms = INFINITE;
|
|
}
|
|
else
|
|
{
|
|
U64 begint = now_time_us();
|
|
if(begint < endt_us)
|
|
{
|
|
U64 sleep_us = endt_us - begint;
|
|
sleep_ms = (U32)((sleep_us + 999)/1000);
|
|
}
|
|
}
|
|
return sleep_ms;
|
|
}
|
|
|
|
internal U32
|
|
w32_unix_time_from_file_time(FILETIME file_time)
|
|
{
|
|
U64 win32_time = ((U64)file_time.dwHighDateTime << 32) | file_time.dwLowDateTime;
|
|
U64 unix_time64 = ((win32_time - 0x19DB1DED53E8000ULL) / 10000000);
|
|
|
|
Assert(unix_time64 <= max_U32);
|
|
U32 unix_time32 = (U32)unix_time64;
|
|
|
|
return unix_time32;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Entity Functions
|
|
|
|
internal W32_Entity *
|
|
w32_entity_alloc(W32_EntityKind kind)
|
|
{
|
|
W32_Entity *result = 0;
|
|
EnterCriticalSection(&w32_state.entity_mutex);
|
|
{
|
|
result = w32_state.entity_free;
|
|
if(result)
|
|
{
|
|
SLLStackPop(w32_state.entity_free);
|
|
}
|
|
else
|
|
{
|
|
result = push_array_no_zero(w32_state.entity_arena, W32_Entity, 1);
|
|
}
|
|
MemoryZeroStruct(result);
|
|
}
|
|
LeaveCriticalSection(&w32_state.entity_mutex);
|
|
result->kind = kind;
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
w32_entity_release(W32_Entity *entity)
|
|
{
|
|
entity->kind = W32_EntityKind_Null;
|
|
EnterCriticalSection(&w32_state.entity_mutex);
|
|
SLLStackPush(w32_state.entity_free, entity);
|
|
LeaveCriticalSection(&w32_state.entity_mutex);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Thread Entry Point
|
|
|
|
internal DWORD
|
|
w32_thread_entry_point(void *ptr)
|
|
{
|
|
W32_Entity *entity = (W32_Entity *)ptr;
|
|
ThreadEntryPointFunctionType *func = entity->thread.func;
|
|
void *thread_ptr = entity->thread.ptr;
|
|
supplement_thread_base_entry_point(func, thread_ptr);
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Debugger Attachment Checking
|
|
|
|
internal B32
|
|
debugger_is_attached(void)
|
|
{
|
|
B32 result = IsDebuggerPresent();
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Platform Time Functions
|
|
|
|
internal U64
|
|
now_time_us(void)
|
|
{
|
|
U64 result = 0;
|
|
LARGE_INTEGER large_int_counter;
|
|
if(QueryPerformanceCounter(&large_int_counter))
|
|
{
|
|
result = (large_int_counter.QuadPart*Million(1))/w32_state.microsecond_resolution;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal U32
|
|
now_time_unix(void)
|
|
{
|
|
FILETIME file_time;
|
|
GetSystemTimeAsFileTime(&file_time);
|
|
U32 unix_time = w32_unix_time_from_file_time(file_time);
|
|
return unix_time;
|
|
}
|
|
|
|
internal DateTime
|
|
now_time_universal(void)
|
|
{
|
|
SYSTEMTIME systime = {0};
|
|
GetSystemTime(&systime);
|
|
DateTime result = {0};
|
|
w32_date_time_from_system_time(&result, &systime);
|
|
return result;
|
|
}
|
|
|
|
internal DateTime
|
|
universal_from_local_time(DateTime *date_time)
|
|
{
|
|
SYSTEMTIME systime = {0};
|
|
w32_system_time_from_date_time(&systime, date_time);
|
|
FILETIME ftime = {0};
|
|
SystemTimeToFileTime(&systime, &ftime);
|
|
FILETIME ftime_local = {0};
|
|
LocalFileTimeToFileTime(&ftime, &ftime_local);
|
|
FileTimeToSystemTime(&ftime_local, &systime);
|
|
DateTime result = {0};
|
|
w32_date_time_from_system_time(&result, &systime);
|
|
return result;
|
|
}
|
|
|
|
internal DateTime
|
|
local_from_universal_time(DateTime *date_time)
|
|
{
|
|
SYSTEMTIME systime = {0};
|
|
w32_system_time_from_date_time(&systime, date_time);
|
|
FILETIME ftime = {0};
|
|
SystemTimeToFileTime(&systime, &ftime);
|
|
FILETIME ftime_local = {0};
|
|
FileTimeToLocalFileTime(&ftime, &ftime_local);
|
|
FileTimeToSystemTime(&ftime_local, &systime);
|
|
DateTime result = {0};
|
|
w32_date_time_from_system_time(&result, &systime);
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
sleep_ms(U32 msec)
|
|
{
|
|
Sleep(msec);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Platform GUID Functions
|
|
|
|
internal Guid
|
|
make_guid(void)
|
|
{
|
|
Guid result;
|
|
MemoryZeroStruct(&result);
|
|
UUID uuid;
|
|
RPC_STATUS rpc_status = UuidCreate(&uuid);
|
|
if(rpc_status == RPC_S_OK)
|
|
{
|
|
result.data1 = uuid.Data1;
|
|
result.data2 = uuid.Data2;
|
|
result.data3 = uuid.Data3;
|
|
MemoryCopyArray(result.data4, uuid.Data4);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Platform Memory Allocation
|
|
|
|
//- rjf: basic
|
|
|
|
internal void *
|
|
reserve_memory(U64 size)
|
|
{
|
|
void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE);
|
|
return result;
|
|
}
|
|
|
|
internal B32
|
|
commit_memory(void *ptr, U64 size)
|
|
{
|
|
B32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0);
|
|
|
|
#if !NO_WIN32_RIO
|
|
if(w32_rio_functions.RIORegisterBuffer)
|
|
{
|
|
// wine does not implement these functions
|
|
w32_rio_functions.RIODeregisterBuffer(w32_rio_functions.RIORegisterBuffer(ptr, size));
|
|
}
|
|
#endif
|
|
|
|
#if PROFILE_TELEMETRY
|
|
tmAlloc(0, ptr, size / 1024, "Win32 Commit");
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
decommit_memory(void *ptr, U64 size)
|
|
{
|
|
VirtualFree(ptr, size, MEM_DECOMMIT);
|
|
#if PROFILE_TELEMETRY
|
|
tmFree(0, ptr);
|
|
#endif
|
|
}
|
|
|
|
internal void
|
|
release_memory(void *ptr, U64 size)
|
|
{
|
|
// NOTE(rjf): size not used - not necessary on Windows, but necessary for other OSes.
|
|
VirtualFree(ptr, 0, MEM_RELEASE);
|
|
}
|
|
|
|
//- rjf: large pages
|
|
|
|
internal void *
|
|
reserve_memory_large(U64 size)
|
|
{
|
|
// we commit on reserve because windows
|
|
void *result = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_LARGE_PAGES, PAGE_READWRITE);
|
|
return result;
|
|
}
|
|
|
|
internal B32
|
|
commit_memory_large(void *ptr, U64 size)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Shared Memory
|
|
|
|
internal SharedMemory
|
|
shared_memory_alloc(U64 size, String8 name)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 name16 = str16_from_8(scratch.arena, name);
|
|
HANDLE file = CreateFileMappingW(INVALID_HANDLE_VALUE,
|
|
0,
|
|
PAGE_READWRITE,
|
|
(U32)((size & 0xffffffff00000000) >> 32),
|
|
(U32)((size & 0x00000000ffffffff)),
|
|
(WCHAR *)name16.str);
|
|
SharedMemory result = {(U64)file};
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
internal SharedMemory
|
|
shared_memory_open(String8 name)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 name16 = str16_from_8(scratch.arena, name);
|
|
HANDLE file = OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, (WCHAR *)name16.str);
|
|
SharedMemory result = {(U64)file};
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
shared_memory_close(SharedMemory handle)
|
|
{
|
|
HANDLE file = (HANDLE)(handle.u64[0]);
|
|
CloseHandle(file);
|
|
}
|
|
|
|
internal void *
|
|
shared_memory_view_open(SharedMemory handle, Rng1U64 range)
|
|
{
|
|
HANDLE file = (HANDLE)(handle.u64[0]);
|
|
U64 offset = range.min;
|
|
U64 size = range.max-range.min;
|
|
void *ptr = MapViewOfFile(file, FILE_MAP_ALL_ACCESS,
|
|
(U32)((offset & 0xffffffff00000000) >> 32),
|
|
(U32)((offset & 0x00000000ffffffff)),
|
|
size);
|
|
return ptr;
|
|
}
|
|
|
|
internal void
|
|
shared_memory_view_close(SharedMemory handle, void *ptr, Rng1U64 range)
|
|
{
|
|
UnmapViewOfFile(ptr);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl System Info
|
|
|
|
internal SystemInfo *
|
|
get_system_info(void)
|
|
{
|
|
return &w32_state.system_info;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Current Thread Info
|
|
|
|
internal U32
|
|
tid(void)
|
|
{
|
|
DWORD id = GetCurrentThreadId();
|
|
return (U32)id;
|
|
}
|
|
|
|
internal void
|
|
set_platform_thread_name(String8 name)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
|
|
// rjf: windows 10 style
|
|
if(w32_SetThreadDescription_func)
|
|
{
|
|
String16 name16 = str16_from_8(scratch.arena, name);
|
|
HRESULT hr = w32_SetThreadDescription_func(GetCurrentThread(), (WCHAR*)name16.str);
|
|
}
|
|
|
|
// rjf: raise-exception style
|
|
{
|
|
String8 name_copy = push_str8_copy(scratch.arena, name);
|
|
#pragma pack(push,8)
|
|
typedef struct THREADNAME_INFO THREADNAME_INFO;
|
|
struct THREADNAME_INFO
|
|
{
|
|
U32 dwType; // Must be 0x1000.
|
|
char *szName; // Pointer to name (in user addr space).
|
|
U32 dwThreadID; // Thread ID (-1=caller thread).
|
|
U32 dwFlags; // Reserved for future use, must be zero.
|
|
};
|
|
#pragma pack(pop)
|
|
THREADNAME_INFO info;
|
|
info.dwType = 0x1000;
|
|
info.szName = (char *)name_copy.str;
|
|
info.dwThreadID = tid();
|
|
info.dwFlags = 0;
|
|
#pragma warning(push)
|
|
#pragma warning(disable: 6320 6322)
|
|
__try
|
|
{
|
|
RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info);
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
}
|
|
#pragma warning(pop)
|
|
}
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Thread Functions
|
|
|
|
internal Thread
|
|
thread_launch(ThreadEntryPointFunctionType *f, void *p)
|
|
{
|
|
ProfBeginFunction();
|
|
W32_Entity *entity = w32_entity_alloc(W32_EntityKind_Thread);
|
|
entity->thread.func = f;
|
|
entity->thread.ptr = p;
|
|
entity->thread.handle = CreateThread(0, 0, w32_thread_entry_point, entity, 0, &entity->thread.tid);
|
|
Thread result = {IntFromPtr(entity)};
|
|
ProfEnd();
|
|
return result;
|
|
}
|
|
|
|
internal B32
|
|
thread_join(Thread thread, U64 endt_us)
|
|
{
|
|
DWORD sleep_ms = w32_sleep_ms_from_endt_us(endt_us);
|
|
W32_Entity *entity = (W32_Entity *)PtrFromInt(thread.u64[0]);
|
|
DWORD wait_result = WAIT_OBJECT_0;
|
|
if(entity != 0)
|
|
{
|
|
wait_result = WaitForSingleObject(entity->thread.handle, sleep_ms);
|
|
CloseHandle(entity->thread.handle);
|
|
w32_entity_release(entity);
|
|
}
|
|
return (wait_result == WAIT_OBJECT_0);
|
|
}
|
|
|
|
internal void
|
|
thread_detach(Thread thread)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(thread.u64[0]);
|
|
if(entity != 0)
|
|
{
|
|
CloseHandle(entity->thread.handle);
|
|
w32_entity_release(entity);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Safe Calls
|
|
|
|
internal void
|
|
safe_call(ThreadEntryPointFunctionType *func, ThreadEntryPointFunctionType *fail_handler, void *ptr)
|
|
{
|
|
__try
|
|
{
|
|
func(ptr);
|
|
}
|
|
__except (EXCEPTION_EXECUTE_HANDLER)
|
|
{
|
|
if(fail_handler != 0)
|
|
{
|
|
fail_handler(ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Synchronization Primitive Functions
|
|
|
|
//- rjf: recursive mutexes
|
|
|
|
internal Mutex
|
|
mutex_alloc(void)
|
|
{
|
|
W32_Entity *entity = w32_entity_alloc(W32_EntityKind_Mutex);
|
|
InitializeCriticalSection(&entity->mutex);
|
|
Mutex result = {IntFromPtr(entity)};
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
mutex_release(Mutex mutex)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(mutex.u64[0]);
|
|
DeleteCriticalSection(&entity->mutex);
|
|
w32_entity_release(entity);
|
|
}
|
|
|
|
internal void
|
|
mutex_take(Mutex mutex)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(mutex.u64[0]);
|
|
EnterCriticalSection(&entity->mutex);
|
|
}
|
|
|
|
internal void
|
|
mutex_drop(Mutex mutex)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(mutex.u64[0]);
|
|
LeaveCriticalSection(&entity->mutex);
|
|
}
|
|
|
|
//- rjf: reader/writer mutexes
|
|
|
|
internal RWMutex
|
|
rw_mutex_alloc(void)
|
|
{
|
|
W32_Entity *entity = w32_entity_alloc(W32_EntityKind_RWMutex);
|
|
InitializeSRWLock(&entity->rw_mutex);
|
|
RWMutex result = {IntFromPtr(entity)};
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
rw_mutex_release(RWMutex rw_mutex)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(rw_mutex.u64[0]);
|
|
w32_entity_release(entity);
|
|
}
|
|
|
|
internal void
|
|
rw_mutex_take(RWMutex rw_mutex, B32 write_mode)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(rw_mutex.u64[0]);
|
|
if(write_mode)
|
|
{
|
|
AcquireSRWLockExclusive(&entity->rw_mutex);
|
|
}
|
|
else
|
|
{
|
|
AcquireSRWLockShared(&entity->rw_mutex);
|
|
}
|
|
}
|
|
|
|
internal void
|
|
rw_mutex_drop(RWMutex rw_mutex, B32 write_mode)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(rw_mutex.u64[0]);
|
|
if(write_mode)
|
|
{
|
|
ReleaseSRWLockExclusive(&entity->rw_mutex);
|
|
}
|
|
else
|
|
{
|
|
ReleaseSRWLockShared(&entity->rw_mutex);
|
|
}
|
|
}
|
|
|
|
//- rjf: condition variables
|
|
|
|
internal CondVar
|
|
cond_var_alloc(void)
|
|
{
|
|
W32_Entity *entity = w32_entity_alloc(W32_EntityKind_ConditionVariable);
|
|
InitializeConditionVariable(&entity->cv);
|
|
CondVar result = {IntFromPtr(entity)};
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
cond_var_release(CondVar cv)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]);
|
|
w32_entity_release(entity);
|
|
}
|
|
|
|
internal B32
|
|
cond_var_wait(CondVar cv, Mutex mutex, U64 endt_us)
|
|
{
|
|
U32 sleep_ms = w32_sleep_ms_from_endt_us(endt_us);
|
|
BOOL result = 0;
|
|
if(sleep_ms > 0)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]);
|
|
W32_Entity *mutex_entity = (W32_Entity*)PtrFromInt(mutex.u64[0]);
|
|
result = SleepConditionVariableCS(&entity->cv, &mutex_entity->mutex, sleep_ms);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal B32
|
|
cond_var_wait_rw(CondVar cv, RWMutex mutex_rw, B32 write_mode, U64 endt_us)
|
|
{
|
|
U32 sleep_ms = w32_sleep_ms_from_endt_us(endt_us);
|
|
BOOL result = 0;
|
|
if(sleep_ms > 0)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]);
|
|
W32_Entity *mutex_entity = (W32_Entity*)PtrFromInt(mutex_rw.u64[0]);
|
|
result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms,
|
|
write_mode ? 0 : CONDITION_VARIABLE_LOCKMODE_SHARED);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
cond_var_signal(CondVar cv)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]);
|
|
WakeConditionVariable(&entity->cv);
|
|
}
|
|
|
|
internal void
|
|
cond_var_broadcast(CondVar cv)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(cv.u64[0]);
|
|
WakeAllConditionVariable(&entity->cv);
|
|
}
|
|
|
|
//- rjf: cross-process semaphores
|
|
|
|
internal Semaphore
|
|
semaphore_alloc(U32 initial_count, U32 max_count, String8 name)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 name16 = str16_from_8(scratch.arena, name);
|
|
HANDLE handle = CreateSemaphoreW(0, initial_count, max_count, (WCHAR *)name16.str);
|
|
Semaphore result = {(U64)handle};
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
semaphore_release(Semaphore semaphore)
|
|
{
|
|
HANDLE handle = (HANDLE)semaphore.u64[0];
|
|
CloseHandle(handle);
|
|
}
|
|
|
|
internal Semaphore
|
|
semaphore_open(String8 name)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 name16 = str16_from_8(scratch.arena, name);
|
|
HANDLE handle = OpenSemaphoreW(SEMAPHORE_ALL_ACCESS, 0, (WCHAR *)name16.str);
|
|
Semaphore result = {(U64)handle};
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
semaphore_close(Semaphore semaphore)
|
|
{
|
|
HANDLE handle = (HANDLE)semaphore.u64[0];
|
|
CloseHandle(handle);
|
|
}
|
|
|
|
internal B32
|
|
semaphore_take(Semaphore semaphore, U64 endt_us)
|
|
{
|
|
U32 sleep_ms = w32_sleep_ms_from_endt_us(endt_us);
|
|
HANDLE handle = (HANDLE)semaphore.u64[0];
|
|
DWORD wait_result = WaitForSingleObject(handle, sleep_ms);
|
|
B32 result = (wait_result == WAIT_OBJECT_0);
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
semaphore_drop_count(Semaphore semaphore, U64 drop_count)
|
|
{
|
|
ReleaseSemaphore((HANDLE)*semaphore.u64, drop_count, 0);
|
|
}
|
|
|
|
//- rjf: barriers
|
|
|
|
internal Barrier
|
|
barrier_alloc(U64 count)
|
|
{
|
|
Barrier result = {0};
|
|
if(w32_InitializeSynchronizationBarrier_func != 0)
|
|
{
|
|
W32_Entity *entity = w32_entity_alloc(W32_EntityKind_Barrier);
|
|
if(entity != 0)
|
|
{
|
|
BOOL init_good = w32_InitializeSynchronizationBarrier_func(&entity->sb, count, -1);
|
|
(void)init_good;
|
|
}
|
|
result.u64[0] = IntFromPtr(entity);
|
|
}
|
|
else
|
|
{
|
|
result = slow_barrier_alloc(count);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
barrier_release(Barrier barrier)
|
|
{
|
|
if(w32_InitializeSynchronizationBarrier_func != 0)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(barrier.u64[0]);
|
|
if(entity != 0)
|
|
{
|
|
w32_DeleteSynchronizationBarrier_func(&entity->sb);
|
|
w32_entity_release(entity);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
slow_barrier_release(barrier);
|
|
}
|
|
}
|
|
|
|
internal void
|
|
barrier_wait(Barrier barrier)
|
|
{
|
|
if(w32_InitializeSynchronizationBarrier_func != 0)
|
|
{
|
|
W32_Entity *entity = (W32_Entity*)PtrFromInt(barrier.u64[0]);
|
|
if(entity != 0)
|
|
{
|
|
w32_EnterSynchronizationBarrier_func(&entity->sb, 0);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
slow_barrier_wait(barrier);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl File System (Implemented Per-OS)
|
|
|
|
//- rjf: files
|
|
|
|
internal File
|
|
file_open(AccessFlags flags, String8 path)
|
|
{
|
|
File result = {0};
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 path16 = str16_from_8(scratch.arena, path);
|
|
DWORD access_flags = 0;
|
|
DWORD share_mode = 0;
|
|
DWORD creation_disposition = OPEN_EXISTING;
|
|
SECURITY_ATTRIBUTES security_attributes = {sizeof(security_attributes), 0, 0};
|
|
if(flags & AccessFlag_Read) {access_flags |= GENERIC_READ;}
|
|
if(flags & AccessFlag_Write) {access_flags |= GENERIC_WRITE;}
|
|
if(flags & AccessFlag_Execute) {access_flags |= GENERIC_EXECUTE;}
|
|
if(flags & AccessFlag_ShareRead) {share_mode |= FILE_SHARE_READ;}
|
|
if(flags & AccessFlag_ShareWrite) {share_mode |= FILE_SHARE_WRITE|FILE_SHARE_DELETE;}
|
|
if(flags & AccessFlag_Write) {creation_disposition = CREATE_ALWAYS;}
|
|
if(flags & AccessFlag_Append) {creation_disposition = OPEN_ALWAYS; access_flags |= FILE_APPEND_DATA; }
|
|
if(flags & AccessFlag_Inherited)
|
|
{
|
|
security_attributes.bInheritHandle = 1;
|
|
}
|
|
HANDLE file = CreateFileW((WCHAR *)path16.str, access_flags, share_mode, &security_attributes, creation_disposition, FILE_ATTRIBUTE_NORMAL, 0);
|
|
if(file != INVALID_HANDLE_VALUE)
|
|
{
|
|
result.u64[0] = (U64)file;
|
|
}
|
|
else
|
|
{
|
|
DWORD err = GetLastError();
|
|
(void)err;
|
|
}
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
file_close(File file)
|
|
{
|
|
if(file_match(file, file_zero())) { return; }
|
|
HANDLE handle = (HANDLE)file.u64[0];
|
|
BOOL result = CloseHandle(handle);
|
|
(void)result;
|
|
}
|
|
|
|
internal U64
|
|
file_read(File file, Rng1U64 rng, void *out_data)
|
|
{
|
|
if(file_match(file, file_zero())) { return 0; }
|
|
|
|
HANDLE handle = (HANDLE)file.u64[0];
|
|
U8 *ptr = out_data;
|
|
U64 off = rng.min;
|
|
while(off != rng.max)
|
|
{
|
|
U64 amt64 = rng.max - off;
|
|
U32 amt32 = (U32)Min(MB(32), amt64);
|
|
DWORD read_size = 0;
|
|
OVERLAPPED overlapped = { .Offset = (U32)off, .OffsetHigh = (U32)(off >> 32) };
|
|
if( ! ReadFile(handle, ptr, amt32, &read_size, &overlapped))
|
|
{
|
|
break;
|
|
}
|
|
ptr += read_size;
|
|
off += read_size;
|
|
}
|
|
|
|
U64 total_read_size = off - rng.min;
|
|
return total_read_size;
|
|
}
|
|
|
|
internal U64
|
|
file_write(File file, Rng1U64 rng, void *data)
|
|
{
|
|
if(file_match(file, file_zero())) { return 0; }
|
|
HANDLE win_handle = (HANDLE)file.u64[0];
|
|
U64 src_off = 0;
|
|
U64 dst_off = rng.min;
|
|
U64 total_write_size = dim_1u64(rng);
|
|
for(;;)
|
|
{
|
|
void *bytes_src = (U8 *)data + src_off;
|
|
U64 bytes_left = total_write_size - src_off;
|
|
DWORD write_size = Min(MB(1), bytes_left);
|
|
DWORD bytes_written = 0;
|
|
OVERLAPPED overlapped = {0};
|
|
overlapped.Offset = (dst_off&0x00000000ffffffffull);
|
|
overlapped.OffsetHigh = (dst_off&0xffffffff00000000ull) >> 32;
|
|
BOOL success = WriteFile(win_handle, bytes_src, write_size, &bytes_written, &overlapped);
|
|
if(success == 0)
|
|
{
|
|
break;
|
|
}
|
|
src_off += bytes_written;
|
|
dst_off += bytes_written;
|
|
if(bytes_left == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return src_off;
|
|
}
|
|
|
|
internal B32
|
|
file_set_time(File file, DateTime time)
|
|
{
|
|
if(file_match(file, file_zero())) { return 0; }
|
|
B32 result = 0;
|
|
HANDLE handle = (HANDLE)file.u64[0];
|
|
SYSTEMTIME system_time = {0};
|
|
w32_system_time_from_date_time(&system_time, &time);
|
|
FILETIME file_time = {0};
|
|
result = (SystemTimeToFileTime(&system_time, &file_time) &&
|
|
SetFileTime(handle, &file_time, &file_time, &file_time));
|
|
return result;
|
|
}
|
|
|
|
internal FileProperties
|
|
properties_from_file(File file)
|
|
{
|
|
if(file_match(file, file_zero())) { FileProperties r = {0}; return r; }
|
|
FileProperties props = {0};
|
|
HANDLE handle = (HANDLE)file.u64[0];
|
|
BY_HANDLE_FILE_INFORMATION info;
|
|
BOOL info_good = GetFileInformationByHandle(handle, &info);
|
|
if(info_good)
|
|
{
|
|
U32 size_lo = info.nFileSizeLow;
|
|
U32 size_hi = info.nFileSizeHigh;
|
|
props.size = (U64)size_lo | (((U64)size_hi)<<32);
|
|
w32_dense_time_from_file_time(&props.modified, &info.ftLastWriteTime);
|
|
w32_dense_time_from_file_time(&props.created, &info.ftCreationTime);
|
|
props.flags = w32_file_property_flags_from_dwFileAttributes(info.dwFileAttributes);
|
|
}
|
|
return props;
|
|
}
|
|
|
|
internal FileID
|
|
id_from_file(File file)
|
|
{
|
|
if(file_match(file, file_zero())) { FileID r = {0}; return r; }
|
|
FileID result = {0};
|
|
HANDLE handle = (HANDLE)file.u64[0];
|
|
BY_HANDLE_FILE_INFORMATION info;
|
|
BOOL is_ok = GetFileInformationByHandle(handle, &info);
|
|
if(is_ok)
|
|
{
|
|
result.v[0] = info.dwVolumeSerialNumber;
|
|
result.v[1] = info.nFileIndexLow;
|
|
result.v[2] = info.nFileIndexHigh;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal B32
|
|
file_reserve_size(File file, U64 size)
|
|
{
|
|
HANDLE handle = (HANDLE)file.u64[0];
|
|
|
|
FILE_ALLOCATION_INFO alloc_info = {0};
|
|
alloc_info.AllocationSize.LowPart = size & max_U32;
|
|
alloc_info.AllocationSize.HighPart = (size >> 32) & max_U32;
|
|
|
|
BOOL is_reserved = SetFileInformationByHandle(handle, FileAllocationInfo, &alloc_info, sizeof(alloc_info));
|
|
return is_reserved;
|
|
}
|
|
|
|
internal B32
|
|
delete_file_at_path(String8 path)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 path16 = str16_from_8(scratch.arena, path);
|
|
B32 result = DeleteFileW((WCHAR*)path16.str);
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
internal B32
|
|
copy_file_path(String8 dst, String8 src)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 dst16 = str16_from_8(scratch.arena, dst);
|
|
String16 src16 = str16_from_8(scratch.arena, src);
|
|
B32 result = CopyFileW((WCHAR*)src16.str, (WCHAR*)dst16.str, 0);
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
internal B32
|
|
move_file_path(String8 dst, String8 src)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 dst16 = str16_from_8(scratch.arena, dst);
|
|
String16 src16 = str16_from_8(scratch.arena, src);
|
|
B32 result = MoveFileW((WCHAR*)src16.str, (WCHAR*)dst16.str);
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
internal String8
|
|
full_path_from_path(Arena *arena, String8 path)
|
|
{
|
|
Temp scratch = scratch_begin(&arena, 1);
|
|
DWORD buffer_size = Max(MAX_PATH, path.size * 2) + 1;
|
|
String16 path16 = str16_from_8(scratch.arena, path);
|
|
WCHAR *buffer = push_array_no_zero(scratch.arena, WCHAR, buffer_size);
|
|
DWORD path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, buffer, NULL);
|
|
if(path16_size > buffer_size)
|
|
{
|
|
arena_pop(scratch.arena, buffer_size);
|
|
buffer_size = path16_size + 1;
|
|
buffer = push_array_no_zero(scratch.arena, WCHAR, buffer_size);
|
|
path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, buffer, NULL);
|
|
}
|
|
String8 full_path = str8_from_16(arena, str16((U16*)buffer, path16_size));
|
|
scratch_end(scratch);
|
|
return full_path;
|
|
}
|
|
|
|
internal B32
|
|
file_path_exists(String8 path)
|
|
{
|
|
Temp scratch = scratch_begin(0,0);
|
|
String16 path16 = str16_from_8(scratch.arena, path);
|
|
DWORD attributes = GetFileAttributesW((WCHAR *)path16.str);
|
|
B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && !!(~attributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
scratch_end(scratch);
|
|
return exists;
|
|
}
|
|
|
|
internal B32
|
|
folder_path_exists(String8 path)
|
|
{
|
|
Temp scratch = scratch_begin(0,0);
|
|
String16 path16 = str16_from_8(scratch.arena, path);
|
|
DWORD attributes = GetFileAttributesW((WCHAR *)path16.str);
|
|
B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && (attributes & FILE_ATTRIBUTE_DIRECTORY);
|
|
scratch_end(scratch);
|
|
return exists;
|
|
}
|
|
|
|
internal FileProperties
|
|
properties_from_file_path(String8 path)
|
|
{
|
|
WIN32_FIND_DATAW find_data = {0};
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 path16 = str16_from_8(scratch.arena, path);
|
|
HANDLE handle = FindFirstFileW((WCHAR *)path16.str, &find_data);
|
|
FileProperties props = {0};
|
|
if(handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
props.size = Compose64Bit(find_data.nFileSizeHigh, find_data.nFileSizeLow);
|
|
w32_dense_time_from_file_time(&props.created, &find_data.ftCreationTime);
|
|
w32_dense_time_from_file_time(&props.modified, &find_data.ftLastWriteTime);
|
|
props.flags = w32_file_property_flags_from_dwFileAttributes(find_data.dwFileAttributes);
|
|
}
|
|
else
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
WCHAR buffer[512] = {0};
|
|
DWORD length = GetLogicalDriveStringsW(sizeof(buffer), buffer);
|
|
U64 last_slash_pos = 0;
|
|
for(;last_slash_pos < path.size; last_slash_pos = str8_find_needle(path, last_slash_pos+1, str8_lit("/"), StringMatchFlag_SlashInsensitive));
|
|
String8 path_trimmed = str8_prefix(path, last_slash_pos);
|
|
for(U64 off = 0; off < (U64)length;)
|
|
{
|
|
String16 next_drive_string_16 = str16_cstring((U16 *)buffer+off);
|
|
off += next_drive_string_16.size+1;
|
|
String8 next_drive_string = str8_from_16(scratch.arena, next_drive_string_16);
|
|
next_drive_string = str8_chop_last_slash(next_drive_string);
|
|
if(str8_match(path_trimmed, next_drive_string, StringMatchFlag_CaseInsensitive))
|
|
{
|
|
props.flags |= FilePropertyFlag_IsFolder;
|
|
break;
|
|
}
|
|
}
|
|
scratch_end(scratch);
|
|
}
|
|
FindClose(handle);
|
|
scratch_end(scratch);
|
|
return props;
|
|
}
|
|
|
|
//- rjf: file maps
|
|
|
|
internal FileMap
|
|
file_map_open(AccessFlags flags, File file)
|
|
{
|
|
FileMap map = {0};
|
|
{
|
|
HANDLE file_handle = (HANDLE)file.u64[0];
|
|
DWORD protect_flags = 0;
|
|
{
|
|
switch(flags)
|
|
{
|
|
default:{}break;
|
|
case AccessFlag_Read:
|
|
{protect_flags = PAGE_READONLY;}break;
|
|
case AccessFlag_Write:
|
|
case AccessFlag_Read|AccessFlag_Write:
|
|
{protect_flags = PAGE_READWRITE;}break;
|
|
case AccessFlag_Execute:
|
|
case AccessFlag_Read|AccessFlag_Execute:
|
|
{protect_flags = PAGE_EXECUTE_READ;}break;
|
|
case AccessFlag_Execute|AccessFlag_Write|AccessFlag_Read:
|
|
case AccessFlag_Execute|AccessFlag_Write:
|
|
{protect_flags = PAGE_EXECUTE_READWRITE;}break;
|
|
}
|
|
}
|
|
HANDLE map_handle = CreateFileMappingA(file_handle, 0, protect_flags, 0, 0, 0);
|
|
map.u64[0] = (U64)map_handle;
|
|
}
|
|
return map;
|
|
}
|
|
|
|
internal void
|
|
file_map_close(FileMap map)
|
|
{
|
|
HANDLE handle = (HANDLE)map.u64[0];
|
|
BOOL result = CloseHandle(handle);
|
|
(void)result;
|
|
}
|
|
|
|
internal void *
|
|
file_map_view_open(FileMap map, AccessFlags flags, Rng1U64 range)
|
|
{
|
|
HANDLE handle = (HANDLE)map.u64[0];
|
|
U32 off_lo = (U32)((range.min&0x00000000ffffffffull)>>0);
|
|
U32 off_hi = (U32)((range.min&0xffffffff00000000ull)>>32);
|
|
U64 size = dim_1u64(range);
|
|
DWORD access_flags = 0;
|
|
{
|
|
switch(flags)
|
|
{
|
|
default:{}break;
|
|
case AccessFlag_Read:
|
|
{
|
|
access_flags = FILE_MAP_READ;
|
|
}break;
|
|
case AccessFlag_Write:
|
|
{
|
|
access_flags = FILE_MAP_WRITE;
|
|
}break;
|
|
case AccessFlag_Read|AccessFlag_Write:
|
|
{
|
|
access_flags = FILE_MAP_ALL_ACCESS;
|
|
}break;
|
|
case AccessFlag_Execute:
|
|
case AccessFlag_Read|AccessFlag_Execute:
|
|
case AccessFlag_Write|AccessFlag_Execute:
|
|
case AccessFlag_Read|AccessFlag_Write|AccessFlag_Execute:
|
|
{
|
|
access_flags = FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE;
|
|
}break;
|
|
}
|
|
}
|
|
void *result = MapViewOfFile(handle, access_flags, off_hi, off_lo, size);
|
|
DWORD err = GetLastError();
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
file_map_view_close(FileMap map, void *ptr, Rng1U64 range)
|
|
{
|
|
BOOL result = UnmapViewOfFile(ptr);
|
|
(void)result;
|
|
}
|
|
|
|
//- rjf: directory iteration
|
|
|
|
internal FileIter *
|
|
file_iter_begin(Arena *arena, String8 path, FileIterFlags flags)
|
|
{
|
|
Temp scratch = scratch_begin(&arena, 1);
|
|
String8 path_with_wildcard = push_str8_cat(scratch.arena, path, str8_lit("\\*"));
|
|
String16 path16 = str16_from_8(scratch.arena, path_with_wildcard);
|
|
FileIter *iter = push_array(arena, FileIter, 1);
|
|
iter->flags = flags;
|
|
W32_FileIter *w32_iter = (W32_FileIter*)iter->memory;
|
|
if(path.size == 0)
|
|
{
|
|
w32_iter->is_volume_iter = 1;
|
|
WCHAR buffer[512] = {0};
|
|
DWORD length = GetLogicalDriveStringsW(sizeof(buffer), buffer);
|
|
String8List drive_strings = {0};
|
|
for(U64 off = 0; off < (U64)length;)
|
|
{
|
|
String16 next_drive_string_16 = str16_cstring((U16 *)buffer+off);
|
|
off += next_drive_string_16.size+1;
|
|
String8 next_drive_string = str8_from_16(arena, next_drive_string_16);
|
|
next_drive_string = str8_chop_last_slash(next_drive_string);
|
|
str8_list_push(scratch.arena, &drive_strings, next_drive_string);
|
|
}
|
|
w32_iter->drive_strings = str8_array_from_list(arena, &drive_strings);
|
|
w32_iter->drive_strings_iter_idx = 0;
|
|
}
|
|
else
|
|
{
|
|
w32_iter->handle = FindFirstFileExW((WCHAR*)path16.str, FindExInfoBasic, &w32_iter->find_data, FindExSearchNameMatch, 0, FIND_FIRST_EX_LARGE_FETCH);
|
|
}
|
|
scratch_end(scratch);
|
|
return iter;
|
|
}
|
|
|
|
internal B32
|
|
file_iter_next(Arena *arena, FileIter *iter, FileInfo *info_out)
|
|
{
|
|
B32 result = 0;
|
|
FileIterFlags flags = iter->flags;
|
|
W32_FileIter *w32_iter = (W32_FileIter*)iter->memory;
|
|
switch(w32_iter->is_volume_iter)
|
|
{
|
|
//- rjf: file iteration
|
|
default:
|
|
case 0:
|
|
{
|
|
if (!(flags & FileIterFlag_Done) && w32_iter->handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
// check is usable
|
|
B32 usable_file = 1;
|
|
|
|
WCHAR *file_name = w32_iter->find_data.cFileName;
|
|
DWORD attributes = w32_iter->find_data.dwFileAttributes;
|
|
if (file_name[0] == '.'){
|
|
if (flags & FileIterFlag_SkipHiddenFiles){
|
|
usable_file = 0;
|
|
}
|
|
else if (file_name[1] == 0){
|
|
usable_file = 0;
|
|
}
|
|
else if (file_name[1] == '.' && file_name[2] == 0){
|
|
usable_file = 0;
|
|
}
|
|
}
|
|
if (attributes & FILE_ATTRIBUTE_DIRECTORY){
|
|
if (flags & FileIterFlag_SkipFolders){
|
|
usable_file = 0;
|
|
}
|
|
}
|
|
else{
|
|
if (flags & FileIterFlag_SkipFiles){
|
|
usable_file = 0;
|
|
}
|
|
}
|
|
|
|
// emit if usable
|
|
if (usable_file){
|
|
info_out->name = str8_from_16(arena, str16_cstring((U16*)file_name));
|
|
info_out->props.size = (U64)w32_iter->find_data.nFileSizeLow | (((U64)w32_iter->find_data.nFileSizeHigh)<<32);
|
|
w32_dense_time_from_file_time(&info_out->props.created, &w32_iter->find_data.ftCreationTime);
|
|
w32_dense_time_from_file_time(&info_out->props.modified, &w32_iter->find_data.ftLastWriteTime);
|
|
info_out->props.flags = w32_file_property_flags_from_dwFileAttributes(attributes);
|
|
result = 1;
|
|
if (!FindNextFileW(w32_iter->handle, &w32_iter->find_data)){
|
|
iter->flags |= FileIterFlag_Done;
|
|
}
|
|
break;
|
|
}
|
|
}while(FindNextFileW(w32_iter->handle, &w32_iter->find_data));
|
|
}
|
|
}break;
|
|
|
|
//- rjf: volume iteration
|
|
case 1:
|
|
{
|
|
result = w32_iter->drive_strings_iter_idx < w32_iter->drive_strings.count;
|
|
if(result != 0)
|
|
{
|
|
MemoryZeroStruct(info_out);
|
|
info_out->name = w32_iter->drive_strings.v[w32_iter->drive_strings_iter_idx];
|
|
info_out->props.flags |= FilePropertyFlag_IsFolder;
|
|
w32_iter->drive_strings_iter_idx += 1;
|
|
}
|
|
}break;
|
|
}
|
|
if(!result)
|
|
{
|
|
iter->flags |= FileIterFlag_Done;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
file_iter_end(FileIter *iter)
|
|
{
|
|
W32_FileIter *w32_iter = (W32_FileIter*)iter->memory;
|
|
HANDLE zero_handle;
|
|
MemoryZeroStruct(&zero_handle);
|
|
if(!MemoryMatchStruct(&zero_handle, &w32_iter->handle))
|
|
{
|
|
FindClose(w32_iter->handle);
|
|
}
|
|
}
|
|
|
|
//- rjf: directory creation
|
|
|
|
internal B32
|
|
make_directory(String8 path)
|
|
{
|
|
B32 result = 0;
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 name16 = str16_from_8(scratch.arena, path);
|
|
WIN32_FILE_ATTRIBUTE_DATA attributes = {0};
|
|
GetFileAttributesExW((WCHAR*)name16.str, GetFileExInfoStandard, &attributes);
|
|
if(attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
result = 1;
|
|
}
|
|
else if(CreateDirectoryW((WCHAR*)name16.str, 0))
|
|
{
|
|
result = 1;
|
|
}
|
|
scratch_end(scratch);
|
|
return(result);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Aborting
|
|
|
|
internal void
|
|
abort_self(U64 exit_code)
|
|
{
|
|
ExitProcess((int)exit_code);
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Process Info
|
|
|
|
internal ProcessInfo *
|
|
get_process_info(void)
|
|
{
|
|
return &w32_state.process_info;
|
|
}
|
|
|
|
internal String8
|
|
get_current_path(Arena *arena)
|
|
{
|
|
Temp scratch = scratch_begin(&arena, 1);
|
|
DWORD length = GetCurrentDirectoryW(0, 0);
|
|
U16 *memory = push_array_no_zero(scratch.arena, U16, length + 1);
|
|
length = GetCurrentDirectoryW(length + 1, (WCHAR*)memory);
|
|
String8 name = str8_from_16(arena, str16(memory, length));
|
|
scratch_end(scratch);
|
|
return name;
|
|
}
|
|
|
|
internal U32
|
|
get_process_start_time_unix(void)
|
|
{
|
|
U32 result = 0;
|
|
HANDLE handle = GetCurrentProcess();
|
|
FILETIME start_time = {0};
|
|
FILETIME exit_time;
|
|
FILETIME kernel_time;
|
|
FILETIME user_time;
|
|
if(GetProcessTimes(handle, &start_time, &exit_time, &kernel_time, &user_time))
|
|
{
|
|
result = w32_unix_time_from_file_time(start_time);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Child Processes
|
|
|
|
internal Process
|
|
process_launch(ProcessLaunchParams *params)
|
|
{
|
|
Process result = {0};
|
|
Temp scratch = scratch_begin(0, 0);
|
|
|
|
//- rjf: form full command string
|
|
String8 cmd = {0};
|
|
{
|
|
StringJoin join_params = {0};
|
|
join_params.pre = str8_lit("\"");
|
|
join_params.sep = str8_lit("\" \"");
|
|
join_params.post = str8_lit("\"");
|
|
cmd = str8_list_join(scratch.arena, ¶ms->cmd_line, &join_params);
|
|
}
|
|
|
|
//- rjf: form environment
|
|
B32 use_null_env_arg = 0;
|
|
String8 env = {0};
|
|
{
|
|
StringJoin join_params2 = {0};
|
|
join_params2.sep = str8_lit("\0");
|
|
join_params2.post = str8_lit("\0");
|
|
String8List all_opts = params->env;
|
|
if(params->inherit_env != 0)
|
|
{
|
|
if(all_opts.node_count != 0)
|
|
{
|
|
MemoryZeroStruct(&all_opts);
|
|
for(String8Node *n = params->env.first; n != 0; n = n->next)
|
|
{
|
|
str8_list_push(scratch.arena, &all_opts, n->string);
|
|
}
|
|
for(String8Node *n = w32_state.process_info.environment.first; n != 0; n = n->next)
|
|
{
|
|
str8_list_push(scratch.arena, &all_opts, n->string);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
use_null_env_arg = 1;
|
|
}
|
|
}
|
|
if(use_null_env_arg == 0)
|
|
{
|
|
env = str8_list_join(scratch.arena, &all_opts, &join_params2);
|
|
}
|
|
}
|
|
|
|
//- rjf: utf-8 -> utf-16
|
|
String16 cmd16 = str16_from_8(scratch.arena, cmd);
|
|
String16 dir16 = str16_from_8(scratch.arena, params->path);
|
|
String16 env16 = {0};
|
|
if(use_null_env_arg == 0)
|
|
{
|
|
env16 = str16_from_8(scratch.arena, env);
|
|
}
|
|
|
|
//- rjf: determine creation flags
|
|
DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT;
|
|
if(params->consoleless)
|
|
{
|
|
creation_flags |= CREATE_NO_WINDOW;
|
|
}
|
|
|
|
//- rjf: launch
|
|
BOOL inherit_handles = 0;
|
|
STARTUPINFOW startup_info = {sizeof(startup_info)};
|
|
if(!file_match(params->stdout_file, file_zero()))
|
|
{
|
|
HANDLE stdout_handle = (HANDLE)params->stdout_file.u64[0];
|
|
startup_info.hStdOutput = stdout_handle;
|
|
startup_info.dwFlags |= STARTF_USESTDHANDLES;
|
|
inherit_handles = 1;
|
|
}
|
|
if(!file_match(params->stderr_file, file_zero()))
|
|
{
|
|
HANDLE stderr_handle = (HANDLE)params->stderr_file.u64[0];
|
|
startup_info.hStdError = stderr_handle;
|
|
startup_info.dwFlags |= STARTF_USESTDHANDLES;
|
|
inherit_handles = 1;
|
|
}
|
|
if(!file_match(params->stdin_file, file_zero()))
|
|
{
|
|
HANDLE stdin_handle = (HANDLE)params->stdin_file.u64[0];
|
|
startup_info.hStdInput = stdin_handle;
|
|
startup_info.dwFlags |= STARTF_USESTDHANDLES;
|
|
inherit_handles = 1;
|
|
}
|
|
PROCESS_INFORMATION process_info = {0};
|
|
if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, inherit_handles, creation_flags, use_null_env_arg ? 0 : (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info))
|
|
{
|
|
result.u64[0] = (U64)process_info.hProcess;
|
|
CloseHandle(process_info.hThread);
|
|
}
|
|
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
internal U64
|
|
pid_from_process(Process process)
|
|
{
|
|
HANDLE process_handle = (HANDLE)process.u64[0];
|
|
U64 result = (U64)GetProcessId(process_handle);
|
|
return result;
|
|
}
|
|
|
|
internal B32
|
|
process_join(Process process, U64 endt_us, U64 *exit_code_out)
|
|
{
|
|
HANDLE process_handle = (HANDLE)(process.u64[0]);
|
|
DWORD sleep_ms = w32_sleep_ms_from_endt_us(endt_us);
|
|
DWORD result = WaitForSingleObject(process_handle, sleep_ms);
|
|
B32 process_joined = (result == WAIT_OBJECT_0);
|
|
if(process_joined && exit_code_out)
|
|
{
|
|
DWORD exit_code = 0;
|
|
if(GetExitCodeProcess(process_handle, &exit_code))
|
|
{
|
|
*exit_code_out = exit_code;
|
|
}
|
|
}
|
|
if(process_joined)
|
|
{
|
|
CloseHandle(process_handle);
|
|
}
|
|
return process_joined;
|
|
}
|
|
|
|
internal void
|
|
process_detach(Process process)
|
|
{
|
|
HANDLE process_handle = (HANDLE)(process.u64[0]);
|
|
CloseHandle(process_handle);
|
|
}
|
|
|
|
internal B32
|
|
process_kill(Process process)
|
|
{
|
|
HANDLE process_handle = (HANDLE)process.u64[0];
|
|
BOOL was_terminated = TerminateProcess(process_handle, 999);
|
|
return was_terminated;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: @per_os_impl Dynamically-Loaded Libraries
|
|
|
|
internal Library
|
|
library_open(String8 path)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
String16 path16 = str16_from_8(scratch.arena, path);
|
|
HMODULE mod = LoadLibraryW((LPCWSTR)path16.str);
|
|
Library result = { (U64)mod };
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
internal void
|
|
library_close(Library lib)
|
|
{
|
|
HMODULE mod = (HMODULE)lib.u64[0];
|
|
FreeLibrary(mod);
|
|
}
|
|
|
|
internal VoidProc *
|
|
library_load_proc(Library lib, String8 name)
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
HMODULE mod = (HMODULE)lib.u64[0];
|
|
name = push_str8_copy(scratch.arena, name);
|
|
VoidProc *result = (VoidProc*)GetProcAddress(mod, (LPCSTR)name.str);
|
|
scratch_end(scratch);
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////
|
|
//~ rjf: Entry Point
|
|
|
|
#include <dbghelp.h>
|
|
#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside
|
|
#include <shlwapi.h>
|
|
|
|
internal B32 win32_g_is_quiet = 0;
|
|
internal B32 win32_g_gen_dump = 0;
|
|
|
|
internal HRESULT WINAPI
|
|
win32_dialog_callback(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, LONG_PTR data)
|
|
{
|
|
if(msg == TDN_HYPERLINK_CLICKED)
|
|
{
|
|
ShellExecuteW(NULL, L"open", (LPWSTR)lparam, NULL, NULL, SW_SHOWNORMAL);
|
|
}
|
|
return S_OK;
|
|
}
|
|
|
|
internal LONG WINAPI
|
|
win32_exception_filter(EXCEPTION_POINTERS* exception_ptrs)
|
|
{
|
|
if(win32_g_is_quiet)
|
|
{
|
|
ExitProcess(1);
|
|
}
|
|
|
|
static volatile LONG first = 0;
|
|
if(InterlockedCompareExchange(&first, 1, 0) != 0)
|
|
{
|
|
// prevent failures in other threads to popup same message box
|
|
// this handler just shows first thread that crashes
|
|
// we are terminating afterwards anyway
|
|
for (;;) Sleep(1000);
|
|
}
|
|
|
|
WCHAR buffer[4096] = {0};
|
|
int buflen = 0;
|
|
|
|
DWORD exception_code = exception_ptrs->ExceptionRecord->ExceptionCode;
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"A fatal exception (code 0x%x) occurred. The process is terminating.\n", exception_code);
|
|
|
|
// load dbghelp dynamically just in case if it is missing
|
|
BOOL (WINAPI *dbg_MiniDumpWriteDump)(HANDLE hProcess, DWORD ProcessId, HANDLE hFile, MINIDUMP_TYPE DumpType, PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, PMINIDUMP_CALLBACK_INFORMATION CallbackParam) = 0;
|
|
HMODULE dbghelp = LoadLibraryA("dbghelp.dll");
|
|
if(dbghelp)
|
|
{
|
|
DWORD (WINAPI *dbg_SymSetOptions)(DWORD SymOptions);
|
|
BOOL (WINAPI *dbg_SymInitializeW)(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess);
|
|
BOOL (WINAPI *dbg_StackWalk64)(DWORD MachineType, HANDLE hProcess, HANDLE hThread,
|
|
LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
|
|
PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
|
|
PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress);
|
|
PVOID (WINAPI *dbg_SymFunctionTableAccess64)(HANDLE hProcess, DWORD64 AddrBase);
|
|
DWORD64 (WINAPI *dbg_SymGetModuleBase64)(HANDLE hProcess, DWORD64 qwAddr);
|
|
BOOL (WINAPI *dbg_SymFromAddrW)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFOW Symbol);
|
|
BOOL (WINAPI *dbg_SymGetLineFromAddrW64)(HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINEW64 Line);
|
|
BOOL (WINAPI *dbg_SymGetModuleInfoW64)(HANDLE hProcess, DWORD64 qwAddr, PIMAGEHLP_MODULEW64 ModuleInfo);
|
|
|
|
*(FARPROC*)&dbg_SymSetOptions = GetProcAddress(dbghelp, "SymSetOptions");
|
|
*(FARPROC*)&dbg_SymInitializeW = GetProcAddress(dbghelp, "SymInitializeW");
|
|
*(FARPROC*)&dbg_StackWalk64 = GetProcAddress(dbghelp, "StackWalk64");
|
|
*(FARPROC*)&dbg_SymFunctionTableAccess64 = GetProcAddress(dbghelp, "SymFunctionTableAccess64");
|
|
*(FARPROC*)&dbg_SymGetModuleBase64 = GetProcAddress(dbghelp, "SymGetModuleBase64");
|
|
*(FARPROC*)&dbg_SymFromAddrW = GetProcAddress(dbghelp, "SymFromAddrW");
|
|
*(FARPROC*)&dbg_SymGetLineFromAddrW64 = GetProcAddress(dbghelp, "SymGetLineFromAddrW64");
|
|
*(FARPROC*)&dbg_SymGetModuleInfoW64 = GetProcAddress(dbghelp, "SymGetModuleInfoW64");
|
|
*(FARPROC*)&dbg_MiniDumpWriteDump = GetProcAddress(dbghelp, "MiniDumpWriteDump");
|
|
|
|
if(dbg_SymSetOptions && dbg_SymInitializeW && dbg_StackWalk64 && dbg_SymFunctionTableAccess64 && dbg_SymGetModuleBase64 && dbg_SymFromAddrW && dbg_SymGetLineFromAddrW64 && dbg_SymGetModuleInfoW64)
|
|
{
|
|
HANDLE process = GetCurrentProcess();
|
|
HANDLE thread = GetCurrentThread();
|
|
CONTEXT context = *exception_ptrs->ContextRecord;
|
|
|
|
WCHAR module_path[MAX_PATH];
|
|
GetModuleFileNameW(NULL, module_path, ArrayCount(module_path));
|
|
PathRemoveFileSpecW(module_path);
|
|
|
|
dbg_SymSetOptions(SYMOPT_EXACT_SYMBOLS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
|
|
if(dbg_SymInitializeW(process, module_path, TRUE))
|
|
{
|
|
// check that raddbg.pdb file is good
|
|
B32 raddbg_pdb_valid = 0;
|
|
{
|
|
IMAGEHLP_MODULEW64 module = {0};
|
|
module.SizeOfStruct = sizeof(module);
|
|
if(dbg_SymGetModuleInfoW64(process, (DWORD64)&win32_exception_filter, &module))
|
|
{
|
|
raddbg_pdb_valid = (module.SymType == SymPdb);
|
|
}
|
|
}
|
|
|
|
if(!raddbg_pdb_valid)
|
|
{
|
|
buflen += wnsprintfW(buffer + buflen, sizeof(buffer) - buflen,
|
|
L"\nThe PDB debug information file for this executable is not valid or was not found. Please rebuild binary to get the call stack.\n");
|
|
}
|
|
else
|
|
{
|
|
STACKFRAME64 frame = {0};
|
|
DWORD image_type;
|
|
#if defined(_M_AMD64)
|
|
image_type = IMAGE_FILE_MACHINE_AMD64;
|
|
frame.AddrPC.Offset = context.Rip;
|
|
frame.AddrPC.Mode = AddrModeFlat;
|
|
frame.AddrFrame.Offset = context.Rbp;
|
|
frame.AddrFrame.Mode = AddrModeFlat;
|
|
frame.AddrStack.Offset = context.Rsp;
|
|
frame.AddrStack.Mode = AddrModeFlat;
|
|
#elif defined(_M_ARM64)
|
|
image_type = IMAGE_FILE_MACHINE_ARM64;
|
|
frame.AddrPC.Offset = context.Pc;
|
|
frame.AddrPC.Mode = AddrModeFlat;
|
|
frame.AddrFrame.Offset = context.Fp;
|
|
frame.AddrFrame.Mode = AddrModeFlat;
|
|
frame.AddrStack.Offset = context.Sp;
|
|
frame.AddrStack.Mode = AddrModeFlat;
|
|
#else
|
|
# error Arch not supported!
|
|
#endif
|
|
|
|
#if BUILD_CONSOLE_INTERFACE
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nCreate a new issue with this report at %S.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL);
|
|
#else
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen,
|
|
L"\nPress Ctrl+C to copy this text to clipboard, then create a new issue at\n"
|
|
L"<a href=\"%S\">%S</a>\n\n", BUILD_ISSUES_LINK_STRING_LITERAL, BUILD_ISSUES_LINK_STRING_LITERAL);
|
|
#endif
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"Call stack:\n");
|
|
|
|
U64 frame_offset = 0;
|
|
|
|
if (frame.AddrPC.Offset == 0)
|
|
{
|
|
// if IP address is 0 then most likely we have called indirectly on NULL function pointer
|
|
// which means no useful stack unwinding will happen, because there's no unwind info for address 0
|
|
// but we can try reading 8 bytes of return address from stack, and start unwinding there
|
|
|
|
ULONG_PTR hi, lo;
|
|
GetCurrentThreadStackLimits(&lo, &hi);
|
|
if (frame.AddrStack.Offset >= lo && frame.AddrStack.Offset <= hi - sizeof(void*))
|
|
{
|
|
frame.AddrPC.Offset = *(DWORD64*)frame.AddrStack.Offset - 1;
|
|
frame.AddrStack.Offset += sizeof(void*);
|
|
#if defined(_M_AMD64)
|
|
context.Rip = frame.AddrPC.Offset;
|
|
context.Rsp = frame.AddrStack.Offset;
|
|
#elif defined(_M_ARM64)
|
|
context.Pc = frame.AddrPC.Offset;
|
|
context.Sp = frame.AddrStack.Offset;
|
|
#endif
|
|
}
|
|
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"1. [NULL]\n");
|
|
frame_offset = 1;
|
|
}
|
|
|
|
for(U32 idx=0; ;idx++)
|
|
{
|
|
const U32 max_frames = 32;
|
|
if(idx == max_frames)
|
|
{
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"...");
|
|
break;
|
|
}
|
|
|
|
if(!dbg_StackWalk64(image_type, process, thread, &frame, &context, 0, dbg_SymFunctionTableAccess64, dbg_SymGetModuleBase64, 0))
|
|
{
|
|
break;
|
|
}
|
|
|
|
U64 address = frame.AddrPC.Offset;
|
|
if(address == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"%u. [0x%I64x]", frame_offset + idx + 1, address);
|
|
|
|
struct {
|
|
SYMBOL_INFOW info;
|
|
WCHAR name[MAX_SYM_NAME];
|
|
} symbol = {0};
|
|
|
|
symbol.info.SizeOfStruct = sizeof(symbol.info);
|
|
symbol.info.MaxNameLen = MAX_SYM_NAME;
|
|
|
|
DWORD64 displacement = 0;
|
|
if(dbg_SymFromAddrW(process, address, &displacement, &symbol.info))
|
|
{
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s +%u", symbol.info.Name, (DWORD)displacement);
|
|
|
|
IMAGEHLP_LINEW64 line = {0};
|
|
line.SizeOfStruct = sizeof(line);
|
|
|
|
DWORD line_displacement = 0;
|
|
if(dbg_SymGetLineFromAddrW64(process, address, &line_displacement, &line))
|
|
{
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L", %s line %u", PathFindFileNameW(line.FileName), line.LineNumber);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IMAGEHLP_MODULEW64 module = {0};
|
|
module.SizeOfStruct = sizeof(module);
|
|
if(dbg_SymGetModuleInfoW64(process, address, &module))
|
|
{
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s", module.ModuleName);
|
|
}
|
|
}
|
|
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nVersion: %S%S", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND);
|
|
|
|
B32 generate_crash_dump = win32_g_gen_dump;
|
|
#if BUILD_CONSOLE_INTERFACE
|
|
fwprintf(stderr, L"\n--- Fatal Exception ---\n");
|
|
fwprintf(stderr, L"%s\n\n", buffer);
|
|
#else
|
|
int selected_button = 0;
|
|
TASKDIALOG_BUTTON generate_dump = {1, L"Generate Crash Dump File"};
|
|
TASKDIALOGCONFIG dialog = {0};
|
|
dialog.cbSize = sizeof(dialog);
|
|
dialog.dwFlags = TDF_SIZE_TO_CONTENT | TDF_ENABLE_HYPERLINKS | TDF_ALLOW_DIALOG_CANCELLATION;
|
|
dialog.pszMainIcon = TD_ERROR_ICON;
|
|
dialog.dwCommonButtons = TDCBF_CLOSE_BUTTON;
|
|
dialog.pszWindowTitle = L"Fatal Exception";
|
|
dialog.pszContent = buffer;
|
|
dialog.pfCallback = &win32_dialog_callback;
|
|
dialog.cButtons = 1;
|
|
dialog.pButtons = &generate_dump;
|
|
TaskDialogIndirect(&dialog, &selected_button, 0, 0);
|
|
generate_crash_dump = (selected_button == generate_dump.nButtonID);
|
|
#endif
|
|
|
|
if(dbg_MiniDumpWriteDump && generate_crash_dump)
|
|
{
|
|
WCHAR dump_file_path[MAX_PATH] = {0};
|
|
SHGetFolderPathW(0, CSIDL_DESKTOP, 0, 0, dump_file_path);
|
|
PathAppendW(dump_file_path, L"raddbg_crash_dump.dmp");
|
|
HANDLE file = CreateFileW(dump_file_path, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
|
|
if(file != INVALID_HANDLE_VALUE)
|
|
{
|
|
MINIDUMP_EXCEPTION_INFORMATION info = {0};
|
|
info.ThreadId = GetCurrentThreadId();
|
|
info.ExceptionPointers = exception_ptrs;
|
|
info.ClientPointers = FALSE;
|
|
BOOL dump_successful = dbg_MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpNormal, &info, 0, 0);
|
|
CloseHandle(file);
|
|
if(dump_successful)
|
|
{
|
|
#if !BUILD_CONSOLE_INTERFACE
|
|
// opens explorer and selects file
|
|
SFGAOF flags = 0;
|
|
PIDLIST_ABSOLUTE list = 0;
|
|
if (SUCCEEDED(SHParseDisplayName(dump_file_path, NULL, &list, 0, &flags)))
|
|
{
|
|
SHOpenFolderAndSelectItems(list, 0, NULL, 0);
|
|
CoTaskMemFree(list);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
ExitProcess(1);
|
|
}
|
|
|
|
#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside
|
|
#define OS_WINDOWS 1
|
|
|
|
internal void
|
|
w32_entry_point_caller(int argc, WCHAR **wargv)
|
|
{
|
|
SetUnhandledExceptionFilter(&win32_exception_filter);
|
|
|
|
//- rjf: dynamically load windows functions which are not guaranteed
|
|
// in all SDKs
|
|
{
|
|
HMODULE module = LoadLibraryA("kernel32.dll");
|
|
w32_SetThreadDescription_func = (W32_SetThreadDescription_Type *)GetProcAddress(module, "SetThreadDescription");
|
|
w32_InitializeSynchronizationBarrier_func = (W32_InitializeSynchronizationBarrier_Type *)GetProcAddress(module, "InitializeSynchronizationBarrier");
|
|
w32_DeleteSynchronizationBarrier_func = (W32_DeleteSynchronizationBarrier_Type *)GetProcAddress(module, "DeleteSynchronizationBarrier");
|
|
w32_EnterSynchronizationBarrier_func = (W32_EnterSynchronizationBarrier_Type *)GetProcAddress(module, "EnterSynchronizationBarrier");
|
|
if(w32_InitializeSynchronizationBarrier_func == 0)
|
|
{
|
|
w32_DeleteSynchronizationBarrier_func = 0;
|
|
w32_EnterSynchronizationBarrier_func = 0;
|
|
}
|
|
FreeLibrary(module);
|
|
}
|
|
|
|
//- rjf: try to allow large pages if we can
|
|
B32 large_pages_allowed = 0;
|
|
{
|
|
HANDLE token;
|
|
if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))
|
|
{
|
|
LUID luid;
|
|
if(LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid))
|
|
{
|
|
TOKEN_PRIVILEGES priv;
|
|
priv.PrivilegeCount = 1;
|
|
priv.Privileges[0].Luid = luid;
|
|
priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
|
large_pages_allowed = !!AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0);
|
|
}
|
|
CloseHandle(token);
|
|
}
|
|
}
|
|
|
|
//- rjf: get RIO extension function table
|
|
{
|
|
// NOTE(mmozeiko): need to get function pointers to RIO functions, and that requires dummy socket
|
|
WSADATA WinSockData;
|
|
WSAStartup(MAKEWORD(2, 2), &WinSockData);
|
|
GUID guid = WSAID_MULTIPLE_RIO;
|
|
DWORD rio_byte = 0;
|
|
SOCKET Sock = socket(AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP);
|
|
WSAIoctl(Sock, SIO_GET_MULTIPLE_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), (void**)&w32_rio_functions, sizeof(w32_rio_functions), &rio_byte, 0, 0);
|
|
closesocket(Sock);
|
|
}
|
|
|
|
//- rjf: get system info
|
|
SYSTEM_INFO sysinfo = {0};
|
|
GetSystemInfo(&sysinfo);
|
|
|
|
//- rjf: set up non-dynamically-alloc'd state
|
|
//
|
|
// (we need to set up some basics before this layer can supply
|
|
// memory allocation primitives)
|
|
{
|
|
w32_state.microsecond_resolution = 1;
|
|
LARGE_INTEGER large_int_resolution;
|
|
if(QueryPerformanceFrequency(&large_int_resolution))
|
|
{
|
|
w32_state.microsecond_resolution = large_int_resolution.QuadPart;
|
|
}
|
|
}
|
|
{
|
|
SystemInfo *info = &w32_state.system_info;
|
|
info->logical_processor_count = (U64)sysinfo.dwNumberOfProcessors;
|
|
info->page_size = sysinfo.dwPageSize;
|
|
info->large_page_size = GetLargePageMinimum();
|
|
info->allocation_granularity = sysinfo.dwAllocationGranularity;
|
|
}
|
|
{
|
|
ProcessInfo *info = &w32_state.process_info;
|
|
info->large_pages_allowed = large_pages_allowed;
|
|
info->pid = GetCurrentProcessId();
|
|
}
|
|
|
|
//- rjf: extract arguments
|
|
Arena *args_arena = arena_alloc(.reserve_size = MB(1), .commit_size = KB(32));
|
|
char **argv = push_array(args_arena, char *, argc);
|
|
for(int i = 0; i < argc; i += 1)
|
|
{
|
|
String16 arg16 = str16_cstring((U16 *)wargv[i]);
|
|
String8 arg8 = str8_from_16(args_arena, arg16);
|
|
if(str8_match(arg8, str8_lit("--quiet"), StringMatchFlag_CaseInsensitive) ||
|
|
str8_match(arg8, str8_lit("-quiet"), StringMatchFlag_CaseInsensitive))
|
|
{
|
|
win32_g_is_quiet = 1;
|
|
}
|
|
if(str8_match(arg8, str8_lit("--large_pages"), StringMatchFlag_CaseInsensitive) ||
|
|
str8_match(arg8, str8_lit("-large_pages"), StringMatchFlag_CaseInsensitive))
|
|
{
|
|
arena_default_flags = ArenaFlag_LargePages;
|
|
arena_default_reserve_size = Max(MB(64), w32_state.system_info.large_page_size);
|
|
arena_default_commit_size = arena_default_reserve_size;
|
|
}
|
|
if(str8_match(arg8, str8_lit("--gen_crash_dump"), StringMatchFlag_CaseInsensitive) ||
|
|
str8_match(arg8, str8_lit("-gen_crash_dump"), StringMatchFlag_CaseInsensitive))
|
|
{
|
|
win32_g_gen_dump = 1;
|
|
}
|
|
argv[i] = (char *)arg8.str;
|
|
}
|
|
|
|
//- rjf: set up thread context
|
|
TCTX *tctx = tctx_alloc();
|
|
tctx_select(tctx);
|
|
|
|
//- rjf: set up dynamically-alloc'd state
|
|
Arena *arena = arena_alloc();
|
|
{
|
|
w32_state.arena = arena;
|
|
{
|
|
SystemInfo *info = &w32_state.system_info;
|
|
U8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0};
|
|
DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
|
|
if(GetComputerNameA((char*)buffer, &size))
|
|
{
|
|
info->machine_name = push_str8_copy(arena, str8(buffer, size));
|
|
}
|
|
}
|
|
}
|
|
{
|
|
ProcessInfo *info = &w32_state.process_info;
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
DWORD size = KB(32);
|
|
U16 *buffer = push_array_no_zero(scratch.arena, U16, size);
|
|
DWORD length = GetModuleFileNameW(0, (WCHAR*)buffer, size);
|
|
String8 name8 = str8_from_16(scratch.arena, str16(buffer, length));
|
|
info->binary_file_path = push_str8_copy(arena, name8);
|
|
info->binary_path = str8_chop_last_slash(info->binary_file_path);
|
|
scratch_end(scratch);
|
|
}
|
|
info->initial_path = get_current_path(arena);
|
|
{
|
|
Temp scratch = scratch_begin(0, 0);
|
|
U64 size = KB(32);
|
|
U16 *buffer = push_array_no_zero(scratch.arena, U16, size);
|
|
if(SUCCEEDED(SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, (WCHAR*)buffer)))
|
|
{
|
|
info->user_program_config_data_path = str8_from_16(arena, str16_cstring(buffer));
|
|
info->user_program_cache_data_path = info->user_program_logs_data_path = info->user_program_config_data_path;
|
|
}
|
|
scratch_end(scratch);
|
|
}
|
|
{
|
|
WCHAR *this_proc_env = GetEnvironmentStringsW();
|
|
U64 start_idx = 0;
|
|
for(U64 idx = 0;; idx += 1)
|
|
{
|
|
if(this_proc_env[idx] == 0)
|
|
{
|
|
if(start_idx == idx)
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
String16 string16 = str16((U16 *)this_proc_env + start_idx, idx - start_idx);
|
|
String8 string = str8_from_16(arena, string16);
|
|
str8_list_push(arena, &info->environment, string);
|
|
start_idx = idx+1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//- rjf: set up entity storage
|
|
InitializeCriticalSection(&w32_state.entity_mutex);
|
|
w32_state.entity_arena = arena_alloc();
|
|
|
|
//- rjf: call into "real" entry point
|
|
main_thread_base_entry_point(argc, argv);
|
|
}
|
|
|
|
#if BUILD_CONSOLE_INTERFACE
|
|
int wmain(int argc, WCHAR **argv)
|
|
{
|
|
w32_entry_point_caller(argc, argv);
|
|
return 0;
|
|
}
|
|
#else
|
|
int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
|
|
{
|
|
CoInitializeEx(0, COINIT_APARTMENTTHREADED);
|
|
w32_entry_point_caller(__argc, __wargv);
|
|
return 0;
|
|
}
|
|
#endif
|