memprof 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README +12 -16
- data/ext/arch.h +49 -6
- data/ext/bin_api.h +67 -19
- data/ext/elf.c +276 -56
- data/ext/mach.c +171 -58
- data/ext/memprof.c +91 -35
- data/ext/x86_64.c +77 -10
- data/ext/x86_64.h +86 -17
- data/ext/x86_gen.h +78 -38
- data/memprof.gemspec +2 -2
- metadata +3 -3
data/README
CHANGED
@@ -5,14 +5,6 @@ What is memprof?
|
|
5
5
|
Memprof is a memory profiler for Ruby that requires no patches to the Ruby VM.
|
6
6
|
It can help you find Ruby level memory leaks in your application.
|
7
7
|
|
8
|
-
Required to install
|
9
|
-
===================
|
10
|
-
If you are using the Linux version, you need to install libelf:
|
11
|
-
|
12
|
-
apt-get install libelfg0-dev
|
13
|
-
|
14
|
-
The experimental OSX version needs no additional libraries.
|
15
|
-
|
16
8
|
How to use
|
17
9
|
==========
|
18
10
|
|
@@ -36,30 +28,34 @@ Memprof.stats also takes an (optional) file name to write the output to a file.
|
|
36
28
|
|
37
29
|
Supported systems
|
38
30
|
=================
|
39
|
-
This only works on unstripped binaries
|
31
|
+
**This only works on unstripped binaries.**
|
40
32
|
|
41
33
|
Currently supporting:
|
42
34
|
|
43
|
-
Linux:
|
35
|
+
Linux (enable-shared AND disable-shared):
|
44
36
|
x86_64 builds of Ruby Enterprise Edition 1.8.6/1.8.7
|
45
|
-
x86_64 builds of MRI Ruby
|
37
|
+
x86_64 builds of MRI Ruby
|
38
|
+
|
39
|
+
Snow Leopard (enable-shared AND disable-shared):
|
40
|
+
x86_64 builds of Ruby Enterprise Edition 1.8.6/1.8.7
|
41
|
+
x86_64 builds of MRI Ruby
|
46
42
|
|
47
43
|
Experimental (somewhat broken) support:
|
48
44
|
|
49
|
-
Linux:
|
45
|
+
Linux and OSX:
|
50
46
|
i386/i686 support.
|
51
47
|
|
52
48
|
Snow Leopard:
|
53
|
-
|
54
|
-
OSX system Ruby, distributed with Snow Leopard
|
49
|
+
OSX system Ruby, distributed with Snow Leopard.
|
55
50
|
|
56
51
|
Coming soon:
|
57
52
|
|
58
|
-
Official support for Snow Leopard.
|
59
|
-
|
60
53
|
Linux:
|
61
54
|
Tracking object allocationns in C extensions.
|
62
55
|
|
56
|
+
Official support for Ruby 1.9
|
57
|
+
Official support for i386/i686
|
58
|
+
|
63
59
|
CREDITS
|
64
60
|
=======
|
65
61
|
Jake Douglas for the Mach O/snow leopard support.
|
data/ext/arch.h
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
#if !defined (_ARCH_H_)
|
2
2
|
#define _ARCH_H_
|
3
3
|
|
4
|
+
/*
|
5
|
+
* The only supported architectures at this time are i{3-6}86 and amd64. All
|
6
|
+
* other architectures should fail.
|
7
|
+
*/
|
4
8
|
#if defined(_ARCH_i386_) || defined(_ARCH_i686_)
|
5
9
|
#include "i386.h"
|
6
10
|
#elif defined(_ARCH_x86_64_)
|
@@ -10,24 +14,63 @@
|
|
10
14
|
#endif
|
11
15
|
|
12
16
|
/*
|
13
|
-
*
|
17
|
+
* arch_get_st2_tramp - architecture specific stage 2 trampoline getter
|
18
|
+
*
|
19
|
+
* This function will return a pointer to an area of memory that contains
|
20
|
+
* the stage 2 trampoline for this architecture.
|
21
|
+
*
|
22
|
+
* This function will also set the out parameter size equal to the size of this
|
23
|
+
* region, if size is not NULL.
|
14
24
|
*/
|
15
25
|
void *
|
16
26
|
arch_get_st2_tramp(size_t *size);
|
17
27
|
|
28
|
+
/*
|
29
|
+
* arch_insert_st1_tramp - architecture specific stage 1 trampoline insert
|
30
|
+
*
|
31
|
+
* Given:
|
32
|
+
* start - a start address to attempt to insert a trampoline at
|
33
|
+
* trampee - the address of the function to trampoline
|
34
|
+
* tramp - a pointer to the trampoline
|
35
|
+
*
|
36
|
+
* This function will inspect the start address to determine if an instruction
|
37
|
+
* which transfers execution to the trampee is present. If so, this function
|
38
|
+
* will rewrite the instruction to ensure that tramp is called instead.
|
39
|
+
*
|
40
|
+
* Returns 0 on success, 1 otherwise.
|
41
|
+
*/
|
18
42
|
int
|
19
43
|
arch_insert_st1_tramp(void *start, void *trampee, void *tramp);
|
20
44
|
|
21
45
|
/*
|
22
|
-
*
|
46
|
+
* arch_get_inline_st2_tramp - architecture specific inline stage 2 tramp getter
|
47
|
+
*
|
48
|
+
* This function will return a pointer to an area of memory that contains the
|
49
|
+
* stage 2 inline trampoline for this architecture.
|
50
|
+
*
|
51
|
+
* This function will also set the out parameter size equal to the size of this
|
52
|
+
* region, if size is not NULL.
|
23
53
|
*/
|
24
54
|
void *
|
25
55
|
arch_get_inline_st2_tramp(size_t *size);
|
26
56
|
|
57
|
+
/*
|
58
|
+
* Given:
|
59
|
+
* - addr - The base address of an instruction sequence.
|
60
|
+
*
|
61
|
+
* - marker - This is the marker to search for which will indicate that the
|
62
|
+
* instruction sequence has been located.
|
63
|
+
*
|
64
|
+
* - trampoline - The address of the handler to redirect execution to.
|
65
|
+
*
|
66
|
+
* - table_entry - Address of where the stage 2 trampoline code will reside
|
67
|
+
*
|
68
|
+
* This function will:
|
69
|
+
* Insert and setup the stage 1 and stage 2 trampolines if addr points to an
|
70
|
+
* instruction that could be from the inlined add_freelist function.
|
71
|
+
*
|
72
|
+
* This function returns 1 on failure and 0 on success.
|
73
|
+
*/
|
27
74
|
int
|
28
75
|
arch_insert_inline_st2_tramp(void *addr, void *marker, void *trampoline, void *table_entry);
|
29
|
-
|
30
|
-
void
|
31
|
-
arch_overwrite_got(void *plt, void *tramp);
|
32
|
-
|
33
76
|
#endif
|
data/ext/bin_api.h
CHANGED
@@ -3,42 +3,90 @@
|
|
3
3
|
#include <stddef.h>
|
4
4
|
#include <stdint.h>
|
5
5
|
|
6
|
-
/* XXX get rid of this */
|
7
|
-
extern size_t pagesize;
|
8
|
-
|
9
6
|
/*
|
10
|
-
*
|
7
|
+
* Instead of getting the pagesize from sysconf over and over again, pollute
|
8
|
+
* the global namespace with a dangerous name.
|
9
|
+
*
|
10
|
+
* XXX change this
|
11
11
|
*/
|
12
|
-
extern
|
13
|
-
extern size_t tramp_size;
|
12
|
+
extern size_t pagesize;
|
14
13
|
|
15
14
|
/*
|
16
|
-
*
|
15
|
+
* Some useful foward declarations for function protoypes here and elsewhere.
|
17
16
|
*/
|
18
|
-
|
19
|
-
|
17
|
+
struct tramp_st2_entry;
|
18
|
+
struct inline_tramp_st2_entry;
|
20
19
|
|
21
20
|
/*
|
22
|
-
*
|
21
|
+
* bin_init - Initialize the binary format specific layer.
|
23
22
|
*/
|
24
23
|
void
|
25
24
|
bin_init();
|
26
25
|
|
26
|
+
/*
|
27
|
+
* bin_find_symbol - find a symbol
|
28
|
+
*
|
29
|
+
* Given:
|
30
|
+
* - sym - a symbol name
|
31
|
+
* - size - optional out parameter
|
32
|
+
*
|
33
|
+
* This function will search for the symbol sym and return its address if
|
34
|
+
* found, or NULL if the symbol could not be found.
|
35
|
+
*
|
36
|
+
* Optionally, this function will set its out parameter size equal to the
|
37
|
+
* size of the symbol.
|
38
|
+
*/
|
27
39
|
void *
|
28
|
-
bin_find_symbol(char *sym, size_t *size);
|
40
|
+
bin_find_symbol(const char *sym, size_t *size);
|
29
41
|
|
42
|
+
/*
|
43
|
+
* bin_allocate_page - allocate a page suitable for trampolines
|
44
|
+
*
|
45
|
+
* This function will allocate a page of memory in the right area of the
|
46
|
+
* virtual address space and with the appropriate permissions for stage 2
|
47
|
+
* trampoline code to live and execute.
|
48
|
+
*/
|
30
49
|
void *
|
31
|
-
|
50
|
+
bin_allocate_page(void);
|
32
51
|
|
33
|
-
|
34
|
-
|
52
|
+
/*
|
53
|
+
* bin_type_size - Return size (in bytes) of a given type.
|
54
|
+
*
|
55
|
+
* Given:
|
56
|
+
* - type - a string representation of a type
|
57
|
+
*
|
58
|
+
* This function will return the size (in bytes) of the type. If no such type
|
59
|
+
* can be found, return 0.
|
60
|
+
*/
|
61
|
+
size_t
|
62
|
+
bin_type_size(const char *type);
|
35
63
|
|
64
|
+
/*
|
65
|
+
* bin_type_member_offset - Return the offset (in bytes) of member in type.
|
66
|
+
*
|
67
|
+
* Given:
|
68
|
+
* - type - a string representation of a type
|
69
|
+
* - member - a string representation of a field int he type
|
70
|
+
*
|
71
|
+
* This function will return the offset (in bytes) of member in type.
|
72
|
+
*
|
73
|
+
* On failure, this function returns -1.
|
74
|
+
*/
|
36
75
|
int
|
37
|
-
|
76
|
+
bin_type_member_offset(const char *type, const char *member);
|
38
77
|
|
78
|
+
/*
|
79
|
+
* bin_update_image - Update a binary image in memory
|
80
|
+
*
|
81
|
+
* Given:
|
82
|
+
* - trampee - the name of the symbol to hook
|
83
|
+
* - tramp - the stage 2 trampoline entry
|
84
|
+
*
|
85
|
+
* this function will update the binary image so that all calls to trampee will
|
86
|
+
* be routed to tramp.
|
87
|
+
*
|
88
|
+
* Returns 0 on success.
|
89
|
+
*/
|
39
90
|
int
|
40
|
-
|
41
|
-
|
42
|
-
void
|
43
|
-
bin_update_image(int entry, char *trampee_addr, struct tramp_st2_entry *tramp);
|
91
|
+
bin_update_image(const char *trampee_addr, struct tramp_st2_entry *tramp);
|
44
92
|
#endif
|
data/ext/elf.c
CHANGED
@@ -3,12 +3,14 @@
|
|
3
3
|
#include "bin_api.h"
|
4
4
|
#include "arch.h"
|
5
5
|
|
6
|
+
#include <assert.h>
|
6
7
|
#include <dwarf.h>
|
7
8
|
#include <err.h>
|
8
9
|
#include <error.h>
|
9
10
|
#include <fcntl.h>
|
10
11
|
#include <libdwarf.h>
|
11
12
|
#include <libelf/gelf.h>
|
13
|
+
#include <limits.h>
|
12
14
|
#include <link.h>
|
13
15
|
#include <stdio.h>
|
14
16
|
#include <stdlib.h>
|
@@ -18,11 +20,15 @@
|
|
18
20
|
|
19
21
|
#include <sys/mman.h>
|
20
22
|
|
21
|
-
/*
|
22
|
-
|
23
|
+
/* The size of a PLT entry */
|
24
|
+
#define PLT_ENTRY_SZ (16)
|
25
|
+
|
26
|
+
/* Keep track of whether this ruby is built with a shared library or not */
|
27
|
+
static int libruby = 0;
|
23
28
|
|
24
29
|
static Dwarf_Debug dwrf = NULL;
|
25
30
|
|
31
|
+
/* Set of ELF specific state about this Ruby binary/shared object */
|
26
32
|
static struct elf_info *ruby_info = NULL;
|
27
33
|
|
28
34
|
struct elf_info {
|
@@ -54,20 +60,88 @@ struct elf_info {
|
|
54
60
|
const char *filename;
|
55
61
|
};
|
56
62
|
|
63
|
+
/*
|
64
|
+
* plt_entry - procedure linkage table entry
|
65
|
+
*
|
66
|
+
* This struct is intended to be "laid onto" a piece of memory to ease the
|
67
|
+
* parsing, use, modification, and length calculation of PLT entries.
|
68
|
+
*
|
69
|
+
* For example:
|
70
|
+
* jmpq *0xaaf00d(%rip) # jump to GOT entry
|
71
|
+
*
|
72
|
+
* # the following instructions are only hit if function has not been
|
73
|
+
* # resolved previously.
|
74
|
+
*
|
75
|
+
* pushq $0x4e # push ID
|
76
|
+
* jmpq 0xcafebabefeedface # invoke the link linker
|
77
|
+
*/
|
78
|
+
struct plt_entry {
|
79
|
+
unsigned char jmp[2];
|
80
|
+
int32_t jmp_disp;
|
81
|
+
|
82
|
+
/* There is no reason (currently) to represent the pushq and jmpq
|
83
|
+
* instructions which invoke the linker.
|
84
|
+
*
|
85
|
+
* We don't need those to hook the GOT; we only need the jmp_disp above.
|
86
|
+
*
|
87
|
+
* TODO represent the extra instructions
|
88
|
+
*/
|
89
|
+
unsigned char pad[10];
|
90
|
+
} __attribute__((__packed__));
|
91
|
+
|
92
|
+
/*
|
93
|
+
* get_got_addr - given a PLT entry, return the global offset table entry that
|
94
|
+
* the entry uses.
|
95
|
+
*/
|
96
|
+
static void *
|
97
|
+
get_got_addr(struct plt_entry *plt)
|
98
|
+
{
|
99
|
+
assert(plt != NULL);
|
100
|
+
/* the jump is relative to the start of the next instruction. */
|
101
|
+
return (void *)&(plt->pad) + plt->jmp_disp;
|
102
|
+
}
|
103
|
+
|
104
|
+
/*
|
105
|
+
* overwrite_got - given the address of a PLT entry, overwrite the address
|
106
|
+
* in the GOT that the PLT entry uses with the address in tramp.
|
107
|
+
*/
|
108
|
+
static void
|
109
|
+
overwrite_got(void *plt, void *tramp)
|
110
|
+
{
|
111
|
+
assert(plt != NULL);
|
112
|
+
assert(tramp != NULL);
|
113
|
+
memcpy(get_got_addr(plt), &tramp, sizeof(void *));
|
114
|
+
return;
|
115
|
+
}
|
57
116
|
|
117
|
+
/*
|
118
|
+
* do_bin_allocate_page - internal page allocation routine
|
119
|
+
*
|
120
|
+
* This function allocates a page suitable for stage 2 trampolines. This page
|
121
|
+
* is allocated based on the location of the text segment of the Ruby binary
|
122
|
+
* or libruby.
|
123
|
+
*
|
124
|
+
* The page has to be located in a 32bit window from the Ruby code so that
|
125
|
+
* jump and call instructions can redirect execution there.
|
126
|
+
*
|
127
|
+
* This function returns the address of the page found or NULL if no page was
|
128
|
+
* found.
|
129
|
+
*/
|
58
130
|
static void *
|
59
|
-
do_bin_allocate_page(
|
131
|
+
do_bin_allocate_page(struct elf_info *info)
|
60
132
|
{
|
61
133
|
void * ret = NULL, *addr = NULL;
|
62
|
-
|
63
|
-
uint16_t max = ~0, count = 0;
|
134
|
+
uint16_t count = 0;
|
64
135
|
|
65
136
|
if (!info)
|
66
137
|
return NULL;
|
67
138
|
|
68
|
-
if (
|
139
|
+
if (libruby) {
|
140
|
+
/* There is a libruby. Start at the end of the text segment and search for
|
141
|
+
* a page.
|
142
|
+
*/
|
69
143
|
addr = info->text_segment + info->text_segment_len;
|
70
|
-
for (; count <
|
144
|
+
for (; count < UINT_MAX; addr += pagesize, count += pagesize) {
|
71
145
|
ret = mmap(addr, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANON|MAP_PRIVATE, -1, 0);
|
72
146
|
if (ret != MAP_FAILED) {
|
73
147
|
memset(ret, 0x90, pagesize);
|
@@ -75,34 +149,69 @@ do_bin_allocate_page(void *cookie)
|
|
75
149
|
}
|
76
150
|
}
|
77
151
|
} else {
|
152
|
+
/* if there is no libruby, use the linux specific MAP_32BIT flag which will
|
153
|
+
* grab a page in the lower 4gb of the address space.
|
154
|
+
*/
|
155
|
+
assert((size_t)info->text_segment <= UINT_MAX);
|
78
156
|
return mmap(NULL, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANON|MAP_PRIVATE|MAP_32BIT, -1, 0);
|
79
157
|
}
|
80
158
|
|
81
159
|
return NULL;
|
82
160
|
}
|
83
161
|
|
162
|
+
/*
|
163
|
+
* bin_allocate_page - allocate a page suitable for holding stage 2 trampolines
|
164
|
+
*
|
165
|
+
* This function is just a wrapper which passes through some internal state.
|
166
|
+
*/
|
84
167
|
void *
|
85
168
|
bin_allocate_page()
|
86
169
|
{
|
87
170
|
return do_bin_allocate_page(ruby_info);
|
88
171
|
}
|
89
172
|
|
173
|
+
/*
|
174
|
+
* get_plt_addr - architecture specific PLT entry retrieval
|
175
|
+
*
|
176
|
+
* Given the internal data and an index, this function returns the address of
|
177
|
+
* the PLT entry at that address.
|
178
|
+
*
|
179
|
+
* A PLT entry takes the form:
|
180
|
+
*
|
181
|
+
* jmpq *0xfeedface(%rip)
|
182
|
+
* pushq $0xaa
|
183
|
+
* jmpq 17110
|
184
|
+
*/
|
90
185
|
static inline GElf_Addr
|
91
|
-
|
186
|
+
get_plt_addr(struct elf_info *info, size_t ndx) {
|
187
|
+
assert(info != NULL);
|
92
188
|
return info->base_addr + info->plt_addr + (ndx + 1) * 16;
|
93
189
|
}
|
94
190
|
|
191
|
+
/*
|
192
|
+
* find_got_addr - find the global offset table entry for specific symbol name.
|
193
|
+
*
|
194
|
+
* Given:
|
195
|
+
* - syname - the symbol name
|
196
|
+
* - info - internal information about the ELF object to search
|
197
|
+
*
|
198
|
+
* This function searches the .rela.plt section of an ELF binary, searcing for
|
199
|
+
* entries that match the symbol name passed in. If one is found, the address
|
200
|
+
* of corresponding entry in .plt is returned.
|
201
|
+
*/
|
95
202
|
static void *
|
96
|
-
|
203
|
+
find_plt_addr(const char *symname, struct elf_info *info)
|
97
204
|
{
|
205
|
+
assert(symname != NULL);
|
206
|
+
|
98
207
|
size_t i = 0;
|
99
|
-
struct elf_info *info = cookie;
|
100
208
|
|
101
|
-
if (
|
209
|
+
if (info == NULL) {
|
102
210
|
info = ruby_info;
|
103
211
|
}
|
104
212
|
|
105
|
-
|
213
|
+
/* Search through each of the .rela.plt entries */
|
214
|
+
for (i = 0; i < info->relplt_count; i++) {
|
106
215
|
GElf_Rela rela;
|
107
216
|
GElf_Sym sym;
|
108
217
|
GElf_Addr addr;
|
@@ -118,8 +227,12 @@ find_got_addr(char *symname, void *cookie)
|
|
118
227
|
return NULL;
|
119
228
|
|
120
229
|
name = info->dynstr + sym.st_name;
|
230
|
+
|
231
|
+
/* The name matches the name of the symbol passed in, so get the PLT entry
|
232
|
+
* address and return it.
|
233
|
+
*/
|
121
234
|
if (strcmp(symname, name) == 0) {
|
122
|
-
addr =
|
235
|
+
addr = get_plt_addr(info, i);
|
123
236
|
return (void *)addr;
|
124
237
|
}
|
125
238
|
}
|
@@ -128,14 +241,33 @@ find_got_addr(char *symname, void *cookie)
|
|
128
241
|
return NULL;
|
129
242
|
}
|
130
243
|
|
244
|
+
/*
|
245
|
+
* do_bin_find_symbol - internal symbol lookup function.
|
246
|
+
*
|
247
|
+
* Given:
|
248
|
+
* - sym - the symbol name to look up
|
249
|
+
* - size - an optional out argument holding the size of the symbol
|
250
|
+
* - elf - an elf information structure
|
251
|
+
*
|
252
|
+
* This function will return the address of the symbol (setting size if desired)
|
253
|
+
* or NULL if nothing can be found.
|
254
|
+
*/
|
131
255
|
static void *
|
132
|
-
do_bin_find_symbol(char *sym, size_t *size, struct elf_info *elf)
|
256
|
+
do_bin_find_symbol(const char *sym, size_t *size, struct elf_info *elf)
|
133
257
|
{
|
134
258
|
char *name = NULL;
|
135
259
|
|
260
|
+
assert(sym != NULL);
|
261
|
+
assert(elf != NULL);
|
262
|
+
|
263
|
+
assert(elf->symtab_data != NULL);
|
264
|
+
assert(elf->symtab_data->d_buf != NULL);
|
265
|
+
|
136
266
|
ElfW(Sym) *esym = (ElfW(Sym)*) elf->symtab_data->d_buf;
|
137
267
|
ElfW(Sym) *lastsym = (ElfW(Sym)*) ((char*) elf->symtab_data->d_buf + elf->symtab_data->d_size);
|
138
268
|
|
269
|
+
assert(esym <= lastsym);
|
270
|
+
|
139
271
|
for (; esym < lastsym; esym++){
|
140
272
|
/* ignore weak/numeric/empty symbols */
|
141
273
|
if ((esym->st_value == 0) ||
|
@@ -144,6 +276,9 @@ do_bin_find_symbol(char *sym, size_t *size, struct elf_info *elf)
|
|
144
276
|
continue;
|
145
277
|
|
146
278
|
name = elf_strptr(elf->elf, elf->symtab_shdr.sh_link, (size_t)esym->st_name);
|
279
|
+
|
280
|
+
assert(name != NULL);
|
281
|
+
|
147
282
|
if (strcmp(name, sym) == 0) {
|
148
283
|
if (size) {
|
149
284
|
*size = esym->st_size;
|
@@ -154,38 +289,64 @@ do_bin_find_symbol(char *sym, size_t *size, struct elf_info *elf)
|
|
154
289
|
return NULL;
|
155
290
|
}
|
156
291
|
|
292
|
+
/*
|
293
|
+
* bin_find_symbol - find the address of a given symbol and set its size if
|
294
|
+
* desired.
|
295
|
+
*
|
296
|
+
* This function is just a wrapper for the internal symbol lookup function.
|
297
|
+
*/
|
157
298
|
void *
|
158
|
-
bin_find_symbol(char *sym, size_t *size)
|
299
|
+
bin_find_symbol(const char *sym, size_t *size)
|
159
300
|
{
|
160
301
|
return do_bin_find_symbol(sym, size, ruby_info);
|
161
302
|
}
|
162
303
|
|
163
|
-
|
164
|
-
bin_update_image
|
304
|
+
/*
|
305
|
+
* bin_update_image - update the ruby binary image in memory.
|
306
|
+
*
|
307
|
+
* Given -
|
308
|
+
* trampee - the name of the symbol to hook
|
309
|
+
* tramp - the stage 2 trampoline entry
|
310
|
+
*
|
311
|
+
* This function will update the ruby binary image so that all calls to trampee
|
312
|
+
* will be routed to tramp.
|
313
|
+
*
|
314
|
+
* Returns 0 on success
|
315
|
+
*/
|
316
|
+
int
|
317
|
+
bin_update_image(const char *trampee, struct tramp_st2_entry *tramp)
|
165
318
|
{
|
166
319
|
void *trampee_addr = NULL;
|
167
320
|
|
168
|
-
|
321
|
+
assert(trampee != NULL);
|
322
|
+
assert(tramp != NULL);
|
323
|
+
assert(tramp->addr != NULL);
|
324
|
+
|
325
|
+
if (!libruby) {
|
169
326
|
unsigned char *byte = ruby_info->text_segment;
|
170
327
|
trampee_addr = bin_find_symbol(trampee, NULL);
|
171
328
|
size_t count = 0;
|
172
329
|
int num = 0;
|
173
330
|
|
331
|
+
assert(byte != NULL);
|
332
|
+
assert(trampee_addr != NULL);
|
333
|
+
|
174
334
|
for(; count < ruby_info->text_segment_len; byte++, count++) {
|
175
|
-
if (arch_insert_st1_tramp(byte, trampee_addr, tramp)) {
|
176
|
-
// printf("tramped %x\n", byte);
|
335
|
+
if (arch_insert_st1_tramp(byte, trampee_addr, tramp) == 0) {
|
177
336
|
num++;
|
178
337
|
}
|
179
338
|
}
|
180
339
|
} else {
|
181
|
-
trampee_addr =
|
182
|
-
|
340
|
+
trampee_addr = find_plt_addr(trampee, NULL);
|
341
|
+
assert(trampee_addr != NULL);
|
342
|
+
overwrite_got(trampee_addr, tramp->addr);
|
183
343
|
}
|
344
|
+
return 0;
|
184
345
|
}
|
185
346
|
|
186
347
|
|
187
348
|
static Dwarf_Die
|
188
|
-
check_die(Dwarf_Die die, char *search, Dwarf_Half type)
|
349
|
+
check_die(Dwarf_Die die, const char *search, Dwarf_Half type)
|
189
350
|
{
|
190
351
|
char *name = 0;
|
191
352
|
Dwarf_Error error = 0;
|
@@ -217,7 +378,7 @@ check_die(Dwarf_Die die, char *search, Dwarf_Half type)
|
|
217
378
|
}
|
218
379
|
|
219
380
|
static Dwarf_Die
|
220
|
-
search_dies(Dwarf_Die die, char *name, Dwarf_Half type)
|
381
|
+
search_dies(Dwarf_Die die, const char *name, Dwarf_Half type)
|
221
382
|
{
|
222
383
|
int res = DW_DLV_ERROR;
|
223
384
|
Dwarf_Die cur_die=die;
|
@@ -269,7 +430,7 @@ search_dies(Dwarf_Die die, char *name, Dwarf_Half type)
|
|
269
430
|
}
|
270
431
|
|
271
432
|
static Dwarf_Die
|
272
|
-
find_die(char *name, Dwarf_Half type)
|
433
|
+
find_die(const char *name, Dwarf_Half type)
|
273
434
|
{
|
274
435
|
Dwarf_Die ret = 0;
|
275
436
|
Dwarf_Unsigned cu_header_length = 0;
|
@@ -328,32 +489,37 @@ find_die(char *name, Dwarf_Half type)
|
|
328
489
|
return ret ? ret : 0;
|
329
490
|
}
|
330
491
|
|
492
|
+
/*
|
493
|
+
* has_libruby - check if this ruby binary is linked against libruby.so
|
494
|
+
*
|
495
|
+
* This function checks if the curreny binary is linked against libruby. If
|
496
|
+
* so, it sets libruby = 1, and fill internal state in the elf_info structure.
|
497
|
+
*
|
498
|
+
* Returns 1 if this binary is linked to libruby.so, 0 if not.
|
499
|
+
*/
|
331
500
|
static int
|
332
|
-
|
501
|
+
has_libruby(struct elf_info *lib)
|
333
502
|
{
|
334
503
|
struct link_map *map = _r_debug.r_map;
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
lib->base_addr = (GElf_Addr)map->l_addr;
|
343
|
-
lib->filename = strdup(map->l_name);
|
344
|
-
}
|
345
|
-
has_libruby = 1;
|
346
|
-
break;
|
504
|
+
|
505
|
+
libruby = 0;
|
506
|
+
while (map) {
|
507
|
+
if (strstr(map->l_name, "libruby.so")) {
|
508
|
+
if (lib) {
|
509
|
+
lib->base_addr = (GElf_Addr)map->l_addr;
|
510
|
+
lib->filename = strdup(map->l_name);
|
347
511
|
}
|
348
|
-
|
512
|
+
libruby = 1;
|
513
|
+
break;
|
349
514
|
}
|
515
|
+
map = map->l_next;
|
350
516
|
}
|
351
517
|
|
352
|
-
return
|
518
|
+
return libruby;
|
353
519
|
}
|
354
520
|
|
355
|
-
|
356
|
-
bin_type_size(char *name)
|
521
|
+
size_t
|
522
|
+
bin_type_size(const char *name)
|
357
523
|
{
|
358
524
|
Dwarf_Unsigned size = 0;
|
359
525
|
Dwarf_Error error;
|
@@ -366,14 +532,14 @@ bin_type_size(char *name)
|
|
366
532
|
res = dwarf_bytesize(die, &size, &error);
|
367
533
|
dwarf_dealloc(dwrf,die,DW_DLA_DIE);
|
368
534
|
if (res == DW_DLV_OK)
|
369
|
-
return
|
535
|
+
return size;
|
370
536
|
}
|
371
537
|
|
372
|
-
return
|
538
|
+
return 0;
|
373
539
|
}
|
374
540
|
|
375
541
|
int
|
376
|
-
bin_type_member_offset(char *type, char *member)
|
542
|
+
bin_type_member_offset(const char *type, const char *member)
|
377
543
|
{
|
378
544
|
Dwarf_Error error;
|
379
545
|
int res = DW_DLV_ERROR;
|
@@ -403,12 +569,21 @@ bin_type_member_offset(char *type, char *member)
|
|
403
569
|
return -1;
|
404
570
|
}
|
405
571
|
|
572
|
+
/*
|
573
|
+
* open_elf - Opens a file from disk and gets the elf reader started.
|
574
|
+
*
|
575
|
+
* Given a filename, this function attempts to open the file and start the
|
576
|
+
* elf reader.
|
577
|
+
*
|
578
|
+
* Returns an Elf object.
|
579
|
+
*/
|
406
580
|
static Elf *
|
407
581
|
open_elf(const char *filename)
|
408
582
|
{
|
409
583
|
Elf *ret = NULL;
|
410
584
|
int fd = 0;
|
411
585
|
|
586
|
+
assert(filename != NULL);
|
412
587
|
if (elf_version(EV_CURRENT) == EV_NONE)
|
413
588
|
errx(EX_SOFTWARE, "ELF library initialization failed: %s",
|
414
589
|
elf_errmsg(-1));
|
@@ -423,30 +598,48 @@ open_elf(const char *filename)
|
|
423
598
|
if (elf_kind(ret) != ELF_K_ELF)
|
424
599
|
errx(EX_DATAERR, "%s is not an ELF object.", filename);
|
425
600
|
|
601
|
+
assert(ret != NULL);
|
426
602
|
return ret;
|
427
603
|
}
|
428
604
|
|
605
|
+
/*
|
606
|
+
* dissect_elf - Parses and stores internal data about an ELF object.
|
607
|
+
*
|
608
|
+
* Given an elf_info structure, this function will attempt to parse the object
|
609
|
+
* and store important state needed to rewrite the object later.
|
610
|
+
*/
|
429
611
|
static void
|
430
612
|
dissect_elf(struct elf_info *info)
|
431
613
|
{
|
614
|
+
assert(info != NULL);
|
615
|
+
|
432
616
|
size_t shstrndx = 0;
|
433
617
|
Elf *elf = info->elf;
|
434
618
|
Elf_Scn *scn = NULL;
|
435
619
|
GElf_Shdr shdr;
|
436
620
|
size_t j = 0;
|
437
621
|
|
438
|
-
if (elf_getshdrstrndx(elf, &shstrndx) == -1)
|
622
|
+
if (elf_getshdrstrndx(elf, &shstrndx) == -1) {
|
439
623
|
errx(EX_SOFTWARE, "getshstrndx() failed: %s.", elf_errmsg(-1));
|
624
|
+
}
|
440
625
|
|
441
|
-
if (gelf_getehdr(elf, &(info->ehdr)) == NULL)
|
626
|
+
if (gelf_getehdr(elf, &(info->ehdr)) == NULL) {
|
442
627
|
errx(EX_SOFTWARE, "Couldn't get elf header.");
|
628
|
+
}
|
443
629
|
|
630
|
+
/* search each ELF header and store important data for each header... */
|
444
631
|
while ((scn = elf_nextscn(elf, scn)) != NULL) {
|
445
632
|
if (gelf_getshdr(scn, &shdr) != &shdr)
|
446
633
|
errx(EX_SOFTWARE, "getshdr() failed: %s.",
|
447
634
|
elf_errmsg(-1));
|
448
635
|
|
449
|
-
|
636
|
+
|
637
|
+
/*
|
638
|
+
* The .dynamic section contains entries that are important to memprof.
|
639
|
+
* Specifically, the .rela.plt section information. The .rela.plt section
|
640
|
+
* indexes the .plt, which will be important for hooking functions in
|
641
|
+
* shared objects.
|
642
|
+
*/
|
450
643
|
if (shdr.sh_type == SHT_DYNAMIC) {
|
451
644
|
Elf_Data *data;
|
452
645
|
data = elf_getdata(scn, NULL);
|
@@ -465,7 +658,12 @@ dissect_elf(struct elf_info *info)
|
|
465
658
|
info->plt_size = dyn.d_un.d_val;
|
466
659
|
}
|
467
660
|
}
|
468
|
-
}
|
661
|
+
}
|
662
|
+
/*
|
663
|
+
* The .dynsym section has useful pieces, too, like the dynamic symbol
|
664
|
+
* table. This table is used when walking the .rela.plt section.
|
665
|
+
*/
|
666
|
+
else if (shdr.sh_type == SHT_DYNSYM) {
|
469
667
|
Elf_Data *data;
|
470
668
|
|
471
669
|
info->dynsym = elf_getdata(scn, NULL);
|
@@ -487,17 +685,29 @@ dissect_elf(struct elf_info *info)
|
|
487
685
|
"Couldn't get .dynstr data");
|
488
686
|
|
489
687
|
info->dynstr = data->d_buf;
|
490
|
-
}
|
688
|
+
}
|
689
|
+
/*
|
690
|
+
* Pull out information (start address and length) of the .text section.
|
691
|
+
*/
|
692
|
+
else if (shdr.sh_type == SHT_PROGBITS &&
|
491
693
|
(shdr.sh_flags == (SHF_ALLOC | SHF_EXECINSTR)) &&
|
492
694
|
strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), ".text") == 0) {
|
493
695
|
|
494
696
|
info->text_segment = (void *)shdr.sh_addr + info->base_addr;
|
495
697
|
info->text_segment_len = shdr.sh_size;
|
496
|
-
}
|
698
|
+
}
|
699
|
+
/*
|
700
|
+
* Pull out information (start address) of the .plt section.
|
701
|
+
*/
|
702
|
+
else if (shdr.sh_type == SHT_PROGBITS) {
|
497
703
|
if (strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), ".plt") == 0) {
|
498
704
|
info->plt_addr = shdr.sh_addr;
|
499
705
|
}
|
500
|
-
}
|
706
|
+
}
|
707
|
+
/*
|
708
|
+
* The symbol table is also needed for bin_find_symbol
|
709
|
+
*/
|
710
|
+
else if (shdr.sh_type == SHT_SYMTAB) {
|
501
711
|
info->symtab_shdr = shdr;
|
502
712
|
if ((info->symtab_data = elf_getdata(scn, info->symtab_data)) == NULL ||
|
503
713
|
info->symtab_data->d_size == 0) {
|
@@ -507,11 +717,15 @@ dissect_elf(struct elf_info *info)
|
|
507
717
|
}
|
508
718
|
}
|
509
719
|
|
720
|
+
/* If this object has no symbol table there's nothing else to do but fail */
|
510
721
|
if (!info->symtab_data) {
|
511
722
|
errx(EX_DATAERR, "binary is stripped. memprof only works on binaries that are not stripped!");
|
512
723
|
}
|
513
724
|
|
514
725
|
|
726
|
+
/*
|
727
|
+
* Walk the sections, pull out, and store the .plt section
|
728
|
+
*/
|
515
729
|
for (j = 1; j < info->ehdr.e_shnum; j++) {
|
516
730
|
scn = elf_getscn(elf, j);
|
517
731
|
if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL)
|
@@ -533,6 +747,11 @@ dissect_elf(struct elf_info *info)
|
|
533
747
|
return;
|
534
748
|
}
|
535
749
|
|
750
|
+
/*
|
751
|
+
* bin_init - initialize the binary parsing/modification layer.
|
752
|
+
*
|
753
|
+
* This function starts the elf parser and sets up internal state.
|
754
|
+
*/
|
536
755
|
void
|
537
756
|
bin_init()
|
538
757
|
{
|
@@ -540,11 +759,12 @@ bin_init()
|
|
540
759
|
|
541
760
|
ruby_info = calloc(1, sizeof(*ruby_info));
|
542
761
|
|
543
|
-
if (!ruby_info)
|
544
|
-
|
762
|
+
if (!ruby_info) {
|
763
|
+
errx(EX_UNAVAILABLE, "Unable to allocate memory to start binary parsing layer");
|
764
|
+
}
|
545
765
|
|
546
|
-
if (!
|
547
|
-
ruby_info->filename =
|
766
|
+
if (!has_libruby(ruby_info)) {
|
767
|
+
ruby_info->filename = "/proc/self/exe";
|
548
768
|
ruby_info->base_addr = 0;
|
549
769
|
}
|
550
770
|
|