summaryrefslogtreecommitdiff
path: root/erts/etc/unix/jit-reader.c
blob: 6419570249739247c90298d40eaa5093ddb8713d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#include "jit-reader.h"

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

/* Useful links
 * - https://pwparchive.wordpress.com/2011/11/20/new-jit-interface-for-gdb/
 * - https://sourceware.org/gdb/current/onlinedocs/gdb/Custom-Debug-Info.html
 * - https://github.com/tetzank/asmjit-utilities
 * - https://github.com/bminor/binutils-gdb/blob/master/gdb/testsuite/gdb.base/jitreader.c
 */

GDB_DECLARE_GPL_COMPATIBLE_READER

#if 0
#define HARD_DEBUG
static FILE *log = NULL;
#define LOG(...) do { fprintf(log, ##__VA_ARGS__); fflush(log); } while(0)
#else
#define LOG(...)
#endif


typedef struct range {
    GDB_CORE_ADDR start;
    GDB_CORE_ADDR end;
    int global;
    char *name;
} range;

typedef struct priv {
    range *ranges;
    int num_ranges;
    uint64_t normal_exit;
} priv;


static enum gdb_status read_debug_info(struct gdb_reader_funcs *self,
                                       struct gdb_symbol_callbacks *cb,
                                       void *memory, long memory_sz) {
    priv *priv = self->priv_data;
    uint64_t num_functions = *(uint64_t*)memory;
    if (num_functions == 0) {
        /* Initialize */
        priv->normal_exit = *(uint64_t*)(memory + sizeof(uint64_t));
        LOG("initialize: normal_exit=%p\r\n",(void*)priv->normal_exit);
    } else {
        GDB_CORE_ADDR mod_start  = *(GDB_CORE_ADDR *)(memory + sizeof(uint64_t));
        GDB_CORE_ADDR mod_end  = mod_start + *(uint64_t*)(memory + sizeof(uint64_t)*2);
        char* module = memory + sizeof(uint64_t)*3;
        char* curr = module + strlen(module) + 1;
        int i;
        struct gdb_object *obj = cb->object_open(cb);

        priv->ranges = realloc(priv->ranges, sizeof(range) * ++priv->num_ranges);
        priv->ranges[priv->num_ranges-1].start = mod_start;
        priv->ranges[priv->num_ranges-1].end = mod_end;
        priv->ranges[priv->num_ranges-1].name = strdup(module);
        priv->ranges[priv->num_ranges-1].global = !strcmp(module, "global");

        LOG("Add module %s (0x%lx, 0x%lx)\r\n", module, mod_start, mod_end);

        for (i = 0; i < num_functions; i++) {
            // get begin and end of code segment
            // A bug in GDB < 9 forces us to open and close the symtab for each iteration
            struct gdb_symtab *symtab = cb->symtab_open(cb, obj, module);
            GDB_CORE_ADDR begin = *(GDB_CORE_ADDR*)curr;
            GDB_CORE_ADDR end = *(GDB_CORE_ADDR*)(curr + sizeof(GDB_CORE_ADDR));
            // get name of function
            const char *name = (const char*)(curr + sizeof(GDB_CORE_ADDR) * 2);
            curr += strlen(name) + 1 + sizeof(GDB_CORE_ADDR) * 2;

            LOG("Add %s (0x%lx, 0x%lx)\r\n", name, begin, end);

            // returned value has no use
            cb->block_open(cb, symtab, NULL, begin, end, name);
            cb->symtab_close(cb, symtab);
        }

        cb->object_close(cb, obj);
    }
    return GDB_SUCCESS;
}

static void regfree(struct gdb_reg_value *reg) {
    free(reg);
}

static struct range *get_range(priv *priv, GDB_CORE_ADDR rip) {
    int i;
    for (i = 0; i < priv->num_ranges; i++)
        if (rip >= priv->ranges[i].start && rip < priv->ranges[i].end)
            return &priv->ranges[i];
    return NULL;
}

static enum gdb_status unwind(struct gdb_reader_funcs *self, struct gdb_unwind_callbacks *cb) {
    int i;
    priv *priv = self->priv_data;
    GDB_CORE_ADDR rip = *(GDB_CORE_ADDR*)cb->reg_get(cb,16)->value;
    GDB_CORE_ADDR rsp = *(GDB_CORE_ADDR*)cb->reg_get(cb,7)->value;
    GDB_CORE_ADDR rbp = *(GDB_CORE_ADDR*)cb->reg_get(cb,6)->value;
    struct range *range = get_range(priv, rip);

    /* Check that rip points to one of the addresses that we handle */
    if (range) {
        struct gdb_reg_value *prev_rsp = malloc(sizeof(struct gdb_reg_value) + sizeof(char*)),
            *prev_rip = malloc(sizeof(struct gdb_reg_value) + sizeof(char*)),
            *prev_rbp = malloc(sizeof(struct gdb_reg_value) + sizeof(char*));

        LOG("UNWIND match %s: rip: 0x%lx rsp: 0x%lx rbp: 0x%lx \r\n", range->name, rip, rsp, rbp);
        /* We use the normal frame-pointer logic to unwind the stack, which means
           that rbp will point to where we stored the previous frames rbp. Also
           the previous frames address will be at rbp + 1 and the previous frames
           rsp will be rbp + 2.

           0x00:                <- prev_rsp
           0x80: prev call addr
           0x10: prev rbp       <- curr rbp
           0x18: current frame
           0x20:                <- curr rip

        */

        for (i = 0; i < 16; i++) {
            if (i != 16 && i != 7 && i != 6) {
                struct gdb_reg_value *reg = malloc(sizeof(struct gdb_reg_value) + sizeof(char*));
                reg->free = &regfree;
                reg->defined = 1;
                reg->size = sizeof(char*);
                *(GDB_CORE_ADDR*)reg->value = *(GDB_CORE_ADDR*)cb->reg_get(cb,i)->value;
                cb->reg_set(cb, i, reg);
            }
        }

        prev_rsp->free = &regfree;
        prev_rsp->defined = 1;
        prev_rsp->size = sizeof(char*);
        prev_rbp->free = &regfree;
        prev_rbp->defined = 1;
        prev_rbp->size = sizeof(char*);
        prev_rip->free = &regfree;
        prev_rip->defined = 1;
        prev_rip->size = sizeof(char*);

        if (range->global) {

            cb->target_read(rbp + 1 * sizeof(char*), &prev_rip->value, sizeof(char*));
            ((GDB_CORE_ADDR*)prev_rsp->value)[0] = rbp + sizeof(char*) * 2;
            cb->target_read(rbp + 0 * sizeof(char*), &prev_rbp->value, sizeof(char*));

        } else {

            if (rip == priv->normal_exit) {
                LOG("Normal exit\r\n");
                ((GDB_CORE_ADDR*)prev_rsp->value)[0] = rsp;
                ((GDB_CORE_ADDR*)prev_rbp->value)[0] = rbp;
                ((GDB_CORE_ADDR*)prev_rip->value)[0] = rip;
            } else {
                cb->target_read(rsp, &prev_rip->value, sizeof(char*));

                for (rsp += sizeof(char*); ; rsp += sizeof(char*)) {
                    cb->target_read(rsp, &prev_rip->value, sizeof(char*));
                    LOG("rsp: 0x%lx rip: 0x%lx\r\n", rsp, *(GDB_CORE_ADDR*)prev_rip->value);
                    /* Check if it is a cp */
                    if ((*(GDB_CORE_ADDR*)prev_rip->value & 0x3) == 0) {
                        break;
                    }
                }
                ((GDB_CORE_ADDR*)prev_rsp->value)[0] = rsp;
                ((GDB_CORE_ADDR*)prev_rbp->value)[0] = rsp - sizeof(char*);
            }
        }

        LOG("UNWIND prev: rip: 0x%lx rsp: 0x%lx rbp: 0x%lx\r\n",
            *(GDB_CORE_ADDR*)prev_rip->value,
            *(GDB_CORE_ADDR*)prev_rsp->value,
            *(GDB_CORE_ADDR*)prev_rbp->value);

        cb->reg_set(cb, 16, prev_rip);
        cb->reg_set(cb, 7, prev_rsp);
        cb->reg_set(cb, 6, prev_rbp);
        return GDB_SUCCESS;
    }
    LOG("UNWIND no match: rip: 0x%lx rsp: 0x%lx rbp: 0x%lx\r\n", rip, rsp, rbp);
    return GDB_FAIL;
}

static struct gdb_frame_id get_frame_id(struct gdb_reader_funcs *self, struct gdb_unwind_callbacks *cb){
    priv *priv = self->priv_data;
    struct gdb_frame_id frame = {0, 0};
    GDB_CORE_ADDR rip = *(GDB_CORE_ADDR*)cb->reg_get(cb,16)->value;
    GDB_CORE_ADDR rbp = *(GDB_CORE_ADDR*)cb->reg_get(cb,6)->value;
    GDB_CORE_ADDR rsp = *(GDB_CORE_ADDR*)cb->reg_get(cb,7)->value;
    struct range *range = get_range(priv, rip);
    LOG("FRAME: rip: 0x%lx rsp: 0x%lx rbp: 0x%lx \r\n", rip, rsp, rbp);
    if (range) {
        frame.code_address = rip;
        if (range->global) {
            frame.stack_address = rbp + sizeof(char*);
        } else {
            GDB_CORE_ADDR prev_rip;
            for (rsp += sizeof(char*); ; rsp += sizeof(char*)) {
                cb->target_read(rsp, &prev_rip, sizeof(char*));
                LOG("rsp: 0x%lx rip: 0x%lx\r\n", rsp, prev_rip);
                /* Check if it is a cp */
                if ((prev_rip & 0x3) == 0) {
                    break;
                }
            }
            frame.stack_address = rsp;
        }
    }
    LOG("FRAME: code_address: 0x%lx stack_address: 0x%lx\r\n",
        frame.code_address, frame.stack_address);
    return frame;
}

static void destroy(struct gdb_reader_funcs *self){
    free(self);
}

struct gdb_reader_funcs *gdb_init_reader(void){
    struct gdb_reader_funcs *funcs = malloc(sizeof(struct gdb_reader_funcs));
    priv *priv = malloc(sizeof(priv));
    priv->num_ranges = 1;
    priv->ranges = malloc(sizeof(range));
    priv->ranges[0].start = 0;
    priv->ranges[0].end = 0;

    funcs->reader_version = GDB_READER_INTERFACE_VERSION;
    funcs->priv_data = priv;

    funcs->read = read_debug_info;
    funcs->unwind = unwind;
    funcs->get_frame_id = get_frame_id;
    funcs->destroy = destroy;

#ifdef HARD_DEBUG
    log = fopen("/tmp/jit-reader.log","w+");
    if (!log) fprintf(stderr,"Could not open /tmp/jit-reader.log");
#endif

    return funcs;
}