Skip to main content

rbspy/core/
ruby_version.rs

1/*
2 * Ruby version specific code for reading a stack trace out of a Ruby process's memory.
3 *
4 * Implemented through a series of macros, because there are subtle differences in struct layout
5 * between similar Ruby versions (like 2.2.1 vs 2.2.2) that mean it's easiest to compile a
6 * different function for every Ruby minor version.
7 *
8 * Defines a bunch of submodules, one per Ruby version (`ruby_1_9_3`, `ruby_2_2_0`, etc.)
9 */
10
11macro_rules! ruby_version_v_1_9_1(
12    ($ruby_version:ident) => (
13        pub mod $ruby_version {
14            use std;
15            use anyhow::{Context, format_err, Result};
16            use bindings::$ruby_version::*;
17            use crate::core::process::ProcessMemory;
18
19            get_stack_trace!(rb_thread_struct);
20            get_execution_context_from_thread!(rb_thread_struct);
21            rstring_as_array_1_9_1!();
22            get_ruby_string_1_9_1!();
23            get_cfps!();
24            get_pos!(rb_iseq_struct);
25            get_lineno_1_9_0!();
26            get_stack_frame_1_9_1!();
27            stack_field_1_9_0!();
28            get_thread_status_1_9_0!();
29            get_thread_id_1_9_0!();
30            get_cfunc_name_unsupported!();
31        }
32    )
33);
34
35macro_rules! ruby_version_v_1_9_2_to_3(
36    // support for absolute paths appears for 1.9.2
37    ($ruby_version:ident) => (
38        pub mod $ruby_version {
39            use std;
40            use anyhow::{Context, format_err, Result};
41            use bindings::$ruby_version::*;
42            use crate::core::process::ProcessMemory;
43
44            get_stack_trace!(rb_thread_struct);
45            get_execution_context_from_thread!(rb_thread_struct);
46            rstring_as_array_1_9_1!();
47            get_ruby_string_1_9_1!();
48            get_cfps!();
49            get_pos!(rb_iseq_struct);
50            get_lineno_1_9_0!();
51            get_stack_frame_1_9_2!();
52            stack_field_1_9_0!();
53            get_thread_status_1_9_0!();
54            get_thread_id_1_9_0!();
55            get_cfunc_name_unsupported!();
56        }
57    )
58);
59
60macro_rules! ruby_version_v_2_0_to_2_2(
61    ($ruby_version:ident) => (
62       pub mod $ruby_version {
63           use std;
64           use anyhow::{Context, format_err, Result};
65           use bindings::$ruby_version::*;
66           use crate::core::process::ProcessMemory;
67
68            // These 4 functions are the
69            // core of how the program works. They're essentially a straight port of
70            // this gdb script:
71            // https://gist.github.com/csfrancis/11376304/raw/7a0450d11e64e3bb7c982b7ad2778f3603188c0f/gdb_ruby_backtrace.py
72            // except without using gdb!!
73            //
74            // `get_cfps` corresponds to
75            // (* const rb_thread_struct *(ruby_current_thread_address_location))->cfp
76            //
77            // `get_ruby_string` is doing ((Struct RString *) address) and then
78            // trying one of two ways to get the actual Ruby string out depending
79            // on how it's stored
80            get_stack_trace!(rb_thread_struct);
81            get_execution_context_from_thread!(rb_thread_struct);
82            rstring_as_array_1_9_1!();
83            get_ruby_string_1_9_1!();
84            get_cfps!();
85            get_pos!(rb_iseq_struct);
86            get_lineno_2_0_0!();
87            get_stack_frame_2_0_0!();
88            stack_field_1_9_0!();
89            get_thread_status_1_9_0!();
90            get_thread_id_1_9_0!();
91            get_cfunc_name_unsupported!();
92        }
93    )
94);
95
96macro_rules! ruby_version_v_2_3_to_2_4(
97    ($ruby_version:ident) => (
98       pub mod $ruby_version {
99           use std;
100           use anyhow::{Context, format_err, Result};
101           use bindings::$ruby_version::*;
102           use crate::core::process::ProcessMemory;
103
104            get_stack_trace!(rb_thread_struct);
105            get_execution_context_from_thread!(rb_thread_struct);
106            rstring_as_array_1_9_1!();
107            get_ruby_string_1_9_1!();
108            get_cfps!();
109            get_pos!(rb_iseq_constant_body);
110            get_lineno_2_3_0!();
111            get_stack_frame_2_3_0!();
112            stack_field_1_9_0!();
113            get_thread_status_1_9_0!();
114            get_thread_id_1_9_0!();
115            get_cfunc_name_unsupported!();
116        }
117    )
118);
119
120macro_rules! ruby_version_v2_5_x(
121    ($ruby_version:ident) => (
122       pub mod $ruby_version {
123           use std;
124           use anyhow::{Context, format_err, Result};
125           use bindings::$ruby_version::*;
126           use crate::core::process::ProcessMemory;
127
128            get_stack_trace!(rb_execution_context_struct);
129            get_execution_context_from_thread!(rb_execution_context_struct);
130            rstring_as_array_1_9_1!();
131            get_ruby_string_1_9_1!();
132            get_cfps!();
133            get_pos!(rb_iseq_constant_body);
134            get_lineno_2_5_0!();
135            get_stack_frame_2_5_0!();
136            stack_field_2_5_0!();
137            get_ruby_string_array_2_5_0!();
138            get_thread_status_2_5_0!();
139            get_thread_id_2_5_0!();
140            #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "windows"))]
141            get_cfunc_name_unsupported!();
142            #[cfg(target_os = "linux")]
143            get_cfunc_name!();
144            #[cfg(target_os = "linux")]
145            get_classpath_unsupported!();
146        }
147    )
148);
149
150macro_rules! ruby_version_v2_6_x(
151    ($ruby_version:ident) => (
152       pub mod $ruby_version {
153           use std;
154           use anyhow::{Context, format_err, Result};
155           use bindings::$ruby_version::*;
156           use crate::core::process::ProcessMemory;
157
158            get_stack_trace!(rb_execution_context_struct);
159            get_execution_context_from_thread!(rb_execution_context_struct);
160            rstring_as_array_1_9_1!();
161            get_ruby_string_1_9_1!();
162            get_ruby_string_array_2_5_0!();
163            get_cfps!();
164            get_pos!(rb_iseq_constant_body);
165            get_lineno_2_6_0!();
166            get_stack_frame_2_5_0!();
167            stack_field_2_5_0!();
168            get_thread_status_2_6_0!();
169            get_thread_id_2_5_0!();
170            #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "windows"))]
171            get_cfunc_name_unsupported!();
172            #[cfg(target_os = "linux")]
173            get_cfunc_name!();
174            #[cfg(target_os = "linux")]
175            get_classpath_unsupported!();
176        }
177    )
178);
179
180macro_rules! ruby_version_v2_7_x(
181    ($ruby_version:ident) => (
182        pub mod $ruby_version {
183            use std;
184            use anyhow::{Context, format_err, Result};
185            use bindings::$ruby_version::*;
186            use crate::core::process::ProcessMemory;
187
188            get_stack_trace!(rb_execution_context_struct);
189            get_execution_context_from_thread!(rb_execution_context_struct);
190            rstring_as_array_1_9_1!();
191            get_ruby_string_1_9_1!();
192            get_ruby_string_array_2_5_0!();
193            get_cfps!();
194            get_pos!(rb_iseq_constant_body);
195            get_lineno_2_6_0!();
196            get_stack_frame_2_5_0!();
197            stack_field_2_5_0!();
198            get_thread_status_2_6_0!();
199            get_thread_id_2_5_0!();
200            get_cfunc_name!();
201            get_classpath_unsupported!();
202        }
203    )
204);
205
206macro_rules! ruby_version_v3_0_x(
207    ($ruby_version:ident) => (
208        pub mod $ruby_version {
209            use std;
210            use anyhow::{Context, format_err, Result};
211            use bindings::$ruby_version::*;
212            use crate::core::process::ProcessMemory;
213
214            get_stack_trace!(rb_execution_context_struct);
215            ec_search_start_offset_3_0_0!();
216            get_execution_context_from_vm!();
217            rstring_as_array_1_9_1!();
218            get_ruby_string_1_9_1!();
219            get_ruby_string_array_2_5_0!();
220            get_cfps!();
221            get_pos!(rb_iseq_constant_body);
222            get_lineno_2_6_0!();
223            get_stack_frame_2_5_0!();
224            stack_field_2_5_0!();
225            get_thread_status_2_6_0!();
226            get_thread_id_2_5_0!();
227            get_cfunc_name!();
228
229            #[allow(non_upper_case_globals)]
230            const ruby_fl_type_RUBY_FL_USHIFT: ruby_fl_type = ruby_fl_ushift_RUBY_FL_USHIFT as i32;
231            get_classpath_unsupported!();
232        }
233    )
234);
235
236macro_rules! ruby_version_v3_1_x(
237    ($ruby_version:ident) => (
238        pub mod $ruby_version {
239            use std;
240            use anyhow::{Context, format_err, Result};
241            use bindings::$ruby_version::*;
242            use crate::core::process::ProcessMemory;
243
244            get_stack_trace!(rb_execution_context_struct);
245            ec_search_start_offset_3_0_0!();
246            get_execution_context_from_vm!();
247            rstring_as_array_3_1_0!();
248            get_ruby_string_1_9_1!();
249            get_ruby_string_array_2_5_0!();
250            get_cfps!();
251            get_pos!(rb_iseq_constant_body);
252            get_lineno_2_6_0!();
253            get_stack_frame_2_5_0!();
254            stack_field_2_5_0!();
255            get_thread_status_2_6_0!();
256            get_thread_id_2_5_0!();
257            get_cfunc_name!();
258
259            #[allow(non_upper_case_globals)]
260            const ruby_fl_type_RUBY_FL_USHIFT: ruby_fl_type = ruby_fl_ushift_RUBY_FL_USHIFT as i32;
261            get_classpath_unsupported!();
262        }
263    )
264);
265
266macro_rules! ruby_version_v3_2_x(
267    ($ruby_version:ident) => (
268        pub mod $ruby_version {
269            use std;
270            use anyhow::{Context, format_err, Result};
271            use bindings::$ruby_version::*;
272            use crate::core::process::ProcessMemory;
273
274            get_stack_trace!(rb_execution_context_struct);
275            ec_search_start_offset_3_0_0!();
276            get_execution_context_from_vm!();
277            get_ruby_string_3_2_0!();
278            get_ruby_string_array_3_2_0!();
279            get_cfps!();
280            get_pos!(rb_iseq_constant_body);
281            get_lineno_2_6_0!();
282            get_stack_frame_2_5_0!();
283            stack_field_2_5_0!();
284            get_thread_status_2_6_0!();
285            get_thread_id_3_2_0!();
286            get_cfunc_name!();
287
288            #[allow(non_upper_case_globals)]
289            const ruby_fl_type_RUBY_FL_USHIFT: ruby_fl_type = ruby_fl_ushift_RUBY_FL_USHIFT as i32;
290            get_classpath_unsupported!();
291        }
292    )
293);
294
295macro_rules! ruby_version_v3_3_x(
296    ($ruby_version:ident) => (
297        pub mod $ruby_version {
298            use std;
299            use anyhow::{Context, format_err, Result};
300            use bindings::$ruby_version::*;
301            use crate::core::process::ProcessMemory;
302
303            get_stack_trace!(rb_execution_context_struct);
304            ec_search_start_offset_3_0_0!();
305            get_execution_context_from_vm!();
306            get_ruby_string_3_3_0!();
307            get_ruby_string_array_3_2_0!();
308            get_cfps!();
309            get_pos!(rb_iseq_constant_body);
310            get_lineno_2_6_0!();
311            get_stack_frame_3_3_0!();
312            stack_field_2_5_0!();
313            get_thread_status_2_6_0!();
314            get_thread_id_3_2_0!();
315            get_cfunc_name!();
316
317            #[allow(non_upper_case_globals)]
318            const ruby_fl_type_RUBY_FL_USHIFT: ruby_fl_type = ruby_fl_ushift_RUBY_FL_USHIFT as i32;
319            get_classpath!();
320            rb_class_real_3_0_0!();
321        }
322    )
323);
324
325macro_rules! ruby_version_v4_0_x(
326    ($ruby_version:ident) => (
327        pub mod $ruby_version {
328            use std;
329            use anyhow::{Context, format_err, Result};
330            use bindings::$ruby_version::*;
331            use crate::core::process::ProcessMemory;
332
333            get_stack_trace!(rb_execution_context_struct);
334            ec_search_start_offset_4_0_0!();
335            get_execution_context_from_vm!();
336            get_ruby_string_3_3_0!();
337            get_ruby_string_array_3_2_0!();
338            get_cfps!();
339            get_pos!(rb_iseq_constant_body);
340            get_lineno_2_6_0!();
341            get_stack_frame_3_3_0!();
342            stack_field_2_5_0!();
343            get_thread_status_2_6_0!();
344            get_thread_id_3_2_0!();
345            get_cfunc_name!();
346
347            #[allow(non_upper_case_globals)]
348            const ruby_fl_type_RUBY_FL_USHIFT: ruby_fl_type = ruby_fl_ushift_RUBY_FL_USHIFT as i32;
349            get_classpath!();
350            rb_class_real_4_0_0!();
351        }
352    )
353);
354
355macro_rules! get_execution_context_from_thread(
356    ($thread_type:ident) => (
357        pub fn get_execution_context<T: ProcessMemory>(
358            current_thread_address_ptr: usize,
359            _ruby_vm_address_ptr: usize,
360            source: &T
361        ) -> Result<usize> {
362            source.copy_struct(current_thread_address_ptr)
363                .context("couldn't read current thread pointer")
364        }
365    )
366);
367
368macro_rules! ec_search_start_offset_3_0_0(
369    () => (
370        pub fn ec_search_start_offset() -> usize {
371            // The initial offsets were found by experimenting
372            return
373                if cfg!(target_os = "windows") {
374                    32
375                } else {
376                    48
377                };
378        }
379    )
380);
381
382macro_rules! ec_search_start_offset_4_0_0(
383    () => (
384        pub fn ec_search_start_offset() -> usize {
385            // The initial offsets were found by experimenting
386            return
387                if cfg!(target_os = "windows") {
388                    30
389                } else {
390                    38
391                };
392        }
393    )
394);
395
396macro_rules! get_execution_context_from_vm(
397    () => (
398        pub fn get_execution_context<T: ProcessMemory>(
399            _current_thread_address_ptr: usize,
400            ruby_vm_address_ptr: usize,
401            source: &T
402        ) -> Result<usize> {
403            // This is a roundabout way to get the execution context address, but it helps us
404            // avoid platform-specific structures in memory (e.g. pthread types) that would
405            // require us to maintain separate ruby-structs bindings for each platform due to
406            // their varying sizes and alignments.
407            let vm_addr: usize = source.copy_struct(ruby_vm_address_ptr)
408                .context("couldn't read Ruby VM pointer")?;
409            let vm: rb_vm_struct = source.copy_struct(vm_addr as usize)
410                .context("couldn't read Ruby VM struct")?;
411
412            // Seek forward in the ractor struct, looking for the main thread's address. There
413            // may be other copies of the main thread address in the ractor struct, so it's
414            // important to jump as close as possible to the main_thread struct field before we
415            // search, which is the purpose of the initial offset. The execution context pointer
416            // is in the memory word just before main_thread (see rb_ractor_struct).
417            //
418            // The initial offsets were found by experimenting.
419            const ADDRESSES_TO_CHECK: usize = 32;
420            let offset = ec_search_start_offset() * std::mem::size_of::<usize>();
421            let main_ractor_address = vm.ractor.main_ractor as usize;
422            let candidate_addresses: [usize; ADDRESSES_TO_CHECK] =
423                source.copy_struct(main_ractor_address + offset)
424                    .context("couldn't read main ractor struct")?;
425
426            candidate_addresses
427            .iter()
428            .enumerate()
429            .filter(|(idx, &addr)| *idx > 0 && addr == vm.ractor.main_thread as usize)
430            .map(|(idx, _)| candidate_addresses[idx - 1])
431            .filter(|&addr| addr != 0)
432            .filter(|&addr| source.copy_struct::<rb_execution_context_struct>(addr as usize).is_ok())
433            .collect::<Vec<usize>>()
434            .first()
435            .map(|&addr| addr as usize)
436            .ok_or_else(|| format_err!("couldn't find execution context"))
437        }
438    )
439);
440
441macro_rules! get_stack_trace(
442    ($thread_type:ident) => (
443        use crate::core::process::Pid;
444        use crate::core::types::{StackFrame, StackTrace};
445
446        pub fn get_stack_trace<T: ProcessMemory>(
447            ruby_current_thread_address_location: usize,
448            ruby_vm_address_location: usize,
449            ruby_global_symbols_address_location: Option<usize>,
450            source: &T,
451            pid: Pid,
452            on_cpu: bool,
453        ) -> Result<Option<StackTrace>, anyhow::Error> {
454            let current_thread_addr: usize = get_execution_context(ruby_current_thread_address_location, ruby_vm_address_location, source)
455                .context("couldn't get execution context")?;
456            let thread: $thread_type = source.copy_struct(current_thread_addr)
457                .context("couldn't get current thread")?;
458
459            if on_cpu && get_thread_status(&thread, source)? != 0 /* THREAD_RUNNABLE */ {
460                // This is in addition to any OS-specific checks for thread activity, and provides
461                // an extra measure of reliability for targets that don't have them. It also works
462                // for coredump targets.
463                return Ok(None);
464            }
465
466            let thread_id = match get_thread_id(&thread, source) {
467                Ok(tid) => Some(tid),
468                Err(e) => {
469                    debug!("Couldn't get thread ID: {}", e);
470                    None
471                },
472            };
473            if stack_field(&thread) as usize == 0 {
474                return Ok(Some(StackTrace {
475                    pid: Some(pid),
476                    trace: vec!(StackFrame::unknown_c_function()),
477                    thread_id: thread_id,
478                    time: Some(SystemTime::now()),
479                    on_cpu: None,
480                }));
481            }
482            let mut trace = Vec::new();
483            let cfps = get_cfps(thread.cfp as usize, stack_base(&thread) as usize, source)?;
484            for cfp in cfps.iter() {
485                let cfunc = ruby_frame_cfunc(&cfp, source)?;
486
487                if !cfunc && cfp.pc as usize != 0 {
488                    let iseq_struct: rb_iseq_struct = source.copy_struct(cfp.iseq as usize)
489                        .context("couldn't copy iseq struct")?;
490
491                    let label_path  = get_stack_frame(&iseq_struct, &cfp, source);
492                    match label_path {
493                        Ok(call)  => trace.push(call),
494                        Err(x) => {
495                            debug!("Error: {:#?}", x);
496                            debug!("cfp: {:?}", cfp);
497                            debug!("thread: {:?}", thread);
498                            debug!("iseq struct: {:?}", iseq_struct);
499                            // this is a heuristic: the intent of this is that it skips function calls into C extensions
500                            if trace.len() > 0 {
501                                debug!("Skipping function call, possibly into C extension");
502                            } else {
503                                return Err(x);
504                            }
505                        }
506                    }
507                } else {
508                    let mut frame = StackFrame::unknown_c_function();
509                    if let Some(global_symbols_addr) = ruby_global_symbols_address_location {
510                        match get_cfunc_name(cfp, global_symbols_addr, source, pid) {
511                            Ok(name) => {
512                                frame = StackFrame{
513                                    name: format!("{} [c function]", name),
514                                    relative_path: "(unknown)".to_string(),
515                                    absolute_path: None,
516                                    lineno: None,
517                                };
518                            },
519                            Err(e) => {
520                                debug!("Unknown C function: {:?}", e);
521                            }
522                        }
523                    }
524                    trace.push(frame);
525                    continue;
526                }
527            }
528            let thread_id = match get_thread_id(&thread, source) {
529                Ok(tid) => Some(tid),
530                Err(e) => {
531                    debug!("Couldn't get thread ID: {}", e);
532                    None
533                },
534            };
535            Ok(Some(StackTrace{trace, pid: Some(pid), thread_id, time: Some(SystemTime::now()), on_cpu: Some(on_cpu)}))
536        }
537
538        use proc_maps::{maps_contain_addr, MapRange};
539        use std::time::SystemTime;
540
541        // Checks whether the address looks even vaguely like a thread struct, mostly by making sure its
542        // addresses are reasonable
543        fn could_be_thread(thread: &$thread_type, all_maps: &[MapRange]) -> bool {
544            maps_contain_addr(thread.tag as usize, all_maps) &&
545                maps_contain_addr(thread.cfp as usize, all_maps) &&
546                maps_contain_addr(stack_field(thread) as usize, all_maps) &&
547                stack_size_field(thread) < 3_000_000
548        }
549
550        fn stack_base(thread: &$thread_type) -> i64 {
551            // Ruby stack grows down, starting at
552            //   ruby_current_thread->stack + ruby_current_thread->stack_size - 1 * sizeof(rb_control_frame_t)
553            // We must skip two dummy frames:
554            // https://github.com/ruby/ruby/blob/5445e0435260b449decf2ac16f9d09bae3cafe72/vm_backtrace.c#L477-L485%C2%A
555            //
556            // The base of the call stack is therefore at
557            //   stack + stack_size * sizeof(VALUE) - sizeof(rb_control_frame_t)
558            // (with everything in bytes).
559            stack_field(thread) + (stack_size_field(thread) * std::mem::size_of::<VALUE>() as i64) - (1 * std::mem::size_of::<rb_control_frame_t>() as i64)
560        }
561
562        pub fn is_maybe_thread<T>(candidate_thread_addr: usize, candidate_thread_addr_ptr: usize, source: &T, all_maps: &[MapRange]) -> bool where T: ProcessMemory {
563            if !maps_contain_addr(candidate_thread_addr, all_maps) {
564                return false;
565            }
566
567            let thread: $thread_type = match source.copy_struct(candidate_thread_addr) {
568                Ok(x) => x,
569                _ => { return false; },
570            };
571
572            if !could_be_thread(&thread, &all_maps) {
573                return false;
574            }
575
576            // finally, try to get an actual stack trace from the source and see if it works
577            get_stack_trace(candidate_thread_addr_ptr, 0, None, source, 0, false).is_ok()
578        }
579    )
580);
581
582macro_rules! stack_field_1_9_0(
583    () => (
584        fn stack_field(thread: &rb_thread_struct) -> i64 {
585            thread.stack as i64
586        }
587
588        fn stack_size_field(thread: &rb_thread_struct) -> i64 {
589            thread.stack_size as i64
590        }
591    )
592);
593
594macro_rules! stack_field_2_5_0(
595    () => (
596        fn stack_field(thread: &rb_execution_context_struct) -> i64 {
597            thread.vm_stack as i64
598        }
599
600        fn stack_size_field(thread: &rb_execution_context_struct) -> i64 {
601            thread.vm_stack_size as i64
602        }
603    )
604);
605
606macro_rules! get_thread_status_1_9_0(
607    () => (
608        fn get_thread_status<T>(thread_struct: &rb_thread_struct, _source: &T) -> Result<u32> {
609            Ok(thread_struct.status as u32)
610        }
611    )
612);
613
614macro_rules! get_thread_status_2_5_0(
615    () => (
616        fn get_thread_status<T>(thread_struct: &rb_execution_context_struct, source: &T)
617                            -> Result<u32> where T: ProcessMemory {
618            let thread: rb_thread_struct = source.copy_struct(thread_struct.thread_ptr as usize)
619                .context(thread_struct.thread_ptr as usize)?;
620            Ok(thread.status as u32)
621        }
622    )
623);
624
625// ->status changed into a bitfield
626macro_rules! get_thread_status_2_6_0(
627    () => (
628        fn get_thread_status<T>(thread_struct: &rb_execution_context_struct, source: &T)
629                            -> Result<u32> where T: ProcessMemory {
630            let thread: rb_thread_struct = source.copy_struct(thread_struct.thread_ptr as usize)
631                .context(thread_struct.thread_ptr as usize)?;
632            Ok(thread.status() as u32)
633        }
634    )
635);
636
637macro_rules! get_thread_id_1_9_0(
638    () => (
639        fn get_thread_id<T>(thread_struct: &rb_thread_struct, _source: &T) -> Result<usize> {
640            Ok(thread_struct.thread_id as usize)
641        }
642    )
643);
644
645macro_rules! get_thread_id_2_5_0(
646    () => (
647        fn get_thread_id<T>(thread_struct: &rb_execution_context_struct, source: &T)
648                            -> Result<usize> where T: ProcessMemory {
649            let thread: rb_thread_struct = source.copy_struct(thread_struct.thread_ptr as usize)
650                .context("couldn't copy thread struct")?;
651            Ok(thread.thread_id as usize)
652        }
653    )
654);
655
656macro_rules! get_thread_id_3_2_0(
657    () => (
658        fn get_thread_id<T>(thread_struct: &rb_execution_context_struct, source: &T)
659                            -> Result<usize> where T: ProcessMemory {
660            let thread: rb_thread_struct = source.copy_struct(thread_struct.thread_ptr as usize)
661                .context("couldn't copy thread struct")?;
662            if thread.nt.is_null() {
663                return Err(format_err!("native thread pointer is NULL"));
664            }
665            let native_thread: rb_native_thread = source.copy_struct(thread.nt as usize)
666                .context("couldn't copy native thread struct")?;
667            Ok(native_thread.thread_id as usize)
668        }
669    )
670);
671
672macro_rules! get_ruby_string_array_2_5_0(
673    () => (
674        // Returns (path, absolute_path)
675        fn get_ruby_string_array<T>(addr: usize, string_class: usize, source: &T) -> Result<(String, String)> where T: ProcessMemory {
676            // todo: we're doing an extra copy here for no reason
677            let rstring: RString = source.copy_struct(addr).context("couldn't copy RString")?;
678            if rstring.basic.klass as usize == string_class {
679                let s = get_ruby_string(addr, source)?;
680                return Ok((s.clone(), s))
681            }
682            // otherwise it's an RArray
683            let rarray: RArray = source.copy_struct(addr).context("couldn't copy RArray")?;
684            // TODO: this assumes that the array contents are stored inline and not on the heap
685            // I think this will always be true but we should check instead
686            // the reason I am not checking is that I don't know how to check yet
687            let path_addr: usize = unsafe { rarray.as_.ary[0] as usize }; // 0 => relative path
688            let abs_path_addr: usize = unsafe { rarray.as_.ary[1] as usize }; // 1 => absolute path
689            let rel_path = get_ruby_string(path_addr, source)?;
690            // In the case of internal ruby functions (and maybe others), we may not get a valid
691            // pointer here
692            let abs_path = get_ruby_string(abs_path_addr, source)
693                .unwrap_or(String::from("unknown"));
694            Ok((rel_path, abs_path))
695        }
696    )
697);
698
699macro_rules! get_ruby_string_array_3_2_0(
700    () => (
701        // Returns (path, absolute_path)
702        fn get_ruby_string_array<T>(addr: usize, string_class: usize, source: &T) -> Result<(String, String)> where T: ProcessMemory {
703            let rstring: RString = source.copy_struct(addr).context("couldn't copy RString")?;
704            if rstring.basic.klass as usize == string_class {
705                let s = get_ruby_string(addr, source)?;
706                return Ok((s.clone(), s))
707            }
708
709            // Due to VWA in ruby 3.2, we can't get the exact length of the RArray. So,
710            // we use these inline structs and assume that there are at least two array
711            // elements when we're reading a pathobj.
712            #[repr(C)]
713            #[derive(Copy, Clone)]
714            struct PaddedRArray {
715                pub basic: RBasic,
716                pub as_: PaddedRArray__bindgen_ty_1,
717            }
718            #[repr(C)]
719            #[derive(Copy, Clone)]
720            union PaddedRArray__bindgen_ty_1 {
721                pub heap: RArray__bindgen_ty_1__bindgen_ty_1,
722                pub ary: [VALUE; 2usize],
723            }
724
725            // otherwise it's an RArray
726            let rarray: PaddedRArray = source.copy_struct(addr).context("couldn't copy RArray")?;
727            // TODO: this assumes that the array contents are stored inline and not on the heap
728            // I think this will always be true but we should check instead
729            // the reason I am not checking is that I don't know how to check yet
730            let path_addr: usize = unsafe { rarray.as_.ary[0] as usize }; // 0 => relative path
731            let abs_path_addr: usize = unsafe { rarray.as_.ary[1] as usize }; // 1 => absolute path
732
733            let rel_path = get_ruby_string(path_addr, source)?;
734            // In the case of internal ruby functions (and maybe others), we may not get a valid
735            // pointer here
736            let abs_path = get_ruby_string(abs_path_addr, source)
737                .unwrap_or(String::from("unknown"));
738
739            Ok((rel_path, abs_path))
740        }
741    )
742);
743
744macro_rules! rstring_as_array_1_9_1(
745    () => (
746        unsafe fn rstring_as_array(rstring: RString) -> [::std::os::raw::c_char; 24usize] {
747            rstring.as_.ary
748        }
749    )
750);
751
752macro_rules! rstring_as_array_3_1_0(
753    () => (
754        unsafe fn rstring_as_array(rstring: RString) -> [::std::os::raw::c_char; 24usize] {
755            rstring.as_.embed.ary
756        }
757    )
758);
759
760macro_rules! get_ruby_string_1_9_1(
761    () => (
762        fn get_ruby_string<T>(
763            addr: usize,
764            source: &T
765        ) -> Result<String> where T: ProcessMemory {
766            let vec = {
767                let rstring: RString = source.copy_struct(addr).context("couldn't copy rstring")?;
768                // See RSTRING_NOEMBED and RUBY_FL_USER1
769                let is_embedded_string = rstring.basic.flags & 1 << 13 == 0;
770                if is_embedded_string {
771                    unsafe { std::ffi::CStr::from_ptr(rstring_as_array(rstring).as_ref().as_ptr()) }
772                    .to_bytes()
773                    .to_vec()
774                } else {
775                    unsafe {
776                        let addr = rstring.as_.heap.ptr as usize;
777                        let len = rstring.as_.heap.len as usize;
778                        source.copy(addr as usize, len).context("couldn't copy ruby string from heap")?
779                    }
780                }
781            };
782
783            String::from_utf8(vec).context("couldn't convert ruby string bytes to string")
784        }
785    )
786);
787
788macro_rules! get_ruby_string_3_2_0(
789    () => (
790        fn get_ruby_string<T>(
791            addr: usize,
792            source: &T
793        ) -> Result<String> where T: ProcessMemory {
794            let rstring: RString = source.copy_struct(addr).context("couldn't copy rstring")?;
795            // See RSTRING_NOEMBED and RUBY_FL_USER1
796            let is_embedded_string = rstring.basic.flags & 1 << 13 == 0;
797            if is_embedded_string {
798                // Workaround for Windows strings until we have OS-specific bindings
799                #[cfg(target_os = "windows")]
800                let addr = addr + 4;
801
802                // The introduction of Variable Width Allocation (VWA) for strings means that
803                // the length of embedded strings varies at runtime. Instead of assuming a
804                // constant length, we need to read the length from the struct.
805                //
806                // See https://bugs.ruby-lang.org/issues/18239
807                let embedded_str_bytes = source.copy(
808                    addr + std::mem::size_of::<RBasic>() + std::mem::size_of::<std::os::raw::c_long>(),
809                    unsafe { rstring.as_.embed.len } as usize
810                ).context("couldn't copy rstring")?;
811                return String::from_utf8(embedded_str_bytes).context("couldn't convert ruby string bytes to string")
812            } else {
813                unsafe {
814                    let addr = rstring.as_.heap.ptr as usize;
815                    let len = rstring.as_.heap.len as usize;
816                    let heap_str_bytes = source.copy(addr as usize, len).context("couldn't copy ruby string from heap")?;
817                    return String::from_utf8(heap_str_bytes).context("couldn't convert ruby string bytes to string");
818                }
819            }
820        }
821    )
822);
823
824macro_rules! get_ruby_string_3_3_0(
825    () => (
826        fn get_ruby_string<T>(
827            addr: usize,
828            source: &T
829        ) -> Result<String> where T: ProcessMemory {
830            let rstring: RString = source.copy_struct(addr).context("couldn't copy rstring")?;
831            if rstring.len > 1_000_000 {
832                return Err(anyhow::anyhow!("string length {} for string at {:X} appears invalid", rstring.len, addr));
833            }
834            // See RSTRING_NOEMBED and RUBY_FL_USER1
835            let is_embedded_string = rstring.basic.flags & 1 << 13 == 0;
836            if is_embedded_string {
837                // Workaround for Windows strings until we have OS-specific bindings
838                #[cfg(target_os = "windows")]
839                let addr = addr + 4;
840
841                // The introduction of Variable Width Allocation (VWA) for strings means that
842                // the length of embedded strings varies at runtime. Instead of assuming a
843                // constant length, we need to read the length from the struct.
844                //
845                // See https://bugs.ruby-lang.org/issues/18239
846                let embedded_str_bytes = source.copy(
847                    addr + std::mem::size_of::<RBasic>() + std::mem::size_of::<std::os::raw::c_long>(),
848                    rstring.len as usize
849                ).context("couldn't copy rstring")?;
850                return String::from_utf8(embedded_str_bytes).context("couldn't convert ruby string bytes to string")
851            } else {
852                unsafe {
853                    let addr = rstring.as_.heap.ptr as usize;
854                    let len = rstring.len as usize;
855                    let heap_str_bytes = source.copy(addr as usize, len).context("couldn't copy ruby string from heap")?;
856                    return String::from_utf8(heap_str_bytes).context("couldn't convert ruby string bytes to string");
857                }
858            }
859        }
860    )
861);
862
863macro_rules! get_stack_frame_1_9_1(
864    () => (
865        fn get_stack_frame<T>(
866            iseq_struct: &rb_iseq_struct,
867            cfp: &rb_control_frame_t,
868            source: &T,
869            ) -> Result<StackFrame> where T: ProcessMemory {
870            Ok(StackFrame{
871                name: get_ruby_string(iseq_struct.name as usize, source)?,
872                relative_path: get_ruby_string(iseq_struct.filename as usize, source)?,
873                absolute_path: None,
874                lineno: match get_lineno(iseq_struct, cfp, source) {
875                    Ok(lineno) => Some(lineno),
876                    Err(e) => {
877                        warn!("couldn't get lineno: {}", e);
878                        None
879                    },
880                }
881            })
882        }
883    )
884);
885
886macro_rules! get_stack_frame_1_9_2(
887    () => (
888        fn get_stack_frame<T>(
889            iseq_struct: &rb_iseq_struct,
890            cfp: &rb_control_frame_t,
891            source: &T,
892            ) -> Result<StackFrame> where T: ProcessMemory {
893            Ok(StackFrame{
894                name: get_ruby_string(iseq_struct.name as usize, source)?,
895                relative_path: get_ruby_string(iseq_struct.filename as usize, source)?,
896                absolute_path: Some(get_ruby_string(iseq_struct.filepath as usize, source)?),
897                lineno: match get_lineno(iseq_struct, cfp, source) {
898                    Ok(lineno) => Some(lineno),
899                    Err(e) => {
900                        warn!("couldn't get lineno: {}", e);
901                        None
902                    },
903                }
904            })
905        }
906    )
907);
908
909macro_rules! get_stack_frame_2_0_0(
910    () => (
911        fn get_stack_frame<T>(
912            iseq_struct: &rb_iseq_struct,
913            cfp: &rb_control_frame_t,
914            source: &T,
915        ) -> Result<StackFrame> where T: ProcessMemory {
916            Ok(StackFrame{
917                name: get_ruby_string(iseq_struct.location.label as usize, source)?,
918                relative_path: get_ruby_string(iseq_struct.location.path as usize, source)?,
919                absolute_path: Some(get_ruby_string(iseq_struct.location.absolute_path as usize, source)?),
920                lineno: match get_lineno(iseq_struct, cfp, source) {
921                    Ok(lineno) => Some(lineno),
922                    Err(e) => {
923                        warn!("couldn't get lineno: {}", e);
924                        None
925                    },
926                }
927            })
928        }
929    )
930);
931
932macro_rules! get_stack_frame_2_3_0(
933    () => (
934        fn get_stack_frame<T>(
935            iseq_struct: &rb_iseq_struct,
936            cfp: &rb_control_frame_t,
937            source: &T,
938        ) -> Result<StackFrame> where T: ProcessMemory {
939            let body: rb_iseq_constant_body = source.copy_struct(iseq_struct.body as usize)
940                .context(iseq_struct.body as usize)?;
941            Ok(StackFrame{
942                name: get_ruby_string(body.location.label as usize, source)?,
943                relative_path: get_ruby_string(body.location.path as usize, source)?,
944                absolute_path: Some(get_ruby_string(body.location.absolute_path as usize, source)?),
945                lineno: match get_lineno(&body, cfp, source) {
946                    Ok(lineno) => Some(lineno),
947                    Err(e) => {
948                        warn!("couldn't get lineno: {}", e);
949                        None
950                    },
951                }
952            })
953        }
954    )
955);
956
957macro_rules! get_stack_frame_2_5_0(
958    () => (
959        fn get_stack_frame<T>(
960            iseq_struct: &rb_iseq_struct,
961            cfp: &rb_control_frame_t,
962            source: &T,
963        ) -> Result<StackFrame> where T: ProcessMemory {
964            if iseq_struct.body == std::ptr::null_mut() {
965                return Err(format_err!("iseq body is null"));
966            }
967            let body: rb_iseq_constant_body = source.copy_struct(iseq_struct.body as usize)
968                .context("couldn't copy rb_iseq_constant_body")?;
969            let rstring: RString = source.copy_struct(body.location.label as usize)
970                .context("couldn't copy RString")?;
971            let (path, absolute_path) = get_ruby_string_array(
972                body.location.pathobj as usize,
973                rstring.basic.klass as usize,
974                source
975            ).context("couldn't get ruby string from iseq body")?;
976            Ok(StackFrame{
977                name: get_ruby_string(body.location.label as usize, source)?,
978                relative_path: path,
979                absolute_path: Some(absolute_path),
980                lineno: match get_lineno(&body, cfp, source) {
981                    Ok(lineno) => Some(lineno),
982                    Err(e) => {
983                        warn!("couldn't get lineno: {}", e);
984                        None
985                    },
986                }
987            })
988        }
989    )
990);
991
992macro_rules! get_classpath_unsupported(
993    () => (
994        fn get_classpath<T>(
995            _cme: usize,
996            _cfunc: bool,
997            _source: &T,
998        ) -> Result<(String, bool)> where T: ProcessMemory {
999            return Err(format_err!("classpath resolution is not supported for this version of Ruby").into());
1000        }
1001    )
1002);
1003
1004macro_rules! rb_class_real_3_0_0(
1005    () => (
1006        fn rb_class_real<T>(
1007            klass: usize,
1008            source: &T,
1009        ) -> Result<usize> where T: ProcessMemory {
1010            const RUBY_T_ICLASS: usize = 0x1c;
1011            const RUBY_FL_SINGLETON: usize = ruby_fl_type_RUBY_FL_USER1 as usize;
1012
1013            // https://github.com/ruby/ruby/blob/v3_4_5/object.c#L290
1014            let mut klass = klass;
1015            let mut rbasic: RBasic = source.copy_struct(klass).context(klass)?;
1016            while (rbasic.flags & (RUBY_T_ICLASS | RUBY_FL_SINGLETON) != 0) {
1017                let rclass: RClass = source.copy_struct(klass).context(klass)?;
1018                klass = rclass.super_;
1019                rbasic = source.copy_struct(klass).context(klass)?;
1020            }
1021            Ok(klass)
1022        }
1023    )
1024);
1025
1026// As part of ruby 4.0.0 [0], the super field was moved from RClass [1] to rb_classext_struct [2].
1027//
1028// [0]: https://github.com/ruby/ruby/commit/382645d440d5da66a0c04557f3ff2ca226de3a27
1029// [1]: https://github.com/ruby/ruby/blob/7a5688e2a27668e48f8d6ff4af5b2208b98a2f5e/internal/class.h#L82
1030// [2]: https://github.com/ruby/ruby/blob/b49ff7cc700ffdba26fabaaf8167eee189797edf/internal/class.h#L81
1031macro_rules! rb_class_real_4_0_0(
1032    () => (
1033        fn rb_class_real<T>(
1034            klass: usize,
1035            source: &T,
1036        ) -> Result<usize> where T: ProcessMemory {
1037            const RUBY_T_ICLASS: usize = 0x1c;
1038            const RUBY_FL_SINGLETON: usize = ruby_fl_type_RUBY_FL_USER1 as usize;
1039
1040            // https://github.com/ruby/ruby/blob/v3_4_5/object.c#L290
1041            let mut klass = klass;
1042            let mut rbasic: RBasic = source.copy_struct(klass).context(klass)?;
1043            while (rbasic.flags & (RUBY_T_ICLASS | RUBY_FL_SINGLETON) != 0) {
1044                let rclass_ext: RClass_and_rb_classext_t = source.copy_struct(klass).context(klass)?;
1045                klass = rclass_ext.classext.super_;
1046                rbasic = source.copy_struct(klass).context(klass)?;
1047            }
1048            Ok(klass)
1049        }
1050    )
1051);
1052
1053macro_rules! get_classpath(
1054    () => (
1055        // https://github.com/ruby/ruby/blob/v3_3_0/variable.c#L260
1056        fn rb_tmp_class_path<T>(
1057            klass: usize,
1058            source: &T,
1059        ) -> Result<usize> where T: ProcessMemory {
1060            const RUBY_T_MODULE: usize = 0x3;
1061            // https://github.com/ruby/ruby/blob/c149708018135595b2c19c5f74baf9475674f394/include/ruby/internal/value_type.h#L142
1062            const RUBY_T_MASK: usize = 0x1f;
1063
1064            let ext: RClass_and_rb_classext_t = source.copy_struct(klass).context(klass)?;
1065            let mut path_addr = if ext.classext.classpath == 0 {
1066                0x0f // Qnil
1067            } else {
1068                ext.classext.classpath
1069            };
1070
1071            let rbasic: RBasic = source.copy_struct(klass).context(klass)?;
1072            if rbasic.flags & RUBY_T_MASK == RUBY_T_MODULE {
1073                let real_klass = rb_class_real(rbasic.klass, source)?;
1074                // https://github.com/ruby/ruby/blob/v3_4_5/variable.c#L359
1075                let ext: RClass_and_rb_classext_t = source.copy_struct(real_klass).context(real_klass)?;
1076                path_addr = if ext.classext.classpath != 0 {
1077                    let modstr = get_ruby_string(ext.classext.classpath as usize, source)?;
1078                    // Looking up the global symbol is not needed
1079                    // we can just compare the string value, albeit less efficient
1080                    if modstr == "Module".to_string() {
1081                        0x00 // QFalse
1082                    } else {
1083                        rb_tmp_class_path(rbasic.klass, source)?
1084                    }
1085                } else {
1086                    rb_tmp_class_path(rbasic.klass, source)?
1087                }
1088            }
1089            return Ok(path_addr);
1090        }
1091
1092        // https://github.com/ruby/ruby/blob/v3_3_0/variable.c#L283
1093        // note the logic of https://github.com/ruby/ruby/blob/v3_3_0/variable.c#L239
1094        // is folded in here, and the base case of the returning classpath
1095        // early if it is a valid string is also moved here
1096        fn rb_class_path<T>(
1097            klass: usize,
1098            source: &T,
1099        ) -> Result<String> where T: ProcessMemory {
1100
1101            let ext: RClass_and_rb_classext_t = source.copy_struct(klass).context(klass)?;
1102            if ext.classext.classpath != 0 {
1103                return get_ruby_string(ext.classext.classpath as usize, source);
1104            };
1105
1106            let path = rb_tmp_class_path(klass, source)?;
1107            let path_fallback = match path {
1108                // Qnil
1109                0x0f => {
1110                    format!("#<Class:{:#08x}>", klass)
1111                }
1112                // QFalse
1113                0x00 => {
1114                    format!("#<Module:{:#08x}>", klass)
1115                }
1116                _ => {
1117                    let path_str = get_ruby_string(path, source)?;
1118                    format!("#<{}:{:#08x}>", path_str, klass)
1119                }
1120            };
1121
1122            return Ok(path_fallback)
1123        }
1124
1125        // Tries to copy https://github.com/ruby/ruby/blob/v3_3_0/vm_backtrace.c#L1772
1126        fn get_classpath<T>(
1127            cme: usize,
1128            cfunc: bool,
1129            source: &T,
1130        ) -> Result<(String, bool)> where T: ProcessMemory {
1131            //https://github.com/ruby/ruby/blob/c149708018135595b2c19c5f74baf9475674f394/include/ruby/internal/value_type.h#L114¬
1132            const RUBY_T_CLASS: usize = 0x2;
1133            // https://github.com/ruby/ruby/blob/c149708018135595b2c19c5f74baf9475674f394/include/ruby/internal/value_type.h#L115C5-L115C74¬
1134            const RUBY_T_MODULE: usize = 0x3;
1135            //https://github.com/ruby/ruby/blob/c149708018135595b2c19c5f74baf9475674f394/include/ruby/internal/value_type.h#L138¬
1136            const RUBY_T_ICLASS: usize = 0x1c;
1137            // https://github.com/ruby/ruby/blob/c149708018135595b2c19c5f74baf9475674f394/include/ruby/internal/value_type.h#L142¬
1138            const RUBY_T_MASK: usize = 0x1f;
1139
1140            const RUBY_FL_SINGLETON: usize = ruby_fl_type_RUBY_FL_USER1 as usize;
1141
1142            // https://github.com/ruby/ruby/blob/7089a4e2d83a3cb1bc394c4ce3638cbc777f4cb9/include/ruby/internal/special_consts.h#L102
1143            const RUBY_IMMEDIATE_MASK: usize = 0x07;
1144
1145            const RUBY_QNIL: usize = 0x04;
1146
1147            let mut singleton = false;
1148
1149            let imemo: rb_method_entry_struct = source.copy_struct(cme).context(cme)?;
1150            // Read the class structure to get flags
1151            let mut klass = if cfunc {
1152                imemo.owner
1153            } else {
1154                imemo.defined_class
1155            };
1156            if klass == RUBY_QNIL {
1157                return Ok(("".to_string(), false))
1158            }
1159            let rbasic: RBasic = source.copy_struct(klass).context(klass)?;
1160
1161            let class_flags = rbasic.flags;
1162            let class_mask = class_flags & RUBY_T_MASK;
1163
1164            match class_mask {
1165                RUBY_T_ICLASS => {
1166                    // For iclass, get the classpath from the klass field
1167                    klass = rbasic.klass as usize;
1168                }
1169                _ => {
1170                    // Check if it's a singleton class
1171                    if class_flags & RUBY_FL_SINGLETON != 0 {
1172                        log::debug!("Got singleton class");
1173                        singleton = true;
1174
1175                        let ext: RClass_and_rb_classext_t = source.copy_struct(klass).context(klass)?;
1176                        klass = unsafe {
1177                            ext.classext.as_.singleton_class.attached_object as usize
1178                        };
1179
1180                        let rbasic: RBasic = source.copy_struct(klass).context(klass)?;
1181                        let class_flags = rbasic.flags;
1182                        let class_mask = class_flags & RUBY_T_MASK;
1183                        if class_mask != RUBY_T_CLASS  && class_mask != RUBY_T_MODULE {
1184                            if (klass & RUBY_IMMEDIATE_MASK == 0) {
1185                                klass = rb_class_real(rbasic.klass, source)?;
1186                                let ext: RClass_and_rb_classext_t = source.copy_struct(klass).context(klass)?;
1187                                let class_path = get_ruby_string(ext.classext.classpath as usize, source)?;
1188                                return Ok((format!("#<{}:{:#08x}>", class_path, klass), singleton));
1189                            } else {
1190                                log::debug!("TODO: Immediate object case is unhandled!");
1191                            }
1192                        }
1193                    }
1194                }
1195            }
1196
1197            if klass == 0 {
1198                return Err(anyhow::anyhow!("klass was empty"));
1199            }
1200
1201            let class_path = rb_class_path(klass, source)?;
1202            Ok((class_path, singleton))
1203        }
1204
1205        // https://github.com/ruby/ruby/blob/v3_3_0/vm_backtrace.c#L1841
1206        pub fn profile_frame_full_label(
1207            class_path: &str,
1208            label: &str,
1209            base_label: &str,
1210            method_name: &str,
1211            singleton: bool,
1212        ) -> String {
1213            let qualified = qualified_method_name(class_path, method_name, singleton);
1214
1215            if qualified.is_empty() || qualified == base_label.to_string() {
1216                return label.to_string();
1217            }
1218
1219            let label_length = label.len();
1220            let base_label_length = base_label.len();
1221            let mut prefix_len = label_length.saturating_sub(base_label_length);
1222
1223            // Ensure prefix_len doesn't exceed label length (defensive programming)
1224            // Note: saturating_sub above already handles the < 0 case
1225            if prefix_len > label_length {
1226                prefix_len = label_length;
1227            }
1228
1229            let profile_label = format!("{}{}", &label[..prefix_len], qualified);
1230
1231            if profile_label.is_empty() {
1232                return String::new();
1233            }
1234
1235            // Get the prefix from label and concatenate with qualified_method_name
1236            profile_label
1237        }
1238    )
1239);
1240
1241macro_rules! get_stack_frame_3_3_0(
1242    () => (
1243        fn get_stack_frame<T>(
1244            iseq_struct: &rb_iseq_struct,
1245            cfp: &rb_control_frame_t,
1246            source: &T,
1247        ) -> Result<StackFrame> where T: ProcessMemory {
1248            let cme = locate_method_entry(&cfp.ep, source)?;
1249            let mut class_path = "".to_string();
1250            let mut singleton = false;
1251            let iseq = if cme != 0 {
1252                let imemo: rb_method_entry_struct = source.copy_struct(cme).context(cme)?;
1253                let method_type = source.copy(imemo.def as usize, 1).context(imemo.def as usize)?;
1254                // https://github.com/ruby/ruby/blob/v3_3_0/internal/imemo.h#L21
1255                let method_type = method_type[0] & 0xf;
1256                // https://github.com/ruby/ruby/blob/v3_3_0/method.h#L110
1257                if method_type == 0 {
1258                    (class_path, singleton) = get_classpath(cme, false, source)?;
1259                    let method_def: rb_method_definition_struct = source.copy_struct(imemo.def as usize).context(imemo.def as usize)?;
1260                    let iseq: rb_iseq_struct = unsafe {
1261                        source.copy_struct(method_def.body.iseq.iseqptr as usize).context("")?
1262                    };
1263                    iseq
1264                } else {
1265                    *iseq_struct
1266                }
1267            } else {
1268                *iseq_struct
1269            };
1270            if iseq_struct.body == std::ptr::null_mut() {
1271                return Err(format_err!("iseq body is null"));
1272            }
1273            let body: rb_iseq_constant_body =
1274                source.copy_struct(iseq.body as usize)
1275                    .context("couldn't copy rb_iseq_constant_body")?;
1276            let frame_flag: usize = unsafe {
1277                source.copy_struct(cfp.ep.offset(0) as usize).context(cfp.ep.offset(0) as usize)?
1278            };
1279            let rstring: RString = source.copy_struct(body.location.label as usize)
1280                .context("couldn't copy RString")?;
1281
1282            let mut method_name = "".to_string();
1283            if body.local_iseq != std::ptr::null_mut() {
1284                // https://github.com/ruby/ruby/blob/v3_3_0/vm_backtrace.c#L1801 calls
1285                // https://github.com/ruby/ruby/blob/v3_3_0/iseq.c#L1234
1286                let local_iseq: rb_iseq_t = source.copy_struct(body.local_iseq as usize)
1287                    .context("couldn't read local iseq")?;
1288                if local_iseq.body != std::ptr::null_mut() {
1289                    let local_body: rb_iseq_constant_body = source.copy_struct(iseq_struct.body as usize)
1290                        .context("couldn't copy rb_iseq_constant_body")?;
1291                    method_name = get_ruby_string(local_body.location.base_label as usize, source)?;
1292                }
1293
1294            }
1295            let (path, absolute_path) = get_ruby_string_array(
1296                body.location.pathobj as usize,
1297                rstring.basic.klass as usize,
1298                source
1299            ).context(format!("couldn't get ruby path string array from iseq body: {:X}", frame_flag)).unwrap_or(("FAILED".to_string(), "FAILED".to_string()));
1300
1301            let label = get_ruby_string(body.location.label as usize, source)?;
1302            let base_label = get_ruby_string(body.location.base_label as usize, source)?;
1303            let full_label = profile_frame_full_label(&class_path, &label, &base_label, &method_name, singleton);
1304
1305            Ok(StackFrame{
1306                name: full_label,
1307                relative_path: path,
1308                absolute_path: Some(absolute_path),
1309                lineno: match get_lineno(&body, cfp, source) {
1310                    Ok(lineno) => Some(lineno),
1311                    Err(e) => {
1312                        warn!("couldn't get lineno: {}", e);
1313                        None
1314                    },
1315                }
1316            })
1317        }
1318    )
1319);
1320
1321macro_rules! get_pos(
1322    ($iseq_type:ident) => (
1323        #[allow(unused)] // this doesn't get used in every ruby version
1324        fn get_pos(iseq_struct: &$iseq_type, cfp: &rb_control_frame_t) -> Result<usize> {
1325            if (cfp.pc as usize) < (iseq_struct.iseq_encoded as usize) {
1326                return Err(crate::core::types::MemoryCopyError::Message(format!("program counter and iseq are out of sync")).into());
1327            }
1328            let mut pos = cfp.pc as usize - iseq_struct.iseq_encoded as usize;
1329            if pos != 0 {
1330                pos -= 1;
1331            }
1332            Ok(pos)
1333        }
1334    )
1335);
1336
1337macro_rules! get_lineno_1_9_0(
1338    () => (
1339        fn get_lineno<T>(
1340            iseq_struct: &rb_iseq_struct,
1341            cfp: &rb_control_frame_t,
1342            source: &T,
1343        ) -> Result<usize> where T: ProcessMemory {
1344            let pos = get_pos(iseq_struct, cfp)?;
1345            let t_size = iseq_struct.insn_info_size as usize;
1346            if t_size == 0 {
1347                Err(format_err!("line number is not available"))
1348            } else if t_size == 1 {
1349                let table: [iseq_insn_info_entry; 1] = source.copy_struct(iseq_struct.insn_info_table as usize)
1350                    .context("couldn't copy instruction table")?;
1351                Ok(table[0].line_no as usize)
1352            } else {
1353                let table: Vec<iseq_insn_info_entry> = source.copy_vec(iseq_struct.insn_info_table as usize, t_size as usize)
1354                    .context("couldn't copy instruction table")?;
1355                for i in 0..t_size {
1356                    if pos == table[i].position as usize {
1357                        return Ok(table[i].line_no as usize)
1358                    } else if table[i].position as usize > pos {
1359                        return Ok(table[i-1].line_no as usize)
1360                    }
1361                }
1362                Ok(table[t_size-1].line_no as usize)
1363            }
1364        }
1365    )
1366);
1367
1368macro_rules! get_lineno_2_0_0(
1369    () => (
1370        fn get_lineno<T>(
1371            iseq_struct: &rb_iseq_struct,
1372            cfp: &rb_control_frame_t,
1373            source: &T,
1374        ) -> Result<usize> where T: ProcessMemory {
1375            let pos = get_pos(iseq_struct, cfp)?;
1376            let t_size = iseq_struct.line_info_size as usize;
1377            if t_size == 0 {
1378                Err(format_err!("line number is not available"))
1379            } else if t_size == 1 {
1380                let table: [iseq_line_info_entry; 1] = source.copy_struct(iseq_struct.line_info_table as usize)
1381                    .context("couldn't copy instruction table")?;
1382                Ok(table[0].line_no as usize)
1383            } else {
1384                let table: Vec<iseq_line_info_entry> = source.copy_vec(iseq_struct.line_info_table as usize, t_size as usize)
1385                    .context("couldn't copy instruction table")?;
1386                for i in 0..t_size {
1387                    if pos == table[i].position as usize {
1388                        return Ok(table[i].line_no as usize)
1389                    } else if table[i].position as usize > pos {
1390                        return Ok(table[i-1].line_no as usize)
1391                    }
1392                }
1393                Ok(table[t_size-1].line_no as usize)
1394            }
1395        }
1396    )
1397);
1398
1399macro_rules! get_lineno_2_3_0(
1400    () => (
1401        fn get_lineno<T>(
1402            iseq_struct: &rb_iseq_constant_body,
1403            cfp: &rb_control_frame_t,
1404            source: &T,
1405        ) -> Result<usize> where T: ProcessMemory {
1406            let pos = get_pos(iseq_struct, cfp)?;
1407            let t_size = iseq_struct.line_info_size as usize;
1408            if t_size == 0 {
1409                Err(format_err!("line number is not available"))
1410            } else if t_size == 1 {
1411                let table: [iseq_line_info_entry; 1] = source.copy_struct(iseq_struct.line_info_table as usize)
1412                    .context("couldn't copy instruction table")?;
1413                Ok(table[0].line_no as usize)
1414            } else {
1415                let table: Vec<iseq_line_info_entry> = source.copy_vec(iseq_struct.line_info_table as usize, t_size as usize)
1416                    .context("couldn't copy instruction table")?;
1417                for i in 0..t_size {
1418                    if pos == table[i].position as usize {
1419                        return Ok(table[i].line_no as usize)
1420                    } else if table[i].position as usize > pos {
1421                        return Ok(table[i-1].line_no as usize)
1422                    }
1423                }
1424                Ok(table[t_size-1].line_no as usize)
1425            }
1426        }
1427    )
1428);
1429
1430macro_rules! get_lineno_2_5_0(
1431    () => (
1432        fn get_lineno<T>(
1433            iseq_struct: &rb_iseq_constant_body,
1434            cfp: &rb_control_frame_t,
1435            source: &T,
1436        ) -> Result<usize> where T: ProcessMemory {
1437            let pos = get_pos(iseq_struct, cfp)?;
1438            let t_size = iseq_struct.insns_info_size as usize;
1439            if t_size == 0 {
1440                Err(format_err!("line number is not available"))
1441            } else if t_size == 1 {
1442                let table: [iseq_insn_info_entry; 1] = source.copy_struct(iseq_struct.insns_info as usize)
1443                    .context("couldn't copy instruction table")?;
1444                Ok(table[0].line_no as usize)
1445            } else {
1446                let table: Vec<iseq_insn_info_entry> = source.copy_vec(iseq_struct.insns_info as usize, t_size as usize)
1447                    .context("couldn't copy instruction table")?;
1448                for i in 0..t_size {
1449                    if pos == table[i].position as usize {
1450                        return Ok(table[i].line_no as usize)
1451                    } else if table[i].position as usize > pos {
1452                        return Ok(table[i-1].line_no as usize)
1453                    }
1454                }
1455                Ok(table[t_size-1].line_no as usize)
1456            }
1457        }
1458    )
1459);
1460
1461macro_rules! get_lineno_2_6_0(
1462    () => (
1463        fn get_lineno<T>(
1464            iseq_struct: &rb_iseq_constant_body,
1465            _cfp: &rb_control_frame_t,
1466            source: &T,
1467        ) -> Result<usize> where T: ProcessMemory {
1468            let t_size = iseq_struct.insns_info.size as usize;
1469            if t_size == 0 {
1470                Err(format_err!("line number is not available"))
1471            } else if t_size == 1 {
1472                let table: [iseq_insn_info_entry; 1] = source.copy_struct(iseq_struct.insns_info.body as usize)
1473                    .context("couldn't copy instruction table")?;
1474                Ok(table[0].line_no as usize)
1475            } else {
1476                // TODO: To handle this properly, we need to imitate ruby's succinct bit vector lookup.
1477                // See https://github.com/rbspy/rbspy/issues/213#issuecomment-826363857
1478                let table: Vec<iseq_insn_info_entry> = source.copy_vec(iseq_struct.insns_info.body as usize, t_size as usize)
1479                    .context(iseq_struct.insns_info.body as usize)?;
1480                Ok(table[t_size-1].line_no as usize)
1481            }
1482        }
1483    )
1484);
1485
1486macro_rules! get_cfps(
1487    () => (
1488        fn get_cfps<T>(
1489            cfp_address: usize,
1490            stack_base: usize,
1491            source: &T
1492        ) -> Result<Vec<rb_control_frame_t>> where T: ProcessMemory {
1493            // If we fail these safety checks, it probably means we've hit some kind of
1494            // race condition. Return an error so that we can try again.
1495            if (stack_base as usize) <= cfp_address {
1496                return Err(crate::core::types::MemoryCopyError::Message(format!("stack base and cfp address out of sync. stack base: {:x}, cfp address: {:x}", stack_base as usize, cfp_address)).into());
1497            }
1498            let cfp_size = (stack_base as usize - cfp_address) as usize / std::mem::size_of::<rb_control_frame_t>();
1499            if cfp_size > 1_000_000 {
1500                return Err(crate::core::types::MemoryCopyError::Message(format!("invalid cfp vector length: {}", cfp_size)).into());
1501            }
1502
1503            source.copy_vec(cfp_address, cfp_size).context("couldn't copy cfp vector")
1504        }
1505    )
1506);
1507
1508macro_rules! get_cfunc_name_unsupported(
1509    () => (
1510        fn ruby_frame_cfunc<T>(
1511            _cfp: &rb_control_frame_t,
1512            _source: &T
1513        ) -> Result<bool> where T: ProcessMemory {
1514            Ok(false)
1515        }
1516
1517        fn get_cfunc_name<T: ProcessMemory>(_cfp: &rb_control_frame_t, _global_symbols_address: usize, _source: &T, _pid: Pid) -> Result<String> {
1518            return Err(format_err!("C function resolution is not supported for this version of Ruby").into());
1519        }
1520    )
1521);
1522
1523macro_rules! get_cfunc_name(
1524    () => (
1525
1526        fn ruby_frame_cfunc<T>(
1527            cfp: &rb_control_frame_t,
1528            source: &T
1529        ) -> Result<bool> where T: ProcessMemory {
1530            let frame_flag: usize = unsafe {
1531                source.copy_struct(cfp.ep.offset(0) as usize).context(cfp.ep.offset(0) as usize)?
1532            };
1533            const VM_FRAME_MAGIC_CFUNC: usize = 0x55550001;
1534            const VM_FRAME_MAGIC_MASK: usize = 0x7fff0001;
1535
1536            Ok((frame_flag & VM_FRAME_MAGIC_MASK) == VM_FRAME_MAGIC_CFUNC)
1537        }
1538
1539        // https://github.com/ruby/ruby/blob/v3_3_0/vm_backtrace.c#L1813
1540        pub fn qualified_method_name(class_path: &str, method_name: &str, singleton: bool) -> String {
1541            if method_name.is_empty() {
1542                return method_name.to_string();
1543            }
1544
1545            if !class_path.is_empty() {
1546                let join_char = if singleton { "." } else { "#" };
1547                return format!("{}{}{}", class_path, join_char, method_name);
1548            }
1549
1550            method_name.to_string()
1551        }
1552
1553        // tries to mimic rb_vm_frame_method_entry
1554        // https://github.com/ruby/ruby/blob/v3_3_0/vm_insnhelper.c#L733
1555        fn locate_method_entry<T>(
1556            ep: &*const usize,
1557            source: &T,
1558        ) -> Result<usize> where T: ProcessMemory {
1559            const VM_ENV_FLAG_LOCAL: usize = 0x2;
1560            let mut ep = ep.clone() as *mut usize;
1561            let mut env_flags: usize = source.copy_struct(ep as usize).context(ep as usize)?;
1562            let mut env_specval: usize = unsafe {
1563                source.copy_struct(ep.offset(-1) as usize).context(ep.offset(-1) as usize)?
1564            };
1565            let mut env_me_cref: usize = unsafe {
1566                source.copy_struct(ep.offset(-2) as usize).context(ep.offset(-2) as usize)?
1567            };
1568
1569            while env_flags & VM_ENV_FLAG_LOCAL != 0 {
1570                let me = check_method_entry(env_me_cref, false, source)?;
1571                if me != 0{
1572                    return Ok(me);
1573                }
1574                unsafe {
1575                    // https://github.com/ruby/ruby/blob/v3_4_5/vm_core.h#L1356
1576                    // we must strip off the GC marking bits from the EP, and mimic
1577                    // https://github.com/ruby/ruby/blob/v3_4_5/vm_core.h#L1501
1578                    ep = (env_specval.clone() & !0x03) as *mut usize;
1579                    env_flags = source.copy_struct(ep as usize).context(ep as usize)?;
1580                    env_specval = source.copy_struct(ep.offset(-1) as usize).context(ep.offset(-1) as usize)?;
1581                    env_me_cref = source.copy_struct(ep.offset(-2) as usize).context(ep.offset(-2) as usize)?;
1582                }
1583            }
1584
1585            let me = check_method_entry(env_me_cref, true, source)?;
1586            Ok(me)
1587        }
1588
1589        // tries to mimic https://github.com/ruby/ruby/blob/v3_3_0/vm_insnhelper.c#L707
1590        fn check_method_entry<T: ProcessMemory>(
1591            raw_imemo: usize,
1592            can_be_svar: bool,
1593            source: &T
1594        ) -> Result<usize> {
1595            //https://github.com/ruby/ruby/blob/v3_4_5/internal/imemo.h#L21
1596            const IMEMO_MASK: usize = 0x0f;
1597            if raw_imemo == 0 {
1598                return Ok(0);
1599            }
1600            let imemo: rb_method_entry_struct = source.copy_struct(raw_imemo).context(raw_imemo)?;
1601
1602            // These type constants are defined in ruby's internal/imemo.h
1603            #[allow(non_upper_case_globals)]
1604            match ((imemo.flags >> ruby_fl_type_RUBY_FL_USHIFT) & IMEMO_MASK) as u32 {
1605                imemo_type_imemo_ment => Ok(raw_imemo),
1606                imemo_type_imemo_svar => {
1607                    if can_be_svar {
1608                        let svar: vm_svar = source.copy_struct(raw_imemo).context(raw_imemo)?;
1609                        check_method_entry(svar.cref_or_me as usize, false, source)
1610                    } else {
1611                        Ok(0)
1612                    }
1613                },
1614                _ => Ok(0)
1615            }
1616        }
1617
1618        // we should read that here and return the qualified name
1619        fn get_cfunc_name<T: ProcessMemory>(
1620            cfp: &rb_control_frame_t,
1621            global_symbols_address: usize,
1622            source: &T,
1623            _pid: Pid
1624        ) -> Result<String> {
1625            const IMEMO_MASK: usize = 0x0f;
1626
1627            let cme = locate_method_entry(&cfp.ep, source)?;
1628            let (class_path, singleton) = get_classpath(cme, true, source).unwrap_or(("".to_string(), false));
1629
1630            let imemo: rb_method_entry_struct = source.copy_struct(cme).context(cme)?;
1631            if imemo.def.is_null() {
1632                return Err(format_err!("No method definition").into());
1633            }
1634
1635
1636            let ttype = ((imemo.flags >> ruby_fl_type_RUBY_FL_USHIFT) & IMEMO_MASK) as usize;
1637            if ttype != imemo_type_imemo_ment as usize {
1638                return Err(format_err!("Not a method entry").into());
1639            }
1640
1641            #[allow(non_camel_case_types)]
1642            type rb_id_serial_t = u32;
1643
1644            // Declared in symbol.c prior to ruby 2.7.0, so not accessible by bindgen
1645            #[allow(non_camel_case_types)]
1646            #[repr(C)]
1647            #[derive(Debug, Copy, Clone)]
1648            struct rb_symbols_t {
1649                last_id: rb_id_serial_t,
1650                str_sym: *mut st_table,
1651                ids: VALUE,
1652                dsymbol_fstr_hash: VALUE,
1653            }
1654
1655            let global_symbols: rb_symbols_t = source.copy_struct(global_symbols_address as usize).context(global_symbols_address as usize)?;
1656            let def: rb_method_definition_struct = source.copy_struct(imemo.def as usize).context(imemo.def as usize)?;
1657            let method_id = def.original_id as usize;
1658
1659            // rb_id_to_serial
1660            let mut serial = method_id;
1661            if method_id > ruby_method_ids_tLAST_OP_ID as usize {
1662                serial = method_id >> ruby_id_types_RUBY_ID_SCOPE_SHIFT;
1663            }
1664
1665            if serial > global_symbols.last_id as usize {
1666                return Err(format_err!("Invalid method ID").into());
1667            }
1668
1669            // ID_ENTRY_UNIT is defined in symbol.c, so not accessible by bindgen
1670            let id_entry_unit = 512;
1671            let idx = serial / id_entry_unit;
1672            let ids: RArray = source.copy_struct(global_symbols.ids as usize).context(global_symbols.ids as usize)?;
1673            let flags = ids.basic.flags as usize;
1674
1675            // string2cstring
1676            let mut ids_ptr = unsafe { ids.as_.heap.ptr as usize };
1677            let mut ids_len = unsafe { ids.as_.heap.len as usize };
1678            if (flags & ruby_fl_type_RUBY_FL_USER1 as usize) > 0 {
1679                ids_ptr = unsafe { ids.as_.ary[0] as usize };
1680                ids_len = (flags & (ruby_fl_type_RUBY_FL_USER3|ruby_fl_type_RUBY_FL_USER4|ruby_fl_type_RUBY_FL_USER5|ruby_fl_type_RUBY_FL_USER6|ruby_fl_type_RUBY_FL_USER7|ruby_fl_type_RUBY_FL_USER8|ruby_fl_type_RUBY_FL_USER9) as usize) >> (ruby_fl_type_RUBY_FL_USHIFT+3);
1681            }
1682            if idx >= ids_len {
1683                return Err(format_err!("Invalid index in IDs array").into());
1684            }
1685
1686            // ids is an array of pointers to RArray. First jump to the right index to get the
1687            // pointer, then copy the _pointer_ into our memory space, and then finally copy the
1688            // pointed-to array into our memory space
1689            let array_remote_ptr = (ids_ptr as usize) + (idx as usize) * std::mem::size_of::<usize>();
1690            let array_ptr: usize = source.copy_struct(array_remote_ptr).context(array_remote_ptr)?;
1691            let array: RArray = source.copy_struct(array_ptr).context(array_ptr)?;
1692
1693            let mut array_ptr = unsafe { array.as_.heap.ptr };
1694            let flags = array.basic.flags as usize;
1695            if (flags & ruby_fl_type_RUBY_FL_USER1 as usize) > 0 {
1696                array_ptr = unsafe { &ids.as_.ary[0] };
1697            }
1698
1699            let offset = (serial % 512) * 2;
1700            let rstring_remote_ptr = (array_ptr as usize) + offset * std::mem::size_of::<usize>();
1701            let rstring_ptr: usize = source.copy_struct(rstring_remote_ptr as usize).context(rstring_remote_ptr as usize)?;
1702            let method_name = get_ruby_string(rstring_ptr as usize, source)?;
1703            Ok(qualified_method_name(&class_path, &method_name, singleton))
1704        }
1705    )
1706);
1707
1708ruby_version_v_1_9_1!(ruby_1_9_1_0);
1709ruby_version_v_1_9_2_to_3!(ruby_1_9_2_0);
1710ruby_version_v_1_9_2_to_3!(ruby_1_9_3_0);
1711ruby_version_v_2_0_to_2_2!(ruby_2_0_0_0);
1712ruby_version_v_2_0_to_2_2!(ruby_2_1_0);
1713ruby_version_v_2_0_to_2_2!(ruby_2_1_1);
1714ruby_version_v_2_0_to_2_2!(ruby_2_1_2);
1715ruby_version_v_2_0_to_2_2!(ruby_2_1_3);
1716ruby_version_v_2_0_to_2_2!(ruby_2_1_4);
1717ruby_version_v_2_0_to_2_2!(ruby_2_1_5);
1718ruby_version_v_2_0_to_2_2!(ruby_2_1_6);
1719ruby_version_v_2_0_to_2_2!(ruby_2_1_7);
1720ruby_version_v_2_0_to_2_2!(ruby_2_1_8);
1721ruby_version_v_2_0_to_2_2!(ruby_2_1_9);
1722ruby_version_v_2_0_to_2_2!(ruby_2_1_10);
1723ruby_version_v_2_0_to_2_2!(ruby_2_2_0);
1724ruby_version_v_2_0_to_2_2!(ruby_2_2_1);
1725ruby_version_v_2_0_to_2_2!(ruby_2_2_2);
1726ruby_version_v_2_0_to_2_2!(ruby_2_2_3);
1727ruby_version_v_2_0_to_2_2!(ruby_2_2_4);
1728ruby_version_v_2_0_to_2_2!(ruby_2_2_5);
1729ruby_version_v_2_0_to_2_2!(ruby_2_2_6);
1730ruby_version_v_2_0_to_2_2!(ruby_2_2_7);
1731ruby_version_v_2_0_to_2_2!(ruby_2_2_8);
1732ruby_version_v_2_0_to_2_2!(ruby_2_2_9);
1733ruby_version_v_2_0_to_2_2!(ruby_2_2_10);
1734ruby_version_v_2_3_to_2_4!(ruby_2_3_0);
1735ruby_version_v_2_3_to_2_4!(ruby_2_3_1);
1736ruby_version_v_2_3_to_2_4!(ruby_2_3_2);
1737ruby_version_v_2_3_to_2_4!(ruby_2_3_3);
1738ruby_version_v_2_3_to_2_4!(ruby_2_3_4);
1739ruby_version_v_2_3_to_2_4!(ruby_2_3_5);
1740ruby_version_v_2_3_to_2_4!(ruby_2_3_6);
1741ruby_version_v_2_3_to_2_4!(ruby_2_3_7);
1742ruby_version_v_2_3_to_2_4!(ruby_2_3_8);
1743ruby_version_v_2_3_to_2_4!(ruby_2_4_0);
1744ruby_version_v_2_3_to_2_4!(ruby_2_4_1);
1745ruby_version_v_2_3_to_2_4!(ruby_2_4_2);
1746ruby_version_v_2_3_to_2_4!(ruby_2_4_3);
1747ruby_version_v_2_3_to_2_4!(ruby_2_4_4);
1748ruby_version_v_2_3_to_2_4!(ruby_2_4_5);
1749ruby_version_v_2_3_to_2_4!(ruby_2_4_6);
1750ruby_version_v_2_3_to_2_4!(ruby_2_4_7);
1751ruby_version_v_2_3_to_2_4!(ruby_2_4_8);
1752ruby_version_v_2_3_to_2_4!(ruby_2_4_9);
1753ruby_version_v_2_3_to_2_4!(ruby_2_4_10);
1754ruby_version_v2_5_x!(ruby_2_5_0);
1755ruby_version_v2_5_x!(ruby_2_5_1);
1756ruby_version_v2_5_x!(ruby_2_5_2);
1757ruby_version_v2_5_x!(ruby_2_5_3);
1758ruby_version_v2_5_x!(ruby_2_5_4);
1759ruby_version_v2_5_x!(ruby_2_5_5);
1760ruby_version_v2_5_x!(ruby_2_5_6);
1761ruby_version_v2_5_x!(ruby_2_5_7);
1762ruby_version_v2_5_x!(ruby_2_5_8);
1763ruby_version_v2_5_x!(ruby_2_5_9);
1764ruby_version_v2_6_x!(ruby_2_6_0);
1765ruby_version_v2_6_x!(ruby_2_6_1);
1766ruby_version_v2_6_x!(ruby_2_6_2);
1767ruby_version_v2_6_x!(ruby_2_6_3);
1768ruby_version_v2_6_x!(ruby_2_6_4);
1769ruby_version_v2_6_x!(ruby_2_6_5);
1770ruby_version_v2_6_x!(ruby_2_6_6);
1771ruby_version_v2_6_x!(ruby_2_6_7);
1772ruby_version_v2_6_x!(ruby_2_6_8);
1773ruby_version_v2_6_x!(ruby_2_6_9);
1774ruby_version_v2_6_x!(ruby_2_6_10);
1775ruby_version_v2_7_x!(ruby_2_7_0);
1776ruby_version_v2_7_x!(ruby_2_7_1);
1777ruby_version_v2_7_x!(ruby_2_7_2);
1778ruby_version_v2_7_x!(ruby_2_7_3);
1779ruby_version_v2_7_x!(ruby_2_7_4);
1780ruby_version_v2_7_x!(ruby_2_7_5);
1781ruby_version_v2_7_x!(ruby_2_7_6);
1782ruby_version_v2_7_x!(ruby_2_7_7);
1783ruby_version_v2_7_x!(ruby_2_7_8);
1784ruby_version_v3_0_x!(ruby_3_0_0);
1785ruby_version_v3_0_x!(ruby_3_0_1);
1786ruby_version_v3_0_x!(ruby_3_0_2);
1787ruby_version_v3_0_x!(ruby_3_0_3);
1788ruby_version_v3_0_x!(ruby_3_0_4);
1789ruby_version_v3_0_x!(ruby_3_0_5);
1790ruby_version_v3_0_x!(ruby_3_0_6);
1791ruby_version_v3_0_x!(ruby_3_0_7);
1792ruby_version_v3_1_x!(ruby_3_1_0);
1793ruby_version_v3_1_x!(ruby_3_1_1);
1794ruby_version_v3_1_x!(ruby_3_1_2);
1795ruby_version_v3_1_x!(ruby_3_1_3);
1796ruby_version_v3_1_x!(ruby_3_1_4);
1797ruby_version_v3_1_x!(ruby_3_1_5);
1798ruby_version_v3_1_x!(ruby_3_1_6);
1799ruby_version_v3_1_x!(ruby_3_1_7);
1800ruby_version_v3_2_x!(ruby_3_2_0);
1801ruby_version_v3_2_x!(ruby_3_2_1);
1802ruby_version_v3_2_x!(ruby_3_2_2);
1803ruby_version_v3_2_x!(ruby_3_2_3);
1804ruby_version_v3_2_x!(ruby_3_2_4);
1805ruby_version_v3_2_x!(ruby_3_2_5);
1806ruby_version_v3_2_x!(ruby_3_2_6);
1807ruby_version_v3_2_x!(ruby_3_2_7);
1808ruby_version_v3_2_x!(ruby_3_2_8);
1809ruby_version_v3_2_x!(ruby_3_2_9);
1810ruby_version_v3_2_x!(ruby_3_2_10);
1811ruby_version_v3_2_x!(ruby_3_2_11);
1812ruby_version_v3_3_x!(ruby_3_3_0);
1813ruby_version_v3_3_x!(ruby_3_3_1);
1814ruby_version_v3_3_x!(ruby_3_3_2);
1815ruby_version_v3_3_x!(ruby_3_3_3);
1816ruby_version_v3_3_x!(ruby_3_3_4);
1817ruby_version_v3_3_x!(ruby_3_3_5);
1818ruby_version_v3_3_x!(ruby_3_3_6);
1819ruby_version_v3_3_x!(ruby_3_3_7);
1820ruby_version_v3_3_x!(ruby_3_3_8);
1821ruby_version_v3_3_x!(ruby_3_3_9);
1822ruby_version_v3_3_x!(ruby_3_3_10);
1823ruby_version_v3_3_x!(ruby_3_3_11);
1824ruby_version_v3_3_x!(ruby_3_4_0);
1825ruby_version_v3_3_x!(ruby_3_4_1);
1826ruby_version_v3_3_x!(ruby_3_4_2);
1827ruby_version_v3_3_x!(ruby_3_4_3);
1828ruby_version_v3_3_x!(ruby_3_4_4);
1829ruby_version_v3_3_x!(ruby_3_4_5);
1830ruby_version_v3_3_x!(ruby_3_4_6);
1831ruby_version_v3_3_x!(ruby_3_4_7);
1832ruby_version_v3_3_x!(ruby_3_4_8);
1833ruby_version_v3_3_x!(ruby_3_4_9);
1834ruby_version_v4_0_x!(ruby_4_0_0);
1835ruby_version_v4_0_x!(ruby_4_0_1);
1836ruby_version_v4_0_x!(ruby_4_0_2);
1837
1838#[cfg(not(debug_assertions))]
1839#[cfg(test)]
1840mod tests {
1841    use rbspy_testdata::*;
1842    use rstest::rstest;
1843
1844    use crate::core::ruby_version;
1845    use crate::core::types::StackFrame;
1846
1847    fn real_stack_trace_1_9_3() -> Vec<StackFrame> {
1848        vec![
1849            StackFrame::unknown_c_function(),
1850            StackFrame {
1851                name: "aaa".to_string(),
1852                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1853                absolute_path: Some(
1854                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
1855                ),
1856                lineno: Some(2),
1857            },
1858            StackFrame {
1859                name: "bbb".to_string(),
1860                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1861                absolute_path: Some(
1862                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
1863                ),
1864                lineno: Some(6),
1865            },
1866            StackFrame {
1867                name: "ccc".to_string(),
1868                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1869                absolute_path: Some(
1870                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
1871                ),
1872                lineno: Some(10),
1873            },
1874            StackFrame {
1875                name: "block in <main>".to_string(),
1876                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1877                absolute_path: Some(
1878                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
1879                ),
1880                lineno: Some(14),
1881            },
1882            StackFrame::unknown_c_function(),
1883            StackFrame {
1884                name: "<main>".to_string(),
1885                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1886                absolute_path: Some(
1887                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
1888                ),
1889                lineno: Some(13),
1890            },
1891        ]
1892    }
1893
1894    fn real_stack_trace_2_7_2() -> Vec<StackFrame> {
1895        vec![
1896            StackFrame {
1897                name: "sleep [c function]".to_string(),
1898                relative_path: "(unknown)".to_string(),
1899                absolute_path: None,
1900                lineno: None,
1901            },
1902            StackFrame {
1903                name: "aaa".to_string(),
1904                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1905                absolute_path: Some("/vagrant/ci/ruby-programs/infinite.rb".to_string()),
1906                lineno: Some(3),
1907            },
1908            StackFrame {
1909                name: "bbb".to_string(),
1910                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1911                absolute_path: Some("/vagrant/ci/ruby-programs/infinite.rb".to_string()),
1912                lineno: Some(7),
1913            },
1914            StackFrame {
1915                name: "ccc".to_string(),
1916                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1917                absolute_path: Some("/vagrant/ci/ruby-programs/infinite.rb".to_string()),
1918                lineno: Some(11),
1919            },
1920            StackFrame {
1921                name: "block in <main>".to_string(),
1922                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1923                absolute_path: Some("/vagrant/ci/ruby-programs/infinite.rb".to_string()),
1924                lineno: Some(15),
1925            },
1926            StackFrame {
1927                name: "loop [c function]".to_string(),
1928                relative_path: "(unknown)".to_string(),
1929                absolute_path: None,
1930                lineno: None,
1931            },
1932        ]
1933    }
1934
1935    fn real_stack_trace_3_1_0() -> Vec<StackFrame> {
1936        vec![
1937            StackFrame {
1938                name: "sleep [c function]".to_string(),
1939                relative_path: "(unknown)".to_string(),
1940                absolute_path: None,
1941                lineno: None,
1942            },
1943            StackFrame {
1944                name: "aaa".to_string(),
1945                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1946                absolute_path: Some(
1947                    "/home/acj/workspace/rbspy/ci/ruby-programs/infinite.rb".to_string(),
1948                ),
1949                lineno: Some(3),
1950            },
1951            StackFrame {
1952                name: "bbb".to_string(),
1953                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1954                absolute_path: Some(
1955                    "/home/acj/workspace/rbspy/ci/ruby-programs/infinite.rb".to_string(),
1956                ),
1957                lineno: Some(7),
1958            },
1959            StackFrame {
1960                name: "ccc".to_string(),
1961                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1962                absolute_path: Some(
1963                    "/home/acj/workspace/rbspy/ci/ruby-programs/infinite.rb".to_string(),
1964                ),
1965                lineno: Some(11),
1966            },
1967            StackFrame {
1968                name: "block in <main>".to_string(),
1969                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1970                absolute_path: Some(
1971                    "/home/acj/workspace/rbspy/ci/ruby-programs/infinite.rb".to_string(),
1972                ),
1973                lineno: Some(15),
1974            },
1975            StackFrame {
1976                name: "loop [c function]".to_string(),
1977                relative_path: "(unknown)".to_string(),
1978                absolute_path: None,
1979                lineno: None,
1980            },
1981        ]
1982    }
1983
1984    fn real_stack_trace_3_2_0() -> Vec<StackFrame> {
1985        vec![
1986            StackFrame {
1987                name: "sleep [c function]".to_string(),
1988                relative_path: "(unknown)".to_string(),
1989                absolute_path: None,
1990                lineno: None,
1991            },
1992            StackFrame {
1993                name: "aaa".to_string(),
1994                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
1995                absolute_path: Some(
1996                    "/home/parallels/rbspy/ci/ruby-programs/infinite.rb".to_string(),
1997                ),
1998                lineno: Some(3),
1999            },
2000            StackFrame {
2001                name: "bbb".to_string(),
2002                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2003                absolute_path: Some(
2004                    "/home/parallels/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2005                ),
2006                lineno: Some(7),
2007            },
2008            StackFrame {
2009                name: "ccc".to_string(),
2010                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2011                absolute_path: Some(
2012                    "/home/parallels/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2013                ),
2014                lineno: Some(11),
2015            },
2016            StackFrame {
2017                name: "block in <main>".to_string(),
2018                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2019                absolute_path: Some(
2020                    "/home/parallels/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2021                ),
2022                lineno: Some(15),
2023            },
2024            StackFrame {
2025                name: "loop [c function]".to_string(),
2026                relative_path: "(unknown)".to_string(),
2027                absolute_path: None,
2028                lineno: None,
2029            },
2030            StackFrame {
2031                name: "<main>".to_string(),
2032                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2033                absolute_path: Some(
2034                    "/home/parallels/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2035                ),
2036                lineno: Some(13),
2037            },
2038        ]
2039    }
2040
2041    fn real_stack_trace_3_3_0() -> Vec<StackFrame> {
2042        vec![
2043            StackFrame {
2044                name: "Kernel#sleep [c function]".to_string(),
2045                relative_path: "(unknown)".to_string(),
2046                absolute_path: None,
2047                lineno: None,
2048            },
2049            StackFrame {
2050                name: "Object#aaa".to_string(),
2051                relative_path: "ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2052                absolute_path: Some(
2053                    "/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2054                ),
2055                lineno: Some(9),
2056            },
2057            StackFrame {
2058                name: "Object#bbb".to_string(),
2059                relative_path: "ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2060                absolute_path: Some(
2061                    "/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2062                ),
2063                lineno: Some(13),
2064            },
2065            StackFrame {
2066                name: "Object#ccc".to_string(),
2067                relative_path: "ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2068                absolute_path: Some(
2069                    "/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2070                ),
2071                lineno: Some(17),
2072            },
2073            StackFrame {
2074                name: "block in <main>".to_string(),
2075                relative_path: "ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2076                absolute_path: Some(
2077                    "/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2078                ),
2079                lineno: Some(21),
2080            },
2081            StackFrame {
2082                name: "Kernel#loop".to_string(),
2083                relative_path: "<internal:kernel>".to_string(),
2084                absolute_path: Some("unknown".to_string()),
2085                lineno: Some(192),
2086            },
2087            StackFrame {
2088                name: "<main>".to_string(),
2089                relative_path: "ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2090                absolute_path: Some(
2091                    "/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2092                ),
2093                lineno: Some(19),
2094            },
2095        ]
2096    }
2097
2098    fn real_stack_trace_with_classes_3_3_0() -> Vec<StackFrame> {
2099        vec![
2100            StackFrame {
2101                name: "Kernel#sleep [c function]".to_string(),
2102                relative_path: "(unknown)".to_string(),
2103                absolute_path: None,
2104                lineno: None,
2105            },
2106            StackFrame {
2107                name: "A#aaa".to_string(),
2108                relative_path: "ci/ruby-programs/infinite_on_cpu_with_classes.rb".to_string(),
2109                absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu_with_classes.rb".to_string()),
2110                lineno: Some(10)
2111            },
2112            StackFrame {
2113                name: "B::Ab#bbb".to_string(),
2114                relative_path: "ci/ruby-programs/infinite_on_cpu_with_classes.rb".to_string(),
2115                absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu_with_classes.rb".to_string()),
2116                lineno: Some(17)
2117            },
2118            StackFrame {
2119                name: "C::Cb#ccc".to_string(),
2120                relative_path: "ci/ruby-programs/infinite_on_cpu_with_classes.rb".to_string(),
2121                absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu_with_classes.rb".to_string()),
2122                lineno: Some(25)
2123            },
2124            StackFrame {
2125                name: "block in looper".to_string(),
2126                relative_path: "ci/ruby-programs/infinite_on_cpu_with_classes.rb".to_string(),
2127                absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu_with_classes.rb".to_string()),
2128                lineno: Some(32)
2129            },
2130            StackFrame {
2131                name: "Kernel#loop".to_string(),
2132                relative_path: "<internal:kernel>".to_string(),
2133                absolute_path: Some("unknown".to_string()),
2134                lineno: Some(192)
2135            },
2136            StackFrame {
2137                name: "Object#looper".to_string(),
2138                relative_path: "ci/ruby-programs/infinite_on_cpu_with_classes.rb".to_string(),
2139                absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu_with_classes.rb".to_string()),
2140                lineno: Some(33)
2141            },
2142        ]
2143    }
2144
2145    fn real_complex_trace_with_classes_3_4_5() -> Vec<StackFrame> {
2146        vec![
2147            StackFrame { name: "Kernel#sleep [c function]".to_string(), relative_path: "(unknown)".to_string(), absolute_path: None, lineno: None },
2148            StackFrame { name: "block in hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(41) },
2149            StackFrame { name: "Kernel#loop".to_string(), relative_path: "<internal:kernel>".to_string(), absolute_path: Some("unknown".to_string()), lineno: Some(173) },
2150            StackFrame { name: "ClassA#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(42) },
2151            StackFrame { name: "ModuleB::ClassB#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(49) },
2152            StackFrame { name: "ModuleC.hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(56) },
2153            StackFrame { name: "ClassWithStaticMethod.hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(62) },
2154            StackFrame { name: "ModuleD#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(68) },
2155            StackFrame { name: "block in <top (required)>".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(75) },
2156            StackFrame { name: "block in <top (required)>".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(77) },
2157            StackFrame { name: "#<ClassD:0x7f27035d2448>.hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(85) },
2158            StackFrame { name: "ClassE#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(90) },
2159            StackFrame { name: "UnboundMethod#bind_call [c function]".to_string(), relative_path: "(unknown)".to_string(), absolute_path: None, lineno: None },
2160            StackFrame { name: "#<Refinement:0x7f27035d1d68>#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(108) },
2161            StackFrame { name: "ModuleE.hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(118) },
2162            StackFrame { name: "ClassH#method_missing".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(126) },
2163            StackFrame { name: "block in hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(133) },
2164            StackFrame { name: "Integer#times".to_string(), relative_path: "<internal:numeric>".to_string(), absolute_path: Some("unknown".to_string()), lineno: Some(261) },
2165            StackFrame { name: "ClassF#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(134) },
2166            StackFrame { name: "block (2 levels) in <top (required)>".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(140) },
2167            StackFrame { name: "#<Class:0x7f27035df648>.hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(147) },
2168            StackFrame { name: "#<Class:0x7f27035dee28>#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(152) },
2169            StackFrame { name: "#<Module:0x7f27035dece8>.hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(158) },
2170            StackFrame { name: "Object#method_with_complex_parameters".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(163) },
2171            StackFrame { name: "block (2 levels) in hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(174) },
2172            StackFrame { name: "ClassJ#hello_helper".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(168) },
2173            StackFrame { name: "block in hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(175) },
2174            StackFrame { name: "ClassJ#hello_helper".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(168) },
2175            StackFrame { name: "ClassJ#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(176) },
2176            StackFrame { name: "<compiled>".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("unknown".to_string()), lineno: Some(181) },
2177            StackFrame { name: "Kernel#eval [c function]".to_string(), relative_path: "(unknown)".to_string(), absolute_path: None, lineno: None },
2178            StackFrame { name: "ClassK#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(182) },
2179            StackFrame { name: "<compiled>".to_string(), relative_path: "(eval at /home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb:187)".to_string(), absolute_path: Some("unknown".to_string()), lineno: Some(1) },
2180            StackFrame { name: "BasicObject#instance_eval [c function]".to_string(), relative_path: "(unknown)".to_string(), absolute_path: None, lineno: None },
2181            StackFrame { name: "ClassL#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(188) },
2182            StackFrame { name: "<compiled>".to_string(), relative_path: "(eval at /home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb:193)".to_string(), absolute_path: Some("unknown".to_string()), lineno: Some(1) },
2183            StackFrame { name: "Kernel#eval [c function]".to_string(), relative_path: "(unknown)".to_string(), absolute_path: None, lineno: None },
2184            StackFrame { name: "ClassM#hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(194) },
2185            StackFrame { name: "block (3 levels) in <top (required)>".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(201) },
2186            StackFrame { name: "Integer#times".to_string(), relative_path: "<internal:numeric>".to_string(), absolute_path: Some("unknown".to_string()), lineno: Some(261) },
2187            StackFrame { name: "block (2 levels) in <top (required)>".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(202) },
2188            StackFrame { name: "Object#top_level_hello".to_string(), relative_path: "/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/interesting_backtrace_helper.rb".to_string()), lineno: Some(207) },
2189            StackFrame { name: "InstanceMethod#work".to_string(), relative_path: "ci/ruby-programs/cme_complex_labels.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/cme_complex_labels.rb".to_string()), lineno: Some(6) },
2190            StackFrame { name: "ModuleMethod#call_instance".to_string(), relative_path: "ci/ruby-programs/cme_complex_labels.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/cme_complex_labels.rb".to_string()), lineno: Some(12) },
2191            StackFrame { name: "Object#work_main".to_string(), relative_path: "ci/ruby-programs/cme_complex_labels.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/cme_complex_labels.rb".to_string()), lineno: Some(22) },
2192            StackFrame { name: "block in <main>".to_string(), relative_path: "ci/ruby-programs/cme_complex_labels.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/cme_complex_labels.rb".to_string()), lineno: Some(26) },
2193            StackFrame { name: "Kernel#loop".to_string(), relative_path: "<internal:kernel>".to_string(), absolute_path: Some("unknown".to_string()), lineno: Some(173) },
2194            StackFrame { name: "<main>".to_string(), relative_path: "ci/ruby-programs/cme_complex_labels.rb".to_string(), absolute_path: Some("/home/runner/work/rbspy/rbspy/ci/ruby-programs/cme_complex_labels.rb".to_string()), lineno: Some(24) },
2195        ]
2196    }
2197
2198    fn real_stack_trace_4_0_0() -> Vec<StackFrame> {
2199        vec![
2200            StackFrame {
2201                name: "Kernel#sleep [c function]".to_string(),
2202                relative_path: "(unknown)".to_string(),
2203                absolute_path: None,
2204                lineno: None,
2205            },
2206            StackFrame {
2207                name: "Object#aaa".to_string(),
2208                relative_path: "ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2209                absolute_path: Some(
2210                    "/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2211                ),
2212                lineno: Some(9),
2213            },
2214            StackFrame {
2215                name: "Object#bbb".to_string(),
2216                relative_path: "ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2217                absolute_path: Some(
2218                    "/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2219                ),
2220                lineno: Some(13),
2221            },
2222            StackFrame {
2223                name: "Object#ccc".to_string(),
2224                relative_path: "ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2225                absolute_path: Some(
2226                    "/home/runner/work/rbspy/rbspy/ci/ruby-programs/infinite_on_cpu.rb".to_string(),
2227                ),
2228                lineno: Some(17),
2229            },
2230            StackFrame {
2231                name: "Kernel#loop".to_string(),
2232                relative_path: "<internal:kernel>".to_string(),
2233                absolute_path: Some("unknown".to_string()),
2234                lineno: Some(174),
2235            },
2236        ]
2237    }
2238
2239    fn real_stack_trace_main() -> Vec<StackFrame> {
2240        vec![
2241            StackFrame::unknown_c_function(),
2242            StackFrame {
2243                name: "aaa".to_string(),
2244                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2245                absolute_path: Some(
2246                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2247                ),
2248                lineno: Some(2),
2249            },
2250            StackFrame {
2251                name: "bbb".to_string(),
2252                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2253                absolute_path: Some(
2254                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2255                ),
2256                lineno: Some(6),
2257            },
2258            StackFrame {
2259                name: "ccc".to_string(),
2260                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2261                absolute_path: Some(
2262                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2263                ),
2264                lineno: Some(10),
2265            },
2266            StackFrame {
2267                name: "block in <main>".to_string(),
2268                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2269                absolute_path: Some(
2270                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2271                ),
2272                lineno: Some(14),
2273            },
2274            StackFrame::unknown_c_function(),
2275            StackFrame {
2276                name: "<main>".to_string(),
2277                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2278                absolute_path: Some(
2279                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2280                ),
2281                lineno: Some(13),
2282            },
2283        ]
2284    }
2285
2286    fn real_stack_trace() -> Vec<StackFrame> {
2287        vec![
2288            StackFrame::unknown_c_function(),
2289            StackFrame {
2290                name: "aaa".to_string(),
2291                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2292                absolute_path: Some(
2293                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2294                ),
2295                lineno: Some(2),
2296            },
2297            StackFrame {
2298                name: "bbb".to_string(),
2299                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2300                absolute_path: Some(
2301                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2302                ),
2303                lineno: Some(6),
2304            },
2305            StackFrame {
2306                name: "ccc".to_string(),
2307                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2308                absolute_path: Some(
2309                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2310                ),
2311                lineno: Some(10),
2312            },
2313            StackFrame {
2314                name: "block in <main>".to_string(),
2315                relative_path: "ci/ruby-programs/infinite.rb".to_string(),
2316                absolute_path: Some(
2317                    "/home/bork/work/rbspy/ci/ruby-programs/infinite.rb".to_string(),
2318                ),
2319                lineno: Some(14),
2320            },
2321            StackFrame::unknown_c_function(),
2322        ]
2323    }
2324
2325    // These tests on core dumps don't work on 32bit platforms (error is
2326    // "Not enough memory resources are available to complete this operation.")
2327    // disable.
2328    #[cfg(target_pointer_width = "64")]
2329    #[test]
2330    fn test_get_ruby_stack_trace_1_9_3() {
2331        let current_thread_addr = 0x823930;
2332        let stack_trace = ruby_version::ruby_1_9_3_0::get_stack_trace::<CoreDump>(
2333            current_thread_addr,
2334            0,
2335            None,
2336            &coredump_1_9_3(),
2337            0,
2338            false,
2339        )
2340        .unwrap()
2341        .unwrap();
2342        assert_eq!(real_stack_trace_1_9_3(), stack_trace.trace);
2343    }
2344
2345    #[cfg(target_pointer_width = "64")]
2346    #[test]
2347    fn test_get_ruby_stack_trace_2_1_6() {
2348        let current_thread_addr = 0x562658abd7f0;
2349        let stack_trace = ruby_version::ruby_2_1_6::get_stack_trace::<CoreDump>(
2350            current_thread_addr,
2351            0,
2352            None,
2353            &coredump_2_1_6(),
2354            0,
2355            false,
2356        )
2357        .unwrap()
2358        .unwrap();
2359        assert_eq!(real_stack_trace_main(), stack_trace.trace);
2360    }
2361
2362    #[cfg(target_pointer_width = "64")]
2363    #[test]
2364    fn test_get_ruby_stack_trace_2_1_6_2() {
2365        // this stack is from a ruby program that is just running `select`
2366        let current_thread_addr = 0x562efcd577f0;
2367        let stack_trace = ruby_version::ruby_2_1_6::get_stack_trace(
2368            current_thread_addr,
2369            0,
2370            None,
2371            &coredump_2_1_6_c_function(),
2372            0,
2373            false,
2374        )
2375        .unwrap()
2376        .unwrap();
2377        assert_eq!(vec!(StackFrame::unknown_c_function()), stack_trace.trace);
2378    }
2379
2380    #[cfg(target_pointer_width = "64")]
2381    #[test]
2382    fn test_get_ruby_stack_trace_2_4_0() {
2383        let current_thread_addr = 0x55df44959920;
2384        let stack_trace = ruby_version::ruby_2_4_0::get_stack_trace::<CoreDump>(
2385            current_thread_addr,
2386            0,
2387            None,
2388            &coredump_2_4_0(),
2389            0,
2390            false,
2391        )
2392        .unwrap()
2393        .unwrap();
2394        assert_eq!(real_stack_trace(), stack_trace.trace);
2395    }
2396
2397    #[cfg(target_pointer_width = "64")]
2398    #[test]
2399    fn test_get_ruby_stack_trace_2_5_0() {
2400        let current_thread_addr = 0x55dd8c3b7758;
2401        let stack_trace = ruby_version::ruby_2_5_0::get_stack_trace::<CoreDump>(
2402            current_thread_addr,
2403            0,
2404            None,
2405            &coredump_2_5_0(),
2406            0,
2407            false,
2408        )
2409        .unwrap()
2410        .unwrap();
2411        assert_eq!(real_stack_trace(), stack_trace.trace);
2412    }
2413
2414    #[cfg(target_pointer_width = "64")]
2415    #[test]
2416    fn test_get_ruby_stack_trace_2_7_2() {
2417        let current_thread_addr = 0x7fdd8d626070;
2418        let global_symbols_addr = Some(0x7fdd8d60eb80);
2419        let stack_trace = ruby_version::ruby_2_7_2::get_stack_trace::<CoreDump>(
2420            current_thread_addr,
2421            0,
2422            global_symbols_addr,
2423            &coredump_2_7_2(),
2424            0,
2425            false,
2426        )
2427        .unwrap()
2428        .unwrap();
2429        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2430    }
2431
2432    #[cfg(target_pointer_width = "64")]
2433    #[test]
2434    fn test_get_ruby_stack_trace_2_7_3() {
2435        let current_thread_addr = 0x7fdd8d626070;
2436        let global_symbols_addr = Some(0x7fdd8d60eb80);
2437        let stack_trace = ruby_version::ruby_2_7_3::get_stack_trace::<CoreDump>(
2438            current_thread_addr,
2439            0,
2440            global_symbols_addr,
2441            &coredump_2_7_2(),
2442            0,
2443            false,
2444        )
2445        .unwrap()
2446        .unwrap();
2447        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2448    }
2449
2450    #[cfg(target_pointer_width = "64")]
2451    #[test]
2452    fn test_get_ruby_stack_trace_2_7_4() {
2453        let current_thread_addr = 0x7fdd8d626070;
2454        let global_symbols_addr = Some(0x7fdd8d60eb80);
2455        let stack_trace = ruby_version::ruby_2_7_4::get_stack_trace::<CoreDump>(
2456            current_thread_addr,
2457            0,
2458            global_symbols_addr,
2459            &coredump_2_7_2(),
2460            0,
2461            false,
2462        )
2463        .unwrap()
2464        .unwrap();
2465        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2466    }
2467
2468    #[cfg(target_pointer_width = "64")]
2469    #[test]
2470    fn test_get_ruby_stack_trace_2_7_5() {
2471        let current_thread_addr = 0x7fdd8d626070;
2472        let global_symbols_addr = Some(0x7fdd8d60eb80);
2473        let stack_trace = ruby_version::ruby_2_7_5::get_stack_trace::<CoreDump>(
2474            current_thread_addr,
2475            0,
2476            global_symbols_addr,
2477            &coredump_2_7_2(),
2478            0,
2479            false,
2480        )
2481        .unwrap()
2482        .unwrap();
2483        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2484    }
2485
2486    #[cfg(target_pointer_width = "64")]
2487    #[test]
2488    fn test_get_ruby_stack_trace_2_7_6() {
2489        let current_thread_addr = 0x7fdd8d626070;
2490        let global_symbols_addr = Some(0x7fdd8d60eb80);
2491        let stack_trace = ruby_version::ruby_2_7_6::get_stack_trace::<CoreDump>(
2492            current_thread_addr,
2493            0,
2494            global_symbols_addr,
2495            &coredump_2_7_2(),
2496            0,
2497            false,
2498        )
2499        .unwrap()
2500        .unwrap();
2501        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2502    }
2503
2504    #[cfg(target_pointer_width = "64")]
2505    #[test]
2506    fn test_get_ruby_stack_trace_2_7_7() {
2507        let current_thread_addr = 0x7fdd8d626070;
2508        let global_symbols_addr = Some(0x7fdd8d60eb80);
2509        let stack_trace = ruby_version::ruby_2_7_7::get_stack_trace::<CoreDump>(
2510            current_thread_addr,
2511            0,
2512            global_symbols_addr,
2513            &coredump_2_7_2(),
2514            0,
2515            false,
2516        )
2517        .unwrap()
2518        .unwrap();
2519        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2520    }
2521
2522    #[cfg(target_pointer_width = "64")]
2523    #[test]
2524    fn test_get_ruby_stack_trace_2_7_8() {
2525        let current_thread_addr = 0x7fdd8d626070;
2526        let global_symbols_addr = Some(0x7fdd8d60eb80);
2527        let stack_trace = ruby_version::ruby_2_7_8::get_stack_trace::<CoreDump>(
2528            current_thread_addr,
2529            0,
2530            global_symbols_addr,
2531            &coredump_2_7_2(),
2532            0,
2533            false,
2534        )
2535        .unwrap()
2536        .unwrap();
2537        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2538    }
2539
2540    #[cfg(target_pointer_width = "64")]
2541    #[test]
2542    fn test_get_ruby_stack_trace_3_0_0() {
2543        let source = coredump_3_0_0();
2544        let vm_addr = 0x7fdacdab7470;
2545        let global_symbols_addr = Some(0x7fdacdaa9d80);
2546        let stack_trace = ruby_version::ruby_3_0_0::get_stack_trace::<CoreDump>(
2547            0,
2548            vm_addr,
2549            global_symbols_addr,
2550            &source,
2551            0,
2552            false,
2553        )
2554        .unwrap()
2555        .unwrap();
2556        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2557    }
2558
2559    #[cfg(target_pointer_width = "64")]
2560    #[test]
2561    fn test_get_ruby_stack_trace_3_0_1() {
2562        let source = coredump_3_0_0();
2563        let vm_addr = 0x7fdacdab7470;
2564        let global_symbols_addr = Some(0x7fdacdaa9d80);
2565        let stack_trace = ruby_version::ruby_3_0_1::get_stack_trace::<CoreDump>(
2566            0,
2567            vm_addr,
2568            global_symbols_addr,
2569            &source,
2570            0,
2571            false,
2572        )
2573        .unwrap()
2574        .unwrap();
2575        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2576    }
2577
2578    #[cfg(target_pointer_width = "64")]
2579    #[test]
2580    fn test_get_ruby_stack_trace_3_0_2() {
2581        let source = coredump_3_0_0();
2582        let vm_addr = 0x7fdacdab7470;
2583        let global_symbols_addr = Some(0x7fdacdaa9d80);
2584        let stack_trace = ruby_version::ruby_3_0_2::get_stack_trace::<CoreDump>(
2585            0,
2586            vm_addr,
2587            global_symbols_addr,
2588            &source,
2589            0,
2590            false,
2591        )
2592        .unwrap()
2593        .unwrap();
2594        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2595    }
2596
2597    #[cfg(target_pointer_width = "64")]
2598    #[test]
2599    fn test_get_ruby_stack_trace_3_0_3() {
2600        let source = coredump_3_0_0();
2601        let vm_addr = 0x7fdacdab7470;
2602        let global_symbols_addr = Some(0x7fdacdaa9d80);
2603        let stack_trace = ruby_version::ruby_3_0_3::get_stack_trace::<CoreDump>(
2604            0,
2605            vm_addr,
2606            global_symbols_addr,
2607            &source,
2608            0,
2609            false,
2610        )
2611        .unwrap()
2612        .unwrap();
2613        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2614    }
2615
2616    #[cfg(target_pointer_width = "64")]
2617    #[test]
2618    fn test_get_ruby_stack_trace_3_0_4() {
2619        let source = coredump_3_0_0();
2620        let vm_addr = 0x7fdacdab7470;
2621        let global_symbols_addr = Some(0x7fdacdaa9d80);
2622        let stack_trace = ruby_version::ruby_3_0_4::get_stack_trace::<CoreDump>(
2623            0,
2624            vm_addr,
2625            global_symbols_addr,
2626            &source,
2627            0,
2628            false,
2629        )
2630        .unwrap()
2631        .unwrap();
2632        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2633    }
2634
2635    #[cfg(target_pointer_width = "64")]
2636    #[test]
2637    fn test_get_ruby_stack_trace_3_0_5() {
2638        let source = coredump_3_0_0();
2639        let vm_addr = 0x7fdacdab7470;
2640        let global_symbols_addr = Some(0x7fdacdaa9d80);
2641        let stack_trace = ruby_version::ruby_3_0_5::get_stack_trace::<CoreDump>(
2642            0,
2643            vm_addr,
2644            global_symbols_addr,
2645            &source,
2646            0,
2647            false,
2648        )
2649        .unwrap()
2650        .unwrap();
2651        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2652    }
2653
2654    #[cfg(target_pointer_width = "64")]
2655    #[test]
2656    fn test_get_ruby_stack_trace_3_0_6() {
2657        let source = coredump_3_0_0();
2658        let vm_addr = 0x7fdacdab7470;
2659        let global_symbols_addr = Some(0x7fdacdaa9d80);
2660        let stack_trace = ruby_version::ruby_3_0_6::get_stack_trace::<CoreDump>(
2661            0,
2662            vm_addr,
2663            global_symbols_addr,
2664            &source,
2665            0,
2666            false,
2667        )
2668        .unwrap()
2669        .unwrap();
2670        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2671    }
2672
2673    #[cfg(target_pointer_width = "64")]
2674    #[test]
2675    fn test_get_ruby_stack_trace_3_0_7() {
2676        let source = coredump_3_0_0();
2677        let vm_addr = 0x7fdacdab7470;
2678        let global_symbols_addr = Some(0x7fdacdaa9d80);
2679        let stack_trace = ruby_version::ruby_3_0_7::get_stack_trace::<CoreDump>(
2680            0,
2681            vm_addr,
2682            global_symbols_addr,
2683            &source,
2684            0,
2685            false,
2686        )
2687        .unwrap()
2688        .unwrap();
2689        assert_eq!(real_stack_trace_2_7_2(), stack_trace.trace);
2690    }
2691
2692    #[cfg(target_pointer_width = "64")]
2693    #[test]
2694    fn test_get_ruby_stack_trace_3_1_0() {
2695        let source = coredump_3_1_0();
2696        let vm_addr = 0x7f0dc0c83c58;
2697        let global_symbols_addr = Some(0x7f0dc0c75e80);
2698        let stack_trace = ruby_version::ruby_3_1_0::get_stack_trace::<CoreDump>(
2699            0,
2700            vm_addr,
2701            global_symbols_addr,
2702            &source,
2703            0,
2704            false,
2705        )
2706        .unwrap()
2707        .unwrap();
2708        assert_eq!(real_stack_trace_3_1_0(), stack_trace.trace);
2709    }
2710
2711    #[cfg(target_pointer_width = "64")]
2712    #[test]
2713    fn test_get_ruby_stack_trace_3_1_1() {
2714        let source = coredump_3_1_0();
2715        let vm_addr = 0x7f0dc0c83c58;
2716        let global_symbols_addr = Some(0x7f0dc0c75e80);
2717        let stack_trace = ruby_version::ruby_3_1_1::get_stack_trace::<CoreDump>(
2718            0,
2719            vm_addr,
2720            global_symbols_addr,
2721            &source,
2722            0,
2723            false,
2724        )
2725        .unwrap()
2726        .unwrap();
2727        assert_eq!(real_stack_trace_3_1_0(), stack_trace.trace);
2728    }
2729
2730    #[cfg(target_pointer_width = "64")]
2731    #[test]
2732    fn test_get_ruby_stack_trace_3_1_2() {
2733        let source = coredump_3_1_0();
2734        let vm_addr = 0x7f0dc0c83c58;
2735        let global_symbols_addr = Some(0x7f0dc0c75e80);
2736        let stack_trace = ruby_version::ruby_3_1_2::get_stack_trace::<CoreDump>(
2737            0,
2738            vm_addr,
2739            global_symbols_addr,
2740            &source,
2741            0,
2742            false,
2743        )
2744        .unwrap()
2745        .unwrap();
2746        assert_eq!(real_stack_trace_3_1_0(), stack_trace.trace);
2747    }
2748
2749    #[cfg(target_pointer_width = "64")]
2750    #[test]
2751    fn test_get_ruby_stack_trace_3_1_3() {
2752        let source = coredump_3_1_0();
2753        let vm_addr = 0x7f0dc0c83c58;
2754        let global_symbols_addr = Some(0x7f0dc0c75e80);
2755        let stack_trace = ruby_version::ruby_3_1_3::get_stack_trace::<CoreDump>(
2756            0,
2757            vm_addr,
2758            global_symbols_addr,
2759            &source,
2760            0,
2761            false,
2762        )
2763        .unwrap()
2764        .unwrap();
2765        assert_eq!(real_stack_trace_3_1_0(), stack_trace.trace);
2766    }
2767
2768    #[cfg(target_pointer_width = "64")]
2769    #[test]
2770    fn test_get_ruby_stack_trace_3_1_4() {
2771        let source = coredump_3_1_0();
2772        let vm_addr = 0x7f0dc0c83c58;
2773        let global_symbols_addr = Some(0x7f0dc0c75e80);
2774        let stack_trace = ruby_version::ruby_3_1_4::get_stack_trace::<CoreDump>(
2775            0,
2776            vm_addr,
2777            global_symbols_addr,
2778            &source,
2779            0,
2780            false,
2781        )
2782        .unwrap()
2783        .unwrap();
2784        assert_eq!(real_stack_trace_3_1_0(), stack_trace.trace);
2785    }
2786
2787    #[cfg(target_pointer_width = "64")]
2788    #[test]
2789    fn test_get_ruby_stack_trace_3_1_5() {
2790        let source = coredump_3_1_0();
2791        let vm_addr = 0x7f0dc0c83c58;
2792        let global_symbols_addr = Some(0x7f0dc0c75e80);
2793        let stack_trace = ruby_version::ruby_3_1_5::get_stack_trace::<CoreDump>(
2794            0,
2795            vm_addr,
2796            global_symbols_addr,
2797            &source,
2798            0,
2799            false,
2800        )
2801        .unwrap()
2802        .unwrap();
2803        assert_eq!(real_stack_trace_3_1_0(), stack_trace.trace);
2804    }
2805
2806    #[cfg(target_pointer_width = "64")]
2807    #[test]
2808    fn test_get_ruby_stack_trace_3_1_6() {
2809        let source = coredump_3_1_0();
2810        let vm_addr = 0x7f0dc0c83c58;
2811        let global_symbols_addr = Some(0x7f0dc0c75e80);
2812        let stack_trace = ruby_version::ruby_3_1_6::get_stack_trace::<CoreDump>(
2813            0,
2814            vm_addr,
2815            global_symbols_addr,
2816            &source,
2817            0,
2818            false,
2819        )
2820        .unwrap()
2821        .unwrap();
2822        assert_eq!(real_stack_trace_3_1_0(), stack_trace.trace);
2823    }
2824
2825    #[cfg(target_pointer_width = "64")]
2826    #[test]
2827    fn test_get_ruby_stack_trace_3_1_7() {
2828        let source = coredump_3_1_0();
2829        let vm_addr = 0x7f0dc0c83c58;
2830        let global_symbols_addr = Some(0x7f0dc0c75e80);
2831        let stack_trace = ruby_version::ruby_3_1_7::get_stack_trace::<CoreDump>(
2832            0,
2833            vm_addr,
2834            global_symbols_addr,
2835            &source,
2836            0,
2837            false,
2838        )
2839        .unwrap()
2840        .unwrap();
2841        assert_eq!(real_stack_trace_3_1_0(), stack_trace.trace);
2842    }
2843
2844    #[cfg(target_pointer_width = "64")]
2845    #[test]
2846    fn test_get_ruby_stack_trace_3_2_0() {
2847        let source = coredump_3_2_0();
2848        let vm_addr = 0xffffb8034578;
2849        let global_symbols_addr = Some(0xffffb8025340);
2850        let stack_trace = ruby_version::ruby_3_2_0::get_stack_trace::<CoreDump>(
2851            0,
2852            vm_addr,
2853            global_symbols_addr,
2854            &source,
2855            0,
2856            false,
2857        )
2858        .unwrap()
2859        .unwrap();
2860        assert_eq!(real_stack_trace_3_2_0(), stack_trace.trace);
2861    }
2862
2863    #[cfg(target_pointer_width = "64")]
2864    #[test]
2865    fn test_get_ruby_stack_trace_3_2_1() {
2866        let source = coredump_3_2_0();
2867        let vm_addr = 0xffffb8034578;
2868        let global_symbols_addr = Some(0xffffb8025340);
2869        let stack_trace = ruby_version::ruby_3_2_1::get_stack_trace::<CoreDump>(
2870            0,
2871            vm_addr,
2872            global_symbols_addr,
2873            &source,
2874            0,
2875            false,
2876        )
2877        .unwrap()
2878        .unwrap();
2879        assert_eq!(real_stack_trace_3_2_0(), stack_trace.trace);
2880    }
2881
2882    #[cfg(target_pointer_width = "64")]
2883    #[test]
2884    fn test_get_ruby_stack_trace_3_2_2() {
2885        let source = coredump_3_2_0();
2886        let vm_addr = 0xffffb8034578;
2887        let global_symbols_addr = Some(0xffffb8025340);
2888        let stack_trace = ruby_version::ruby_3_2_2::get_stack_trace::<CoreDump>(
2889            0,
2890            vm_addr,
2891            global_symbols_addr,
2892            &source,
2893            0,
2894            false,
2895        )
2896        .unwrap()
2897        .unwrap();
2898        assert_eq!(real_stack_trace_3_2_0(), stack_trace.trace);
2899    }
2900
2901    #[cfg(target_pointer_width = "64")]
2902    #[test]
2903    fn test_get_ruby_stack_trace_3_2_3() {
2904        let source = coredump_3_2_0();
2905        let vm_addr = 0xffffb8034578;
2906        let global_symbols_addr = Some(0xffffb8025340);
2907        let stack_trace = ruby_version::ruby_3_2_3::get_stack_trace::<CoreDump>(
2908            0,
2909            vm_addr,
2910            global_symbols_addr,
2911            &source,
2912            0,
2913            false,
2914        )
2915        .unwrap()
2916        .unwrap();
2917        assert_eq!(real_stack_trace_3_2_0(), stack_trace.trace);
2918    }
2919
2920    #[cfg(target_pointer_width = "64")]
2921    #[test]
2922    fn test_get_ruby_stack_trace_3_2_4() {
2923        let source = coredump_3_2_0();
2924        let vm_addr = 0xffffb8034578;
2925        let global_symbols_addr = Some(0xffffb8025340);
2926        let stack_trace = ruby_version::ruby_3_2_4::get_stack_trace::<CoreDump>(
2927            0,
2928            vm_addr,
2929            global_symbols_addr,
2930            &source,
2931            0,
2932            false,
2933        )
2934        .unwrap()
2935        .unwrap();
2936        assert_eq!(real_stack_trace_3_2_0(), stack_trace.trace);
2937    }
2938
2939    #[cfg(target_pointer_width = "64")]
2940    #[test]
2941    fn test_get_ruby_stack_trace_3_2_5() {
2942        let source = coredump_3_2_0();
2943        let vm_addr = 0xffffb8034578;
2944        let global_symbols_addr = Some(0xffffb8025340);
2945        let stack_trace = ruby_version::ruby_3_2_5::get_stack_trace::<CoreDump>(
2946            0,
2947            vm_addr,
2948            global_symbols_addr,
2949            &source,
2950            0,
2951            false,
2952        )
2953        .unwrap();
2954        assert_eq!(real_stack_trace_3_2_0(), stack_trace.unwrap().trace);
2955    }
2956
2957    #[cfg(target_pointer_width = "64")]
2958    #[test]
2959    fn test_get_ruby_stack_trace_3_2_6() {
2960        let source = coredump_3_2_0();
2961        let vm_addr = 0xffffb8034578;
2962        let global_symbols_addr = Some(0xffffb8025340);
2963        let stack_trace = ruby_version::ruby_3_2_6::get_stack_trace::<CoreDump>(
2964            0,
2965            vm_addr,
2966            global_symbols_addr,
2967            &source,
2968            0,
2969            false,
2970        )
2971        .unwrap();
2972        assert_eq!(real_stack_trace_3_2_0(), stack_trace.unwrap().trace);
2973    }
2974
2975    #[cfg(target_pointer_width = "64")]
2976    #[test]
2977    fn test_get_ruby_stack_trace_3_2_7() {
2978        let source = coredump_3_2_0();
2979        let vm_addr = 0xffffb8034578;
2980        let global_symbols_addr = Some(0xffffb8025340);
2981        let stack_trace = ruby_version::ruby_3_2_7::get_stack_trace::<CoreDump>(
2982            0,
2983            vm_addr,
2984            global_symbols_addr,
2985            &source,
2986            0,
2987            false,
2988        )
2989        .unwrap();
2990        assert_eq!(real_stack_trace_3_2_0(), stack_trace.unwrap().trace);
2991    }
2992
2993    #[cfg(target_pointer_width = "64")]
2994    #[test]
2995    fn test_get_ruby_stack_trace_3_2_8() {
2996        let source = coredump_3_2_0();
2997        let vm_addr = 0xffffb8034578;
2998        let global_symbols_addr = Some(0xffffb8025340);
2999        let stack_trace = ruby_version::ruby_3_2_8::get_stack_trace::<CoreDump>(
3000            0,
3001            vm_addr,
3002            global_symbols_addr,
3003            &source,
3004            0,
3005            false,
3006        )
3007        .unwrap();
3008        assert_eq!(real_stack_trace_3_2_0(), stack_trace.unwrap().trace);
3009    }
3010
3011    #[cfg(target_pointer_width = "64")]
3012    #[test]
3013    fn test_get_ruby_stack_trace_3_2_9() {
3014        let source = coredump_3_2_0();
3015        let vm_addr = 0xffffb8034578;
3016        let global_symbols_addr = Some(0xffffb8025340);
3017        let stack_trace = ruby_version::ruby_3_2_9::get_stack_trace::<CoreDump>(
3018            0,
3019            vm_addr,
3020            global_symbols_addr,
3021            &source,
3022            0,
3023            false,
3024        )
3025        .unwrap();
3026        assert_eq!(real_stack_trace_3_2_0(), stack_trace.unwrap().trace);
3027    }
3028
3029    #[cfg(target_pointer_width = "64")]
3030    #[test]
3031    fn test_get_ruby_stack_trace_3_2_10() {
3032        let source = coredump_3_2_0();
3033        let vm_addr = 0xffffb8034578;
3034        let global_symbols_addr = Some(0xffffb8025340);
3035        let stack_trace = ruby_version::ruby_3_2_10::get_stack_trace::<CoreDump>(
3036            0,
3037            vm_addr,
3038            global_symbols_addr,
3039            &source,
3040            0,
3041            false,
3042        )
3043        .unwrap();
3044        assert_eq!(real_stack_trace_3_2_0(), stack_trace.unwrap().trace);
3045    }
3046
3047    #[cfg(target_pointer_width = "64")]
3048    #[test]
3049    fn test_get_ruby_stack_trace_3_2_11() {
3050        let source = coredump_3_2_0();
3051        let vm_addr = 0xffffb8034578;
3052        let global_symbols_addr = Some(0xffffb8025340);
3053        let stack_trace = ruby_version::ruby_3_2_11::get_stack_trace::<CoreDump>(
3054            0,
3055            vm_addr,
3056            global_symbols_addr,
3057            &source,
3058            0,
3059            false,
3060        )
3061        .unwrap();
3062        assert_eq!(real_stack_trace_3_2_0(), stack_trace.unwrap().trace);
3063    }
3064
3065    #[cfg(target_pointer_width = "64")]
3066    #[test]
3067    fn test_get_ruby_stack_trace_3_3_0() {
3068        let source = coredump_3_3_0();
3069        let vm_addr = 0x7f43435f4988;
3070        let global_symbols_addr = Some(0x7f43435e3c60);
3071        let stack_trace = ruby_version::ruby_3_3_0::get_stack_trace::<CoreDump>(
3072            0,
3073            vm_addr,
3074            global_symbols_addr,
3075            &source,
3076            0,
3077            false,
3078        )
3079        .unwrap()
3080        .unwrap();
3081        assert_eq!(real_stack_trace_3_3_0(), stack_trace.trace);
3082    }
3083
3084    #[cfg(target_pointer_width = "64")]
3085    #[test]
3086    fn test_get_ruby_stack_trace_with_classes_3_3_0() {
3087        let source = coredump_with_classes_3_3_0();
3088        let vm_addr = 0x7f58cb7f4988;
3089        let global_symbols_addr = Some(0x7f58cb7e3c60);
3090        let stack_trace = ruby_version::ruby_3_3_0::get_stack_trace::<CoreDump>(
3091            0,
3092            vm_addr,
3093            global_symbols_addr,
3094            &source,
3095            0,
3096            false,
3097        )
3098        .unwrap();
3099        assert_eq!(
3100            real_stack_trace_with_classes_3_3_0(),
3101            stack_trace.unwrap().trace
3102        );
3103    }
3104
3105    #[cfg(target_pointer_width = "64")]
3106    #[test]
3107    fn test_get_ruby_stack_trace_3_3_1() {
3108        let source = coredump_3_3_0();
3109        let vm_addr = 0x7f43435f4988;
3110        let global_symbols_addr = Some(0x7f43435e3c60);
3111        let stack_trace = ruby_version::ruby_3_3_1::get_stack_trace::<CoreDump>(
3112            0,
3113            vm_addr,
3114            global_symbols_addr,
3115            &source,
3116            0,
3117            false,
3118        )
3119        .unwrap()
3120        .unwrap();
3121        assert_eq!(real_stack_trace_3_3_0(), stack_trace.trace);
3122    }
3123
3124    #[cfg(target_pointer_width = "64")]
3125    #[test]
3126    fn test_get_ruby_stack_trace_3_3_2() {
3127        let source = coredump_3_3_0();
3128        let vm_addr = 0x7f43435f4988;
3129        let global_symbols_addr = Some(0x7f43435e3c60);
3130        let stack_trace = ruby_version::ruby_3_3_2::get_stack_trace::<CoreDump>(
3131            0,
3132            vm_addr,
3133            global_symbols_addr,
3134            &source,
3135            0,
3136            false,
3137        )
3138        .unwrap()
3139        .unwrap();
3140        assert_eq!(real_stack_trace_3_3_0(), stack_trace.trace);
3141    }
3142
3143    #[cfg(target_pointer_width = "64")]
3144    #[test]
3145    fn test_get_ruby_stack_trace_3_3_3() {
3146        let source = coredump_3_3_0();
3147        let vm_addr = 0x7f43435f4988;
3148        let global_symbols_addr = Some(0x7f43435e3c60);
3149        let stack_trace = ruby_version::ruby_3_3_3::get_stack_trace::<CoreDump>(
3150            0,
3151            vm_addr,
3152            global_symbols_addr,
3153            &source,
3154            0,
3155            false,
3156        )
3157        .unwrap();
3158        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3159    }
3160
3161    #[cfg(target_pointer_width = "64")]
3162    #[test]
3163    fn test_get_ruby_stack_trace_3_3_4() {
3164        let source = coredump_3_3_0();
3165        let vm_addr = 0x7f43435f4988;
3166        let global_symbols_addr = Some(0x7f43435e3c60);
3167        let stack_trace = ruby_version::ruby_3_3_4::get_stack_trace::<CoreDump>(
3168            0,
3169            vm_addr,
3170            global_symbols_addr,
3171            &source,
3172            0,
3173            false,
3174        )
3175        .unwrap();
3176        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3177    }
3178
3179    #[cfg(target_pointer_width = "64")]
3180    #[test]
3181    fn test_get_ruby_stack_trace_3_3_5() {
3182        let source = coredump_3_3_0();
3183        let vm_addr = 0x7f43435f4988;
3184        let global_symbols_addr = Some(0x7f43435e3c60);
3185        let stack_trace = ruby_version::ruby_3_3_5::get_stack_trace::<CoreDump>(
3186            0,
3187            vm_addr,
3188            global_symbols_addr,
3189            &source,
3190            0,
3191            false,
3192        )
3193        .unwrap();
3194        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3195    }
3196
3197    #[cfg(target_pointer_width = "64")]
3198    #[test]
3199    fn test_get_ruby_stack_trace_3_3_6() {
3200        let source = coredump_3_3_0();
3201        let vm_addr = 0x7f43435f4988;
3202        let global_symbols_addr = Some(0x7f43435e3c60);
3203        let stack_trace = ruby_version::ruby_3_3_6::get_stack_trace::<CoreDump>(
3204            0,
3205            vm_addr,
3206            global_symbols_addr,
3207            &source,
3208            0,
3209            false,
3210        )
3211        .unwrap();
3212        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3213    }
3214
3215    #[cfg(target_pointer_width = "64")]
3216    #[test]
3217    fn test_get_ruby_stack_trace_3_3_7() {
3218        let source = coredump_3_3_0();
3219        let vm_addr = 0x7f43435f4988;
3220        let global_symbols_addr = Some(0x7f43435e3c60);
3221        let stack_trace = ruby_version::ruby_3_3_7::get_stack_trace::<CoreDump>(
3222            0,
3223            vm_addr,
3224            global_symbols_addr,
3225            &source,
3226            0,
3227            false,
3228        )
3229        .unwrap();
3230        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3231    }
3232
3233    #[cfg(target_pointer_width = "64")]
3234    #[test]
3235    fn test_get_ruby_stack_trace_3_3_8() {
3236        let source = coredump_3_3_0();
3237        let vm_addr = 0x7f43435f4988;
3238        let global_symbols_addr = Some(0x7f43435e3c60);
3239        let stack_trace = ruby_version::ruby_3_3_8::get_stack_trace::<CoreDump>(
3240            0,
3241            vm_addr,
3242            global_symbols_addr,
3243            &source,
3244            0,
3245            false,
3246        )
3247        .unwrap();
3248        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3249    }
3250
3251    #[cfg(target_pointer_width = "64")]
3252    #[test]
3253    fn test_get_ruby_stack_trace_3_3_9() {
3254        let source = coredump_3_3_0();
3255        let vm_addr = 0x7f43435f4988;
3256        let global_symbols_addr = Some(0x7f43435e3c60);
3257        let stack_trace = ruby_version::ruby_3_3_9::get_stack_trace::<CoreDump>(
3258            0,
3259            vm_addr,
3260            global_symbols_addr,
3261            &source,
3262            0,
3263            false,
3264        )
3265        .unwrap();
3266        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3267    }
3268
3269    #[cfg(target_pointer_width = "64")]
3270    #[test]
3271    fn test_get_ruby_stack_trace_3_3_10() {
3272        let source = coredump_3_3_0();
3273        let vm_addr = 0x7f43435f4988;
3274        let global_symbols_addr = Some(0x7f43435e3c60);
3275        let stack_trace = ruby_version::ruby_3_3_10::get_stack_trace::<CoreDump>(
3276            0,
3277            vm_addr,
3278            global_symbols_addr,
3279            &source,
3280            0,
3281            false,
3282        )
3283        .unwrap();
3284        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3285    }
3286
3287    #[cfg(target_pointer_width = "64")]
3288    #[test]
3289    fn test_get_ruby_stack_trace_3_3_11() {
3290        let source = coredump_3_3_0();
3291        let vm_addr = 0x7f43435f4988;
3292        let global_symbols_addr = Some(0x7f43435e3c60);
3293        let stack_trace = ruby_version::ruby_3_3_11::get_stack_trace::<CoreDump>(
3294            0,
3295            vm_addr,
3296            global_symbols_addr,
3297            &source,
3298            0,
3299            false,
3300        )
3301        .unwrap();
3302        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3303    }
3304
3305    #[cfg(target_pointer_width = "64")]
3306    #[test]
3307    fn test_get_ruby_stack_trace_3_4_0() {
3308        let source = coredump_3_3_0();
3309        let vm_addr = 0x7f43435f4988;
3310        let global_symbols_addr = Some(0x7f43435e3c60);
3311        let stack_trace = ruby_version::ruby_3_4_0::get_stack_trace::<CoreDump>(
3312            0,
3313            vm_addr,
3314            global_symbols_addr,
3315            &source,
3316            0,
3317            false,
3318        )
3319        .unwrap();
3320        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3321    }
3322
3323    #[cfg(target_pointer_width = "64")]
3324    #[test]
3325    fn test_get_ruby_stack_trace_3_4_1() {
3326        let source = coredump_3_3_0();
3327        let vm_addr = 0x7f43435f4988;
3328        let global_symbols_addr = Some(0x7f43435e3c60);
3329        let stack_trace = ruby_version::ruby_3_4_1::get_stack_trace::<CoreDump>(
3330            0,
3331            vm_addr,
3332            global_symbols_addr,
3333            &source,
3334            0,
3335            false,
3336        )
3337        .unwrap();
3338        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3339    }
3340
3341    #[cfg(target_pointer_width = "64")]
3342    #[test]
3343    fn test_get_ruby_stack_trace_3_4_2() {
3344        let source = coredump_3_3_0();
3345        let vm_addr = 0x7f43435f4988;
3346        let global_symbols_addr = Some(0x7f43435e3c60);
3347        let stack_trace = ruby_version::ruby_3_4_2::get_stack_trace::<CoreDump>(
3348            0,
3349            vm_addr,
3350            global_symbols_addr,
3351            &source,
3352            0,
3353            false,
3354        )
3355        .unwrap();
3356        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3357    }
3358
3359    #[cfg(target_pointer_width = "64")]
3360    #[test]
3361    fn test_get_ruby_stack_trace_3_4_3() {
3362        let source = coredump_3_3_0();
3363        let vm_addr = 0x7f43435f4988;
3364        let global_symbols_addr = Some(0x7f43435e3c60);
3365        let stack_trace = ruby_version::ruby_3_4_3::get_stack_trace::<CoreDump>(
3366            0,
3367            vm_addr,
3368            global_symbols_addr,
3369            &source,
3370            0,
3371            false,
3372        )
3373        .unwrap();
3374        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3375    }
3376
3377    #[cfg(target_pointer_width = "64")]
3378    #[test]
3379    fn test_get_ruby_stack_trace_3_4_4() {
3380        let source = coredump_3_3_0();
3381        let vm_addr = 0x7f43435f4988;
3382        let global_symbols_addr = Some(0x7f43435e3c60);
3383        let stack_trace = ruby_version::ruby_3_4_4::get_stack_trace::<CoreDump>(
3384            0,
3385            vm_addr,
3386            global_symbols_addr,
3387            &source,
3388            0,
3389            false,
3390        )
3391        .unwrap();
3392        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3393    }
3394
3395    #[cfg(target_pointer_width = "64")]
3396    #[test]
3397    fn test_get_ruby_stack_trace_3_4_5() {
3398        let source = coredump_3_3_0();
3399        let vm_addr = 0x7f43435f4988;
3400        let global_symbols_addr = Some(0x7f43435e3c60);
3401        let stack_trace = ruby_version::ruby_3_4_5::get_stack_trace::<CoreDump>(
3402            0,
3403            vm_addr,
3404            global_symbols_addr,
3405            &source,
3406            0,
3407            false,
3408        )
3409        .unwrap();
3410        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3411    }
3412
3413    #[cfg(target_pointer_width = "64")]
3414    #[test]
3415    fn test_get_ruby_stack_trace_complex_3_4_5() {
3416        let source = coredump_complex_3_4_5();
3417        let vm_addr = 0x7f271feb5390;
3418        let global_symbols_addr = Some(0x7f271fea3dc0);
3419        let stack_trace = ruby_version::ruby_3_3_0::get_stack_trace::<CoreDump>(
3420            0,
3421            vm_addr,
3422            global_symbols_addr,
3423            &source,
3424            0,
3425            false,
3426        )
3427        .unwrap();
3428        assert_eq!(
3429            real_complex_trace_with_classes_3_4_5(),
3430            stack_trace.unwrap().trace
3431        );
3432    }
3433
3434    #[cfg(target_pointer_width = "64")]
3435    #[test]
3436    fn test_get_ruby_stack_trace_3_4_6() {
3437        let source = coredump_3_3_0();
3438        let vm_addr = 0x7f43435f4988;
3439        let global_symbols_addr = Some(0x7f43435e3c60);
3440        let stack_trace = ruby_version::ruby_3_4_6::get_stack_trace::<CoreDump>(
3441            0,
3442            vm_addr,
3443            global_symbols_addr,
3444            &source,
3445            0,
3446            false,
3447        )
3448        .unwrap();
3449        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3450    }
3451
3452    #[cfg(target_pointer_width = "64")]
3453    #[test]
3454    fn test_get_ruby_stack_trace_3_4_7() {
3455        let source = coredump_3_3_0();
3456        let vm_addr = 0x7f43435f4988;
3457        let global_symbols_addr = Some(0x7f43435e3c60);
3458        let stack_trace = ruby_version::ruby_3_4_7::get_stack_trace::<CoreDump>(
3459            0,
3460            vm_addr,
3461            global_symbols_addr,
3462            &source,
3463            0,
3464            false,
3465        )
3466        .unwrap();
3467        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3468    }
3469
3470    #[cfg(target_pointer_width = "64")]
3471    #[test]
3472    fn test_get_ruby_stack_trace_3_4_8() {
3473        let source = coredump_3_3_0();
3474        let vm_addr = 0x7f43435f4988;
3475        let global_symbols_addr = Some(0x7f43435e3c60);
3476        let stack_trace = ruby_version::ruby_3_4_8::get_stack_trace::<CoreDump>(
3477            0,
3478            vm_addr,
3479            global_symbols_addr,
3480            &source,
3481            0,
3482            false,
3483        )
3484        .unwrap();
3485        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3486    }
3487
3488    #[cfg(target_pointer_width = "64")]
3489    #[test]
3490    fn test_get_ruby_stack_trace_3_4_9() {
3491        let source = coredump_3_3_0();
3492        let vm_addr = 0x7f43435f4988;
3493        let global_symbols_addr = Some(0x7f43435e3c60);
3494        let stack_trace = ruby_version::ruby_3_4_9::get_stack_trace::<CoreDump>(
3495            0,
3496            vm_addr,
3497            global_symbols_addr,
3498            &source,
3499            0,
3500            false,
3501        )
3502        .unwrap();
3503        assert_eq!(real_stack_trace_3_3_0(), stack_trace.unwrap().trace);
3504    }
3505
3506    #[cfg(target_pointer_width = "64")]
3507    #[test]
3508    fn test_get_ruby_stack_trace_4_0_0() {
3509        let source = coredump_4_0_0();
3510        let vm_addr = 0x7fa56b875738;
3511        let global_symbols_addr = Some(0x7fa56b862b50);
3512        let stack_trace = ruby_version::ruby_4_0_0::get_stack_trace::<CoreDump>(
3513            0,
3514            vm_addr,
3515            global_symbols_addr,
3516            &source,
3517            0,
3518            false,
3519        )
3520        .unwrap()
3521        .unwrap();
3522        assert_eq!(real_stack_trace_4_0_0(), stack_trace.trace);
3523    }
3524
3525    #[cfg(target_pointer_width = "64")]
3526    #[test]
3527    fn test_get_ruby_stack_trace_4_0_1() {
3528        let source = coredump_4_0_0();
3529        let vm_addr = 0x7fa56b875738;
3530        let global_symbols_addr = Some(0x7fa56b862b50);
3531        let stack_trace = ruby_version::ruby_4_0_1::get_stack_trace::<CoreDump>(
3532            0,
3533            vm_addr,
3534            global_symbols_addr,
3535            &source,
3536            0,
3537            false,
3538        )
3539        .unwrap()
3540        .unwrap();
3541        assert_eq!(real_stack_trace_4_0_0(), stack_trace.trace);
3542    }
3543
3544    #[cfg(target_pointer_width = "64")]
3545    #[test]
3546    fn test_get_ruby_stack_trace_4_0_2() {
3547        let source = coredump_4_0_0();
3548        let vm_addr = 0x7fa56b875738;
3549        let global_symbols_addr = Some(0x7fa56b862b50);
3550        let stack_trace = ruby_version::ruby_4_0_2::get_stack_trace::<CoreDump>(
3551            0,
3552            vm_addr,
3553            global_symbols_addr,
3554            &source,
3555            0,
3556            false,
3557        )
3558        .unwrap()
3559        .unwrap();
3560        assert_eq!(real_stack_trace_4_0_0(), stack_trace.trace);
3561    }
3562
3563    #[rstest]
3564    #[case::no_class_not_singleton("", "foo", false, "foo")]
3565    #[case::class_not_singleton("ClassA", "foo", false, "ClassA#foo")]
3566    #[case::class_with_singleton("ClassA", "foo", true, "ClassA.foo")]
3567    #[case::empty_returns_empty("", "", false, "")]
3568    fn test_qualified_method_name(
3569        #[case] class_path: &str,
3570        #[case] method_name: &str,
3571        #[case] singleton: bool,
3572        #[case] expected: &str,
3573    ) {
3574        let qualified =
3575            ruby_version::ruby_3_3_0::qualified_method_name(class_path, method_name, singleton);
3576        assert_eq!(expected.to_string(), qualified);
3577    }
3578    #[rstest]
3579    #[case::no_class_uses_label("", "block in foo", "foo", "foo", false, "block in foo")]
3580    #[case::no_method_uses_label("", "block in foo", "foo", "", false, "block in foo")]
3581    #[case::no_class_no_base_label_no_method_uses_label(
3582        "",
3583        "block in foo",
3584        "",
3585        "",
3586        false,
3587        "block in foo"
3588    )]
3589    #[case::class_uses_label_prefix(
3590        "ClassA",
3591        "block in foo",
3592        "foo",
3593        "foo",
3594        false,
3595        "block in ClassA#foo"
3596    )]
3597    #[case::class_uses_label_prefix_singleton(
3598        "ClassA",
3599        "block in foo",
3600        "foo",
3601        "foo",
3602        true,
3603        "block in ClassA.foo"
3604    )]
3605    fn test_profile_frame_full_label(
3606        #[case] class_path: &str,
3607        #[case] label: &str,
3608        #[case] base_label: &str,
3609        #[case] method_name: &str,
3610        #[case] singleton: bool,
3611        #[case] expected: &str,
3612    ) {
3613        let full_label = ruby_version::ruby_3_3_0::profile_frame_full_label(
3614            class_path,
3615            label,
3616            base_label,
3617            method_name,
3618            singleton,
3619        );
3620        assert_eq!(expected.to_string(), full_label);
3621    }
3622}