memprof 0.2.9 → 0.3.0

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/.gitignore CHANGED
@@ -4,3 +4,7 @@
4
4
  Makefile
5
5
  *.dSYM
6
6
  *.log
7
+ *.gem
8
+ ext/dst/*
9
+ ext/src/yajl-1.0.9/*
10
+
data/Rakefile CHANGED
@@ -7,3 +7,33 @@ task :spec do
7
7
  sh "ruby spec/memprof_spec.rb"
8
8
  end
9
9
  task :default => :spec
10
+
11
+ # Should be used like:
12
+ # rake --trace ci[1.8.7,shared]
13
+
14
+ task :ci, [:ruby_type, :lib_option] do |t, args|
15
+ ruby_type, lib_option = args[:ruby_type], args[:lib_option]
16
+ raise "#{ruby_type} is not a supported ruby version" unless ["1.8.6", "1.8.7", "ree"].include?(ruby_type)
17
+ raise "#{lib_option} is not a supported " unless ["shared", "static"].include?(lib_option)
18
+
19
+ lib_option = case lib_option
20
+ when "static"
21
+ "--disable-shared"
22
+ when "shared"
23
+ "--enable-shared"
24
+ end
25
+
26
+ sh "/usr/bin/env bash -c \"
27
+ source ~/.rvm/scripts/rvm &&
28
+ rvm install #{ruby_type} --reconfigure -C #{lib_option} &&
29
+ rvm #{ruby_type} --symlink memprof &&
30
+ memprof_gem install bacon\""
31
+
32
+ Dir.chdir('ext') do
33
+ sh '/usr/bin/env bash -c "make clean"' rescue nil
34
+ sh "~/.rvm/bin/memprof_ruby extconf.rb"
35
+ sh '/usr/bin/env bash -c "make"'
36
+ end
37
+ sh "~/.rvm/bin/memprof_ruby spec/memprof_spec.rb"
38
+ end
39
+
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'optparse'
5
+ require 'restclient'
6
+ require 'term/ansicolor'
7
+
8
+ MEMPROF_URL = "https://memprof.com/upload"
9
+
10
+ MEMPROF_BANNER = <<-EOF
11
+ Memprof Uploader
12
+ http://www.memprof.com
13
+ ======================
14
+
15
+ EOF
16
+
17
+ class MemprofUploader
18
+ include Term::ANSIColor
19
+
20
+ def initialize(args=ARGV)
21
+ puts MEMPROF_BANNER
22
+
23
+ @parser = OptionParser.new do |opts|
24
+ opts.banner = "Usage:"
25
+ opts.on("-p", "--pid <pid>", Integer, "PID of the process to dump (required)") {|arg| @pid = arg }
26
+ opts.on("-n", "--name <name>", "Name for your dump (required)") {|arg| @name = arg }
27
+ opts.on("-k", "--key <key>", "Memprof.com API key (required)") {|arg| @key = arg }
28
+ opts.on("-d", "--[no-]delete", "Delete dump file after uploading (default true)") {|arg| @delete_dump = arg }
29
+ opts.on("-s", "--seconds <seconds>", "Seconds to wait for the dump (default 60)") {|arg| @secs = arg}
30
+ opts.on("-t", "--[no-]test", "Test run (don't actually upload) (default false)") {|arg| @test = arg }
31
+ opts.on("-f", "--file <path>", "Upload specific json dump (optional)") {|arg| @file = arg }
32
+ opts.on("--put-my-data-on-the-internet", "Confirm that you understand\n" +
33
+ "memprof.com will show all your \n".rjust(80) +
34
+ "internal data on the internet (required)".rjust(80)) {|arg| @confirmed = true}
35
+ end
36
+
37
+ begin
38
+ @parser.parse!
39
+ rescue Exception => e
40
+ if e.kind_of?(SystemExit)
41
+ raise e
42
+ else
43
+ fail_with(e.message)
44
+ end
45
+ end
46
+
47
+ # Make this default to true if the user didn't pass any preference.
48
+ @delete_dump = true if @delete_dump.nil?
49
+
50
+ # Make this default to 60 if the user didn't pass the number of seconds.
51
+ @secs = 60 if @secs.nil?
52
+
53
+ if @file
54
+ fail_with("File not found: #{@file}") unless File.exists?(@file)
55
+ end
56
+
57
+ if @pid.nil? and @file.nil?
58
+ fail_with("Missing PID! (-p PID)")
59
+ elsif @name.nil? || @name.empty?
60
+ fail_with("Missing name! (-n 'my application')")
61
+ elsif @key.nil? || @key.empty?
62
+ fail_with("Missing API key! (-k KEY)")
63
+ elsif !@confirmed
64
+ fail_with("\nERROR: You MUST CONFIRM that you understand ALL YOUR CODE "+
65
+ "WILL BE PUBLICLY \nAVAILABLE ON THE INTERNET by passing " +
66
+ "--put-my-data-on-the-internet", true)
67
+ end
68
+ end
69
+
70
+ def run!
71
+ dump_filename = @file ? compress_file(@file, "/tmp/#{File.basename @file}.gz") : compress_file(get_dump_filename(@pid))
72
+
73
+ begin
74
+ upload_dump(dump_filename, @name, @key)
75
+ ensure
76
+ if @delete_dump
77
+ File.delete(dump_filename)
78
+ puts "\nDeleted dump file."
79
+ end
80
+ end
81
+
82
+ puts "Finished!"
83
+ end
84
+
85
+ private
86
+
87
+ def compress_file(filename, output=nil)
88
+ puts "Compressing with gzip..."
89
+ output ||= "#{filename}.gz"
90
+ `gzip -c '#{filename}' > '#{output}'`
91
+ output
92
+ end
93
+
94
+ def get_dump_filename(pid)
95
+ # Get a listing of files before we signal for the new dump
96
+ # We'll compare against this later to see which is the new file.
97
+ old_files = Dir.glob("/tmp/memprof-#{pid}-*")
98
+ signal_process(pid)
99
+ puts "Waiting 5 seconds for process #{pid} to create a new dump..."
100
+
101
+ file = nil
102
+
103
+ 5.times do |i|
104
+ sleep 1 unless file = (Dir.glob("/tmp/memprof-#{pid}-*") - old_files).first and break
105
+ fail_with("Timed out after waiting 5 seconds. Make sure you added require '#{File.expand_path('../../lib/memprof/signal', __FILE__)}' to your application.") if i+1 == 5
106
+ end
107
+
108
+ puts "\nFound file #{file}"
109
+
110
+ if file =~ /\.IN_PROGRESS/
111
+ file = file.sub(/\.IN_PROGRESS/, "")
112
+ puts "Dump in progress. Waiting #{@secs} seconds for it to complete..."
113
+ @secs.times do |i|
114
+ sleep 1 unless File.exist?(file) and break
115
+ fail_with("Timed out after waiting #{@secs} seconds") if i+1 == @secs
116
+ end
117
+ end
118
+
119
+ file
120
+ end
121
+
122
+ def upload_dump(filename, name, key)
123
+ return if @test
124
+
125
+ file = File.open(filename, "r")
126
+
127
+ puts "\nUploading to memprof.com..."
128
+ response = RestClient.post(MEMPROF_URL, :upload => file, :name => name, :key => key)
129
+
130
+ puts "Response from server:"
131
+ p response.to_s
132
+ end
133
+
134
+ def fail_with(str, yell=true)
135
+ puts @parser.to_s
136
+ puts
137
+ if yell
138
+ print red, bold, str, reset
139
+ puts
140
+ else
141
+ puts "\n" + str
142
+ end
143
+ puts
144
+ exit(1)
145
+ end
146
+
147
+ def signal_process(pid)
148
+ begin
149
+ Process.kill("URG", pid)
150
+ rescue Errno::ESRCH
151
+ fail_with("No such process #{pid}!")
152
+ end
153
+ puts "Signaled process #{pid} with SIGURG"
154
+ end
155
+
156
+ end
157
+
158
+ MemprofUploader.new(ARGV).run!
@@ -21,6 +21,8 @@ bin_init();
21
21
  * Given:
22
22
  * - sym - a symbol name
23
23
  * - size - optional out parameter
24
+ * - search_libs - 0 to search only ruby/libruby, 1 to search other
25
+ * libraries, too.
24
26
  *
25
27
  * This function will search for the symbol sym and return its address if
26
28
  * found, or NULL if the symbol could not be found.
@@ -29,7 +31,7 @@ bin_init();
29
31
  * size of the symbol.
30
32
  */
31
33
  void *
32
- bin_find_symbol(const char *sym, size_t *size);
34
+ bin_find_symbol(const char *sym, size_t *size, int search_libs);
33
35
 
34
36
  /*
35
37
  * bin_find_symbol_name - find a symbol's name
@@ -85,6 +87,8 @@ bin_type_member_offset(const char *type, const char *member);
85
87
  * Given:
86
88
  * - trampee - the name of the symbol to hook
87
89
  * - tramp - the stage 2 trampoline entry
90
+ * - orig_func - out parameter storing the address of the function that was
91
+ * originally being called.
88
92
  *
89
93
  * this function will update the binary image so that all calls to trampee will
90
94
  * be routed to tramp.
@@ -92,5 +96,5 @@ bin_type_member_offset(const char *type, const char *member);
92
96
  * Returns 0 on success.
93
97
  */
94
98
  int
95
- bin_update_image(const char *trampee, struct tramp_st2_entry *tramp);
99
+ bin_update_image(const char *trampee, struct tramp_st2_entry *tramp, void **orig_func);
96
100
  #endif
data/ext/elf.c CHANGED
@@ -90,6 +90,7 @@ typedef enum {
90
90
  * entry.
91
91
  */
92
92
  typedef linkmap_cb_status (*linkmap_cb)(struct link_map *, void *);
93
+ typedef void (*linkmap_lib_cb)(struct elf_info *lib, void *data);
93
94
 
94
95
  static void open_elf(struct elf_info *info);
95
96
  static int dissect_elf(struct elf_info *info, int find_debug);
@@ -140,27 +141,36 @@ get_got_addr(struct plt_entry *plt)
140
141
  /*
141
142
  * overwrite_got - given the address of a PLT entry, overwrite the address
142
143
  * in the GOT that the PLT entry uses with the address in tramp.
144
+ *
145
+ * returns the original function address
143
146
  */
144
- static void
147
+ static void *
145
148
  overwrite_got(void *plt, const void *tramp)
146
149
  {
147
150
  assert(plt != NULL);
148
151
  assert(tramp != NULL);
152
+ void *ret = NULL;
153
+
154
+ memcpy(&ret, get_got_addr(plt), sizeof(ret));
149
155
  memcpy(get_got_addr(plt), &tramp, sizeof(void *));
150
- return;
156
+ return ret;
151
157
  }
152
158
 
153
159
  struct plt_hook_data {
154
160
  const char *sym;
155
- const void *tramp;
161
+ void *addr;
162
+ };
163
+
164
+ struct dso_iter_data {
165
+ linkmap_lib_cb cb;
166
+ void *passthru;
156
167
  };
157
168
 
158
169
  static linkmap_cb_status
159
- hook_func_cb(struct link_map *map, void *data)
170
+ for_each_dso_cb(struct link_map *map, void *data)
160
171
  {
161
- struct plt_hook_data *hook_data = data;
172
+ struct dso_iter_data *iter_data = data;
162
173
  struct elf_info curr_lib;
163
- void *trampee_addr = NULL;
164
174
 
165
175
  /* skip a few things we don't care about */
166
176
  if (strstr(map->l_name, "linux-vdso")) {
@@ -203,10 +213,7 @@ hook_func_cb(struct link_map *map, void *data)
203
213
  dbg_printf("dissected the elf file: %s, base: %lx\n",
204
214
  curr_lib.filename, (unsigned long)curr_lib.base_addr);
205
215
 
206
- if ((trampee_addr = find_plt_addr(hook_data->sym, &curr_lib)) != NULL) {
207
- dbg_printf("found: %s @ %p\n", hook_data->sym, trampee_addr);
208
- overwrite_got(trampee_addr, hook_data->tramp);
209
- }
216
+ iter_data->cb(&curr_lib, iter_data->passthru);
210
217
 
211
218
  elf_end(curr_lib.elf);
212
219
  close(curr_lib.fd);
@@ -214,12 +221,26 @@ hook_func_cb(struct link_map *map, void *data)
214
221
  }
215
222
 
216
223
  static void
217
- hook_required_objects(void *tramp)
224
+ for_each_dso(linkmap_lib_cb cb, void *passthru)
218
225
  {
219
- struct plt_hook_data data;
220
- data.sym = "rb_newobj";
221
- data.tramp = tramp;
222
- walk_linkmap(hook_func_cb, &data);
226
+ struct dso_iter_data data;
227
+ data.cb = cb;
228
+ data.passthru = passthru;
229
+ walk_linkmap(for_each_dso_cb, &data);
230
+ }
231
+
232
+ static void
233
+ hook_required_objects(struct elf_info *info, void *data)
234
+ {
235
+ assert(info != NULL);
236
+ struct plt_hook_data *hook_data = data;
237
+ void *trampee_addr = NULL;
238
+
239
+ if ((trampee_addr = find_plt_addr(hook_data->sym, info)) != NULL) {
240
+ dbg_printf("found: %s @ %p\n", hook_data->sym, trampee_addr);
241
+ overwrite_got(trampee_addr, hook_data->addr);
242
+ }
243
+
223
244
  return;
224
245
  }
225
246
 
@@ -320,6 +341,8 @@ find_plt_addr(const char *symname, struct elf_info *info)
320
341
  info = ruby_info;
321
342
  }
322
343
 
344
+ assert(info != NULL);
345
+
323
346
  /* Search through each of the .rela.plt entries */
324
347
  for (i = 0; i < info->relplt_count; i++) {
325
348
  GElf_Rela rela;
@@ -365,38 +388,78 @@ find_plt_addr(const char *symname, struct elf_info *info)
365
388
  static void *
366
389
  do_bin_find_symbol(const char *sym, size_t *size, struct elf_info *elf)
367
390
  {
368
- char *name = NULL;
391
+ const char *name = NULL;
369
392
 
370
393
  assert(sym != NULL);
371
394
  assert(elf != NULL);
372
395
 
373
- assert(elf->symtab_data != NULL);
374
- assert(elf->symtab_data->d_buf != NULL);
396
+ ElfW(Sym) *esym, *lastsym = NULL;
397
+ if (elf->symtab_data && elf->symtab_data->d_buf) {
398
+ dbg_printf("checking symtab....\n");
399
+ esym = (ElfW(Sym)*) elf->symtab_data->d_buf;
400
+ lastsym = (ElfW(Sym)*) ((char*) elf->symtab_data->d_buf + elf->symtab_data->d_size);
375
401
 
376
- ElfW(Sym) *esym = (ElfW(Sym)*) elf->symtab_data->d_buf;
377
- ElfW(Sym) *lastsym = (ElfW(Sym)*) ((char*) elf->symtab_data->d_buf + elf->symtab_data->d_size);
402
+ assert(esym <= lastsym);
378
403
 
379
- assert(esym <= lastsym);
404
+ for (; esym < lastsym; esym++){
405
+ /* ignore numeric/empty symbols */
406
+ if ((esym->st_value == 0) ||
407
+ (ELF32_ST_BIND(esym->st_info)== STB_NUM))
408
+ continue;
380
409
 
381
- for (; esym < lastsym; esym++){
382
- /* ignore weak/numeric/empty symbols */
383
- if ((esym->st_value == 0) ||
384
- (ELF32_ST_BIND(esym->st_info)== STB_WEAK) ||
385
- (ELF32_ST_BIND(esym->st_info)== STB_NUM))
386
- continue;
410
+ name = elf_strptr(elf->elf, elf->symtab_shdr.sh_link, (size_t)esym->st_name);
387
411
 
388
- name = elf_strptr(elf->elf, elf->symtab_shdr.sh_link, (size_t)esym->st_name);
412
+ if (name && strcmp(name, sym) == 0) {
413
+ if (size) {
414
+ *size = esym->st_size;
415
+ }
416
+ dbg_printf("Found symbol: %s in symtab\n", sym);
417
+ return elf->base_addr + (void *)esym->st_value;
418
+ }
419
+ }
420
+ }
421
+
422
+ if (elf->dynsym && elf->dynsym->d_buf) {
423
+ dbg_printf("checking dynsymtab....\n");
424
+ esym = (ElfW(Sym) *) elf->dynsym->d_buf;
425
+ lastsym = (ElfW(Sym) *) ((char *) elf->dynsym->d_buf + elf->dynsym->d_size);
426
+
427
+ for (; esym < lastsym; esym++){
428
+ /* ignore numeric/empty symbols */
429
+ if ((esym->st_value == 0) ||
430
+ (ELF32_ST_BIND(esym->st_info)== STB_NUM))
431
+ continue;
389
432
 
390
- if (name && strcmp(name, sym) == 0) {
391
- if (size) {
392
- *size = esym->st_size;
433
+ name = elf->dynstr + esym->st_name;
434
+
435
+ if (name && strcmp(name, sym) == 0) {
436
+ if (size) {
437
+ *size = esym->st_size;
438
+ }
439
+ dbg_printf("Found symbol: %s in dynsym\n", sym);
440
+ return elf->base_addr + (void *)esym->st_value;
393
441
  }
394
- return elf->base_addr + (void *)esym->st_value;
395
442
  }
396
443
  }
444
+
445
+ dbg_printf("Couldn't find symbol: %s in dynsym\n", sym);
397
446
  return NULL;
398
447
  }
399
448
 
449
+ static void
450
+ find_symbol_cb(struct elf_info *info, void *data)
451
+ {
452
+ assert(info != NULL);
453
+ void *trampee_addr = NULL;
454
+ struct plt_hook_data *hook_data = data;
455
+ void *ret = do_bin_find_symbol(hook_data->sym, NULL, info);
456
+ if (ret) {
457
+ hook_data->addr = ret;
458
+ dbg_printf("found %s @ %p, fn addr: %p\n", hook_data->sym, trampee_addr,
459
+ hook_data->addr);
460
+ }
461
+ }
462
+
400
463
  /*
401
464
  * bin_find_symbol - find the address of a given symbol and set its size if
402
465
  * desired.
@@ -404,9 +467,20 @@ do_bin_find_symbol(const char *sym, size_t *size, struct elf_info *elf)
404
467
  * This function is just a wrapper for the internal symbol lookup function.
405
468
  */
406
469
  void *
407
- bin_find_symbol(const char *sym, size_t *size)
470
+ bin_find_symbol(const char *sym, size_t *size, int search_libs)
408
471
  {
409
- return do_bin_find_symbol(sym, size, ruby_info);
472
+ void *ret = do_bin_find_symbol(sym, size, ruby_info);
473
+
474
+ if (!ret && search_libs) {
475
+ dbg_printf("Didn't find symbol: %s in ruby, searching other libs\n", sym);
476
+ struct plt_hook_data hd;
477
+ hd.sym = sym;
478
+ hd.addr = NULL;
479
+ for_each_dso(find_symbol_cb, &hd);
480
+ ret = hd.addr;
481
+ }
482
+
483
+ return ret;
410
484
  }
411
485
 
412
486
  /*
@@ -467,6 +541,8 @@ bin_find_symbol_name(void *sym) {
467
541
  * Given -
468
542
  * trampee - the name of the symbol to hook
469
543
  * tramp - the stage 2 trampoline entry
544
+ * orig_func - out parameter storing the address of the function that was
545
+ * originally called.
470
546
  *
471
547
  * This function will update the ruby binary image so that all calls to trampee
472
548
  * will be routed to tramp.
@@ -474,7 +550,7 @@ bin_find_symbol_name(void *sym) {
474
550
  * Returns 0 on success
475
551
  */
476
552
  int
477
- bin_update_image(const char *trampee, struct tramp_st2_entry *tramp)
553
+ bin_update_image(const char *trampee, struct tramp_st2_entry *tramp, void **orig_func)
478
554
  {
479
555
  void *trampee_addr = NULL;
480
556
 
@@ -482,32 +558,56 @@ bin_update_image(const char *trampee, struct tramp_st2_entry *tramp)
482
558
  assert(tramp != NULL);
483
559
  assert(tramp->addr != NULL);
484
560
 
485
- if (!libruby) {
561
+ /* first check if the symbol is in the PLT */
562
+ trampee_addr = find_plt_addr(trampee, NULL);
563
+
564
+ /* it isn't in the PLT, try to find it in the binary itself */
565
+ if (!trampee_addr) {
566
+ dbg_printf("Couldn't find %s in the PLT...\n", trampee);
486
567
  unsigned char *byte = ruby_info->text_segment;
487
- trampee_addr = bin_find_symbol(trampee, NULL);
568
+ trampee_addr = bin_find_symbol(trampee, NULL, 0);
488
569
  size_t count = 0;
489
570
  int num = 0;
490
571
 
491
572
  assert(byte != NULL);
492
- assert(trampee_addr != NULL);
573
+
574
+ if (!trampee_addr) {
575
+ dbg_printf("WARNING: Couldn't find: %s anywhere, so not tramping!\n", trampee);
576
+ return 0;
577
+ }
578
+
579
+ if (orig_func) {
580
+ *orig_func = trampee_addr;
581
+ }
493
582
 
494
583
  for(; count < ruby_info->text_segment_len; byte++, count++) {
495
584
  if (arch_insert_st1_tramp(byte, trampee_addr, tramp) == 0) {
496
585
  num++;
497
586
  }
498
587
  }
588
+ dbg_printf("Inserted %d tramps for: %s\n", num, trampee);
499
589
  } else {
500
- trampee_addr = find_plt_addr(trampee, NULL);
501
- assert(trampee_addr != NULL);
502
- overwrite_got(trampee_addr, tramp->addr);
503
- }
590
+ void *ret = NULL;
591
+ dbg_printf("Found %s in the PLT, inserting tramp...\n", trampee);
592
+ ret = overwrite_got(trampee_addr, tramp->addr);
504
593
 
505
- if (strcmp("rb_newobj", trampee) == 0) {
506
- dbg_printf("Trying to hook rb_newobj in other libraries...\n");
507
- hook_required_objects(tramp->addr);
508
- dbg_printf("Done searching other libraries for rb_newobj!\n");
594
+ assert(ret != NULL);
595
+
596
+ if (orig_func) {
597
+ *orig_func = ret;
598
+ dbg_printf("setting orig function: %p\n", *orig_func);
599
+ }
509
600
  }
510
601
 
602
+ dbg_printf("Trying to hook %s in other libraries...\n", trampee);
603
+
604
+ struct plt_hook_data data;
605
+ data.addr = tramp->addr;
606
+ data.sym = trampee;
607
+ for_each_dso(hook_required_objects, &data);
608
+
609
+ dbg_printf("Done searching other libraries for %s\n", trampee);
610
+
511
611
  return 0;
512
612
  }
513
613