ruby-mine

exploring the mine

ruby-debug

von cypher am 19.02.2008 (13 Uhr)

Meta-Was?

'Ruby Tuesday' soll eine wöchentliche Serie über ein Ruby-Tool oder eine Ruby-Library werden, die vielleicht noch nicht so bekannt sind. Jeder Artikel soll den Sinn und Zweck des Tools oder der Library kurz erläutern, und eine Installationshilfe (sofern notwendig) und eine kurze Gebrauchsanleitung liefern.

Vorschläge für Tools oder Libraries werden natürlich gern entgegengenommen, entweder in den Kommentaren oder per eMail.

Wenn ihr selber einen Artikel beisteuern wollt, seit ihr natürlich herzlich eingeladen, dazu mir bitte einfach eine eMail schicken.

Was?

ruby-debug ist ein Ruby Debugger. In Zeiten von Unit Tests mag ein Debugger vielleicht schon wie ein Anachronismus wirken, aber manchmal helfen auch die besten Testfälle nichts, und man muss in die Eingeweide einer Applikation oder Library tauchen.

Installieren & Einrichten

ruby-debug ist per Rubygems verfügbar:

gem install ruby-debug

Unter Debian muss das ruby-dev-Paket installiert sein.

Verwenden

Man kann ruby-debug auf zwei Wegen benutzen.

Der erste ist mittels dem rdebug-Kommando:

$ cat test.rb
puts "Hello, world!"
$ rdebug test.rb 
test.rb:1
puts "Hello, world!"
(rdb:1)

rdebug startet Ruby, lädt unser Programm, und hält sofort auf der ersten Zeile an.

Die zweite Möglichkeit geht mittels der debugger-Methode. Dazu muss man zuerst ein require 'ruby-debug' in seinem Programm schreiben. Danach ruft man einfach die debugger-Methode an der Stelle auf an der man den Debugger aktivieren möchte (mehrere Aufrufe sind natürlich auch möglich).

In beiden Fällen landet man im Kommandozeilen-Interface von ruby-debug. Falls man graphische Debugger gewöhnt ist, kann das ein leichter Schock sein, aber alles ist halb so schlimm. ruby-debug zeigt uns unsere derzeitige Position an, und die Zeile die als nächstes ausgeführt wird. Per help kann man sich eine Liste aller Kommandos anzeigen lassen, help <kommando> zeigt die Hilfe zu einem spezifischen Kommando an. Die meisten Kommandos unterstützen eine Vielzahl an Optionen. Ausserdem lassen sich die meisten Kommandos abkürzen indem man nur den ersten Buchstaben ihres Namens tippt.

Nehmen wir mal an wir wollen folgendes Ruby-Programm debuggen:

class ATestClass

  attr_accessor :foo

  def initialize(foo = "default")
    @foo = foo    
  end

  def a_method
    puts "Entering a_method..."
    @bar = 3
    puts "@foo = #{@foo}"
  end

  def another_method
    @bar = 42
  end
end

a = ATestClass.new("some string")

a.a_method

a.another_method

puts "Done."

Wir starten ruby-debug mit dem rdebug Befehl, und landen in der Kommandozeile:

$ rdebug test.rb 
test.rb:1
class ATestClass
(rdb:1)

Hier können wir uns mit list den Code ansehen, in dem wir uns gerade befinden:

(rdb:1) list
[-4, 5] in ./test.rb
=> 1  class ATestClass
   2    
   3    attr_accessor :foo
   4    
   5    def initialize(foo = "default")

Mittels next führen wir die nächste Zeile Code aus:

(rdb:1) next
test.rb:20
a = ATestClass.new("some string")

Alternativ zu next gibt es auch step. Der grosse Unterschied zwischen den beiden Kommandos: step steigt bei einem etwaigen Methodenaufruf in die Methode rein, während bei next über den Methodenaufruf steigt (die Methode wird natürlich noch immer aufgerufen). Nachdem wir gerne wissen möchten was in initialize passiert, tippen wir step ein:

(rdb:1) step
test.rb:6
@foo = foo

Die Befehle p, pp, ps und putl erlauben es uns Ruby-Audrücke im lokalen Kontext zu evaluieren und deren Ergebnis anzuzeigen. Die ersten beiden funktionieren genau wie in Ruby, während ps ein Array als Ergebnis erwartet, welches dann sortiert und reihenweise ausgegeben wird. putl macht das gleiche, aber ohne zu sortieren.

Damit kann man sich den Wert von foo anzeigen lassen:

(rdb:1) p foo
"some string"

ruby-debug bietet auch das var-Kommando an, mit dem man sich wahlweise alle lokalen, Instanz- und globalen Variablen sowie die Konstanten anzeigen kann.

Mit dem break-Kommando lassen sich Breakpoints setzen. Dieses ist dabei sehr mächtig: Entweder man gibt ihm den Dateinamen und die Zeile an wo der Breakpoint gesetzt werden soll (im Format file:line), wobei der Dateiname weggelassen werden kann wenn der Breakpoint in der aktuellen Datei gesetzt werden soll. Oder man setzt den Breakpoint auf eine Methode mittels class#method bzw. class.method für Klassenmethoden. In beiden Fällen kann am Ende noch ein if mitsamt einem Ruby-Ausdruck anhängen, wodurch der Breakpoint nur dann ausgelöst wird wenn dieser Ausdruck wahr ist. So können wir z.B. einen Breakpoint auf die Zeile 11 setzen ("@bar = 3"), und einen auf die Methode another_method:

(rdb:1) break 11
Breakpoint 1 file test.rb, line 11
(rdb:1) break ATestClass#another_method
Breakpoint 2 at ATestClass::another_method

Mit continue lassen wir unser Programm jetzt so lange weiterlaufen, bis es sich entweder beendet oder es auf einen Breakpoint trifft:

(rdb:1) continue
Entering a_method...
Breakpoint 1 at test.rb:11
test.rb:11
@bar = 3
(rdb:1) var instance
@foo = "some string"

Nochmal continue, und wir treffen auf unseren zweiten Breakpoint:

(rdb:1) continue
@foo = some string
Breakpoint 2 at ATestClass:another_method
test.rb:15
def another_method
(rdb:1) var instance
@bar = 3
@foo = "some string"

Falls wir schon mehrere Breakpoints gesetzt haben, dann können wir diese mit save FILE in eine Datei abspeichern. Später können diese z.B. in einer neuen Debug-Session mittels source FILE wieder geladen werden.

EDIT: Skade weisst darauf hin dass es auch ein irb-Kommando gibt. Dieses ist zwar als experimentell gekennzeichnet und hat bei mir nie richtig funktioniert (weshalb ich ursprünglich auch nichts darüber geschrieben habe), aber laut Skade funktioniert es bei ihm ohne Probleme. Deshalb: mit irb kann man eine IRB-Session im aktuellen Kontext starten. Sehr praktisch wenn es funktioniert.

Fin

Damit habt ihr hoffentlich genug Wissen um mit ruby-debug die ersten Schritte zu wagen. Nächste Woche geht es dann um ruby-prof, einer mächtigen Alternative zu profile.


Kommentar schreiben

Name (notwendig)

Mail (wird nicht veröffentlicht)

Webseite


Kommentare

  1. janfri schrieb am 19.02.2008 (15 Uhr)

    Danke, sehr schön. :)

  2. Johannes schrieb am 20.02.2008 (09 Uhr)

    Ich nehme mal an, dass sich die Syntax des Debuggers an die vom gdb anlehnt, oder?

  3. cypher schrieb am 20.02.2008 (15 Uhr)

    Die meisten Befehle von ruby-debug funktionieren allerdings tatsächlich gleich oder ähnlich wie in gdb. Es sind halt beides (Kommandozeilen)Debugger, insofern lässt sich da eine gewisse Ähnlichkeit sowieso nicht vermeiden :) Aber gdb kann halt auch viel mehr, weil es einfach auf einem tieferen Level als ruby-debug arbeitet. ruby-debug muss sich sozusagen nur mit Ruby auskennen, wärend gdb mit kompilierten Programm zurechtkommen muss (die nicht nur in C geschrieben sind).

  4. Skade schrieb am 24.02.2008 (02 Uhr)

    Interessant finde ich, dass du nicht erwähnst, dass man mit "irb" direkt einen irb-Session am aktuellen Punkt beginnen kann. Ansonsten ein sehr schöner Artikel. Gruß Skade

  5. cypher schrieb am 24.02.2008 (12 Uhr)

    Ja, weil das Feature erstens als experimentell bezeichnet wurde und zweitens bei mir nicht immer richtig funktionierte (Segfaults und so). Deshalb hab ich es nicht erwähnt.

  6. Skade schrieb am 25.02.2008 (22 Uhr)

    Hui, mir noch nicht untergekommen - ich benutze das sehr gerne und ausgiebig.

  7. cypher schrieb am 26.02.2008 (13 Uhr)

    Aha. Ich hab den Artikel mit dem irb-Kommando geupdated, mit dem Hinweis das es eben experimentell ist und YMMV.