6

Every Ruby object is of type VALUE in C. How do I print it in a readable way?

Any other tips concerning debugging of Ruby C extensions are welcome.

Matheus Moreira
  • 17,106
  • 3
  • 68
  • 107
x-yuri
  • 16,722
  • 15
  • 114
  • 161

3 Answers3

8

You can call p on Ruby objects with the C function rb_p. For example:

VALUE empty_array = rb_ary_new();
rb_p(empty_array); // prints out "[]"
rmosolgo
  • 1,854
  • 1
  • 18
  • 23
3

Here's what I came up with:

static void d(VALUE v) {
    ID sym_puts = rb_intern("puts");
    ID sym_inspect = rb_intern("inspect");
    rb_funcall(rb_mKernel, sym_puts, 1,
        rb_funcall(v, sym_inspect, 0));
}

Having it in a C file, you can output VALUEs like so:

VALUE v;
d(v);

I've borrowed the idea from this article.

x-yuri
  • 16,722
  • 15
  • 114
  • 161
0

I've found an interesting way using Natvis files in Visual Studio.

I have created C++ wrapper objects over the Ruby C API - this gives me a little bit more type safety and the syntax becomes more similar to writing actual Ruby.

I won't be posting the whole code - too long for that, I plan on open sourcing it eventually.

But the gist of it is:

class Object
{
public:
  Object(VALUE value) : value_(value)
  {
    assert(NIL_P(value_) || kind_of(rb_cObject));
  }

  operator VALUE() const
  {
    return value_;
  }
  // [More code] ...
}

Then lets take the String class for example:

class String : public Object
{
public:
  String() : Object(GetVALUE("")) {}
  String(VALUE value) : Object(value)
  {
    CheckTypeOfOrNil(value_, String::klass());
  }
  String(std::string value) : Object( GetVALUE(value.c_str()) ) {}
  String(const char* value) : Object( GetVALUE(value) ) {}


  operator std::string()
  {
    return StringValueCStr(value_);
  }

  operator std::string() const
  {
    return operator std::string();
  }

  static VALUE klass()
  {
    return rb_cString;
  }

  // String.empty?
  bool empty()
  {
    return length() == 0;
  }

  size_t length() const
  {
    return static_cast<size_t>(RSTRING_LEN(value_));
  }

  size_t size() const
  {
    return length();
  };

};

So - my wrappers make sure to check that the VALUE they wrap is of expected type or Nil.

I then wrote some natvis files for Visual Studio which will provide some real time debug information for my wrapper objects as I step through the code:

<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">

<Type Name="SUbD::ruby::String">
  <DisplayString Condition="value_ == RUBY_Qnil">Ruby String: Nil</DisplayString>
  <DisplayString Condition="value_ != RUBY_Qnil">Ruby String: {((struct RString*)value_)->as.heap.ptr,s}</DisplayString>
  <StringView Condition="value_ != RUBY_Qnil">((struct RString*)value_)->as.heap.ptr,s</StringView>
  <Expand>
    <Item Name="[VALUE]">value_</Item>
    <Item Name="[size]" Condition="value_ != RUBY_Qnil">((struct RString*)value_)->as.heap.len</Item>
    <Item Name="[string]" Condition="value_ != RUBY_Qnil">((struct RString*)value_)->as.heap.ptr</Item>
    <Item Name="[capacity]" Condition="value_ != RUBY_Qnil">((struct RString*)value_)->as.heap.aux.capa</Item>
  </Expand>
</Type>

</AutoVisualizer>

Note that this is all hard-coded to the exact internal structure of Ruby 2.0. This will not work in Ruby 1.8 or 1.9 - haven't tried with 2.1 or 2.2 yet. Also, there might be mutations of how the String can be stored which I haven't added yet. (Short strings can be stored as immediate values.) (In fact - the natvis posted above only works for 32bit - not 64bit at the moment.)

But once that is set up I can step through code and inspect the Ruby strings almost like they are std::string:

Visual Studio displaying information about Ruby String class

Getting it all to work isn't trivial. If you noticed in my natvis my RUBY_Qnil references - they would not work unless I added this piece of debug code to my project:

// Required in order to make them available to natvis files in Visual Studio.
#ifdef _DEBUG
const auto DEBUG_RUBY_Qnil              = RUBY_Qnil;
const auto DEBUG_RUBY_FIXNUM_FLAG       = RUBY_FIXNUM_FLAG;
const auto DEBUG_RUBY_T_MASK            = RUBY_T_MASK;
const auto DEBUG_RUBY_T_FLOAT           = RUBY_T_FLOAT;
const auto DEBUG_RARRAY_EMBED_FLAG      = RARRAY_EMBED_FLAG;
const auto DEBUG_RARRAY_EMBED_LEN_SHIFT = RARRAY_EMBED_LEN_SHIFT;
const auto DEBUG_RARRAY_EMBED_LEN_MASK  = RARRAY_EMBED_LEN_MASK;
#endif

You cannot use macros in natvis definitions unfortunately, so that's why I had to manually expand many of them into the natvis file by inspecting the Ruby source itself. (The Ruby Cross Reference is of great help here: http://rxr.whitequark.org/mri/ident?v=2.0.0-p247)

It's still WIP, but it's already saved me a ton of headaches. Eventually I want to extract the debug setup on GitHub: https://github.com/thomthom (Keep an eye on that account if you are interested.)

thomthom
  • 2,854
  • 1
  • 23
  • 53