memprof 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/README +41 -8
- data/ext/bin_api.h +83 -0
- data/ext/elf.c +106 -0
- data/ext/extconf.rb +7 -2
- data/ext/mach.c +108 -0
- data/ext/memprof.c +403 -111
- data/memprof.gemspec +14 -9
- metadata +10 -7
data/.gitignore
ADDED
data/README
CHANGED
@@ -2,17 +2,50 @@ memprof (c) Joe Damato @joedamato http://timetobleed.com
|
|
2
2
|
|
3
3
|
What is memprof?
|
4
4
|
================
|
5
|
+
Memprof is a memory profiler for Ruby that requires no patches to the Ruby VM.
|
6
|
+
It can help you find Ruby level memory leaks in your application.
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
+
How to use
|
9
|
+
==========
|
8
10
|
|
11
|
+
require 'memprof'
|
12
|
+
Memprof.start
|
9
13
|
|
10
|
-
|
11
|
-
=====================
|
14
|
+
# ruby code
|
12
15
|
|
13
|
-
|
16
|
+
Memprof.stop
|
17
|
+
Memprof.dump
|
14
18
|
|
15
|
-
|
16
|
-
|
19
|
+
That will monitor allocations and frees that happen between "start" and "stop"
|
20
|
+
and will output information to standard error.
|
17
21
|
|
18
|
-
|
22
|
+
Memprof.dump also takes an (optional) file name to write the output to a file.
|
23
|
+
|
24
|
+
Supported systems
|
25
|
+
=================
|
26
|
+
Currently supporting:
|
27
|
+
|
28
|
+
Linux:
|
29
|
+
x86_64 builds of Ruby Enterprise Edition 1.8.6/1.8.7
|
30
|
+
x86_64 builds of MRI Ruby if built with --disable-shared
|
31
|
+
|
32
|
+
Experimental support:
|
33
|
+
|
34
|
+
Snow Leopard:
|
35
|
+
x86_64 builds of MRI (both enable-shared and disable-shared)
|
36
|
+
|
37
|
+
Coming soon:
|
38
|
+
|
39
|
+
Official support for Snow Leopard.
|
40
|
+
|
41
|
+
Linux:
|
42
|
+
Tracking object allocationns in C extensions.
|
43
|
+
x86_64 builds of MRI Ruby with --enable-shared
|
44
|
+
|
45
|
+
i386/i686 support for all the above.
|
46
|
+
|
47
|
+
CREDITS
|
48
|
+
=======
|
49
|
+
Jake Douglas for the Mach O/snow leopard support.
|
50
|
+
|
51
|
+
Aman Gupta for various bug fixes and other cleanup.
|
data/ext/bin_api.h
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
#if !defined(BIN_API__)
|
2
|
+
#define BIN_API__
|
3
|
+
#include <stddef.h>
|
4
|
+
#include <stdint.h>
|
5
|
+
|
6
|
+
/* generic file format stuff */
|
7
|
+
extern void *text_segment;
|
8
|
+
extern unsigned long text_segment_len;
|
9
|
+
extern size_t pagesize;
|
10
|
+
|
11
|
+
/*
|
12
|
+
* trampoline specific stuff
|
13
|
+
*/
|
14
|
+
extern struct tramp_tbl_entry *tramp_table;
|
15
|
+
extern size_t tramp_size;
|
16
|
+
|
17
|
+
/*
|
18
|
+
* inline trampoline specific stuff
|
19
|
+
*/
|
20
|
+
extern size_t inline_tramp_size;
|
21
|
+
extern struct inline_tramp_tbl_entry *inline_tramp_table;
|
22
|
+
|
23
|
+
/* trampoline types */
|
24
|
+
struct tramp_inline {
|
25
|
+
unsigned char jmp[1];
|
26
|
+
uint32_t displacement;
|
27
|
+
unsigned char pad[2];
|
28
|
+
} __attribute__((__packed__));
|
29
|
+
|
30
|
+
struct tramp_tbl_entry {
|
31
|
+
unsigned char rbx_save[1];
|
32
|
+
unsigned char mov[2];
|
33
|
+
void *addr;
|
34
|
+
unsigned char callq[2];
|
35
|
+
unsigned char rbx_restore[1];
|
36
|
+
unsigned char ret[1];
|
37
|
+
} __attribute__((__packed__));
|
38
|
+
|
39
|
+
struct inline_tramp_tbl_entry {
|
40
|
+
unsigned char rex[1];
|
41
|
+
unsigned char mov[1];
|
42
|
+
unsigned char src_reg[1];
|
43
|
+
uint32_t mov_displacement;
|
44
|
+
|
45
|
+
struct {
|
46
|
+
unsigned char push_rdi[1];
|
47
|
+
unsigned char mov_rdi[3];
|
48
|
+
uint32_t rdi_source_displacement;
|
49
|
+
unsigned char push_rbx[1];
|
50
|
+
unsigned char push_rbp[1];
|
51
|
+
unsigned char save_rsp[3];
|
52
|
+
unsigned char align_rsp[4];
|
53
|
+
unsigned char mov[2];
|
54
|
+
void *addr;
|
55
|
+
unsigned char callq[2];
|
56
|
+
unsigned char leave[1];
|
57
|
+
unsigned char rbx_restore[1];
|
58
|
+
unsigned char rdi_restore[1];
|
59
|
+
} __attribute__((__packed__)) frame;
|
60
|
+
|
61
|
+
unsigned char jmp[1];
|
62
|
+
uint32_t jmp_displacement;
|
63
|
+
} __attribute__((__packed__));
|
64
|
+
|
65
|
+
void
|
66
|
+
update_callqs(int entry, void *trampee_addr);
|
67
|
+
|
68
|
+
/*
|
69
|
+
* EXPORTED API.
|
70
|
+
*/
|
71
|
+
void
|
72
|
+
bin_init();
|
73
|
+
|
74
|
+
void *
|
75
|
+
bin_find_symbol(char *sym, size_t *size);
|
76
|
+
|
77
|
+
void
|
78
|
+
bin_update_image(int entry, void *trampee_addr);
|
79
|
+
|
80
|
+
void *
|
81
|
+
bin_allocate_page();
|
82
|
+
|
83
|
+
#endif
|
data/ext/elf.c
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
#if defined(HAVE_ELF)
|
2
|
+
|
3
|
+
#include "bin_api.h"
|
4
|
+
|
5
|
+
#include <stdio.h>
|
6
|
+
#include <fcntl.h>
|
7
|
+
#include <gelf.h>
|
8
|
+
#include <link.h>
|
9
|
+
#include <sysexits.h>
|
10
|
+
#include <unistd.h>
|
11
|
+
|
12
|
+
#include <sys/mman.h>
|
13
|
+
|
14
|
+
static ElfW(Shdr) symtab_shdr;
|
15
|
+
static Elf *elf = NULL;
|
16
|
+
static Elf_Data *symtab_data = NULL;
|
17
|
+
|
18
|
+
void *
|
19
|
+
bin_allocate_page()
|
20
|
+
{
|
21
|
+
return mmap(NULL, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANON|MAP_PRIVATE|MAP_32BIT, -1, 0);
|
22
|
+
}
|
23
|
+
|
24
|
+
void
|
25
|
+
bin_update_image(int entry, void *trampee_addr)
|
26
|
+
{
|
27
|
+
update_callqs(entry, trampee_addr);
|
28
|
+
}
|
29
|
+
|
30
|
+
void *
|
31
|
+
bin_find_symbol(char *sym, size_t *size)
|
32
|
+
{
|
33
|
+
char *name = NULL;
|
34
|
+
|
35
|
+
/*now print the symbols*/
|
36
|
+
ElfW(Sym) *esym = (ElfW(Sym)*) symtab_data->d_buf;
|
37
|
+
ElfW(Sym) *lastsym = (ElfW(Sym)*) ((char*) symtab_data->d_buf + symtab_data->d_size);
|
38
|
+
/* now loop through the symbol table and print it*/
|
39
|
+
for (; esym < lastsym; esym++){
|
40
|
+
if ((esym->st_value == 0) ||
|
41
|
+
(ELF32_ST_BIND(esym->st_info)== STB_WEAK) ||
|
42
|
+
(ELF32_ST_BIND(esym->st_info)== STB_NUM))
|
43
|
+
continue;
|
44
|
+
name = elf_strptr(elf, symtab_shdr.sh_link, (size_t)esym->st_name);
|
45
|
+
if (strcmp(name, sym) == 0) {
|
46
|
+
if (size) {
|
47
|
+
*size = esym->st_size;
|
48
|
+
}
|
49
|
+
return (void *)esym->st_value;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
return NULL;
|
53
|
+
}
|
54
|
+
|
55
|
+
|
56
|
+
void
|
57
|
+
bin_init()
|
58
|
+
{
|
59
|
+
int fd;
|
60
|
+
ElfW(Shdr) shdr;
|
61
|
+
size_t shstrndx;
|
62
|
+
char *filename;
|
63
|
+
Elf_Scn *scn;
|
64
|
+
|
65
|
+
if (elf_version(EV_CURRENT) == EV_NONE)
|
66
|
+
errx(EX_SOFTWARE, "ELF library initialization failed: %s",
|
67
|
+
elf_errmsg(-1));
|
68
|
+
|
69
|
+
asprintf(&filename, "/proc/%ld/exe", (long)getpid());
|
70
|
+
|
71
|
+
if ((fd = open(filename, O_RDONLY, 0)) < 0)
|
72
|
+
err(EX_NOINPUT, "open \%s\" failed", filename);
|
73
|
+
|
74
|
+
if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
|
75
|
+
errx(EX_SOFTWARE, "elf_begin() failed: %s.",
|
76
|
+
elf_errmsg(-1));
|
77
|
+
|
78
|
+
if (elf_kind(elf) != ELF_K_ELF)
|
79
|
+
errx(EX_DATAERR, "%s is not an ELF object.", filename);
|
80
|
+
|
81
|
+
if (elf_getshstrndx(elf, &shstrndx) == 0)
|
82
|
+
errx(EX_SOFTWARE, "getshstrndx() failed: %s.",
|
83
|
+
elf_errmsg(-1));
|
84
|
+
|
85
|
+
scn = NULL;
|
86
|
+
|
87
|
+
while ((scn = elf_nextscn(elf, scn)) != NULL) {
|
88
|
+
if (gelf_getshdr(scn, &shdr) != &shdr)
|
89
|
+
errx(EX_SOFTWARE, "getshdr() failed: %s.",
|
90
|
+
elf_errmsg(-1));
|
91
|
+
|
92
|
+
if (shdr.sh_type == SHT_PROGBITS &&
|
93
|
+
(shdr.sh_flags == (SHF_ALLOC | SHF_EXECINSTR)) &&
|
94
|
+
strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), ".text") == 0) {
|
95
|
+
|
96
|
+
text_segment = (void *)shdr.sh_addr;
|
97
|
+
text_segment_len = shdr.sh_size;
|
98
|
+
} else if (shdr.sh_type == SHT_SYMTAB) {
|
99
|
+
symtab_shdr = shdr;
|
100
|
+
if ((symtab_data = elf_getdata(scn,symtab_data)) == 0 || symtab_data->d_size == 0) {
|
101
|
+
return;
|
102
|
+
}
|
103
|
+
}
|
104
|
+
}
|
105
|
+
}
|
106
|
+
#endif
|
data/ext/extconf.rb
CHANGED
@@ -1,5 +1,10 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
def add_define(name)
|
4
|
+
$defs.push("-D#{name}")
|
5
|
+
end
|
6
|
+
|
7
|
+
if (add_define("HAVE_ELF") if have_library('elf', 'gelf_getshdr')) ||
|
8
|
+
(add_define("HAVE_MACH") if have_header('mach-o/dyld.h'))
|
9
|
+
create_makefile('memprof')
|
5
10
|
end
|
data/ext/mach.c
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
#if defined(HAVE_MACH)
|
2
|
+
|
3
|
+
#include "bin_api.h"
|
4
|
+
|
5
|
+
#include <limits.h>
|
6
|
+
#include <sysexits.h>
|
7
|
+
#include <sys/mman.h>
|
8
|
+
|
9
|
+
#include <mach-o/dyld.h>
|
10
|
+
#include <mach-o/getsect.h>
|
11
|
+
#include <mach-o/loader.h>
|
12
|
+
#include <mach-o/ldsyms.h>
|
13
|
+
|
14
|
+
static void
|
15
|
+
set_text_segment(struct mach_header *header, const char *sectname)
|
16
|
+
{
|
17
|
+
text_segment = getsectdatafromheader_64((struct mach_header_64*)header, "__TEXT", sectname, (uint64_t*)&text_segment_len);
|
18
|
+
if (!text_segment)
|
19
|
+
errx(EX_UNAVAILABLE, "Failed to locate the %s section", sectname);
|
20
|
+
}
|
21
|
+
|
22
|
+
static void
|
23
|
+
update_dyld_stubs(int entry, void *trampee_addr)
|
24
|
+
{
|
25
|
+
char *byte = text_segment;
|
26
|
+
size_t count = 0;
|
27
|
+
|
28
|
+
for(; count < text_segment_len; count++) {
|
29
|
+
if (*byte == '\xff') {
|
30
|
+
int off = *(int *)(byte+2);
|
31
|
+
if (trampee_addr == (void*)(*(long long*)(byte + 6 + off))) {
|
32
|
+
*(long long*)(byte + 6 + off) = tramp_table[entry].addr;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
byte++;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
void *
|
40
|
+
bin_allocate_page()
|
41
|
+
{
|
42
|
+
void *ret = NULL;
|
43
|
+
size_t i = 0;
|
44
|
+
|
45
|
+
for (i = pagesize; i < INT_MAX - pagesize; i += pagesize) {
|
46
|
+
ret = mmap((void*)(NULL + i), 2*pagesize, PROT_WRITE|PROT_READ|PROT_EXEC,
|
47
|
+
MAP_ANON|MAP_PRIVATE, -1, 0);
|
48
|
+
|
49
|
+
if (tramp_table != MAP_FAILED)
|
50
|
+
return ret;
|
51
|
+
}
|
52
|
+
return NULL;
|
53
|
+
}
|
54
|
+
|
55
|
+
void
|
56
|
+
bin_update_image(int entry, void *trampee_addr)
|
57
|
+
{
|
58
|
+
// Modify any callsites residing inside the text segment of the executable itself
|
59
|
+
set_text_segment((struct mach_header*)&_mh_execute_header, "__text");
|
60
|
+
update_callqs(entry, trampee_addr);
|
61
|
+
|
62
|
+
// Modify all dyld stubs in shared libraries that have been loaded
|
63
|
+
int i, j, k;
|
64
|
+
int header_count = _dyld_image_count();
|
65
|
+
|
66
|
+
// Go through all the mach objects that are loaded into this process
|
67
|
+
for (i=0; i < header_count; i++) {
|
68
|
+
const struct mach_header *current_hdr = _dyld_get_image_header(i);
|
69
|
+
int lc_count = current_hdr->ncmds;
|
70
|
+
|
71
|
+
// this as a char* because we need to step it forward by an arbitrary number of bytes
|
72
|
+
const char *lc = ((const char*) current_hdr) + sizeof(struct mach_header_64);
|
73
|
+
|
74
|
+
// Check all the load commands in the object to see if they are segment commands
|
75
|
+
for (j = 0; j < lc_count; j++) {
|
76
|
+
if (((struct load_command*)lc)->cmd == LC_SEGMENT_64) {
|
77
|
+
const struct segment_command_64 *seg = (const struct segment_command_64 *) lc;
|
78
|
+
const struct section_64 * sect = (const struct section_64*)(lc + sizeof(struct segment_command_64));
|
79
|
+
int section_count = (seg->cmdsize - sizeof(struct segment_command_64)) / sizeof(struct section_64);
|
80
|
+
|
81
|
+
// Search the segment for a section containing dyld stub functions
|
82
|
+
for (k=0; k < section_count; k++) {
|
83
|
+
if (strncmp(sect->sectname, "__symbol_stub", 13) == 0) {
|
84
|
+
set_text_segment((struct mach_header*)current_hdr, sect->sectname);
|
85
|
+
text_segment += _dyld_get_image_vmaddr_slide(i);
|
86
|
+
update_dyld_stubs(entry, trampee_addr);
|
87
|
+
}
|
88
|
+
sect++;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
lc += ((struct load_command*)lc)->cmdsize;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
void *
|
97
|
+
bin_find_symbol(char *sym, size_t *size) {
|
98
|
+
void *ptr = NULL;
|
99
|
+
_dyld_lookup_and_bind((const char*)sym, &ptr, NULL);
|
100
|
+
return ptr;
|
101
|
+
}
|
102
|
+
|
103
|
+
void
|
104
|
+
bin_init()
|
105
|
+
{
|
106
|
+
/* mach-o is so cool it needs no initialization */
|
107
|
+
}
|
108
|
+
#endif
|
data/ext/memprof.c
CHANGED
@@ -1,78 +1,289 @@
|
|
1
1
|
#define _GNU_SOURCE
|
2
2
|
#include <err.h>
|
3
3
|
#include <fcntl.h>
|
4
|
-
#include <
|
4
|
+
#include <stddef.h>
|
5
5
|
#include <stdio.h>
|
6
6
|
#include <stdint.h>
|
7
7
|
#include <stdlib.h>
|
8
8
|
#include <unistd.h>
|
9
|
-
#include <link.h>
|
10
9
|
#include <sysexits.h>
|
11
10
|
#include <sys/mman.h>
|
11
|
+
#include <err.h>
|
12
12
|
|
13
|
+
#include <st.h>
|
13
14
|
#include <ruby.h>
|
14
15
|
#include <intern.h>
|
16
|
+
#include <node.h>
|
15
17
|
|
16
|
-
|
17
|
-
unsigned char mov[2];
|
18
|
-
long long addr;
|
19
|
-
unsigned char callq[2];
|
20
|
-
unsigned char ret;
|
21
|
-
unsigned char pad[3];
|
22
|
-
} __attribute__((__packed__));;
|
18
|
+
#include "bin_api.h"
|
23
19
|
|
20
|
+
size_t pagesize;
|
21
|
+
void *text_segment = NULL;
|
22
|
+
unsigned long text_segment_len = 0;
|
24
23
|
|
25
|
-
|
26
|
-
|
24
|
+
/*
|
25
|
+
trampoline specific stuff
|
26
|
+
*/
|
27
|
+
struct tramp_tbl_entry *tramp_table = NULL;
|
28
|
+
size_t tramp_size = 0;
|
27
29
|
|
28
30
|
/*
|
29
|
-
|
30
|
-
*/
|
31
|
-
|
32
|
-
|
31
|
+
inline trampoline specific stuff
|
32
|
+
*/
|
33
|
+
size_t inline_tramp_size = 0;
|
34
|
+
struct inline_tramp_tbl_entry *inline_tramp_table = NULL;
|
33
35
|
|
34
36
|
/*
|
35
|
-
|
36
|
-
*/
|
37
|
-
static
|
38
|
-
static
|
39
|
-
static Elf_Data *symtab_data = NULL;
|
37
|
+
* bleak_house stuff
|
38
|
+
*/
|
39
|
+
static int track_objs = 0;
|
40
|
+
static st_table *objs = NULL;
|
40
41
|
|
42
|
+
struct obj_track {
|
43
|
+
VALUE obj;
|
44
|
+
char *source;
|
45
|
+
int line;
|
46
|
+
};
|
41
47
|
|
42
48
|
static void
|
43
|
-
error_tramp()
|
49
|
+
error_tramp()
|
50
|
+
{
|
44
51
|
printf("WARNING: NO TRAMPOLINE SET.\n");
|
45
52
|
return;
|
46
53
|
}
|
47
54
|
|
48
55
|
static VALUE
|
49
|
-
newobj_tramp()
|
50
|
-
|
51
|
-
|
56
|
+
newobj_tramp()
|
57
|
+
{
|
58
|
+
VALUE ret = rb_newobj();
|
59
|
+
struct obj_track *tracker = NULL;
|
60
|
+
|
61
|
+
if (track_objs) {
|
62
|
+
tracker = malloc(sizeof(*tracker));
|
63
|
+
|
64
|
+
if (tracker) {
|
65
|
+
if (ruby_current_node && ruby_current_node->nd_file && *ruby_current_node->nd_file) {
|
66
|
+
tracker->source = strdup(ruby_current_node->nd_file);
|
67
|
+
tracker->line = nd_line(ruby_current_node);
|
68
|
+
} else if (ruby_sourcefile) {
|
69
|
+
tracker->source = strdup(ruby_sourcefile);
|
70
|
+
tracker->line = ruby_sourceline;
|
71
|
+
} else {
|
72
|
+
tracker->source = strdup("__null__");
|
73
|
+
tracker->line = 0;
|
74
|
+
}
|
75
|
+
|
76
|
+
tracker->obj = ret;
|
77
|
+
st_insert(objs, (st_data_t)ret, (st_data_t)tracker);
|
78
|
+
} else {
|
79
|
+
fprintf(stderr, "Warning, unable to allocate a tracker. You are running dangerously low on RAM!\n");
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
return ret;
|
84
|
+
}
|
85
|
+
|
86
|
+
static void
|
87
|
+
freelist_tramp(unsigned long rval)
|
88
|
+
{
|
89
|
+
struct obj_track *tracker = NULL;
|
90
|
+
|
91
|
+
if (track_objs) {
|
92
|
+
st_delete(objs, (st_data_t *) &rval, (st_data_t *) &tracker);
|
93
|
+
if (tracker) {
|
94
|
+
free(tracker->source);
|
95
|
+
free(tracker);
|
96
|
+
}
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
static int
|
101
|
+
memprof_tabulate(st_data_t key, st_data_t record, st_data_t arg)
|
102
|
+
{
|
103
|
+
st_table *table = (st_table *)arg;
|
104
|
+
struct obj_track *tracker = (struct obj_track *)record;
|
105
|
+
char *source_key = NULL;
|
106
|
+
unsigned long count = 0;
|
107
|
+
char *type = NULL;
|
108
|
+
|
109
|
+
switch (TYPE(tracker->obj)) {
|
110
|
+
case T_NONE:
|
111
|
+
type = "__none__"; break;
|
112
|
+
case T_BLKTAG:
|
113
|
+
type = "__blktag__"; break;
|
114
|
+
case T_UNDEF:
|
115
|
+
type = "__undef__"; break;
|
116
|
+
case T_VARMAP:
|
117
|
+
type = "__varmap__"; break;
|
118
|
+
case T_SCOPE:
|
119
|
+
type = "__scope__"; break;
|
120
|
+
case T_NODE:
|
121
|
+
type = "__node__"; break;
|
122
|
+
default:
|
123
|
+
if (RBASIC(tracker->obj)->klass) {
|
124
|
+
type = rb_obj_classname(tracker->obj);
|
125
|
+
} else {
|
126
|
+
type = "__unknown__";
|
127
|
+
}
|
128
|
+
}
|
129
|
+
|
130
|
+
asprintf(&source_key, "%s:%d:%s", tracker->source, tracker->line, type);
|
131
|
+
st_lookup(table, (st_data_t)source_key, (st_data_t *)&count);
|
132
|
+
if (st_insert(table, (st_data_t)source_key, ++count)) {
|
133
|
+
free(source_key);
|
134
|
+
}
|
135
|
+
|
136
|
+
free(tracker->source);
|
137
|
+
return ST_DELETE;
|
138
|
+
}
|
139
|
+
|
140
|
+
struct results {
|
141
|
+
char **entries;
|
142
|
+
unsigned long num_entries;
|
143
|
+
};
|
144
|
+
|
145
|
+
static int
|
146
|
+
memprof_do_dump(st_data_t key, st_data_t record, st_data_t arg)
|
147
|
+
{
|
148
|
+
struct results *res = (struct results *)arg;
|
149
|
+
unsigned long count = (unsigned long)record;
|
150
|
+
char *source = (char *)key;
|
151
|
+
|
152
|
+
asprintf(&(res->entries[res->num_entries++]), "%7d %s", count, source);
|
153
|
+
|
154
|
+
free(source);
|
155
|
+
return ST_DELETE;
|
156
|
+
}
|
157
|
+
|
158
|
+
static VALUE
|
159
|
+
memprof_start(VALUE self)
|
160
|
+
{
|
161
|
+
if (track_objs == 1)
|
162
|
+
return Qfalse;
|
163
|
+
|
164
|
+
track_objs = 1;
|
165
|
+
return Qtrue;
|
166
|
+
}
|
167
|
+
|
168
|
+
static VALUE
|
169
|
+
memprof_stop(VALUE self)
|
170
|
+
{
|
171
|
+
if (track_objs == 0)
|
172
|
+
return Qfalse;
|
173
|
+
|
174
|
+
track_objs = 0;
|
175
|
+
return Qtrue;
|
176
|
+
}
|
177
|
+
|
178
|
+
static int
|
179
|
+
memprof_strcmp(const void *obj1, const void *obj2)
|
180
|
+
{
|
181
|
+
char *str1 = *(char **)obj1;
|
182
|
+
char *str2 = *(char **)obj2;
|
183
|
+
return strcmp(str2, str1);
|
184
|
+
}
|
185
|
+
|
186
|
+
static VALUE
|
187
|
+
memprof_dump(int argc, VALUE *argv, VALUE self)
|
188
|
+
{
|
189
|
+
st_table *tmp_table;
|
190
|
+
struct results res;
|
191
|
+
int i;
|
192
|
+
VALUE str;
|
193
|
+
FILE *out = NULL;
|
194
|
+
|
195
|
+
rb_scan_args(argc, argv, "01", &str);
|
196
|
+
|
197
|
+
if (RTEST(str)) {
|
198
|
+
out = fopen(StringValueCStr(str), "w");
|
199
|
+
if (!out)
|
200
|
+
rb_raise(rb_eArgError, "unable to open output file");
|
201
|
+
}
|
202
|
+
|
203
|
+
track_objs = 0;
|
204
|
+
|
205
|
+
tmp_table = st_init_strtable();
|
206
|
+
st_foreach(objs, memprof_tabulate, (st_data_t)tmp_table);
|
207
|
+
|
208
|
+
res.num_entries = 0;
|
209
|
+
res.entries = malloc(sizeof(char*) * tmp_table->num_entries);
|
210
|
+
|
211
|
+
st_foreach(tmp_table, memprof_do_dump, (st_data_t)&res);
|
212
|
+
st_free_table(tmp_table);
|
213
|
+
|
214
|
+
qsort(res.entries, res.num_entries, sizeof(char*), &memprof_strcmp);
|
215
|
+
for (i=0; i < res.num_entries; i++) {
|
216
|
+
fprintf(out ? out : stderr, "%s\n", res.entries[i]);
|
217
|
+
free(res.entries[i]);
|
218
|
+
}
|
219
|
+
free(res.entries);
|
220
|
+
|
221
|
+
track_objs = 1;
|
222
|
+
return Qnil;
|
52
223
|
}
|
53
224
|
|
54
225
|
static void
|
55
|
-
create_tramp_table()
|
56
|
-
|
226
|
+
create_tramp_table()
|
227
|
+
{
|
228
|
+
int i, j = 0;
|
57
229
|
|
58
230
|
struct tramp_tbl_entry ent = {
|
59
|
-
.
|
60
|
-
.
|
61
|
-
.
|
62
|
-
.
|
63
|
-
.
|
231
|
+
.rbx_save = {'\x53'}, // push rbx
|
232
|
+
.mov = {'\x48', '\xbb'}, // mov addr into rbx
|
233
|
+
.addr = error_tramp, // ^^^
|
234
|
+
.callq = {'\xff', '\xd3'}, // callq rbx
|
235
|
+
.rbx_restore = {'\x5b'}, // pop rbx
|
236
|
+
.ret = {'\xc3'}, // ret
|
64
237
|
};
|
65
238
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
239
|
+
struct inline_tramp_tbl_entry inline_ent = {
|
240
|
+
.rex = {'\x48'},
|
241
|
+
.mov = {'\x89'},
|
242
|
+
.src_reg = {'\x05'},
|
243
|
+
.mov_displacement = 0,
|
244
|
+
|
245
|
+
.frame = {
|
246
|
+
.push_rdi = {'\x57'},
|
247
|
+
.mov_rdi = {'\x48', '\x8b', '\x3d'},
|
248
|
+
.rdi_source_displacement = 0,
|
249
|
+
.push_rbx = {'\x53'},
|
250
|
+
.push_rbp = {'\x55'},
|
251
|
+
.save_rsp = {'\x48', '\x89', '\xe5'},
|
252
|
+
.align_rsp = {'\x48', '\x83', '\xe4', '\xf0'},
|
253
|
+
.mov = {'\x48', '\xbb'},
|
254
|
+
.addr = error_tramp,
|
255
|
+
.callq = {'\xff', '\xd3'},
|
256
|
+
.leave = {'\xc9'},
|
257
|
+
.rbx_restore = {'\x5b'},
|
258
|
+
.rdi_restore = {'\x5f'},
|
259
|
+
},
|
260
|
+
|
261
|
+
.jmp = {'\xe9'},
|
262
|
+
.jmp_displacement = 0,
|
263
|
+
};
|
264
|
+
|
265
|
+
if ((tramp_table = bin_allocate_page()) == MAP_FAILED) {
|
266
|
+
fprintf(stderr, "Failed to allocate memory for stage 1 trampoline.\n");
|
267
|
+
return;
|
268
|
+
}
|
269
|
+
|
270
|
+
if ((inline_tramp_table = bin_allocate_page()) == MAP_FAILED) {
|
271
|
+
fprintf(stderr, "Faied to allocate memory for the stage 1 inline trampoline.\n");
|
272
|
+
return;
|
273
|
+
}
|
274
|
+
|
275
|
+
for (j = 0; j < pagesize/sizeof(struct tramp_tbl_entry); j ++ ) {
|
276
|
+
memcpy(tramp_table + j, &ent, sizeof(struct tramp_tbl_entry));
|
277
|
+
}
|
278
|
+
|
279
|
+
for (j = 0; j < pagesize/sizeof(struct inline_tramp_tbl_entry); j++) {
|
280
|
+
memcpy(inline_tramp_table + j, &inline_ent, sizeof(struct inline_tramp_tbl_entry));
|
71
281
|
}
|
72
282
|
}
|
73
283
|
|
74
|
-
|
75
|
-
|
284
|
+
void
|
285
|
+
update_callqs(int entry, void *trampee_addr)
|
286
|
+
{
|
76
287
|
char *byte = text_segment;
|
77
288
|
size_t count = 0;
|
78
289
|
int fn_addr = 0;
|
@@ -92,93 +303,174 @@ update_image(int entry, void *trampee_addr) {
|
|
92
303
|
}
|
93
304
|
}
|
94
305
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
306
|
+
|
307
|
+
static void
|
308
|
+
hook_freelist(int entry)
|
309
|
+
{
|
310
|
+
long sizes[] = { 0, 0, 0 };
|
311
|
+
void *sym1 = bin_find_symbol("gc_sweep", &sizes[0]);
|
312
|
+
|
313
|
+
if (sym1 == NULL) {
|
314
|
+
/* this is MRI ... */
|
315
|
+
sym1 = bin_find_symbol("garbage_collect", &sizes[0]);
|
316
|
+
}
|
317
|
+
|
318
|
+
void *sym2 = bin_find_symbol("finalize_list", &sizes[1]);
|
319
|
+
void *sym3 = bin_find_symbol("rb_gc_force_recycle", &sizes[2]);
|
320
|
+
void *freelist_callers[] = { sym1, sym2, sym3 };
|
321
|
+
int max = 3;
|
322
|
+
size_t i = 0;
|
323
|
+
char *byte = freelist_callers[0];
|
324
|
+
void *freelist = bin_find_symbol("freelist", NULL);
|
325
|
+
uint32_t mov_target = 0;
|
326
|
+
void *aligned_addr = NULL;
|
327
|
+
size_t count = 0;
|
328
|
+
|
329
|
+
/* This is the stage 1 trampoline for hooking the inlined add_freelist
|
330
|
+
* function .
|
331
|
+
*
|
332
|
+
* NOTE: The original instruction mov %reg, freelist is 7 bytes wide,
|
333
|
+
* whereas jmpq $displacement is only 5 bytes wide. We *must* pad out
|
334
|
+
* the next two bytes. This will be important to remember below.
|
335
|
+
*/
|
336
|
+
struct tramp_inline tramp = {
|
337
|
+
.jmp = {'\xe9'},
|
338
|
+
.displacement = 0,
|
339
|
+
.pad = {'\x90', '\x90'},
|
340
|
+
};
|
341
|
+
|
342
|
+
struct inline_tramp_tbl_entry *inl_tramp_st2 = NULL;
|
343
|
+
|
344
|
+
for (;i < max;) {
|
345
|
+
/* make sure it is a mov instruction */
|
346
|
+
if (byte[1] == '\x89') {
|
347
|
+
|
348
|
+
/* Read the REX byte to make sure it is a mov that we care about */
|
349
|
+
if ((byte[0] == '\x48') ||
|
350
|
+
(byte[0] == '\x4c')) {
|
351
|
+
|
352
|
+
/* Grab the target of the mov. REMEMBER: in this case the target is
|
353
|
+
* a 32bit displacment that gets added to RIP (where RIP is the adress of
|
354
|
+
* the next instruction).
|
355
|
+
*/
|
356
|
+
mov_target = *(uint32_t *)(byte + 3);
|
357
|
+
|
358
|
+
/* Sanity check. Ensure that the displacement from freelist to the next
|
359
|
+
* instruction matches the mov_target. If so, we know this mov is
|
360
|
+
* updating freelist.
|
361
|
+
*/
|
362
|
+
if ((freelist - (void *)(byte+7)) == mov_target) {
|
363
|
+
/* Before the stage 1 trampoline gets written, we need to generate
|
364
|
+
* the code for the stage 2 trampoline. Let's copy over the REX byte
|
365
|
+
* and the byte which mentions the source register into the stage 2
|
366
|
+
* trampoline.
|
367
|
+
*/
|
368
|
+
inl_tramp_st2 = inline_tramp_table + entry;
|
369
|
+
inl_tramp_st2->rex[0] = byte[0];
|
370
|
+
inl_tramp_st2->src_reg[0] = byte[2];
|
371
|
+
|
372
|
+
/* Setup the stage 1 trampoline. Calculate the displacement to
|
373
|
+
* the stage 2 trampoline from the next instruction.
|
374
|
+
*
|
375
|
+
* REMEMBER!!!! The next instruction will be NOP after our stage 1
|
376
|
+
* trampoline is written. This is 5 bytes into the structure, even
|
377
|
+
* though the original instruction we overwrote was 7 bytes.
|
378
|
+
*/
|
379
|
+
tramp.displacement = (uint32_t)((void *)(inl_tramp_st2) - (void *)(byte+5));
|
380
|
+
|
381
|
+
/* Figure out what page the stage 1 tramp is gonna be written to, mark
|
382
|
+
* it WRITE, write the trampoline in, and then remove WRITE permission.
|
383
|
+
*/
|
384
|
+
aligned_addr = (void*)(((long)byte)&~(0xffff));
|
385
|
+
mprotect(aligned_addr, (((void *)byte) - aligned_addr) + 10, PROT_READ|PROT_WRITE|PROT_EXEC);
|
386
|
+
memcpy(byte, &tramp, sizeof(struct tramp_inline));
|
387
|
+
mprotect(aligned_addr, (((void *)byte) - aligned_addr) + 10, PROT_READ|PROT_EXEC);
|
388
|
+
|
389
|
+
/* Finish setting up the stage 2 trampoline. */
|
390
|
+
|
391
|
+
/* calculate the displacement to freelist from the next instruction.
|
392
|
+
*
|
393
|
+
* This is used to replicate the original instruction we overwrote.
|
394
|
+
*/
|
395
|
+
inl_tramp_st2->mov_displacement = freelist - (void *)&(inl_tramp_st2->frame);
|
396
|
+
|
397
|
+
/* fill in the displacement to freelist from the next instruction.
|
398
|
+
*
|
399
|
+
* This is to arrange for the new value in freelist to be in %rdi, and as such
|
400
|
+
* be the first argument to the C handler. As per the amd64 ABI.
|
401
|
+
*/
|
402
|
+
inl_tramp_st2->frame.rdi_source_displacement = freelist - (void *)&(inl_tramp_st2->frame.push_rbx);
|
403
|
+
|
404
|
+
/* jmp back to the instruction after stage 1 trampoline was inserted
|
405
|
+
*
|
406
|
+
* This can be 5 or 7, it doesn't matter. If its 5, we'll hit our 2
|
407
|
+
* NOPS. If its 7, we'll land directly on the next instruction.
|
408
|
+
*/
|
409
|
+
inl_tramp_st2->jmp_displacement = (uint32_t)((void *)(byte + 7) -
|
410
|
+
(void *)(inline_tramp_table + entry + 1));
|
411
|
+
|
412
|
+
/* write the address of our C level trampoline in to the structure */
|
413
|
+
inl_tramp_st2->frame.addr = freelist_tramp;
|
414
|
+
|
415
|
+
/* track the new entry and new trampoline size */
|
416
|
+
entry++;
|
417
|
+
inline_tramp_size++;
|
418
|
+
}
|
419
|
+
}
|
112
420
|
}
|
421
|
+
|
422
|
+
if (count >= sizes[i]) {
|
423
|
+
count = 0;
|
424
|
+
i ++;
|
425
|
+
byte = freelist_callers[i];
|
426
|
+
}
|
427
|
+
count++;
|
428
|
+
byte++;
|
113
429
|
}
|
114
|
-
return NULL;
|
115
430
|
}
|
116
431
|
|
117
432
|
static void
|
118
|
-
insert_tramp(char *trampee, void *tramp)
|
119
|
-
|
433
|
+
insert_tramp(char *trampee, void *tramp)
|
434
|
+
{
|
435
|
+
void *trampee_addr = bin_find_symbol(trampee, NULL);
|
120
436
|
int entry = tramp_size;
|
121
|
-
|
122
|
-
tramp_size++;
|
123
|
-
update_image(entry, trampee_addr);
|
124
|
-
}
|
437
|
+
int inline_ent = inline_tramp_size;
|
125
438
|
|
126
|
-
|
127
|
-
{
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
if (elf_version(EV_CURRENT) == EV_NONE)
|
135
|
-
errx(EX_SOFTWARE, "ELF library initialization failed: %s",
|
136
|
-
elf_errmsg(-1));
|
137
|
-
|
138
|
-
asprintf(&filename, "/proc/%ld/exe", (long)getpid());
|
139
|
-
|
140
|
-
if ((fd = open(filename, O_RDONLY, 0)) < 0)
|
141
|
-
err(EX_NOINPUT, "open \%s\" failed", filename);
|
142
|
-
|
143
|
-
if ((elf = elf_begin(fd, ELF_C_READ, NULL)) == NULL)
|
144
|
-
errx(EX_SOFTWARE, "elf_begin() failed: %s.",
|
145
|
-
elf_errmsg(-1));
|
146
|
-
|
147
|
-
if (elf_kind(elf) != ELF_K_ELF)
|
148
|
-
errx(EX_DATAERR, "%s is not an ELF object.", filename);
|
149
|
-
|
150
|
-
if (elf_getshstrndx(elf, &shstrndx) == 0)
|
151
|
-
errx(EX_SOFTWARE, "getshstrndx() failed: %s.",
|
152
|
-
elf_errmsg(-1));
|
153
|
-
|
154
|
-
scn = NULL;
|
155
|
-
|
156
|
-
while ((scn = elf_nextscn(elf, scn)) != NULL) {
|
157
|
-
if (gelf_getshdr(scn, &shdr) != &shdr)
|
158
|
-
errx(EX_SOFTWARE, "getshdr() failed: %s.",
|
159
|
-
elf_errmsg(-1));
|
160
|
-
|
161
|
-
if (shdr.sh_type == SHT_PROGBITS &&
|
162
|
-
(shdr.sh_flags == (SHF_ALLOC | SHF_EXECINSTR)) &&
|
163
|
-
strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), ".text") == 0) {
|
164
|
-
|
165
|
-
text_segment = (void *)shdr.sh_addr;
|
166
|
-
text_segment_len = shdr.sh_size;
|
167
|
-
} else if (shdr.sh_type == SHT_SYMTAB) {
|
168
|
-
symtab_shdr = shdr;
|
169
|
-
if ((symtab_data = elf_getdata(scn,symtab_data)) == 0 || symtab_data->d_size == 0) {
|
170
|
-
return;
|
171
|
-
}
|
439
|
+
if (trampee_addr == NULL) {
|
440
|
+
if (strcmp("add_freelist", trampee) == 0) {
|
441
|
+
/* XXX super hack */
|
442
|
+
inline_tramp_table[inline_tramp_size].frame.addr = tramp;
|
443
|
+
inline_tramp_size++;
|
444
|
+
hook_freelist(inline_ent);
|
445
|
+
} else {
|
446
|
+
return;
|
172
447
|
}
|
448
|
+
} else {
|
449
|
+
tramp_table[tramp_size].addr = tramp;
|
450
|
+
tramp_size++;
|
451
|
+
bin_update_image(entry, trampee_addr);
|
173
452
|
}
|
453
|
+
}
|
174
454
|
|
455
|
+
void
|
456
|
+
Init_memprof()
|
457
|
+
{
|
458
|
+
VALUE memprof = rb_define_module("Memprof");
|
459
|
+
rb_define_singleton_method(memprof, "start", memprof_start, 0);
|
460
|
+
rb_define_singleton_method(memprof, "stop", memprof_stop, 0);
|
461
|
+
rb_define_singleton_method(memprof, "dump", memprof_dump, -1);
|
175
462
|
|
463
|
+
pagesize = getpagesize();
|
464
|
+
objs = st_init_numtable();
|
465
|
+
bin_init();
|
176
466
|
create_tramp_table();
|
177
467
|
|
468
|
+
#if defined(HAVE_MACH)
|
469
|
+
insert_tramp("_rb_newobj", newobj_tramp);
|
470
|
+
#elif defined(HAVE_ELF)
|
178
471
|
insert_tramp("rb_newobj", newobj_tramp);
|
179
|
-
|
180
|
-
(void) elf_end(e);
|
181
|
-
(void) close(fd);
|
472
|
+
insert_tramp("add_freelist", freelist_tramp);
|
182
473
|
#endif
|
474
|
+
|
183
475
|
return;
|
184
476
|
}
|
data/memprof.gemspec
CHANGED
@@ -1,17 +1,22 @@
|
|
1
1
|
spec = Gem::Specification.new do |s|
|
2
2
|
s.name = 'memprof'
|
3
|
-
s.version = '0.0
|
4
|
-
s.date = '2009-
|
5
|
-
s.summary = 'Ruby
|
3
|
+
s.version = '0.1.0'
|
4
|
+
s.date = '2009-12-10'
|
5
|
+
s.summary = 'Ruby Memory Profiler'
|
6
|
+
s.description = "Ruby memory profiler similar to bleak_house, but without patches to the Ruby VM"
|
6
7
|
s.email = "ice799@gmail.com"
|
7
8
|
s.homepage = "http://github.com/ice799/memprof"
|
8
|
-
s.description = "Ruby memory profiler gem"
|
9
9
|
s.has_rdoc = false
|
10
10
|
s.authors = ["Joe Damato"]
|
11
11
|
s.extensions = "ext/extconf.rb"
|
12
|
-
s.
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
s.files = %w[
|
13
|
+
.gitignore
|
14
|
+
README
|
15
|
+
ext/bin_api.h
|
16
|
+
ext/elf.c
|
17
|
+
ext/extconf.rb
|
18
|
+
ext/mach.c
|
19
|
+
ext/memprof.c
|
20
|
+
memprof.gemspec
|
21
|
+
]
|
17
22
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: memprof
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Damato
|
@@ -9,11 +9,11 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-12-10 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
16
|
-
description: Ruby memory profiler
|
16
|
+
description: Ruby memory profiler similar to bleak_house, but without patches to the Ruby VM
|
17
17
|
email: ice799@gmail.com
|
18
18
|
executables: []
|
19
19
|
|
@@ -22,10 +22,14 @@ extensions:
|
|
22
22
|
extra_rdoc_files: []
|
23
23
|
|
24
24
|
files:
|
25
|
+
- .gitignore
|
25
26
|
- README
|
26
|
-
-
|
27
|
-
- ext/
|
27
|
+
- ext/bin_api.h
|
28
|
+
- ext/elf.c
|
28
29
|
- ext/extconf.rb
|
30
|
+
- ext/mach.c
|
31
|
+
- ext/memprof.c
|
32
|
+
- memprof.gemspec
|
29
33
|
has_rdoc: true
|
30
34
|
homepage: http://github.com/ice799/memprof
|
31
35
|
licenses: []
|
@@ -35,7 +39,6 @@ rdoc_options: []
|
|
35
39
|
|
36
40
|
require_paths:
|
37
41
|
- lib
|
38
|
-
- ext
|
39
42
|
required_ruby_version: !ruby/object:Gem::Requirement
|
40
43
|
requirements:
|
41
44
|
- - ">="
|
@@ -54,6 +57,6 @@ rubyforge_project:
|
|
54
57
|
rubygems_version: 1.3.5
|
55
58
|
signing_key:
|
56
59
|
specification_version: 3
|
57
|
-
summary: Ruby
|
60
|
+
summary: Ruby Memory Profiler
|
58
61
|
test_files: []
|
59
62
|
|