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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54e3870e49e076db276f5db8ce6fdd4d3678a7dee52b5d7c11c047425cea931a
4
- data.tar.gz: da7e2f00f3f569b5e1867c88343b29a68124f3cf005eca650bd32a5d45c3732b
3
+ metadata.gz: e804bb5ef00e6d7a3f9b7a0a57468026f3f58dbc36671e28d49f3a65e0cb6ad5
4
+ data.tar.gz: c1c2851579f8eab2bc9fbbdd8b6efa82636d48e141f8a7ea505b9f8e5329b7ab
5
5
  SHA512:
6
- metadata.gz: f481358789f4a4577a29fb143c5951bc75cf47f69304094ba3b40361a1c1dce0f4736bda189a547222f65c88a5afb0bc13e7cfdc6bd723a5e21ceb327c32e1f0
7
- data.tar.gz: 385384d64fe51ed93d084afa16d10e07565ed7d8f7d2bc25250bfe62c5962f607a061ad385d93203ab9768b878d29bd4ece827ca31d3bd30162ba93c4713d9b2
6
+ metadata.gz: 2bd4613237a00b0c872ba229cb0401af1ea9d9eb9ea559f3ded3a44779c6e5e6c4599a7639abac19c1e3e8069c5dc44de8d157f32dbb3bed160d3d987c8c8458
7
+ data.tar.gz: 3846369e8865a9ec7d8c2589b3d1eb3737c242479ac3df8bb9b781ae64fdb0ed3d10a9da503cae7c5a2568b879251fbe5d74e67d88ca9dbfba98ac56a591a5dd
data/README.adoc CHANGED
@@ -8,6 +8,11 @@ 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: **⚠️ 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 ⚠️.**
15
+
11
16
  image:https://img.shields.io/badge/Contributor%20Covenant-2.0-4baaaa.svg["Contributor Covenant", link="CODE_OF_CONDUCT.adoc"]
12
17
  image:https://badge.fury.io/rb/backtracie.svg["Gem Version", link="https://badge.fury.io/rb/backtracie"]
13
18
  image:https://github.com/ivoanjo/backtracie/actions/workflows/test.yml/badge.svg["CI Status", link="https://github.com/ivoanjo/backtracie/actions/workflows/test.yml"]
@@ -159,6 +164,9 @@ Other interesting links on this matter:
159
164
  * https://ruby-hacking-guide.github.io/: Ruby Hacking Guide
160
165
  ** This is one of the most through and deep guides out there to the MRI internals. Very detailed and in depth, but outdated.
161
166
  * http://patshaughnessy.net/ruby-under-a-microscope: Book with a really good introduction to MRI internals.
167
+ * https://www.youtube.com/watch?v=QDbj4Y0E5xo: Video on the implementation of backtrace APIs in MRI and how partial backtraces were optimized in 3.0/3.1.
168
+ * https://github.com/ruby/ruby/pull/4336 and https://github.com/ruby/ruby/pull/4108: Segfaults due to execution context changes to support Ractors in Ruby 3.0
169
+ * https://kumisystems.dl.sourceforge.net/project/elftoolchain/Documentation/libelf-by-example/20120308/libelf-by-example.pdf: Looking inside elf files 😱
162
170
 
163
171
  My blog posts on better backtraces:
164
172
 
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
@@ -16,120 +16,139 @@
16
16
  // You should have received a copy of the GNU Lesser General Public License
17
17
  // along with backtracie. If not, see <http://www.gnu.org/licenses/>.
18
18
 
19
- #include <stdbool.h>
20
-
21
- #include "ruby/ruby.h"
22
- #include "ruby/debug.h"
23
-
24
19
  #include "extconf.h"
25
20
 
26
- #include "ruby_shards.h"
21
+ #if 0
22
+ // Disabled until this is fixed to not break Windows
23
+ #include <dlfcn.h>
24
+ #endif
27
25
 
28
- #define MAX_STACK_DEPTH 2000 // FIXME: Need to handle when this is not enough
26
+ #include <ruby.h>
27
+ #include <ruby/debug.h>
28
+ #include <ruby/intern.h>
29
+ #include <stdbool.h>
30
+ #include <stdio.h>
31
+
32
+ #include "backtracie_private.h"
33
+ #include "public/backtracie.h"
29
34
 
30
35
  #define VALUE_COUNT(array) (sizeof(array) / sizeof(VALUE))
31
- #define SAFE_NAVIGATION(function, maybe_nil) ((maybe_nil) != Qnil ? function(maybe_nil) : Qnil)
36
+ #define SAFE_NAVIGATION(function, maybe_nil) \
37
+ ((maybe_nil) != Qnil ? function(maybe_nil) : Qnil)
38
+
39
+ // non-static, used in backtracie_frames.c
40
+ VALUE backtracie_main_object_instance = Qnil;
41
+ VALUE backtracie_frame_wrapper_class = Qnil;
32
42
 
33
- static VALUE main_object_instance = Qnil;
34
43
  static ID ensure_object_is_thread_id;
35
44
  static ID to_s_id;
36
- static ID current_id;
37
- static ID backtrace_locations_id;
38
45
  static VALUE backtracie_module = Qnil;
39
46
  static VALUE backtracie_location_class = Qnil;
40
47
 
41
48
  static VALUE primitive_caller_locations(VALUE self);
42
49
  static VALUE primitive_backtrace_locations(VALUE self, VALUE thread);
43
- static VALUE collect_backtrace_locations(VALUE self, VALUE thread, int ignored_stack_top_frames);
44
- inline static VALUE new_location(VALUE absolute_path, VALUE base_label, VALUE label, VALUE lineno, VALUE path, VALUE qualified_method_name, VALUE debug);
45
- static VALUE ruby_frame_to_location(raw_location *the_location);
46
- static VALUE qualified_method_name_for_location(raw_location *the_location);
47
- static VALUE cfunc_frame_to_location(raw_location *the_location, raw_location *last_ruby_location);
48
- static VALUE frame_from_location(raw_location *the_location);
49
- static VALUE qualified_method_name_for_block(raw_location *the_location);
50
- static VALUE qualified_method_name_from_self(raw_location *the_location);
51
- static bool is_self_class_singleton(raw_location *the_location);
52
- static bool is_defined_class_a_refinement(raw_location *the_location);
53
- static VALUE debug_raw_location(raw_location *the_location);
50
+ static VALUE collect_backtrace_locations(VALUE self, VALUE thread,
51
+ int ignored_stack_top_frames);
52
+ inline static VALUE new_location(VALUE absolute_path, VALUE base_label,
53
+ VALUE label, VALUE lineno, VALUE path,
54
+ VALUE qualified_method_name,
55
+ VALUE path_is_synthetic, VALUE debug);
56
+ static VALUE frame_to_location(const raw_location *raw_loc,
57
+ const raw_location *prev_ruby_loc);
58
+ static VALUE debug_raw_location(const raw_location *the_location);
54
59
  static VALUE debug_frame(VALUE frame);
55
- static inline VALUE to_boolean(bool value) ;
60
+ static VALUE cfunc_function_info(const raw_location *the_location);
61
+ static inline VALUE to_boolean(bool value);
56
62
 
63
+ BACKTRACIE_API
57
64
  void Init_backtracie_native_extension(void) {
58
- main_object_instance = backtracie_rb_vm_top_self();
65
+ backtracie_main_object_instance =
66
+ rb_funcall(rb_const_get(rb_cObject, rb_intern("TOPLEVEL_BINDING")),
67
+ rb_intern("eval"), 1, rb_str_new2("self"));
59
68
  ensure_object_is_thread_id = rb_intern("ensure_object_is_thread");
60
69
  to_s_id = rb_intern("to_s");
61
- current_id = rb_intern("current");
62
- backtrace_locations_id = rb_intern("backtrace_locations");
63
70
 
64
71
  backtracie_module = rb_const_get(rb_cObject, rb_intern("Backtracie"));
65
72
  rb_global_variable(&backtracie_module);
66
73
 
67
- rb_define_module_function(backtracie_module, "backtrace_locations", primitive_backtrace_locations, 1);
74
+ rb_define_module_function(backtracie_module, "backtrace_locations",
75
+ primitive_backtrace_locations, 1);
68
76
 
69
- backtracie_location_class = rb_const_get(backtracie_module, rb_intern("Location"));
77
+ backtracie_location_class =
78
+ rb_const_get(backtracie_module, rb_intern("Location"));
70
79
  rb_global_variable(&backtracie_location_class);
71
80
 
72
- VALUE backtracie_primitive_module = rb_define_module_under(backtracie_module, "Primitive");
81
+ VALUE backtracie_primitive_module =
82
+ rb_define_module_under(backtracie_module, "Primitive");
73
83
 
74
- rb_define_module_function(backtracie_primitive_module, "caller_locations", primitive_caller_locations, 0);
75
- }
84
+ rb_define_module_function(backtracie_primitive_module, "caller_locations",
85
+ primitive_caller_locations, 0);
76
86
 
77
- // Get array of Backtracie::Locations for a given thread; if thread is nil, returns for the current thread
78
- static VALUE collect_backtrace_locations(VALUE self, VALUE thread, int ignored_stack_top_frames) {
79
- int stack_depth = 0;
80
- raw_location raw_locations[MAX_STACK_DEPTH];
81
-
82
- #ifndef PRE_MJIT_RUBY
83
- if (thread == Qnil) {
84
- // Get for own thread
85
- stack_depth = backtracie_rb_profile_frames(MAX_STACK_DEPTH, raw_locations);
86
- } else {
87
- stack_depth = backtracie_rb_profile_frames_for_thread(thread, MAX_STACK_DEPTH, raw_locations);
88
- if (stack_depth == 0 && !backtracie_is_thread_alive(thread)) return Qnil;
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
- }
87
+ backtracie_frame_wrapper_class =
88
+ rb_define_class_under(backtracie_module, "FrameWrapper", rb_cObject);
89
+ // this class should only be instantiated via backtracie_frame_wrapper_new
90
+ rb_undef_alloc_func(backtracie_frame_wrapper_class);
91
+
92
+ // Create some classes which are used to simulate interesting scenarios in
93
+ // tests
94
+ backtracie_init_c_test_helpers(backtracie_module);
95
+ }
99
96
 
100
- VALUE locations_array = rb_funcall(thread, backtrace_locations_id, 0);
101
- stack_depth = backtracie_profile_frames_from_ruby_locations(locations_array, raw_locations);
102
- #endif
97
+ // Get array of Backtracie::Locations for a given thread; if thread is nil,
98
+ // returns for the current thread
99
+ static VALUE collect_backtrace_locations(VALUE self, VALUE thread,
100
+ int ignored_stack_top_frames) {
101
+ if (!RTEST(thread)) {
102
+ thread = rb_thread_current();
103
+ }
103
104
 
104
- VALUE locations = rb_ary_new_capa(stack_depth - ignored_stack_top_frames);
105
+ // To maintain compatability with the Ruby thread backtrace behavior, if a
106
+ // thread is dead, then return nil.
107
+ if (!backtracie_is_thread_alive(thread)) {
108
+ return Qnil;
109
+ }
105
110
 
106
- // MRI does not give us the path or line number for frames implemented using C code. The convention in
107
- // Kernel#caller_locations is to instead use the path and line number of the last Ruby frame seen.
108
- // Thus, we keep that frame here to able to replicate that behavior.
109
- // (This is why we also iterate the frames array backwards below -- so that it's easier to keep the last_ruby_frame)
110
- raw_location *last_ruby_location = 0;
111
+ int raw_frame_count = backtracie_frame_count_for_thread(thread);
111
112
 
112
- for (int i = stack_depth - 1; i >= ignored_stack_top_frames; i--) {
113
- VALUE location = Qnil;
113
+ // Allocate memory for the raw_locations, and keep track of it on the Ruby
114
+ // heap so it will be GC'd even if we raise.
115
+ // Zero the frame array so our mark function doesn't get confused too.
116
+ VALUE frame_wrapper = backtracie_frame_wrapper_new(raw_frame_count);
117
+ raw_location *raw_frames = backtracie_frame_wrapper_frames(frame_wrapper);
118
+ int *raw_frames_len = backtracie_frame_wrapper_len(frame_wrapper);
114
119
 
115
- if (raw_locations[i].is_ruby_frame) {
116
- last_ruby_location = &raw_locations[i];
117
- location = ruby_frame_to_location(&raw_locations[i]);
118
- } else {
119
- location = cfunc_frame_to_location(&raw_locations[i], last_ruby_location);
120
+ for (int i = ignored_stack_top_frames; i < raw_frame_count; i++) {
121
+ bool valid_frame = backtracie_capture_frame_for_thread(
122
+ thread, i, &raw_frames[*raw_frames_len]);
123
+ if (valid_frame) {
124
+ (*raw_frames_len)++;
120
125
  }
126
+ }
121
127
 
122
- rb_ary_store(locations, i - ignored_stack_top_frames, location);
128
+ VALUE rb_locations = rb_ary_new_capa(*raw_frames_len);
129
+ // Iterate _backwards_ through the frames, so we can keep track of the
130
+ // previous ruby frame for a C frame. This is required because C frames don't
131
+ // have filenames or line numbers; we must instead use the filename/lineno of
132
+ // the _caller_ of the function.
133
+ raw_location *prev_ruby_loc = NULL;
134
+ for (int i = *raw_frames_len - 1; i >= 0; i--) {
135
+ if (raw_frames[i].is_ruby_frame) {
136
+ prev_ruby_loc = &raw_frames[i];
137
+ }
138
+ VALUE rb_loc = frame_to_location(&raw_frames[i], prev_ruby_loc);
139
+ rb_ary_store(rb_locations, i, rb_loc);
123
140
  }
124
141
 
125
- return locations;
142
+ RB_GC_GUARD(frame_wrapper);
143
+ return rb_locations;
126
144
  }
127
145
 
128
146
  static VALUE primitive_caller_locations(VALUE self) {
129
147
  // Ignore:
130
148
  // * the current stack frame (native)
131
149
  // * the Backtracie.caller_locations that called us
132
- // * the frame from the caller itself (since we're replicating the semantics of Kernel#caller_locations)
150
+ // * the frame from the caller itself (since we're replicating the semantics
151
+ // of Kernel#caller_locations)
133
152
  int ignored_stack_top_frames = 3;
134
153
 
135
154
  return collect_backtrace_locations(self, Qnil, ignored_stack_top_frames);
@@ -143,230 +162,121 @@ static VALUE primitive_backtrace_locations(VALUE self, VALUE thread) {
143
162
  return collect_backtrace_locations(self, thread, ignored_stack_top_frames);
144
163
  }
145
164
 
146
- inline static VALUE new_location(
147
- VALUE absolute_path,
148
- VALUE base_label,
149
- VALUE label,
150
- VALUE lineno,
151
- VALUE path,
152
- VALUE qualified_method_name,
153
- VALUE debug
154
- ) {
155
- VALUE arguments[] = { absolute_path, base_label, label, lineno, path, qualified_method_name, debug };
156
- return rb_class_new_instance(VALUE_COUNT(arguments), arguments, backtracie_location_class);
157
- }
158
-
159
- static VALUE ruby_frame_to_location(raw_location *the_location) {
160
- VALUE frame = frame_from_location(the_location);
161
-
162
- return new_location(
163
- rb_profile_frame_absolute_path(frame),
164
- rb_profile_frame_base_label(frame),
165
- rb_profile_frame_label(the_location->iseq),
166
- INT2FIX(the_location->line_number),
167
- rb_profile_frame_path(frame),
168
- qualified_method_name_for_location(the_location),
169
- debug_raw_location(the_location)
170
- );
171
- }
172
-
173
- static VALUE qualified_method_name_for_location(raw_location *the_location) {
174
- VALUE frame = frame_from_location(the_location);
175
- VALUE defined_class = backtracie_defined_class(the_location);
176
- VALUE qualified_method_name = Qnil;
177
-
178
- if (is_defined_class_a_refinement(the_location)) {
179
- qualified_method_name = backtracie_refinement_name(the_location);
180
- rb_str_concat(qualified_method_name, rb_str_new2("#"));
181
- rb_str_concat(qualified_method_name, rb_profile_frame_label(frame));
182
- } else if (is_self_class_singleton(the_location)) {
183
- qualified_method_name = qualified_method_name_from_self(the_location);
184
- } else if (backtracie_iseq_is_block(the_location) || backtracie_iseq_is_eval(the_location)) {
185
- qualified_method_name = qualified_method_name_for_block(the_location);
186
- } else if (defined_class != Qnil && rb_mod_name(defined_class) == Qnil) {
187
- // Instance of an anonymous class. Let's find it a name
188
- VALUE superclass = defined_class;
189
- VALUE superclass_name = Qnil;
190
- do {
191
- superclass = RCLASS_SUPER(superclass);
192
- superclass_name = rb_mod_name(superclass);
193
- } while (superclass_name == Qnil);
194
-
195
- qualified_method_name = rb_str_new2("");
196
- rb_str_concat(qualified_method_name, superclass_name);
197
- rb_str_concat(qualified_method_name, rb_str_new2("$anonymous#"));
198
- rb_str_concat(qualified_method_name, rb_profile_frame_base_label(frame_from_location(the_location)));
199
- } else {
200
- qualified_method_name = backtracie_rb_profile_frame_qualified_method_name(frame);
201
-
202
- if (qualified_method_name == Qnil) {
203
- qualified_method_name = qualified_method_name_from_self(the_location);
204
- }
205
- }
206
-
207
- if (backtracie_iseq_is_block(the_location) || backtracie_iseq_is_eval(the_location)) {
208
- rb_str_concat(qualified_method_name, rb_str_new2("{block}"));
209
- }
210
-
211
- return qualified_method_name;
212
- }
213
-
214
- static VALUE cfunc_frame_to_location(raw_location *the_location, raw_location *last_ruby_location) {
215
- VALUE last_ruby_frame =
216
- last_ruby_location != 0 ? frame_from_location(last_ruby_location) : Qnil;
217
-
218
- // Replaces label and base_label in cfuncs
219
- VALUE method_name =
220
- the_location->should_use_cfunc_name ?
221
- the_location->cfunc_name : backtracie_rb_profile_frame_method_name(the_location->callable_method_entry);
222
-
223
- return new_location(
224
- SAFE_NAVIGATION(rb_profile_frame_absolute_path, last_ruby_frame),
225
- method_name,
226
- method_name,
227
- last_ruby_location != 0 ? INT2FIX(last_ruby_location->line_number) : Qnil,
228
- SAFE_NAVIGATION(rb_profile_frame_path, last_ruby_frame),
229
- backtracie_rb_profile_frame_qualified_method_name(the_location->callable_method_entry),
230
- debug_raw_location(the_location)
231
- );
232
- }
233
-
234
- static VALUE frame_from_location(raw_location *the_location) {
235
- return \
236
- the_location->should_use_iseq ||
237
- // This one is somewhat weird, but the regular MRI Ruby APIs seem to pick the iseq for evals as well
238
- backtracie_iseq_is_eval(the_location) ?
239
- the_location->iseq : the_location->callable_method_entry;
240
- }
241
-
242
- static VALUE qualified_method_name_for_block(raw_location *the_location) {
243
- VALUE class_name = backtracie_rb_profile_frame_classpath(the_location->callable_method_entry);
244
- VALUE method_name = backtracie_called_id(the_location);
245
- VALUE is_singleton_method = rb_profile_frame_singleton_method_p(the_location->iseq);
246
-
247
- VALUE name = rb_str_new2("");
248
- rb_str_concat(name, class_name);
249
- rb_str_concat(name, is_singleton_method ? rb_str_new2(".") : rb_str_new2("#"));
250
- rb_str_concat(name, rb_sym2str(method_name));
251
-
252
- return name;
165
+ inline static VALUE new_location(VALUE absolute_path, VALUE base_label,
166
+ VALUE label, VALUE lineno, VALUE path,
167
+ VALUE qualified_method_name,
168
+ VALUE path_is_synthetic, VALUE debug) {
169
+ VALUE arguments[] = {
170
+ absolute_path, base_label, label, lineno, path,
171
+ qualified_method_name, path_is_synthetic, debug};
172
+ return rb_class_new_instance(VALUE_COUNT(arguments), arguments,
173
+ backtracie_location_class);
253
174
  }
254
175
 
255
- static VALUE qualified_method_name_from_self(raw_location *the_location) {
256
- if (the_location->self == Qnil) return Qnil;
257
-
258
- VALUE self_class = rb_class_of(the_location->self);
259
- bool is_self_class_singleton = FL_TEST(self_class, FL_SINGLETON);
260
-
261
- VALUE name = rb_str_new2("");
262
- if (is_self_class_singleton) {
263
- if (the_location->self == main_object_instance) {
264
- rb_str_concat(name, rb_str_new2("Object$<main>#"));
265
- } else {
266
- // Crawl up the hierarchy to find a real class
267
- VALUE the_class = rb_class_real(self_class);
268
-
269
- // This is similar to BetterBacktraceHelper's self_class_module_or_class?
270
- // If the real class of this object is the actual class Class or the class Module,
271
- // it means that this is a method being directly called on a given Class/Module,
272
- // e.g. Kernel.puts or BasicObject.name. In this case, Ruby already sets the name
273
- // correctly, so we just delegate.
274
- if (the_class == rb_cModule || the_class == rb_cClass) {
275
- // Is the class/module is anonymous?
276
- if (rb_mod_name(the_location->self) == Qnil) {
277
- rb_str_concat(name, rb_funcall(the_class, to_s_id, 0));
278
- rb_str_concat(name, rb_str_new2("$singleton."));
279
- } else {
280
- return SAFE_NAVIGATION(backtracie_rb_profile_frame_qualified_method_name, the_location->callable_method_entry);
281
- }
282
- } else {
283
- rb_str_concat(name, rb_funcall(the_class, to_s_id, 0));
284
- rb_str_concat(name, rb_str_new2("$singleton#"));
285
- }
286
- }
287
- } else {
288
- // Not very sure if this branch of the if is ever reached, and if it would be for a instance or static call, so
289
- // let's just have these defaults and revisit as needed
290
- rb_str_concat(name, rb_funcall(self_class, to_s_id, 0));
291
- rb_str_concat(name, rb_str_new2("#"));
292
- }
176
+ static VALUE frame_to_location(const raw_location *raw_loc,
177
+ const raw_location *prev_ruby_loc) {
178
+ // If raw_loc != prev_ruby_loc, that means this location is a cfunc, and not a
179
+ // ruby frame; so, it doesn't _actually_ have a path. For compatability with
180
+ // Thread#backtrace et. al., we return the frame of the previous
181
+ // actually-a-ruby-frame location. When we do that, we set a flag
182
+ // path_is_synthetic on the location so that interested callers can know if
183
+ // that's the case.
184
+ VALUE filename_abs;
185
+ VALUE filename_rel;
186
+ VALUE line_number;
187
+ VALUE path_is_synthetic;
188
+ if (prev_ruby_loc) {
189
+ filename_abs = backtracie_frame_filename_rbstr(prev_ruby_loc, true);
190
+ filename_rel = backtracie_frame_filename_rbstr(prev_ruby_loc, false);
191
+ line_number = INT2NUM(backtracie_frame_line_number(prev_ruby_loc));
192
+ path_is_synthetic = (raw_loc != prev_ruby_loc) ? Qtrue : Qfalse;
293
193
 
294
- if (backtracie_iseq_is_block(the_location) || backtracie_iseq_is_eval(the_location)) {
295
- // Nothing to do, {block} will be appended in qualified_method_name_for_location which called us
296
194
  } else {
297
- rb_str_concat(name, rb_profile_frame_base_label(frame_from_location(the_location)));
195
+ filename_abs = rb_str_new2("(in native code)");
196
+ filename_rel = rb_str_dup(filename_abs);
197
+ line_number = INT2NUM(0);
198
+ path_is_synthetic = Qtrue;
298
199
  }
299
200
 
300
- return name;
201
+ return new_location(filename_abs, backtracie_frame_label_rbstr(raw_loc, true),
202
+ backtracie_frame_label_rbstr(raw_loc, false), line_number,
203
+ filename_rel, backtracie_frame_name_rbstr(raw_loc),
204
+ path_is_synthetic, debug_raw_location(raw_loc));
301
205
  }
302
206
 
303
- static bool is_self_class_singleton(raw_location *the_location) {
304
- return the_location->self != Qnil && FL_TEST(rb_class_of(the_location->self), FL_SINGLETON);
305
- }
306
-
307
- static bool is_defined_class_a_refinement(raw_location *the_location) {
308
- VALUE defined_class = backtracie_defined_class(the_location);
207
+ static VALUE debug_raw_location(const raw_location *the_location) {
208
+ VALUE arguments[] = {
209
+ ID2SYM(rb_intern("ruby_frame?")),
210
+ /* => */ to_boolean(the_location->is_ruby_frame),
211
+ ID2SYM(rb_intern("self_is_real_self?")),
212
+ /* => */ to_boolean(the_location->self_is_real_self),
213
+ ID2SYM(rb_intern("rb_profile_frames")),
214
+ /* => */ debug_frame(backtracie_frame_for_rb_profile(the_location)),
215
+ ID2SYM(rb_intern("self_or_self_class")),
216
+ /* => */ the_location->self_or_self_class,
217
+ ID2SYM(rb_intern("pc")),
218
+ /* => */ ULONG2NUM((uintptr_t)the_location->pc),
219
+ ID2SYM(rb_intern("cfunc_function_info")),
220
+ /* => */ cfunc_function_info(the_location)};
309
221
 
310
- return defined_class != Qnil && FL_TEST(rb_class_of(defined_class), RMODULE_IS_REFINEMENT);
222
+ VALUE debug_hash = rb_hash_new();
223
+ for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2)
224
+ rb_hash_aset(debug_hash, arguments[i], arguments[i + 1]);
225
+ return debug_hash;
311
226
  }
312
227
 
313
- static VALUE debug_raw_location(raw_location *the_location) {
314
- VALUE self_class = SAFE_NAVIGATION(rb_class_of, the_location->self);
315
-
316
- VALUE arguments[] = {
317
- ID2SYM(rb_intern("ruby_frame?")) , /* => */ to_boolean(the_location->is_ruby_frame),
318
- ID2SYM(rb_intern("should_use_iseq")), /* => */ to_boolean(the_location->should_use_iseq),
319
- ID2SYM(rb_intern("should_use_cfunc_name")), /* => */ to_boolean(the_location->should_use_cfunc_name),
320
- ID2SYM(rb_intern("vm_method_type")), /* => */ INT2FIX(the_location->vm_method_type),
321
- ID2SYM(rb_intern("line_number")), /* => */ INT2FIX(the_location->line_number),
322
- ID2SYM(rb_intern("called_id")), /* => */ backtracie_called_id(the_location),
323
- // TODO: On Ruby < 3.0, running be pry -e 'require "backtracie"; Backtracie.caller_locations' with this being
324
- // exposed causes a VM segfault inside the pretty printing code
325
- // ID2SYM(rb_intern("defined_class")), /* => */ backtracie_defined_class(the_location),
326
- ID2SYM(rb_intern("defined_class_refinement?")), /* => */ to_boolean(is_defined_class_a_refinement(the_location)),
327
- ID2SYM(rb_intern("self_class")), /* => */ self_class,
328
- ID2SYM(rb_intern("real_class")), /* => */ SAFE_NAVIGATION(rb_class_real, self_class),
329
- ID2SYM(rb_intern("self")), /* => */ the_location->self,
330
- ID2SYM(rb_intern("self_class_singleton?")), /* => */ to_boolean(is_self_class_singleton(the_location)),
331
- ID2SYM(rb_intern("iseq_is_block?")), /* => */ to_boolean(backtracie_iseq_is_block(the_location)),
332
- ID2SYM(rb_intern("iseq_is_eval?")), /* => */ to_boolean(backtracie_iseq_is_eval(the_location)),
333
- ID2SYM(rb_intern("cfunc_name")), /* => */ the_location->cfunc_name,
334
- ID2SYM(rb_intern("iseq")), /* => */ debug_frame(the_location->iseq),
335
- ID2SYM(rb_intern("callable_method_entry")), /* => */ debug_frame(the_location->callable_method_entry)
336
- };
228
+ static VALUE debug_frame(VALUE frame) {
229
+ if (frame == Qnil)
230
+ return Qnil;
231
+
232
+ VALUE arguments[] = {ID2SYM(rb_intern("path")),
233
+ /* => */ rb_profile_frame_path(frame),
234
+ ID2SYM(rb_intern("absolute_path")),
235
+ /* => */ rb_profile_frame_absolute_path(frame),
236
+ ID2SYM(rb_intern("label")),
237
+ /* => */ rb_profile_frame_label(frame),
238
+ ID2SYM(rb_intern("base_label")),
239
+ /* => */ rb_profile_frame_base_label(frame),
240
+ ID2SYM(rb_intern("full_label")),
241
+ /* => */ rb_profile_frame_full_label(frame),
242
+ ID2SYM(rb_intern("first_lineno")),
243
+ /* => */ rb_profile_frame_first_lineno(frame),
244
+ ID2SYM(rb_intern("classpath")),
245
+ /* => */ rb_profile_frame_classpath(frame),
246
+ ID2SYM(rb_intern("singleton_method_p")),
247
+ /* => */ rb_profile_frame_singleton_method_p(frame),
248
+ ID2SYM(rb_intern("method_name")),
249
+ /* => */ rb_profile_frame_method_name(frame),
250
+ ID2SYM(rb_intern("qualified_method_name")),
251
+ /* => */ rb_profile_frame_qualified_method_name(frame)};
337
252
 
338
253
  VALUE debug_hash = rb_hash_new();
339
- #ifndef PRE_MJIT_RUBY
340
- // FIXME: Hack, need to actually fix this
341
- rb_hash_bulk_insert(VALUE_COUNT(arguments), arguments, debug_hash);
342
- #endif
254
+ for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2)
255
+ rb_hash_aset(debug_hash, arguments[i], arguments[i + 1]);
343
256
  return debug_hash;
344
257
  }
345
258
 
346
- static VALUE debug_frame(VALUE frame) {
347
- if (frame == Qnil) return Qnil;
259
+ static VALUE cfunc_function_info(const raw_location *the_location) {
260
+ return Qnil;
261
+ #if 0 // Disabled until this can be fixed up to not break Windows/macOS
262
+ Dl_info symbol_info;
263
+ struct Elf64_Sym *elf_symbol = 0;
264
+
265
+ if (the_location->cfunc_function == NULL ||
266
+ !dladdr1(the_location->cfunc_function, &symbol_info, (void**) &elf_symbol, RTLD_DL_SYMENT)) return Qnil;
267
+
268
+ VALUE fname = symbol_info.dli_fname == NULL ? Qnil : rb_str_new2(symbol_info.dli_fname);
269
+ VALUE sname = symbol_info.dli_sname == NULL ? Qnil : rb_str_new2(symbol_info.dli_sname);
348
270
 
349
271
  VALUE arguments[] = {
350
- ID2SYM(rb_intern("path")), /* => */ rb_profile_frame_path(frame),
351
- ID2SYM(rb_intern("absolute_path")), /* => */ rb_profile_frame_absolute_path(frame),
352
- ID2SYM(rb_intern("label")), /* => */ rb_profile_frame_label(frame),
353
- ID2SYM(rb_intern("base_label")), /* => */ rb_profile_frame_base_label(frame),
354
- ID2SYM(rb_intern("full_label")), /* => */ rb_profile_frame_full_label(frame),
355
- ID2SYM(rb_intern("first_lineno")), /* => */ rb_profile_frame_first_lineno(frame),
356
- ID2SYM(rb_intern("classpath")), /* => */ backtracie_rb_profile_frame_classpath(frame),
357
- ID2SYM(rb_intern("singleton_method_p")), /* => */ rb_profile_frame_singleton_method_p(frame),
358
- ID2SYM(rb_intern("method_name")), /* => */ rb_profile_frame_method_name(frame),
359
- ID2SYM(rb_intern("qualified_method_name")), /* => */ backtracie_rb_profile_frame_qualified_method_name(frame)
272
+ ID2SYM(rb_intern("dli_fname")), /* => */ fname,
273
+ ID2SYM(rb_intern("dli_sname")), /* => */ sname
360
274
  };
361
275
 
362
276
  VALUE debug_hash = rb_hash_new();
363
- #ifndef PRE_MJIT_RUBY
364
- // FIXME: Hack, need to actually fix this
365
- rb_hash_bulk_insert(VALUE_COUNT(arguments), arguments, debug_hash);
366
- #endif
277
+ for (long unsigned int i = 0; i < VALUE_COUNT(arguments); i += 2) rb_hash_aset(debug_hash, arguments[i], arguments[i+1]);
367
278
  return debug_hash;
279
+ #endif
368
280
  }
369
281
 
370
- static inline VALUE to_boolean(bool value) {
371
- return value ? Qtrue : Qfalse;
372
- }
282
+ static inline VALUE to_boolean(bool value) { return value ? Qtrue : Qfalse; }