RubyMotion Debugging Guide for Android
This article covers how to debug RubyMotion Android projects using the builtin debugging facility. RubyMotion apps can be debugged either on the emulator or the device.
At the time of this writing, the debugging experience in RubyMotion is still a work in progress, and this document might change any time to reflect the progresses that have been made in this regard.
1. Getting Started
The RubyMotion debugger for Android projects is based on GDB, the Android NDK debugger.
GDB is traditionally used to debug programs written in C-based languages, however, RubyMotion brings Ruby support to GDB, allowing it to connect and introspect RubyMotion processes.
|The GDB support is at this point experimental and also quite low-level. Our goal is to build a higher-level, friendlier debugger on top of GDB that will provide a better experience for Ruby developers.|
This document aims at covering the main features that one might need in order to debug a RubyMotion app with GDB. This document is not a complete GDB manual. We highly recommend reading the official user manual if an exhaustive guide is needed.
|In this guide we will use the longhand versions of all debugger commands, but most, if not all, have shorthand versions which you can find in the official user manual.|
1.1. Debugging Symbols
The RubyMotion compiler implements the DWARF debugging format metadata for the Ruby language. This allows external programs such as debuggers or profilers to retrieve source-level information about an existing RubyMotion application.
In Android, the metadata is saved right inside the application
ELF shared library, in the build directory of your project.
release modes have debugging symbols, however, as the
release mode activates compilation optimizations, the debugging experience will be better under the
development mode. For example, in the
release mode, certain local variables and arguments might not be accessible in the debugger as they could be optimized.
1.2. Attaching the Debugger
In order to start the debugger, the
debug option can be set to any value on the appropriate rake target.
When working with the
emulator rake task, the debugger will directly attach itself to the app and replace the interactive shell (REPL).
$ rake emulator debug=1
When working with the
device rake task, the build system will start the iOS debugging server on the device then remotely attach the debugger on your shell right after the application has been deployed on the device.
$ rake device debug=1
2. Managing Breakpoints
To set a breakpoint to a given location in the source code, use the
break command and pass both the file and the line number, separated by
As an example, the following command sets a breakpoint on the 16th line of the hello_view.rb file.
(gdb) break main_activity.rb:16 Breakpoint 1 at 0x5ec294d0: file main_activity.rb, line 16.
The info breakpoints command can be used to list the breakpoints that have been set in the current debugger environment.
(gdb) info breakpoints Num Type Disp Enb Address What 1 breakpoint keep y 0x5ec294d0 in rb_scope__dispatchTouchEvent__ at main_activity.rb:16
As you can see our breakpoint
main_activity.rb:16 is right there and is enabled. The
disable commands can respectively enable or disable a given breakpoint using its number.
Since our breakpoint is number 1 in the list, we can disable it like this:
(lldb) disable 1
3. Getting the Backtrace
Once you hit a breakpoint, it is often interesting to check out the execution backtrace, which will tell you where the method is called from.
This can be done by using the
(gdb) backtrace #0 rb_scope__dispatchTouchEvent__ (_self=0x53e00025, event=0x53e0fe29) at main_activity.rb:16 #1 0x5ec29e10 in __unnamed_3070 () from /Users/lrz/src/RubyMotionSamples/android/Hello/build/Development-19/lib/armeabi/libpayload.so #2 0x4164c350 in ?? () #3 0x4164c350 in ?? ()
Backtrace frames in your code can be identified with the
rb_scope__ prefix and the
Here, the very first frame in the backtrace is the method defined in the breakpoint location:
dispatchTouchEvent. The other frames below the breakpoint are native Android NDK calls.
frame command lets you switch to a specific frame in the backtrace. By default you will be at the top frame (#0), but assuming you want to go down to frame #2, in order to inspect its context, you can type the following command to do so.
(gdb) frame select 2
Obviously it mainly matters when you want to go down to a specific Ruby-defined location in the backtrace.
backtrace command only returns the backtrace of the current thread. When dealing with a multithreaded program, you may sometimes want to print the backtrace of all running threads, for instance when you are debugging a race condition.
The following command will print the backtrace of all the running threads in the terminal.
(gdb) thread apply all backtrace
Similar to switching frames, the debugger will let you switch threads using the
thread command. This can be useful if you want to inspect a specific Ruby method frame in another running thread. The following command will switch the debugger prompt to the thread #4.
(gdb) thread 4 [Switching to thread 4 (Thread 10224)] #0 0x401434b0 in recvmsg () from /Users/lrz/src/RubyMotionSamples/android/Hello/build/Development-19/obj/local/armeabi/libc.so
4. Inspecting Objects
After checking the backtrace, you may want to inspect the objects around. The debugger will let you print them using specialized commands.
4.1. Local Variables
We just hit a breakpoint defined in the
onCreate method. As you can see from the breakpoint, we are inside a function that accepts two arguments:
In RubyMotion, the
_self argument is a pointer to the
self object exposed in Ruby, which represents a reference to the receiver of the method. In the debugger,
_self is visible as the first argument of the method.
We can inspect the values of both
savedInstanceState by using the
print-ruby-object command. This RubyMotion-defined command sends the
inspect message to the given object and returns its value. The command can also be called using the
pro shortcut which we will use as a convenience.
Breakpoint 1, rb_scope__onCreate__ (_self=0xc3e00019, savedInstanceState=0x67c0001d) at main_activity.rb:13 13 end (gdb) pro _self #<MainActivity:0xc3e00019> (gdb) pro savedInstanceState #<android.os.Bundle:0x67c0001d>
The list of local variables can be printed using the info locals command. The list will also include the addresses of each local variable.
5 elems =  (gdb) info locals elems = 0x5d800025
These local variables can also be individually inspected on the terminal by using the
(gdb) pro elems 
4.2. Instance Variables
Instance variables of an object can be printed using the
(gdb) print-ruby-ivars @text = #<android.widget.TextView:0x1ef00059>
5. Control Flow
next command will continue the execution of the program until the next source-level location. This is usually the very next line in the Ruby source code. This means that the debugger has not yet executed the line that it indicates is the current line, keep this in mind when inspecting variables and their values.
Breakpoint 1, rb_scope__onCreate__ (_self=<optimized out>, savedInstanceState=<optimized out>) at main_activity.rb:5 5 elems =  (gdb) n 6 elems << 1 (gdb) pro elems  (gdb) n 7 elems << 2 (gdb) pro elems 
continue command will continue the execution of the program until it reaches a breakpoint.
(gdb) continue Continuing.
When the program runs, you can always stop its execution and go back to the debugger prompt by typing the
control`c+ (^C`) keyboard shortcut.
^C Program received signal SIGINT, Interrupt. [...] (gdb)
If you want to quit the debugger, just type the
quit command and confirm that you want to exit. It will terminate the application and return you back to the shell prompt.
(gdb) quit A debugging session is active. Inferior 1 [Remote target] will be detached. Quit anyway? (y or n) y $