memprof 0.2.1 → 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,3 +2,5 @@
2
2
  *.so
3
3
  *.bundle
4
4
  Makefile
5
+ *.dSYM
6
+ *.log
@@ -0,0 +1,9 @@
1
+ task :spec do
2
+ Dir.chdir('ext') do
3
+ sh "make clean" rescue nil
4
+ sh "ruby extconf.rb"
5
+ sh "make"
6
+ end
7
+ sh "ruby spec/memprof_spec.rb"
8
+ end
9
+ task :default => :spec
@@ -1,11 +1,3 @@
1
- if RUBY_PLATFORM =~ /darwin/
2
- STDERR.puts "\n\n"
3
- STDERR.puts "***************************************************************************************"
4
- STDERR.puts "**************************** osx is not supported (yet) =( ****************************"
5
- STDERR.puts "***************************************************************************************"
6
- exit(1)
7
- end
8
-
9
1
  if RUBY_VERSION >= "1.9"
10
2
  STDERR.puts "\n\n"
11
3
  STDERR.puts "***************************************************************************************"
@@ -52,7 +44,11 @@ unless File.exists?("#{CWD}/dst/lib/libyajl_ext.a")
52
44
  sys("#{Config::CONFIG['bindir']}/#{Config::CONFIG['ruby_install_name']} extconf.rb")
53
45
 
54
46
  sys("make")
55
- sys("ar rv libyajl_ext.a #{Dir['*.o'].join(' ')}")
47
+ if RUBY_PLATFORM =~ /darwin/
48
+ sys("libtool -static -o libyajl_ext.a #{Dir['*.o'].join(' ')}")
49
+ else
50
+ sys("ar rv libyajl_ext.a #{Dir['*.o'].join(' ')}")
51
+ end
56
52
 
57
53
  FileUtils.mkdir_p "#{CWD}/dst/lib"
58
54
  FileUtils.cp 'libyajl_ext.a', "#{CWD}/dst/lib"
@@ -142,9 +138,46 @@ if RUBY_PLATFORM =~ /linux/
142
138
  add_define 'HAVE_DWARF'
143
139
  end
144
140
 
145
- if have_header('mach-o/dyld')
141
+ if have_header('mach-o/dyld.h')
146
142
  is_macho = true
147
143
  add_define 'HAVE_MACH'
144
+ # XXX How to determine this properly? RUBY_PLATFORM reports "i686-darwin10.2.0" on Snow Leopard.
145
+ add_define "_ARCH_x86_64_"
146
+
147
+ expressions = [
148
+ [:sizeof__RVALUE, "sizeof(RVALUE)"],
149
+ [:sizeof__heaps_slot, "sizeof(struct heaps_slot)"],
150
+ [:offset__heaps_slot__slot, "(int)&(((struct heaps_slot *)0)->slot)"],
151
+ [:offset__heaps_slot__limit, "(int)&(((struct heaps_slot *)0)->limit)"]
152
+ # "&add_freelist",
153
+ # "&rb_newobj",
154
+ # "&freelist",
155
+ # "&heaps",
156
+ # "&heaps_used"
157
+ ]
158
+
159
+ pid = fork{sleep}
160
+ output = IO.popen('gdb --interpreter=mi --quiet', 'w+') do |io|
161
+ io.puts "attach #{pid}"
162
+ expressions.each do |name, expr|
163
+ io.puts "-data-evaluate-expression #{expr.dump}"
164
+ end
165
+ io.puts 'quit'
166
+ io.puts 'y'
167
+ io.close_write
168
+ io.read
169
+ end
170
+ Process.kill 9, pid
171
+
172
+ attach, *results = output.grep(/^\^/).map{ |l| l.strip }
173
+ if results.find{ |l| l =~ /^\^error/ }
174
+ raise "Unsupported platform: #{results.inspect}"
175
+ end
176
+
177
+ values = results.map{ |l| l[/value="(.+?)"/, 1] }
178
+ vars = Hash[ *expressions.map{|n,e| n }.zip(values).flatten(1) ].each do |name, val|
179
+ add_define "#{name}=#{val}"
180
+ end
148
181
  end
149
182
 
150
183
  arch = RUBY_PLATFORM[/(.*)-linux/,1]
data/ext/mach.c CHANGED
@@ -1,107 +1,335 @@
1
1
  #if defined(HAVE_MACH)
2
2
 
3
3
  #include "bin_api.h"
4
+ #include "arch.h"
4
5
 
5
6
  #include <limits.h>
6
7
  #include <string.h>
7
8
  #include <sysexits.h>
8
9
  #include <sys/mman.h>
10
+ #include <stdio.h>
11
+ #include <sys/stat.h>
12
+ #include <dlfcn.h>
13
+ #include <stdlib.h>
9
14
 
10
15
  #include <mach-o/dyld.h>
11
16
  #include <mach-o/getsect.h>
12
17
  #include <mach-o/loader.h>
13
18
  #include <mach-o/ldsyms.h>
19
+ #include <mach-o/nlist.h>
20
+
21
+ // The jmp instructions in the dyld stub table are 6 bytes,
22
+ // 2 bytes for the instruction and 4 bytes for the offset operand
23
+ //
24
+ // This jmp does not jump to the offset operand, but instead
25
+ // looks up an absolute address stored at the offset and jumps to that.
26
+ // Offset is the offset from the address of the _next_ instruction sequence.
27
+ //
28
+ // We need to deference the address at this offset to find the real
29
+ // target of the dyld stub entry.
30
+
31
+ struct dyld_stub_entry {
32
+ unsigned char jmp[2];
33
+ uint32_t offset;
34
+ } __attribute((__packed__));
35
+
36
+ static inline void*
37
+ get_dyld_stub_target(struct dyld_stub_entry *entry) {
38
+ // If the instructions match up, then dereference the address at the offset
39
+ if (entry->jmp[0] == 0xff && entry->jmp[1] == 0x25)
40
+ return *((void**)((void*)(entry + 1) + entry->offset));
41
+
42
+ return NULL;
43
+ }
44
+
45
+ static inline void
46
+ set_dyld_stub_target(struct dyld_stub_entry *entry, void *addr) {
47
+ *((void**)((void*)(entry + 1) + entry->offset)) = addr;
48
+ }
14
49
 
15
50
  static void
16
- set_text_segment(const struct mach_header *header, const char *sectname)
51
+ update_dyld_stub_table(void *table, uint64_t len, void *trampee_addr, struct tramp_st2_entry *tramp)
17
52
  {
18
- text_segment = getsectdatafromheader_64((const struct mach_header_64*)header, "__TEXT", sectname, (uint64_t*)&text_segment_len);
19
- if (!text_segment)
20
- errx(EX_UNAVAILABLE, "Failed to locate the %s section", sectname);
53
+ struct dyld_stub_entry *entry = (struct dyld_stub_entry*) table;
54
+ void *max_addr = table + len;
55
+
56
+ for(; (void*)entry < max_addr; entry++) {
57
+ void *target = get_dyld_stub_target(entry);
58
+ if (trampee_addr == target) {
59
+ set_dyld_stub_target(entry, tramp->addr);
60
+ }
61
+ }
62
+ }
63
+
64
+
65
+ // This function tells us if the passed stub table address
66
+ // is something that we should try to update (by looking at it's filename)
67
+
68
+ static inline int
69
+ should_update_stub_table(void *addr) {
70
+ // Only try to update dyld stub entries in files that match "libruby.dylib" or "*.bundle" (other C gems)
71
+ Dl_info info;
72
+
73
+ if (dladdr(addr, &info)) {
74
+ size_t len = strlen(info.dli_fname);
75
+
76
+ if (len >= 6) {
77
+ const char *possible_bundle = (info.dli_fname + len - 6);
78
+ if (strcmp(possible_bundle, "bundle") == 0)
79
+ return 1;
80
+ }
81
+
82
+ if (len >= 13) {
83
+ const char *possible_libruby = (info.dli_fname + len - 13);
84
+ if (strcmp(possible_libruby, "libruby.dylib") == 0)
85
+ return 1;
86
+ }
87
+ }
88
+ return 0;
21
89
  }
22
90
 
23
91
  static void
24
- update_dyld_stubs(int entry, void *trampee_addr)
25
- {
26
- char *byte = text_segment;
27
- size_t count = 0;
28
-
29
- for(; count < text_segment_len; count++) {
30
- if (*byte == '\xff') {
31
- int off = *(int *)(byte+2);
32
- if (trampee_addr == *((void**)(byte + 6 + off))) {
33
- *((void**)(byte + 6 + off)) = tramp_table[entry].addr;
92
+ update_mach_section(const struct mach_header *header, const struct section_64 *sect, intptr_t slide, void *trampee_addr, struct tramp_st2_entry *tramp) {
93
+ uint64_t len = 0;
94
+ void *section = getsectdatafromheader_64((const struct mach_header_64*)header, "__TEXT", sect->sectname, &len) + slide;
95
+
96
+ if (strncmp(sect->sectname, "__symbol_stub", 13) == 0) {
97
+ if (should_update_stub_table(section))
98
+ update_dyld_stub_table(section, sect->size, trampee_addr, tramp);
99
+ }
100
+
101
+ if (strcmp(sect->sectname, "__text") == 0) {
102
+ size_t count = 0;
103
+ for(; count < len; section++, count++) {
104
+ if (arch_insert_st1_tramp(section, trampee_addr, tramp)) {
105
+ // printf("tramped %p for %s\n", byte, trampee);
106
+ }
107
+ }
108
+ }
109
+ }
110
+
111
+ static void
112
+ update_bin_for_mach_header(const struct mach_header *header, intptr_t slide, void *trampee_addr, struct tramp_st2_entry *tramp) {
113
+ int i, j;
114
+ int lc_count = header->ncmds;
115
+
116
+ // this as a char* because we need to step it forward by an arbitrary number of bytes
117
+ const char *lc = ((const char*) header) + sizeof(struct mach_header_64);
118
+
119
+ // Check all the load commands in the object to see if they are segment commands
120
+ for (i = 0; i < lc_count; i++, lc += ((struct load_command*)lc)->cmdsize) {
121
+ if (((struct load_command*)lc)->cmd == LC_SEGMENT_64) {
122
+ const struct segment_command_64 *seg = (const struct segment_command_64 *) lc;
123
+ const struct section_64 * sect = (const struct section_64*)(lc + sizeof(struct segment_command_64));
124
+ int section_count = (seg->cmdsize - sizeof(struct segment_command_64)) / sizeof(struct section_64);
125
+
126
+ for (j=0; j < section_count; j++, sect++) {
127
+ update_mach_section(header, sect, slide, trampee_addr, tramp);
34
128
  }
35
129
  }
36
- byte++;
37
130
  }
38
131
  }
39
132
 
133
+
134
+ // This function takes a pointer to a loaded *in memory* mach_header_64
135
+ // and returns it's image index. This will NOT work for mach objects read
136
+ // from a file.
137
+
138
+ int
139
+ find_dyld_image_index(const struct mach_header_64 *hdr) {
140
+ int i;
141
+
142
+ for (i = 0; i < _dyld_image_count(); i++) {
143
+ const struct mach_header_64 *tmphdr = (const struct mach_header_64*) _dyld_get_image_header(i);
144
+ if (hdr == tmphdr)
145
+ return i;
146
+ }
147
+ errx(EX_SOFTWARE, "Could not find image index");
148
+ }
149
+
150
+
151
+ // This function returns a buffer containing the file that is presumed
152
+ // to be either the Ruby executable or libruby. (Wherever rb_newobj is found.)
153
+ //
154
+ // The passed pointer index is set to the image index for the associated
155
+ // in-memory mach image.
156
+ //
157
+ // !!! The pointer returned by this function must be freed !!!
158
+
40
159
  void *
41
- bin_allocate_page()
42
- {
43
- void *ret = NULL;
44
- size_t i = 0;
160
+ get_ruby_file_and_header_index(int *index) {
161
+ void *ptr = NULL;
162
+ void *buf = NULL;
163
+ Dl_info info;
164
+ struct stat filestat;
45
165
 
46
- for (i = pagesize; i < INT_MAX - pagesize; i += pagesize) {
47
- ret = mmap((void*)(NULL + i), pagesize, PROT_WRITE|PROT_READ|PROT_EXEC,
48
- MAP_ANON|MAP_PRIVATE, -1, 0);
166
+ // We can use this is a reasonably sure method of finding the file
167
+ // that the Ruby junk resides in.
168
+ ptr = dlsym(RTLD_DEFAULT, "rb_newobj");
49
169
 
50
- if (tramp_table != MAP_FAILED) {
51
- memset(tramp_table, 0x90, pagesize);
52
- return ret;
170
+ if (!ptr)
171
+ errx(EX_SOFTWARE, "Could not find rb_newobj in this process. WTF???");
172
+
173
+ if (!dladdr(ptr, &info) || !info.dli_fname)
174
+ errx(EX_SOFTWARE, "Could not find the Mach object associated with rb_newobj.");
175
+
176
+ FILE *file = fopen(info.dli_fname, "r");
177
+ if (!file)
178
+ errx(EX_OSFILE, "Failed to open Ruby file %s", info.dli_fname);
179
+
180
+ stat(info.dli_fname, &filestat);
181
+ buf = malloc(filestat.st_size);
182
+
183
+ if (fread(buf, filestat.st_size, 1, file) != 1)
184
+ errx(EX_OSFILE, "Failed to fread() Ruby file %s", info.dli_fname);
185
+
186
+ fclose(file);
187
+
188
+ *index = find_dyld_image_index((const struct mach_header_64*) info.dli_fbase);
189
+ return buf;
190
+ }
191
+
192
+
193
+ // This function compares two nlist_64 structures by their n_value field (address)
194
+ // used by qsort in build_sorted_nlist_table
195
+
196
+ int
197
+ nlist_cmp(const void *obj1, const void *obj2) {
198
+ const struct nlist_64 *nlist1 = *(const struct nlist_64**) obj1;
199
+ const struct nlist_64 *nlist2 = *(const struct nlist_64**) obj2;
200
+
201
+ if (nlist1->n_value == nlist2->n_value)
202
+ return 0;
203
+ else if (nlist1->n_value < nlist2->n_value)
204
+ return -1;
205
+ else
206
+ return 1;
207
+ }
208
+
209
+
210
+ // This function returns an array of pointers to nlist_64 entries in the symbol table
211
+ // of the file pointed to by the passed mach header, sorted by address, and sets the
212
+ // passed uint32_t pointers nsyms and stroff to those fields found in the symtab_command structure.
213
+ //
214
+ // !!! The pointer returned by this function must be freed !!!
215
+
216
+ const struct nlist_64 **
217
+ build_sorted_nlist_table(const struct mach_header_64 *hdr, uint32_t *nsyms, uint32_t *stroff) {
218
+ const struct nlist_64 **base;
219
+ uint32_t i, j;
220
+
221
+ const char *lc = (const char*) hdr + sizeof(struct mach_header_64);
222
+
223
+ for (i = 0; i < hdr->ncmds; i++) {
224
+ if (((const struct load_command*)lc)->cmd == LC_SYMTAB) {
225
+ const struct symtab_command *sc = (const struct symtab_command*) lc;
226
+ const struct nlist_64 *symbol_table = (const struct nlist_64*)((const char*)hdr + sc->symoff);
227
+
228
+ base = malloc(sc->nsyms * sizeof(struct nlist_64*));
229
+
230
+ for (j = 0; j < sc->nsyms; j++)
231
+ base[j] = symbol_table + j;
232
+
233
+ qsort(base, sc->nsyms, sizeof(struct nlist_64*), &nlist_cmp);
234
+
235
+ *nsyms = sc->nsyms;
236
+ *stroff = sc->stroff;
237
+ return base;
53
238
  }
239
+
240
+ lc += ((const struct load_command*)lc)->cmdsize;
54
241
  }
55
- return NULL;
242
+ errx(EX_SOFTWARE, "Unable to find LC_SYMTAB");
243
+ }
244
+
245
+ void *
246
+ bin_find_symbol(char *symbol, size_t *size) {
247
+ // Correctly prefix the symbol with a '_' (whats a prettier way to do this?)
248
+ size_t len = strlen(symbol);
249
+ char real_symbol[len + 2];
250
+ memcpy(real_symbol, "_", 1);
251
+ memcpy((real_symbol + 1), symbol, len);
252
+ memcpy((real_symbol + len + 1), "\0", 1);
253
+
254
+ void *ptr = NULL;
255
+ void *file = NULL;
256
+
257
+ uint32_t i, j, k;
258
+ uint32_t stroff, nsyms = 0;
259
+ int index = 0;
260
+
261
+ file = get_ruby_file_and_header_index(&index);
262
+
263
+ const struct mach_header_64 *hdr = (const struct mach_header_64*) file;
264
+ if (hdr->magic != MH_MAGIC_64)
265
+ errx(EX_SOFTWARE, "Magic for Ruby Mach-O file doesn't match");
266
+
267
+ const struct nlist_64 **nlist_table = build_sorted_nlist_table(hdr, &nsyms, &stroff);
268
+ const char *string_table = (const char*)hdr + stroff;
269
+
270
+ for (i=0; i < nsyms; i++) {
271
+ const struct nlist_64 *nlist_entry = nlist_table[i];
272
+ const char *string = string_table + nlist_entry->n_un.n_strx;
273
+
274
+ if (strcmp(real_symbol, string) == 0) {
275
+ const uint64_t addr = nlist_entry->n_value;
276
+ ptr = (void*)(addr + _dyld_get_image_vmaddr_slide(index));
277
+
278
+ if (size) {
279
+ const struct nlist_64 *next_entry = NULL;
280
+
281
+ j = 1;
282
+ while (next_entry == NULL) {
283
+ const struct nlist_64 *tmp_entry = nlist_table[i + j];
284
+ if (nlist_entry->n_value != tmp_entry->n_value)
285
+ next_entry = tmp_entry;
286
+ j++;
287
+ }
288
+
289
+ *size = (next_entry->n_value - addr);
290
+ }
291
+ break;
292
+ }
293
+ }
294
+
295
+ free(nlist_table);
296
+ free(file);
297
+ return ptr;
56
298
  }
57
299
 
58
300
  void
59
- bin_update_image(int entry, void *trampee_addr)
301
+ bin_update_image(int entry, char *trampee, struct tramp_st2_entry *tramp)
60
302
  {
61
- int i, j, k;
303
+ int i;
62
304
  int header_count = _dyld_image_count();
305
+ void *trampee_addr = bin_find_symbol(trampee, NULL);
63
306
 
64
307
  // Go through all the mach objects that are loaded into this process
65
308
  for (i=0; i < header_count; i++) {
66
309
  const struct mach_header *current_hdr = _dyld_get_image_header(i);
310
+ if ((void*)current_hdr == &_mh_bundle_header)
311
+ continue;
67
312
 
68
- // Modify any callsites residing inside the text segment
69
- set_text_segment(current_hdr, "__text");
70
- text_segment += _dyld_get_image_vmaddr_slide(i);
71
- update_callqs(entry, trampee_addr);
72
-
73
- int lc_count = current_hdr->ncmds;
74
-
75
- // this as a char* because we need to step it forward by an arbitrary number of bytes
76
- const char *lc = ((const char*) current_hdr) + sizeof(struct mach_header_64);
77
-
78
- // Check all the load commands in the object to see if they are segment commands
79
- for (j = 0; j < lc_count; j++) {
80
- if (((struct load_command*)lc)->cmd == LC_SEGMENT_64) {
81
- const struct segment_command_64 *seg = (const struct segment_command_64 *) lc;
82
- const struct section_64 * sect = (const struct section_64*)(lc + sizeof(struct segment_command_64));
83
- int section_count = (seg->cmdsize - sizeof(struct segment_command_64)) / sizeof(struct section_64);
84
-
85
- // Search the segment for a section containing dyld stub functions
86
- for (k=0; k < section_count; k++) {
87
- if (strncmp(sect->sectname, "__symbol_stub", 13) == 0) {
88
- set_text_segment((struct mach_header*)current_hdr, sect->sectname);
89
- text_segment += _dyld_get_image_vmaddr_slide(i);
90
- update_dyld_stubs(entry, trampee_addr);
91
- }
92
- sect++;
93
- }
94
- }
95
- lc += ((struct load_command*)lc)->cmdsize;
96
- }
313
+ update_bin_for_mach_header(current_hdr, _dyld_get_image_vmaddr_slide(i), trampee_addr, tramp);
97
314
  }
98
315
  }
99
316
 
100
- void *
101
- bin_find_symbol(char *sym, size_t *size) {
102
- void *ptr = NULL;
103
- _dyld_lookup_and_bind((const char*)sym, &ptr, NULL);
104
- return ptr;
317
+ void *
318
+ bin_allocate_page()
319
+ {
320
+ void *ret = NULL;
321
+ size_t i = 0;
322
+
323
+ for (i = pagesize; i < INT_MAX - pagesize; i += pagesize) {
324
+ ret = mmap((void*)(NULL + i), pagesize, PROT_WRITE|PROT_READ|PROT_EXEC,
325
+ MAP_ANON|MAP_PRIVATE, -1, 0);
326
+
327
+ if (ret != MAP_FAILED) {
328
+ memset(ret, 0x90, pagesize);
329
+ return ret;
330
+ }
331
+ }
332
+ return NULL;
105
333
  }
106
334
 
107
335
  int
@@ -14,6 +14,7 @@
14
14
  #include <sysexits.h>
15
15
  #include <sys/mman.h>
16
16
  #include <err.h>
17
+ #include <assert.h>
17
18
 
18
19
  #include <st.h>
19
20
  #include <intern.h>
@@ -83,10 +84,17 @@ newobj_tramp()
83
84
  return ret;
84
85
  }
85
86
 
87
+ static void (*rb_add_freelist)(VALUE);
88
+
86
89
  static void
87
90
  freelist_tramp(unsigned long rval)
88
91
  {
89
92
  struct obj_track *tracker = NULL;
93
+
94
+ if (rb_add_freelist) {
95
+ rb_add_freelist(rval);
96
+ }
97
+
90
98
  if (track_objs && objs) {
91
99
  st_delete(objs, (st_data_t *) &rval, (st_data_t *) &tracker);
92
100
  if (tracker) {
@@ -390,9 +398,11 @@ obj_dump(VALUE obj, yajl_gen gen)
390
398
  yajl_gen_value(gen, obj);
391
399
 
392
400
  struct obj_track *tracker = NULL;
393
- if (st_lookup(objs, (st_data_t)obj, (st_data_t *)&tracker)) {
394
- yajl_gen_cstr(gen, "source");
395
- yajl_gen_format(gen, "%s:%d", tracker->source, tracker->line);
401
+ if (st_lookup(objs, (st_data_t)obj, (st_data_t *)&tracker) && BUILTIN_TYPE(obj) != T_NODE) {
402
+ yajl_gen_cstr(gen, "file");
403
+ yajl_gen_cstr(gen, tracker->source);
404
+ yajl_gen_cstr(gen, "line");
405
+ yajl_gen_integer(gen, tracker->line);
396
406
  }
397
407
 
398
408
  yajl_gen_cstr(gen, "type");
@@ -716,16 +726,26 @@ memprof_dump(int argc, VALUE *argv, VALUE self)
716
726
  static VALUE
717
727
  memprof_dump_all(int argc, VALUE *argv, VALUE self)
718
728
  {
719
- int sizeof_RVALUE = bin_type_size("RVALUE");
720
729
  char *heaps = *(char**)bin_find_symbol("heaps",0);
721
730
  int heaps_used = *(int*)bin_find_symbol("heaps_used",0);
722
- int sizeof_heaps_slot = bin_type_size("heaps_slot");
723
- int offset_limit = bin_type_member_offset("heaps_slot", "limit");
724
- int offset_slot = bin_type_member_offset("heaps_slot", "slot");
731
+
732
+ #ifndef sizeof__RVALUE
733
+ int sizeof__RVALUE = bin_type_size("RVALUE");
734
+ #endif
735
+ #ifndef sizeof__heaps_slot
736
+ int sizeof__heaps_slot = bin_type_size("heaps_slot");
737
+ #endif
738
+ #ifndef offset__heaps_slot__limit
739
+ int offset__heaps_slot__limit = bin_type_member_offset("heaps_slot", "limit");
740
+ #endif
741
+ #ifndef offset__heaps_slot__slot
742
+ int offset__heaps_slot__slot = bin_type_member_offset("heaps_slot", "slot");
743
+ #endif
744
+
725
745
  char *p, *pend;
726
746
  int i, limit;
727
747
 
728
- if (sizeof_RVALUE < 0 || sizeof_heaps_slot < 0)
748
+ if (sizeof__RVALUE < 0 || sizeof__heaps_slot < 0)
729
749
  rb_raise(eUnsupported, "could not find internal heap");
730
750
 
731
751
  VALUE str;
@@ -747,9 +767,9 @@ memprof_dump_all(int argc, VALUE *argv, VALUE self)
747
767
  //yajl_gen_array_open(gen);
748
768
 
749
769
  for (i=0; i < heaps_used; i++) {
750
- p = *(char**)(heaps + (i * sizeof_heaps_slot) + offset_slot);
751
- limit = *(int*)(heaps + (i * sizeof_heaps_slot) + offset_limit);
752
- pend = p + (sizeof_RVALUE * limit);
770
+ p = *(char**)(heaps + (i * sizeof__heaps_slot) + offset__heaps_slot__slot);
771
+ limit = *(int*)(heaps + (i * sizeof__heaps_slot) + offset__heaps_slot__limit);
772
+ pend = p + (sizeof__RVALUE * limit);
753
773
 
754
774
  while (p < pend) {
755
775
  if (RBASIC(p)->flags) {
@@ -761,7 +781,7 @@ memprof_dump_all(int argc, VALUE *argv, VALUE self)
761
781
  while(fputc('\n', out ? out : stdout) == EOF);
762
782
  }
763
783
 
764
- p += sizeof_RVALUE;
784
+ p += sizeof__RVALUE;
765
785
  }
766
786
  }
767
787
 
@@ -813,16 +833,19 @@ hook_freelist(int entry)
813
833
  void *freelist_inliners[FREELIST_INLINES];
814
834
  void *freelist = NULL;
815
835
  unsigned char *byte = NULL;
836
+ int tramps_completed = 0;
816
837
 
817
838
  freelist_inliners[0] = bin_find_symbol("gc_sweep", &sizes[0]);
818
839
  /* sometimes gc_sweep gets inlined in garbage_collect */
819
- if (!freelist_inliners[0]) {
840
+ /* on REE, it gets inlined into garbage_collect_0 */
841
+ if (!freelist_inliners[0])
842
+ freelist_inliners[0] = bin_find_symbol("garbage_collect_0", &sizes[0]);
843
+ if (!freelist_inliners[0])
820
844
  freelist_inliners[0] = bin_find_symbol("garbage_collect", &sizes[0]);
821
- if (!freelist_inliners[0]) {
822
- /* couldn't find garbage_collect either. */
823
- fprintf(stderr, "Couldn't find gc_sweep or garbage_collect!\n");
824
- return;
825
- }
845
+ if (!freelist_inliners[0]) {
846
+ /* couldn't find anything containing gc_sweep. */
847
+ fprintf(stderr, "Couldn't find gc_sweep or garbage_collect!\n");
848
+ return;
826
849
  }
827
850
 
828
851
  freelist_inliners[1] = bin_find_symbol("finalize_list", &sizes[1]);
@@ -853,6 +876,15 @@ hook_freelist(int entry)
853
876
  /* insert occurred, so increment internal counters for the tramp table */
854
877
  entry++;
855
878
  inline_tramp_size++;
879
+
880
+ /* add_freelist() only gets inlined *ONCE* into any of the 3 functions that we're scanning, */
881
+ /* so move on to the next 'inliner' when after we tramp the first instruction we find. */
882
+ /* REE's gc_sweep has 2 calls, but this gets optimized into a single inlining and a jmp to it */
883
+ /* older patchlevels of 1.8.7 don't have an add_freelist(), but the instruction should be the same */
884
+ tramps_completed++;
885
+ i++;
886
+ byte = freelist_inliners[i];
887
+ continue;
856
888
  }
857
889
 
858
890
  /* if we've looked at all the bytes in this function... */
@@ -863,6 +895,8 @@ hook_freelist(int entry)
863
895
  }
864
896
  byte++;
865
897
  }
898
+
899
+ assert(tramps_completed == 3);
866
900
  }
867
901
 
868
902
  static void
@@ -881,6 +915,10 @@ insert_tramp(char *trampee, void *tramp)
881
915
  return;
882
916
  }
883
917
  } else {
918
+ if (strcmp("add_freelist", trampee) == 0) {
919
+ rb_add_freelist = trampee_addr;
920
+ }
921
+
884
922
  tramp_table[tramp_size].addr = tramp;
885
923
  bin_update_image(entry, trampee, &tramp_table[tramp_size]);
886
924
  tramp_size++;
@@ -904,13 +942,10 @@ Init_memprof()
904
942
  objs = st_init_numtable();
905
943
  bin_init();
906
944
  create_tramp_table();
945
+ rb_add_freelist = NULL;
907
946
 
908
- #if defined(HAVE_MACH)
909
- insert_tramp("_rb_newobj", newobj_tramp);
910
- #elif defined(HAVE_ELF)
911
947
  insert_tramp("rb_newobj", newobj_tramp);
912
948
  insert_tramp("add_freelist", freelist_tramp);
913
- #endif
914
949
 
915
950
  rb_classname = bin_find_symbol("classname", 0);
916
951
 
@@ -0,0 +1,18 @@
1
+ require File.expand_path('../../memprof', __FILE__)
2
+ module Memprof
3
+ class Middleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+ def call(env)
8
+ ret = nil
9
+ Memprof.track{
10
+ ret = @app.call(env)
11
+ puts
12
+ puts '-' * 80
13
+ puts '-' * 80
14
+ }
15
+ ret
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ require File.expand_path('../../memprof', __FILE__)
2
+ Memprof.start
3
+ trap('USR2'){
4
+ pid = Process.pid
5
+ fork{
6
+ Memprof.dump_all("/tmp/memprof-#{pid}-#{Time.now.to_i}.json")
7
+ exit!
8
+ }
9
+ }
@@ -1,32 +1,13 @@
1
1
  spec = Gem::Specification.new do |s|
2
2
  s.name = 'memprof'
3
- s.version = '0.2.1'
4
- s.date = '2010-02-20'
3
+ s.version = '0.2.5'
4
+ s.date = '2010-02-27'
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
- s.email = "ice799@gmail.com"
8
7
  s.homepage = "http://github.com/ice799/memprof"
9
8
  s.has_rdoc = false
10
9
  s.authors = ["Joe Damato", "Aman Gupta", "Jake Douglas"]
10
+ s.email = ["joe@memprof.com", "aman@memprof.com", "jake@memprof.com"]
11
11
  s.extensions = "ext/extconf.rb"
12
- s.files = %w[
13
- .gitignore
14
- README
15
- ext/arch.h
16
- ext/bin_api.h
17
- ext/elf.c
18
- ext/extconf.rb
19
- ext/i386.c
20
- ext/i386.h
21
- ext/mach.c
22
- ext/memprof.c
23
- ext/src/libdwarf-20091118.tar.gz
24
- ext/src/libelf-0.8.13.tar.gz
25
- ext/src/yajl-1.0.9.tar.gz
26
- ext/x86_64.c
27
- ext/x86_64.h
28
- ext/x86_gen.h
29
- memprof.gemspec
30
- spec/memprof_spec.rb
31
- ]
12
+ s.files = `git ls-files`.split
32
13
  end
@@ -61,7 +61,8 @@ describe Memprof do
61
61
  1.23+1
62
62
  Memprof.dump(filename)
63
63
 
64
- filedata.should =~ /"source": "#{__FILE__}:#{__LINE__-3}"/
64
+ filedata.should =~ /"file": "#{__FILE__}"/
65
+ filedata.should =~ /"line": #{__LINE__-4}/
65
66
  filedata.should =~ /"type": "float"/
66
67
  filedata.should =~ /"data": 2\.23/
67
68
  end
@@ -75,11 +76,27 @@ describe Memprof do
75
76
  Memprof.stop
76
77
  Memprof.dump_all(filename)
77
78
 
78
- File.open(filename, 'r').each_line do |line|
79
- if line =~ /"data": "dump out the entire heap"/
80
- break :found
81
- end
82
- end.should == :found
79
+ obj = File.open(filename, 'r').each_line.find do |line|
80
+ line =~ /"dump out the entire heap"/
81
+ end
82
+
83
+ obj.should =~ /"length":24/
84
+ obj.should =~ /"type":"string"/
85
+ obj.should =~ /"_id":"0x(\w+?)"/
86
+ end
87
+
88
+ should 'dump out the entire heap with tracking info' do
89
+ Memprof.start
90
+ @str = "some random" + " string"
91
+ Memprof.dump_all(filename)
92
+
93
+ obj = File.open(filename, 'r').each_line.find do |line|
94
+ line =~ /"some random string"/
95
+ end
96
+
97
+ obj.should =~ /"type":"string"/
98
+ obj.should =~ /"file":".+?memprof_spec.rb"/
99
+ obj.should =~ /"line":#{__LINE__-9}/
83
100
  end
84
101
  end
85
102
 
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memprof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 5
9
+ version: 0.2.5
5
10
  platform: ruby
6
11
  authors:
7
12
  - Joe Damato
@@ -11,12 +16,15 @@ autorequire:
11
16
  bindir: bin
12
17
  cert_chain: []
13
18
 
14
- date: 2010-02-20 00:00:00 -08:00
19
+ date: 2010-02-27 00:00:00 -08:00
15
20
  default_executable:
16
21
  dependencies: []
17
22
 
18
23
  description: Ruby memory profiler similar to bleak_house, but without patches to the Ruby VM
19
- email: ice799@gmail.com
24
+ email:
25
+ - joe@memprof.com
26
+ - aman@memprof.com
27
+ - jake@memprof.com
20
28
  executables: []
21
29
 
22
30
  extensions:
@@ -26,6 +34,7 @@ extra_rdoc_files: []
26
34
  files:
27
35
  - .gitignore
28
36
  - README
37
+ - Rakefile
29
38
  - ext/arch.h
30
39
  - ext/bin_api.h
31
40
  - ext/elf.c
@@ -40,6 +49,8 @@ files:
40
49
  - ext/x86_64.c
41
50
  - ext/x86_64.h
42
51
  - ext/x86_gen.h
52
+ - lib/memprof/middleware.rb
53
+ - lib/memprof/usr2.rb
43
54
  - memprof.gemspec
44
55
  - spec/memprof_spec.rb
45
56
  has_rdoc: true
@@ -55,18 +66,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
55
66
  requirements:
56
67
  - - ">="
57
68
  - !ruby/object:Gem::Version
69
+ segments:
70
+ - 0
58
71
  version: "0"
59
- version:
60
72
  required_rubygems_version: !ruby/object:Gem::Requirement
61
73
  requirements:
62
74
  - - ">="
63
75
  - !ruby/object:Gem::Version
76
+ segments:
77
+ - 0
64
78
  version: "0"
65
- version:
66
79
  requirements: []
67
80
 
68
81
  rubyforge_project:
69
- rubygems_version: 1.3.5
82
+ rubygems_version: 1.3.6
70
83
  signing_key:
71
84
  specification_version: 3
72
85
  summary: Ruby Memory Profiler