Ejemplo n.º 1
0
DECLARE_TEST(environment, builtin) {
	const string_const_t* cmdline = environment_command_line();

	EXPECT_GE(array_size(cmdline), 1);
#if !BUILD_MONOLITHIC
	EXPECT_NE_MSGFORMAT(string_find_string(STRING_ARGS(cmdline[0]), STRING_CONST("test-environment"),
	                                       0), STRING_NPOS, "Commandline: %.*s", (int)cmdline[0].length, cmdline[0].str);
	EXPECT_CONSTSTRINGEQ(environment_executable_name(), string_const(STRING_CONST("test-environment")));
#elif FOUNDATION_PLATFORM_ANDROID
	EXPECT_NE_MSGFORMAT(string_find_string(STRING_ARGS(cmdline[0]),
	                                       STRING_CONST("com.rampantpixels.foundation.test"), 0), STRING_NPOS, "Commandline: %.*s",
	                    (int)cmdline[0].length, cmdline[0].str);
#elif !FOUNDATION_PLATFORM_PNACL
	EXPECT_NE_MSGFORMAT(string_find_string(STRING_ARGS(cmdline[0]), STRING_CONST("test-all"), 0),
	                    STRING_NPOS, "Commandline: %.*s", (int)cmdline[0].length, cmdline[0].str);
	EXPECT_CONSTSTRINGEQ(environment_executable_name(), string_const(STRING_CONST("test-all")));
#endif
	EXPECT_NE(environment_initial_working_directory().str, 0);
	EXPECT_NE(environment_initial_working_directory().length, 0);
	EXPECT_CONSTSTRINGEQ(environment_initial_working_directory(),
	                     environment_current_working_directory());

	EXPECT_NE(environment_home_directory().str, 0);
	EXPECT_NE(environment_home_directory().length, 0);

	EXPECT_NE(environment_temporary_directory().str, 0);
	EXPECT_NE(environment_temporary_directory().length, 0);

#if !FOUNDATION_PLATFORM_PNACL
	EXPECT_NE(environment_variable(STRING_CONST("PATH")).str, 0);
	EXPECT_NE(environment_variable(STRING_CONST("PATH")).length, 0);
#endif

	return 0;
}
Ejemplo n.º 2
0
DECLARE_TEST( environment, builtin )
{
	char const* const* cmdline = environment_command_line();

	EXPECT_GE( array_size( cmdline ), 1 );
#if !FOUNDATION_PLATFORM_ANDROID && !FOUNDATION_PLATFORM_IOS
	EXPECT_NE( string_find_string( cmdline[0], "test-environment", 0 ), STRING_NPOS );

	EXPECT_STREQ( environment_executable_name(), "test-environment" );
#endif
	EXPECT_NE( environment_initial_working_directory(), 0 );
	EXPECT_NE( string_length( environment_initial_working_directory() ), 0 );
	EXPECT_STREQ( environment_initial_working_directory(), environment_current_working_directory() );
	
	EXPECT_NE( environment_home_directory(), 0 );
	EXPECT_NE( string_length( environment_home_directory() ), 0 );

	EXPECT_NE( environment_temporary_directory(), 0 );
	EXPECT_NE( string_length( environment_temporary_directory() ), 0 );

	EXPECT_NE( environment_variable( "PATH" ), 0 );
	EXPECT_NE( string_length( environment_variable( "PATH" ) ), 0 );
	
	return 0;
}
Ejemplo n.º 3
0
DECLARE_TEST( environment, builtin )
{
	char const* const* cmdline = environment_command_line();

	EXPECT_GE( array_size( cmdline ), 1 );
#if !BUILD_MONOLITHIC
	EXPECT_NE_MSGFORMAT( string_find_string( cmdline[0], "test-environment", 0 ), STRING_NPOS, "Commandline: %s", cmdline[0] );
	EXPECT_STREQ( environment_executable_name(), "test-environment" );
#elif FOUNDATION_PLATFORM_ANDROID
	EXPECT_NE_MSGFORMAT( string_find_string( cmdline[0], "com.rampantpixels.foundation.test", 0 ), STRING_NPOS, "Commandline: %s", cmdline[0] );
#elif !FOUNDATION_PLATFORM_PNACL
	EXPECT_NE_MSGFORMAT( string_find_string( cmdline[0], "test-all", 0 ), STRING_NPOS, "Commandline: %s", cmdline[0] );
	EXPECT_STREQ( environment_executable_name(), "test-all" );
#endif
	EXPECT_NE( environment_initial_working_directory(), 0 );
	EXPECT_NE( string_length( environment_initial_working_directory() ), 0 );
	EXPECT_STREQ( environment_initial_working_directory(), environment_current_working_directory() );

	EXPECT_NE( environment_home_directory(), 0 );
	EXPECT_NE( string_length( environment_home_directory() ), 0 );

	EXPECT_NE( environment_temporary_directory(), 0 );
	EXPECT_NE( string_length( environment_temporary_directory() ), 0 );

#if !FOUNDATION_PLATFORM_PNACL
	EXPECT_NE( environment_variable( "PATH" ), 0 );
	EXPECT_NE( string_length( environment_variable( "PATH" ) ), 0 );
#endif

	return 0;
}
Ejemplo n.º 4
0
static NOINLINE const char* _expand_environment( hash_t key, char* var )
{
	if( key == HASH_EXECUTABLE_NAME )
		return environment_executable_name();
	else if( key == HASH_EXECUTABLE_DIRECTORY )
		return environment_executable_directory();
	else if( key == HASH_EXECUTABLE_PATH )
		return environment_executable_path();
	else if( key == HASH_INITIAL_WORKING_DIRECTORY )
		return environment_initial_working_directory();
	else if( key == HASH_CURRENT_WORKING_DIRECTORY )
		return environment_current_working_directory();
	else if( key == HASH_HOME_DIRECTORY )
		return environment_home_directory();
	else if( key == HASH_TEMPORARY_DIRECTORY )
		return environment_temporary_directory();
	else if( string_equal_substr( var, "variable[", 9 ) )  //variable[varname] - Environment variable named "varname"
	{
		const char* value;
		unsigned int end_pos = string_find( var, ']', 9 );
		if( end_pos != STRING_NPOS )
			var[end_pos] = 0;
		value = environment_variable( var );
		if( end_pos != STRING_NPOS )
			var[end_pos] = ']';
		return value;
	}
	return "";
}
Ejemplo n.º 5
0
void config_load( const char* name, hash_t filter_section, bool built_in, bool overwrite )
{
	/*lint --e{838} Safety null assign all pointers for all preprocessor paths */
	/*lint --e{750} Unused macros in some paths */
#define NUM_SEARCH_PATHS 10
#define ANDROID_ASSET_PATH_INDEX 5
	char* sub_exe_path = 0;
	char* exe_parent_path = 0;
	char* exe_processed_path = 0;
	char* abs_exe_parent_path = 0;
	char* abs_exe_processed_path = 0;
	char* bundle_path = 0;
	char* home_dir = 0;
	char* cmdline_path = 0;
	char* cwd_config_path = 0;
	const char* paths[NUM_SEARCH_PATHS];
#if !FOUNDATION_PLATFORM_FAMILY_MOBILE
	const char* const* cmd_line;
	int icl, clsize;
#endif
	int start_path, i, j;

	const char buildsuffix[4][9] = { "/debug", "/release", "/profile", "/deploy" };
	const char platformsuffix[7][14] = { "/win32", "/win64", "/osx", "/ios", "/android", "/raspberrypi", "/unknown" };
	const char binsuffix[1][5] = { "/bin" };

	FOUNDATION_ASSERT( name );

	sub_exe_path = path_merge( environment_executable_directory(), "config" );
	exe_parent_path = path_merge( environment_executable_directory(), "../config" );
	abs_exe_parent_path = path_make_absolute( exe_parent_path );

	exe_processed_path = string_clone( environment_executable_directory() );
	for( i = 0; i < 4; ++i )
	{
		if( string_ends_with( exe_processed_path, buildsuffix[i] ) )
		{
			exe_processed_path[ string_length( exe_processed_path ) - string_length( buildsuffix[i] ) ] = 0;
			break;
		}
	}
	for( i = 0; i < 7; ++i )
	{
		if( string_ends_with( exe_processed_path, platformsuffix[i] ) )
		{
			exe_processed_path[ string_length( exe_processed_path ) - string_length( platformsuffix[i] ) ] = 0;
			break;
		}
	}
	for( i = 0; i < 1; ++i )
	{
		if( string_ends_with( exe_processed_path, binsuffix[i] ) )
		{
			exe_processed_path[ string_length( exe_processed_path ) - string_length( binsuffix[i] ) ] = 0;
			break;
		}
	}
	exe_processed_path = path_append( exe_processed_path, "config" );
	abs_exe_processed_path = path_make_absolute( exe_processed_path );
	
	paths[0] = environment_executable_directory();
	paths[1] = sub_exe_path;
	paths[2] = abs_exe_parent_path;
	paths[3] = abs_exe_processed_path;

#if FOUNDATION_PLATFORM_FAMILY_DESKTOP && !BUILD_DEPLOY
	paths[4] = environment_initial_working_directory();
#else
	paths[4] = 0;
#endif

#if FOUNDATION_PLATFORM_APPLE
	bundle_path = path_merge( environment_executable_directory(), "../Resources/config" );
	paths[5] = bundle_path;
#elif FOUNDATION_PLATFORM_ANDROID
	paths[5] = "/config";
#else
	paths[5] = 0;
#endif

#if FOUNDATION_PLATFORM_FAMILY_DESKTOP
	paths[6] = environment_current_working_directory();
#else
	paths[6] = 0;
#endif

	paths[7] = 0;
	paths[8] = 0;

	string_deallocate( exe_parent_path );
	string_deallocate( exe_processed_path );

#if FOUNDATION_PLATFORM_FAMILY_DESKTOP
	cwd_config_path = path_merge( environment_current_working_directory(), "config" );
	paths[7] = cwd_config_path;

	cmd_line = environment_command_line();
	/*lint -e{850} We modify loop var to skip extra arg */
	for( icl = 0, clsize = array_size( cmd_line ); icl < clsize; ++icl )
	{
		/*lint -e{613} array_size( cmd_line ) in loop condition does the null pointer guard */
		if( string_equal_substr( cmd_line[icl], "--configdir", 11 ) )
		{
			if( string_equal_substr( cmd_line[icl], "--configdir=", 12 ) )
			{
				paths[8] = cmdline_path = string_substr( cmd_line[icl], 12, STRING_NPOS );
			}
			else if( icl < ( clsize - 1 ) )
			{
				paths[8] = cmdline_path = string_clone( cmd_line[++icl] );
			}
		}
	}
#endif
	
	start_path = 0;
	if( !built_in )
	{
#if FOUNDATION_PLATFORM_WINDOWS
		home_dir = path_merge( environment_home_directory(), environment_application()->config_dir );
#elif FOUNDATION_PLATFORM_LINUX || FOUNDATION_PLATFORM_MACOSX
		home_dir = path_prepend( string_concat( ".", environment_application()->config_dir ), environment_home_directory() );
#endif
		if( home_dir )
			paths[9] = home_dir;
		start_path = 9;
	}
	else
	{
		paths[9] = 0;
	}

	for( i = start_path; i < NUM_SEARCH_PATHS; ++i )
	{
		char* filename;
		stream_t* istream;
		bool path_already_searched = false;

		if( !paths[i] )
			continue;

		for( j = start_path; j < i; ++j )
		{
			if( paths[j] && string_equal( paths[j], paths[i] ) )
			{
				path_already_searched = true;
				break;
			}
		}
		if( path_already_searched )
			continue;
		
		//TODO: Support loading configs from virtual file system (i.e in zip/other packages)
		filename = string_append( path_merge( paths[i], name ), ".ini" );
		istream = 0;
#if FOUNDATION_PLATFORM_ANDROID
		if( i == ANDROID_ASSET_PATH_INDEX )
			istream = asset_stream_open( filename, STREAM_IN );
		else
#endif
		istream = stream_open( filename, STREAM_IN );
		if( istream )
		{
			config_parse( istream, filter_section, overwrite );
			stream_deallocate( istream );
		}
		string_deallocate( filename );

		if( built_in )
		{
			const char* FOUNDATION_PLATFORM_name =
#if FOUNDATION_PLATFORM_WINDOWS
				"windows";
#elif FOUNDATION_PLATFORM_LINUX_RASPBERRYPI
				"raspberrypi";
#elif FOUNDATION_PLATFORM_LINUX
				"linux";
#elif FOUNDATION_PLATFORM_MACOSX
				"osx";
#elif FOUNDATION_PLATFORM_IOS
				"ios";
#elif FOUNDATION_PLATFORM_ANDROID
				"android";
#else
#  error Insert platform name
				"unknown";
#endif
			filename = string_append( path_append( path_merge( paths[i], FOUNDATION_PLATFORM_name ), name ), ".ini" );
#if FOUNDATION_PLATFORM_ANDROID
			if( i == ANDROID_ASSET_PATH_INDEX )
				istream = asset_stream_open( filename, STREAM_IN );
			else
#endif
			istream = stream_open( filename, STREAM_IN );
			if( istream )
			{
				config_parse( istream, filter_section, overwrite );
				stream_deallocate( istream );
			}
			string_deallocate( filename );
		}
	}

	string_deallocate( home_dir );
	string_deallocate( cmdline_path );
	string_deallocate( sub_exe_path );
	string_deallocate( abs_exe_processed_path );
	string_deallocate( abs_exe_parent_path );
	string_deallocate( bundle_path );
	string_deallocate( cwd_config_path );
}
Ejemplo n.º 6
0
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
}