As the first in a series on debugging native C RubyGems on Windows using the RubyInstaller's DevKit, this post focuses on a few configuration actions you must take in order to begin the actual debugging session using GDB from the DevKit.
First, if you haven't done so already, install Ruby on your Windows system using one of the RubyInstaller downloads.
Third, install the rake-compiler
gem by typing
gem install rake-compiler.
In future posts we'll be building and debugging (on Windows) a native C RubyGem
that causes the Ruby interpreter to segfault and crash. Don't worry, the crash
is isolated to the Ruby interpreter rather than your Windows system.
UPDATE: for those wanting to get a jump on Part 2, here's the
oops-null source repo for the native
RubyGem, and the source gem is available from rubygems.org.
If you want to start debugging it, ensure the DevKit is installed and type
gem install oops-null -- --enable-debug.
In general, building native RubyGems on Windows is rather straightforward these
days once you've properly configured your RubyInstaller + DevKit environment to
take advantage of the
rake-compiler. The key remaining technical hurdle is to
figure out how to ensure both Ruby and your native RubyGem includes the debugging
symbols required for efficient debugging with GDB.
The first one's easy; all of our RubyInstaller downloads are built with debugging symbols included.
However, building your native RubyGem with debugging symbols is a bit more
challenging because, by default, symbols are stripped from the native shared
library by lines similar to the following found in the gem's
# Ruby 1.9.2 build configuration cflags = $(optflags) $(debugflags) $(warnflags) optflags = -O3 debugflags = -g ... CFLAGS = $(cflags) ... # NOTE: MRI 1.8.7 has LDSHARED = gcc -shared -s LDSHARED = $(CC) -shared $(if $(filter-out -g -g0,$(debugflags)),,-s) ... $(DLLIB): $(DEFFILE) $(OBJS) Makefile @-$(RM) $(@) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)
Is this a bug with MRI Ruby's build configuration? Not at all.
As debugging symbols add size to your shared library, for typical use you want
the symbols removed. Also, while you normally want more performance rather than
-O3 performance optimization can make it harder to debug the root
cause of many problems.
Since this is Just How MRI Ruby Works, you're stuck right? Not really since you're
a persistent hacker who rarely takes 'No' for an answer. You puzzle over the above
Makefile snippet for awhile, look at the
mkmf.rb source code, then decide
to include the following little hack (lines 3-8) in the gem's
file responsible for creating the
Makefile which builds and installs the native
require 'mkmf' # override normal build configuration to build debug friendly library # if installed via 'gem install oops-null -- --enable-debug' if enable_config('debug') puts '[INFO] enabling debug library build configuration.' if RUBY_VERSION < '1.9' $CFLAGS = CONFIG['CFLAGS'].gsub(/\s\-O\d?\s/, ' -O0 ') $CFLAGS.gsub!(/\s?\-g\w*\s/, ' -ggdb3 ') CONFIG['LDSHARED'] = CONFIG['LDSHARED'].gsub(/\s\-s(\s|\z)/, ' ') else CONFIG['debugflags'] << ' -ggdb3 -O0' end end create_makefile('oops_null/oops_null')
UPDATE: refactored to work with MRI 1.8.7 and use proper
NOTE: another option may be to put a similar hack into the gem's
and count on
require's behavior of trying to prevent multiple loads of the
rbconfig file. This could cause other issues, but I've not looked into it.
While you can simply type
gem install oops-null -- --enable-debug to build and
install the debuggable
oops-null native RubyGem, if you've cloned the
source repository you can also build the debuggable gem locally and install it by
typing something similar to:
C:\projects\oops-null-git>rake gem (in C:/projects/oops-null-git) Temporarily enhancing PATH to include DevKit... mkdir -p pkg ... ln Rakefile pkg/oops-null-0.2.0/Rakefile WARNING: no rubyforge_project specified mv oops-null-0.2.0.gem pkg/oops-null-0.2.0.gem C:\projects\oops-null-git>gem install pkg\oops-null-0.2.0.gem -- --enable-debug Temporarily enhancing PATH to include DevKit... Building native extensions. This could take a while... Successfully installed oops-null-0.2.0 1 gem installed
While the above hack isn't perfect by any means, adding it to a gem's
currently ensures that the gem's native shared library will contain usable debug
symbols needed by GDB. While this hack works for your gem's, it doesn't solve the
problem when you're trying to debug someone else's native C RubyGem.
In the next post, I'll show you one way to begin debugging (on Windows with GDB) a native C RubyGem that segfault's the Ruby interpreter. Until then, if you've found another clever way to build native RubyGem's with included symbols, I'd like to hear from you. Drop me an email!