فهرست منبع

added sampling profiler and chrome profiler json converter

Nicolas Cannasse 5 سال پیش
والد
کامیت
38c6a22471
14فایلهای تغییر یافته به همراه543 افزوده شده و 23 حذف شده
  1. 1 0
      CMakeLists.txt
  2. 1 1
      Makefile
  3. 1 0
      hl.vcxproj
  4. 15 14
      hl.vcxproj.filters
  5. 2 0
      other/profiler/.gitignore
  6. 19 0
      other/profiler/.vscode/launch.json
  7. 15 0
      other/profiler/.vscode/tasks.json
  8. 190 0
      other/profiler/ProfileGen.hx
  9. 3 0
      other/profiler/profiler.hxml
  10. 3 0
      src/hlmodule.h
  11. 8 0
      src/main.c
  12. 15 8
      src/module.c
  13. 263 0
      src/profile.c
  14. 7 0
      src/std/sys.c

+ 1 - 0
CMakeLists.txt

@@ -133,6 +133,7 @@ add_executable(hl
     src/main.c
     src/module.c
     src/debugger.c
+	src/profile.c
 )
 
 target_link_libraries(hl libhl)

+ 1 - 1
Makefile

@@ -25,7 +25,7 @@ STD = src/std/array.o src/std/buffer.o src/std/bytes.o src/std/cast.o src/std/da
 	src/std/socket.o src/std/string.o src/std/sys.o src/std/types.o src/std/ucs2.o src/std/thread.o src/std/process.o \
 	src/std/track.o
 
-HL = src/code.o src/jit.o src/main.o src/module.o src/debugger.o
+HL = src/code.o src/jit.o src/main.o src/module.o src/debugger.o src/profile.o
 
 FMT = libs/fmt/fmt.o libs/fmt/sha1.o include/mikktspace/mikktspace.o libs/fmt/mikkt.o libs/fmt/dxt.o
 

+ 1 - 0
hl.vcxproj

@@ -263,6 +263,7 @@
     <ClCompile Include="src\jit.c" />
     <ClCompile Include="src\main.c" />
     <ClCompile Include="src\module.c" />
+    <ClCompile Include="src\profile.c" />
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="src\hl.h" />

+ 15 - 14
hl.vcxproj.filters

@@ -1,15 +1,16 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <ItemGroup>
-    <ClCompile Include="src\main.c" />
-    <ClCompile Include="src\code.c" />
-    <ClCompile Include="src\module.c" />
-    <ClCompile Include="src\jit.c" />
-    <ClCompile Include="src\debugger.c" />
-  </ItemGroup>
-  <ItemGroup>
-    <ClInclude Include="src\hlmodule.h" />
-    <ClInclude Include="src\opcodes.h" />
-    <ClInclude Include="src\hl.h" />
-  </ItemGroup>
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <ClCompile Include="src\main.c" />
+    <ClCompile Include="src\code.c" />
+    <ClCompile Include="src\module.c" />
+    <ClCompile Include="src\jit.c" />
+    <ClCompile Include="src\debugger.c" />
+    <ClCompile Include="src\profile.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="src\hlmodule.h" />
+    <ClInclude Include="src\opcodes.h" />
+    <ClInclude Include="src\hl.h" />
+  </ItemGroup>
 </Project>

+ 2 - 0
other/profiler/.gitignore

@@ -0,0 +1,2 @@
+/Profile.json
+/hlprofile.dump

+ 19 - 0
other/profiler/.vscode/launch.json

@@ -0,0 +1,19 @@
+{
+	// Utilisez IntelliSense pour en savoir plus sur les attributs possibles.
+	// Pointez pour afficher la description des attributs existants.
+	// Pour plus d'informations, visitez : https://go.microsoft.com/fwlink/?linkid=830387
+	"version": "0.2.0",
+	"configurations": [
+		{
+			"name": "HashLink (launch)",
+			"request": "launch",
+			"type": "hl",
+			"hxml": "profiler.hxml",
+			"cwd": "${workspaceFolder}",
+			"preLaunchTask": {
+				"type": "haxe",
+				"args": "active configuration"
+			}
+		}
+	]
+}

+ 15 - 0
other/profiler/.vscode/tasks.json

@@ -0,0 +1,15 @@
+{
+// Consultez https://go.microsoft.com/fwlink/?LinkId=733558
+	// pour voir la documentation sur le format de tasks.json
+	"version": "2.0.0",
+	"tasks": [
+		{
+			"type": "haxe",
+			"args": "active configuration",
+			"group": {
+				"kind": "build",
+				"isDefault": true
+			}
+		}
+	]
+}

+ 190 - 0
other/profiler/ProfileGen.hx

@@ -0,0 +1,190 @@
+
+class StackElement {
+	static var UID = 1;
+	public var id : Int;
+	public var desc : String;
+	public var file : String;
+	public var line : Int;
+	public function new(desc:String) {
+		id = UID++;
+		if( desc.charCodeAt(desc.length-1) == ')'.code ) {
+			var p = desc.lastIndexOf('(');
+			var sep = desc.lastIndexOf(':');
+			if( p > 0 && sep > p ) {
+				file = desc.substr(p+1,sep-p-1);
+				var sline = desc.substr(sep+1,desc.length - sep - 2);
+				line = Std.parseInt(sline);
+				desc = desc.substr(0,p);
+				desc = desc.split("$").join("");
+				if( StringTools.endsWith(desc,".__constructor__") )
+					desc = desc.substr(0,-15)+"new";
+			}
+		}
+		this.desc = desc;
+	}
+}
+
+class StackLink {
+	static var UID = 1;
+	public var id : Int;
+	public var elt : StackElement;
+	public var parent : StackLink;
+	public var children : Map<String,StackLink> = new Map();
+	public function new(elt) {
+		id = UID++;
+		this.elt = elt;
+	}
+	public function getChildren(elt:StackElement) {
+		var c = children.get(elt.desc);
+		if( c == null ) {
+			c = new StackLink(elt);
+			c.parent = this;
+			children.set(elt.desc,c);
+		}
+		return c;
+	}
+}
+
+class ProfileGen {
+
+	static function makeStacks( st : Array<StackLink> ) {
+		var m = new Map();
+		for( s in st ) {
+			var s = s;
+			while( s != null ) {
+				if( m.exists(s.id) ) break;
+				m.set(s.id, s);
+				s = s.parent;
+			}
+		}
+		var unique = Lambda.array(m);
+		unique.sort(function(s1,s2) return s1.id - s2.id);
+		return [for( s in unique ) {
+			callFrame : s.elt.file == null ? cast {
+				functionName : s.elt.desc,
+				scriptId : 0,
+			} : {
+				functionName : s.elt.desc,
+				scriptId : 1,
+				url : "file://"+s.elt.file.split("\\").join("/"),
+				lineNumber : s.elt.line,
+			},
+			id : s.id,
+			parent : s.parent == null ? null : s.parent.id,
+		}];
+	}
+
+	static function main() {
+		var file = Sys.args()[0];
+		if( file == null ) file = "hlprofile.dump";
+		if( sys.FileSystem.isDirectory(file) ) file += "/hlprofile.dump";
+
+		var f = sys.io.File.read(file);
+		if( f.readString(4) != "PROF" ) throw "Invalid profiler file";
+		var version = f.readInt32();
+		var sampleCount = f.readInt32();
+		var cache = new Map();
+		var frames = [];
+		var rootElt = new StackElement("(root)");
+		while( true ) {
+			var time = try f.readDouble() catch( e : haxe.io.Eof ) break;
+			var tid = f.readInt32();
+			var count = f.readInt32();
+			var stack = [];
+			for( i in 0...count ) {
+				var file = f.readInt32();
+				if( file == -1 )
+					continue;
+				var line = f.readInt32();
+				var elt : StackElement;
+				if( file < 0 ) {
+					file &= 0x7FFFFFFF;
+					elt = cache.get(file+"-"+line);
+					if( elt == null ) throw "assert";
+				} else {
+					var len = f.readInt32();
+					var buf = new StringBuf();
+					for( i in 0...len ) buf.addChar(f.readUInt16());
+					var str = buf.toString();
+					elt = new StackElement(str);
+					cache.set(file+"-"+line, elt);
+				}
+				stack[i] = elt;
+			}
+			frames.push({ time : time, thread : tid, stack : stack });
+		}
+		var lastT = frames[0].time - 1 / sampleCount;
+		var defStart = frames[0].stack[frames[0].stack.length - 1];
+		var rootStack = new StackLink(rootElt);
+		var timeDeltas = [];
+		var allStacks = [];
+		for( f in frames ) {
+			var st = rootStack;
+			var start = 0;
+			if( f.stack[f.stack.length-1].desc == defStart.desc ) start++;
+			for( i in start...f.stack.length ) {
+				var s = f.stack[f.stack.length - 1 - i];
+				if( s == null ) continue;
+				st = st.getChildren(s);
+			}
+			allStacks.push(st);
+			timeDeltas.push(Std.int((f.time - lastT)*1000000));
+			lastT = f.time;
+		}
+
+		var t0 = frames[0].time;
+		function timeStamp(t:Float) {
+			return Std.int((t - t0) * 1000000);
+		}
+
+		var tid = frames[0].thread;
+		var json : Dynamic = [
+			{
+				pid : 0,
+				tid : tid,
+				ts : 0,
+				ph : "P",
+				cat : "disabled-by-default-v8.cpu_profiler",
+			    name : "Profile",
+				id : "0x1",
+				args: { data : { startTime : 0 } },
+			},
+			{
+				pid : 0,
+				tid : tid,
+				ts : 0,
+				ph : "B",
+				cat : "devtools.timeline",
+				name : "FunctionCall",
+			},
+			{
+				pid : 0,
+				tid : tid,
+				ts : timeStamp(frames[frames.length-1].time),
+				ph : "E",
+				cat : "devtools.timeline",
+				name : "FunctionCall"
+			},
+			{
+				pid : 0,
+				tid : tid,
+				ts : 0,
+				ph : "P",
+				cat : "disabled-by-default-v8.cpu_profiler",
+				name : "ProfileChunk",
+				id : "0x1",
+				args : {
+					data : {
+						cpuProfile : {
+							nodes : makeStacks(allStacks),
+							samples : [for( s in allStacks ) s.id],
+						},
+						timeDeltas : timeDeltas,
+					}
+				}
+			}
+		];
+		sys.io.File.saveContent("Profile.json", haxe.Json.stringify(json,"\t"));
+	}
+
+}

+ 3 - 0
other/profiler/profiler.hxml

@@ -0,0 +1,3 @@
+-hl profiler.hl
+-main ProfileGen
+-dce no

+ 3 - 0
src/hlmodule.h

@@ -147,6 +147,9 @@ h_bool hl_module_patch( hl_module *m, hl_code *code );
 void hl_module_free( hl_module *m );
 h_bool hl_module_debug( hl_module *m, int port, h_bool wait );
 
+void hl_profile_start( int sample_count );
+void hl_profile_end();
+
 jit_ctx *hl_jit_alloc();
 void hl_jit_free( jit_ctx *ctx, h_bool can_reset );
 void hl_jit_reset( jit_ctx *ctx, hl_module *m );

+ 8 - 0
src/main.c

@@ -143,6 +143,7 @@ int main(int argc, pchar *argv[]) {
 	int debug_port = -1;
 	bool debug_wait = false;
 	bool hot_reload = false;
+	int profile_count = -1;
 	main_context ctx;
 	bool isExc = false;
 	int first_boot_arg = -1;
@@ -169,6 +170,11 @@ int main(int argc, pchar *argv[]) {
 			hot_reload = true;
 			continue;
 		}
+		if( pcompare(arg,PSTR("--profile")) == 0 ) {
+			if( argc-- == 0 ) break;
+			profile_count = ptoi(*argv++);
+			continue;
+		}
 		if( *arg == '-' || *arg == '+' ) {
 			if( first_boot_arg < 0 ) first_boot_arg = argc + 1;
 			// skip value
@@ -222,7 +228,9 @@ int main(int argc, pchar *argv[]) {
 	ctx.c.fun = ctx.m->functions_ptrs[ctx.m->code->entrypoint];
 	ctx.c.hasValue = 0;
 	setup_handler();
+	if( profile_count > 0 ) hl_profile_start(profile_count);
 	ctx.ret = hl_dyn_call_safe(&ctx.c,NULL,0,&isExc);
+	if( profile_count > 0 ) hl_profile_end();
 	if( isExc ) {
 		varray *a = hl_exception_stack();
 		int i;

+ 15 - 8
src/module.c

@@ -75,7 +75,7 @@ static bool module_resolve_pos( hl_module *m, void *addr, int *fidx, int *fpos )
 	return true;
 }
 
-static uchar *module_resolve_symbol( void *addr, uchar *out, int *outSize ) {
+uchar *hl_module_resolve_symbol_full( void *addr, uchar *out, int *outSize, int **r_debug_addr ) {
 	int *debug_addr;
 	int file, line;
 	int size = *outSize;
@@ -95,6 +95,7 @@ static uchar *module_resolve_symbol( void *addr, uchar *out, int *outSize ) {
 	// extract debug info
 	fdebug = m->code->functions + fidx;
 	debug_addr = fdebug->debug + ((fpos&0xFFFF) * 2);
+	if( r_debug_addr ) *r_debug_addr = debug_addr;
 	file = debug_addr[0];
 	line = debug_addr[1];
 	if( fdebug->obj )
@@ -103,19 +104,21 @@ static uchar *module_resolve_symbol( void *addr, uchar *out, int *outSize ) {
 		pos += usprintf(out,size - pos,USTR("%s.~%s.%d("),fdebug->field.ref->obj->name, fdebug->field.ref->field.name, fdebug->ref);
 	else
 		pos += usprintf(out,size - pos,USTR("fun$%d("),fdebug->findex);
-	pos += hl_from_utf8(out + pos,size - pos,m->code->debugfiles[file]);
+	pos += hl_from_utf8(out + pos,size - pos,m->code->debugfiles[file&0x7FFFFFFF]);
 	pos += usprintf(out + pos, size - pos, USTR(":%d)"), line);
 	*outSize = pos;
 	return out;
 }
 
-static int module_capture_stack( void **stack, int size ) {
-	void **stack_ptr = (void**)&stack;
+static uchar *module_resolve_symbol( void *addr, uchar *out, int *outSize ) {
+	return hl_module_resolve_symbol_full(addr,out,outSize,NULL);
+}
+
+int hl_module_capture_stack_range( void *stack_top, void **stack_ptr, void **out, int size ) {
 #if defined(HL_64) && defined(HL_WIN)
 #else
 	void *stack_bottom = stack_ptr;
 #endif
-	void *stack_top = hl_get_thread()->stack_top;
 	int count = 0;
 	if( modules_count == 1 ) {
 		hl_module *m = cur_modules[0];
@@ -131,7 +134,7 @@ static int module_capture_stack( void **stack, int size ) {
 			void *module_addr = *stack_ptr++; // EIP
 			if( module_addr >= (void*)code && module_addr < (void*)(code + code_size) ) {
 				if( count == size ) break;
-				stack[count++] = module_addr;
+				out[count++] = module_addr;
 			}
 #else
 			void *stack_addr = *stack_ptr++; // EBP
@@ -139,7 +142,7 @@ static int module_capture_stack( void **stack, int size ) {
 				void *module_addr = *stack_ptr; // EIP
 				if( module_addr >= (void*)code && module_addr < (void*)(code + code_size) ) {
 					if( count == size ) break;
-					stack[count++] = module_addr;
+					out[count++] = module_addr;
 				}
 			}
 #endif
@@ -170,7 +173,7 @@ static int module_capture_stack( void **stack, int size ) {
 							code_size -= s;
 							if( module_addr < (void*)code || module_addr >= (void*)(code + code_size) ) continue;
 						}
-						stack[count++] = module_addr;
+						out[count++] = module_addr;
 						break;
 					}
 				}
@@ -180,6 +183,10 @@ static int module_capture_stack( void **stack, int size ) {
 	return count;
 }
 
+static int module_capture_stack( void **stack, int size ) {
+	return hl_module_capture_stack_range(hl_get_thread()->stack_top, (void**)&stack, stack, size);
+}
+
 static void hl_module_types_dump( void (*fdump)( void *, int) ) {
 	int ntypes = 0;
 	int i, j, fcount = 0;

+ 263 - 0
src/profile.c

@@ -0,0 +1,263 @@
+/*
+ * Copyright (C)2015-2019 Haxe Foundation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include <hl.h>
+#include <hlmodule.h>
+
+#define MAX_STACK_SIZE (8 << 20)
+#define MAX_STACK_COUNT 2048
+
+HL_API double hl_sys_time( void );
+HL_API void *hl_gc_threads_info( void );
+HL_API void hl_sys_before_exit( void * );
+int hl_module_capture_stack_range( void *stack_top, void **stack_ptr, void **out, int size );
+uchar *hl_module_resolve_symbol_full( void *addr, uchar *out, int *outSize, int **r_debug_addr );
+
+typedef struct {
+	int count;
+	bool stopping_world;
+	hl_thread_info **threads;
+} hl_gc_threads;
+
+typedef struct _thread_handle thread_handle;
+typedef struct _profile_data profile_data;
+
+struct _thread_handle {
+	int tid;
+#	ifdef HL_WIN_DESKTOP
+	HANDLE h;
+#	endif
+	hl_thread_info *inf;
+	thread_handle *next;
+};
+
+struct _profile_data {
+	int currentPos;
+	int dataSize;
+	unsigned char *data;
+	profile_data *next;
+};
+
+typedef struct {
+	profile_data *r;
+	int pos;
+} profile_reader;
+
+static struct {
+	int sample_count;
+	thread_handle *handles;
+	void **tmpMemory;
+	void *stackOut[MAX_STACK_COUNT];
+	profile_data *record;
+	profile_data *first_record;
+} data = {0};
+
+static void *get_thread_stackptr( thread_handle *t ) {
+#ifdef HL_WIN_DESKTOP
+	CONTEXT c;
+	c.ContextFlags = CONTEXT_CONTROL;
+	if( !GetThreadContext(t->h,&c) ) return NULL;
+#	ifdef HL_64
+	return (void*)c.Rsp;
+#	else
+	return (void*)c.Esp;
+#	endif
+#else
+	return NULL;
+#endif
+}
+
+static void thread_data_init( thread_handle *t ) {
+#ifdef HL_WIN
+	t->h = OpenThread(THREAD_ALL_ACCESS,FALSE, t->tid);
+#endif
+}
+
+static void thread_data_free( thread_handle *t ) {
+#ifdef HL_WIN
+	CloseHandle(t->h);
+#endif
+}
+
+static bool pause_thread( thread_handle *t, bool b ) {
+#ifdef HL_WIN
+	if( b )
+		return (int)SuspendThread(t->h) >= 0;
+	else {
+		ResumeThread(t->h);
+		return true;
+	}
+#else
+	return false;
+#endif
+}
+
+static void record_data( void *ptr, int size ) {
+	profile_data *r = data.record;
+	if( !r || r->currentPos + size > r->dataSize ) {
+		r = malloc(sizeof(profile_data));
+		r->currentPos = 0;
+		r->dataSize = 1 << 20;
+		r->data = malloc(r->dataSize);
+		r->next = NULL;
+		if( data.record )
+			data.record->next = r;
+		else
+			data.first_record = r;
+		data.record = r;
+		fflush(stdout);
+	}
+	memcpy(r->data + r->currentPos, ptr, size);
+	r->currentPos += size;
+}
+
+static void read_thread_data( thread_handle *t ) {
+	if( !pause_thread(t,true) )
+		return;
+
+	void *stack = get_thread_stackptr(t);
+	if( !stack ) {
+		pause_thread(t,false);
+		return;
+	}
+
+	int size = (int)((unsigned char*)t->inf->stack_top - (unsigned char*)stack);
+	if( size > MAX_STACK_SIZE ) size = MAX_STACK_SIZE;
+	memcpy(data.tmpMemory,stack,size);
+	pause_thread(t, false);
+
+	int count = hl_module_capture_stack_range((char*)data.tmpMemory+size, (void**)data.tmpMemory, data.stackOut, MAX_STACK_COUNT);
+	double time = hl_sys_time();
+	record_data(&time,sizeof(double));
+	record_data(&t->tid,sizeof(int));
+	record_data(&count,sizeof(int));
+	record_data(data.stackOut,sizeof(void*)*count);
+}
+
+static void hl_profile_loop( void *_ ) {
+	double wait_time = 1. / data.sample_count;
+	double next = hl_sys_time();
+	int skip = 0;
+	data.tmpMemory = malloc(MAX_STACK_SIZE);
+	while( true ) {
+		if( hl_sys_time() < next ) {
+			skip++;
+			continue;
+		}
+		hl_gc_threads *threads = (hl_gc_threads*)hl_gc_threads_info();
+		int i;
+		thread_handle *prev = NULL;
+		thread_handle *cur = data.handles;
+		for(i=0;i<threads->count;i++) {
+			hl_thread_info *t = threads->threads[i];
+			if( !cur || cur->tid != t->thread_id ) {
+				thread_handle *h = malloc(sizeof(thread_handle));
+				h->tid = t->thread_id;
+				h->inf = t;
+				thread_data_init(h);
+				h->next = cur;
+				cur = h;
+				if( prev == NULL ) data.handles = h; else prev->next = h;
+			}
+			read_thread_data(cur);
+			prev = cur;
+			cur = cur->next;
+		}
+		if( prev ) prev->next = NULL; else data.handles = NULL;
+		while( cur != NULL ) {
+			thread_data_free(cur);
+			free(cur);
+			cur = cur->next;
+		}
+		next += wait_time;
+	}
+}
+
+void hl_profile_start( int sample_count ) {
+#	if defined(HL_THREADS) && defined(HL_WIN_DESKTOP)
+	data.sample_count = sample_count;
+	hl_thread_start(hl_profile_loop,NULL,false);
+	hl_sys_before_exit(hl_profile_end);
+#	endif
+}
+
+static bool read_profile_data( profile_reader *r, void *ptr, int size ) {
+	while( size ) {
+		if( r->r == NULL ) return false;
+		int bytes = r->r->currentPos - r->pos;
+		if( bytes > size ) bytes = size;
+		memcpy(ptr, r->r->data + r->pos, bytes);
+		size -= bytes;
+		r->pos += bytes;
+		if( r->pos == r->r->currentPos ) {
+			r->r = r->r->next;
+			r->pos = 0;
+		}
+	}
+	return true;
+}
+
+void hl_profile_end() {
+	if( !data.first_record ) return;
+	FILE *f = fopen("hlprofile.dump","wb");
+	int version = HL_VERSION;
+	fwrite("PROF",1,4,f);
+	fwrite(&version,1,4,f);
+	fwrite(&data.sample_count,1,4,f);
+	profile_reader r;
+	r.r = data.first_record;
+	r.pos = 0;
+	int samples = 0;
+	int skipCount = 0, total = 0;
+	while( true ) {
+		double time;
+		int i, tid, count;
+		if( !read_profile_data(&r,&time, sizeof(double)) ) break;
+		read_profile_data(&r,&tid,sizeof(int));
+		read_profile_data(&r,&count,sizeof(int));
+		read_profile_data(&r,data.stackOut,sizeof(void*)*count);
+		fwrite(&time,1,8,f);
+		fwrite(&tid,1,4,f);
+		fwrite(&count,1,4,f);
+		total += count;
+		for(i=0;i<count;i++) {
+			uchar outStr[256];
+			int outSize = 256;
+			int *debug_addr = NULL;
+			if( hl_module_resolve_symbol_full(data.stackOut[i],outStr,&outSize,&debug_addr) == NULL ) {
+				int bad = -1;
+				fwrite(&bad,1,4,f);
+			} else {
+				fwrite(debug_addr,1,8,f);
+				if( (debug_addr[0] & 0x80000000) == 0 ) {
+					debug_addr[0] |= 0x80000000;
+					fwrite(&outSize,1,4,f);
+					fwrite(outStr,1,outSize*sizeof(uchar),f);
+				} else
+					skipCount++;
+			}
+		}
+		samples++;
+	}
+	fclose(f);
+	printf("%d profile samples saved\n", samples);
+}
+

+ 7 - 0
src/std/sys.c

@@ -157,7 +157,14 @@ HL_PRIM void hl_sys_print( vbyte *msg ) {
 	hl_blocking(false);
 }
 
+
+static void *f_before_exit = NULL;
+HL_PRIM void hl_sys_before_exit( void *fptr ) {
+	f_before_exit = fptr;
+}
+
 HL_PRIM void hl_sys_exit( int code ) {
+	if( f_before_exit ) ((void(*)())f_before_exit)();
 	exit(code);
 }