memprof 0.2.1 → 0.2.5

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
@@ -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