backtracie 0.2.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.adoc +8 -0
- data/backtracie.gemspec +3 -0
- data/ext/backtracie_native_extension/backtracie.c +185 -275
- data/ext/backtracie_native_extension/backtracie_frames.c +930 -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 +59 -34
- 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
- data/lib/backtracie.rb +11 -2
- metadata +33 -9
- data/ext/backtracie_native_extension/ruby_shards.c +0 -593
- data/ext/backtracie_native_extension/ruby_shards.h +0 -176
@@ -0,0 +1,930 @@
|
|
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
|
+
st_lookup(RCLASS_IV_TBL(klass), id__attached__, (st_data_t *)&singleton_of);
|
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
|
+
mod_to_s_anon(klass, strout);
|
613
|
+
strbuilder_append(strout, "$anonymous");
|
614
|
+
return;
|
615
|
+
}
|
616
|
+
|
617
|
+
// Non-anonymous module/class.
|
618
|
+
// something like "#{klazz.name}"
|
619
|
+
strbuilder_append_value(strout, klass_name);
|
620
|
+
}
|
621
|
+
|
622
|
+
static void minimal_location_method_qualifier(const minimal_location_t *loc,
|
623
|
+
strbuilder_t *strout) {
|
624
|
+
// First, check if it's a special object.
|
625
|
+
if (loc->method_qualifier_contents ==
|
626
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
|
627
|
+
if (loc->method_qualifier.self == backtracie_main_object_instance) {
|
628
|
+
strbuilder_append(strout, "Object$<main>#");
|
629
|
+
return;
|
630
|
+
}
|
631
|
+
if (loc->method_qualifier.self == rb_mRubyVMFrozenCore) {
|
632
|
+
strbuilder_append(strout, "RubyVM::FrozenCore#");
|
633
|
+
return;
|
634
|
+
}
|
635
|
+
}
|
636
|
+
|
637
|
+
// Next, check if it's a refinement.
|
638
|
+
if (loc->method_qualifier_contents ==
|
639
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS) {
|
640
|
+
VALUE class_of_defined_class =
|
641
|
+
rb_class_of(loc->method_qualifier.cme_defined_class);
|
642
|
+
if (RTEST(class_of_defined_class) &&
|
643
|
+
FL_TEST(class_of_defined_class, RMODULE_IS_REFINEMENT)) {
|
644
|
+
// The method being called is defined on a refinement.
|
645
|
+
mod_to_s_refinement(class_of_defined_class, strout);
|
646
|
+
strbuilder_append(strout, "#");
|
647
|
+
return;
|
648
|
+
}
|
649
|
+
}
|
650
|
+
|
651
|
+
// Next, check if the method receiver is itself a class or module.
|
652
|
+
if (loc->method_qualifier_contents ==
|
653
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
|
654
|
+
if (class_or_module_or_iclass(loc->method_qualifier.self)) {
|
655
|
+
// We have something like SomeModule.foo being called directly like that,
|
656
|
+
// without an instance.
|
657
|
+
mod_to_s(loc->method_qualifier.self, strout);
|
658
|
+
strbuilder_append(strout, ".");
|
659
|
+
return;
|
660
|
+
}
|
661
|
+
}
|
662
|
+
|
663
|
+
// Means the method receiver is _not_ a module/class; so, print the name of
|
664
|
+
// the class that the method is defined on.
|
665
|
+
VALUE method_target;
|
666
|
+
switch (loc->method_qualifier_contents) {
|
667
|
+
case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF:
|
668
|
+
method_target = rb_class_of(loc->method_qualifier.self);
|
669
|
+
break;
|
670
|
+
case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS:
|
671
|
+
method_target = loc->method_qualifier.self_class;
|
672
|
+
break;
|
673
|
+
case BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS:
|
674
|
+
method_target = loc->method_qualifier.cme_defined_class;
|
675
|
+
break;
|
676
|
+
}
|
677
|
+
|
678
|
+
mod_to_s(method_target, strout);
|
679
|
+
strbuilder_append(strout, "#");
|
680
|
+
}
|
681
|
+
|
682
|
+
static void minimal_location_method_name(const minimal_location_t *loc,
|
683
|
+
strbuilder_t *strout) {
|
684
|
+
if (loc->method_name_contents == BACKTRACIE_METHOD_NAME_CONTENTS_CME_ID) {
|
685
|
+
// With a callable method entry, things are simple; just use that.
|
686
|
+
VALUE method_name = rb_id2str(loc->method_name.cme_method_id);
|
687
|
+
strbuilder_append_value(strout, method_name);
|
688
|
+
if (loc->has_iseq_type && iseq_type_is_block_or_eval(loc->iseq_type)) {
|
689
|
+
strbuilder_append(strout, "{block}");
|
690
|
+
}
|
691
|
+
} else {
|
692
|
+
// With no CME, we _DO NOT_ want to use iseq->base_label if we're a block,
|
693
|
+
// because otherwise it will print something like "block in (something)". In
|
694
|
+
// fact, using the iseq->base_label is pretty much a last resort. If we
|
695
|
+
// manage to write _anything_ else in our backtrace, we won't use it.
|
696
|
+
bool did_write_anything = false;
|
697
|
+
if (loc->method_qualifier_contents ==
|
698
|
+
BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF) {
|
699
|
+
if (RB_TYPE_P(loc->method_qualifier.self, T_CLASS)) {
|
700
|
+
// No CME, and self being a class/module, means we're executing code
|
701
|
+
// inside a class Foo; ...; end;
|
702
|
+
strbuilder_append(strout, "{class exec}");
|
703
|
+
did_write_anything = true;
|
704
|
+
}
|
705
|
+
if (RB_TYPE_P(loc->method_qualifier.self, T_MODULE)) {
|
706
|
+
strbuilder_append(strout, "{module exec}");
|
707
|
+
did_write_anything = true;
|
708
|
+
}
|
709
|
+
}
|
710
|
+
if (loc->has_iseq_type && iseq_type_is_block_or_eval(loc->iseq_type)) {
|
711
|
+
strbuilder_append(strout, "{block}");
|
712
|
+
did_write_anything = true;
|
713
|
+
}
|
714
|
+
if (!did_write_anything) {
|
715
|
+
// As a fallback, use whatever is on the base_label.
|
716
|
+
// n.b. method_name_contents is guaranteed to be set to
|
717
|
+
// CONTENTS_BASE_LABEL in this branch.
|
718
|
+
strbuilder_append_value(strout, loc->method_name.base_label);
|
719
|
+
}
|
720
|
+
}
|
721
|
+
}
|
722
|
+
|
723
|
+
// This is mostly a reimplementation of pathobj_path from vm_core.h
|
724
|
+
// returns true if a path was found, and false otherwise
|
725
|
+
static bool iseq_path(const rb_iseq_t *iseq, bool absolute,
|
726
|
+
strbuilder_t *strout) {
|
727
|
+
if (!iseq) {
|
728
|
+
return 0;
|
729
|
+
}
|
730
|
+
|
731
|
+
VALUE path_str;
|
732
|
+
#ifdef PRE_LOCATION_PATHOBJ
|
733
|
+
rb_iseq_location_t loc = iseq->body->location;
|
734
|
+
path_str = absolute ? loc.absolute_path : loc.path;
|
735
|
+
#else
|
736
|
+
VALUE pathobj = iseq->body->location.pathobj;
|
737
|
+
if (RB_TYPE_P(pathobj, T_STRING)) {
|
738
|
+
path_str = pathobj;
|
739
|
+
} else {
|
740
|
+
BACKTRACIE_ASSERT(RB_TYPE_P(pathobj, T_ARRAY));
|
741
|
+
int path_type = absolute ? PATHOBJ_REALPATH : PATHOBJ_PATH;
|
742
|
+
path_str = RARRAY_AREF(pathobj, path_type);
|
743
|
+
}
|
744
|
+
#endif
|
745
|
+
|
746
|
+
if (RTEST(path_str)) {
|
747
|
+
strbuilder_append_value(strout, path_str);
|
748
|
+
return 1;
|
749
|
+
} else {
|
750
|
+
return 0;
|
751
|
+
}
|
752
|
+
}
|
753
|
+
|
754
|
+
/**********************************************************************
|
755
|
+
vm_backtrace.c -
|
756
|
+
$Author: ko1 $
|
757
|
+
created at: Sun Jun 03 00:14:20 2012
|
758
|
+
Copyright (C) 1993-2012 Yukihiro Matsumoto
|
759
|
+
**********************************************************************/
|
760
|
+
|
761
|
+
static int calc_lineno(const rb_iseq_t *iseq, const void *pc) {
|
762
|
+
VM_ASSERT(iseq);
|
763
|
+
VM_ASSERT(iseq->body);
|
764
|
+
VM_ASSERT(iseq->body->iseq_encoded);
|
765
|
+
VM_ASSERT(iseq->body->iseq_size);
|
766
|
+
if (!pc) {
|
767
|
+
/* This can happen during VM bootup. */
|
768
|
+
VM_ASSERT(iseq->body->type == ISEQ_TYPE_TOP);
|
769
|
+
VM_ASSERT(!iseq->body->local_table);
|
770
|
+
VM_ASSERT(!iseq->body->local_table_size);
|
771
|
+
return 0;
|
772
|
+
} else {
|
773
|
+
ptrdiff_t n = (const VALUE *)pc - iseq->body->iseq_encoded;
|
774
|
+
VM_ASSERT(n <= iseq->body->iseq_size);
|
775
|
+
VM_ASSERT(n >= 0);
|
776
|
+
ASSUME(n >= 0);
|
777
|
+
size_t pos = n; /* no overflow */
|
778
|
+
if (LIKELY(pos)) {
|
779
|
+
/* use pos-1 because PC points next instruction at the beginning of
|
780
|
+
* instruction */
|
781
|
+
pos--;
|
782
|
+
}
|
783
|
+
return rb_iseq_line_no(iseq, pos);
|
784
|
+
}
|
785
|
+
}
|
786
|
+
|
787
|
+
// ----------------------------------------------------------------------
|
788
|
+
|
789
|
+
/**********************************************************************
|
790
|
+
|
791
|
+
vm_insnhelper.c - instruction helper functions.
|
792
|
+
|
793
|
+
$Author$
|
794
|
+
|
795
|
+
Copyright (C) 2007 Koichi Sasada
|
796
|
+
|
797
|
+
**********************************************************************/
|
798
|
+
|
799
|
+
static rb_callable_method_entry_t *copied_check_method_entry(VALUE obj,
|
800
|
+
int can_be_svar) {
|
801
|
+
if (obj == Qfalse)
|
802
|
+
return NULL;
|
803
|
+
|
804
|
+
#if VM_CHECK_MODE > 0
|
805
|
+
if (!RB_TYPE_P(obj, T_IMEMO))
|
806
|
+
rb_bug("copied_check_method_entry: unknown type: %s", rb_obj_info(obj));
|
807
|
+
#endif
|
808
|
+
|
809
|
+
switch (imemo_type(obj)) {
|
810
|
+
case imemo_ment:
|
811
|
+
return (rb_callable_method_entry_t *)obj;
|
812
|
+
case imemo_cref:
|
813
|
+
return NULL;
|
814
|
+
case imemo_svar:
|
815
|
+
if (can_be_svar) {
|
816
|
+
return copied_check_method_entry(((struct vm_svar *)obj)->cref_or_me,
|
817
|
+
FALSE);
|
818
|
+
}
|
819
|
+
default:
|
820
|
+
#if VM_CHECK_MODE > 0
|
821
|
+
rb_bug("copied_check_method_entry: svar should not be there:");
|
822
|
+
#endif
|
823
|
+
return NULL;
|
824
|
+
}
|
825
|
+
}
|
826
|
+
|
827
|
+
static const rb_callable_method_entry_t *
|
828
|
+
copied_vm_frame_method_entry(const rb_control_frame_t *cfp) {
|
829
|
+
const VALUE *ep = cfp->ep;
|
830
|
+
rb_callable_method_entry_t *me;
|
831
|
+
|
832
|
+
while (!VM_ENV_LOCAL_P(ep)) {
|
833
|
+
if ((me = copied_check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF],
|
834
|
+
FALSE)) != NULL)
|
835
|
+
return me;
|
836
|
+
ep = VM_ENV_PREV_EP(ep);
|
837
|
+
}
|
838
|
+
|
839
|
+
return copied_check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE);
|
840
|
+
}
|
841
|
+
|
842
|
+
// ----------------------------------------------------------------------
|
843
|
+
|
844
|
+
static const rb_callable_method_entry_t *
|
845
|
+
backtracie_vm_frame_method_entry(const rb_control_frame_t *cfp) {
|
846
|
+
#ifdef PRE_MJIT_RUBY
|
847
|
+
// In < ruby 2.6, the symbol for rb_vm_frame_method_entry is hidden
|
848
|
+
return copied_vm_frame_method_entry(cfp);
|
849
|
+
#else
|
850
|
+
return rb_vm_frame_method_entry(cfp);
|
851
|
+
#endif
|
852
|
+
}
|
853
|
+
|
854
|
+
VALUE backtracie_frame_wrapper_new(size_t count) {
|
855
|
+
frame_wrapper_t *frame_data;
|
856
|
+
VALUE wrapper =
|
857
|
+
TypedData_Make_Struct(backtracie_frame_wrapper_class, frame_wrapper_t,
|
858
|
+
&backtracie_frame_wrapper_type, frame_data);
|
859
|
+
frame_data->capa = count;
|
860
|
+
frame_data->len = 0;
|
861
|
+
frame_data->frames = xcalloc(count, sizeof(raw_location));
|
862
|
+
return wrapper;
|
863
|
+
}
|
864
|
+
|
865
|
+
raw_location *backtracie_frame_wrapper_frames(VALUE wrapper) {
|
866
|
+
frame_wrapper_t *frame_data;
|
867
|
+
TypedData_Get_Struct(wrapper, frame_wrapper_t, &backtracie_frame_wrapper_type,
|
868
|
+
frame_data);
|
869
|
+
return frame_data->frames;
|
870
|
+
}
|
871
|
+
int *backtracie_frame_wrapper_len(VALUE wrapper) {
|
872
|
+
frame_wrapper_t *frame_data;
|
873
|
+
TypedData_Get_Struct(wrapper, frame_wrapper_t, &backtracie_frame_wrapper_type,
|
874
|
+
frame_data);
|
875
|
+
return &frame_data->len;
|
876
|
+
}
|
877
|
+
|
878
|
+
static void backtracie_frame_wrapper_mark(void *ptr) {
|
879
|
+
frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
|
880
|
+
for (int i = 0; i < frame_data->len; i++) {
|
881
|
+
backtracie_frame_mark_movable(&frame_data->frames[i]);
|
882
|
+
}
|
883
|
+
}
|
884
|
+
static void backtracie_frame_wrapper_compact(void *ptr) {
|
885
|
+
frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
|
886
|
+
for (int i = 0; i < frame_data->len; i++) {
|
887
|
+
backtracie_frame_compact(&frame_data->frames[i]);
|
888
|
+
}
|
889
|
+
}
|
890
|
+
static void backtracie_frame_wrapper_free(void *ptr) {
|
891
|
+
frame_wrapper_t *frame_data = (frame_wrapper_t *)ptr;
|
892
|
+
xfree(frame_data->frames);
|
893
|
+
}
|
894
|
+
static size_t backtracie_frame_wrapper_memsize(const void *ptr) {
|
895
|
+
const frame_wrapper_t *frame_data = (const frame_wrapper_t *)ptr;
|
896
|
+
return sizeof(frame_wrapper_t) + sizeof(raw_location) * frame_data->capa;
|
897
|
+
}
|
898
|
+
|
899
|
+
bool backtracie_capture_minimal_frame_for_thread(VALUE thread, int frame_index,
|
900
|
+
minimal_location_t *loc) {
|
901
|
+
raw_location raw_loc;
|
902
|
+
bool ret = backtracie_capture_frame_for_thread(thread, frame_index, &raw_loc);
|
903
|
+
if (!ret) {
|
904
|
+
return ret;
|
905
|
+
}
|
906
|
+
raw_location_to_minimal_location(&raw_loc, loc);
|
907
|
+
|
908
|
+
return ret;
|
909
|
+
}
|
910
|
+
|
911
|
+
size_t backtracie_minimal_frame_name_cstr(const minimal_location_t *loc,
|
912
|
+
char *buf, size_t buflen) {
|
913
|
+
|
914
|
+
strbuilder_t builder;
|
915
|
+
strbuilder_init(&builder, buf, buflen);
|
916
|
+
minimal_location_method_qualifier(loc, &builder);
|
917
|
+
minimal_location_method_name(loc, &builder);
|
918
|
+
return builder.attempted_size;
|
919
|
+
}
|
920
|
+
|
921
|
+
size_t backtracie_minimal_frame_filename_cstr(const minimal_location_t *loc,
|
922
|
+
char *buf, size_t buflen) {
|
923
|
+
|
924
|
+
strbuilder_t builder;
|
925
|
+
strbuilder_init(&builder, buf, buflen);
|
926
|
+
if (RTEST(loc->filename)) {
|
927
|
+
strbuilder_append_value(&builder, loc->filename);
|
928
|
+
}
|
929
|
+
return builder.attempted_size;
|
930
|
+
}
|