backtracie 0.3.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.adoc +3 -0
- data/ext/backtracie_native_extension/backtracie.c +182 -245
- data/ext/backtracie_native_extension/backtracie_frames.c +941 -0
- data/ext/backtracie_native_extension/backtracie_private.h +20 -0
- data/ext/backtracie_native_extension/c_test_helpers.c +62 -0
- data/ext/backtracie_native_extension/extconf.rb +11 -17
- data/ext/backtracie_native_extension/public/backtracie.h +268 -0
- data/ext/backtracie_native_extension/strbuilder.c +123 -0
- data/ext/backtracie_native_extension/strbuilder.h +23 -0
- data/lib/backtracie/location.rb +6 -1
- data/lib/backtracie/version.rb +1 -1
- metadata +9 -5
- data/ext/backtracie_native_extension/ruby_shards.c +0 -533
- data/ext/backtracie_native_extension/ruby_shards.h +0 -170
@@ -0,0 +1,941 @@
|
|
1
|
+
// backtracie: Ruby gem for beautiful backtraces
|
2
|
+
// Copyright (C) 2021 Ivo Anjo <ivo@ivoanjo.me>
|
3
|
+
//
|
4
|
+
// This file is part of backtracie.
|
5
|
+
//
|
6
|
+
// backtracie is free software: you can redistribute it and/or modify
|
7
|
+
// it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
// the Free Software Foundation, either version 3 of the License, or
|
9
|
+
// (at your option) any later version.
|
10
|
+
//
|
11
|
+
// backtracie is distributed in the hope that it will be useful,
|
12
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
// GNU Lesser General Public License for more details.
|
15
|
+
//
|
16
|
+
// You should have received a copy of the GNU Lesser General Public License
|
17
|
+
// along with backtracie. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
// -----------------------------------------------------------------------------
|
20
|
+
// The file below has modified versions of code extracted from the Ruby project.
|
21
|
+
// The Ruby project copyright and license follow:
|
22
|
+
// -----------------------------------------------------------------------------
|
23
|
+
|
24
|
+
// Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
|
25
|
+
// You can redistribute it and/or modify it under either the terms of the
|
26
|
+
// 2-clause BSDL (see the file BSDL), or the conditions below:
|
27
|
+
|
28
|
+
// 1. You may make and give away verbatim copies of the source form of the
|
29
|
+
// software without restriction, provided that you duplicate all of the
|
30
|
+
// original copyright notices and associated disclaimers.
|
31
|
+
|
32
|
+
// 2. You may modify your copy of the software in any way, provided that
|
33
|
+
// you do at least ONE of the following:
|
34
|
+
|
35
|
+
// a. place your modifications in the Public Domain or otherwise
|
36
|
+
// make them Freely Available, such as by posting said
|
37
|
+
// modifications to Usenet or an equivalent medium, or by allowing
|
38
|
+
// the author to include your modifications in the software.
|
39
|
+
|
40
|
+
// b. use the modified software only within your corporation or
|
41
|
+
// organization.
|
42
|
+
|
43
|
+
// c. give non-standard binaries non-standard names, with
|
44
|
+
// instructions on where to get the original software distribution.
|
45
|
+
|
46
|
+
// d. make other distribution arrangements with the author.
|
47
|
+
|
48
|
+
// 3. You may distribute the software in object code or binary form,
|
49
|
+
// provided that you do at least ONE of the following:
|
50
|
+
|
51
|
+
// a. distribute the binaries and library files of the software,
|
52
|
+
// together with instructions (in the manual page or equivalent)
|
53
|
+
// on where to get the original distribution.
|
54
|
+
|
55
|
+
// b. accompany the distribution with the machine-readable source of
|
56
|
+
// the software.
|
57
|
+
|
58
|
+
// c. give non-standard binaries non-standard names, with
|
59
|
+
// instructions on where to get the original software distribution.
|
60
|
+
|
61
|
+
// d. make other distribution arrangements with the author.
|
62
|
+
|
63
|
+
// 4. You may modify and include the part of the software into any other
|
64
|
+
// software (possibly commercial). But some files in the distribution
|
65
|
+
// are not written by the author, so that they are not under these terms.
|
66
|
+
|
67
|
+
// For the list of those files and their copying conditions, see the
|
68
|
+
// file LEGAL.
|
69
|
+
|
70
|
+
// 5. The scripts and library files supplied as input to or produced as
|
71
|
+
// output from the software do not automatically fall under the
|
72
|
+
// copyright of the software, but belong to whomever generated them,
|
73
|
+
// and may be sold commercially, and may be aggregated with this
|
74
|
+
// software.
|
75
|
+
|
76
|
+
// 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
77
|
+
// IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
78
|
+
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
79
|
+
// PURPOSE.
|
80
|
+
|
81
|
+
// -----------------------------------------------------------------------------
|
82
|
+
|
83
|
+
// backtracie_frames.c contains a number of borrowed functions from the MRI Ruby
|
84
|
+
// (usually 3.0.0) source tree:
|
85
|
+
// * A few were copy-pasted verbatim, and are dependencies for other functions
|
86
|
+
// * A few were copy-pasted and then changed so we can add features and fixes
|
87
|
+
// * A few were copy-pasted verbatim, with the objective of backporting
|
88
|
+
// their 3.0.0 behavior to earlier Ruby versions.
|
89
|
+
//
|
90
|
+
// `git blame` usually documents which functions were added for what reason.
|
91
|
+
|
92
|
+
// Note that since the RUBY_MJIT_HEADER is a very special header, meant for
|
93
|
+
// internal use only, it has a number of quirks:
|
94
|
+
//
|
95
|
+
// 1. I've seen a few segfaults when trying to call back into original Ruby
|
96
|
+
// functions. E.g. even if the API is used
|
97
|
+
// correctly, just the mere inclusion of RUBY_MJIT_HEADER causes usage to
|
98
|
+
// crash. Thus, as much as possible, it's better to define functions OUTSIDE
|
99
|
+
// this file.
|
100
|
+
//
|
101
|
+
// 2. On Windows, I've observed "multiple definition of `something...'" (such as
|
102
|
+
// `rb_vm_ep_local_ep') whenever there
|
103
|
+
// are multiple files in the codebase that include the RUBY_MJIT_HEADER.
|
104
|
+
// It looks like (some?) Windows versions of Ruby define a bunch of functions
|
105
|
+
// in the RUBY_MJIT_HEADER itself without marking them as "static" (e.g. not
|
106
|
+
// visible to the outside of the file), and thus the linker then complains
|
107
|
+
// when linking together several files which all have these non-private
|
108
|
+
// symbols. One possible hacky solution suggested by the internets is to use
|
109
|
+
// the "-Wl,-allow-multiple-definition" linker flags to ignore this problem;
|
110
|
+
// instead I've chosen to implement all usage of the RUBY_MJIT_HEADER on this
|
111
|
+
// file -- no other file in backtracie shall include RUBY_MJIT_HEADER. It's a
|
112
|
+
// simpler approach, and hopefully avoids any problems.
|
113
|
+
|
114
|
+
#include "extconf.h"
|
115
|
+
|
116
|
+
#ifndef PRE_MJIT_RUBY
|
117
|
+
#ifndef RUBY_MJIT_HEADER_INCLUDED
|
118
|
+
#define RUBY_MJIT_HEADER_INCLUDED
|
119
|
+
#include RUBY_MJIT_HEADER
|
120
|
+
#endif
|
121
|
+
#endif
|
122
|
+
|
123
|
+
#include <stdbool.h>
|
124
|
+
#include <stdint.h>
|
125
|
+
#include <stdlib.h>
|
126
|
+
|
127
|
+
#ifdef PRE_MJIT_RUBY
|
128
|
+
// The order of includes here is very important in older versions of Ruby
|
129
|
+
// clang-format off
|
130
|
+
#include <ruby.h>
|
131
|
+
#include <vm_core.h>
|
132
|
+
#include <method.h>
|
133
|
+
#include <iseq.h>
|
134
|
+
#include <regenc.h>
|
135
|
+
// clang-format on
|
136
|
+
#endif
|
137
|
+
|
138
|
+
#include "backtracie_private.h"
|
139
|
+
#include "public/backtracie.h"
|
140
|
+
#include "strbuilder.h"
|
141
|
+
|
142
|
+
#ifndef PRE_RB_ISEQ_TYPE
|
143
|
+
// This was renamed for Ruby >= 3.2
|
144
|
+
#define RB_ISEQ_TYPE rb_iseq_type
|
145
|
+
#else
|
146
|
+
#define RB_ISEQ_TYPE iseq_type
|
147
|
+
#endif
|
148
|
+
|
149
|
+
#ifdef PRE_EXECUTION_CONTEXT
|
150
|
+
// The thread and its execution context were separated on Ruby 2.5; prior to
|
151
|
+
// that, everything was part of the thread
|
152
|
+
#define rb_execution_context_t rb_thread_t
|
153
|
+
#endif
|
154
|
+
|
155
|
+
#ifdef PRE_VM_ENV_RENAMES
|
156
|
+
#define VM_ENV_LOCAL_P VM_EP_LEP_P
|
157
|
+
#define VM_ENV_PREV_EP VM_EP_PREV_EP
|
158
|
+
#define VM_ENV_DATA_INDEX_ME_CREF -1
|
159
|
+
#define VM_FRAME_RUBYFRAME_P(cfp) RUBY_VM_NORMAL_ISEQ_P(cfp->iseq)
|
160
|
+
#endif
|
161
|
+
|
162
|
+
// This is managed in backtracie.c
|
163
|
+
extern VALUE backtracie_main_object_instance;
|
164
|
+
extern VALUE backtracie_frame_wrapper_class;
|
165
|
+
static void raw_location_to_minimal_location(const raw_location *raw_loc,
|
166
|
+
minimal_location_t *min_loc);
|
167
|
+
static void mod_to_s_anon(VALUE klass, strbuilder_t *strout);
|
168
|
+
static void mod_to_s_refinement(VALUE klass, strbuilder_t *strout);
|
169
|
+
static void mod_to_s_singleton(VALUE klass, strbuilder_t *strout);
|
170
|
+
static void mod_to_s(VALUE klass, strbuilder_t *strout);
|
171
|
+
static void minimal_location_method_qualifier(const minimal_location_t *loc,
|
172
|
+
strbuilder_t *strout);
|
173
|
+
static void minimal_location_method_name(const minimal_location_t *loc,
|
174
|
+
strbuilder_t *strout);
|
175
|
+
static bool frame_filename(const raw_location *loc, bool absolute,
|
176
|
+
strbuilder_t *strout);
|
177
|
+
static bool iseq_path(const rb_iseq_t *iseq, bool absolute,
|
178
|
+
strbuilder_t *strout);
|
179
|
+
static int frame_label(const raw_location *loc, bool base,
|
180
|
+
strbuilder_t *strout);
|
181
|
+
static int calc_lineno(const rb_iseq_t *iseq, const void *pc);
|
182
|
+
static const rb_callable_method_entry_t *
|
183
|
+
backtracie_vm_frame_method_entry(const rb_control_frame_t *cfp);
|
184
|
+
|
185
|
+
static void backtracie_frame_wrapper_mark(void *ptr);
|
186
|
+
static void backtracie_frame_wrapper_compact(void *ptr);
|
187
|
+
static void backtracie_frame_wrapper_free(void *ptr);
|
188
|
+
static size_t backtracie_frame_wrapper_memsize(const void *ptr);
|
189
|
+
static const rb_data_type_t backtracie_frame_wrapper_type = {
|
190
|
+
.wrap_struct_name = "backtracie_frame_wrapper",
|
191
|
+
.function = {.dmark = backtracie_frame_wrapper_mark,
|
192
|
+
.dfree = backtracie_frame_wrapper_free,
|
193
|
+
.dsize = backtracie_frame_wrapper_memsize,
|
194
|
+
#ifndef PRE_GC_MARK_MOVABLE
|
195
|
+
.dcompact = backtracie_frame_wrapper_compact,
|
196
|
+
#endif
|
197
|
+
.reserved = {0}},
|
198
|
+
.parent = NULL,
|
199
|
+
.data = NULL,
|
200
|
+
// This is safe, because our free function does not do anything which could
|
201
|
+
// yield the GVL.
|
202
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
203
|
+
};
|
204
|
+
|
205
|
+
typedef struct {
|
206
|
+
raw_location *frames;
|
207
|
+
size_t capa;
|
208
|
+
int len;
|
209
|
+
} frame_wrapper_t;
|
210
|
+
|
211
|
+
static bool object_has_special_bt_handling(VALUE obj) {
|
212
|
+
return obj == backtracie_main_object_instance || obj == rb_mRubyVMFrozenCore;
|
213
|
+
}
|
214
|
+
|
215
|
+
static bool iseq_type_is_block_or_eval(enum RB_ISEQ_TYPE type) {
|
216
|
+
return type == ISEQ_TYPE_EVAL || type == ISEQ_TYPE_BLOCK;
|
217
|
+
}
|
218
|
+
|
219
|
+
static bool iseq_type_is_eval(enum RB_ISEQ_TYPE type) {
|
220
|
+
return type == ISEQ_TYPE_EVAL;
|
221
|
+
}
|
222
|
+
|
223
|
+
static bool iseq_is_block_or_eval(const rb_iseq_t *iseq) {
|
224
|
+
if (!iseq)
|
225
|
+
return false;
|
226
|
+
return iseq_type_is_block_or_eval(iseq->body->type);
|
227
|
+
}
|
228
|
+
|
229
|
+
static bool iseq_is_eval(const rb_iseq_t *iseq) {
|
230
|
+
if (!iseq)
|
231
|
+
return false;
|
232
|
+
return iseq_type_is_eval(iseq->body->type);
|
233
|
+
}
|
234
|
+
|
235
|
+
static bool class_or_module_or_iclass(VALUE obj) {
|
236
|
+
return RB_TYPE_P(obj, T_CLASS) || RB_TYPE_P(obj, T_ICLASS) ||
|
237
|
+
RB_TYPE_P(obj, T_MODULE);
|
238
|
+
}
|
239
|
+
|
240
|
+
bool backtracie_is_thread_alive(VALUE thread) {
|
241
|
+
// In here we're assuming that what we got is really a Thread or its subclass.
|
242
|
+
// This assumption NEEDS to be verified by the caller, otherwise I see a
|
243
|
+
// segfault in your future.
|
244
|
+
rb_thread_t *thread_pointer = (rb_thread_t *)DATA_PTR(thread);
|
245
|
+
|
246
|
+
return !(thread_pointer->to_kill || thread_pointer->status == THREAD_KILLED);
|
247
|
+
}
|
248
|
+
|
249
|
+
void backtracie_frame_mark(const raw_location *loc) {
|
250
|
+
rb_gc_mark(loc->iseq);
|
251
|
+
rb_gc_mark(loc->callable_method_entry);
|
252
|
+
rb_gc_mark(loc->self_or_self_class);
|
253
|
+
}
|
254
|
+
|
255
|
+
void backtracie_frame_mark_movable(const raw_location *loc) {
|
256
|
+
#ifdef PRE_GC_MARK_MOVABLE
|
257
|
+
backtracie_frame_mark(loc);
|
258
|
+
#else
|
259
|
+
rb_gc_mark_movable(loc->iseq);
|
260
|
+
rb_gc_mark_movable(loc->callable_method_entry);
|
261
|
+
rb_gc_mark_movable(loc->self_or_self_class);
|
262
|
+
#endif
|
263
|
+
}
|
264
|
+
|
265
|
+
void backtracie_frame_compact(raw_location *loc) {
|
266
|
+
#ifndef PRE_GC_MARK_MOVABLE
|
267
|
+
loc->iseq = rb_gc_location(loc->iseq);
|
268
|
+
loc->callable_method_entry = rb_gc_location(loc->callable_method_entry);
|
269
|
+
loc->self_or_self_class = rb_gc_location(loc->self_or_self_class);
|
270
|
+
#endif
|
271
|
+
}
|
272
|
+
|
273
|
+
static int
|
274
|
+
backtracie_frame_count_for_execution_context(rb_execution_context_t *ec) {
|
275
|
+
const rb_control_frame_t *last_cfp = ec->cfp;
|
276
|
+
// -2 because of the two dummy frames at the bottom of the stack.
|
277
|
+
const rb_control_frame_t *start_cfp = RUBY_VM_END_CONTROL_FRAME(ec) - 2;
|
278
|
+
|
279
|
+
if (ec->cfp == NULL) {
|
280
|
+
// This case means that a new thread hasn't called _any_ ruby method yet, it
|
281
|
+
// seems. Returning zero from here is the most sensible answer.
|
282
|
+
return 0;
|
283
|
+
} else if (start_cfp < last_cfp) {
|
284
|
+
return 0;
|
285
|
+
} else {
|
286
|
+
return (int)(start_cfp - last_cfp + 1);
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
int backtracie_frame_count_for_thread(VALUE thread) {
|
291
|
+
if (!backtracie_is_thread_alive(thread))
|
292
|
+
return 0;
|
293
|
+
rb_thread_t *thread_pointer = (rb_thread_t *)DATA_PTR(thread);
|
294
|
+
|
295
|
+
#ifndef PRE_EXECUTION_CONTEXT
|
296
|
+
return backtracie_frame_count_for_execution_context(thread_pointer->ec);
|
297
|
+
#else
|
298
|
+
return backtracie_frame_count_for_execution_context(thread_pointer);
|
299
|
+
#endif
|
300
|
+
}
|
301
|
+
|
302
|
+
static bool backtracie_capture_frame_for_execution_context(
|
303
|
+
rb_execution_context_t *ec, int frame_index, raw_location *loc) {
|
304
|
+
// The frame argument is zero-based with zero being "the frame closest to
|
305
|
+
// where execution is now" (I couldn't decide if this was supposed to be the
|
306
|
+
// "top" or "bottom" of the callstack; but lower frame argument --> more
|
307
|
+
// recently called function.
|
308
|
+
|
309
|
+
const rb_control_frame_t *cfp = ec->cfp + frame_index;
|
310
|
+
if (!RUBY_VM_VALID_CONTROL_FRAME_P(cfp, RUBY_VM_END_CONTROL_FRAME(ec) - 1)) {
|
311
|
+
// -1 because of the two "dummy" frames at the bottom of the stack.
|
312
|
+
// (it's -1, not -2, because RUBY_VM_VALID_CONTROL_FRAME_P checks if the
|
313
|
+
// given frame is >= the end frame, not >).,
|
314
|
+
// Means we're past the end of the stack.
|
315
|
+
BACKTRACIE_ASSERT_FAIL("called capture_frame with an invalid index");
|
316
|
+
}
|
317
|
+
|
318
|
+
const rb_callable_method_entry_t *cme = backtracie_vm_frame_method_entry(cfp);
|
319
|
+
|
320
|
+
// Work out validity, or otherwise, of this frame.
|
321
|
+
// This expression is derived from what backtrace_each in vm_backtrace.c does.
|
322
|
+
bool is_valid = (!(cfp->iseq && !cfp->pc) &&
|
323
|
+
(VM_FRAME_RUBYFRAME_P(cfp) ||
|
324
|
+
(cme && cme->def->type == VM_METHOD_TYPE_CFUNC)));
|
325
|
+
if (!is_valid) {
|
326
|
+
// Don't include this frame in backtraces
|
327
|
+
return false;
|
328
|
+
}
|
329
|
+
|
330
|
+
loc->is_ruby_frame = VM_FRAME_RUBYFRAME_P(cfp);
|
331
|
+
loc->iseq = (VALUE)cfp->iseq;
|
332
|
+
loc->callable_method_entry = (VALUE)cme;
|
333
|
+
if (object_has_special_bt_handling(cfp->self) ||
|
334
|
+
class_or_module_or_iclass(cfp->self)) {
|
335
|
+
loc->self_or_self_class = cfp->self;
|
336
|
+
loc->self_is_real_self = 1;
|
337
|
+
} else {
|
338
|
+
loc->self_or_self_class = rb_class_of(cfp->self);
|
339
|
+
loc->self_is_real_self = 0;
|
340
|
+
}
|
341
|
+
loc->pc = cfp->pc;
|
342
|
+
return true;
|
343
|
+
}
|
344
|
+
|
345
|
+
bool backtracie_capture_frame_for_thread(VALUE thread, int frame_index,
|
346
|
+
raw_location *loc) {
|
347
|
+
if (!backtracie_is_thread_alive(thread)) {
|
348
|
+
return false;
|
349
|
+
}
|
350
|
+
rb_thread_t *thread_pointer = (rb_thread_t *)DATA_PTR(thread);
|
351
|
+
|
352
|
+
#ifndef PRE_EXECUTION_CONTEXT
|
353
|
+
return backtracie_capture_frame_for_execution_context(thread_pointer->ec,
|
354
|
+
frame_index, loc);
|
355
|
+
#else
|
356
|
+
return backtracie_capture_frame_for_execution_context(thread_pointer,
|
357
|
+
frame_index, loc);
|
358
|
+
#endif
|
359
|
+
}
|
360
|
+
|
361
|
+
int backtracie_frame_line_number(const raw_location *loc) {
|
362
|
+
return calc_lineno((rb_iseq_t *)loc->iseq, loc->pc);
|
363
|
+
}
|
364
|
+
|
365
|
+
size_t backtracie_frame_name_cstr(const raw_location *loc, char *buf,
|
366
|
+
size_t buflen) {
|
367
|
+
strbuilder_t builder;
|
368
|
+
strbuilder_init(&builder, buf, buflen);
|
369
|
+
|
370
|
+
minimal_location_t min_loc;
|
371
|
+
raw_location_to_minimal_location(loc, &min_loc);
|
372
|
+
minimal_location_method_qualifier(&min_loc, &builder);
|
373
|
+
minimal_location_method_name(&min_loc, &builder);
|
374
|
+
|
375
|
+
return builder.attempted_size;
|
376
|
+
}
|
377
|
+
|
378
|
+
VALUE backtracie_frame_name_rbstr(const raw_location *loc) {
|
379
|
+
strbuilder_t builder;
|
380
|
+
strbuilder_init_growable(&builder, 256);
|
381
|
+
|
382
|
+
minimal_location_t min_loc;
|
383
|
+
raw_location_to_minimal_location(loc, &min_loc);
|
384
|
+
minimal_location_method_qualifier(&min_loc, &builder);
|
385
|
+
minimal_location_method_name(&min_loc, &builder);
|
386
|
+
|
387
|
+
VALUE ret = strbuilder_to_value(&builder);
|
388
|
+
strbuilder_free_growable(&builder);
|
389
|
+
return ret;
|
390
|
+
}
|
391
|
+
|
392
|
+
size_t backtracie_frame_filename_cstr(const raw_location *loc, bool absolute,
|
393
|
+
char *buf, size_t buflen) {
|
394
|
+
strbuilder_t builder;
|
395
|
+
strbuilder_init(&builder, buf, buflen);
|
396
|
+
|
397
|
+
frame_filename(loc, absolute, &builder);
|
398
|
+
|
399
|
+
return builder.attempted_size;
|
400
|
+
}
|
401
|
+
|
402
|
+
VALUE backtracie_frame_filename_rbstr(const raw_location *loc, bool absolute) {
|
403
|
+
strbuilder_t builder;
|
404
|
+
strbuilder_init_growable(&builder, 256);
|
405
|
+
|
406
|
+
bool fname_found = frame_filename(loc, absolute, &builder);
|
407
|
+
|
408
|
+
VALUE ret = Qnil;
|
409
|
+
if (fname_found) {
|
410
|
+
ret = strbuilder_to_value(&builder);
|
411
|
+
}
|
412
|
+
strbuilder_free_growable(&builder);
|
413
|
+
return ret;
|
414
|
+
}
|
415
|
+
|
416
|
+
static bool frame_filename(const raw_location *loc, bool absolute,
|
417
|
+
strbuilder_t *strout) {
|
418
|
+
return iseq_path((const rb_iseq_t *)loc->iseq, absolute, strout);
|
419
|
+
}
|
420
|
+
|
421
|
+
size_t backtracie_frame_label_cstr(const raw_location *loc, bool base,
|
422
|
+
char *buf, size_t buflen) {
|
423
|
+
strbuilder_t builder;
|
424
|
+
strbuilder_init(&builder, buf, buflen);
|
425
|
+
|
426
|
+
frame_label(loc, base, &builder);
|
427
|
+
|
428
|
+
return builder.attempted_size;
|
429
|
+
}
|
430
|
+
|
431
|
+
VALUE backtracie_frame_label_rbstr(const raw_location *loc, bool base) {
|
432
|
+
strbuilder_t builder;
|
433
|
+
strbuilder_init_growable(&builder, 256);
|
434
|
+
|
435
|
+
int label_found = frame_label(loc, base, &builder);
|
436
|
+
|
437
|
+
VALUE ret = Qnil;
|
438
|
+
if (label_found) {
|
439
|
+
ret = strbuilder_to_value(&builder);
|
440
|
+
}
|
441
|
+
strbuilder_free_growable(&builder);
|
442
|
+
return ret;
|
443
|
+
}
|
444
|
+
|
445
|
+
VALUE backtracie_frame_for_rb_profile(const raw_location *loc) {
|
446
|
+
const rb_iseq_t *iseq = NULL;
|
447
|
+
const rb_callable_method_entry_t *cme = NULL;
|
448
|
+
if (RTEST(loc->iseq)) {
|
449
|
+
iseq = (rb_iseq_t *)loc->iseq;
|
450
|
+
}
|
451
|
+
if (RTEST(loc->callable_method_entry)) {
|
452
|
+
cme = (rb_callable_method_entry_t *)cme;
|
453
|
+
}
|
454
|
+
|
455
|
+
// This one is somewhat weird, but the regular MRI Ruby APIs seem to pick the
|
456
|
+
// iseq for evals as well
|
457
|
+
if (iseq_is_eval(iseq)) {
|
458
|
+
return loc->iseq;
|
459
|
+
}
|
460
|
+
// This comes from the original rb_profile_frames logic, which would only
|
461
|
+
// return the iseq when the cme type is not VM_METHOD_TYPE_ISEQ
|
462
|
+
if (cme && cme->def->type != VM_METHOD_TYPE_ISEQ) {
|
463
|
+
return loc->callable_method_entry;
|
464
|
+
}
|
465
|
+
|
466
|
+
if (iseq) {
|
467
|
+
return loc->iseq;
|
468
|
+
}
|
469
|
+
return Qnil;
|
470
|
+
}
|
471
|
+
|
472
|
+
static int frame_label(const raw_location *loc, bool base,
|
473
|
+
strbuilder_t *strout) {
|
474
|
+
if (loc->is_ruby_frame) {
|
475
|
+
// Replicate what rb_profile_frames would do
|
476
|
+
if (!RTEST(loc->iseq)) {
|
477
|
+
return 0;
|
478
|
+
}
|
479
|
+
rb_iseq_t *iseq = (rb_iseq_t *)loc->iseq;
|
480
|
+
VALUE label =
|
481
|
+
base ? iseq->body->location.base_label : iseq->body->location.label;
|
482
|
+
strbuilder_append_value(strout, label);
|
483
|
+
} else {
|
484
|
+
if (!RTEST(loc->callable_method_entry)) {
|
485
|
+
return 0;
|
486
|
+
}
|
487
|
+
rb_callable_method_entry_t *cme =
|
488
|
+
(rb_callable_method_entry_t *)loc->callable_method_entry;
|
489
|
+
strbuilder_append_value(strout, rb_id2str(cme->def->original_id));
|
490
|
+
}
|
491
|
+
return 1;
|
492
|
+
}
|
493
|
+
|
494
|
+
static void raw_location_to_minimal_location(const raw_location *raw_loc,
|
495
|
+
minimal_location_t *min_loc) {
|
496
|
+
|
497
|
+
min_loc->is_ruby_frame = raw_loc->is_ruby_frame;
|
498
|
+
if (RTEST(raw_loc->callable_method_entry)) {
|
499
|
+
min_loc->method_name_contents = BACKTRACIE_METHOD_NAME_CONTENTS_CME_ID;
|
500
|
+
min_loc->method_name.cme_method_id =
|
501
|
+
((rb_callable_method_entry_t *)raw_loc->callable_method_entry)
|
502
|
+
->called_id;
|
503
|
+
} else if (RTEST(raw_loc->iseq)) {
|
504
|
+
min_loc->method_name_contents = BACKTRACIE_METHOD_NAME_CONTENTS_BASE_LABEL;
|
505
|
+
min_loc->method_name.base_label =
|
506
|
+
((rb_iseq_t *)raw_loc->iseq)->body->location.base_label;
|
507
|
+
} else {
|
508
|
+
min_loc->method_name_contents = BACKTRACIE_METHOD_NAME_CONTENTS_BASE_LABEL;
|
509
|
+
min_loc->method_name.base_label = Qnil;
|
510
|
+
}
|
511
|
+
|
512
|
+
if (RTEST(raw_loc->iseq)) {
|
513
|
+
min_loc->has_iseq_type = 1;
|
514
|
+
min_loc->iseq_type = ((rb_iseq_t *)raw_loc->iseq)->body->type;
|
515
|
+
min_loc->line_number = calc_lineno((rb_iseq_t *)raw_loc->iseq, raw_loc->pc);
|
516
|
+
} else {
|
517
|
+
min_loc->has_iseq_type = 0;
|
518
|
+
min_loc->line_number = 0;
|
519
|
+
}
|
520
|
+
|
521
|
+
if (RTEST(raw_loc->self_is_real_self)) {
|
522
|
+
min_loc->method_qualifier_contents =
|
523
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF;
|
524
|
+
min_loc->method_qualifier.self = raw_loc->self_or_self_class;
|
525
|
+
} else if (RTEST(raw_loc->callable_method_entry)) {
|
526
|
+
min_loc->method_qualifier_contents =
|
527
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS;
|
528
|
+
min_loc->method_qualifier.cme_defined_class =
|
529
|
+
((rb_callable_method_entry_t *)raw_loc->callable_method_entry)
|
530
|
+
->defined_class;
|
531
|
+
} else {
|
532
|
+
min_loc->method_qualifier_contents =
|
533
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS;
|
534
|
+
min_loc->method_qualifier.self_class = raw_loc->self_or_self_class;
|
535
|
+
}
|
536
|
+
}
|
537
|
+
|
538
|
+
static void mod_to_s_anon(VALUE klass, strbuilder_t *strout) {
|
539
|
+
// Anonymous module/class - print the name of the first non-anonymous super.
|
540
|
+
// something like "#{klazz.ancestors.map(&:name).compact.first}$anonymous"
|
541
|
+
//
|
542
|
+
// Note that if klazz is a module, we want to do this on klazz.class, not
|
543
|
+
// klazz itself:
|
544
|
+
//
|
545
|
+
// irb(main):008:0> m = Module.new
|
546
|
+
// => #<Module:0x00000000021a7208>
|
547
|
+
// irb(main):009:0> m.ancestors
|
548
|
+
// => [#<Module:0x00000000021a7208>]
|
549
|
+
// # Not very useful - nothing with a name is in the ancestor chain
|
550
|
+
// irb(main):010:0> m.class.ancestors
|
551
|
+
// => [Module, Object, Kernel, BasicObject]
|
552
|
+
// # Much more useful - we can call this Module$anonymous.
|
553
|
+
//
|
554
|
+
VALUE superclass = klass;
|
555
|
+
VALUE superclass_name = Qnil;
|
556
|
+
// Find an actual class - every _class_ is guaranteed to be a descendant of
|
557
|
+
// BasicObject at least, which has a name, so we'll be able to name this
|
558
|
+
// _something_.
|
559
|
+
while (!RB_TYPE_P(superclass, T_CLASS)) {
|
560
|
+
superclass = rb_class_of(superclass);
|
561
|
+
}
|
562
|
+
do {
|
563
|
+
superclass = rb_class_superclass(superclass);
|
564
|
+
BACKTRACIE_ASSERT(RTEST(superclass));
|
565
|
+
superclass_name = rb_mod_name(superclass);
|
566
|
+
} while (!RTEST(superclass_name));
|
567
|
+
strbuilder_append_value(strout, superclass_name);
|
568
|
+
}
|
569
|
+
|
570
|
+
static void mod_to_s_singleton(VALUE klass, strbuilder_t *strout) {
|
571
|
+
VALUE singleton_of = rb_class_real(klass);
|
572
|
+
// If this is the singleton_class of a Class, or Module, we want to print
|
573
|
+
// the _value_ of the object, and _NOT_ its class.
|
574
|
+
// Basically:
|
575
|
+
// module MyModule; end;
|
576
|
+
// klazz = MyModule.singleton_class =>
|
577
|
+
// we want to output "MyModule"
|
578
|
+
//
|
579
|
+
// klazz = Something.new.singleton_class =>
|
580
|
+
// we want to output "Something"
|
581
|
+
//
|
582
|
+
if (singleton_of == rb_cModule || singleton_of == rb_cClass) {
|
583
|
+
// The first case. Use the id_attached symbol to get what this is the
|
584
|
+
// singleton_class _of_.
|
585
|
+
singleton_of = rb_ivar_get(klass, id__attached__);
|
586
|
+
}
|
587
|
+
mod_to_s(singleton_of, strout);
|
588
|
+
}
|
589
|
+
|
590
|
+
static void mod_to_s_refinement(VALUE refinement_module, strbuilder_t *strout) {
|
591
|
+
ID id_refined_class;
|
592
|
+
CONST_ID(id_refined_class, "__refined_class__");
|
593
|
+
VALUE refined_class = rb_attr_get(refinement_module, id_refined_class);
|
594
|
+
ID id_defined_at;
|
595
|
+
CONST_ID(id_defined_at, "__defined_at__");
|
596
|
+
VALUE defined_at = rb_attr_get(refinement_module, id_defined_at);
|
597
|
+
|
598
|
+
mod_to_s(refined_class, strout);
|
599
|
+
strbuilder_append(strout, "$refinement@");
|
600
|
+
mod_to_s(defined_at, strout);
|
601
|
+
}
|
602
|
+
|
603
|
+
static void mod_to_s(VALUE klass, strbuilder_t *strout) {
|
604
|
+
if (FL_TEST(klass, FL_SINGLETON)) {
|
605
|
+
mod_to_s_singleton(klass, strout);
|
606
|
+
strbuilder_append(strout, "$singleton");
|
607
|
+
return;
|
608
|
+
}
|
609
|
+
|
610
|
+
VALUE klass_name = rb_mod_name(klass);
|
611
|
+
if (!RTEST(rb_mod_name(klass))) {
|
612
|
+
// If I understood it correctly, a T_ICLASS represents the inclusion of a module into
|
613
|
+
// a class. In this situation, we usually want to use the module name instead.
|
614
|
+
if (RB_TYPE_P(klass, T_ICLASS)) {
|
615
|
+
VALUE included_module = RBASIC(klass)->klass;
|
616
|
+
mod_to_s(included_module, strout);
|
617
|
+
return;
|
618
|
+
} else {
|
619
|
+
mod_to_s_anon(klass, strout);
|
620
|
+
strbuilder_append(strout, "$anonymous");
|
621
|
+
return;
|
622
|
+
}
|
623
|
+
}
|
624
|
+
|
625
|
+
// Non-anonymous module/class.
|
626
|
+
// something like "#{klazz.name}"
|
627
|
+
strbuilder_append_value(strout, klass_name);
|
628
|
+
}
|
629
|
+
|
630
|
+
static void minimal_location_method_qualifier(const minimal_location_t *loc,
|
631
|
+
strbuilder_t *strout) {
|
632
|
+
// First, check if it's a special object.
|
633
|
+
if (loc->method_qualifier_contents ==
|
634
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
|
635
|
+
if (loc->method_qualifier.self == backtracie_main_object_instance) {
|
636
|
+
strbuilder_append(strout, "Object$<main>#");
|
637
|
+
return;
|
638
|
+
}
|
639
|
+
if (loc->method_qualifier.self == rb_mRubyVMFrozenCore) {
|
640
|
+
strbuilder_append(strout, "RubyVM::FrozenCore#");
|
641
|
+
return;
|
642
|
+
}
|
643
|
+
}
|
644
|
+
|
645
|
+
// Next, check if it's a refinement.
|
646
|
+
if (loc->method_qualifier_contents ==
|
647
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS) {
|
648
|
+
VALUE class_of_defined_class =
|
649
|
+
rb_class_of(loc->method_qualifier.cme_defined_class);
|
650
|
+
if (RTEST(class_of_defined_class) &&
|
651
|
+
FL_TEST(class_of_defined_class, RMODULE_IS_REFINEMENT)) {
|
652
|
+
// The method being called is defined on a refinement.
|
653
|
+
mod_to_s_refinement(class_of_defined_class, strout);
|
654
|
+
strbuilder_append(strout, "#");
|
655
|
+
return;
|
656
|
+
}
|
657
|
+
}
|
658
|
+
|
659
|
+
// Next, check if the method receiver is itself a class or module.
|
660
|
+
if (loc->method_qualifier_contents ==
|
661
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
|
662
|
+
if (class_or_module_or_iclass(loc->method_qualifier.self)) {
|
663
|
+
// We have something like SomeModule.foo being called directly like that,
|
664
|
+
// without an instance.
|
665
|
+
mod_to_s(loc->method_qualifier.self, strout);
|
666
|
+
strbuilder_append(strout, ".");
|
667
|
+
return;
|
668
|
+
}
|
669
|
+
}
|
670
|
+
|
671
|
+
// Means the method receiver is _not_ a module/class; so, print the name of
|
672
|
+
// the class that the method is defined on.
|
673
|
+
VALUE method_target;
|
674
|
+
switch (loc->method_qualifier_contents) {
|
675
|
+
case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF:
|
676
|
+
method_target = rb_class_of(loc->method_qualifier.self);
|
677
|
+
break;
|
678
|
+
case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS:
|
679
|
+
method_target = loc->method_qualifier.self_class;
|
680
|
+
break;
|
681
|
+
case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS:
|
682
|
+
method_target = loc->method_qualifier.cme_defined_class;
|
683
|
+
break;
|
684
|
+
default:
|
685
|
+
strbuilder_append(strout, "FIXME_SHOULD_NEVER_HAPPEN");
|
686
|
+
return;
|
687
|
+
}
|
688
|
+
|
689
|
+
mod_to_s(method_target, strout);
|
690
|
+
strbuilder_append(strout, "#");
|
691
|
+
}
|
692
|
+
|
693
|
+
static void minimal_location_method_name(const minimal_location_t *loc,
|
694
|
+
strbuilder_t *strout) {
|
695
|
+
if (loc->method_name_contents == BACKTRACIE_METHOD_NAME_CONTENTS_CME_ID) {
|
696
|
+
// With a callable method entry, things are simple; just use that.
|
697
|
+
VALUE method_name = rb_id2str(loc->method_name.cme_method_id);
|
698
|
+
strbuilder_append_value(strout, method_name);
|
699
|
+
if (loc->has_iseq_type && iseq_type_is_block_or_eval(loc->iseq_type)) {
|
700
|
+
strbuilder_append(strout, "{block}");
|
701
|
+
}
|
702
|
+
} else {
|
703
|
+
// With no CME, we _DO NOT_ want to use iseq->base_label if we're a block,
|
704
|
+
// because otherwise it will print something like "block in (something)". In
|
705
|
+
// fact, using the iseq->base_label is pretty much a last resort. If we
|
706
|
+
// manage to write _anything_ else in our backtrace, we won't use it.
|
707
|
+
bool did_write_anything = false;
|
708
|
+
if (loc->method_qualifier_contents ==
|
709
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
|
710
|
+
if (RB_TYPE_P(loc->method_qualifier.self, T_CLASS)) {
|
711
|
+
// No CME, and self being a class/module, means we're executing code
|
712
|
+
// inside a class Foo; ...; end;
|
713
|
+
strbuilder_append(strout, "{class exec}");
|
714
|
+
did_write_anything = true;
|
715
|
+
}
|
716
|
+
if (RB_TYPE_P(loc->method_qualifier.self, T_MODULE)) {
|
717
|
+
strbuilder_append(strout, "{module exec}");
|
718
|
+
did_write_anything = true;
|
719
|
+
}
|
720
|
+
}
|
721
|
+
if (loc->has_iseq_type && iseq_type_is_block_or_eval(loc->iseq_type)) {
|
722
|
+
strbuilder_append(strout, "{block}");
|
723
|
+
did_write_anything = true;
|
724
|
+
}
|
725
|
+
if (!did_write_anything) {
|
726
|
+
// As a fallback, use whatever is on the base_label.
|
727
|
+
// n.b. method_name_contents is guaranteed to be set to
|
728
|
+
// CONTENTS_BASE_LABEL in this branch.
|
729
|
+
strbuilder_append_value(strout, loc->method_name.base_label);
|
730
|
+
}
|
731
|
+
}
|
732
|
+
}
|
733
|
+
|
734
|
+
// This is mostly a reimplementation of pathobj_path from vm_core.h
|
735
|
+
// returns true if a path was found, and false otherwise
|
736
|
+
static bool iseq_path(const rb_iseq_t *iseq, bool absolute,
|
737
|
+
strbuilder_t *strout) {
|
738
|
+
if (!iseq) {
|
739
|
+
return 0;
|
740
|
+
}
|
741
|
+
|
742
|
+
VALUE path_str;
|
743
|
+
#ifdef PRE_LOCATION_PATHOBJ
|
744
|
+
rb_iseq_location_t loc = iseq->body->location;
|
745
|
+
path_str = absolute ? loc.absolute_path : loc.path;
|
746
|
+
#else
|
747
|
+
VALUE pathobj = iseq->body->location.pathobj;
|
748
|
+
if (RB_TYPE_P(pathobj, T_STRING)) {
|
749
|
+
path_str = pathobj;
|
750
|
+
} else {
|
751
|
+
BACKTRACIE_ASSERT(RB_TYPE_P(pathobj, T_ARRAY));
|
752
|
+
int path_type = absolute ? PATHOBJ_REALPATH : PATHOBJ_PATH;
|
753
|
+
path_str = RARRAY_AREF(pathobj, path_type);
|
754
|
+
}
|
755
|
+
#endif
|
756
|
+
|
757
|
+
if (RTEST(path_str)) {
|
758
|
+
strbuilder_append_value(strout, path_str);
|
759
|
+
return 1;
|
760
|
+
} else {
|
761
|
+
return 0;
|
762
|
+
}
|
763
|
+
}
|
764
|
+
|
765
|
+
/**********************************************************************
|
766
|
+
vm_backtrace.c -
|
767
|
+
$Author: ko1 $
|
768
|
+
created at: Sun Jun 03 00:14:20 2012
|
769
|
+
Copyright (C) 1993-2012 Yukihiro Matsumoto
|
770
|
+
**********************************************************************/
|
771
|
+
|
772
|
+
static int calc_lineno(const rb_iseq_t *iseq, const void *pc) {
|
773
|
+
VM_ASSERT(iseq);
|
774
|
+
VM_ASSERT(iseq->body);
|
775
|
+
VM_ASSERT(iseq->body->iseq_encoded);
|
776
|
+
VM_ASSERT(iseq->body->iseq_size);
|
777
|
+
if (!pc) {
|
778
|
+
/* This can happen during VM bootup. */
|
779
|
+
VM_ASSERT(iseq->body->type == ISEQ_TYPE_TOP);
|
780
|
+
VM_ASSERT(!iseq->body->local_table);
|
781
|
+
VM_ASSERT(!iseq->body->local_table_size);
|
782
|
+
return 0;
|
783
|
+
} else {
|
784
|
+
ptrdiff_t n = (const VALUE *)pc - iseq->body->iseq_encoded;
|
785
|
+
VM_ASSERT(n <= iseq->body->iseq_size);
|
786
|
+
VM_ASSERT(n >= 0);
|
787
|
+
ASSUME(n >= 0);
|
788
|
+
size_t pos = n; /* no overflow */
|
789
|
+
if (LIKELY(pos)) {
|
790
|
+
/* use pos-1 because PC points next instruction at the beginning of
|
791
|
+
* instruction */
|
792
|
+
pos--;
|
793
|
+
}
|
794
|
+
return rb_iseq_line_no(iseq, pos);
|
795
|
+
}
|
796
|
+
}
|
797
|
+
|
798
|
+
// ----------------------------------------------------------------------
|
799
|
+
|
800
|
+
/**********************************************************************
|
801
|
+
|
802
|
+
vm_insnhelper.c - instruction helper functions.
|
803
|
+
|
804
|
+
$Author$
|
805
|
+
|
806
|
+
Copyright (C) 2007 Koichi Sasada
|
807
|
+
|
808
|
+
**********************************************************************/
|
809
|
+
|
810
|
+
static rb_callable_method_entry_t *copied_check_method_entry(VALUE obj,
|
811
|
+
int can_be_svar) {
|
812
|
+
if (obj == Qfalse)
|
813
|
+
return NULL;
|
814
|
+
|
815
|
+
#if VM_CHECK_MODE > 0
|
816
|
+
if (!RB_TYPE_P(obj, T_IMEMO))
|
817
|
+
rb_bug("copied_check_method_entry: unknown type: %s", rb_obj_info(obj));
|
818
|
+
#endif
|
819
|
+
|
820
|
+
switch (imemo_type(obj)) {
|
821
|
+
case imemo_ment:
|
822
|
+
return (rb_callable_method_entry_t *)obj;
|
823
|
+
case imemo_cref:
|
824
|
+
return NULL;
|
825
|
+
case imemo_svar:
|
826
|
+
if (can_be_svar) {
|
827
|
+
return copied_check_method_entry(((struct vm_svar *)obj)->cref_or_me,
|
828
|
+
FALSE);
|
829
|
+
}
|
830
|
+
default:
|
831
|
+
#if VM_CHECK_MODE > 0
|
832
|
+
rb_bug("copied_check_method_entry: svar should not be there:");
|
833
|
+
#endif
|
834
|
+
return NULL;
|
835
|
+
}
|
836
|
+
}
|
837
|
+
|
838
|
+
static const rb_callable_method_entry_t *
|
839
|
+
copied_vm_frame_method_entry(const rb_control_frame_t *cfp) {
|
840
|
+
const VALUE *ep = cfp->ep;
|
841
|
+
rb_callable_method_entry_t *me;
|
842
|
+
|
843
|
+
while (!VM_ENV_LOCAL_P(ep)) {
|
844
|
+
if ((me = copied_check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF],
|
845
|
+
FALSE)) != NULL)
|
846
|
+
return me;
|
847
|
+
ep = VM_ENV_PREV_EP(ep);
|
848
|
+
}
|
849
|
+
|
850
|
+
return copied_check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE);
|
851
|
+
}
|
852
|
+
|
853
|
+
// ----------------------------------------------------------------------
|
854
|
+
|
855
|
+
static const rb_callable_method_entry_t *
|
856
|
+
backtracie_vm_frame_method_entry(const rb_control_frame_t *cfp) {
|
857
|
+
#ifdef PRE_MJIT_RUBY
|
858
|
+
// In < ruby 2.6, the symbol for rb_vm_frame_method_entry is hidden
|
859
|
+
return copied_vm_frame_method_entry(cfp);
|
860
|
+
#else
|
861
|
+
return rb_vm_frame_method_entry(cfp);
|
862
|
+
#endif
|
863
|
+
}
|
864
|
+
|
865
|
+
VALUE backtracie_frame_wrapper_new(size_t count) {
|
866
|
+
frame_wrapper_t *frame_data;
|
867
|
+
VALUE wrapper =
|
868
|
+
TypedData_Make_Struct(backtracie_frame_wrapper_class, frame_wrapper_t,
|
869
|
+
&backtracie_frame_wrapper_type, frame_data);
|
870
|
+
frame_data->capa = count;
|
871
|
+
frame_data->len = 0;
|
872
|
+
frame_data->frames = xcalloc(count, sizeof(raw_location));
|
873
|
+
return wrapper;
|
874
|
+
}
|
875
|
+
|
876
|
+
raw_location *backtracie_frame_wrapper_frames(VALUE wrapper) {
|
877
|
+
frame_wrapper_t *frame_data;
|
878
|
+
TypedData_Get_Struct(wrapper, frame_wrapper_t, &backtracie_frame_wrapper_type,
|
879
|
+
frame_data);
|
880
|
+
return frame_data->frames;
|
881
|
+
}
|
882
|
+
int *backtracie_frame_wrapper_len(VALUE wrapper) {
|
883
|
+
frame_wrapper_t *frame_data;
|
884
|
+
TypedData_Get_Struct(wrapper, frame_wrapper_t, &backtracie_frame_wrapper_type,
|
885
|
+
frame_data);
|
886
|
+
return &frame_data->len;
|
887
|
+
}
|
888
|
+
|
889
|
+
static void backtracie_frame_wrapper_mark(void *ptr) {
|
890
|
+
frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
|
891
|
+
for (int i = 0; i < frame_data->len; i++) {
|
892
|
+
backtracie_frame_mark_movable(&frame_data->frames[i]);
|
893
|
+
}
|
894
|
+
}
|
895
|
+
static void backtracie_frame_wrapper_compact(void *ptr) {
|
896
|
+
frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
|
897
|
+
for (int i = 0; i < frame_data->len; i++) {
|
898
|
+
backtracie_frame_compact(&frame_data->frames[i]);
|
899
|
+
}
|
900
|
+
}
|
901
|
+
static void backtracie_frame_wrapper_free(void *ptr) {
|
902
|
+
frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
|
903
|
+
xfree(frame_data->frames);
|
904
|
+
}
|
905
|
+
static size_t backtracie_frame_wrapper_memsize(const void *ptr) {
|
906
|
+
const frame_wrapper_t *frame_data = (const frame_wrapper_t *)ptr;
|
907
|
+
return sizeof(frame_wrapper_t) + sizeof(raw_location) * frame_data->capa;
|
908
|
+
}
|
909
|
+
|
910
|
+
bool backtracie_capture_minimal_frame_for_thread(VALUE thread, int frame_index,
|
911
|
+
minimal_location_t *loc) {
|
912
|
+
raw_location raw_loc;
|
913
|
+
bool ret = backtracie_capture_frame_for_thread(thread, frame_index, &raw_loc);
|
914
|
+
if (!ret) {
|
915
|
+
return ret;
|
916
|
+
}
|
917
|
+
raw_location_to_minimal_location(&raw_loc, loc);
|
918
|
+
|
919
|
+
return ret;
|
920
|
+
}
|
921
|
+
|
922
|
+
size_t backtracie_minimal_frame_name_cstr(const minimal_location_t *loc,
|
923
|
+
char *buf, size_t buflen) {
|
924
|
+
|
925
|
+
strbuilder_t builder;
|
926
|
+
strbuilder_init(&builder, buf, buflen);
|
927
|
+
minimal_location_method_qualifier(loc, &builder);
|
928
|
+
minimal_location_method_name(loc, &builder);
|
929
|
+
return builder.attempted_size;
|
930
|
+
}
|
931
|
+
|
932
|
+
size_t backtracie_minimal_frame_filename_cstr(const minimal_location_t *loc,
|
933
|
+
char *buf, size_t buflen) {
|
934
|
+
|
935
|
+
strbuilder_t builder;
|
936
|
+
strbuilder_init(&builder, buf, buflen);
|
937
|
+
if (RTEST(loc->filename)) {
|
938
|
+
strbuilder_append_value(&builder, loc->filename);
|
939
|
+
}
|
940
|
+
return builder.attempted_size;
|
941
|
+
}
|