> ## Content Index
> Fetch the complete content index at: https://serpapi.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# How to create Ruby C extension and debug in VS Code
- URL: https://serpapi.com/blog/how-to-debug-ruby-c-extension-in-vs-code/
- Published: 2023-01-11T09:37:28.000Z
- Updated: 2023-01-11T09:37:28.000Z
- Author: Yicheng Zhou
- Tags: debugging, c extension, Ruby, vs code

When developing [Nokolexbor](https://github.com/serpapi/nokolexbor), I found debugging with `gdb` or `lldb` was troublesome because I have to do everything with commands. For me, it will be more delightful to debug with an interactive GUI, which makes it convenient to control breakpoints and steps, inspect values and navigate through the code. This post shows how to use VS Code debugging tools to debug Ruby C extension from scratch.

## Create the gem project

We demonstrate this on an empty gem project with extensions. The project can be easily created by:

```
bundle gem example_gem --ext
```

If you already have a working gem project, just jump to the next section.

Let's modify `ext/example_gem/example_gem.c` to add a native method `example_plus` under `ExampleClass` that can be called by ruby code.

```C
#include "example_gem.h"

VALUE rb_mExampleGem;
VALUE rb_cExampleClass;

static VALUE
example_plus(VALUE self, VALUE rb_a, VALUE rb_b)
{
  int a, b;
  a = NUM2INT(rb_a);
  b = NUM2INT(rb_b);
  return INT2NUM(a + b);
}

void Init_example_gem(void)
{
  rb_mExampleGem = rb_define_module("ExampleGem");
  rb_cExampleClass = rb_define_class_under(rb_mExampleGem, "ExampleClass", rb_cObject);
  rb_define_method(rb_cExampleClass, "example_plus", example_plus, 2);
}

```

Now, compile the extension: `rake compile`. If everything goes well, a shared library named `example_gem.so` or `example_gem.bundle` will be created under `lib/example_gem`

Let's test it in `irb` and make sure it works:

```
❯ irb -Ilib -rexample_gem
irb(main):001:0> obj = ExampleGem::ExampleClass.new
=> #<ExampleGem::ExampleClass:0x00007f902022ac88>
irb(main):002:0> obj.example_plus(2, 3)
=> 5
```

## Debug in VS Code

By default, ruby compiles the C extension with `-O3`, which loses debug information. We should modify `ext/example_gem/extconf.rb` to add debug options.

```
require "mkmf"

if ENV['EXAMPLE_DEBUG']
  CONFIG["optflags"] = "-O0"
  CONFIG["debugflags"] = "-ggdb3"
end

create_makefile("example_gem/example_gem")
```

Here we are using an environment variable `EXAMPLE_DEBUG` to control whether we want to compile with debug information.

Just compile with `EXAMPLE_DEBUG=1 rake compile`.

Some posts would suggest that you compile ruby itself with the same debug flags as well, but as long as you don't need to debug ruby-related functions, just use the ruby you've installed.

Now, let's open VS Code. First, install the official `C/C++` extension

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2023/01/Screenshot-2023-01-05-at-21.06.26.png)

Switch to `Run and Debug` tab. Click `create a launch.json file`.

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2023/01/Screenshot-2023-01-05-at-21.13.13.png)

On the open `launch.json` tab. Click `Add Configuration...` button on the bottom right. Select `C/C++: (lldb) Launch` (on Linux, this option may be `C/C++: (gdb) Launch`).

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2023/01/Screenshot-2023-01-05-at-21.14.51.png)

Change `program` and `args` to tell VS Code how to run the program. Note that you should set `program` to the absolute path of the actual `ruby` program. A single `ruby` will probably not work if you are using a ruby version manager such as `rvm` or `rbenv`, because the `ruby` command will refer to a script instead of the ruby binary. You might also want to set `cwd` to `${workspaceFolder}` to let ruby find the lib path correctly. Here is my final setting:

```
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "(lldb) Launch",
      "type": "cppdbg",
      "request": "launch",
      "program": "/Users/zyc/.rbenv/versions/2.7.2/bin/ruby",
      "args": ["-Ilib", "-rexample_gem", "-e", "puts ExampleGem::ExampleClass.new.example_plus(2, 3)"],
      "stopAtEntry": false,
      "cwd": "${workspaceFolder}",
      "environment": [],
      "externalConsole": false,
      "MIMode": "lldb"
    }
  ]
}
```

Everything is set! Let's add a breakpoint to the C code and start debugging.

Click on the space left to the line number to add a breakpoint (red dot). And click `Start Debugging` button.

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2023/01/Screenshot-2023-01-05-at-21.42.11.png)

We can see that the code stopped at the breakpoint. The debugging toolbox showed up and you can do all the debugging actions you are familiar with.

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2023/01/Screenshot-2023-01-05-at-21.42.52.png)

## Attach to a running process

Sometimes it's not convenient to run the code directly from the raw ruby process. For example, your code must run under Ruby-on-Rails environment, which is not easy to set up manually. Instead of launching ruby, we can attach to a running ruby process and debug from there.

Let's open `launch.json` again, click the `Add Configuration...` button on the bottom right, and select `C/C++: (lldb) Attach` (on Linux, this option may be `C/C++: (gdb) Attach`), edit the newly inserted JSON configuration:

```
  "configurations": [
    {
      "name": "(lldb) Attach",
      "type": "cppdbg",
      "request": "attach",
      "program": "/Users/zyc/.rbenv/versions/2.7.2/bin/ruby",
      "MIMode": "lldb"
    },
    ...
  ]
```

Now, switch to `Attach`, start your `rails c` somewhere, and click `Start Debugging`

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2023/01/Screenshot-2023-01-05-at-21.57.31.png)

A list will pop up for you to select the target process to attach. Select `ruby`

![](https://storage.ghost.io/c/a5/00/a5004977-0dd2-4bcd-9292-dd0e05d4c59e/content/images/2023/01/image.png)

Finally, run the code in the Rails console that will trigger the breakpoint, and the rest will be the same as debugging with `Launch`.

That's all for this tutorial. I hope you find it useful. Happy debugging!