In Progress
Unit 1, Lesson 21
In Progress

Memory Management in C Extensions – Part 2

In the first episode of this two-part series guest chef Jeremy Evans joined us to demonstrate how to code Ruby native C extensions so that they don’t leak memory. Today, Jeremy shows us how to write C extension code that’s free of pesky segmentation faults, by ensuring it never tries to reclaim memory that is still in use. Enjoy!

Video transcript & code

In the last episode, we went over a couple ways to help avoid memory leaks in Ruby C extensions. We used rb_str_buf_new to have Ruby manage some memory for us, and used rb_ensure to make sure we freed memory appropriately when calling Ruby methods.


Last Episode: Avoiding Memory Leaks

This episode we'll tackle a separate but related memory managment issue in C extensions, which is how to ensure that Ruby doesn't free memory while we are still using it.


This Episode: Avoiding Use-After-Free

Ruby freeing memory that is still used is actually much worse than leaking memory, because instead of just wasting memory, we end up with undefined and often non-deterministic behavior where our code will segfault at random places.


Undefined Behavior,
Non-Determinism,
and Segmentation Faults,
Oh My!

First, we need to understand why Ruby frees memory while we are still using it. In general with Ruby, this happens when the garbage collector cannot find a reference to the object, but there actually is a reference it isn't aware of.


Garbage Collector

One case where this happens is in static C variables, what we think of as global variables.


Static C Variables

Let's say we are writing a Ruby C extension called string_help. When Ruby loads our string_help C extension, it will look for the Init_string_help function and call it.


void Init_string_help(void) {
}

Static variables in C are stored in a single place in memory, outside of the program stack that Ruby scans for object references. Let's add a hello_prefix static variable for a Ruby object and set the value to the Ruby string Hello inside the Init_string_help function. Remember from the last episode that in C, all Ruby objects have type VALUE.


VALUE hello_prefix;

void Init_string_help(void) {
  hello_prefix = rb_str_new2("Hello ");
}

We can use this static variable inside our functions. Let's say we add an add_hello function, which takes a Ruby object for a string containing a name, and adds Hello before the string.


VALUE hello_prefix;

VALUE add_hello(VALUE name) {
  return rb_str_plus(hello_prefix, name);
}

void Init_string_help(void) {
  hello_prefix = rb_str_new2("Hello ");
}

Unfortunately, we'll find that this may work initially, but sometime later the function starts adding Avdi in front of the string we pass to add_hello, and later when we call add_hello, the entire Ruby program crashes. Why did this happen?

It happened because the Ruby garbage collector doesn't know about hello_prefix, so it thinks the Hello string we created in Init_string_help was no longer used, and garbage collected it. Later a Ruby string object containing Avdi was created at the same location the Hello string was pointing to, and sometime after that the memory location was freed and then unmapped, and Ruby segfaulted when trying to access the memory.


VALUE hello_prefix;

VALUE add_hello(VALUE name) {
  /* hello_prefix was ruby string "Hello "
     then ruby string "Avdi", 
     later ??? */ 
  return rb_str_plus(hello_prefix, name);
}

void Init_string_help(void) {
  hello_prefix = rb_str_new2("Hello ");
}

To fix this terrible situation from occurring, we need to call the rb_global_variable function with the address of any static variable we are using, which in this case would be the address of hello_prefix.


VALUE hello_prefix;

VALUE add_hello(VALUE name) {
  return rb_str_plus(hello_prefix, name);
}

void Init_string_help(void) {
  hello_prefix = rb_str_new2("Hello ");

  rb_global_variable(&hello_prefix);
}

Note that we pass rb_global_variable the address of the VALUE, not the VALUE itself.


VALUE hello_prefix;

VALUE add_hello(VALUE name) {
  return rb_str_plus(hello_prefix, name);
}

void Init_string_help(void) {
  hello_prefix = rb_str_new2("Hello ");

  /* Not this */
  rb_global_variable(hello_prefix);
}

Let's say we want to support a suffix in our add_hello function by adding a hello_suffix static variable. Now that we know about rb_global_variable, we might think the most natural way to add support would be to create another ruby string, and then add another rb_global_variable call. Unfortunately, while this will work most of the time, we can get unlucky.


VALUE hello_prefix;
VALUE hello_suffix;

VALUE add_hello(VALUE name) {
  return rb_str_plus(rb_str_plus(hello_prefix, name), hello_suffix);
}

void Init_string_help(void) {
  hello_prefix = rb_str_new2("Hello ");
  hello_suffix = rb_str_new2("!");

  rb_global_variable(&hello_prefix);
  rb_global_variable(&hello_suffix);
}

Garbage collection in Ruby happens any time we try to allocate a Ruby object and there are no free memory slots for the object. Let's say that happens when we are creating the Ruby string for hello_suffix. If garbage collection is triggered then, Ruby will not know about the ruby string we allocated and assigned to hello_prefix, and will free that object, leading to very bad things later when add_hello uses the hello_prefix variable.


VALUE hello_prefix;
VALUE hello_suffix;

VALUE add_hello(VALUE name) {
  return rb_str_plus(rb_str_plus(hello_prefix, name), hello_suffix);
}

void Init_string_help(void) {
  hello_prefix = rb_str_new2("Hello ");

  /* Garbage collection here */
  hello_suffix = rb_str_new2("!");

  rb_global_variable(&hello_prefix);
  rb_global_variable(&hello_suffix);
}

To fix this issue, we should call rb_global_variable directly after creating the object.


VALUE hello_prefix;
VALUE hello_suffix;

VALUE add_hello(VALUE name) {
  return rb_str_plus(rb_str_plus(hello_prefix, name), hello_suffix);
}

void Init_string_help(void) {
  hello_prefix = rb_str_new2("Hello ");
  rb_global_variable(&hello_prefix);

  hello_suffix = rb_str_new2("!");
  rb_global_variable(&hello_suffix);
}

rb_global_variable works by adding the address we provide to the linked list of addresses that Ruby will scan during garbage collection.


rb_gc_register_address(VALUE *addr)
{
    rb_objspace_t *objspace = &rb_objspace;
    struct gc_list *tmp;

    tmp = ALLOC(struct gc_list);
    tmp->next = global_list;
    tmp->varptr = addr;
    global_list = tmp;
}

Another situation that will cause Ruby to free memory before we are finished using it is when we are storing a reference to our object on the program stack, but the compiler optimizes away the reference as it is no longer needed.


Too smart compilers!

In our last episode, we wrote a doubled_str function. Let's say our doubled_str function was such a hit, people are asking for a function that quadruples the string. We think this is a perfect time to reuse our doubled_str function, and create a quad_str function to do so. This function isn't too complex, it just uses doubled_str to return a ruby string object, gets the underlying memory and doubles it again.

Unfortunately, a smart enough compiler can optimize away the reference to the string2 variable, since it is not used after the ds variable is created, causing undefined behavior when the doubled_str function tries to access the ds variable.


VALUE quad_str(char *string, long length) {
  VALUE string2, string4;
  char *ds;

  string2 = doubled_str(string, length);
  ds = RSTRING_PTR(string2);
  string4 = doubled_str(ds, length * 2);
  return string4;
}

This can be fixed by using the RB_GC_GUARD macro to fool the compiler into thinking the string2 variable is still needed after the doubled_str function call.


VALUE quad_str(char *string, long length) {
  VALUE string2, string4;
  char *ds;

  string2 = doubled_str(string, length);
  ds = RSTRING_PTR(string2);
  string4 = doubled_str(ds, length * 2);
  RB_GC_GUARD(string2);
  return string4;
}

RB_GC_GUARD uses some deep black magic to fool the compiler into thinking it must keep a reference to the related ruby object on the stack, with specific implementations for GCC and Microsoft Visual Studio, and a fallback implementation for other compilers.


#ifdef __GNUC__
#define RB_GC_GUARD(v) \
    (*__extension__ ({ \
  volatile VALUE *rb_gc_guarded_ptr = &(v); \
  __asm__("" : : "m"(rb_gc_guarded_ptr)); \
  rb_gc_guarded_ptr; \
    }))
#elif defined _MSC_VER
#pragma optimize("", off)
static inline volatile VALUE *rb_gc_guarded_ptr(volatile VALUE *ptr) {return ptr;}
#pragma optimize("", on)
#define RB_GC_GUARD(v) (*rb_gc_guarded_ptr(&(v)))
#else
volatile VALUE *rb_gc_guarded_ptr_val(volatile VALUE *ptr, VALUE val);
#define HAVE_RB_GC_GUARDED_PTR_VAL 1
#define RB_GC_GUARD(v) (*rb_gc_guarded_ptr_val(&(v),(v)))
#endif

We need to make sure we call RB_GC_GUARD after the function call that uses the ds variable, and not before, as calling it before may not have the desired effect of keeping a reference to string2 on the program stack when calling the doubled_str function.


VALUE quad_str(char *string, long length) {
  VALUE string2, string4;
  char *ds;

  string2 = doubled_str(string, length);
  ds = RSTRING_PTR(string2);
  RB_GC_GUARD(string2);
  string4 = doubled_str(ds, length * 2);
  return string4;
}

This episode we learned about ways to ensure Ruby does not free memory before we are finished using it, using rb_global_variable for static variables and RB_GC_GUARD for variables stored on the stack. While programming in C is generally more difficult than programming in Ruby for many reasons, having to manage memory manually is one of the biggest challenges. Hopefully these last two episodes have better prepared us if we do have to use Ruby's C-API.


Memory Management: Just One of Many Dragons

Responses