memprof 0.1.3 → 0.2.0
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/README +5 -5
- data/ext/arch.h +30 -0
- data/ext/bin_api.h +13 -52
- data/ext/elf.c +479 -45
- data/ext/extconf.rb +96 -8
- data/ext/i386.c +106 -0
- data/ext/i386.h +75 -0
- data/ext/mach.c +12 -0
- data/ext/memprof.c +575 -186
- data/ext/src/libdwarf-20091118.tar.gz +0 -0
- data/ext/src/yajl-1.0.8.tar.gz +0 -0
- data/ext/x86_64.c +124 -0
- data/ext/x86_64.h +78 -0
- data/ext/x86_gen.h +100 -0
- data/memprof.gemspec +10 -1
- data/spec/memprof_spec.rb +85 -0
- metadata +10 -1
Binary file
|
Binary file
|
data/ext/x86_64.c
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
#if defined (_ARCH_x86_64_)
|
2
|
+
|
3
|
+
#include <stdint.h>
|
4
|
+
#include <string.h>
|
5
|
+
|
6
|
+
#include "arch.h"
|
7
|
+
#include "x86_gen.h"
|
8
|
+
|
9
|
+
/* This is the stage 1 inline trampoline for hooking the inlined add_freelist
|
10
|
+
* function .
|
11
|
+
*
|
12
|
+
* NOTE: The original instruction mov %reg, freelist is 7 bytes wide,
|
13
|
+
* whereas jmpq $displacement is only 5 bytes wide. We *must* pad out
|
14
|
+
* the next two bytes. This will be important to remember below.
|
15
|
+
*/
|
16
|
+
struct inline_st1_tramp {
|
17
|
+
unsigned char jmp;
|
18
|
+
int32_t displacement;
|
19
|
+
unsigned char pad[2];
|
20
|
+
} __attribute__((__packed__)) inline_st1_tramp = {
|
21
|
+
.jmp = '\xe9',
|
22
|
+
.displacement = 0,
|
23
|
+
.pad = {'\x90','\x90'},
|
24
|
+
};
|
25
|
+
|
26
|
+
struct inline_st1_base {
|
27
|
+
unsigned char rex;
|
28
|
+
unsigned char mov;
|
29
|
+
unsigned char src_reg;
|
30
|
+
int32_t displacement;
|
31
|
+
} __attribute__((__packed__)) inline_st1_mov = {
|
32
|
+
.rex = 0,
|
33
|
+
.mov = '\x89',
|
34
|
+
.src_reg = 0,
|
35
|
+
.displacement = 0
|
36
|
+
};
|
37
|
+
|
38
|
+
/*
|
39
|
+
* inline tramp stuff below
|
40
|
+
*/
|
41
|
+
static int
|
42
|
+
arch_check_ins(struct inline_st1_base *base)
|
43
|
+
{
|
44
|
+
/* is it a mov instruction? */
|
45
|
+
if (base->mov == 0x89 &&
|
46
|
+
|
47
|
+
/* maybe. read the REX byte to find out for sure */
|
48
|
+
(base->rex == 0x48 ||
|
49
|
+
base->rex == 0x4c)) {
|
50
|
+
|
51
|
+
/* success */
|
52
|
+
return 1;
|
53
|
+
}
|
54
|
+
|
55
|
+
return 0;
|
56
|
+
}
|
57
|
+
|
58
|
+
int
|
59
|
+
arch_insert_inline_st2_tramp(void *addr, void *marker, void *trampoline, void *table_entry)
|
60
|
+
{
|
61
|
+
struct inline_st1_base *base = addr;
|
62
|
+
struct inline_tramp_st2_entry *entry = table_entry;
|
63
|
+
|
64
|
+
if (!arch_check_ins(base))
|
65
|
+
return 1;
|
66
|
+
|
67
|
+
/* Sanity check. Ensure that the displacement from freelist to the next
|
68
|
+
* instruction matches the mov_target. If so, we know this mov is
|
69
|
+
* updating freelist.
|
70
|
+
*/
|
71
|
+
if (marker - (void *)(base + 1) == base->displacement) {
|
72
|
+
/* Before the stage 1 trampoline gets written, we need to generate
|
73
|
+
* the code for the stage 2 trampoline. Let's copy over the REX byte
|
74
|
+
* and the byte which mentions the source register into the stage 2
|
75
|
+
* trampoline.
|
76
|
+
*/
|
77
|
+
default_inline_st2_tramp.rex = base->rex;
|
78
|
+
default_inline_st2_tramp.src_reg = base->src_reg;
|
79
|
+
|
80
|
+
/* Setup the stage 1 trampoline. Calculate the displacement to
|
81
|
+
* the stage 2 trampoline from the next instruction.
|
82
|
+
*
|
83
|
+
* REMEMBER!!!! The next instruction will be NOP after our stage 1
|
84
|
+
* trampoline is written. This is 5 bytes into the structure, even
|
85
|
+
* though the original instruction we overwrote was 7 bytes.
|
86
|
+
*/
|
87
|
+
inline_st1_tramp.displacement = table_entry - (void *)(addr + 5);
|
88
|
+
|
89
|
+
copy_instructions(addr, &inline_st1_tramp, sizeof(inline_st1_tramp));
|
90
|
+
|
91
|
+
/* Finish setting up the stage 2 trampoline. */
|
92
|
+
|
93
|
+
/* calculate the displacement to freelist from the next instruction.
|
94
|
+
*
|
95
|
+
* This is used to replicate the original instruction we overwrote.
|
96
|
+
*/
|
97
|
+
default_inline_st2_tramp.mov_displacement = marker - (void *)&(entry->frame);
|
98
|
+
|
99
|
+
/* fill in the displacement to freelist from the next instruction.
|
100
|
+
*
|
101
|
+
* This is to arrange for the new value in freelist to be in %rdi, and as such
|
102
|
+
* be the first argument to the C handler. As per the amd64 ABI.
|
103
|
+
*/
|
104
|
+
default_inline_st2_tramp.frame.rdi_source_displacement = marker - (void *)&(entry->frame.push_rbx);
|
105
|
+
|
106
|
+
/* jmp back to the instruction after stage 1 trampoline was inserted
|
107
|
+
*
|
108
|
+
* This can be 5 or 7, it doesn't matter. If its 5, we'll hit our 2
|
109
|
+
* NOPS. If its 7, we'll land directly on the next instruction.
|
110
|
+
*/
|
111
|
+
default_inline_st2_tramp.jmp_displacement = (addr + sizeof(*base)) -
|
112
|
+
(table_entry + sizeof(default_inline_st2_tramp));
|
113
|
+
|
114
|
+
/* write the address of our C level trampoline in to the structure */
|
115
|
+
default_inline_st2_tramp.frame.addr = trampoline;
|
116
|
+
|
117
|
+
memcpy(table_entry, &default_inline_st2_tramp, sizeof(default_inline_st2_tramp));
|
118
|
+
|
119
|
+
return 0;
|
120
|
+
}
|
121
|
+
|
122
|
+
return 1;
|
123
|
+
}
|
124
|
+
#endif
|
data/ext/x86_64.h
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
#if !defined(_x86_64_h_)
|
2
|
+
#define _x86_64_h_
|
3
|
+
|
4
|
+
#include <stdint.h>
|
5
|
+
#include "arch.h"
|
6
|
+
|
7
|
+
/*
|
8
|
+
* This is the "normal" stage 2 trampoline with a default entry pre-filled
|
9
|
+
*/
|
10
|
+
static struct tramp_st2_entry {
|
11
|
+
unsigned char rbx_save;
|
12
|
+
unsigned char mov[2];
|
13
|
+
void *addr;
|
14
|
+
unsigned char call[2];
|
15
|
+
unsigned char rbx_restore;
|
16
|
+
unsigned char ret;
|
17
|
+
} __attribute__((__packed__)) default_st2_tramp = {
|
18
|
+
.rbx_save = 0x53, // push rbx
|
19
|
+
.mov = {'\x48', '\xbb'}, // mov addr into rbx
|
20
|
+
.addr = 0, // ^^^
|
21
|
+
.call = {'\xff', '\xd3'}, // call rbx
|
22
|
+
.rbx_restore = 0x5b, // pop rbx
|
23
|
+
.ret = 0xc3, // ret
|
24
|
+
};
|
25
|
+
|
26
|
+
/*
|
27
|
+
* This is the inline stage 2 trampoline with a default entry pre-filled
|
28
|
+
*/
|
29
|
+
static struct inline_tramp_st2_entry {
|
30
|
+
unsigned char rex;
|
31
|
+
unsigned char mov;
|
32
|
+
unsigned char src_reg;
|
33
|
+
uint32_t mov_displacement;
|
34
|
+
|
35
|
+
struct {
|
36
|
+
unsigned char push_rdi;
|
37
|
+
unsigned char mov_rdi[3];
|
38
|
+
uint32_t rdi_source_displacement;
|
39
|
+
unsigned char push_rbx;
|
40
|
+
unsigned char push_rbp;
|
41
|
+
unsigned char save_rsp[3];
|
42
|
+
unsigned char align_rsp[4];
|
43
|
+
unsigned char mov[2];
|
44
|
+
void *addr;
|
45
|
+
unsigned char call[2];
|
46
|
+
unsigned char leave;
|
47
|
+
unsigned char rbx_restore;
|
48
|
+
unsigned char rdi_restore;
|
49
|
+
} __attribute__((__packed__)) frame;
|
50
|
+
|
51
|
+
unsigned char jmp;
|
52
|
+
uint32_t jmp_displacement;
|
53
|
+
} __attribute__((__packed__)) default_inline_st2_tramp = {
|
54
|
+
.rex = 0x48,
|
55
|
+
.mov = 0x89,
|
56
|
+
.src_reg = 0x05,
|
57
|
+
.mov_displacement = 0,
|
58
|
+
|
59
|
+
.frame = {
|
60
|
+
.push_rdi = 0x57,
|
61
|
+
.mov_rdi = {'\x48', '\x8b', '\x3d'},
|
62
|
+
.rdi_source_displacement = 0,
|
63
|
+
.push_rbx = 0x53,
|
64
|
+
.push_rbp = 0x55,
|
65
|
+
.save_rsp = {'\x48', '\x89', '\xe5'},
|
66
|
+
.align_rsp = {'\x48', '\x83', '\xe4', '\xf0'},
|
67
|
+
.mov = {'\x48', '\xbb'},
|
68
|
+
.addr = 0,
|
69
|
+
.call = {'\xff', '\xd3'},
|
70
|
+
.leave = 0xc9,
|
71
|
+
.rbx_restore = 0x5b,
|
72
|
+
.rdi_restore = 0x5f,
|
73
|
+
},
|
74
|
+
|
75
|
+
.jmp = 0xe9,
|
76
|
+
.jmp_displacement = 0,
|
77
|
+
};
|
78
|
+
#endif
|
data/ext/x86_gen.h
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
#if !defined(_x86_gen_)
|
2
|
+
#define _x86_gen_
|
3
|
+
|
4
|
+
#include <sys/mman.h>
|
5
|
+
#include <stdint.h>
|
6
|
+
#include "arch.h"
|
7
|
+
|
8
|
+
/* This structure makes it easier to find and update call instructions that
|
9
|
+
* will become the stage 1 trampoline
|
10
|
+
*/
|
11
|
+
struct st1_base {
|
12
|
+
unsigned char call;
|
13
|
+
int32_t displacement;
|
14
|
+
} __attribute__((__packed__)) st1_mov = {
|
15
|
+
.call = '\xe8',
|
16
|
+
.displacement = 0,
|
17
|
+
};
|
18
|
+
|
19
|
+
struct plt_entry {
|
20
|
+
unsigned char jmp[2];
|
21
|
+
uint32_t jmp_disp;
|
22
|
+
unsigned char pad[10];
|
23
|
+
} __attribute__((__packed__));
|
24
|
+
|
25
|
+
static inline void *
|
26
|
+
page_align(void *addr)
|
27
|
+
{
|
28
|
+
return (void *)((size_t)addr & ~(0xFFFF));
|
29
|
+
}
|
30
|
+
|
31
|
+
static void
|
32
|
+
copy_instructions(void *to, void *from, size_t count)
|
33
|
+
{
|
34
|
+
void *aligned_addr = page_align(to);
|
35
|
+
mprotect(aligned_addr, (to - aligned_addr) + 10, PROT_READ|PROT_WRITE|PROT_EXEC);
|
36
|
+
memcpy(to, from, count);
|
37
|
+
mprotect(aligned_addr, (to - aligned_addr) + 10, PROT_READ|PROT_EXEC);
|
38
|
+
|
39
|
+
return;
|
40
|
+
}
|
41
|
+
|
42
|
+
#define WRITE_INSTRUCTIONS(start, len, stmt) do { \
|
43
|
+
mprotect(start, len, PROT_READ | PROT_WRITE | PROT_EXEC); \
|
44
|
+
stmt; \
|
45
|
+
mprotect(start, len, PROT_READ | PROT_EXEC); \
|
46
|
+
} while (0)
|
47
|
+
|
48
|
+
void
|
49
|
+
arch_insert_st1_tramp(void *start, void *trampee, void *tramp)
|
50
|
+
{
|
51
|
+
int32_t fn_addr = 0;
|
52
|
+
struct st1_base *check = (struct st1_base *)start;
|
53
|
+
void *aligned_addr = page_align(&(check->displacement));
|
54
|
+
|
55
|
+
if (check->call == 0xe8) {
|
56
|
+
fn_addr = check->displacement;
|
57
|
+
if ((trampee - (void *)(check + 1)) == fn_addr) {
|
58
|
+
WRITE_INSTRUCTIONS(aligned_addr,
|
59
|
+
((void *)&(check->displacement) - aligned_addr) + 10,
|
60
|
+
(check->displacement = (tramp - (void *)(check + 1))));
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
|
67
|
+
static void *
|
68
|
+
get_got_addr(struct plt_entry *plt)
|
69
|
+
{
|
70
|
+
return (void *)&(plt->pad) + plt->jmp_disp;
|
71
|
+
}
|
72
|
+
|
73
|
+
void
|
74
|
+
arch_overwrite_got(void *plt, void *tramp)
|
75
|
+
{
|
76
|
+
memcpy(get_got_addr(plt), &tramp, sizeof(void *));
|
77
|
+
return;
|
78
|
+
}
|
79
|
+
|
80
|
+
void *
|
81
|
+
arch_get_st2_tramp(size_t *size)
|
82
|
+
{
|
83
|
+
if (size) {
|
84
|
+
*size = sizeof(struct tramp_st2_entry);
|
85
|
+
}
|
86
|
+
|
87
|
+
return &default_st2_tramp;
|
88
|
+
}
|
89
|
+
|
90
|
+
|
91
|
+
void *
|
92
|
+
arch_get_inline_st2_tramp(size_t *size)
|
93
|
+
{
|
94
|
+
if (size) {
|
95
|
+
*size = sizeof(struct inline_tramp_st2_entry);
|
96
|
+
}
|
97
|
+
|
98
|
+
return &default_inline_st2_tramp;
|
99
|
+
}
|
100
|
+
#endif
|
data/memprof.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
spec = Gem::Specification.new do |s|
|
2
2
|
s.name = 'memprof'
|
3
|
-
s.version = '0.
|
3
|
+
s.version = '0.2.0'
|
4
4
|
s.date = '2009-12-14'
|
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"
|
@@ -12,12 +12,21 @@ spec = Gem::Specification.new do |s|
|
|
12
12
|
s.files = %w[
|
13
13
|
.gitignore
|
14
14
|
README
|
15
|
+
ext/arch.h
|
15
16
|
ext/bin_api.h
|
16
17
|
ext/elf.c
|
17
18
|
ext/extconf.rb
|
19
|
+
ext/i386.c
|
20
|
+
ext/i386.h
|
18
21
|
ext/mach.c
|
19
22
|
ext/memprof.c
|
23
|
+
ext/src/libdwarf-20091118.tar.gz
|
20
24
|
ext/src/libelf-0.8.13.tar.gz
|
25
|
+
ext/src/yajl-1.0.8.tar.gz
|
26
|
+
ext/x86_64.c
|
27
|
+
ext/x86_64.h
|
28
|
+
ext/x86_gen.h
|
21
29
|
memprof.gemspec
|
30
|
+
spec/memprof_spec.rb
|
22
31
|
]
|
23
32
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../ext/memprof"
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bacon'
|
5
|
+
Bacon.summary_on_exit
|
6
|
+
|
7
|
+
require 'tempfile'
|
8
|
+
|
9
|
+
describe Memprof do
|
10
|
+
@tempfile = Tempfile.new('memprof_spec')
|
11
|
+
|
12
|
+
def filename
|
13
|
+
@tempfile.path
|
14
|
+
end
|
15
|
+
|
16
|
+
def filedata
|
17
|
+
File.read(filename)
|
18
|
+
end
|
19
|
+
|
20
|
+
before do
|
21
|
+
Memprof.stop
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'print stats to a file' do
|
25
|
+
Memprof.start
|
26
|
+
"abc"
|
27
|
+
Memprof.stats(filename)
|
28
|
+
|
29
|
+
filedata.strip.should == "1 #{__FILE__}:#{__LINE__-3}:String"
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'allow calling ::stats multiple times' do
|
33
|
+
Memprof.start
|
34
|
+
[]
|
35
|
+
Memprof.stats(filename)
|
36
|
+
[]
|
37
|
+
Memprof.stats(filename)
|
38
|
+
|
39
|
+
filedata.strip.split("\n").size.should == 2
|
40
|
+
end
|
41
|
+
|
42
|
+
should 'clear stats after ::stats!' do
|
43
|
+
Memprof.start
|
44
|
+
[]
|
45
|
+
Memprof.stats!(filename)
|
46
|
+
Memprof.stats(filename)
|
47
|
+
|
48
|
+
filedata.strip.should.be.empty
|
49
|
+
end
|
50
|
+
|
51
|
+
should 'collect stats via ::track' do
|
52
|
+
Memprof.track(filename) do
|
53
|
+
"abc"
|
54
|
+
end
|
55
|
+
|
56
|
+
filedata.should =~ /1 #{__FILE__}:#{__LINE__-3}:String/
|
57
|
+
end
|
58
|
+
|
59
|
+
should 'dump objects as json' do
|
60
|
+
Memprof.start
|
61
|
+
1.23+1
|
62
|
+
Memprof.dump(filename)
|
63
|
+
|
64
|
+
filedata.should =~ /"source": "#{__FILE__}:#{__LINE__-3}"/
|
65
|
+
filedata.should =~ /"type": "float"/
|
66
|
+
filedata.should =~ /"data": 2\.23/
|
67
|
+
end
|
68
|
+
|
69
|
+
should 'raise error when calling ::stats or ::dump without ::start' do
|
70
|
+
lambda{ Memprof.stats }.should.raise(RuntimeError).message.should =~ /Memprof.start/
|
71
|
+
lambda{ Memprof.dump }.should.raise(RuntimeError).message.should =~ /Memprof.start/
|
72
|
+
end
|
73
|
+
|
74
|
+
should 'dump out the entire heap' do
|
75
|
+
Memprof.stop
|
76
|
+
Memprof.dump_all(filename)
|
77
|
+
|
78
|
+
File.open(filename, 'r').each_line do |line|
|
79
|
+
if line =~ /"data": "dump out the entire heap"/
|
80
|
+
break :found
|
81
|
+
end
|
82
|
+
end.should == :found
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
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.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe Damato
|
@@ -26,13 +26,22 @@ extra_rdoc_files: []
|
|
26
26
|
files:
|
27
27
|
- .gitignore
|
28
28
|
- README
|
29
|
+
- ext/arch.h
|
29
30
|
- ext/bin_api.h
|
30
31
|
- ext/elf.c
|
31
32
|
- ext/extconf.rb
|
33
|
+
- ext/i386.c
|
34
|
+
- ext/i386.h
|
32
35
|
- ext/mach.c
|
33
36
|
- ext/memprof.c
|
37
|
+
- ext/src/libdwarf-20091118.tar.gz
|
34
38
|
- ext/src/libelf-0.8.13.tar.gz
|
39
|
+
- ext/src/yajl-1.0.8.tar.gz
|
40
|
+
- ext/x86_64.c
|
41
|
+
- ext/x86_64.h
|
42
|
+
- ext/x86_gen.h
|
35
43
|
- memprof.gemspec
|
44
|
+
- spec/memprof_spec.rb
|
36
45
|
has_rdoc: true
|
37
46
|
homepage: http://github.com/ice799/memprof
|
38
47
|
licenses: []
|