backtracie 0.2.2 → 0.3.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 +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
|