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.
Second, ensure you've installed the RubyInstaller's DevKit toolchain. If you're installing it for the first time, read these instructions. No really, read the instructions!
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 mkmf
generated
Makefile
:
# 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
less, the -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 extconf.rb
, the
file responsible for creating the Makefile
which builds and installs the native
RubyGem.
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 mkmf
customization.
NOTE: another option may be to put a similar hack into the gem's Rakefile
and count on require
's behavior of trying to prevent multiple loads of the
same 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 oops-null
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 extconf.rb
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!