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.
Binary file
@@ -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
@@ -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
@@ -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
@@ -1,6 +1,6 @@
1
1
  spec = Gem::Specification.new do |s|
2
2
  s.name = 'memprof'
3
- s.version = '0.1.3'
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.1.3
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: []