toivoh / Debug.jl

Prototype interactive debugger for Julia
Other
86 stars 13 forks source link

Printing program context #37

Closed nfoti closed 11 years ago

nfoti commented 11 years ago

I'm wondering if it would be possible to print program context while debugging like in pdb and adding an "l" (lowercase L) command to print some program lines around the current line.

I played around a bit and see that the AST is available so some program context could potentially be displayed. However, it would be really useful if the raw code could be attached before the julia parser converted it to the AST.

This would make debugging much easier when one doesn't have a text editor open.

If this is something you'd like to add I'd be happy to help.

toivoh commented 11 years ago

I would definitely like to add it. The question is how :) And it's great if you want to help. See https://groups.google.com/d/msg/julia-users/9Gfp3OZJGk0/V1ACjZK7TQUJ for a little more background. We can continue the discussion here if you like.

nfoti commented 11 years ago

Great! I've done some poking around with the augmented AST and I've found all the information we need to reconstruct the code.

There is one issue though in that we only have the AST for the source lines that the @debug invocation is applied to. This may not be so bad, but we won't get the behavior of gdb or pdb where we'll see the parts of the file around what's being debugged. Given that one needs to mark code to debug I don't think this matters right now. If the debugging information were ever integrated into the core julia AST so that debugging was "always on" and breakpoints could be inserted anywhere (like in Matlab or python) then this may be something to think about.

I think it is possible to write a pretty_print function that can reconstruct the source. I can take a stab at these two things over the next few days, but I have very little time, so if you want to run with it then that's fine with me.

toivoh commented 11 years ago

You can traverse down the tree already - see argsof in src/Meta.jl. The augmented AST closely follows the structure of regular Julia ASTs, but with some extra stuff put in. I am the author of Julia's current AST pretty printing in Base/show.jl (for Expr etc.). I was thinking to refactor that to expose the knowledge of how to print an AST so that it could be reused by e.g. the augmented AST. That could hopefully allow to add in some filtering of starting and finishing lines as well.

nfoti commented 11 years ago

As you can see in my updated post above I figured out how to move down the tree. I think refactoring that code would be very useful. I'll hold off on doing anything until you've done that, as anything I do now will be a hack.

I don't think this will be too hard to incorporate and will make julia debugging incredibly easier.

Thanks.

nfoti commented 11 years ago

I've looked at the AST printing code in show.jl and it doesn't seem like it would be too hard to adapt it to work with the augmented AST. Adding the ability to only print lines in a certain range also seems straightforward (maybe a bit tedious though).

Let me know what your plans are with the show.jl printing code and I'll be happy to start hacking away on the augmented AST version.

nfoti commented 11 years ago

I noticed that if you put a @debug statement in a file and use evalfile the line numbers that the debugger reports do not correspond to the line numbers of the program. Obviously the debugger is getting its line numbers from the AST, not the file.

I'm thinking that a debugfile function would be really useful for the case where you have some functions and a driver program to test them, or just a script that does some analysis. It would basically do the same thing that evalfile does, but it would wrap the source file in a @debug macro so that users can just place @bp's in their script.

The debugfile function could also just save the source code in an array and we can easily print program context by looking up line locations into that array (according to some map). Printing program context from code from the interactive session will require traversing the decorated AST and whatnot. The case where one is debugging the contents of a file seems like the more important (and easier) case to get program context working for though as the debugger will probably be more often used to debug large programs.

I'm not sure how well this will play with modules, but one could always just debug a module as a bunch of functions and wrap the result in a module.

I'm going to play with this a bit as I would like to switch to julia for my research code after my current project and having this kind of debugging functionality is what I need. Any feedback or advice is greatly appreciated though.

Thanks.

toivoh commented 11 years ago

Sorry for the silence. I've been starting to look at refactoring the pretty printing code, but I'm not able to amass very much time for this project right now.

Looking at the implementation of evalfile, it seems that it will reset the line number at the start of each top level expression (and forget about all file info). This won't affect just @debug, but anything that needs line numbers, like stack traces from exceptions. I think that you should file an issue about this with Julia.

It might not be so hard write a debugfile function, I might give it a shot at some point. Though I guess that the main gain would be to save you from enclosing the contents in @debug begin ... end. It is an interesting idea to save the source code lines in this case, but I'm not sure how to integrate that info into the whole. Suppose that you have marked up some code with @debug in another file two, how should that act when trying to print source lines there? Another complication is that if you step into a macro, the corresponding lines will not exist in the current file. They might very well exist in another file, but right now you can't find out which one from the AST. That is the main reason why I am inclined to print parts of the AST, rather than verbatim source code.

nfoti commented 11 years ago

No problem, I just had some free cycles and ran into a large enough problem with python yesterday that would have been trivial with julia that I wanted to think about this a bit more. No rush on refactoring either. I agree, there are problems with the debugfile idea, but the big advantage I saw was that the debugger could be connected to the actual source code. It would be great if a source code debugger would work, but you're probably right that given the current state of things we should just print parts of the AST.

Looking at debuggers for LLVM languages it seems like LLVM has some debugging features, like being able to tell you what lines in a source file an instruction corresponds to. The core guys may need to expose some low-level parts of the language in order for a source level debugger to be created. To be honest, at some point a larger discussion needs to probably happen with Jeff, Stefan, and the rest of the community about how to build a source level debugger. I think what you have created here is great, but I think something as full featured as gdb, pdb or the Matlab debugger will require some low-level integration into the language. Given that people seem to be writing more complex programs with julia this may need to happen sooner than later.

Anyways, you bring up some excellent points regarding macros. Macros are probably better debugged in a LISP-like manner, where Debug.jl is already very good and just printing the AST will work. However, more data analysis-type code that probably uses fewer macros may be better served by a source level debugger.

toivoh commented 11 years ago

Thinking about this some more, I have come round to that printing verbatim source lines is actually a good idea. It's nice to be able to recognize your own source code, it's probably easier to implement than pretty printing a range of lines from the AST, and it will work in many cases. If you're still interested to take a stab at it, I'd be more than happy to provide you with guidance. I still think that pretty printing lines from the AST will be a nice complement to have, but maybe in a little longer time frame. We might expose some bugs by trying to print lines from the source, but that's just as well.

User commands are processed in the function trap(node, scope) in UI.jl. The current line and file are accessible as string(node.loc.file) (I just pushed a small update to let node.loc.file be a Symbol, to save memory) and node.loc.file (or at least the debuggers best guess of them). A good start could be to try to open that file (it seems to be an absolute path), and if it succeeds, print a number of surrounding lines.

nfoti commented 11 years ago

Great, I think that the strategy you outlined of opening the file and printing some lines will be a good first pass at this. It seems like a lot of nodes in the decorated AST don't have a file associated with them even though the corresponding node in the AST does. It would be helpful if the file information made it through to the decorated AST for these cases. Additionally, all nodes that are trapped during debugging should probably have a corresponding file and line number, we should always know where the code we're trapped at is (unless it's like a ccall). I tried to figure out why the file information wasn't making it through for all nodes in the AST but haven't figured it out yet.

Thanks.

Just for reference, I've been putting the following code in a text file to use for testing:

using Debug

@debug begin

function f(x,y)
    w = x*y
    g(y)
end

function g(y)
    t = 0.
    for i in 1:5
        t += i*pi
    end
    return t
end

@bp
println(f(2,3))
println(f(1,1))
println(f(2,2))

end
toivoh commented 11 years ago

Sorry, I forgot to tell you that Julia only inserts source file info for functions right now. Does it work if you single step through a function? The current approach is intended to take the source file info from line nodes in begin blocks and propagate downwards into the following nodes in that block. I should verify that this really works.

I could probably add a step that assigns the source file that a function is defined in to the surrounding top level code. And yes, you need to include or require the file to get the source file info into the AST.

nfoti commented 11 years ago

I have a preliminary version of printing program context while debugging in the printsrc branch of my fork (link). When a node traps in the trap function in UI.jl the lines above and below the current line are printed (with line numbers and an indicator for which is the current line). I also added two commands to the debugger, l which prints 5 lines above and below the current line as well as l d, where d is a digit will print d lines above and below the current line. This could be extended to arbitrary numbers, but I thought that more than 9 lines above and below would look awful.

The code to check out is in UI.jl. I added a print_context function there and reworked the event-loop in the trap function.

Unfortunately, context is only printed for nodes that have a valid file, which as you say are only lines in functions. As you can see in the test code above, I wrap everything in a begin block, but the println's don't get files assigned to them. Only the lines inside the functions get files. Additionally, it would be great if Breakpoint nodes could get files associated since the debugger actually stops at them. Maybe when a breakpoint is trapped the debugger automatically moves ahead to the next executable line. It's kind of weird to stop on the breakpoint.

Is adding file information to other nodes in the tree something that can be done in the Debug package, or is it something that needs to be added to the julia parser. I actually just did a simple find-and-replace in the parser to always add a line-number-filename-node rather than a line-number-node and recompiled julia and now I get file and line locations at all executable lines (so the debugger prints program context everywhere). I'm not sure if there are bad things that could come of this, but it does seem to be a simple solution. Maybe the parser should also store the file as a symbol rather than a string to save memory. Let me know what you think and I can start a thread on julia-dev if you think this is something that should be added to the parser, and then all the code in Debug.jl will just work.

I also added a p command to the debugger so that one can easily print variables with names s,n,c,, etc. like in gdb/pdb.

toivoh commented 11 years ago

I looked a little bit at your branch, it looks good by what I could tell. It would be nice to print an error message when the file could not be opened though, like Failed to open source file XXXX or No source file info available. When you feel ready, can you submit a pull request with the line printing stuff? Then I can review the actual changes, and hopefully pull this in and publish it.

I checked, and it seems to me that breakpoint nodes do get a file, if they're inside a function. It's an easy change to not stop at breakpoint nodes, but that would make it much harder to use the ignore breakpoint feature right now. There should really be convenient ways to navigate the augmented AST, so that you can e.g. disable a breakpoint even if you are not standing on it. Then I think that it would make more sense not to stop at them.

Regarding more file info, I definitely think that the best solution would be if the julia parser could include it from the beginning. The only thing I can do in the Debug package is to guess the left-out values, which is bound to be wrong sometimes. I opened an issue about it at https://github.com/JuliaLang/julia/issues/1636, but not much has happened since. You're more than welcome to start a thread about it. I do think that the file name is saved as a symbol; that's the way it appears in the Julia AST:s anyway.

Also, have you been editing your posts after you submitted them? I usually read my github notifications in my email, but when I came here, there seemed to be parts that I hadn't read.

nfoti commented 11 years ago

Sounds good. I'll clean up my code a bit and submit a pull request. I'll start a thread on julia-dev and reference issue 1636 about getting the parser to emit file information.

Yes, I have been editing my posts. Not sure why I thought they'd get forwarded to you or that you'd check the discussion again. I'll reply in email if I need to update anything.

On Tue, Mar 5, 2013 at 2:46 PM, toivoh notifications@github.com wrote:

I looked a little bit at your branch, it looks good by what I could tell. It would be nice to print an error message when the file could not be opened though, like Failed to open source file XXXX or No source file info available. When you feel ready, can you submit a pull request with the line printing stuff? Then I can review the actual changes, and hopefully pull this in and publish it.

I checked, and it seems to me that breakpoint nodes do get a file, if they're inside a function. It's an easy change to not stop at breakpoint nodes, but that would make it much harder to use the ignore breakpoint feature right now. There should really be convenient ways to navigate the augmented AST, so that you can e.g. disable a breakpoint even if you are not standing on it. Then I think that it would make more sense not to stop at them.

Regarding more file info, I definitely think that the best solution would be if the julia parser could include it from the beginning. The only thing I can do in the Debug package is to guess the left-out values, which is bound to be wrong sometimes. I opened an issue about it at JuliaLang/julia#1636 https://github.com/JuliaLang/julia/issues/1636, but not much has happened since. You're more than welcome to start a thread about it. I do think that the file name is saved as a symbol; that's the way it appears in the Julia AST:s anyway.

Also, have you been editing your posts after you submitted them? I usually read my github notifications in my email, but when I came here, there seemed to be parts that I hadn't read.

— Reply to this email directly or view it on GitHubhttps://github.com/toivoh/Debug.jl/issues/37#issuecomment-14461015 .

toivoh commented 11 years ago

Sounds great! No worries, I've done the same thing (now I know why you shouldn't :) Just thought you should know, so that I don't miss your communication.

toivoh commented 11 years ago

I think that we can close this now that #40 is merged.