memprof 0.2.5 → 0.2.6
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 +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
|
|