int main_run(void* main_arg) { #if !BUILD_MONOLITHIC string_const_t pattern; string_t* exe_paths = 0; size_t iexe, exesize; process_t* process = 0; string_t process_path = { 0, 0 }; unsigned int* exe_flags = 0; #else void* test_result; #endif #if FOUNDATION_PLATFORM_IOS || FOUNDATION_PLATFORM_ANDROID || FOUNDATION_PLATFORM_PNACL int remain_counter = 0; #endif #if BUILD_DEBUG const string_const_t build_name = string_const(STRING_CONST("debug")); #elif BUILD_RELEASE const string_const_t build_name = string_const(STRING_CONST("release")); #elif BUILD_PROFILE const string_const_t build_name = string_const(STRING_CONST("profile")); #elif BUILD_DEPLOY const string_const_t build_name = string_const(STRING_CONST("deploy")); #endif #if BUILD_MONOLITHIC const string_const_t build_type = string_const(STRING_CONST(" monolithic")); #else const string_const_t build_type = string_empty(); #endif char* pathbuf; int process_result = 0; thread_t event_thread; FOUNDATION_UNUSED(main_arg); FOUNDATION_UNUSED(build_name); log_set_suppress(HASH_TEST, ERRORLEVEL_DEBUG); log_infof(HASH_TEST, STRING_CONST("Task library v%s built for %s using %s (%.*s%.*s)"), string_from_version_static(task_module_version()).str, FOUNDATION_PLATFORM_DESCRIPTION, FOUNDATION_COMPILER_DESCRIPTION, STRING_FORMAT(build_name), STRING_FORMAT(build_type)); thread_initialize(&event_thread, event_loop, 0, STRING_CONST("event_thread"), THREAD_PRIORITY_NORMAL, 0); thread_start(&event_thread); pathbuf = memory_allocate(HASH_STRING, BUILD_MAX_PATHLEN, 0, MEMORY_PERSISTENT); while (!thread_is_running(&event_thread)) thread_sleep(10); #if FOUNDATION_PLATFORM_IOS || FOUNDATION_PLATFORM_ANDROID || FOUNDATION_PLATFORM_PNACL while (!_test_should_start) { #if FOUNDATION_PLATFORM_ANDROID system_process_events(); #endif thread_sleep(100); } #endif fs_remove_directory(STRING_ARGS(environment_temporary_directory())); #if BUILD_MONOLITHIC test_run_fn tests[] = { test_task_run, 0 }; #if FOUNDATION_PLATFORM_ANDROID thread_t test_thread; thread_initialize(&test_thread, test_runner, tests, STRING_CONST("test_runner"), THREAD_PRIORITY_NORMAL, 0); thread_start(&test_thread); log_debug(HASH_TEST, STRING_CONST("Starting test runner thread")); while (!thread_is_running(&test_thread)) { system_process_events(); thread_sleep(10); } while (thread_is_running(&test_thread)) { system_process_events(); thread_sleep(10); } test_result = thread_join(&test_thread); process_result = (int)(intptr_t)test_result; thread_finalize(&test_thread); #else test_result = test_runner(tests); process_result = (int)(intptr_t)test_result; #endif if (process_result != 0) log_warnf(HASH_TEST, WARNING_SUSPICIOUS, STRING_CONST("Tests failed with exit code %d"), process_result); #if FOUNDATION_PLATFORM_IOS || FOUNDATION_PLATFORM_ANDROID || FOUNDATION_PLATFORM_PNACL while (!_test_should_terminate && _test_have_focus && (remain_counter < 50)) { system_process_events(); thread_sleep(100); ++remain_counter; } #endif log_debug(HASH_TEST, STRING_CONST("Exiting main loop")); #else // !BUILD_MONOLITHIC //Find all test executables in the current executable directory #if FOUNDATION_PLATFORM_WINDOWS pattern = string_const(STRING_CONST("^test-.*\\.exe$")); #elif FOUNDATION_PLATFORM_MACOSX pattern = string_const(STRING_CONST("^test-.*$")); #elif FOUNDATION_PLATFORM_POSIX pattern = string_const(STRING_CONST("^test-.*$")); #else # error Not implemented #endif exe_paths = fs_matching_files(STRING_ARGS(environment_executable_directory()), STRING_ARGS(pattern), false); array_resize(exe_flags, array_size(exe_paths)); memset(exe_flags, 0, sizeof(unsigned int) * array_size(exe_flags)); #if FOUNDATION_PLATFORM_MACOSX //Also search for test applications string_const_t app_pattern = string_const(STRING_CONST("^test-.*\\.app$")); regex_t* app_regex = regex_compile(app_pattern.str, app_pattern.length); string_t* subdirs = fs_subdirs(STRING_ARGS(environment_executable_directory())); for (size_t idir = 0, dirsize = array_size(subdirs); idir < dirsize; ++idir) { if (regex_match(app_regex, subdirs[idir].str, subdirs[idir].length, 0, 0)) { string_t exe_path = { subdirs[idir].str, subdirs[idir].length - 4 }; array_push(exe_paths, exe_path); array_push(exe_flags, PROCESS_MACOSX_USE_OPENAPPLICATION); } } string_array_deallocate(subdirs); regex_deallocate(app_regex); #endif for (iexe = 0, exesize = array_size(exe_paths); iexe < exesize; ++iexe) { string_const_t* process_args = 0; string_const_t exe_file_name = path_base_file_name(STRING_ARGS(exe_paths[iexe])); if (string_equal(STRING_ARGS(exe_file_name), STRING_ARGS(environment_executable_name()))) continue; //Don't run self process_path = path_concat(pathbuf, BUILD_MAX_PATHLEN, STRING_ARGS(environment_executable_directory()), STRING_ARGS(exe_paths[iexe])); process = process_allocate(); process_set_executable_path(process, STRING_ARGS(process_path)); process_set_working_directory(process, STRING_ARGS(environment_executable_directory())); process_set_flags(process, PROCESS_ATTACHED | exe_flags[iexe]); if (!_test_memory_tracker) array_push(process_args, string_const(STRING_CONST("--no-memory-tracker"))); process_set_arguments(process, process_args, array_size(process_args)); log_infof(HASH_TEST, STRING_CONST("Running test executable: %.*s"), STRING_FORMAT(exe_paths[iexe])); process_result = process_spawn(process); while (process_result == PROCESS_WAIT_INTERRUPTED) { thread_sleep(10); process_result = process_wait(process); } process_deallocate(process); array_deallocate(process_args); if (process_result != 0) { if (process_result >= PROCESS_INVALID_ARGS) log_warnf(HASH_TEST, WARNING_SUSPICIOUS, STRING_CONST("Tests failed, process terminated with error %x"), process_result); else log_warnf(HASH_TEST, WARNING_SUSPICIOUS, STRING_CONST("Tests failed with exit code %d"), process_result); process_set_exit_code(-1); goto exit; } log_infof(HASH_TEST, STRING_CONST("All tests from %.*s passed (%d)"), STRING_FORMAT(exe_paths[iexe]), process_result); } log_info(HASH_TEST, STRING_CONST("All tests passed")); exit: if (exe_paths) string_array_deallocate(exe_paths); array_deallocate(exe_flags); #endif _test_should_terminate = true; thread_signal(&event_thread); thread_finalize(&event_thread); memory_deallocate(pathbuf); log_infof(HASH_TEST, STRING_CONST("Tests exiting: %s (%d)"), process_result ? "FAILED" : "PASSED", process_result); if (process_result) memory_set_tracker(memory_tracker_none()); return process_result; }
unsigned int stacktrace_capture( void** trace, unsigned int max_depth, unsigned int skip_frames ) { unsigned int num_frames = 0; if( !trace ) return 0; if( !max_depth ) max_depth = BUILD_SIZE_STACKTRACE_DEPTH; if( max_depth > BUILD_SIZE_STACKTRACE_DEPTH ) max_depth = BUILD_SIZE_STACKTRACE_DEPTH; if( !_stackwalk_initialized ) { if( !_initialize_stackwalker() ) { memset( trace, 0, sizeof( void* ) * max_depth ); return num_frames; } } #if FOUNDATION_PLATFORM_WINDOWS && ( FOUNDATION_COMPILER_MSVC || FOUNDATION_COMPILER_INTEL ) // Add 1 skip frame for this function call ++skip_frames; # if USE_CAPTURESTACKBACKTRACE if( CallRtlCaptureStackBackTrace ) { void* local_trace[BUILD_SIZE_STACKTRACE_DEPTH]; if( max_depth + skip_frames > BUILD_SIZE_STACKTRACE_DEPTH ) max_depth = BUILD_SIZE_STACKTRACE_DEPTH - skip_frames; num_frames = (unsigned int)CallRtlCaptureStackBackTrace( skip_frames, max_depth, local_trace, 0 ); if( num_frames > max_depth ) num_frames = max_depth; memcpy( trace, local_trace, sizeof( void* ) * num_frames ); memset( trace + num_frames, 0, sizeof( void* ) * ( max_depth - num_frames ) ); } else { # else { # endif # if FOUNDATION_PLATFORM_ARCH_X86_64 // Raise an exception so helper has access to context record. __try { RaiseException( 0, // Application-defined exception code. 0, // Zero indicates continuable exception. 0, // Number of arguments in args array (ignored if args is null) 0 ); // Array of arguments } __except( _capture_stack_trace_helper( trace, max_depth, skip_frames, (GetExceptionInformation())->ContextRecord ) ) { } # else // Use a bit of inline assembly to capture the information relevant to stack walking which is // basically EIP and EBP. CONTEXT context; memset( &context, 0, sizeof( CONTEXT ) ); context.ContextFlags = CONTEXT_FULL; log_warnf( WARNING_DEPRECATED, "********** REIMPLEMENT FALLBACK STACKTRACE **********" ); /* Use a fake function call to pop the return address and retrieve EIP.*/ __asm { call FakeStackTraceCall FakeStackTraceCall: pop eax mov context.Eip, eax mov context.Ebp, ebp mov context.Esp, esp } // Capture the back trace. _capture_stack_trace_helper( trace, max_depth, skip_frames, &context ); # endif } #elif FOUNDATION_PLATFORM_APPLE //TODO: Implement #elif FOUNDATION_PLATFORM_POSIX // Add 1 skip frames for this function call skip_frames += 1; void* localframes[BUILD_SIZE_STACKTRACE_DEPTH]; num_frames = (unsigned int)backtrace( localframes, BUILD_SIZE_STACKTRACE_DEPTH ); if( num_frames > skip_frames ) { num_frames -= skip_frames; if( num_frames > max_depth ) num_frames = max_depth; memcpy( trace, localframes + skip_frames, sizeof( void* ) * num_frames ); } else trace[0] = 0; #endif return num_frames; } static bool _symbol_resolve_initialized = false; static bool _initialize_symbol_resolve() { if( _symbol_resolve_initialized ) return true; #if FOUNDATION_PLATFORM_WINDOWS { unsigned int options; void* dll = LoadLibraryA( "PSAPI.DLL" ); if( !dll ) return _symbol_resolve_initialized; CallEnumProcesses = (EnumProcessesFn)GetProcAddress( dll, "EnumProcesses" ); CallEnumProcessModules = (EnumProcessModulesFn)GetProcAddress( dll, "EnumProcessModules" ); CallGetModuleFileNameEx = (GetModuleFileNameExFn)GetProcAddress( dll, "GetModuleFileNameExA" ); CallGetModuleBaseName = (GetModuleBaseNameFn)GetProcAddress( dll, "GetModuleBaseNameA" ); CallGetModuleInformation = (GetModuleInformationFn)GetProcAddress( dll, "GetModuleInformation" ); if( !CallEnumProcesses || !CallEnumProcessModules || !CallGetModuleFileNameEx || !CallGetModuleBaseName || !CallGetModuleInformation ) return _symbol_resolve_initialized; dll = LoadLibraryA( "DBGHELP.DLL" ); if( !dll ) return _symbol_resolve_initialized; CallSymInitialize = (SymInitializeFn)GetProcAddress( dll, "SymInitialize" ); CallSymSetOptions = (SymSetOptionsFn)GetProcAddress( dll, "SymSetOptions" ); CallSymGetOptions = (SymGetOptionsFn)GetProcAddress( dll, "SymGetOptions" ); CallSymLoadModule64 = (SymLoadModule64Fn)GetProcAddress( dll, "SymLoadModule64" ); CallSymSetSearchPath = (SymSetSearchPathFn)GetProcAddress( dll, "SymSetSearchPath" ); CallSymGetModuleInfo64 = (SymGetModuleInfo64Fn)GetProcAddress( dll, "SymGetModuleInfo64" ); CallSymGetLineFromAddr64 = (SymGetLineFromAddr64Fn)GetProcAddress( dll, "SymGetLineFromAddr64" ); CallSymGetSymFromAddr64 = (SymGetSymFromAddr64Fn)GetProcAddress( dll, "SymGetSymFromAddr64" ); CallSymGetModuleBase64 = (SymGetModuleBase64Fn)GetProcAddress( dll, "SymGetModuleBase64" ); CallSymFunctionTableAccess64 = (SymFunctionTableAccess64Fn)GetProcAddress( dll, "SymFunctionTableAccess64" ); if( !CallSymInitialize || !CallSymSetOptions || !CallSymGetOptions || !CallSymLoadModule64 || !CallSymSetSearchPath || !CallSymGetModuleInfo64 || !CallSymGetLineFromAddr64 || !CallSymGetSymFromAddr64 || !CallSymGetModuleBase64 || !CallSymFunctionTableAccess64 ) return _symbol_resolve_initialized; options = CallSymGetOptions(); options |= SYMOPT_LOAD_LINES; options |= SYMOPT_DEBUG; options |= SYMOPT_UNDNAME; options |= SYMOPT_LOAD_LINES; options |= SYMOPT_FAIL_CRITICAL_ERRORS; options |= SYMOPT_DEFERRED_LOADS; options |= SYMOPT_ALLOW_ABSOLUTE_SYMBOLS; options |= SYMOPT_EXACT_SYMBOLS; options |= SYMOPT_CASE_INSENSITIVE; CallSymSetOptions( options ); CallSymInitialize( GetCurrentProcess(), 0, TRUE ); } _load_process_modules(); _symbol_resolve_initialized = true; #else _symbol_resolve_initialized = true; #endif return _symbol_resolve_initialized; } static NOINLINE char** _resolve_stack_frames( void** frames, unsigned int max_frames ) { #if FOUNDATION_PLATFORM_WINDOWS char** lines = 0; char symbol_buffer[ sizeof( IMAGEHLP_SYMBOL64 ) + 512 ]; PIMAGEHLP_SYMBOL64 symbol; DWORD displacement = 0; uint64_t displacement64 = 0; unsigned int iaddr = 0; unsigned int last_error; bool found = false; HANDLE process_handle = GetCurrentProcess(); int buffer_offset = 0; bool last_was_main = false; IMAGEHLP_LINE64 line64; IMAGEHLP_MODULE64 module64; for( iaddr = 0; ( iaddr < max_frames ) && !last_was_main; ++iaddr ) { char* resolved = 0; const char* function_name = "??"; const char* file_name = "??"; const char* module_name = "??"; unsigned int line_number = 0; //Allow first frame to be null in case of a function call to a null pointer if( iaddr && !frames[iaddr] ) break; // Initialize symbol. symbol = (PIMAGEHLP_SYMBOL64)symbol_buffer; memset( symbol, 0, sizeof( symbol_buffer ) ); symbol->SizeOfStruct = sizeof( symbol_buffer ); symbol->MaxNameLength = 512; // Get symbol from address. if( CallSymGetSymFromAddr64 && CallSymGetSymFromAddr64( process_handle, (uint64_t)((uintptr_t)frames[iaddr]), &displacement64, symbol ) ) { int offset = 0; while( symbol->Name[offset] < 32 ) ++offset; function_name = symbol->Name + offset; } else { // No symbol found for this address. last_error = GetLastError(); } memset( &line64, 0, sizeof( line64 ) ); line64.SizeOfStruct = sizeof( line64 ); if( CallSymGetLineFromAddr64 && CallSymGetLineFromAddr64( process_handle, (uint64_t)((uintptr_t)frames[iaddr]), &displacement, &line64 ) ) { file_name = line64.FileName; line_number = line64.LineNumber; } memset( &module64, 0, sizeof( module64 ) ); module64.SizeOfStruct = sizeof( module64 ); if( CallSymGetModuleInfo64 && CallSymGetModuleInfo64( process_handle, (uint64_t)((uintptr_t)frames[iaddr]), &module64 ) ) { int last_slash = STRING_NPOS; module_name = module64.ImageName; last_slash = string_rfind( module_name, '\\', STRING_NPOS ); if( last_slash != STRING_NPOS ) module_name += last_slash + 1; } resolved = string_format( "[" STRING_FORMAT_POINTER "] %s (%s:%d +%d bytes) [in %s]", frames[iaddr], function_name, file_name, line_number, displacement, module_name ); array_push( lines, resolved ); if( string_equal( function_name, "main" ) ) last_was_main = true; } return lines; #elif FOUNDATION_PLATFORM_LINUX char** addrs = 0; char** lines = 0; const char** args = 0; process_t* proc = process_allocate(); unsigned int num_frames = 0; unsigned int requested_frames = 0; bool last_was_main = false; if( !string_length( environment_executable_path() ) ) { for( unsigned int iaddr = 0; iaddr < max_frames; ++iaddr ) { //Allow first frame to be null in case of a function call to a null pointer if( iaddr && !frames[iaddr] ) break; array_push( lines, string_format( "[" STRING_FORMAT_POINTER "]", frames[iaddr] ) ); } return lines; } array_push( args, "-e" ); array_push( args, environment_executable_path() ); array_push( args, "-f" ); for( unsigned int iaddr = 0; iaddr < max_frames; ++iaddr ) { //Allow first frame to be null in case of a function call to a null pointer if( iaddr && !frames[iaddr] ) break; char* addr = string_format( STRING_FORMAT_POINTER, frames[iaddr] ); array_push( addrs, addr ); array_push( args, addr ); ++requested_frames; } process_set_working_directory( proc, environment_initial_working_directory() ); process_set_executable_path( proc, "/usr/bin/addr2line" ); process_set_arguments( proc, args, array_size( args ) ); process_set_flags( proc, PROCESS_ATTACHED | PROCESS_STDSTREAMS ); process_spawn( proc ); stream_t* procout = process_stdout( proc ); while( !stream_eos( procout ) && ( num_frames < requested_frames ) && !last_was_main ) { char* function = stream_read_line( procout, '\n' ); char* filename = stream_read_line( procout, '\n' ); array_push( lines, string_format( "[" STRING_FORMAT_POINTER "] %s (%s)", frames[num_frames], function && string_length( function ) ? function : "??", filename && string_length( filename ) ? filename : "??" ) ); if( string_equal( function, "main" ) ) last_was_main = true; string_deallocate( function ); string_deallocate( filename ); ++num_frames; } process_wait( proc ); process_deallocate( proc ); string_array_deallocate( addrs ); array_deallocate( args ); return lines; #else char** lines = 0; for( unsigned int iaddr = 0; iaddr < max_frames; ++iaddr ) { //Allow first frame to be null in case of a function call to a null pointer if( iaddr && !frames[iaddr] ) break; array_push( lines, string_format( "[" STRING_FORMAT_POINTER "]\n", frames[iaddr] ) ); } return lines; #endif }