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 +2 -0
- data/Rakefile +9 -0
- data/ext/extconf.rb +43 -10
- data/ext/mach.c +290 -62
- data/ext/memprof.c +57 -22
- data/lib/memprof/middleware.rb +18 -0
- data/lib/memprof/usr2.rb +9 -0
- data/memprof.gemspec +4 -23
- data/spec/memprof_spec.rb +23 -6
- metadata +19 -6
data/Rakefile
ADDED
data/ext/extconf.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
51
|
+
update_dyld_stub_table(void *table, uint64_t len, void *trampee_addr, struct tramp_st2_entry *tramp)
|
17
52
|
{
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
42
|
-
|
43
|
-
void *
|
44
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
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,
|
301
|
+
bin_update_image(int entry, char *trampee, struct tramp_st2_entry *tramp)
|
60
302
|
{
|
61
|
-
int i
|
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
|
-
|
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
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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
|
data/ext/memprof.c
CHANGED
@@ -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, "
|
395
|
-
|
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
|
-
|
723
|
-
|
724
|
-
int
|
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 (
|
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 *
|
751
|
-
limit = *(int*)(heaps + (i *
|
752
|
-
pend = p + (
|
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 +=
|
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
|
-
|
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
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
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
|
data/lib/memprof/usr2.rb
ADDED
data/memprof.gemspec
CHANGED
@@ -1,32 +1,13 @@
|
|
1
1
|
spec = Gem::Specification.new do |s|
|
2
2
|
s.name = 'memprof'
|
3
|
-
s.version = '0.2.
|
4
|
-
s.date = '2010-02-
|
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 =
|
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
|
data/spec/memprof_spec.rb
CHANGED
@@ -61,7 +61,8 @@ describe Memprof do
|
|
61
61
|
1.23+1
|
62
62
|
Memprof.dump(filename)
|
63
63
|
|
64
|
-
filedata.should =~ /"
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
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-
|
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:
|
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.
|
82
|
+
rubygems_version: 1.3.6
|
70
83
|
signing_key:
|
71
84
|
specification_version: 3
|
72
85
|
summary: Ruby Memory Profiler
|