@@ -42,6 +42,9 @@ def skip_building_extension!(reason)
4242# (https://github.com/msgpack/msgpack-ruby/blob/18ce08f6d612fe973843c366ac9a0b74c4e50599/ext/msgpack/extconf.rb#L8)
4343append_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
4649append_cflags '-Wno-declaration-after-statement'
4750
@@ -98,13 +101,153 @@ def skip_building_extension!(reason)
98101extra_relative_rpaths . each { |folder | $LDFLAGS += " -Wl,-rpath,$$$\\ \\ {ORIGIN\\ }/#{ folder . to_str } " }
99102Logging . 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.
105165EXTENSION_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