backtracie 0.3.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.adoc +3 -0
- data/ext/backtracie_native_extension/backtracie.c +182 -245
- data/ext/backtracie_native_extension/backtracie_frames.c +941 -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 +11 -17
- 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 +9 -5
- 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,7 +18,7 @@
|
|
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
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" \
|
@@ -38,28 +38,20 @@ $CFLAGS << " " << "-Wno-declaration-after-statement"
|
|
38
38
|
# cause a segfault later. Let's ensure that never happens.
|
39
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 < "2.6"
|
58
|
-
$defs << "-DPRE_MJIT_RUBY"
|
59
|
-
end
|
50
|
+
$defs << "-DPRE_MJIT_RUBY" if RUBY_VERSION < "2.6"
|
60
51
|
|
61
52
|
if RUBY_VERSION < "2.5"
|
62
53
|
$CFLAGS << " " << "-DPRE_EXECUTION_CONTEXT" # Flag that there's no execution context, we need to use threads instead
|
54
|
+
$CFLAGS << " " << "-DPRE_LOCATION_PATHOBJ"
|
63
55
|
$CFLAGS << " " << "-Wno-attributes" # Silence a few warnings that we can't do anything about
|
64
56
|
end
|
65
57
|
|
@@ -67,6 +59,8 @@ if RUBY_VERSION < "2.4"
|
|
67
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
66
|
if RUBY_VERSION < "2.6"
|
@@ -75,7 +69,7 @@ if RUBY_VERSION < "2.6"
|
|
75
69
|
|
76
70
|
require "debase/ruby_core_source"
|
77
71
|
dir_config("ruby") # allow user to pass in non-standard core include directory
|
78
|
-
|
72
|
+
unless Debase::RubyCoreSource.create_makefile_with_core(
|
79
73
|
proc { ["vm_core.h", "method.h", "iseq.h", "regenc.h"].map { |it| have_header(it) }.uniq == [true] },
|
80
74
|
"backtracie_native_extension"
|
81
75
|
)
|
@@ -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, ...) __attribute__ ((format (printf, 2, 3)));
|
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
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backtracie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivo Anjo
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: debase-ruby_core_source
|
@@ -45,9 +45,13 @@ files:
|
|
45
45
|
- README.adoc
|
46
46
|
- backtracie.gemspec
|
47
47
|
- ext/backtracie_native_extension/backtracie.c
|
48
|
+
- ext/backtracie_native_extension/backtracie_frames.c
|
49
|
+
- ext/backtracie_native_extension/backtracie_private.h
|
50
|
+
- ext/backtracie_native_extension/c_test_helpers.c
|
48
51
|
- ext/backtracie_native_extension/extconf.rb
|
49
|
-
- ext/backtracie_native_extension/
|
50
|
-
- ext/backtracie_native_extension/
|
52
|
+
- ext/backtracie_native_extension/public/backtracie.h
|
53
|
+
- ext/backtracie_native_extension/strbuilder.c
|
54
|
+
- ext/backtracie_native_extension/strbuilder.h
|
51
55
|
- lib/backtracie.rb
|
52
56
|
- lib/backtracie/location.rb
|
53
57
|
- lib/backtracie/version.rb
|
@@ -71,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
75
|
- !ruby/object:Gem::Version
|
72
76
|
version: '0'
|
73
77
|
requirements: []
|
74
|
-
rubygems_version: 3.1
|
78
|
+
rubygems_version: 3.4.1
|
75
79
|
signing_key:
|
76
80
|
specification_version: 4
|
77
81
|
summary: Ruby gem for beautiful backtraces
|