Permalink
Fetching contributors…
Cannot retrieve contributors at this time
382 lines (312 sloc) 15.4 KB

Topaz: The MagLev Low Level Debugger

Debugging scripts with MagLev

Topaz is currently the primary debugging tool for use with MagLev. Most of this document describes debugging with Topaz.

An alternate approach is to use GemTools, written in Squeak, for debugging. See debug from smalltalk

Use -d to run MagLev in debug mode

Run maglev with the -d option to run in debug mode. Uncaught exceptions will land you in the Topaz prompt, where you can examine the stack etc. Here is a sample file:

$ cat debug_example.rb
def raise_exception
  raise "Sample exception"
end

def another_method
  raise_exception
end

another_method

To debug, invoke MagLev with the -d option:

$ maglev-ruby -d debug_example.rb
 _____________________________________________________________________________
|                             Configuration Files                             |
|                                                                             |
| System File: /Users/pmclain/projects/maglev/git/etc/system.conf             |
|                                                                             |
| Executable File: /Users/pmclain/tmp/gem.conf                                |
| Warning:  File not found (errno=2,ENOENT, The file or directory specified cannot
| be found)                                                                   |
|           using defaults.                                                   |
|_____________________________________________________________________________|
(vmGc spaceSizes: eden init 2000K max 28128K , survivor init 336K max 4688K,
 vmGc    old max 112496K, code max 30000K, perm max 15000K, pom 10 * 12504K = 125040K,
 vmGc    remSet 2880K, meSpace max 143384K oopMapSize 1048576 )
 _____________________________________________________________________________
|             GemStone/S64 Object-Oriented Data Management System             |
|               Copyright (C) GemStone Systems, Inc. 1986-2008.               |
|                            All rights reserved.                             |
|     covered by Patent Number 6,567,905 Generational Garbage Collector.      |
+-----------------------------------------------------------------------------+
|    PROGRAM: topaz, Linear GemStone Interface (Linked Session)               |
|    VERSION: 3.0.0, Tue Dec  2 17:07:56 2008 pmclain private build           |
|      BUILD: 64bit-20633-PRIVATE                                             |
|  BUILT FOR: Darwin (Mac)                                                    |
|       MODE: 64 bit                                                          |
| RUNNING ON: 2-CPU cairo.gemstone.com i386 (Darwin 9.5.0 ) 2400MHz MacBook4,1|
| 2048MB                                                                      |
| PROCESS ID: 16567     DATE: 12/04/08 18:01:15 PST                           |
|   USER IDS: REAL=pmclain (650) EFFECTIVE=pmclain (650)                      |
|_____________________________________________________________________________|
[Info]: LNK client/gem GCI levels = 801/801
-----------------------------------------------------
GemStone: Error         Nonfatal
Error, 'Sample exception'
Error Category: 231169 [GemStone] Number: 2023 Arg Count: 1 Context : 91077889
Arg 1: [91078913 sz:16 cls: 74753 String] Sample exception
topaz 1>

We are now at the Topaz prompt and can look at the stack with the where command (abbreviated wh):

topaz 1> wh
==> 1 Exception >> _pass:with: (envId 0) @8 line 23   [methId 2266369]
2 Exception >> pass (envId 0) @2 line 14   [methId 4062465]
3 [] in  RubyFile >> load (envId 0) @4 line 18   [methId 53753089]
4 Exception >> _executeHandler: (envId 0) @2 line 7   [methId 2186753]
5 Exception >> _pass:with: (envId 0) @4 line 18   [methId 2266369]
6 Exception >> pass (envId 0) @2 line 14   [methId 4062465]
7 [] in  RubyCompiler >> compileFileNamed:do: (envId 0) @4 line 26   [methId 52167937]
8 Exception >> _executeHandler: (envId 0) @2 line 7   [methId 2186753]
9 Exception >> signal (envId 0) @1 line 1   [methId 2180865]
10 Exception >> signal: (envId 0) @3 line 7   [methId 2161921]
11 Exception class >> signal: (envId 0) @3 line 4   [methId 2045953]
12 Object >> raise:: (envId 1) @2 line 2   [methId 72786689]
13 String >> signal (envId 1) @2 line 2   [methId 72575745]
14 String >> signal: (envId 1) @2 line 1   [methId 72632321]
15 Object >> raise: (envId 1) @2 line 2   [methId 72742913]
16 Object >> raise_exception (envId 1) @2 line 2   [methId 91096065]
17 Object >> another_method (envId 1) @2 line 2   [methId 91097089]
18 Object >> _compileFile (envId 1) @6 line 9   [methId 91098113]
19 [] in  RubyCompiler >> compileFileNamed:do: (envId 0) @3 line 10   [methId 52139009]
20 [] in  RubyCompiler >> loadFileNamed: (envId 0) @2 line 2   [methId 51476481]
21 [] in  RubyCompiler >> compileFileNamed:do: (envId 0) @3 line 10   [methId 52141569]
...
topaz 1>

To see the location in the ruby source file, we'll look at stack frame 18, using the frame command (abbreviated fr):

topaz 1> fr 18
18 Object >> _compileFile (envId 1) @6 line 9   [methId 91098113]
    receiver [91094785 sz:0 cls: 72193 Object] anObject
topaz 1>

And then list the current frame's source:

topaz 1> list
   def raise_exception
     raise "Sample exception"
   end

   def another_method
     raise_exception
   end

   another_method
 * ^6                                                                 *******

   # method starts at line 1 of file /Users/pmclain/tmp/debug_example.rb
topaz 1>

We can move down the stack with the down command:

topaz 1> down
17 Object >> another_method (envId 1) @2 line 2   [methId 91097089]
    receiver [91094785 sz:0 cls: 72193 Object] anObject
topaz 1>

And we can focus in on the current section of the stack frame by using where with a parameter:

topaz 1> wh 10
17 Object >> another_method (envId 1) @2 line 2   [methId 91097089]
18 Object >> _compileFile (envId 1) @6 line 9   [methId 91098113]
19 [] in  RubyCompiler >> compileFileNamed:do: (envId 0) @3 line 10   [methId 52139009]
20 [] in  RubyCompiler >> loadFileNamed: (envId 0) @2 line 2   [methId 51476481]
21 [] in  RubyCompiler >> compileFileNamed:do: (envId 0) @3 line 10   [methId 52141569]
22 ExecBlock >> on:do: (envId 0) @2 line 53   [methId 4329729]
23 [] in  RubyCompiler >> compileFileNamed:do: (envId 0) @6 line 11   [methId 52148225]
24 ExecBlock >> ensure: (envId 0) @2 line 10   [methId 4310273]
25 [] in  RubyCompiler >> compileFileNamed:do: (envId 0) @12 line 19   [methId 52168193]
26 ExecBlock >> on:do: (envId 0) @2 line 53   [methId 4329729]
topaz 1> list
   def another_method
     raise_exception
 * ^2                                                                 *******

   # method starts at line 5 of file /Users/pmclain/tmp/debug_example.rb
topaz 1> down
16 Object >> raise_exception (envId 1) @2 line 2   [methId 91096065]
    receiver [91094785 sz:0 cls: 72193 Object] anObject
topaz 1> list
   def raise_exception
     raise "Sample exception"
 * ^2                                                                 *******

   # method starts at line 1 of file /Users/pmclain/tmp/debug_example.rb
topaz 1>

Etc. Read the Topaz manual for all the details.

Debug by installing an exception handler

Often, it is convenient to debug when catching a specific exception. You can install a debug handler in your ruby code (Exception.install_debug_block) that will be called for all ruby exceptions. You can then choose to drop down into topaz:

# Run this file with "maglev-ruby -d" to see how the debug block interacts
# with the topaz debugger.

# This debug block will catch all ruby exceptions raised by the program.
# This example prints all the exceptions, and drops into topaz for
# RuntimeErrors
Exception.install_debug_block do |e|
  puts "-- Caught #{e}"      # Print all exceptions

  case e
  when RuntimeError
    # drop into topaz if we get a RuntimeError
    nil.pause
  end

  # Since we did not re-raise the exception, the program will continue.
end

def raise_a_ruckus(exception)
  begin
    puts "-- About to raise #{exception}"
    raise exception
  rescue Exception
    # Rescue, just so we don't terminate
  end
end

# Raise some random exceptions, to see how
[IOError.new, RuntimeError.new, NotImplementedError.new].each do |e|
  raise_a_ruckus(e)
end

If you run the script above, with maglev-ruby -d, then you'll see each of the exceptions printed on stdout, and then you'll be dropped into the topaz debugger when the RuntimeError is raised. You can examine the stack, and then continue, and the rest of the program will complete.

Pausing with nil.pause

If you run in debug mode, you can pause and get the Topaz prompt using nil.pause (or anything_else.pause) in your ruby code:

nil.pause if rand(10) == 7 # if you're lucky, pause 10% of the time

Then you can continue with continue:

Debugging src/kernel code

Sometimes it is helpful to add debugging code in the src/kernel/* code. Since this is bootstrap code, you may want to turn off debugging code until after the bootstrap phase (since not all objects/methods are available). You can use the $MaglevInBootstrap global variable to test the bootstrap state.

E.g., here we instrument Module#include to print out module inclusion:

# src/kernel/Module.rb
# ...
def include(*modules)
  # this variant gets bridge methods
  unless $MaglevInBootstrap
    # puts not available during bootstrap
    puts "======= (Module) #{self}.include(#{modules.inspect})"
  end
  modules.reverse.each do |a_module|
    a_module.append_features(self)
    a_module.included(self)
  end
  self
end
def include(a_module)
  # variant needed for bootstrap
  unless $MaglevInBootstrap
    # puts not available during bootstrap
    puts "======= (Module) #{self}.include(#{a_module})"
  end
  a_module.append_features(self)
  a_module.included(self)
  self
end

We then reload primitives (to enable our debug code; rake maglev:reload_prims). When we run a program:

...
======= (Module) Gem::Version.include(Comparable)
======= (Module) Gem::Requirement.include(Comparable)
======= (Module) FileUtils.include(FileUtils::StreamUtils_)
======= (Module) FileUtils::Entry_.include(FileUtils::StreamUtils_)
======= (Module) FileUtils::Verbose.include(FileUtils)
======= (Module) FileUtils::NoWrite.include(FileUtils)
======= (Module) FileUtils::DryRun.include(FileUtils)
...

Topaz Overview, Documentation and Help

Most of the scripts and rake tasks are based on Topaz scripts and use the initialization files in $MAGLEV_HOME/etc/* to log into Topaz and setup the MagLev Ruby environment.

Topaz Manual

The Topaz Programming Environment manual contains documentation on using Topaz with GemStone/S. However, that version of the manual is for the 2.3 version of GemStone/S, and MagLev runs on the 3.0 version of GemStone/S. GemStone/S 3.0 has not yet been publicly released. When 3.0 documentation becomes available, GemStone will update the link.

Topaz Help

Topaz version 3.0 has online help, which includes the Ruby-specific commands that are not covered in the manual:

topaz 1> help

  TOPAZ - a programmer's interface to the GemStone system

  For an overview of Topaz, see the "Overview" help topic.

Help is available for:
HELP       ABORT      BEGIN      BREAK      C          CATEGORY:
CLASSMETHOD:          COMMIT     CONTINUE   DEFINE     DISPLAY
DISASSEM   DOIT       DOWN [<anInteger>]    EDIT       ERRORCOUNT
EXIT [<status>]       EXITIFNOERROR         EXPECTBUG
EXPECTERROR           EXPECTVALUE           FILEOUT
FRAME [<anInteger>]   GCITRACE   HIERARCHY  IFERR      IFERR_LIST
IFERR_CLEAR           IFERROR    INPUT      LEVEL      LIMIT
LIST       LISTW      LOADUA     LOGIN      LOGOUT     LOOKUP
METHOD:    OBJECT     OBJ1       OBJ2       OBJ1Z      OBJ2Z
Object_Specification_Formats     OMIT       OPAL       OUTPUT
PAUSEFORDEBUG         PRINTIT    PROTECTMETHODS
UNPROTECTMETHODS      RELEASEALL            REMARK
REMOVEALLMETHODS      REMOVEALLCLASSMETHODS RUBYHIERARCHY
RUBYLIST   RUBYLOOKUP RUBYMETHOD RUBYCLASSMETHOD       RUBYRUN
RUN        RUNENV     INTERP     INTERPENV  NBRUN      NBSTEP
NBRESULT   SEND       SENDENV    SET        SHELL      SPAWN
STACK      STACKWAITFORDEBUG     STK        STATUS     STEP
TEMPORARY  THREAD     THREADS    TIME       TOPAZPAUSEFORDEBUG
TOPAZWAITFORDEBUG     QUIT [<status>]       UP [<anInteger>]
WHERE <various options>          Overview   Blanks
Break_Handling_in_Topaz          Debugging_Smalltalk_DB_Code
Initialization        Local_Variables       Multiple_Sessions
Specifying_Objects    Strings    Linked_vs_RPC
Network_Resource_String
Topic?

press RETURN to continue…

Summary of Topaz commands

Exit and Help

exit

Exit Topaz

help

Get help

Stack info

where

List current stack

where nn

Show only nn frames starting at current frame.

where string

Show only frames containing string. string cannot start with +, -, a decimal digit, or contain whitespace. Search is case-sensitive. Current frame is set to first frame matched by the search.

where 'string'

Same as above, but 'string' may contain digits and whitespace.

frame

Show information about the current frame

list

List source code for the current frame

listw

List a window of source code around the current IP location. Useful if you have a long method, and just want to concentrate on the current location.

down

Move down one stack frame.

up

Move up one stack frame.

Example usage of WHERE for debuggging a RubySpec failure

fr 5

See reason for failure where the “pause” for DEBUG_SPEC is

wh _co

Search for _compileFile frames

l

Window listing for first _compileFile frame

Break point control

break method Foo bar @ 2

Break at step point 2 in Foo#bar.

continue

Continue after a break point.

set class Foo

Set the current class to Foo

list step method: bar

List source code with possible break points shown. Use this to figure out which numeric parameter to pass via @ nn for the break method above.

list breaks

List current break points

list breaks method: foo

List

Examining variables at different levels of detail

display oops

Turn on display of object ids (necessary for examining variables)

obj1 @ NNN

Inspect the object with id NNN, one level deep

obj2 @ NNN

Inspect the object with id NNN, two levels deep

level N

Set the default display level to N.

Running Topaz directly

You can get a Topaz prompt via rake (but you will not yet be logged into the VM).

$ cd $MAGLEV_HOME
$ rake dev:topaz
(in /Users/pmclain/maglev)
topaz 1>

Type exit to leave Topaz