memprof 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/memprof +7 -5
- data/ext/arch.h +2 -0
- data/ext/elf.c +5 -4
- data/ext/extconf.rb +9 -1
- data/ext/mach.c +0 -2
- data/ext/memprof.c +49 -144
- data/ext/tracer.c +99 -0
- data/ext/tracer.h +33 -0
- data/ext/tracers/malloc.c +163 -0
- data/ext/util.h +6 -1
- data/ext/x86_gen.c +73 -0
- data/ext/x86_gen.h +21 -71
- data/memprof.gemspec +2 -2
- metadata +7 -3
data/bin/memprof
CHANGED
@@ -26,7 +26,8 @@ class MemprofUploader
|
|
26
26
|
opts.on("-n", "--name <name>", "Name for your dump (required)") {|arg| @name = arg }
|
27
27
|
opts.on("-k", "--key <key>", "Memprof.com API key (required)") {|arg| @key = arg }
|
28
28
|
opts.on("-d", "--[no-]delete", "Delete dump file after uploading (default true)") {|arg| @delete_dump = arg }
|
29
|
-
opts.on("-s", "--seconds <seconds>",
|
29
|
+
opts.on("-s", "--seconds <seconds>",
|
30
|
+
Integer, "Seconds to wait for the dump (default 300)") {|arg| @secs = arg }
|
30
31
|
opts.on("-t", "--[no-]test", "Test run (don't actually upload) (default false)") {|arg| @test = arg }
|
31
32
|
opts.on("-f", "--file <path>", "Upload specific json dump (optional)") {|arg| @file = arg }
|
32
33
|
opts.on("--put-my-data-on-the-internet", "Confirm that you understand\n" +
|
@@ -48,7 +49,7 @@ class MemprofUploader
|
|
48
49
|
@delete_dump = true if @delete_dump.nil?
|
49
50
|
|
50
51
|
# Make this default to 60 if the user didn't pass the number of seconds.
|
51
|
-
@secs
|
52
|
+
@secs ||= 300
|
52
53
|
|
53
54
|
if @file
|
54
55
|
fail_with("File not found: #{@file}") unless File.exists?(@file)
|
@@ -96,13 +97,14 @@ class MemprofUploader
|
|
96
97
|
# We'll compare against this later to see which is the new file.
|
97
98
|
old_files = Dir.glob("/tmp/memprof-#{pid}-*")
|
98
99
|
signal_process(pid)
|
99
|
-
|
100
|
+
timeout = 30
|
101
|
+
puts "Waiting #{timeout} seconds for process #{pid} to create a new dump..."
|
100
102
|
|
101
103
|
file = nil
|
102
104
|
|
103
|
-
|
105
|
+
timeout.times do |i|
|
104
106
|
sleep 1 unless file = (Dir.glob("/tmp/memprof-#{pid}-*") - old_files).first and break
|
105
|
-
fail_with("Timed out after waiting
|
107
|
+
fail_with("Timed out after waiting #{timeout} seconds. Make sure you added require '#{File.expand_path('../../lib/memprof/signal', __FILE__)}' to your application.") if i+1 == timeout
|
106
108
|
end
|
107
109
|
|
108
110
|
puts "\nFound file #{file}"
|
data/ext/arch.h
CHANGED
data/ext/elf.c
CHANGED
@@ -15,8 +15,8 @@
|
|
15
15
|
#include <libgen.h>
|
16
16
|
#include <limits.h>
|
17
17
|
#include <link.h>
|
18
|
-
#include <stdio.h>
|
19
18
|
#include <stdlib.h>
|
19
|
+
#include <stdio.h>
|
20
20
|
#include <string.h>
|
21
21
|
#include <sysexits.h>
|
22
22
|
#include <unistd.h>
|
@@ -135,6 +135,7 @@ get_got_addr(struct plt_entry *plt)
|
|
135
135
|
{
|
136
136
|
assert(plt != NULL);
|
137
137
|
/* the jump is relative to the start of the next instruction. */
|
138
|
+
dbg_printf("PLT addr: %p, .got.plt slot: %p\n", plt, (void *)&(plt->pad) + plt->jmp_disp);
|
138
139
|
return (void *)&(plt->pad) + plt->jmp_disp;
|
139
140
|
}
|
140
141
|
|
@@ -151,8 +152,9 @@ overwrite_got(void *plt, const void *tramp)
|
|
151
152
|
assert(tramp != NULL);
|
152
153
|
void *ret = NULL;
|
153
154
|
|
154
|
-
memcpy(&ret, get_got_addr(plt), sizeof(
|
155
|
-
|
155
|
+
memcpy(&ret, get_got_addr(plt), sizeof(void *));
|
156
|
+
copy_instructions(get_got_addr(plt), &tramp, sizeof(void *));
|
157
|
+
dbg_printf("GOT value overwritten to: %p\n", tramp);
|
156
158
|
return ret;
|
157
159
|
}
|
158
160
|
|
@@ -189,7 +191,6 @@ for_each_dso_cb(struct link_map *map, void *data)
|
|
189
191
|
dbg_printf("found an empty string (skipping)\n");
|
190
192
|
return CB_CONTINUE;
|
191
193
|
}
|
192
|
-
|
193
194
|
memset(&curr_lib, 0, sizeof(curr_lib));
|
194
195
|
dbg_printf("trying to open elf object: %s\n", map->l_name);
|
195
196
|
curr_lib.filename = map->l_name;
|
data/ext/extconf.rb
CHANGED
@@ -160,7 +160,8 @@ if have_header('mach-o/dyld.h')
|
|
160
160
|
# 'rb_newobj',
|
161
161
|
# 'freelist',
|
162
162
|
# 'heaps',
|
163
|
-
# 'heaps_used'
|
163
|
+
# 'heaps_used',
|
164
|
+
# 'finalizer_table'
|
164
165
|
]
|
165
166
|
|
166
167
|
expressions = []
|
@@ -218,7 +219,14 @@ if ENV['MEMPROF_DEBUG'] == '1'
|
|
218
219
|
end
|
219
220
|
|
220
221
|
if is_elf or is_macho
|
222
|
+
$objs = Dir['{.,*}/*.c'].map{ |file| file.gsub(/\.c(pp)?$/, '.o') }
|
221
223
|
create_makefile('memprof')
|
224
|
+
|
225
|
+
makefile = File.read('Makefile')
|
226
|
+
makefile.gsub!('-c $<', '-o $(patsubst %.c,%.o,$<) -c $<')
|
227
|
+
makefile.gsub!(/(CLEANOBJS\s*=)/, '\1 */*.o')
|
228
|
+
|
229
|
+
File.open('Makefile', 'w+'){ |f| f.puts(makefile) }
|
222
230
|
else
|
223
231
|
raise 'unsupported platform'
|
224
232
|
end
|
data/ext/mach.c
CHANGED
data/ext/memprof.c
CHANGED
@@ -20,6 +20,7 @@
|
|
20
20
|
|
21
21
|
#include "arch.h"
|
22
22
|
#include "bin_api.h"
|
23
|
+
#include "tracer.h"
|
23
24
|
#include "tramp.h"
|
24
25
|
#include "util.h"
|
25
26
|
|
@@ -334,152 +335,17 @@ memprof_track(int argc, VALUE *argv, VALUE self)
|
|
334
335
|
return Qnil;
|
335
336
|
}
|
336
337
|
|
337
|
-
struct memprof_malloc_stats {
|
338
|
-
size_t malloc_bytes_requested;
|
339
|
-
size_t calloc_bytes_requested;
|
340
|
-
size_t realloc_bytes_requested;
|
341
|
-
|
342
|
-
size_t malloc_bytes_actual;
|
343
|
-
size_t calloc_bytes_actual;
|
344
|
-
size_t realloc_bytes_actual;
|
345
|
-
size_t free_bytes_actual;
|
346
|
-
|
347
|
-
size_t malloc_calls;
|
348
|
-
size_t calloc_calls;
|
349
|
-
size_t realloc_calls;
|
350
|
-
size_t free_calls;
|
351
|
-
};
|
352
|
-
|
353
|
-
static struct memprof_malloc_stats memprof_malloc_stats;
|
354
|
-
static void *orig_malloc, *orig_realloc, *orig_calloc, *orig_free;
|
355
|
-
static size_t (*malloc_usable_size)(void *ptr);
|
356
|
-
|
357
|
-
static void *
|
358
|
-
malloc_tramp(size_t size)
|
359
|
-
{
|
360
|
-
void *ret = NULL;
|
361
|
-
memprof_malloc_stats.malloc_bytes_requested += size;
|
362
|
-
memprof_malloc_stats.malloc_calls++;
|
363
|
-
ret = malloc(size);
|
364
|
-
memprof_malloc_stats.malloc_bytes_actual += malloc_usable_size(ret);
|
365
|
-
return ret;
|
366
|
-
}
|
367
|
-
|
368
|
-
static void *
|
369
|
-
calloc_tramp(size_t nmemb, size_t size)
|
370
|
-
{
|
371
|
-
void *ret = NULL;
|
372
|
-
memprof_malloc_stats.calloc_bytes_requested += (nmemb * size);
|
373
|
-
memprof_malloc_stats.calloc_calls++;
|
374
|
-
ret = calloc(nmemb, size);
|
375
|
-
memprof_malloc_stats.calloc_bytes_actual += malloc_usable_size(ret);
|
376
|
-
return ret;
|
377
|
-
}
|
378
|
-
|
379
|
-
static void *
|
380
|
-
realloc_tramp(void *ptr, size_t size)
|
381
|
-
{
|
382
|
-
/* TODO need to check malloc_usable_size of before/after i guess? */
|
383
|
-
void *ret = NULL;
|
384
|
-
memprof_malloc_stats.realloc_bytes_requested += size;
|
385
|
-
memprof_malloc_stats.realloc_calls++;
|
386
|
-
ret = realloc(ptr, size);
|
387
|
-
memprof_malloc_stats.realloc_bytes_actual += malloc_usable_size(ptr);
|
388
|
-
return ret;
|
389
|
-
}
|
390
|
-
|
391
|
-
static void
|
392
|
-
free_tramp(void *ptr)
|
393
|
-
{
|
394
|
-
/* TODO use malloc_usable_size to track bytes freed? */
|
395
|
-
memprof_malloc_stats.free_bytes_actual += malloc_usable_size(ptr);
|
396
|
-
memprof_malloc_stats.free_calls++;
|
397
|
-
free(ptr);
|
398
|
-
}
|
399
|
-
|
400
|
-
static void
|
401
|
-
memprof_start_track_bytes()
|
402
|
-
{
|
403
|
-
struct tramp_st2_entry tmp;
|
404
|
-
|
405
|
-
if (!malloc_usable_size) {
|
406
|
-
if ((malloc_usable_size =
|
407
|
-
bin_find_symbol("MallocExtension_GetAllocatedSize", NULL, 1)) == NULL) {
|
408
|
-
malloc_usable_size = bin_find_symbol("malloc_usable_size", NULL, 1);
|
409
|
-
dbg_printf("tcmalloc was not found...\n");
|
410
|
-
}
|
411
|
-
assert(malloc_usable_size != NULL);
|
412
|
-
dbg_printf("malloc_usable_size: %p\n", malloc_usable_size);
|
413
|
-
}
|
414
|
-
|
415
|
-
tmp.addr = malloc_tramp;
|
416
|
-
bin_update_image("malloc", &tmp, &orig_malloc);
|
417
|
-
assert(orig_malloc != NULL);
|
418
|
-
dbg_printf("orig_malloc: %p\n", orig_malloc);
|
419
|
-
|
420
|
-
tmp.addr = realloc_tramp;
|
421
|
-
bin_update_image("realloc", &tmp, &orig_realloc);
|
422
|
-
dbg_printf("orig_realloc: %p\n", orig_realloc);
|
423
|
-
|
424
|
-
tmp.addr = calloc_tramp;
|
425
|
-
bin_update_image("calloc", &tmp, &orig_calloc);
|
426
|
-
dbg_printf("orig_calloc: %p\n", orig_calloc);
|
427
|
-
|
428
|
-
tmp.addr = free_tramp;
|
429
|
-
bin_update_image("free", &tmp, &orig_free);
|
430
|
-
assert(orig_free != NULL);
|
431
|
-
dbg_printf("orig_free: %p\n", orig_free);
|
432
|
-
}
|
433
|
-
|
434
|
-
static void
|
435
|
-
memprof_stop_track_bytes()
|
436
|
-
{
|
437
|
-
struct tramp_st2_entry tmp;
|
438
|
-
|
439
|
-
tmp.addr = orig_malloc;
|
440
|
-
bin_update_image("malloc", &tmp, NULL);
|
441
|
-
|
442
|
-
tmp.addr = orig_realloc;
|
443
|
-
bin_update_image("realloc", &tmp, NULL);
|
444
|
-
|
445
|
-
tmp.addr = orig_calloc;
|
446
|
-
bin_update_image("calloc", &tmp, NULL);
|
447
|
-
|
448
|
-
tmp.addr = orig_free;
|
449
|
-
bin_update_image("free", &tmp, NULL);
|
450
|
-
}
|
451
|
-
|
452
|
-
static void
|
453
|
-
memprof_reset_track_bytes()
|
454
|
-
{
|
455
|
-
memset(&memprof_malloc_stats, 0, sizeof(memprof_malloc_stats));
|
456
|
-
}
|
457
|
-
|
458
338
|
static VALUE
|
459
|
-
|
339
|
+
memprof_track_fun(int argc, VALUE *argv, VALUE self)
|
460
340
|
{
|
461
341
|
if (!rb_block_given_p())
|
462
342
|
rb_raise(rb_eArgError, "block required");
|
463
343
|
|
464
|
-
|
344
|
+
trace_invoke_all(TRACE_START);
|
465
345
|
rb_yield(Qnil);
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
memprof_malloc_stats.calloc_bytes_requested);
|
470
|
-
fprintf(stderr, "================ Actual ====================\n");
|
471
|
-
fprintf(stderr, "Malloced: %zd, Realloced: %zd, Calloced: %zd, Freed: %zd\n",
|
472
|
-
memprof_malloc_stats.malloc_bytes_actual, memprof_malloc_stats.realloc_bytes_actual,
|
473
|
-
memprof_malloc_stats.calloc_bytes_actual, memprof_malloc_stats.free_bytes_actual);
|
474
|
-
fprintf(stderr, "================ Call count ====================\n");
|
475
|
-
fprintf(stderr, "Calls to malloc: %zd, realloc: %zd, calloc: %zd, free: %zd\n",
|
476
|
-
memprof_malloc_stats.malloc_calls,
|
477
|
-
memprof_malloc_stats.realloc_calls,
|
478
|
-
memprof_malloc_stats.calloc_calls,
|
479
|
-
memprof_malloc_stats.free_calls);
|
480
|
-
|
481
|
-
memprof_reset_track_bytes();
|
482
|
-
memprof_stop_track_bytes();
|
346
|
+
trace_invoke_all(TRACE_DUMP);
|
347
|
+
trace_invoke_all(TRACE_STOP);
|
348
|
+
trace_invoke_all(TRACE_RESET);
|
483
349
|
return Qnil;
|
484
350
|
}
|
485
351
|
|
@@ -1410,6 +1276,17 @@ globals_each_dump(st_data_t key, st_data_t record, st_data_t arg)
|
|
1410
1276
|
return ST_CONTINUE;
|
1411
1277
|
}
|
1412
1278
|
|
1279
|
+
static int
|
1280
|
+
finalizers_each_dump(st_data_t key, st_data_t val, st_data_t arg)
|
1281
|
+
{
|
1282
|
+
yajl_gen gen = (yajl_gen)arg;
|
1283
|
+
yajl_gen_array_open(gen);
|
1284
|
+
yajl_gen_value(gen, (VALUE)key);
|
1285
|
+
yajl_gen_value(gen, (VALUE)val);
|
1286
|
+
yajl_gen_array_close(gen);
|
1287
|
+
return ST_CONTINUE;
|
1288
|
+
}
|
1289
|
+
|
1413
1290
|
static void
|
1414
1291
|
memprof_dump_globals(yajl_gen gen)
|
1415
1292
|
{
|
@@ -1562,6 +1439,29 @@ memprof_dump_ps(yajl_gen gen)
|
|
1562
1439
|
}
|
1563
1440
|
}
|
1564
1441
|
|
1442
|
+
static void
|
1443
|
+
memprof_dump_finalizers(yajl_gen gen)
|
1444
|
+
{
|
1445
|
+
st_table *finalizer_table = *(st_table **)memprof_config.finalizer_table;
|
1446
|
+
if (finalizer_table) {
|
1447
|
+
yajl_gen_map_open(gen);
|
1448
|
+
|
1449
|
+
yajl_gen_cstr(gen, "_id");
|
1450
|
+
yajl_gen_cstr(gen, "finalizers");
|
1451
|
+
|
1452
|
+
yajl_gen_cstr(gen, "type");
|
1453
|
+
yajl_gen_cstr(gen, "finalizers");
|
1454
|
+
|
1455
|
+
yajl_gen_cstr(gen, "data");
|
1456
|
+
yajl_gen_array_open(gen);
|
1457
|
+
st_foreach(finalizer_table, finalizers_each_dump, (st_data_t)gen);
|
1458
|
+
yajl_gen_array_close(gen);
|
1459
|
+
|
1460
|
+
yajl_gen_map_close(gen);
|
1461
|
+
yajl_gen_reset(gen);
|
1462
|
+
}
|
1463
|
+
}
|
1464
|
+
|
1565
1465
|
static void
|
1566
1466
|
json_print(void *ctx, const char * str, unsigned int len)
|
1567
1467
|
{
|
@@ -1648,6 +1548,7 @@ memprof_dump_all(int argc, VALUE *argv, VALUE self)
|
|
1648
1548
|
|
1649
1549
|
track_objs = 0;
|
1650
1550
|
|
1551
|
+
memprof_dump_finalizers(gen);
|
1651
1552
|
memprof_dump_globals(gen);
|
1652
1553
|
memprof_dump_stack(gen);
|
1653
1554
|
|
@@ -1727,6 +1628,8 @@ init_memprof_config_extended() {
|
|
1727
1628
|
/* Stuff for dumping the heap */
|
1728
1629
|
memprof_config.heaps = bin_find_symbol("heaps", NULL, 0);
|
1729
1630
|
memprof_config.heaps_used = bin_find_symbol("heaps_used", NULL, 0);
|
1631
|
+
memprof_config.finalizer_table = bin_find_symbol("finalizer_table", NULL, 0);
|
1632
|
+
|
1730
1633
|
#ifdef sizeof__RVALUE
|
1731
1634
|
memprof_config.sizeof_RVALUE = sizeof__RVALUE;
|
1732
1635
|
#else
|
@@ -1869,21 +1772,21 @@ init_memprof_config_extended() {
|
|
1869
1772
|
}
|
1870
1773
|
if (memprof_config.gc_sweep_size == 0)
|
1871
1774
|
errors_printed += fprintf(stderr,
|
1872
|
-
"Failed to determine the size of gc_sweep/garbage_collect_0/garbage_collect: %
|
1775
|
+
"Failed to determine the size of gc_sweep/garbage_collect_0/garbage_collect: %zd\n",
|
1873
1776
|
memprof_config.gc_sweep_size);
|
1874
1777
|
if (memprof_config.finalize_list == NULL)
|
1875
1778
|
errors_printed += fprintf(stderr,
|
1876
1779
|
"Failed to locate finalize_list\n");
|
1877
1780
|
if (memprof_config.finalize_list_size == 0)
|
1878
1781
|
errors_printed += fprintf(stderr,
|
1879
|
-
"Failed to determine the size of finalize_list: %
|
1782
|
+
"Failed to determine the size of finalize_list: %zd\n",
|
1880
1783
|
memprof_config.finalize_list_size);
|
1881
1784
|
if (memprof_config.rb_gc_force_recycle == NULL)
|
1882
1785
|
errors_printed += fprintf(stderr,
|
1883
1786
|
"Failed to locate rb_gc_force_recycle\n");
|
1884
1787
|
if (memprof_config.rb_gc_force_recycle_size == 0)
|
1885
1788
|
errors_printed += fprintf(stderr,
|
1886
|
-
"Failed to determine the size of rb_gc_force_recycle: %
|
1789
|
+
"Failed to determine the size of rb_gc_force_recycle: %zd\n",
|
1887
1790
|
memprof_config.rb_gc_force_recycle_size);
|
1888
1791
|
if (memprof_config.freelist == NULL)
|
1889
1792
|
errors_printed += fprintf(stderr,
|
@@ -1915,7 +1818,7 @@ Init_memprof()
|
|
1915
1818
|
rb_define_singleton_method(memprof, "track", memprof_track, -1);
|
1916
1819
|
rb_define_singleton_method(memprof, "dump", memprof_dump, -1);
|
1917
1820
|
rb_define_singleton_method(memprof, "dump_all", memprof_dump_all, -1);
|
1918
|
-
rb_define_singleton_method(memprof, "
|
1821
|
+
rb_define_singleton_method(memprof, "track_fun", memprof_track_fun, -1);
|
1919
1822
|
|
1920
1823
|
objs = st_init_numtable();
|
1921
1824
|
init_memprof_config_base();
|
@@ -1923,6 +1826,8 @@ Init_memprof()
|
|
1923
1826
|
init_memprof_config_extended();
|
1924
1827
|
create_tramp_table();
|
1925
1828
|
|
1829
|
+
install_malloc_tracer();
|
1830
|
+
|
1926
1831
|
gc_hook = Data_Wrap_Struct(rb_cObject, sourcefile_marker, NULL, NULL);
|
1927
1832
|
rb_global_variable(&gc_hook);
|
1928
1833
|
|
data/ext/tracer.c
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
#include <assert.h>
|
2
|
+
#include <stdlib.h>
|
3
|
+
#include <string.h>
|
4
|
+
|
5
|
+
#include "tracer.h"
|
6
|
+
#include "util.h"
|
7
|
+
|
8
|
+
/*
|
9
|
+
XXX if we ever need a linked list for anything else ever, remove this crap
|
10
|
+
and switch to a generic macro-based linked list implementation
|
11
|
+
*/
|
12
|
+
struct tracer_list {
|
13
|
+
struct tracer *tracer;
|
14
|
+
struct tracer_list *next;
|
15
|
+
};
|
16
|
+
|
17
|
+
static struct tracer_list *tracer_list = NULL;
|
18
|
+
|
19
|
+
int
|
20
|
+
trace_insert(struct tracer *trace)
|
21
|
+
{
|
22
|
+
assert(trace != NULL);
|
23
|
+
|
24
|
+
struct tracer_list *entry = malloc(sizeof(*entry));
|
25
|
+
entry->tracer = trace;
|
26
|
+
|
27
|
+
entry->next = tracer_list;
|
28
|
+
tracer_list = entry;
|
29
|
+
return 0;
|
30
|
+
}
|
31
|
+
|
32
|
+
int
|
33
|
+
trace_remove(const char *id)
|
34
|
+
{
|
35
|
+
struct tracer_list *tmp, *prev;
|
36
|
+
tmp = prev = tracer_list;
|
37
|
+
|
38
|
+
while (tmp) {
|
39
|
+
if (strcmp(id, tmp->tracer->id) == 0) {
|
40
|
+
tmp->next = tmp->next;
|
41
|
+
free(tmp->tracer->id);
|
42
|
+
free(tmp->tracer);
|
43
|
+
free(tmp);
|
44
|
+
return 0;
|
45
|
+
}
|
46
|
+
prev = tmp;
|
47
|
+
tmp = tmp->next;
|
48
|
+
}
|
49
|
+
|
50
|
+
return 1;
|
51
|
+
}
|
52
|
+
|
53
|
+
static void
|
54
|
+
do_trace_invoke(struct tracer *trace, trace_fn fn)
|
55
|
+
{
|
56
|
+
switch (fn) {
|
57
|
+
case TRACE_START:
|
58
|
+
trace->start();
|
59
|
+
break;
|
60
|
+
case TRACE_STOP:
|
61
|
+
trace->stop();
|
62
|
+
break;
|
63
|
+
case TRACE_RESET:
|
64
|
+
trace->reset();
|
65
|
+
break;
|
66
|
+
case TRACE_DUMP:
|
67
|
+
trace->dump();
|
68
|
+
break;
|
69
|
+
default:
|
70
|
+
dbg_printf("invoked a non-existant trace function type: %d", fn);
|
71
|
+
assert(1==0);
|
72
|
+
}
|
73
|
+
|
74
|
+
return;
|
75
|
+
}
|
76
|
+
|
77
|
+
int
|
78
|
+
trace_invoke_all(trace_fn fn)
|
79
|
+
{
|
80
|
+
struct tracer_list *tmp = tracer_list;
|
81
|
+
while (tmp) {
|
82
|
+
do_trace_invoke(tmp->tracer, fn);
|
83
|
+
tmp = tmp->next;
|
84
|
+
}
|
85
|
+
return 0;
|
86
|
+
}
|
87
|
+
|
88
|
+
int
|
89
|
+
trace_invoke(const char *id, trace_fn fn)
|
90
|
+
{
|
91
|
+
struct tracer_list *tmp = tracer_list;
|
92
|
+
while (tmp) {
|
93
|
+
if (strcmp(id, tmp->tracer->id) == 0) {
|
94
|
+
do_trace_invoke(tmp->tracer, fn);
|
95
|
+
}
|
96
|
+
tmp = tmp->next;
|
97
|
+
}
|
98
|
+
return 0;
|
99
|
+
}
|
data/ext/tracer.h
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#if !defined(__TRACER__H_)
|
2
|
+
#define __TRACER__H_
|
3
|
+
|
4
|
+
struct tracer {
|
5
|
+
char *id;
|
6
|
+
void (*start)();
|
7
|
+
void (*stop)();
|
8
|
+
void (*reset)();
|
9
|
+
void (*dump)();
|
10
|
+
};
|
11
|
+
|
12
|
+
typedef enum {
|
13
|
+
TRACE_START,
|
14
|
+
TRACE_STOP,
|
15
|
+
TRACE_RESET,
|
16
|
+
TRACE_DUMP,
|
17
|
+
} trace_fn;
|
18
|
+
|
19
|
+
int
|
20
|
+
trace_insert(struct tracer *trace);
|
21
|
+
|
22
|
+
int
|
23
|
+
trace_remove(const char *id);
|
24
|
+
|
25
|
+
int
|
26
|
+
trace_invoke_all(trace_fn fn);
|
27
|
+
|
28
|
+
int
|
29
|
+
trace_invoke(const char *id, trace_fn fn);
|
30
|
+
|
31
|
+
/* for now, these will live here */
|
32
|
+
extern void install_malloc_tracer();
|
33
|
+
#endif
|
@@ -0,0 +1,163 @@
|
|
1
|
+
#include <assert.h>
|
2
|
+
#include <stdio.h>
|
3
|
+
#include <stdlib.h>
|
4
|
+
#include <string.h>
|
5
|
+
|
6
|
+
#include "arch.h"
|
7
|
+
#include "bin_api.h"
|
8
|
+
#include "tracer.h"
|
9
|
+
#include "util.h"
|
10
|
+
|
11
|
+
struct memprof_malloc_stats {
|
12
|
+
size_t malloc_bytes_requested;
|
13
|
+
size_t calloc_bytes_requested;
|
14
|
+
size_t realloc_bytes_requested;
|
15
|
+
|
16
|
+
size_t malloc_bytes_actual;
|
17
|
+
size_t calloc_bytes_actual;
|
18
|
+
size_t realloc_bytes_actual;
|
19
|
+
size_t free_bytes_actual;
|
20
|
+
|
21
|
+
size_t malloc_calls;
|
22
|
+
size_t calloc_calls;
|
23
|
+
size_t realloc_calls;
|
24
|
+
size_t free_calls;
|
25
|
+
};
|
26
|
+
|
27
|
+
static struct tracer tracer;
|
28
|
+
static struct memprof_malloc_stats memprof_malloc_stats;
|
29
|
+
static void *(*orig_malloc)(size_t), *(*orig_realloc)(void *, size_t),
|
30
|
+
*(*orig_calloc)(size_t, size_t), (*orig_free)(void *);
|
31
|
+
static size_t (*malloc_usable_size)(void *ptr);
|
32
|
+
|
33
|
+
static void *
|
34
|
+
malloc_tramp(size_t size)
|
35
|
+
{
|
36
|
+
void *ret = NULL;
|
37
|
+
memprof_malloc_stats.malloc_bytes_requested += size;
|
38
|
+
memprof_malloc_stats.malloc_calls++;
|
39
|
+
ret = orig_malloc(size);
|
40
|
+
memprof_malloc_stats.malloc_bytes_actual += malloc_usable_size(ret);
|
41
|
+
return ret;
|
42
|
+
}
|
43
|
+
|
44
|
+
static void *
|
45
|
+
calloc_tramp(size_t nmemb, size_t size)
|
46
|
+
{
|
47
|
+
void *ret = NULL;
|
48
|
+
memprof_malloc_stats.calloc_bytes_requested += (nmemb * size);
|
49
|
+
memprof_malloc_stats.calloc_calls++;
|
50
|
+
ret = (*orig_calloc)(nmemb, size);
|
51
|
+
memprof_malloc_stats.calloc_bytes_actual += malloc_usable_size(ret);
|
52
|
+
return ret;
|
53
|
+
}
|
54
|
+
|
55
|
+
static void *
|
56
|
+
realloc_tramp(void *ptr, size_t size)
|
57
|
+
{
|
58
|
+
/* TODO need to check malloc_usable_size of before/after i guess? */
|
59
|
+
void *ret = NULL;
|
60
|
+
memprof_malloc_stats.realloc_bytes_requested += size;
|
61
|
+
memprof_malloc_stats.realloc_calls++;
|
62
|
+
ret = orig_realloc(ptr, size);
|
63
|
+
memprof_malloc_stats.realloc_bytes_actual += malloc_usable_size(ptr);
|
64
|
+
return ret;
|
65
|
+
}
|
66
|
+
|
67
|
+
static void
|
68
|
+
free_tramp(void *ptr)
|
69
|
+
{
|
70
|
+
memprof_malloc_stats.free_bytes_actual += malloc_usable_size(ptr);
|
71
|
+
memprof_malloc_stats.free_calls++;
|
72
|
+
orig_free(ptr);
|
73
|
+
}
|
74
|
+
|
75
|
+
static void
|
76
|
+
malloc_trace_stop()
|
77
|
+
{
|
78
|
+
struct tramp_st2_entry tmp;
|
79
|
+
|
80
|
+
tmp.addr = orig_malloc;
|
81
|
+
bin_update_image("malloc", &tmp, NULL);
|
82
|
+
|
83
|
+
tmp.addr = orig_realloc;
|
84
|
+
bin_update_image("realloc", &tmp, NULL);
|
85
|
+
|
86
|
+
if (orig_calloc) {
|
87
|
+
tmp.addr = orig_calloc;
|
88
|
+
bin_update_image("calloc", &tmp, NULL);
|
89
|
+
}
|
90
|
+
|
91
|
+
tmp.addr = orig_free;
|
92
|
+
bin_update_image("free", &tmp, NULL);
|
93
|
+
}
|
94
|
+
|
95
|
+
static void
|
96
|
+
malloc_trace_reset()
|
97
|
+
{
|
98
|
+
memset(&memprof_malloc_stats, 0, sizeof(memprof_malloc_stats));
|
99
|
+
}
|
100
|
+
|
101
|
+
static void
|
102
|
+
malloc_trace_dump()
|
103
|
+
{
|
104
|
+
fprintf(stderr, "================ Requested ====================\n");
|
105
|
+
fprintf(stderr, "Malloced: %zd, Realloced: %zd, Calloced: %zd\n",
|
106
|
+
memprof_malloc_stats.malloc_bytes_requested, memprof_malloc_stats.realloc_bytes_requested,
|
107
|
+
memprof_malloc_stats.calloc_bytes_requested);
|
108
|
+
fprintf(stderr, "================ Actual ====================\n");
|
109
|
+
fprintf(stderr, "Malloced: %zd, Realloced: %zd, Calloced: %zd, Freed: %zd\n",
|
110
|
+
memprof_malloc_stats.malloc_bytes_actual, memprof_malloc_stats.realloc_bytes_actual,
|
111
|
+
memprof_malloc_stats.calloc_bytes_actual, memprof_malloc_stats.free_bytes_actual);
|
112
|
+
fprintf(stderr, "================ Call count ====================\n");
|
113
|
+
fprintf(stderr, "Calls to malloc: %zd, realloc: %zd, calloc: %zd, free: %zd\n",
|
114
|
+
memprof_malloc_stats.malloc_calls,
|
115
|
+
memprof_malloc_stats.realloc_calls,
|
116
|
+
memprof_malloc_stats.calloc_calls,
|
117
|
+
memprof_malloc_stats.free_calls);
|
118
|
+
}
|
119
|
+
|
120
|
+
static void
|
121
|
+
malloc_trace_start()
|
122
|
+
{
|
123
|
+
struct tramp_st2_entry tmp;
|
124
|
+
|
125
|
+
if (!malloc_usable_size) {
|
126
|
+
if ((malloc_usable_size =
|
127
|
+
bin_find_symbol("MallocExtension_GetAllocatedSize", NULL, 1)) == NULL) {
|
128
|
+
malloc_usable_size = bin_find_symbol("malloc_usable_size", NULL, 1);
|
129
|
+
dbg_printf("tcmalloc was not found...\n");
|
130
|
+
}
|
131
|
+
assert(malloc_usable_size != NULL);
|
132
|
+
dbg_printf("malloc_usable_size: %p\n", malloc_usable_size);
|
133
|
+
}
|
134
|
+
|
135
|
+
tmp.addr = malloc_tramp;
|
136
|
+
bin_update_image("malloc", &tmp, (void **)(&orig_malloc));
|
137
|
+
assert(orig_malloc != NULL);
|
138
|
+
dbg_printf("orig_malloc: %p\n", orig_malloc);
|
139
|
+
|
140
|
+
tmp.addr = realloc_tramp;
|
141
|
+
bin_update_image("realloc", &tmp,(void **)(&orig_realloc));
|
142
|
+
dbg_printf("orig_realloc: %p\n", orig_realloc);
|
143
|
+
|
144
|
+
tmp.addr = calloc_tramp;
|
145
|
+
bin_update_image("calloc", &tmp, (void **)(&orig_calloc));
|
146
|
+
dbg_printf("orig_calloc: %p\n", orig_calloc);
|
147
|
+
|
148
|
+
tmp.addr = free_tramp;
|
149
|
+
bin_update_image("free", &tmp, (void **)(&orig_free));
|
150
|
+
assert(orig_free != NULL);
|
151
|
+
dbg_printf("orig_free: %p\n", orig_free);
|
152
|
+
}
|
153
|
+
|
154
|
+
void install_malloc_tracer()
|
155
|
+
{
|
156
|
+
tracer.start = malloc_trace_start;
|
157
|
+
tracer.stop = malloc_trace_stop;
|
158
|
+
tracer.reset = malloc_trace_reset;
|
159
|
+
tracer.dump = malloc_trace_dump;
|
160
|
+
tracer.id = strdup("malloc_tracer");
|
161
|
+
|
162
|
+
trace_insert(&tracer);
|
163
|
+
}
|
data/ext/util.h
CHANGED
@@ -2,7 +2,11 @@
|
|
2
2
|
#define __util_h__
|
3
3
|
|
4
4
|
#if defined(_MEMPROF_DEBUG)
|
5
|
-
#
|
5
|
+
#include <stdio.h>
|
6
|
+
#define dbg_printf(...) do {\
|
7
|
+
fprintf(stderr, "%s:%d ", __FILE__, __LINE__);\
|
8
|
+
fprintf(stderr, __VA_ARGS__);\
|
9
|
+
} while (0)
|
6
10
|
#else
|
7
11
|
#define dbg_printf(...)
|
8
12
|
#endif
|
@@ -33,6 +37,7 @@ struct memprof_config {
|
|
33
37
|
|
34
38
|
void *heaps;
|
35
39
|
void *heaps_used;
|
40
|
+
void *finalizer_table;
|
36
41
|
|
37
42
|
size_t sizeof_RVALUE;
|
38
43
|
size_t sizeof_heaps_slot;
|
data/ext/x86_gen.c
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#include <assert.h>
|
2
|
+
#include <stdint.h>
|
3
|
+
#include "arch.h"
|
4
|
+
#include "x86_gen.h"
|
5
|
+
|
6
|
+
/*
|
7
|
+
* arch_insert_st1_tramp - architecture specific stage 1 trampoline insert
|
8
|
+
*
|
9
|
+
* Given:
|
10
|
+
* - a start address (start),
|
11
|
+
* - the absolute address of the function to intercept (trampee),
|
12
|
+
* - the absolute address of the code to execute instead (tramp),
|
13
|
+
*
|
14
|
+
* This function will:
|
15
|
+
* - interpret address start as a struct st1_base,
|
16
|
+
* - check that the instruction at call is actually a call
|
17
|
+
* - if so, check that the target of the call is trampee
|
18
|
+
* - and change the target to tramp
|
19
|
+
*
|
20
|
+
* Returns 0 on success, 1 otherwise.
|
21
|
+
*/
|
22
|
+
int
|
23
|
+
arch_insert_st1_tramp(void *start, void *trampee, void *tramp)
|
24
|
+
{
|
25
|
+
assert(start != NULL);
|
26
|
+
assert(trampee != NULL);
|
27
|
+
assert(tramp != NULL);
|
28
|
+
|
29
|
+
int32_t fn_addr = 0;
|
30
|
+
struct st1_base *check = start;
|
31
|
+
|
32
|
+
if (check->call == 0xe8) {
|
33
|
+
fn_addr = check->displacement;
|
34
|
+
if ((trampee - (void *)(check + 1)) == fn_addr) {
|
35
|
+
WRITE_INSTRUCTIONS(&check->displacement,
|
36
|
+
sizeof(*check),
|
37
|
+
(check->displacement = (tramp - (void *)(check + 1))));
|
38
|
+
return 0;
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
return 1;
|
43
|
+
}
|
44
|
+
|
45
|
+
/*
|
46
|
+
* arch_get_st2_tramp - architecture specific stage 2 tramp accessor. This
|
47
|
+
* function returns a pointer to the default stage 2 trampoline setting size
|
48
|
+
* if a non-NULL pointer was passed in.
|
49
|
+
*/
|
50
|
+
void *
|
51
|
+
arch_get_st2_tramp(size_t *size)
|
52
|
+
{
|
53
|
+
if (size) {
|
54
|
+
*size = sizeof(default_st2_tramp);
|
55
|
+
}
|
56
|
+
|
57
|
+
return &default_st2_tramp;
|
58
|
+
}
|
59
|
+
|
60
|
+
/*
|
61
|
+
* arch_get_inline_st2_tramp - architecture specific inline stage 2 tramp
|
62
|
+
* accessor. This function returns a pointer to the default inline stage 2
|
63
|
+
* trampoline setting size if a non-NULL pointer was passed in.
|
64
|
+
*/
|
65
|
+
void *
|
66
|
+
arch_get_inline_st2_tramp(size_t *size)
|
67
|
+
{
|
68
|
+
if (size) {
|
69
|
+
*size = sizeof(default_inline_st2_tramp);
|
70
|
+
}
|
71
|
+
|
72
|
+
return &default_inline_st2_tramp;
|
73
|
+
}
|
data/ext/x86_gen.h
CHANGED
@@ -2,8 +2,10 @@
|
|
2
2
|
#define _x86_gen_
|
3
3
|
|
4
4
|
#include <assert.h>
|
5
|
-
#include <sys/mman.h>
|
6
5
|
#include <stdint.h>
|
6
|
+
#include <stdlib.h>
|
7
|
+
#include <string.h>
|
8
|
+
#include <sys/mman.h>
|
7
9
|
#include "arch.h"
|
8
10
|
|
9
11
|
/*
|
@@ -15,7 +17,7 @@
|
|
15
17
|
*
|
16
18
|
* For example: callq <0xdeadbeef> #rb_newobj
|
17
19
|
*/
|
18
|
-
struct st1_base {
|
20
|
+
static struct st1_base {
|
19
21
|
unsigned char call;
|
20
22
|
int32_t displacement;
|
21
23
|
} __attribute__((__packed__)) st1_mov = {
|
@@ -53,8 +55,24 @@ copy_instructions(void *dest, void *src, size_t count)
|
|
53
55
|
|
54
56
|
mprotect(aligned_addr, (dest - aligned_addr) + count, PROT_READ|PROT_WRITE|PROT_EXEC);
|
55
57
|
memcpy(dest, src, count);
|
56
|
-
mprotect(aligned_addr, (dest - aligned_addr) + count, PROT_READ|PROT_EXEC);
|
57
58
|
|
59
|
+
/*
|
60
|
+
* XXX This has to be commented out because setting certian sections to
|
61
|
+
* readonly (.got.plt, et al.) will cause the rtld to die.
|
62
|
+
*
|
63
|
+
* There is no way to get the current permissions bits for a page.
|
64
|
+
*
|
65
|
+
* The way to solve this is:
|
66
|
+
*
|
67
|
+
* 1.) copy_instructions can take a final_permissions mask and each
|
68
|
+
* overwrite site can put in the 'Right Thing'
|
69
|
+
*
|
70
|
+
* 2.) Each overwrite site can look up the 'Right Thing' in the object
|
71
|
+
* header and pass it in, ensuring the desired permissions are
|
72
|
+
* set after.
|
73
|
+
*
|
74
|
+
* mprotect(aligned_addr, (dest - aligned_addr) + count, PROT_READ|PROT_EXEC);
|
75
|
+
*/
|
58
76
|
return;
|
59
77
|
}
|
60
78
|
|
@@ -70,72 +88,4 @@ copy_instructions(void *dest, void *src, size_t count)
|
|
70
88
|
mprotect(aligned_addr, count, PROT_READ | PROT_EXEC); \
|
71
89
|
} while (0)
|
72
90
|
|
73
|
-
/*
|
74
|
-
* arch_insert_st1_tramp - architecture specific stage 1 trampoline insert
|
75
|
-
*
|
76
|
-
* Given:
|
77
|
-
* - a start address (start),
|
78
|
-
* - the absolute address of the function to intercept (trampee),
|
79
|
-
* - the absolute address of the code to execute instead (tramp),
|
80
|
-
*
|
81
|
-
* This function will:
|
82
|
-
* - interpret address start as a struct st1_base,
|
83
|
-
* - check that the instruction at call is actually a call
|
84
|
-
* - if so, check that the target of the call is trampee
|
85
|
-
* - and change the target to tramp
|
86
|
-
*
|
87
|
-
* Returns 0 on success, 1 otherwise.
|
88
|
-
*/
|
89
|
-
int
|
90
|
-
arch_insert_st1_tramp(void *start, void *trampee, void *tramp)
|
91
|
-
{
|
92
|
-
assert(start != NULL);
|
93
|
-
assert(trampee != NULL);
|
94
|
-
assert(tramp != NULL);
|
95
|
-
|
96
|
-
int32_t fn_addr = 0;
|
97
|
-
struct st1_base *check = start;
|
98
|
-
|
99
|
-
if (check->call == 0xe8) {
|
100
|
-
fn_addr = check->displacement;
|
101
|
-
if ((trampee - (void *)(check + 1)) == fn_addr) {
|
102
|
-
WRITE_INSTRUCTIONS(&check->displacement,
|
103
|
-
sizeof(*check),
|
104
|
-
(check->displacement = (tramp - (void *)(check + 1))));
|
105
|
-
return 0;
|
106
|
-
}
|
107
|
-
}
|
108
|
-
|
109
|
-
return 1;
|
110
|
-
}
|
111
|
-
|
112
|
-
/*
|
113
|
-
* arch_get_st2_tramp - architecture specific stage 2 tramp accessor. This
|
114
|
-
* function returns a pointer to the default stage 2 trampoline setting size
|
115
|
-
* if a non-NULL pointer was passed in.
|
116
|
-
*/
|
117
|
-
void *
|
118
|
-
arch_get_st2_tramp(size_t *size)
|
119
|
-
{
|
120
|
-
if (size) {
|
121
|
-
*size = sizeof(default_st2_tramp);
|
122
|
-
}
|
123
|
-
|
124
|
-
return &default_st2_tramp;
|
125
|
-
}
|
126
|
-
|
127
|
-
/*
|
128
|
-
* arch_get_inline_st2_tramp - architecture specific inline stage 2 tramp
|
129
|
-
* accessor. This function returns a pointer to the default inline stage 2
|
130
|
-
* trampoline setting size if a non-NULL pointer was passed in.
|
131
|
-
*/
|
132
|
-
void *
|
133
|
-
arch_get_inline_st2_tramp(size_t *size)
|
134
|
-
{
|
135
|
-
if (size) {
|
136
|
-
*size = sizeof(default_inline_st2_tramp);
|
137
|
-
}
|
138
|
-
|
139
|
-
return &default_inline_st2_tramp;
|
140
|
-
}
|
141
91
|
#endif
|
data/memprof.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
spec = Gem::Specification.new do |s|
|
2
2
|
s.name = 'memprof'
|
3
|
-
s.version = '0.3.
|
4
|
-
s.date = '2010-
|
3
|
+
s.version = '0.3.1'
|
4
|
+
s.date = '2010-04-13'
|
5
5
|
s.summary = 'Ruby Memory Profiler'
|
6
6
|
s.description = "Ruby memory profiler similar to bleak_house, but without patches to the Ruby VM"
|
7
7
|
s.homepage = "http://github.com/ice799/memprof"
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 3
|
8
|
-
-
|
9
|
-
version: 0.3.
|
8
|
+
- 1
|
9
|
+
version: 0.3.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Joe Damato
|
@@ -17,7 +17,7 @@ autorequire:
|
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
19
|
|
20
|
-
date: 2010-
|
20
|
+
date: 2010-04-13 00:00:00 -04:00
|
21
21
|
default_executable:
|
22
22
|
dependencies:
|
23
23
|
- !ruby/object:Gem::Dependency
|
@@ -73,12 +73,16 @@ files:
|
|
73
73
|
- ext/src/libdwarf-20091118.tar.gz
|
74
74
|
- ext/src/libelf-0.8.13.tar.gz
|
75
75
|
- ext/src/yajl-1.0.9.tar.gz
|
76
|
+
- ext/tracer.c
|
77
|
+
- ext/tracer.h
|
78
|
+
- ext/tracers/malloc.c
|
76
79
|
- ext/tramp.c
|
77
80
|
- ext/tramp.h
|
78
81
|
- ext/util.c
|
79
82
|
- ext/util.h
|
80
83
|
- ext/x86_64.c
|
81
84
|
- ext/x86_64.h
|
85
|
+
- ext/x86_gen.c
|
82
86
|
- ext/x86_gen.h
|
83
87
|
- lib/memprof/middleware.rb
|
84
88
|
- lib/memprof/signal.rb
|