In julia, install the
load("pkg.jl") Pkg.init() # If you haven't done it before Pkg.add("Debug")
@debug macro to mark code that you want to step through.
@debug can only be used in global scope, since it needs access to all
scopes that surround a piece of code to be analyzed.
julia> load("Debug.jl") julia> using Debug julia> @debug let # line 1 x = 0 # line 2 for k=1:3 # line 3 @bp # line 4 x += k # line 5 end # line 6 print("x = $x") # line 7 end at :5 debug:5> k,x (1,0) debug:5> x = 10 10 debug:5> k,x (1,10) debug:5> c at :5 debug:5> k,x (2,11) debug:5> c at :5 debug:5> k,x (3,13) debug:5> s at :7 debug:7> k,x in anonymous: k not defined debug:7> x 16 debug:7> x += 4 20 debug:7> s x = 20
When inside a
@bp breaks and enters the debugger.
The following single-character commands have special meaing:
s: step into
c: continue to next breakpoint
q: quit debug session (calls
Any command string that is not one of these single characters is parsed and evaluated in the current scope.
@debug macro takes an optional trap function to be used instead of
the default interactive trap. The example
load("Debug.jl") using Base, Debug firstline = -1 function trap(loc::Loc, scope::Scope) global firstline = (firstline == -1) ? loc.line : firstline line = loc.line - firstline + 1 print(line, ":") if (line == 2); debug_eval(scope, :(x += 1)) end if (line > 1); print("\tx = ", debug_eval(scope, :x)) end if (line == 3); print("\tk = ", debug_eval(scope, :k)) end println() end @debug trap function f(n) x = 0 # line 1 for k=1:n # line 2 x += k # line 3 end # line 4 x = x*x # line 5 x # line 6 end f(3)
produces the output
1: 2: x = 1 3: x = 1 k = 1 3: x = 2 k = 2 3: x = 4 k = 3 5: x = 7 7: x = 49
scope argument passed to the
trap function can be used with
debug_eval(scope, ex) to evaluate an expression
ex as if it were in
How it Works
The main effort so far has gone into analyzing the scoping of symbols in a piece of code, and to modify code to allow one piece of code to be evaluated as if it were at some particular point in another piece of code. The interactive debug facility is built on top of this toolbox.
- The code passed to
@debugis analyzed to mark each block and symbol with an environment (static scope). Each environment has a parent and a set of symbols that are introduced in it, including reintroduced symbols that shadow definitions in an outer scope.
- The code is then instrumented to insert a trap call after each line number
in a block. A
Scope(runtime scope) object that contains getter and setter functions for each visible local symbol is also created upon entry to each block that lies within a new environment.
- The code passed to
debug_evalis analyzed in the same way as to
@debug. The code is then grafted into the supplied scope by replacing each reads/write to a variable with a call to the corresponding getter/setter function, if it is visible at that point in the grafted code.
I have tried to encode the scoping rules of julia as accurately as possible, but I'm bound to have missed something. Also,
- The scoping rules for
forblocks etc. in global scope are not quite accurate.
- Code within macro expansions may become tagged with the wrong source file.
Known issues can also be found at the issues page. Bug reports and feature requests are welcome.
The interactive debugger is very crude so far. It should be the next target for improvement once the scoping analysis is reasonably accurate.