backtracie 0.2.2 → 0.3.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 +4 -3
- data/backtracie.gemspec +3 -0
- data/ext/backtracie_native_extension/backtracie.c +14 -42
- data/ext/backtracie_native_extension/extconf.rb +47 -16
- data/ext/backtracie_native_extension/ruby_shards.c +76 -136
- data/ext/backtracie_native_extension/ruby_shards.h +12 -18
- data/lib/backtracie/version.rb +1 -1
- data/lib/backtracie.rb +11 -2
- metadata +24 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b8ef71cf1cfd9ca005e0ecb0fe7434a9c8121bc46fc70c34209c74ecad1c705c
|
4
|
+
data.tar.gz: 723d3c66fb919432481428609a8316243e160805ff4142757f9ec65cf254f2d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ceffbd048f44b6563298f301bde8dd498c1ea92be244e90a42390f4d387d58ef3837d936afbd4c63376ebfa2a20984989a2f8e1426ab9cccc30fc8c844c78a68
|
7
|
+
data.tar.gz: cc6e34b726934f01ae92bf089954bace7063619ce8e3d71b1d1eea610d153f10641257399388ef70bcd7730b1aea89039d4f7e6e2b960f2b09e937fd4d802969
|
data/README.adoc
CHANGED
@@ -8,9 +8,10 @@ A Ruby gem for beautiful backtraces.
|
|
8
8
|
|
9
9
|
Aims to offer alternatives to `Thread#backtrace`, `Thread#backtrace_locations`, `Kernel#caller` and `Kernel#caller_locations` with similar (or better!?) performance, but with better metadata (such as class names).
|
10
10
|
|
11
|
-
WARNING:
|
12
|
-
|
13
|
-
|
11
|
+
WARNING: **⚠️ This gem is currently an exploration ground for the use of low-level techniques to get at internal Ruby VM structures.
|
12
|
+
Due to the way it works, bugs can easily cause your whole Ruby VM to crash;
|
13
|
+
Please take this into account if you plan on using this gem.
|
14
|
+
Still, contributions and bug reports are mega welcome, and I hope to one day be able to remove this warning ⚠️.**
|
14
15
|
|
15
16
|
image:https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg["Contributor Covenant", link="CODE_OF_CONDUCT.adoc"]
|
16
17
|
image:https://badge.fury.io/rb/backtracie.svg["Gem Version", link="https://badge.fury.io/rb/backtracie"]
|
data/backtracie.gemspec
CHANGED
@@ -46,4 +46,7 @@ Gem::Specification.new do |spec|
|
|
46
46
|
end
|
47
47
|
spec.require_paths = ["lib", "ext"]
|
48
48
|
spec.extensions = ["ext/backtracie_native_extension/extconf.rb"]
|
49
|
+
|
50
|
+
# Enables support for Ruby 2.5 and below. Not used at all in the others.
|
51
|
+
spec.add_dependency "debase-ruby_core_source", "~> 0.10", ">= 0.10.12"
|
49
52
|
end
|
@@ -33,8 +33,6 @@
|
|
33
33
|
static VALUE main_object_instance = Qnil;
|
34
34
|
static ID ensure_object_is_thread_id;
|
35
35
|
static ID to_s_id;
|
36
|
-
static ID current_id;
|
37
|
-
static ID backtrace_locations_id;
|
38
36
|
static VALUE backtracie_module = Qnil;
|
39
37
|
static VALUE backtracie_location_class = Qnil;
|
40
38
|
|
@@ -52,14 +50,12 @@ static bool is_self_class_singleton(raw_location *the_location);
|
|
52
50
|
static bool is_defined_class_a_refinement(raw_location *the_location);
|
53
51
|
static VALUE debug_raw_location(raw_location *the_location);
|
54
52
|
static VALUE debug_frame(VALUE frame);
|
55
|
-
static inline VALUE to_boolean(bool value)
|
53
|
+
static inline VALUE to_boolean(bool value);
|
56
54
|
|
57
55
|
void Init_backtracie_native_extension(void) {
|
58
|
-
main_object_instance =
|
56
|
+
main_object_instance = rb_funcall(rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")), rb_intern("eval"), 1, rb_str_new2("self"));
|
59
57
|
ensure_object_is_thread_id = rb_intern("ensure_object_is_thread");
|
60
58
|
to_s_id = rb_intern("to_s");
|
61
|
-
current_id = rb_intern("current");
|
62
|
-
backtrace_locations_id = rb_intern("backtrace_locations");
|
63
59
|
|
64
60
|
backtracie_module = rb_const_get(rb_cObject, rb_intern("Backtracie"));
|
65
61
|
rb_global_variable(&backtracie_module);
|
@@ -79,28 +75,13 @@ static VALUE collect_backtrace_locations(VALUE self, VALUE thread, int ignored_s
|
|
79
75
|
int stack_depth = 0;
|
80
76
|
raw_location raw_locations[MAX_STACK_DEPTH];
|
81
77
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
}
|
90
|
-
#else
|
91
|
-
VALUE current_thread = rb_funcall(rb_cThread, current_id, 0);
|
92
|
-
|
93
|
-
if (thread == Qnil || thread == current_thread) {
|
94
|
-
thread = current_thread;
|
95
|
-
// Since we're going to sample the current thread, we don't want Thread#backtrace_locations to show up
|
96
|
-
// on the stack (as we want to match the exact depth we get from MRI), so we ignore one extra frame
|
97
|
-
ignored_stack_top_frames++;
|
98
|
-
}
|
99
|
-
|
100
|
-
VALUE locations_array = rb_funcall(thread, backtrace_locations_id, 0);
|
101
|
-
if (locations_array == Qnil) return Qnil;
|
102
|
-
stack_depth = backtracie_profile_frames_from_ruby_locations(locations_array, raw_locations);
|
103
|
-
#endif
|
78
|
+
if (thread == Qnil) {
|
79
|
+
// Get for own thread
|
80
|
+
stack_depth = backtracie_rb_profile_frames(MAX_STACK_DEPTH, raw_locations);
|
81
|
+
} else {
|
82
|
+
stack_depth = backtracie_rb_profile_frames_for_thread(thread, MAX_STACK_DEPTH, raw_locations);
|
83
|
+
if (stack_depth == 0 && !backtracie_is_thread_alive(thread)) return Qnil;
|
84
|
+
}
|
104
85
|
|
105
86
|
VALUE locations = rb_ary_new_capa(stack_depth - ignored_stack_top_frames);
|
106
87
|
|
@@ -217,9 +198,7 @@ static VALUE cfunc_frame_to_location(raw_location *the_location, raw_location *l
|
|
217
198
|
last_ruby_location != 0 ? frame_from_location(last_ruby_location) : Qnil;
|
218
199
|
|
219
200
|
// Replaces label and base_label in cfuncs
|
220
|
-
VALUE method_name =
|
221
|
-
the_location->should_use_cfunc_name ?
|
222
|
-
the_location->cfunc_name : backtracie_rb_profile_frame_method_name(the_location->callable_method_entry);
|
201
|
+
VALUE method_name = backtracie_rb_profile_frame_method_name(the_location->callable_method_entry);
|
223
202
|
|
224
203
|
return new_location(
|
225
204
|
SAFE_NAVIGATION(rb_profile_frame_absolute_path, last_ruby_frame),
|
@@ -317,7 +296,6 @@ static VALUE debug_raw_location(raw_location *the_location) {
|
|
317
296
|
VALUE arguments[] = {
|
318
297
|
ID2SYM(rb_intern("ruby_frame?")) , /* => */ to_boolean(the_location->is_ruby_frame),
|
319
298
|
ID2SYM(rb_intern("should_use_iseq")), /* => */ to_boolean(the_location->should_use_iseq),
|
320
|
-
ID2SYM(rb_intern("should_use_cfunc_name")), /* => */ to_boolean(the_location->should_use_cfunc_name),
|
321
299
|
ID2SYM(rb_intern("vm_method_type")), /* => */ INT2FIX(the_location->vm_method_type),
|
322
300
|
ID2SYM(rb_intern("line_number")), /* => */ INT2FIX(the_location->line_number),
|
323
301
|
ID2SYM(rb_intern("called_id")), /* => */ backtracie_called_id(the_location),
|
@@ -331,16 +309,13 @@ static VALUE debug_raw_location(raw_location *the_location) {
|
|
331
309
|
ID2SYM(rb_intern("self_class_singleton?")), /* => */ to_boolean(is_self_class_singleton(the_location)),
|
332
310
|
ID2SYM(rb_intern("iseq_is_block?")), /* => */ to_boolean(backtracie_iseq_is_block(the_location)),
|
333
311
|
ID2SYM(rb_intern("iseq_is_eval?")), /* => */ to_boolean(backtracie_iseq_is_eval(the_location)),
|
334
|
-
ID2SYM(rb_intern("cfunc_name")), /* => */ the_location->cfunc_name,
|
335
312
|
ID2SYM(rb_intern("iseq")), /* => */ debug_frame(the_location->iseq),
|
336
|
-
ID2SYM(rb_intern("callable_method_entry")), /* => */ debug_frame(the_location->callable_method_entry)
|
313
|
+
ID2SYM(rb_intern("callable_method_entry")), /* => */ debug_frame(the_location->callable_method_entry),
|
314
|
+
ID2SYM(rb_intern("original_id")), /* => */ the_location->original_id
|
337
315
|
};
|
338
316
|
|
339
317
|
VALUE debug_hash = rb_hash_new();
|
340
|
-
|
341
|
-
// FIXME: Hack, need to actually fix this
|
342
|
-
rb_hash_bulk_insert(VALUE_COUNT(arguments), arguments, debug_hash);
|
343
|
-
#endif
|
318
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(debug_hash, arguments[i], arguments[i+1]);
|
344
319
|
return debug_hash;
|
345
320
|
}
|
346
321
|
|
@@ -361,10 +336,7 @@ static VALUE debug_frame(VALUE frame) {
|
|
361
336
|
};
|
362
337
|
|
363
338
|
VALUE debug_hash = rb_hash_new();
|
364
|
-
|
365
|
-
// FIXME: Hack, need to actually fix this
|
366
|
-
rb_hash_bulk_insert(VALUE_COUNT(arguments), arguments, debug_hash);
|
367
|
-
#endif
|
339
|
+
for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(debug_hash, arguments[i], arguments[i+1]);
|
368
340
|
return debug_hash;
|
369
341
|
}
|
370
342
|
|
@@ -34,6 +34,10 @@ $CFLAGS << " " << "-Wno-unused-function"
|
|
34
34
|
# Really dislike the "define everything at the beginning of the function" thing, sorry!
|
35
35
|
$CFLAGS << " " << "-Wno-declaration-after-statement"
|
36
36
|
|
37
|
+
# If we forget to include a Ruby header, the function call may still appear to work, but then
|
38
|
+
# cause a segfault later. Let's ensure that never happens.
|
39
|
+
$CFLAGS << " " << "-Werror-implicit-function-declaration"
|
40
|
+
|
37
41
|
# Enable us to use """modern""" C
|
38
42
|
if RUBY_VERSION < "2.4"
|
39
43
|
$CFLAGS << " " << "-std=c99"
|
@@ -44,29 +48,56 @@ if RUBY_VERSION < "3"
|
|
44
48
|
$defs << "-DCFUNC_FRAMES_BACKPORT_NEEDED"
|
45
49
|
end
|
46
50
|
|
47
|
-
# Backport https://github.com/ruby/ruby/pull/3084 (present in 2.7 and 3.0) to Ruby 2.6
|
51
|
+
# Backport https://github.com/ruby/ruby/pull/3084 (present in 2.7 and 3.0) to Ruby <= 2.6
|
48
52
|
if RUBY_VERSION.start_with?("2.6")
|
49
53
|
$defs << "-DCLASSPATH_BACKPORT_NEEDED"
|
50
54
|
end
|
51
55
|
|
56
|
+
# Older Rubies don't have the MJIT header, see below for details
|
52
57
|
if RUBY_VERSION < "2.6"
|
53
58
|
$defs << "-DPRE_MJIT_RUBY"
|
54
59
|
end
|
55
60
|
|
61
|
+
if RUBY_VERSION < "2.5"
|
62
|
+
$CFLAGS << " " << "-DPRE_EXECUTION_CONTEXT" # Flag that there's no execution context, we need to use threads instead
|
63
|
+
$CFLAGS << " " << "-Wno-attributes" # Silence a few warnings that we can't do anything about
|
64
|
+
end
|
65
|
+
|
66
|
+
if RUBY_VERSION < "2.4"
|
67
|
+
$CFLAGS << " " << "-DPRE_VM_ENV_RENAMES" # Flag that it's a really old Ruby, and a few constants were since renamed
|
68
|
+
end
|
69
|
+
|
56
70
|
create_header
|
57
71
|
|
58
|
-
|
59
|
-
#
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
72
|
+
if RUBY_VERSION < "2.6"
|
73
|
+
# Use the debase-ruby_core_source gem to get access to Ruby internal structures (no MJIT header -- the preferred
|
74
|
+
# option -- is available for these older Rubies)
|
75
|
+
|
76
|
+
require "debase/ruby_core_source"
|
77
|
+
dir_config("ruby") # allow user to pass in non-standard core include directory
|
78
|
+
if !Debase::RubyCoreSource.create_makefile_with_core(
|
79
|
+
proc { ["vm_core.h", "method.h", "iseq.h", "regenc.h"].map { |it| have_header(it) }.uniq == [true] },
|
80
|
+
"backtracie_native_extension"
|
81
|
+
)
|
82
|
+
raise "Error during native gem setup -- `Debase::RubyCoreSource.create_makefile_with_core` failed"
|
83
|
+
end
|
84
|
+
|
85
|
+
else
|
86
|
+
# Use MJIT header to get access to Ruby internal structures.
|
87
|
+
|
88
|
+
# The Ruby MJIT header is always (afaik?) suffixed with the exact RUBY version,
|
89
|
+
# including patch (e.g. 2.7.2). Thus, we add to the header file a definition
|
90
|
+
# containing the exact file, so that it can be used in a #include in the C code.
|
91
|
+
header_contents =
|
92
|
+
File.read($extconf_h)
|
93
|
+
.sub("#endif",
|
94
|
+
<<~EXTCONF_H.strip
|
95
|
+
#define RUBY_MJIT_HEADER "rb_mjit_min_header-#{RUBY_VERSION}.h"
|
96
|
+
|
97
|
+
#endif
|
98
|
+
EXTCONF_H
|
99
|
+
)
|
100
|
+
File.open($extconf_h, "w") { |file| file.puts(header_contents) }
|
101
|
+
|
102
|
+
create_makefile "backtracie_native_extension"
|
103
|
+
end
|
@@ -105,14 +105,31 @@
|
|
105
105
|
#include "extconf.h"
|
106
106
|
|
107
107
|
#ifndef PRE_MJIT_RUBY
|
108
|
-
|
109
108
|
#ifndef RUBY_MJIT_HEADER_INCLUDED
|
110
109
|
#define RUBY_MJIT_HEADER_INCLUDED
|
111
110
|
#include RUBY_MJIT_HEADER
|
112
111
|
#endif
|
112
|
+
#endif
|
113
113
|
|
114
114
|
#include "ruby_shards.h"
|
115
115
|
|
116
|
+
#ifdef PRE_MJIT_RUBY
|
117
|
+
#include <iseq.h>
|
118
|
+
#include <regenc.h>
|
119
|
+
#endif
|
120
|
+
|
121
|
+
#ifdef PRE_EXECUTION_CONTEXT
|
122
|
+
// The thread and its execution context were separated on Ruby 2.5; prior to that, everything was part of the thread
|
123
|
+
#define rb_execution_context_t rb_thread_t
|
124
|
+
#endif
|
125
|
+
|
126
|
+
#ifdef PRE_VM_ENV_RENAMES
|
127
|
+
#define VM_ENV_LOCAL_P VM_EP_LEP_P
|
128
|
+
#define VM_ENV_PREV_EP VM_EP_PREV_EP
|
129
|
+
#define VM_ENV_DATA_INDEX_ME_CREF -1
|
130
|
+
#define VM_FRAME_RUBYFRAME_P(cfp) RUBY_VM_NORMAL_ISEQ_P(cfp->iseq)
|
131
|
+
#endif
|
132
|
+
|
116
133
|
/**********************************************************************
|
117
134
|
vm_backtrace.c -
|
118
135
|
$Author: ko1 $
|
@@ -187,19 +204,24 @@ static int backtracie_rb_profile_frames_for_execution_context(
|
|
187
204
|
// Initialize the raw_location, to avoid issues
|
188
205
|
raw_locations[i].is_ruby_frame = false;
|
189
206
|
raw_locations[i].should_use_iseq = false;
|
190
|
-
raw_locations[i].should_use_cfunc_name = false;
|
191
207
|
raw_locations[i].vm_method_type = 0;
|
192
208
|
raw_locations[i].line_number = 0;
|
193
209
|
raw_locations[i].iseq = Qnil;
|
194
210
|
raw_locations[i].callable_method_entry = Qnil;
|
195
|
-
raw_locations[i].
|
211
|
+
raw_locations[i].original_id = Qnil;
|
196
212
|
|
197
213
|
// The current object this is getting called on!
|
198
214
|
raw_locations[i].self = cfp->self;
|
199
215
|
|
200
216
|
cme = rb_vm_frame_method_entry(cfp);
|
201
217
|
|
202
|
-
if (
|
218
|
+
if (cfp->iseq && !cfp->pc) {
|
219
|
+
// Do nothing -- this frame should not be used
|
220
|
+
// Bugfix: rb_profile_frames did not do this (skip a frame when there's no pc), but backtrace_each did, and that
|
221
|
+
// caused the "Backtracie.backtrace_locations when sampling a map from an enumerable returns the same number of items as the Ruby API"
|
222
|
+
// test to fail -- Backtracie returned one more frame than Ruby. I suspect that, as usual, this is yet another case where
|
223
|
+
// rb_profile_frames fails us.
|
224
|
+
} else if (VM_FRAME_RUBYFRAME_P(cfp)) {
|
203
225
|
raw_locations[i].is_ruby_frame = true;
|
204
226
|
raw_locations[i].iseq = (VALUE) cfp->iseq;
|
205
227
|
|
@@ -223,6 +245,7 @@ static int backtracie_rb_profile_frames_for_execution_context(
|
|
223
245
|
raw_locations[i].callable_method_entry = (VALUE) cme;
|
224
246
|
raw_locations[i].vm_method_type = cme->def->type;
|
225
247
|
raw_locations[i].line_number = 0;
|
248
|
+
raw_locations[i].original_id = ID2SYM(cme->def->original_id);
|
226
249
|
|
227
250
|
i++;
|
228
251
|
}
|
@@ -235,7 +258,12 @@ static int backtracie_rb_profile_frames_for_execution_context(
|
|
235
258
|
}
|
236
259
|
|
237
260
|
int backtracie_rb_profile_frames(int limit, raw_location *raw_locations) {
|
238
|
-
|
261
|
+
#ifndef PRE_EXECUTION_CONTEXT
|
262
|
+
return backtracie_rb_profile_frames_for_execution_context(GET_EC(), limit, raw_locations);
|
263
|
+
#else
|
264
|
+
// FIXME: Figure out how to make GET_EC (GET_THREAD) work for Ruby <= 2.4
|
265
|
+
return 0;
|
266
|
+
#endif
|
239
267
|
}
|
240
268
|
|
241
269
|
bool backtracie_is_thread_alive(VALUE thread) {
|
@@ -253,7 +281,11 @@ int backtracie_rb_profile_frames_for_thread(VALUE thread, int limit, raw_locatio
|
|
253
281
|
// the caller, otherwise I see a segfault in your future.
|
254
282
|
rb_thread_t *thread_pointer = (rb_thread_t*) DATA_PTR(thread);
|
255
283
|
|
256
|
-
|
284
|
+
#ifndef PRE_EXECUTION_CONTEXT
|
285
|
+
return backtracie_rb_profile_frames_for_execution_context(thread_pointer->ec, limit, raw_locations);
|
286
|
+
#else
|
287
|
+
return backtracie_rb_profile_frames_for_execution_context(thread_pointer, limit, raw_locations);
|
288
|
+
#endif
|
257
289
|
}
|
258
290
|
|
259
291
|
VALUE backtracie_called_id(raw_location *the_location) {
|
@@ -273,10 +305,6 @@ VALUE backtracie_defined_class(raw_location *the_location) {
|
|
273
305
|
->defined_class;
|
274
306
|
}
|
275
307
|
|
276
|
-
VALUE backtracie_rb_vm_top_self() {
|
277
|
-
return GET_VM()->top_self;
|
278
|
-
}
|
279
|
-
|
280
308
|
bool backtracie_iseq_is_block(raw_location *the_location) {
|
281
309
|
if (the_location->iseq == Qnil) return false;
|
282
310
|
|
@@ -452,142 +480,54 @@ backported_rb_profile_frame_qualified_method_name(VALUE frame)
|
|
452
480
|
}
|
453
481
|
#endif // CLASSPATH_BACKPORT_NEEDED
|
454
482
|
|
455
|
-
#endif // ifndef PRE_MJIT_RUBY
|
456
|
-
|
457
|
-
// -----------------------------------------------------------------------------
|
458
|
-
|
459
483
|
#ifdef PRE_MJIT_RUBY
|
460
484
|
|
461
|
-
|
462
|
-
|
463
|
-
#include "ruby.h"
|
464
|
-
|
465
|
-
#include "ruby_shards.h"
|
485
|
+
// A few extra functions copied from the Ruby sources, needed to support Ruby < 2.6
|
466
486
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
} type;
|
473
|
-
|
474
|
-
union {
|
475
|
-
struct {
|
476
|
-
const VALUE iseq; // Originally const rb_iseq_t *
|
477
|
-
union {
|
478
|
-
const VALUE *pc;
|
479
|
-
int lineno;
|
480
|
-
} lineno;
|
481
|
-
} iseq;
|
482
|
-
struct {
|
483
|
-
ID mid;
|
484
|
-
struct rb_backtrace_location_struct *prev_loc;
|
485
|
-
} cfunc;
|
486
|
-
} body;
|
487
|
-
} rb_backtrace_location_t;
|
488
|
-
|
489
|
-
struct valued_frame_info {
|
490
|
-
rb_backtrace_location_t *loc;
|
491
|
-
VALUE btobj;
|
492
|
-
};
|
493
|
-
|
494
|
-
// For Ruby < 2.6, we can't rely on the MJIT header. So we need to get... crafty. This alternative to
|
495
|
-
// backtracie_rb_profile_frames_for_execution_context(...) piggybacks on the actual
|
496
|
-
// Thread::Backtrace::Locations instances returned by Ruby's Thread#backtrace_locations, and then uses
|
497
|
-
// knowledge of the VM internal layouts to get the data we need out of them.
|
498
|
-
//
|
499
|
-
// Currently, this approach gets us a lot less information than the Ruby >= 2.6 approach (e.g. currently we
|
500
|
-
// get exactly the same as Thread::Backtrace::Locations offers), so it's a crappier replacement, but it does
|
501
|
-
// get us support for older Rubies so it's better than nothing (to check the differences in behavior, check which
|
502
|
-
// tests are disabled on < 2.6).
|
503
|
-
int backtracie_profile_frames_from_ruby_locations(
|
504
|
-
VALUE ruby_locations_array,
|
505
|
-
raw_location *raw_locations
|
506
|
-
) {
|
507
|
-
Check_Type(ruby_locations_array, T_ARRAY);
|
508
|
-
int ruby_locations_array_size = RARRAY_LEN(ruby_locations_array);
|
509
|
-
|
510
|
-
for (int i = 0; i < ruby_locations_array_size; i++) {
|
511
|
-
// FIXME: We should validate that what we get out of the array is a Thread::Backtrace::Location instance
|
512
|
-
VALUE location = rb_ary_entry(ruby_locations_array, i);
|
513
|
-
|
514
|
-
// The leap of faith -- let's get into the data from the Location instance
|
515
|
-
struct valued_frame_info *location_payload = DATA_PTR(location);
|
487
|
+
/**********************************************************************
|
488
|
+
vm_insnhelper.c - instruction helper functions.
|
489
|
+
$Author$
|
490
|
+
Copyright (C) 2007 Koichi Sasada
|
491
|
+
**********************************************************************/
|
516
492
|
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
493
|
+
static rb_callable_method_entry_t *
|
494
|
+
check_method_entry(VALUE obj, int can_be_svar)
|
495
|
+
{
|
496
|
+
if (obj == Qfalse) return NULL;
|
521
497
|
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
}
|
498
|
+
#if VM_CHECK_MODE > 0
|
499
|
+
if (!RB_TYPE_P(obj, T_IMEMO)) rb_bug("check_method_entry: unknown type: %s", rb_obj_info(obj));
|
500
|
+
#endif
|
526
501
|
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
location_payload->loc->type == LOCATION_TYPE_ISEQ_CALCED ?
|
536
|
-
location_payload->loc->body.iseq.lineno.lineno : 0;
|
537
|
-
raw_locations[i].iseq = (VALUE) location_payload->loc->body.iseq.iseq;
|
538
|
-
raw_locations[i].callable_method_entry = Qnil;
|
539
|
-
raw_locations[i].self = Qnil;
|
540
|
-
raw_locations[i].cfunc_name = Qnil;
|
541
|
-
} else {
|
542
|
-
// cfunc frame
|
543
|
-
raw_locations[i].is_ruby_frame = false;
|
544
|
-
raw_locations[i].should_use_iseq = false;
|
545
|
-
raw_locations[i].should_use_cfunc_name = true;
|
546
|
-
raw_locations[i].vm_method_type = -1;
|
547
|
-
raw_locations[i].line_number = 0;
|
548
|
-
raw_locations[i].iseq = Qnil;
|
549
|
-
raw_locations[i].callable_method_entry = Qnil;
|
550
|
-
raw_locations[i].self = Qnil;
|
551
|
-
raw_locations[i].cfunc_name = rb_id2str(location_payload->loc->body.cfunc.mid);
|
552
|
-
}
|
502
|
+
switch (imemo_type(obj)) {
|
503
|
+
case imemo_ment:
|
504
|
+
return (rb_callable_method_entry_t *)obj;
|
505
|
+
case imemo_cref:
|
506
|
+
return NULL;
|
507
|
+
case imemo_svar:
|
508
|
+
if (can_be_svar) {
|
509
|
+
return check_method_entry(((struct vm_svar *)obj)->cref_or_me, FALSE);
|
553
510
|
}
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
return Qnil;
|
561
|
-
}
|
562
|
-
|
563
|
-
VALUE backtracie_defined_class(raw_location *the_location) {
|
564
|
-
// Not available on PRE_MJIT_RUBY
|
565
|
-
return Qnil;
|
566
|
-
}
|
567
|
-
|
568
|
-
VALUE backtracie_rb_vm_top_self() {
|
569
|
-
// This is only used on gem initialization so... Let's be a bit lazy :)
|
570
|
-
return rb_eval_string("TOPLEVEL_BINDING.eval('self')");
|
571
|
-
}
|
572
|
-
|
573
|
-
bool backtracie_iseq_is_block(raw_location *the_location) {
|
574
|
-
// Not available on PRE_MJIT_RUBY
|
575
|
-
return false;
|
511
|
+
default:
|
512
|
+
#if VM_CHECK_MODE > 0
|
513
|
+
rb_bug("check_method_entry: svar should not be there:");
|
514
|
+
#endif
|
515
|
+
return NULL;
|
516
|
+
}
|
576
517
|
}
|
577
518
|
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
519
|
+
const rb_callable_method_entry_t *
|
520
|
+
rb_vm_frame_method_entry(const rb_control_frame_t *cfp)
|
521
|
+
{
|
522
|
+
const VALUE *ep = cfp->ep;
|
523
|
+
rb_callable_method_entry_t *me;
|
582
524
|
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
}
|
525
|
+
while (!VM_ENV_LOCAL_P(ep)) {
|
526
|
+
if ((me = check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], FALSE)) != NULL) return me;
|
527
|
+
ep = VM_ENV_PREV_EP(ep);
|
528
|
+
}
|
587
529
|
|
588
|
-
|
589
|
-
// Not available on PRE_MJIT_RUBY
|
590
|
-
return Qnil;
|
530
|
+
return check_method_entry(ep[VM_ENV_DATA_INDEX_ME_CREF], TRUE);
|
591
531
|
}
|
592
532
|
|
593
|
-
#endif
|
533
|
+
#endif
|
@@ -89,6 +89,11 @@
|
|
89
89
|
Copyright (C) 2009 Koichi Sasada
|
90
90
|
**********************************************************************/
|
91
91
|
|
92
|
+
#ifdef PRE_MJIT_RUBY
|
93
|
+
#include <stdbool.h>
|
94
|
+
#include <vm_core.h>
|
95
|
+
#include <method.h>
|
96
|
+
#else
|
92
97
|
#ifndef RUBY_MJIT_HEADER_INCLUDED
|
93
98
|
typedef enum {
|
94
99
|
VM_METHOD_TYPE_ISEQ, /*!< Ruby method */
|
@@ -104,10 +109,7 @@
|
|
104
109
|
VM_METHOD_TYPE_MISSING, /*!< wrapper for method_missing(id) */
|
105
110
|
VM_METHOD_TYPE_REFINED, /*!< refinement */
|
106
111
|
} rb_method_type_t;
|
107
|
-
|
108
|
-
|
109
|
-
// Needed for Ruby 2.6 as this is not defined on any public header
|
110
|
-
void rb_hash_bulk_insert(long, const VALUE *, VALUE);
|
112
|
+
#endif
|
111
113
|
#endif
|
112
114
|
|
113
115
|
// -----------------------------------------------------------------------------
|
@@ -118,32 +120,24 @@ typedef struct {
|
|
118
120
|
// for ruby frames where the callable_method_entry is not of type VM_METHOD_TYPE_ISEQ, most of the metadata we
|
119
121
|
// want can be found by querying the iseq, and there may not even be an callable_method_entry
|
120
122
|
unsigned int should_use_iseq : 1;
|
121
|
-
unsigned int should_use_cfunc_name : 1;
|
122
123
|
|
123
|
-
rb_method_type_t vm_method_type
|
124
|
+
rb_method_type_t vm_method_type;
|
124
125
|
int line_number;
|
125
126
|
VALUE iseq;
|
126
127
|
VALUE callable_method_entry;
|
127
128
|
VALUE self;
|
128
|
-
VALUE
|
129
|
+
VALUE original_id;
|
129
130
|
} raw_location;
|
130
131
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
bool backtracie_is_thread_alive(VALUE thread);
|
135
|
-
#endif
|
132
|
+
int backtracie_rb_profile_frames(int limit, raw_location *raw_locations);
|
133
|
+
int backtracie_rb_profile_frames_for_thread(VALUE thread, int limit, raw_location *raw_locations);
|
134
|
+
bool backtracie_is_thread_alive(VALUE thread);
|
136
135
|
VALUE backtracie_called_id(raw_location *the_location);
|
137
136
|
VALUE backtracie_defined_class(raw_location *the_location);
|
138
|
-
VALUE backtracie_rb_vm_top_self();
|
139
137
|
bool backtracie_iseq_is_block(raw_location *the_location);
|
140
138
|
bool backtracie_iseq_is_eval(raw_location *the_location);
|
141
139
|
VALUE backtracie_refinement_name(raw_location *the_location);
|
142
140
|
|
143
|
-
#ifdef PRE_MJIT_RUBY
|
144
|
-
int backtracie_profile_frames_from_ruby_locations(VALUE ruby_locations_array, raw_location *raw_locations);
|
145
|
-
#endif
|
146
|
-
|
147
141
|
// -----------------------------------------------------------------------------
|
148
142
|
|
149
143
|
// Ruby 3.0 finally added support for showing "cfunc frames" (frames for methods written in C) in stack traces:
|
@@ -159,7 +153,7 @@ VALUE backtracie_refinement_name(raw_location *the_location);
|
|
159
153
|
#define backtracie_rb_profile_frame_method_name rb_profile_frame_method_name
|
160
154
|
#endif
|
161
155
|
|
162
|
-
// Backport https://github.com/ruby/ruby/pull/3084 (present in 2.7 and 3.0) to Ruby 2.6
|
156
|
+
// Backport https://github.com/ruby/ruby/pull/3084 (present in 2.7 and 3.0) to Ruby <= 2.6
|
163
157
|
// The interesting bit is actually the fix to rb_profile_frame_classpath BUT since rb_profile_frame_qualified_method_name
|
164
158
|
// internally relies on rb_profile_frame_classpath we also need to add a copy of that one as well.
|
165
159
|
#ifdef CLASSPATH_BACKPORT_NEEDED
|
data/lib/backtracie/version.rb
CHANGED
data/lib/backtracie.rb
CHANGED
@@ -28,8 +28,17 @@ require "backtracie_native_extension"
|
|
28
28
|
module Backtracie
|
29
29
|
module_function
|
30
30
|
|
31
|
-
|
32
|
-
|
31
|
+
if RUBY_VERSION < "2.5"
|
32
|
+
def caller_locations
|
33
|
+
# FIXME: We're having some trouble getting the current thread on older Rubies, see the FIXME on
|
34
|
+
# backtracie_rb_profile_frames. A workaround is to just pass in the reference to the current thread explicitly
|
35
|
+
# (and slice off a few frames, since caller_locations is supposed to start from the caller of our caller)
|
36
|
+
backtrace_locations(Thread.current)[3..-1]
|
37
|
+
end
|
38
|
+
else
|
39
|
+
def caller_locations
|
40
|
+
Primitive.caller_locations
|
41
|
+
end
|
33
42
|
end
|
34
43
|
|
35
44
|
# Defined via native code only; not redirecting via Primitive to avoid an extra stack frame on the stack
|
metadata
CHANGED
@@ -1,15 +1,35 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backtracie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivo Anjo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
12
|
-
dependencies:
|
11
|
+
date: 2021-09-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: debase-ruby_core_source
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.10'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.10.12
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.10'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.10.12
|
13
33
|
description: Ruby gem for beautiful backtraces
|
14
34
|
email:
|
15
35
|
- ivo@ivoanjo.me
|
@@ -51,7 +71,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
51
71
|
- !ruby/object:Gem::Version
|
52
72
|
version: '0'
|
53
73
|
requirements: []
|
54
|
-
rubygems_version: 3.
|
74
|
+
rubygems_version: 3.1.4
|
55
75
|
signing_key:
|
56
76
|
specification_version: 4
|
57
77
|
summary: Ruby gem for beautiful backtraces
|