memprof 0.2.9 → 0.3.0

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