backtracie 0.2.1 → 1.0.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 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; }