memprof 0.2.7 → 0.2.9
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 +15 -6
- data/ext/bin_api.h +1 -9
- data/ext/elf.c +398 -81
- data/ext/extconf.rb +2 -0
- data/ext/mach.c +97 -86
- data/ext/memprof.c +267 -167
- data/ext/tramp.c +143 -0
- data/ext/tramp.h +23 -0
- data/ext/util.c +72 -0
- data/ext/util.h +40 -0
- data/ext/x86_64.c +2 -2
- data/lib/memprof/usr2.rb +1 -0
- data/memprof.gemspec +3 -3
- metadata +17 -5
data/README
CHANGED
@@ -28,8 +28,6 @@ Memprof.stats also takes an (optional) file name to write the output to a file.
|
|
28
28
|
|
29
29
|
Supported systems
|
30
30
|
=================
|
31
|
-
**This only works on unstripped binaries.**
|
32
|
-
|
33
31
|
Currently supporting:
|
34
32
|
|
35
33
|
Linux (enable-shared AND disable-shared):
|
@@ -40,19 +38,28 @@ Currently supporting:
|
|
40
38
|
x86_64 builds of Ruby Enterprise Edition 1.8.6/1.8.7
|
41
39
|
x86_64 builds of MRI Ruby
|
42
40
|
|
41
|
+
Support for unstripped binaries and stripped binaries if (and only if) the
|
42
|
+
symbol files live in /usr/lib/debug/
|
43
|
+
|
44
|
+
You can get symbol files for system Rubies by installing -dbg packages.
|
45
|
+
|
46
|
+
For example, on ubuntu: apt-get install libruby1.8-dbg
|
47
|
+
|
48
|
+
Snow Leopard:
|
49
|
+
x86_64 builds of Ruby Enterprise Edition 1.8.7
|
50
|
+
x86_64 builds of MRI Ruby (enable-shared and disable-shared)
|
51
|
+
Unstripped binaries only!
|
52
|
+
|
43
53
|
Experimental (somewhat broken) support:
|
44
54
|
|
45
55
|
Linux and OSX:
|
46
56
|
i386/i686 support.
|
47
57
|
|
48
58
|
Snow Leopard:
|
49
|
-
OSX system Ruby, distributed with Snow Leopard
|
59
|
+
OSX system Ruby, distributed with Snow Leopard
|
50
60
|
|
51
61
|
Coming soon:
|
52
62
|
|
53
|
-
Linux:
|
54
|
-
Tracking object allocationns in C extensions.
|
55
|
-
|
56
63
|
Official support for Ruby 1.9
|
57
64
|
Official support for i386/i686
|
58
65
|
|
@@ -61,3 +68,5 @@ CREDITS
|
|
61
68
|
Jake Douglas for the Mach O/snow leopard support.
|
62
69
|
|
63
70
|
Aman Gupta for various bug fixes and other cleanup.
|
71
|
+
|
72
|
+
Rob Benson for 1.9 support and cleanup.
|
data/ext/bin_api.h
CHANGED
@@ -3,14 +3,6 @@
|
|
3
3
|
#include <stddef.h>
|
4
4
|
#include <stdint.h>
|
5
5
|
|
6
|
-
/*
|
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
|
-
*/
|
12
|
-
extern size_t pagesize;
|
13
|
-
|
14
6
|
/*
|
15
7
|
* Some useful foward declarations for function protoypes here and elsewhere.
|
16
8
|
*/
|
@@ -100,5 +92,5 @@ bin_type_member_offset(const char *type, const char *member);
|
|
100
92
|
* Returns 0 on success.
|
101
93
|
*/
|
102
94
|
int
|
103
|
-
bin_update_image(const char *
|
95
|
+
bin_update_image(const char *trampee, struct tramp_st2_entry *tramp);
|
104
96
|
#endif
|
data/ext/elf.c
CHANGED
@@ -2,14 +2,17 @@
|
|
2
2
|
#define _GNU_SOURCE
|
3
3
|
#include "bin_api.h"
|
4
4
|
#include "arch.h"
|
5
|
+
#include "util.h"
|
5
6
|
|
6
7
|
#include <assert.h>
|
7
8
|
#include <dwarf.h>
|
8
9
|
#include <err.h>
|
9
10
|
#include <error.h>
|
11
|
+
#include <errno.h>
|
10
12
|
#include <fcntl.h>
|
11
13
|
#include <libdwarf.h>
|
12
14
|
#include <libelf/gelf.h>
|
15
|
+
#include <libgen.h>
|
13
16
|
#include <limits.h>
|
14
17
|
#include <link.h>
|
15
18
|
#include <stdio.h>
|
@@ -19,10 +22,18 @@
|
|
19
22
|
#include <unistd.h>
|
20
23
|
|
21
24
|
#include <sys/mman.h>
|
25
|
+
#include <sys/stat.h>
|
26
|
+
#include <sys/types.h>
|
27
|
+
|
28
|
+
extern struct memprof_config memprof_config;
|
22
29
|
|
23
30
|
/* The size of a PLT entry */
|
24
31
|
#define PLT_ENTRY_SZ (16)
|
25
32
|
|
33
|
+
/* The system-wide debug symbol directory */
|
34
|
+
/* XXX this should be set via extconf and not hardcoded */
|
35
|
+
#define DEBUGDIR "/usr/lib/debug"
|
36
|
+
|
26
37
|
/* Keep track of whether this ruby is built with a shared library or not */
|
27
38
|
static int libruby = 0;
|
28
39
|
|
@@ -32,6 +43,7 @@ static Dwarf_Debug dwrf = NULL;
|
|
32
43
|
static struct elf_info *ruby_info = NULL;
|
33
44
|
|
34
45
|
struct elf_info {
|
46
|
+
int fd;
|
35
47
|
Elf *elf;
|
36
48
|
|
37
49
|
GElf_Addr base_addr;
|
@@ -48,6 +60,10 @@ struct elf_info {
|
|
48
60
|
size_t plt_size;
|
49
61
|
size_t plt_count;
|
50
62
|
|
63
|
+
GElf_Addr debuglink_addr;
|
64
|
+
size_t debuglink_sz;
|
65
|
+
Elf_Data *debuglink_data;
|
66
|
+
|
51
67
|
GElf_Ehdr ehdr;
|
52
68
|
|
53
69
|
Elf_Data *dynsym;
|
@@ -58,8 +74,28 @@ struct elf_info {
|
|
58
74
|
Elf_Data *symtab_data;
|
59
75
|
|
60
76
|
const char *filename;
|
77
|
+
|
78
|
+
struct elf_info *debug_data;
|
61
79
|
};
|
62
80
|
|
81
|
+
/* These callback return statuses are used to tell the invoker of the callback
|
82
|
+
* (walk_linkmap) whether to continue walking or bail out.
|
83
|
+
*/
|
84
|
+
typedef enum {
|
85
|
+
CB_CONTINUE,
|
86
|
+
CB_EXIT,
|
87
|
+
} linkmap_cb_status;
|
88
|
+
|
89
|
+
/* A callback type that specifies a function that can be fired on each link map
|
90
|
+
* entry.
|
91
|
+
*/
|
92
|
+
typedef linkmap_cb_status (*linkmap_cb)(struct link_map *, void *);
|
93
|
+
|
94
|
+
static void open_elf(struct elf_info *info);
|
95
|
+
static int dissect_elf(struct elf_info *info, int find_debug);
|
96
|
+
static void *find_plt_addr(const char *symname, struct elf_info *info);
|
97
|
+
static void walk_linkmap(linkmap_cb cb, void *data);
|
98
|
+
|
63
99
|
/*
|
64
100
|
* plt_entry - procedure linkage table entry
|
65
101
|
*
|
@@ -106,7 +142,7 @@ get_got_addr(struct plt_entry *plt)
|
|
106
142
|
* in the GOT that the PLT entry uses with the address in tramp.
|
107
143
|
*/
|
108
144
|
static void
|
109
|
-
overwrite_got(void *plt, void *tramp)
|
145
|
+
overwrite_got(void *plt, const void *tramp)
|
110
146
|
{
|
111
147
|
assert(plt != NULL);
|
112
148
|
assert(tramp != NULL);
|
@@ -114,6 +150,79 @@ overwrite_got(void *plt, void *tramp)
|
|
114
150
|
return;
|
115
151
|
}
|
116
152
|
|
153
|
+
struct plt_hook_data {
|
154
|
+
const char *sym;
|
155
|
+
const void *tramp;
|
156
|
+
};
|
157
|
+
|
158
|
+
static linkmap_cb_status
|
159
|
+
hook_func_cb(struct link_map *map, void *data)
|
160
|
+
{
|
161
|
+
struct plt_hook_data *hook_data = data;
|
162
|
+
struct elf_info curr_lib;
|
163
|
+
void *trampee_addr = NULL;
|
164
|
+
|
165
|
+
/* skip a few things we don't care about */
|
166
|
+
if (strstr(map->l_name, "linux-vdso")) {
|
167
|
+
dbg_printf("found vdso (skipping): %s\n", map->l_name);
|
168
|
+
return CB_CONTINUE;
|
169
|
+
} else if (strstr(map->l_name, "ld-linux")) {
|
170
|
+
dbg_printf("found ld-linux (skipping): %s\n", map->l_name);
|
171
|
+
return CB_CONTINUE;
|
172
|
+
} else if (strstr(map->l_name, "libruby")) {
|
173
|
+
dbg_printf("found libruby (skipping): %s\n", map->l_name);
|
174
|
+
return CB_CONTINUE;
|
175
|
+
} else if (strstr(map->l_name, "memprof")) {
|
176
|
+
dbg_printf("found memprof (skipping): %s\n", map->l_name);
|
177
|
+
return CB_CONTINUE;
|
178
|
+
} else if (!map->l_name || map->l_name[0] == '\0') {
|
179
|
+
dbg_printf("found an empty string (skipping)\n");
|
180
|
+
return CB_CONTINUE;
|
181
|
+
}
|
182
|
+
|
183
|
+
memset(&curr_lib, 0, sizeof(curr_lib));
|
184
|
+
dbg_printf("trying to open elf object: %s\n", map->l_name);
|
185
|
+
curr_lib.filename = map->l_name;
|
186
|
+
open_elf(&curr_lib);
|
187
|
+
|
188
|
+
if (curr_lib.elf == NULL) {
|
189
|
+
dbg_printf("opening the elf object (%s) failed! (skipping)\n", map->l_name);
|
190
|
+
return CB_CONTINUE;
|
191
|
+
}
|
192
|
+
|
193
|
+
curr_lib.base_addr = map->l_addr;
|
194
|
+
curr_lib.filename = map->l_name;
|
195
|
+
|
196
|
+
if (dissect_elf(&curr_lib, 0) == 2) {
|
197
|
+
dbg_printf("elf file, %s hit an unrecoverable error (skipping)\n", map->l_name);
|
198
|
+
elf_end(curr_lib.elf);
|
199
|
+
close(curr_lib.fd);
|
200
|
+
return CB_CONTINUE;
|
201
|
+
}
|
202
|
+
|
203
|
+
dbg_printf("dissected the elf file: %s, base: %lx\n",
|
204
|
+
curr_lib.filename, (unsigned long)curr_lib.base_addr);
|
205
|
+
|
206
|
+
if ((trampee_addr = find_plt_addr(hook_data->sym, &curr_lib)) != NULL) {
|
207
|
+
dbg_printf("found: %s @ %p\n", hook_data->sym, trampee_addr);
|
208
|
+
overwrite_got(trampee_addr, hook_data->tramp);
|
209
|
+
}
|
210
|
+
|
211
|
+
elf_end(curr_lib.elf);
|
212
|
+
close(curr_lib.fd);
|
213
|
+
return CB_CONTINUE;
|
214
|
+
}
|
215
|
+
|
216
|
+
static void
|
217
|
+
hook_required_objects(void *tramp)
|
218
|
+
{
|
219
|
+
struct plt_hook_data data;
|
220
|
+
data.sym = "rb_newobj";
|
221
|
+
data.tramp = tramp;
|
222
|
+
walk_linkmap(hook_func_cb, &data);
|
223
|
+
return;
|
224
|
+
}
|
225
|
+
|
117
226
|
/*
|
118
227
|
* do_bin_allocate_page - internal page allocation routine
|
119
228
|
*
|
@@ -131,7 +240,7 @@ static void *
|
|
131
240
|
do_bin_allocate_page(struct elf_info *info)
|
132
241
|
{
|
133
242
|
void * ret = NULL, *addr = NULL;
|
134
|
-
|
243
|
+
uint32_t count = 0;
|
135
244
|
|
136
245
|
if (!info)
|
137
246
|
return NULL;
|
@@ -141,10 +250,10 @@ do_bin_allocate_page(struct elf_info *info)
|
|
141
250
|
* a page.
|
142
251
|
*/
|
143
252
|
addr = info->text_segment + info->text_segment_len;
|
144
|
-
for (; count <
|
145
|
-
ret = mmap(addr, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANON|MAP_PRIVATE, -1, 0);
|
253
|
+
for (; count < USHRT_MAX; addr += memprof_config.pagesize, count += memprof_config.pagesize) {
|
254
|
+
ret = mmap(addr, memprof_config.pagesize, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANON|MAP_PRIVATE, -1, 0);
|
146
255
|
if (ret != MAP_FAILED) {
|
147
|
-
memset(ret, 0x90, pagesize);
|
256
|
+
memset(ret, 0x90, memprof_config.pagesize);
|
148
257
|
return ret;
|
149
258
|
}
|
150
259
|
}
|
@@ -153,7 +262,7 @@ do_bin_allocate_page(struct elf_info *info)
|
|
153
262
|
* grab a page in the lower 4gb of the address space.
|
154
263
|
*/
|
155
264
|
assert((size_t)info->text_segment <= UINT_MAX);
|
156
|
-
return mmap(NULL, pagesize, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANON|MAP_PRIVATE|MAP_32BIT, -1, 0);
|
265
|
+
return mmap(NULL, memprof_config.pagesize, PROT_WRITE|PROT_READ|PROT_EXEC, MAP_ANON|MAP_PRIVATE|MAP_32BIT, -1, 0);
|
157
266
|
}
|
158
267
|
|
159
268
|
return NULL;
|
@@ -185,7 +294,8 @@ bin_allocate_page()
|
|
185
294
|
static inline GElf_Addr
|
186
295
|
get_plt_addr(struct elf_info *info, size_t ndx) {
|
187
296
|
assert(info != NULL);
|
188
|
-
|
297
|
+
dbg_printf("file: %s, base: %lx\n", info->filename, (unsigned long)info->base_addr);
|
298
|
+
return info->base_addr + info->plt_addr + (ndx + 1) * PLT_ENTRY_SZ;
|
189
299
|
}
|
190
300
|
|
191
301
|
/*
|
@@ -277,9 +387,7 @@ do_bin_find_symbol(const char *sym, size_t *size, struct elf_info *elf)
|
|
277
387
|
|
278
388
|
name = elf_strptr(elf->elf, elf->symtab_shdr.sh_link, (size_t)esym->st_name);
|
279
389
|
|
280
|
-
|
281
|
-
|
282
|
-
if (strcmp(name, sym) == 0) {
|
390
|
+
if (name && strcmp(name, sym) == 0) {
|
283
391
|
if (size) {
|
284
392
|
*size = esym->st_size;
|
285
393
|
}
|
@@ -393,6 +501,13 @@ bin_update_image(const char *trampee, struct tramp_st2_entry *tramp)
|
|
393
501
|
assert(trampee_addr != NULL);
|
394
502
|
overwrite_got(trampee_addr, tramp->addr);
|
395
503
|
}
|
504
|
+
|
505
|
+
if (strcmp("rb_newobj", trampee) == 0) {
|
506
|
+
dbg_printf("Trying to hook rb_newobj in other libraries...\n");
|
507
|
+
hook_required_objects(tramp->addr);
|
508
|
+
dbg_printf("Done searching other libraries for rb_newobj!\n");
|
509
|
+
}
|
510
|
+
|
396
511
|
return 0;
|
397
512
|
}
|
398
513
|
|
@@ -542,31 +657,80 @@ find_die(const char *name, Dwarf_Half type)
|
|
542
657
|
}
|
543
658
|
|
544
659
|
/*
|
545
|
-
*
|
660
|
+
* find_libruby_cb - a callback which attempts to locate libruby in the linkmap
|
546
661
|
*
|
547
|
-
* This
|
548
|
-
*
|
662
|
+
* This callback is fired from walk_linkmap for each item on the linkmap. This
|
663
|
+
* function searchs for libruby and stores data about it in the object passed
|
664
|
+
* through.
|
549
665
|
*
|
550
|
-
*
|
666
|
+
* This function return CB_EXIT once libruby is found to terminate the link map
|
667
|
+
* walker. If libruby isn't found, this function returns CB_CONTINUE.
|
551
668
|
*/
|
552
|
-
static
|
553
|
-
|
669
|
+
static linkmap_cb_status
|
670
|
+
find_libruby_cb(struct link_map *map, void *data)
|
671
|
+
{
|
672
|
+
struct elf_info *lib = data;
|
673
|
+
|
674
|
+
assert(map != NULL);
|
675
|
+
assert(data != NULL);
|
676
|
+
|
677
|
+
if (strstr(map->l_name, "libruby")) {
|
678
|
+
dbg_printf("Found a libruby.so!\n");
|
679
|
+
if (lib) {
|
680
|
+
lib->base_addr = (GElf_Addr)map->l_addr;
|
681
|
+
lib->filename = strdup(map->l_name);
|
682
|
+
}
|
683
|
+
libruby = 1;
|
684
|
+
return CB_EXIT;
|
685
|
+
}
|
686
|
+
return CB_CONTINUE;
|
687
|
+
}
|
688
|
+
|
689
|
+
/*
|
690
|
+
* walk_linkmap - walk the linkmap firing a callback along the way.
|
691
|
+
*
|
692
|
+
* Given:
|
693
|
+
* - cb - a callback function
|
694
|
+
* - data - and any private data to pass through (optional)
|
695
|
+
*
|
696
|
+
* This function will crawl the linkmap and fire the callback on each item
|
697
|
+
* found.
|
698
|
+
*
|
699
|
+
* If the callback returns CB_CONTINUE, this function will move to the next
|
700
|
+
* (if any) item in the link map.
|
701
|
+
*
|
702
|
+
* If the callback returns CB_EXIT, this function will return immediately.
|
703
|
+
*/
|
704
|
+
static void
|
705
|
+
walk_linkmap(linkmap_cb cb, void *data)
|
554
706
|
{
|
555
707
|
struct link_map *map = _r_debug.r_map;
|
556
708
|
|
557
|
-
|
709
|
+
assert(map);
|
710
|
+
|
558
711
|
while (map) {
|
559
|
-
|
560
|
-
|
561
|
-
lib->base_addr = (GElf_Addr)map->l_addr;
|
562
|
-
lib->filename = strdup(map->l_name);
|
563
|
-
}
|
564
|
-
libruby = 1;
|
712
|
+
dbg_printf("Found a linkmap entry: %s\n", map->l_name);
|
713
|
+
if (cb(map, data) == CB_EXIT)
|
565
714
|
break;
|
566
|
-
}
|
567
715
|
map = map->l_next;
|
568
716
|
}
|
569
717
|
|
718
|
+
return;
|
719
|
+
}
|
720
|
+
|
721
|
+
/*
|
722
|
+
* has_libruby - check if this ruby binary is linked against libruby.so
|
723
|
+
*
|
724
|
+
* This function checks if the curreny binary is linked against libruby. If
|
725
|
+
* so, it sets libruby = 1, and fill internal state in the elf_info structure.
|
726
|
+
*
|
727
|
+
* Returns 1 if this binary is linked to libruby.so, 0 if not.
|
728
|
+
*/
|
729
|
+
static int
|
730
|
+
has_libruby(struct elf_info *lib)
|
731
|
+
{
|
732
|
+
libruby = 0;
|
733
|
+
walk_linkmap(find_libruby_cb, lib);
|
570
734
|
return libruby;
|
571
735
|
}
|
572
736
|
|
@@ -627,31 +791,131 @@ bin_type_member_offset(const char *type, const char *member)
|
|
627
791
|
* Given a filename, this function attempts to open the file and start the
|
628
792
|
* elf reader.
|
629
793
|
*
|
630
|
-
* Returns an
|
794
|
+
* Returns an elf_info object that must be freed by the caller.
|
631
795
|
*/
|
632
|
-
static
|
633
|
-
open_elf(
|
796
|
+
static void
|
797
|
+
open_elf(struct elf_info *info)
|
634
798
|
{
|
635
|
-
Elf *ret = NULL;
|
636
|
-
int fd = 0;
|
637
799
|
|
638
|
-
assert(
|
800
|
+
assert(info != NULL);
|
801
|
+
|
639
802
|
if (elf_version(EV_CURRENT) == EV_NONE)
|
640
|
-
errx(EX_SOFTWARE, "ELF library initialization failed: %s",
|
641
|
-
elf_errmsg(-1));
|
803
|
+
errx(EX_SOFTWARE, "ELF library initialization failed: %s", elf_errmsg(-1));
|
642
804
|
|
643
|
-
if ((fd = open(filename, O_RDONLY, 0)) < 0)
|
644
|
-
err(EX_NOINPUT, "open \%s\" failed", filename);
|
805
|
+
if ((info->fd = open(info->filename, O_RDONLY, 0)) < 0)
|
806
|
+
err(EX_NOINPUT, "open \%s\" failed", info->filename);
|
645
807
|
|
646
|
-
if ((
|
647
|
-
errx(EX_SOFTWARE, "elf_begin() failed: %s.",
|
648
|
-
elf_errmsg(-1));
|
808
|
+
if ((info->elf = elf_begin(info->fd, ELF_C_READ, NULL)) == NULL)
|
809
|
+
errx(EX_SOFTWARE, "elf_begin() failed: %s.", elf_errmsg(-1));
|
649
810
|
|
650
|
-
if (elf_kind(
|
651
|
-
errx(EX_DATAERR, "%s is not an ELF object.", filename);
|
811
|
+
if (elf_kind(info->elf) != ELF_K_ELF)
|
812
|
+
errx(EX_DATAERR, "%s is not an ELF object.", info->filename);
|
652
813
|
|
653
|
-
|
654
|
-
|
814
|
+
return;
|
815
|
+
}
|
816
|
+
|
817
|
+
static char *
|
818
|
+
get_debuglink_info(struct elf_info *elf, unsigned long *crc_out)
|
819
|
+
{
|
820
|
+
char *basename = (char *) elf->debuglink_data->d_buf;
|
821
|
+
unsigned long offset = strlen(basename) + 1;
|
822
|
+
offset = (offset + 3) & ~3;
|
823
|
+
|
824
|
+
memcpy(crc_out, elf->debuglink_data->d_buf + offset, 4);
|
825
|
+
|
826
|
+
return basename;
|
827
|
+
}
|
828
|
+
|
829
|
+
static int
|
830
|
+
verify_debug_checksum(const char *filename, unsigned long crc)
|
831
|
+
{
|
832
|
+
struct stat stat_buf;
|
833
|
+
void *region = NULL;
|
834
|
+
int fd = open(filename, O_RDONLY);
|
835
|
+
unsigned long crc_check = 0;
|
836
|
+
|
837
|
+
if (fd == -1) {
|
838
|
+
dbg_printf("Couldn't open debug file: %s, because: %s\n", filename, strerror(errno));
|
839
|
+
return 1;
|
840
|
+
}
|
841
|
+
|
842
|
+
if (fstat(fd, &stat_buf) == -1) {
|
843
|
+
dbg_printf("Couldn't stat debug file: %s, because: %s\n", filename, strerror(errno));
|
844
|
+
return 1;
|
845
|
+
}
|
846
|
+
|
847
|
+
region = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
848
|
+
if (region == MAP_FAILED) {
|
849
|
+
dbg_printf("mapping a section of size: %zd has failed!\n", stat_buf.st_size);
|
850
|
+
return 1;
|
851
|
+
}
|
852
|
+
|
853
|
+
crc_check = gnu_debuglink_crc32(crc_check, region, stat_buf.st_size);
|
854
|
+
dbg_printf("supposed crc: %lx, computed crc: %lx\n", crc, crc_check);
|
855
|
+
|
856
|
+
munmap(region, stat_buf.st_size);
|
857
|
+
close(fd);
|
858
|
+
|
859
|
+
return !(crc == crc_check);
|
860
|
+
}
|
861
|
+
|
862
|
+
static int
|
863
|
+
find_debug_syms(struct elf_info *elf)
|
864
|
+
{
|
865
|
+
/*
|
866
|
+
* XXX TODO perhaps allow users to specify an env var (or something) pointing
|
867
|
+
* to where the debug symbols live?
|
868
|
+
*/
|
869
|
+
unsigned long crc = 0;
|
870
|
+
char *basename = get_debuglink_info(elf, &crc);
|
871
|
+
char *debug_file = NULL, *dir = NULL;
|
872
|
+
char *tmp = strdup(elf->filename);
|
873
|
+
|
874
|
+
assert(basename != NULL);
|
875
|
+
dbg_printf(".gnu_debuglink base file name: %s, crc: %lx\n", basename, crc);
|
876
|
+
|
877
|
+
dir = dirname(tmp);
|
878
|
+
debug_file = malloc(strlen(DEBUGDIR) + strlen(dir) +
|
879
|
+
strlen("/") + strlen(basename) + 1);
|
880
|
+
|
881
|
+
strncat(debug_file, DEBUGDIR, strlen(DEBUGDIR));
|
882
|
+
strncat(debug_file, dir, strlen(dir));
|
883
|
+
strncat(debug_file, "/", strlen("/"));
|
884
|
+
strncat(debug_file, basename, strlen(basename));
|
885
|
+
|
886
|
+
elf->debug_data = calloc(1, sizeof(*elf->debug_data));
|
887
|
+
elf->debug_data->filename = debug_file;
|
888
|
+
|
889
|
+
dbg_printf("Possible debug symbols in: %s\n", elf->debug_data->filename);
|
890
|
+
|
891
|
+
if (verify_debug_checksum(elf->debug_data->filename, crc)) {
|
892
|
+
dbg_printf("Checksum verification of debug file: %s failed!", elf->debug_data->filename);
|
893
|
+
free(debug_file);
|
894
|
+
free(elf->debug_data);
|
895
|
+
return 1;
|
896
|
+
}
|
897
|
+
|
898
|
+
open_elf(elf->debug_data);
|
899
|
+
|
900
|
+
if (dissect_elf(elf->debug_data, 0) != 0) {
|
901
|
+
/* free debug_data */
|
902
|
+
dbg_printf("Dissection of debug data failed!\n");
|
903
|
+
free(debug_file);
|
904
|
+
free(elf->debug_data);
|
905
|
+
return 1;
|
906
|
+
}
|
907
|
+
|
908
|
+
elf->symtab_data = elf->debug_data->symtab_data;
|
909
|
+
elf->symtab_shdr = elf->debug_data->symtab_shdr;
|
910
|
+
|
911
|
+
/* XXX free stuff */
|
912
|
+
/* LEAK elf_end(elf->elf); */
|
913
|
+
|
914
|
+
elf->elf = elf->debug_data->elf;
|
915
|
+
close(elf->debug_data->fd);
|
916
|
+
|
917
|
+
dbg_printf("Finished dissecting debug data\n");
|
918
|
+
return 0;
|
655
919
|
}
|
656
920
|
|
657
921
|
/*
|
@@ -659,32 +923,43 @@ open_elf(const char *filename)
|
|
659
923
|
*
|
660
924
|
* Given an elf_info structure, this function will attempt to parse the object
|
661
925
|
* and store important state needed to rewrite the object later.
|
926
|
+
*
|
927
|
+
* TODO better error handling
|
928
|
+
*
|
929
|
+
* Returns 1 on hard errors.
|
930
|
+
* Returns 2 on recoverable errors (missing symbol table).
|
931
|
+
* Returns 0 on success.
|
662
932
|
*/
|
663
|
-
static
|
664
|
-
dissect_elf(struct elf_info *info)
|
933
|
+
static int
|
934
|
+
dissect_elf(struct elf_info *info, int find_debug)
|
665
935
|
{
|
666
936
|
assert(info != NULL);
|
667
937
|
|
668
|
-
size_t shstrndx = 0;
|
938
|
+
size_t shstrndx = 0, j = 0;
|
669
939
|
Elf *elf = info->elf;
|
670
940
|
Elf_Scn *scn = NULL;
|
671
941
|
GElf_Shdr shdr;
|
672
|
-
|
942
|
+
int ret = 0;
|
673
943
|
|
674
944
|
if (elf_getshdrstrndx(elf, &shstrndx) == -1) {
|
675
|
-
|
945
|
+
dbg_printf("getshstrndx() failed: %s.", elf_errmsg(-1));
|
946
|
+
ret = 1;
|
947
|
+
goto out;
|
676
948
|
}
|
677
949
|
|
678
950
|
if (gelf_getehdr(elf, &(info->ehdr)) == NULL) {
|
679
|
-
|
951
|
+
dbg_printf("Couldn't get elf header.");
|
952
|
+
ret = 1;
|
953
|
+
goto out;
|
680
954
|
}
|
681
955
|
|
682
956
|
/* search each ELF header and store important data for each header... */
|
683
957
|
while ((scn = elf_nextscn(elf, scn)) != NULL) {
|
684
|
-
if (gelf_getshdr(scn, &shdr) != &shdr)
|
685
|
-
|
686
|
-
|
687
|
-
|
958
|
+
if (gelf_getshdr(scn, &shdr) != &shdr) {
|
959
|
+
dbg_printf("getshdr() failed: %s.", elf_errmsg(-1));
|
960
|
+
ret = 1;
|
961
|
+
goto out;
|
962
|
+
}
|
688
963
|
|
689
964
|
/*
|
690
965
|
* The .dynamic section contains entries that are important to memprof.
|
@@ -699,8 +974,9 @@ dissect_elf(struct elf_info *info)
|
|
699
974
|
for (j = 0; j < shdr.sh_size / shdr.sh_entsize; ++j) {
|
700
975
|
GElf_Dyn dyn;
|
701
976
|
if (gelf_getdyn(data, j, &dyn) == NULL) {
|
702
|
-
|
703
|
-
|
977
|
+
dbg_printf("Couldn't get .dynamic data from loaded library.");
|
978
|
+
ret = 1;
|
979
|
+
goto out;
|
704
980
|
}
|
705
981
|
|
706
982
|
if (dyn.d_tag == DT_JMPREL) {
|
@@ -720,21 +996,26 @@ dissect_elf(struct elf_info *info)
|
|
720
996
|
|
721
997
|
info->dynsym = elf_getdata(scn, NULL);
|
722
998
|
info->dynsym_count = shdr.sh_size / shdr.sh_entsize;
|
723
|
-
if (info->dynsym == NULL
|
724
|
-
|
725
|
-
|
726
|
-
|
999
|
+
if (info->dynsym == NULL || elf_getdata(scn, info->dynsym) != NULL) {
|
1000
|
+
dbg_printf("Couldn't get .dynsym data ");
|
1001
|
+
ret = 1;
|
1002
|
+
goto out;
|
1003
|
+
}
|
727
1004
|
|
728
1005
|
scn = elf_getscn(elf, shdr.sh_link);
|
729
|
-
if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL)
|
730
|
-
|
731
|
-
|
1006
|
+
if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL) {
|
1007
|
+
dbg_printf("Couldn't get section header.");
|
1008
|
+
ret = 1;
|
1009
|
+
goto out;
|
1010
|
+
}
|
732
1011
|
|
733
1012
|
data = elf_getdata(scn, NULL);
|
734
1013
|
if (data == NULL || elf_getdata(scn, data) != NULL
|
735
|
-
|| shdr.sh_size != data->d_size || data->d_off)
|
736
|
-
|
737
|
-
|
1014
|
+
|| shdr.sh_size != data->d_size || data->d_off) {
|
1015
|
+
dbg_printf("Couldn't get .dynstr data");
|
1016
|
+
ret = 1;
|
1017
|
+
goto out;
|
1018
|
+
}
|
738
1019
|
|
739
1020
|
info->dynstr = data->d_buf;
|
740
1021
|
}
|
@@ -752,9 +1033,21 @@ dissect_elf(struct elf_info *info)
|
|
752
1033
|
* Pull out information (start address) of the .plt section.
|
753
1034
|
*/
|
754
1035
|
else if (shdr.sh_type == SHT_PROGBITS) {
|
755
|
-
|
756
|
-
|
757
|
-
|
1036
|
+
if (strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), ".plt") == 0) {
|
1037
|
+
info->plt_addr = shdr.sh_addr;
|
1038
|
+
} else if (strcmp(elf_strptr(elf, shstrndx, shdr.sh_name), ".gnu_debuglink") == 0) {
|
1039
|
+
info->debuglink_addr = shdr.sh_addr;
|
1040
|
+
info->debuglink_sz = shdr.sh_size;
|
1041
|
+
dbg_printf("address: %lx, size: %zd\n", shdr.sh_addr, shdr.sh_size);
|
1042
|
+
|
1043
|
+
if ((info->debuglink_data = elf_getdata(scn, NULL)) == NULL ||
|
1044
|
+
info->debuglink_data->d_size == 0) {
|
1045
|
+
dbg_printf(".gnu_debuglink section existed, but wasn't readable.\n");
|
1046
|
+
ret = 2;
|
1047
|
+
goto out;
|
1048
|
+
}
|
1049
|
+
dbg_printf("size: %zd\n", shdr.sh_size);
|
1050
|
+
}
|
758
1051
|
}
|
759
1052
|
/*
|
760
1053
|
* The symbol table is also needed for bin_find_symbol
|
@@ -763,42 +1056,52 @@ dissect_elf(struct elf_info *info)
|
|
763
1056
|
info->symtab_shdr = shdr;
|
764
1057
|
if ((info->symtab_data = elf_getdata(scn, info->symtab_data)) == NULL ||
|
765
1058
|
info->symtab_data->d_size == 0) {
|
766
|
-
|
767
|
-
|
1059
|
+
dbg_printf("shared lib has a broken symbol table. Is it stripped? "
|
1060
|
+
"memprof only works on shared libs that are not stripped!\n");
|
1061
|
+
ret = 2;
|
1062
|
+
goto out;
|
768
1063
|
}
|
769
1064
|
}
|
770
1065
|
}
|
771
1066
|
|
772
1067
|
/* If this object has no symbol table there's nothing else to do but fail */
|
773
1068
|
if (!info->symtab_data) {
|
774
|
-
|
1069
|
+
dbg_printf("binary is stripped. memprof only works on binaries that are not stripped!\n");
|
1070
|
+
ret = 1;
|
775
1071
|
}
|
776
1072
|
|
777
|
-
|
778
1073
|
/*
|
779
1074
|
* Walk the sections, pull out, and store the .plt section
|
780
1075
|
*/
|
781
1076
|
for (j = 1; j < info->ehdr.e_shnum; j++) {
|
782
1077
|
scn = elf_getscn(elf, j);
|
783
|
-
if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL)
|
784
|
-
|
785
|
-
|
1078
|
+
if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL) {
|
1079
|
+
dbg_printf("Couldn't get section header from library.");
|
1080
|
+
ret = 1;
|
1081
|
+
goto out;
|
1082
|
+
}
|
786
1083
|
|
787
1084
|
if (shdr.sh_addr == info->relplt_addr
|
788
1085
|
&& shdr.sh_size == info->plt_size) {
|
789
1086
|
info->relplt = elf_getdata(scn, NULL);
|
790
1087
|
info->relplt_count = shdr.sh_size / shdr.sh_entsize;
|
791
|
-
if (info->relplt == NULL
|
792
|
-
|
793
|
-
|
794
|
-
|
1088
|
+
if (info->relplt == NULL || elf_getdata(scn, info->relplt) != NULL) {
|
1089
|
+
dbg_printf("Couldn't get .rel*.plt data from");
|
1090
|
+
ret = 1;
|
1091
|
+
goto out;
|
1092
|
+
}
|
795
1093
|
break;
|
796
1094
|
}
|
797
1095
|
}
|
798
1096
|
|
799
|
-
|
1097
|
+
out:
|
1098
|
+
if (find_debug && ret == 1) {
|
1099
|
+
find_debug_syms(info);
|
1100
|
+
}
|
1101
|
+
return ret;
|
800
1102
|
}
|
801
1103
|
|
1104
|
+
|
802
1105
|
/*
|
803
1106
|
* bin_init - initialize the binary parsing/modification layer.
|
804
1107
|
*
|
@@ -809,6 +1112,9 @@ bin_init()
|
|
809
1112
|
{
|
810
1113
|
Dwarf_Error dwrf_err;
|
811
1114
|
|
1115
|
+
ASSERT_ON_COMPILE(sizeof(unsigned long) == sizeof(GElf_Addr));
|
1116
|
+
ASSERT_ON_COMPILE(sizeof(unsigned long) == sizeof(Elf64_Addr));
|
1117
|
+
|
812
1118
|
ruby_info = calloc(1, sizeof(*ruby_info));
|
813
1119
|
|
814
1120
|
if (!ruby_info) {
|
@@ -816,15 +1122,26 @@ bin_init()
|
|
816
1122
|
}
|
817
1123
|
|
818
1124
|
if (!has_libruby(ruby_info)) {
|
819
|
-
|
1125
|
+
dbg_printf("This ruby binary has no libruby\n");
|
1126
|
+
char *filename = calloc(1, 255);
|
1127
|
+
if (readlink("/proc/self/exe", filename, 255) == -1) {
|
1128
|
+
errx(EX_UNAVAILABLE, "Unable to follow /proc/self/exe symlink: %s", strerror(errno));
|
1129
|
+
}
|
1130
|
+
ruby_info->filename = filename;
|
820
1131
|
ruby_info->base_addr = 0;
|
1132
|
+
dbg_printf("The path to the binary is: %s\n", ruby_info->filename);
|
821
1133
|
}
|
822
1134
|
|
823
|
-
|
824
|
-
|
1135
|
+
open_elf(ruby_info);
|
1136
|
+
|
1137
|
+
if (dissect_elf(ruby_info, 1) == 2) {
|
1138
|
+
errx(EX_DATAERR, "Error trying to parse elf file: %s\n", ruby_info->filename);
|
1139
|
+
}
|
825
1140
|
|
826
1141
|
if (dwarf_elf_init(ruby_info->elf, DW_DLC_READ, NULL, NULL, &dwrf, &dwrf_err) != DW_DLV_OK) {
|
827
1142
|
errx(EX_DATAERR, "unable to read debugging data from binary. was it compiled with -g? is it unstripped?");
|
828
1143
|
}
|
1144
|
+
|
1145
|
+
dbg_printf("bin_init finished\n");
|
829
1146
|
}
|
830
1147
|
#endif
|