memprof 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|