backtracie 0.3.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.adoc +3 -0
- data/ext/backtracie_native_extension/backtracie.c +182 -245
- data/ext/backtracie_native_extension/backtracie_frames.c +930 -0
- data/ext/backtracie_native_extension/backtracie_private.h +20 -0
- data/ext/backtracie_native_extension/c_test_helpers.c +62 -0
- data/ext/backtracie_native_extension/extconf.rb +36 -42
- data/ext/backtracie_native_extension/public/backtracie.h +268 -0
- data/ext/backtracie_native_extension/strbuilder.c +123 -0
- data/ext/backtracie_native_extension/strbuilder.h +23 -0
- data/lib/backtracie/location.rb +6 -1
- data/lib/backtracie/version.rb +1 -1
- metadata +12 -8
- data/ext/backtracie_native_extension/ruby_shards.c +0 -533
- data/ext/backtracie_native_extension/ruby_shards.h +0 -170
@@ -0,0 +1,20 @@
|
|
1
|
+
#ifndef BACKTRACIE_PRIVATE_H
|
2
|
+
#define BACKTRACIE_PRIVATE_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <stdbool.h>
|
6
|
+
|
7
|
+
// Need to define an assert macro - we might have just used RUBY_ASSERT, but
|
8
|
+
// that's not exported in Ruby < 2.7.
|
9
|
+
#define BACKTRACIE_ASSERT(expr) BACKTRACIE_ASSERT_MSG((expr), (#expr))
|
10
|
+
#define BACKTRACIE_ASSERT_MSG(expr, msg) \
|
11
|
+
do { \
|
12
|
+
if ((expr) == 0) { \
|
13
|
+
rb_bug("backtracie gem: %s:%d: %s", __FILE__, __LINE__, msg); \
|
14
|
+
} \
|
15
|
+
} while (0)
|
16
|
+
#define BACKTRACIE_ASSERT_FAIL(msg) BACKTRACIE_ASSERT_MSG(0, msg)
|
17
|
+
|
18
|
+
bool backtracie_is_thread_alive(VALUE thread);
|
19
|
+
void backtracie_init_c_test_helpers(VALUE backtracie_module);
|
20
|
+
#endif
|
@@ -0,0 +1,62 @@
|
|
1
|
+
#include "backtracie_private.h"
|
2
|
+
#include "public/backtracie.h"
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <ruby/thread.h>
|
6
|
+
|
7
|
+
static VALUE backtracie_backtrace_from_thread(VALUE self);
|
8
|
+
static VALUE backtracie_backtrace_from_thread_cthread(void *ctx);
|
9
|
+
static VALUE stdlib_backtrace_from_thread(VALUE self);
|
10
|
+
static VALUE stdlib_backtrace_from_thread_cthread(void *ctx);
|
11
|
+
static VALUE backtracie_backtrace_from_empty_thread(VALUE self);
|
12
|
+
static VALUE backtracie_backtrace_from_empty_thread_cthread(void *ctx);
|
13
|
+
|
14
|
+
void backtracie_init_c_test_helpers(VALUE backtracie_module) {
|
15
|
+
VALUE test_helpers_mod =
|
16
|
+
rb_define_module_under(backtracie_module, "TestHelpers");
|
17
|
+
rb_define_singleton_method(test_helpers_mod,
|
18
|
+
"backtracie_backtrace_from_thread",
|
19
|
+
backtracie_backtrace_from_thread, 0);
|
20
|
+
rb_define_singleton_method(test_helpers_mod, "stdlib_backtrace_from_thread",
|
21
|
+
stdlib_backtrace_from_thread, 0);
|
22
|
+
rb_define_singleton_method(test_helpers_mod,
|
23
|
+
"backtracie_backtrace_from_empty_thread",
|
24
|
+
backtracie_backtrace_from_empty_thread, 0);
|
25
|
+
}
|
26
|
+
|
27
|
+
static VALUE backtracie_backtrace_from_thread(VALUE self) {
|
28
|
+
VALUE th = rb_thread_create(backtracie_backtrace_from_thread_cthread, NULL);
|
29
|
+
return rb_funcall(th, rb_intern("value"), 0);
|
30
|
+
}
|
31
|
+
|
32
|
+
static VALUE backtracie_backtrace_from_thread_cthread(void *ctx) {
|
33
|
+
VALUE backtracie_mod = rb_const_get(rb_cObject, rb_intern("Backtracie"));
|
34
|
+
return rb_funcall(backtracie_mod, rb_intern("backtrace_locations"), 1,
|
35
|
+
rb_thread_current());
|
36
|
+
}
|
37
|
+
|
38
|
+
static VALUE stdlib_backtrace_from_thread(VALUE self) {
|
39
|
+
VALUE th = rb_thread_create(stdlib_backtrace_from_thread_cthread, NULL);
|
40
|
+
return rb_funcall(th, rb_intern("value"), 0);
|
41
|
+
}
|
42
|
+
|
43
|
+
static VALUE stdlib_backtrace_from_thread_cthread(void *ctx) {
|
44
|
+
return rb_funcall(rb_thread_current(), rb_intern("backtrace_locations"), 0);
|
45
|
+
}
|
46
|
+
|
47
|
+
static VALUE backtracie_backtrace_from_empty_thread(VALUE self) {
|
48
|
+
VALUE backtracie_mod = rb_const_get(rb_cObject, rb_intern("Backtracie"));
|
49
|
+
VALUE th =
|
50
|
+
rb_thread_create(backtracie_backtrace_from_empty_thread_cthread, NULL);
|
51
|
+
VALUE bt =
|
52
|
+
rb_funcall(backtracie_mod, rb_intern("backtrace_locations"), 1, th);
|
53
|
+
rb_thread_kill(th);
|
54
|
+
rb_funcall(th, rb_intern("join"), 0);
|
55
|
+
return bt;
|
56
|
+
}
|
57
|
+
|
58
|
+
static VALUE backtracie_backtrace_from_empty_thread_cthread(void *ctx) {
|
59
|
+
// yield the GVL without creating a frame of any kind
|
60
|
+
rb_thread_sleep(-1);
|
61
|
+
return Qnil;
|
62
|
+
}
|
@@ -18,68 +18,62 @@
|
|
18
18
|
# You should have received a copy of the GNU Lesser General Public License
|
19
19
|
# along with backtracie. If not, see <http://www.gnu.org/licenses/>.
|
20
20
|
|
21
|
-
if [
|
21
|
+
if %w[jruby truffleruby].include?(RUBY_ENGINE)
|
22
22
|
raise \
|
23
|
-
"\n#{
|
23
|
+
"\n#{'-' * 80}\nSorry! This gem is unsupported on #{RUBY_ENGINE}. Since it relies on a lot of guts of MRI Ruby, " \
|
24
24
|
"it's impossible to make a direct port.\n" \
|
25
|
-
"Perhaps a #{RUBY_ENGINE} equivalent could be created -- help is welcome! :)\n#{
|
25
|
+
"Perhaps a #{RUBY_ENGINE} equivalent could be created -- help is welcome! :)\n#{'-' * 80}"
|
26
26
|
end
|
27
27
|
|
28
|
-
require
|
28
|
+
require 'mkmf'
|
29
29
|
|
30
30
|
# This warning gets really annoying when we include the Ruby mjit header file,
|
31
31
|
# let's omit it
|
32
|
-
$CFLAGS <<
|
32
|
+
$CFLAGS << ' ' << '-Wno-unused-function'
|
33
33
|
|
34
34
|
# Really dislike the "define everything at the beginning of the function" thing, sorry!
|
35
|
-
$CFLAGS <<
|
35
|
+
$CFLAGS << ' ' << '-Wno-declaration-after-statement'
|
36
36
|
|
37
37
|
# If we forget to include a Ruby header, the function call may still appear to work, but then
|
38
38
|
# cause a segfault later. Let's ensure that never happens.
|
39
|
-
$CFLAGS <<
|
39
|
+
$CFLAGS << ' ' << '-Werror-implicit-function-declaration'
|
40
40
|
|
41
|
-
# Enable us to use """modern""" C
|
42
|
-
|
43
|
-
|
44
|
-
end
|
41
|
+
# Enable us to use """modern""" C. Note that Ruby requires GNU extensions; specifically,
|
42
|
+
# NSIG is expected to be defined in signal.h.
|
43
|
+
$CFLAGS << ' ' << '-std=gnu99' if RUBY_VERSION < '2.4'
|
45
44
|
|
46
|
-
|
47
|
-
if RUBY_VERSION < "3"
|
48
|
-
$defs << "-DCFUNC_FRAMES_BACKPORT_NEEDED"
|
49
|
-
end
|
45
|
+
$CFLAGS << ' ' << '-DPRE_RB_ISEQ_TYPE' if RUBY_VERSION < '3.2'
|
50
46
|
|
51
|
-
|
52
|
-
if RUBY_VERSION.start_with?("2.6")
|
53
|
-
$defs << "-DCLASSPATH_BACKPORT_NEEDED"
|
54
|
-
end
|
47
|
+
$CFLAGS << ' ' << '-DPRE_GC_MARK_MOVABLE' if RUBY_VERSION < '2.7'
|
55
48
|
|
56
49
|
# Older Rubies don't have the MJIT header, see below for details
|
57
|
-
if RUBY_VERSION <
|
58
|
-
$defs << "-DPRE_MJIT_RUBY"
|
59
|
-
end
|
50
|
+
$defs << '-DPRE_MJIT_RUBY' if RUBY_VERSION < '2.6'
|
60
51
|
|
61
|
-
if RUBY_VERSION <
|
62
|
-
$CFLAGS <<
|
63
|
-
$CFLAGS <<
|
52
|
+
if RUBY_VERSION < '2.5'
|
53
|
+
$CFLAGS << ' ' << '-DPRE_EXECUTION_CONTEXT' # Flag that there's no execution context, we need to use threads instead
|
54
|
+
$CFLAGS << ' ' << '-DPRE_LOCATION_PATHOBJ'
|
55
|
+
$CFLAGS << ' ' << '-Wno-attributes' # Silence a few warnings that we can't do anything about
|
64
56
|
end
|
65
57
|
|
66
|
-
if RUBY_VERSION <
|
67
|
-
$CFLAGS <<
|
58
|
+
if RUBY_VERSION < '2.4'
|
59
|
+
$CFLAGS << ' ' << '-DPRE_VM_ENV_RENAMES' # Flag that it's a really old Ruby, and a few constants were since renamed
|
68
60
|
end
|
69
61
|
|
62
|
+
$CFLAGS << ' ' << '-DBACKTRACIE_EXPORTS'
|
63
|
+
append_cflags ['-fvisibility=hidden']
|
70
64
|
create_header
|
71
65
|
|
72
|
-
if RUBY_VERSION <
|
66
|
+
if RUBY_VERSION < '2.6'
|
73
67
|
# Use the debase-ruby_core_source gem to get access to Ruby internal structures (no MJIT header -- the preferred
|
74
68
|
# option -- is available for these older Rubies)
|
75
69
|
|
76
|
-
require
|
77
|
-
dir_config(
|
78
|
-
|
79
|
-
proc { [
|
80
|
-
|
70
|
+
require 'debase/ruby_core_source'
|
71
|
+
dir_config('ruby') # allow user to pass in non-standard core include directory
|
72
|
+
unless Debase::RubyCoreSource.create_makefile_with_core(
|
73
|
+
proc { ['vm_core.h', 'method.h', 'iseq.h', 'regenc.h'].map { |it| have_header(it) }.uniq == [true] },
|
74
|
+
'backtracie_native_extension'
|
81
75
|
)
|
82
|
-
raise
|
76
|
+
raise 'Error during native gem setup -- `Debase::RubyCoreSource.create_makefile_with_core` failed'
|
83
77
|
end
|
84
78
|
|
85
79
|
else
|
@@ -90,14 +84,14 @@ else
|
|
90
84
|
# containing the exact file, so that it can be used in a #include in the C code.
|
91
85
|
header_contents =
|
92
86
|
File.read($extconf_h)
|
93
|
-
|
94
|
-
|
95
|
-
|
87
|
+
.sub('#endif',
|
88
|
+
<<~EXTCONF_H.strip
|
89
|
+
#define RUBY_MJIT_HEADER "rb_mjit_min_header-#{RUBY_VERSION}.h"
|
96
90
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
File.open($extconf_h,
|
91
|
+
#endif
|
92
|
+
EXTCONF_H
|
93
|
+
)
|
94
|
+
File.open($extconf_h, 'w') { |file| file.puts(header_contents) }
|
101
95
|
|
102
|
-
create_makefile
|
96
|
+
create_makefile 'backtracie_native_extension'
|
103
97
|
end
|
@@ -0,0 +1,268 @@
|
|
1
|
+
// backtracie: Ruby gem for beautiful backtraces
|
2
|
+
// Copyright (C) 2021 Ivo Anjo <ivo@ivoanjo.me>
|
3
|
+
//
|
4
|
+
// This file is part of backtracie.
|
5
|
+
//
|
6
|
+
// backtracie is free software: you can redistribute it and/or modify
|
7
|
+
// it under the terms of the GNU Lesser General Public License as published by
|
8
|
+
// the Free Software Foundation, either version 3 of the License, or
|
9
|
+
// (at your option) any later version.
|
10
|
+
//
|
11
|
+
// backtracie is distributed in the hope that it will be useful,
|
12
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
// GNU Lesser General Public License for more details.
|
15
|
+
//
|
16
|
+
// You should have received a copy of the GNU Lesser General Public License
|
17
|
+
// along with backtracie. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
#ifndef BACKTRACIE_PUBLIC_H
|
20
|
+
#define BACKTRACIE_PUBLIC_H
|
21
|
+
|
22
|
+
#include <ruby.h>
|
23
|
+
#include <stdbool.h>
|
24
|
+
#include <stdint.h>
|
25
|
+
|
26
|
+
// clang-format off
|
27
|
+
#if defined(_WIN32) || defined(_WIN64)
|
28
|
+
# ifdef BACKTRACIE_EXPORTS
|
29
|
+
# define BACKTRACIE_API __declspec(dllexport)
|
30
|
+
# else
|
31
|
+
# define BACKTRACIE_API __declspec(dllimport)
|
32
|
+
# endif
|
33
|
+
#else
|
34
|
+
# define BACKTRACIE_API __attribute__((visibility("default")))
|
35
|
+
#endif
|
36
|
+
// clang-format on
|
37
|
+
|
38
|
+
typedef struct {
|
39
|
+
// The first element of this struct is a bitfield, comprised of uint32_t's.
|
40
|
+
// Ruby requires that VALUE be at least 32-bit but not necessarily 64-bit;
|
41
|
+
// using uint32_t for the bitfield ensures we have a minimum of pointless
|
42
|
+
// padding in this struct no matter the system.
|
43
|
+
|
44
|
+
// 1 -> ruby frame / 0 -> cfunc frame
|
45
|
+
uint32_t is_ruby_frame : 1;
|
46
|
+
// 1 means self really is the self object, 0 means it's the class of the self.
|
47
|
+
uint32_t self_is_real_self : 1;
|
48
|
+
// The iseq & cme; one, both, or neither might actually be available; if
|
49
|
+
// they're not, they will be Qnil. These need to be GC marked if you wish to
|
50
|
+
// keep the location alive.
|
51
|
+
VALUE iseq;
|
52
|
+
VALUE callable_method_entry;
|
53
|
+
// This is either self, or rb_class_of(self), depending on a few thing.
|
54
|
+
// It will be the actual self object for the frame if
|
55
|
+
// - self is the toplevel binding,
|
56
|
+
// - self is rb_mRubyVMFrozenCore,
|
57
|
+
// - self is a class or module
|
58
|
+
// Otherwise, it will be rb_class_of(self).
|
59
|
+
// This is done so that, should the caller decide to retain the raw_location
|
60
|
+
// instance for a while, GC mark its VALUEs, and stringify the backtrace
|
61
|
+
// later, we both:
|
62
|
+
// - retain enough information to actually produce a good name for the
|
63
|
+
// method,
|
64
|
+
// - but also don't hold onto random objects that would otherwise be GC'd
|
65
|
+
// just because they happened to be in a backtrace.
|
66
|
+
// This needs to be marked if you wish to keep the location alive
|
67
|
+
VALUE self_or_self_class;
|
68
|
+
// Raw PC pointer; not much use to callers, but saved so we can calculate the
|
69
|
+
// lineno later on.
|
70
|
+
const void *pc;
|
71
|
+
} raw_location;
|
72
|
+
|
73
|
+
// Returns the number of Ruby frames currently live on the thread; this
|
74
|
+
// can be used to judge how many times backtracie_capture_frame_for_thread()
|
75
|
+
// should be called to capture the actual frames.
|
76
|
+
BACKTRACIE_API int backtracie_frame_count_for_thread(VALUE thread);
|
77
|
+
// Fills in the raw_location struct with the details of the Ruby call stack
|
78
|
+
// frame on the given thread at the given index. The given index may or may not
|
79
|
+
// refer to a "valid" frame on the ruby stack; there are some entries on the
|
80
|
+
// Ruby stack which are not actually meaningful from a call-stack perspective.
|
81
|
+
//
|
82
|
+
// The maximum allowable value for i is
|
83
|
+
// (backtracie_frame_count_for_thread(thread) - 1). Any value higher than this
|
84
|
+
// will crash.
|
85
|
+
|
86
|
+
// If the given index DOES refer to a valid frame on the Ruby stack:
|
87
|
+
// - *loc will be filled in with details of the frame,
|
88
|
+
// - the return value will be true
|
89
|
+
// If the given index DOES NOT refer to a valid frame, then:
|
90
|
+
// - *loc will be totally untouched,
|
91
|
+
// - the return value will be false
|
92
|
+
//
|
93
|
+
// The intended usage of this API looks something like this:
|
94
|
+
//
|
95
|
+
// VALUE thread = rb_thread_current();
|
96
|
+
// int max_frame_count = backtracie_frame_count_for_thread(thread);
|
97
|
+
// raw_location *locs = xcalloc(frame_count ,sizeof(raw_location));
|
98
|
+
// int frame_counter = 0;
|
99
|
+
// for (int i = 0; i < max_frame_count; i++) {
|
100
|
+
// bool is_valid = backtracie_capture_frame_for_thread(thread, i,
|
101
|
+
// &locs[frame_counter]);
|
102
|
+
// if (is_valid) frame_counter++;
|
103
|
+
// }
|
104
|
+
BACKTRACIE_API
|
105
|
+
bool backtracie_capture_frame_for_thread(VALUE thread, int frame_index,
|
106
|
+
raw_location *loc);
|
107
|
+
// Get the "qualified method name" for the frame. This is a string that best
|
108
|
+
// describes what method is being called, intended for human interpretation.
|
109
|
+
// Writes a NULL-term'd string of at most buflen chars (including NULL
|
110
|
+
// terminator) to *buf. It returns the number of characters (NOT including NULL
|
111
|
+
// terminator) that would be required to store the full string (which might be >
|
112
|
+
// buflen, if it has been truncated). Essentially, has the same semantics as
|
113
|
+
// snprintf or strlcat.
|
114
|
+
BACKTRACIE_API
|
115
|
+
size_t backtracie_frame_name_cstr(const raw_location *loc, char *buf,
|
116
|
+
size_t buflen);
|
117
|
+
// Like backtracie_frame_name_cstr, but will allocate memory to ensure that
|
118
|
+
// there is no truncation of the method name; returns a Ruby string.
|
119
|
+
BACKTRACIE_API
|
120
|
+
VALUE backtracie_frame_name_rbstr(const raw_location *loc);
|
121
|
+
// Returns the filename of the source file for this backtrace location. Has the
|
122
|
+
// same string handling semantics as backtracie_frame_name_cstr.
|
123
|
+
//
|
124
|
+
// Pass true/false for absolute to get the full, realpath vs just the path.
|
125
|
+
//
|
126
|
+
// If loc is a cfunc, and therefore has no filename, 0 is returned (and no
|
127
|
+
// string is written to *buf)
|
128
|
+
BACKTRACIE_API
|
129
|
+
size_t backtracie_frame_filename_cstr(const raw_location *loc, bool absolute,
|
130
|
+
char *buf, size_t buflen);
|
131
|
+
// Like backtracie_frame_filename_cstr, but returns a Ruby string. Will allocate
|
132
|
+
// memory to ensure there is no truncation. Returns Qnil if there is no
|
133
|
+
// filename.
|
134
|
+
BACKTRACIE_API
|
135
|
+
VALUE backtracie_frame_filename_rbstr(const raw_location *loc, bool absolute);
|
136
|
+
|
137
|
+
// Returns the source line number of the given location, if it's a Ruby frame
|
138
|
+
// (otherwise, returns 0).
|
139
|
+
BACKTRACIE_API
|
140
|
+
int backtracie_frame_line_number(const raw_location *loc);
|
141
|
+
// Returns a string which would be like the "label" returned by
|
142
|
+
// rb_profile_frames or Thread#backtrace or similar.
|
143
|
+
BACKTRACIE_API
|
144
|
+
size_t backtracie_frame_label_cstr(const raw_location *loc, bool base,
|
145
|
+
char *buf, size_t buflen);
|
146
|
+
// Like backtracie_frame_label_cstr, but returns a ruby string.
|
147
|
+
BACKTRACIE_API
|
148
|
+
VALUE backtracie_frame_label_rbstr(const raw_location *loc, bool base);
|
149
|
+
// Returns a VALUE that can be passed into the rb_profile_frames family of
|
150
|
+
// methods
|
151
|
+
BACKTRACIE_API
|
152
|
+
VALUE backtracie_frame_for_rb_profile(const raw_location *loc);
|
153
|
+
// Marks the contained ruby objects; for use when you want to persist the
|
154
|
+
// raw_location object beyond the current call stack.
|
155
|
+
BACKTRACIE_API
|
156
|
+
void backtracie_frame_mark(const raw_location *loc);
|
157
|
+
// Like backtracie_frame_mark, but calls rb_gc_mark_movable if available.
|
158
|
+
BACKTRACIE_API
|
159
|
+
void backtracie_frame_mark_movable(const raw_location *loc);
|
160
|
+
// Updates *loc's VALUEs to point to possibly-moved locations
|
161
|
+
BACKTRACIE_API
|
162
|
+
void backtracie_frame_compact(raw_location *loc);
|
163
|
+
|
164
|
+
// This is an optional facility to create a VALUE containing an array of
|
165
|
+
// backtracie frames, which itself owns the process of marking them. When using
|
166
|
+
// this, you need only mark the wrapper, and the wrapper will take care of
|
167
|
+
// marking the individual frames. NOTE - if you keep a reference to this value
|
168
|
+
// on the stack, you _probably_ need to use RB_GC_GUARD() on it, because the
|
169
|
+
// Ruby GC cannot trace usage of the underlying pointer.
|
170
|
+
BACKTRACIE_API
|
171
|
+
VALUE backtracie_frame_wrapper_new(size_t capa);
|
172
|
+
// Returns the underying array of frames for use
|
173
|
+
BACKTRACIE_API
|
174
|
+
raw_location *backtracie_frame_wrapper_frames(VALUE wrapper);
|
175
|
+
// This returns a pointer to the len, so you can update the size of the
|
176
|
+
// contained list of frames (up to capa)
|
177
|
+
BACKTRACIE_API
|
178
|
+
int *backtracie_frame_wrapper_len(VALUE wrapper);
|
179
|
+
|
180
|
+
// ========= "Minimal" API ========
|
181
|
+
// This part of the API defines a "minimal" version of raw_location, called
|
182
|
+
// minimal_location_t. The problem this solves is that marking the iseq &
|
183
|
+
// callable method entry stored on the raw_location struct can actually mark a
|
184
|
+
// whole lot of memory, but only a small amount of those things are actually
|
185
|
+
// needed to compute the method name.
|
186
|
+
|
187
|
+
// These values define union descriminator values for minimal_location_t.
|
188
|
+
// It's tempting to make this an enum, but apparently an enum in a bitfield is
|
189
|
+
// implementation-defined behaviour.
|
190
|
+
// It would also be tempting to define these as "const uint16_t", but the
|
191
|
+
// ancient version of mingw used to build for Ruby 2.3 on Windows doesn't like
|
192
|
+
// using such a constant in a switch statement somehow (??).
|
193
|
+
// So... #define's it is.
|
194
|
+
#define BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF 0u
|
195
|
+
#define BACKTRACIE_METHOD_QUALIFIER_CONTENTS_SELF_CLASS 1u
|
196
|
+
#define BACKTRACIE_METHOD_QUALIFIER_CONTENTS_CME_CLASS 2u
|
197
|
+
#define BACKTRACIE_METHOD_NAME_CONTENTS_CME_ID 0u
|
198
|
+
#define BACKTRACIE_METHOD_NAME_CONTENTS_BASE_LABEL 1u
|
199
|
+
|
200
|
+
typedef struct {
|
201
|
+
// 1 -> ruby frame / 0 -> cfunc frame
|
202
|
+
uint16_t is_ruby_frame : 1;
|
203
|
+
// What is in the method_qualifier field?
|
204
|
+
// 0 -> self
|
205
|
+
// 1 -> self_class
|
206
|
+
// 2 -> cme_defined_class
|
207
|
+
uint16_t method_qualifier_contents : 2;
|
208
|
+
// 0 -> cme_method_id
|
209
|
+
// 1 -> base_label
|
210
|
+
uint16_t method_name_contents : 1;
|
211
|
+
// 1 means iseq_type is populated
|
212
|
+
uint16_t has_iseq_type : 1;
|
213
|
+
|
214
|
+
// Comes from iseq->body->type; is an iseq_type enum.
|
215
|
+
// 5 bits is _way_ more than enough to store this enum.
|
216
|
+
uint16_t iseq_type : 5;
|
217
|
+
|
218
|
+
// We have six bits left over if someone can come up with a good use for them.
|
219
|
+
uint16_t reserved_bits : 6;
|
220
|
+
|
221
|
+
// Line number - we calculate this at capture time because it's fast, and it
|
222
|
+
// saves us from storing the iseq.
|
223
|
+
uint32_t line_number;
|
224
|
+
|
225
|
+
// Contains either the callable method entry ID or the iseq base label. The
|
226
|
+
// former if has_cme_method_id is set, the latter if not.
|
227
|
+
// If has_cme_method_id is set, this need not be marked, but if it is NOT set,
|
228
|
+
// then method_name.base_label needs to be marked.
|
229
|
+
union {
|
230
|
+
// Comes from callable_method_entry->called_id
|
231
|
+
ID cme_method_id;
|
232
|
+
// Comes from iseq->body->location.base_label
|
233
|
+
VALUE base_label;
|
234
|
+
} method_name;
|
235
|
+
|
236
|
+
// VALUE for the filename (abs), or Qnil
|
237
|
+
// Needs to be marked
|
238
|
+
VALUE filename;
|
239
|
+
|
240
|
+
// See method_qualifier_contents flag for possible values that can be in here.
|
241
|
+
// Needs to be marked
|
242
|
+
union {
|
243
|
+
VALUE self; // the VALUE the method was called on
|
244
|
+
VALUE self_class; // rb_class_of(the VALUE the method was called on)
|
245
|
+
VALUE cme_defined_class; // the class that the callable_method_entry is
|
246
|
+
// defined on
|
247
|
+
} method_qualifier;
|
248
|
+
} minimal_location_t;
|
249
|
+
|
250
|
+
// This is like backtracie_capture_frame_for_thread, but captures a
|
251
|
+
// minimal_location_t instead of a raw_location.
|
252
|
+
BACKTRACIE_API
|
253
|
+
bool backtracie_capture_minimal_frame_for_thread(VALUE thread, int frame_index,
|
254
|
+
minimal_location_t *loc);
|
255
|
+
|
256
|
+
// This is like backtracie_frame_name_cstr, but works on a minimal_location_t
|
257
|
+
// instead of a raw_location
|
258
|
+
BACKTRACIE_API
|
259
|
+
size_t backtracie_minimal_frame_name_cstr(const minimal_location_t *loc,
|
260
|
+
char *buf, size_t buflen);
|
261
|
+
|
262
|
+
// This is like backtracie_frame_filename_cstr, but works on a
|
263
|
+
// minimal_location_t instead of a raw_location. Note that this always returns
|
264
|
+
// the absolute filename.
|
265
|
+
BACKTRACIE_API
|
266
|
+
size_t backtracie_minimal_frame_filename_cstr(const minimal_location_t *loc,
|
267
|
+
char *buf, size_t buflen);
|
268
|
+
#endif
|
@@ -0,0 +1,123 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <stdarg.h>
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <stdlib.h>
|
5
|
+
|
6
|
+
#include "backtracie_private.h"
|
7
|
+
#include "strbuilder.h"
|
8
|
+
|
9
|
+
void strbuilder_init(strbuilder_t *str, char *buf, size_t bufsize) {
|
10
|
+
str->original_buf = buf;
|
11
|
+
str->original_bufsize = bufsize;
|
12
|
+
str->curr_ptr = str->original_buf;
|
13
|
+
str->attempted_size = 0;
|
14
|
+
if (str->original_bufsize > 0) {
|
15
|
+
str->original_buf[0] = '\0';
|
16
|
+
}
|
17
|
+
str->growable = false;
|
18
|
+
}
|
19
|
+
|
20
|
+
void strbuilder_init_growable(strbuilder_t *str, size_t initial_bufsize) {
|
21
|
+
str->original_bufsize = initial_bufsize;
|
22
|
+
str->original_buf = malloc(str->original_bufsize);
|
23
|
+
str->curr_ptr = str->original_buf;
|
24
|
+
str->attempted_size = 0;
|
25
|
+
if (str->original_bufsize > 0) {
|
26
|
+
str->original_buf[0] = '\0';
|
27
|
+
}
|
28
|
+
str->growable = true;
|
29
|
+
}
|
30
|
+
|
31
|
+
void strbuilder_free_growable(strbuilder_t *str) {
|
32
|
+
BACKTRACIE_ASSERT(str->growable);
|
33
|
+
free(str->original_buf);
|
34
|
+
}
|
35
|
+
|
36
|
+
static void strbuilder_grow(strbuilder_t *str) {
|
37
|
+
ptrdiff_t offset = str->curr_ptr - str->original_buf;
|
38
|
+
str->original_bufsize = str->original_bufsize * 2;
|
39
|
+
str->original_buf = realloc(str->original_buf, str->original_bufsize);
|
40
|
+
str->curr_ptr = str->original_buf + offset;
|
41
|
+
}
|
42
|
+
|
43
|
+
void strbuilder_appendf(strbuilder_t *str, const char *fmt, ...) {
|
44
|
+
va_list fmtargs;
|
45
|
+
va_start(fmtargs, fmt);
|
46
|
+
|
47
|
+
retry:;
|
48
|
+
// The size left in the buffer
|
49
|
+
size_t max_writesize =
|
50
|
+
str->original_bufsize - (str->curr_ptr - str->original_buf);
|
51
|
+
// vsnprintf returns the number of bytes it _would_ have written, not
|
52
|
+
// including the null terminator.
|
53
|
+
size_t attempted_writesize_wo_nullterm =
|
54
|
+
vsnprintf(str->curr_ptr, max_writesize, fmt, fmtargs);
|
55
|
+
if (attempted_writesize_wo_nullterm >= max_writesize) {
|
56
|
+
// Can we grow & retry?
|
57
|
+
if (str->growable) {
|
58
|
+
strbuilder_grow(str);
|
59
|
+
goto retry;
|
60
|
+
}
|
61
|
+
// If the string (including nullterm) would have exceeded the bufsize,
|
62
|
+
// point str->curr_ptr to one-past-the-end of the buffer.
|
63
|
+
// This will make subsequent calls to vsnprintf() receive zero for
|
64
|
+
// max_writesize, no further things can be appended to this buffer.
|
65
|
+
str->curr_ptr = str->original_buf + str->original_bufsize;
|
66
|
+
} else {
|
67
|
+
// If there's still room in the buffer, advance the curr_ptr.
|
68
|
+
str->curr_ptr = str->curr_ptr + attempted_writesize_wo_nullterm;
|
69
|
+
}
|
70
|
+
str->attempted_size += attempted_writesize_wo_nullterm;
|
71
|
+
va_end(fmtargs);
|
72
|
+
}
|
73
|
+
|
74
|
+
void strbuilder_append(strbuilder_t *str, const char *cat) {
|
75
|
+
retry:;
|
76
|
+
size_t max_writesize =
|
77
|
+
str->original_bufsize - (str->curr_ptr - str->original_buf);
|
78
|
+
size_t attempted_writesize_wo_nullterm =
|
79
|
+
strlcat(str->curr_ptr, cat, max_writesize);
|
80
|
+
if (attempted_writesize_wo_nullterm >= max_writesize) {
|
81
|
+
if (str->growable) {
|
82
|
+
strbuilder_grow(str);
|
83
|
+
goto retry;
|
84
|
+
}
|
85
|
+
str->curr_ptr = str->original_buf + str->original_bufsize;
|
86
|
+
} else {
|
87
|
+
str->curr_ptr = str->curr_ptr + attempted_writesize_wo_nullterm;
|
88
|
+
}
|
89
|
+
str->attempted_size += attempted_writesize_wo_nullterm;
|
90
|
+
}
|
91
|
+
|
92
|
+
void strbuilder_append_value(strbuilder_t *str, VALUE val) {
|
93
|
+
BACKTRACIE_ASSERT(RB_TYPE_P(val, T_STRING));
|
94
|
+
|
95
|
+
const char *val_ptr = RSTRING_PTR(val);
|
96
|
+
size_t val_len = RSTRING_LEN(val);
|
97
|
+
|
98
|
+
retry:;
|
99
|
+
size_t max_writesize =
|
100
|
+
str->original_bufsize - (str->curr_ptr - str->original_buf);
|
101
|
+
size_t chars_to_copy = val_len;
|
102
|
+
if (chars_to_copy + 1 > max_writesize) {
|
103
|
+
if (str->growable) {
|
104
|
+
strbuilder_grow(str);
|
105
|
+
goto retry;
|
106
|
+
}
|
107
|
+
chars_to_copy = max_writesize - 1; // leave room for NULL terminator.
|
108
|
+
}
|
109
|
+
memcpy(str->curr_ptr, val_ptr, chars_to_copy);
|
110
|
+
str->curr_ptr[chars_to_copy] = '\0';
|
111
|
+
str->attempted_size += val_len;
|
112
|
+
if (val_len + 1 > max_writesize) {
|
113
|
+
str->curr_ptr = str->original_buf + str->original_bufsize;
|
114
|
+
} else {
|
115
|
+
str->curr_ptr += val_len;
|
116
|
+
}
|
117
|
+
|
118
|
+
RB_GC_GUARD(val);
|
119
|
+
}
|
120
|
+
|
121
|
+
VALUE strbuilder_to_value(strbuilder_t *str) {
|
122
|
+
return rb_str_new2(str->original_buf);
|
123
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#ifndef STRBUILDER_H
|
2
|
+
#define STRBUILDER_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
#include <stdbool.h>
|
6
|
+
#include <stddef.h>
|
7
|
+
|
8
|
+
typedef struct {
|
9
|
+
char *original_buf;
|
10
|
+
char *curr_ptr;
|
11
|
+
size_t original_bufsize;
|
12
|
+
size_t attempted_size;
|
13
|
+
bool growable;
|
14
|
+
} strbuilder_t;
|
15
|
+
|
16
|
+
void strbuilder_append(strbuilder_t *str, const char *cat);
|
17
|
+
void strbuilder_appendf(strbuilder_t *str, const char *fmt, ...);
|
18
|
+
void strbuilder_append_value(strbuilder_t *str, VALUE val);
|
19
|
+
VALUE strbuilder_to_value(strbuilder_t *str);
|
20
|
+
void strbuilder_init(strbuilder_t *str, char *buf, size_t bufsize);
|
21
|
+
void strbuilder_init_growable(strbuilder_t *str, size_t initial_bufsize);
|
22
|
+
void strbuilder_free_growable(strbuilder_t *str);
|
23
|
+
#endif
|
data/lib/backtracie/location.rb
CHANGED
@@ -27,16 +27,21 @@ module Backtracie
|
|
27
27
|
attr_accessor :lineno
|
28
28
|
attr_accessor :path
|
29
29
|
attr_accessor :qualified_method_name
|
30
|
+
attr_accessor :path_is_synthetic
|
30
31
|
|
31
32
|
# Note: The order of arguments is hardcoded in the native extension in the `new_location` function --
|
32
33
|
# keep them in sync
|
33
|
-
def initialize(
|
34
|
+
def initialize(
|
35
|
+
absolute_path, base_label, label, lineno, path, qualified_method_name,
|
36
|
+
path_is_synthetic, debug
|
37
|
+
)
|
34
38
|
@absolute_path = absolute_path
|
35
39
|
@base_label = base_label
|
36
40
|
@label = label
|
37
41
|
@lineno = lineno
|
38
42
|
@path = path
|
39
43
|
@qualified_method_name = qualified_method_name
|
44
|
+
@path_is_synthetic = path_is_synthetic
|
40
45
|
@debug = debug
|
41
46
|
|
42
47
|
freeze
|
data/lib/backtracie/version.rb
CHANGED