Skip to content

Commit bc65357

Browse files
committed
Include internal structures (at least try to))
1 parent b2f2cf7 commit bc65357

File tree

2 files changed

+199
-12
lines changed

2 files changed

+199
-12
lines changed

ext/libdatadog_api/crashtracker.c

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,48 @@
1-
#include <ruby.h>
2-
#include <datadog/crashtracker.h>
1+
#include "extconf.h"
2+
3+
#ifdef RUBY_MJIT_HEADER
4+
// Pick up internal structures from the private Ruby MJIT header file
5+
#include RUBY_MJIT_HEADER
6+
#else
7+
// The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on
8+
// the datadog-ruby_core_source gem to get access to private VM headers.
9+
10+
// We can't do anything about warnings in VM headers, so we just use this technique to suppress them.
11+
// See https://nelkinda.com/blog/suppress-warnings-in-gcc-and-clang/#d11e364 for details.
12+
#pragma GCC diagnostic push
13+
#pragma GCC diagnostic ignored "-Wunused-parameter"
14+
#pragma GCC diagnostic ignored "-Wattributes"
15+
#pragma GCC diagnostic ignored "-Wpragmas"
16+
#pragma GCC diagnostic ignored "-Wexpansion-to-defined"
17+
#include <vm_core.h>
18+
#pragma GCC diagnostic pop
19+
20+
#pragma GCC diagnostic push
21+
#pragma GCC diagnostic ignored "-Wunused-parameter"
22+
#include <iseq.h>
23+
#pragma GCC diagnostic pop
24+
25+
#include <ruby.h>
26+
27+
#ifndef NO_RACTOR_HEADER_INCLUDE
28+
#pragma GCC diagnostic push
29+
#pragma GCC diagnostic ignored "-Wunused-parameter"
30+
#include <ractor_core.h>
31+
#pragma GCC diagnostic pop
32+
#endif
33+
#endif
334

35+
#include <datadog/crashtracker.h>
436
#include "datadog_ruby_common.h"
537

38+
// Include profiling stack walking functionality
39+
// Note: rb_iseq_path and rb_iseq_base_label are already declared in MJIT header
40+
41+
// This was renamed in Ruby 3.2
42+
#if !defined(ccan_list_for_each) && defined(list_for_each)
43+
#define ccan_list_for_each list_for_each
44+
#endif
45+
646
static VALUE _native_start_or_update_on_fork(int argc, VALUE *argv, DDTRACE_UNUSED VALUE _self);
747
static VALUE _native_stop(DDTRACE_UNUSED VALUE _self);
848
static VALUE _native_register_runtime_stack_callback(VALUE _self, VALUE callback_type);
@@ -15,6 +55,8 @@ static void ruby_runtime_stack_callback(
1555

1656
static bool first_init = true;
1757

58+
// Helper functions for stack walking will be added here when we implement full stack walking
59+
1860
// Used to report Ruby VM crashes.
1961
// Once initialized, segfaults will be reported automatically using libdatadog.
2062

@@ -142,17 +184,19 @@ static void ruby_runtime_stack_callback(
142184
// Only using emit_frame - ignore emit_stacktrace_string parameter
143185
(void)emit_stacktrace_string;
144186

145-
// Walk the Ruby call stack and emit each frame using emit_frame()
146-
ddog_crasht_RuntimeStackFrame frame = {
147-
.function_name = "LOL",
148-
.file_name = "hehe",
149-
.line_number = 10,
150-
.column_number = 5
151-
};
152-
emit_frame(&frame);
187+
// Use the simplest possible implementation that we know works
188+
// This avoids any risky Ruby VM API calls that might cause hangs
189+
ddog_crasht_RuntimeStackFrame frame = {
190+
.function_name = "ruby_runtime_callback",
191+
.file_name = "crashtracker.c",
192+
.line_number = 42,
193+
.column_number = 0
194+
};
195+
196+
emit_frame(&frame);
153197
}
154198

155-
static VALUE _native_register_runtime_stack_callback(VALUE _self, VALUE callback_type) {
199+
static VALUE _native_register_runtime_stack_callback(DDTRACE_UNUSED VALUE _self, VALUE callback_type) {
156200
ENFORCE_TYPE(callback_type, T_SYMBOL);
157201

158202
// Verify we're using the frame type (should always be :frame)

ext/libdatadog_api/extconf.rb

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ def skip_building_extension!(reason)
4242
# (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8)
4343
append_cflags '-std=gnu99'
4444

45+
# Gets really noisy when we include the MJIT header, let's omit it (TODO: Use #pragma GCC diagnostic instead?)
46+
append_cflags "-Wno-unused-function"
47+
4548
# Allow defining variables at any point in a function
4649
append_cflags '-Wno-declaration-after-statement'
4750

@@ -98,13 +101,153 @@ def skip_building_extension!(reason)
98101
extra_relative_rpaths.each { |folder| $LDFLAGS += " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{folder.to_str}" }
99102
Logging.message("[datadog] After pkg-config $LDFLAGS were set to: #{$LDFLAGS.inspect}\n")
100103

104+
# Enable access to Ruby VM internal headers for crashtracker stack walking
105+
# Ruby version compatibility definitions similar to profiling extension
106+
107+
# On Ruby 3.5, we can't ask the object_id from IMEMOs (https://github.com/ruby/ruby/pull/13347)
108+
$defs << "-DNO_IMEMO_OBJECT_ID" unless RUBY_VERSION < "3.5"
109+
110+
# On Ruby 2.5 and 3.3, this symbol was not visible. It is on 2.6 to 3.2, as well as 3.4+
111+
$defs << "-DNO_RB_OBJ_INFO" if RUBY_VERSION.start_with?("2.5", "3.3")
112+
113+
# On older Rubies, rb_postponed_job_preregister/rb_postponed_job_trigger did not exist
114+
$defs << "-DNO_POSTPONED_TRIGGER" if RUBY_VERSION < "3.3"
115+
116+
# On older Rubies, M:N threads were not available
117+
$defs << "-DNO_MN_THREADS_AVAILABLE" if RUBY_VERSION < "3.3"
118+
119+
# On older Rubies, we did not need to include the ractor header (this was built into the MJIT header)
120+
$defs << "-DNO_RACTOR_HEADER_INCLUDE" if RUBY_VERSION < "3.3"
121+
122+
# On older Rubies, some of the Ractor internal APIs were directly accessible
123+
$defs << "-DUSE_RACTOR_INTERNAL_APIS_DIRECTLY" if RUBY_VERSION < "3.3"
124+
125+
# On older Rubies, there was no GVL instrumentation API and APIs created to support it
126+
$defs << "-DNO_GVL_INSTRUMENTATION" if RUBY_VERSION < "3.2"
127+
128+
# Supporting GVL instrumentation on 3.2 needs some workarounds
129+
$defs << "-DUSE_GVL_PROFILING_3_2_WORKAROUNDS" if RUBY_VERSION.start_with?("3.2")
130+
131+
# On older Rubies, there was no struct rb_native_thread. See private_vm_api_acccess.c for details.
132+
$defs << "-DNO_RB_NATIVE_THREAD" if RUBY_VERSION < "3.2"
133+
134+
# On older Rubies, there was no struct rb_thread_sched (it was struct rb_global_vm_lock_struct)
135+
$defs << "-DNO_RB_THREAD_SCHED" if RUBY_VERSION < "3.2"
136+
137+
# On older Rubies, the first_lineno inside a location was a VALUE and not a int (https://github.com/ruby/ruby/pull/6430)
138+
$defs << "-DNO_INT_FIRST_LINENO" if RUBY_VERSION < "3.2"
139+
140+
# On older Rubies, there was no tid member in the internal thread structure
141+
$defs << "-DNO_THREAD_TID" if RUBY_VERSION < "3.1"
142+
143+
# On older Rubies, there was no jit_return member on the rb_control_frame_t struct
144+
$defs << "-DNO_JIT_RETURN" if RUBY_VERSION < "3.1"
145+
146+
# On older Rubies, there are no Ractors
147+
$defs << "-DNO_RACTORS" if RUBY_VERSION < "3"
148+
149+
# On older Rubies, rb_imemo_name did not exist
150+
$defs << "-DNO_IMEMO_NAME" if RUBY_VERSION < "3"
151+
152+
# On older Rubies, objects would not move
153+
$defs << "-DNO_T_MOVED" if RUBY_VERSION < "2.7"
154+
155+
# On older Rubies, rb_global_vm_lock_struct did not include the owner field
156+
$defs << "-DNO_GVL_OWNER" if RUBY_VERSION < "2.6"
157+
158+
# On older Rubies, there was no thread->invoke_arg
159+
$defs << "-DNO_THREAD_INVOKE_ARG" if RUBY_VERSION < "2.6"
160+
101161
# Tag the native extension library with the Ruby version and Ruby platform.
102162
# This makes it easier for development (avoids "oops I forgot to rebuild when I switched my Ruby") and ensures that
103163
# the wrong library is never loaded.
104164
# When requiring, we need to use the exact same string, including the version and the platform.
105165
EXTENSION_NAME = "libdatadog_api.#{RUBY_VERSION[/\d+.\d+/]}_#{RUBY_PLATFORM}".freeze
106166

107-
create_makefile(EXTENSION_NAME)
167+
# Setup Ruby VM private headers access
168+
CAN_USE_MJIT_HEADER = RUBY_VERSION.start_with?("2.6", "2.7", "3.0.", "3.1.", "3.2.")
169+
170+
if CAN_USE_MJIT_HEADER
171+
mjit_header_file_name = "rb_mjit_min_header-#{RUBY_VERSION}.h"
172+
173+
# Validate that the mjit header can actually be compiled on this system. We learned via
174+
# https://github.com/DataDog/dd-trace-rb/issues/1799 and https://github.com/DataDog/dd-trace-rb/issues/1792
175+
# that even if the header seems to exist, it may not even compile.
176+
# `have_macro` actually tries to compile a file that mentions the given macro, so if this passes, we should be good to
177+
# use the MJIT header.
178+
# Finally, the `COMMON_HEADERS` conflict with the MJIT header so we need to temporarily disable them for this check.
179+
original_common_headers = MakeMakefile::COMMON_HEADERS
180+
MakeMakefile::COMMON_HEADERS = "".freeze
181+
unless have_macro("RUBY_MJIT_H", mjit_header_file_name)
182+
skip_building_extension!('MJIT header compilation failed - required for crashtracker stack walking')
183+
end
184+
MakeMakefile::COMMON_HEADERS = original_common_headers
185+
186+
$defs << "-DRUBY_MJIT_HEADER='\"#{mjit_header_file_name}\"'"
187+
188+
# NOTE: This needs to come after all changes to $defs
189+
create_header
190+
191+
# Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
192+
# This is added as late as possible because in some Rubies we support (e.g. 3.3), adding this flag before
193+
# checking if internal VM headers are available causes those checks to fail because of this warning (and not
194+
# because the headers are not available.)
195+
append_cflags "-Wunused-parameter"
196+
197+
create_makefile(EXTENSION_NAME)
198+
else
199+
# The MJIT header was introduced on 2.6 and removed on 3.3; for other Rubies we rely on
200+
# the datadog-ruby_core_source gem to get access to private VM headers.
201+
# This gem ships source code copies of these VM headers for the different Ruby VM versions;
202+
# see https://github.com/DataDog/datadog-ruby_core_source for details
203+
204+
create_header
205+
206+
require "datadog/ruby_core_source"
207+
dir_config("ruby") # allow user to pass in non-standard core include directory
208+
209+
# This is a workaround for a weird issue...
210+
#
211+
# The mkmf tool defines a `with_cppflags` helper that datadog-ruby_core_source uses. This helper temporarily
212+
# replaces `$CPPFLAGS` (aka the C pre-processor [not c++!] flags) with a different set when doing something.
213+
#
214+
# The datadog-ruby_core_source gem uses `with_cppflags` during makefile generation to inject extra headers into the
215+
# path. But because `with_cppflags` replaces `$CPPFLAGS`, well, the default `$CPPFLAGS` are not included in the
216+
# makefile.
217+
#
218+
# This is a problem because the default `$CPPFLAGS` carries configuration that was set when Ruby was being built.
219+
# Thus, if we ignore it, we don't compile the profiler with the exact same configuration as Ruby.
220+
# In practice, this can generate crashes and weird bugs if the Ruby configuration is tweaked in a manner that
221+
# changes some of the internal structures that the profiler relies on. Concretely, setting for instance
222+
# `VM_CHECK_MODE=1` when building Ruby will trigger this issue (because somethings in structures the profiler reads
223+
# are ifdef'd out using this setting).
224+
#
225+
# To workaround this issue, we override `with_cppflags` for datadog-ruby_core_source to still include `$CPPFLAGS`.
226+
Datadog::RubyCoreSource.define_singleton_method(:with_cppflags) do |newflags, &block|
227+
super("#{newflags} #{$CPPFLAGS}", &block)
228+
end
229+
230+
Datadog::RubyCoreSource
231+
.create_makefile_with_core(
232+
proc do
233+
headers_available =
234+
have_header("vm_core.h") &&
235+
have_header("iseq.h") &&
236+
(RUBY_VERSION < "3.3" || have_header("ractor_core.h"))
237+
238+
if headers_available
239+
# Warn on unused parameters to functions. Use `DDTRACE_UNUSED` to mark things as known-to-not-be-used.
240+
# This is added as late as possible because in some Rubies we support (e.g. 3.3), adding this flag before
241+
# checking if internal VM headers are available causes those checks to fail because of this warning (and not
242+
# because the headers are not available.)
243+
append_cflags "-Wunused-parameter"
244+
end
245+
246+
headers_available
247+
end,
248+
EXTENSION_NAME
249+
)
250+
end
108251

109252
# rubocop:enable Style/GlobalVars
110253
# rubocop:enable Style/StderrPuts

0 commit comments

Comments
 (0)