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