What is the standard way to debug Julia?

Hi all!

Sorry for the noobish question (borderline between Tooling and General usage). I needed to debug a piece of Julia code yesterday, so I checked online what was the official debugger. I was surprised to find there isn’t one; or better, there are different packages (third-party-ish) used to accomplished debugging. Since the documentation I found is a bit dated, and IIUC debugging is a topic actively developed in Julia, I wanted to ask you what is the updated situation, as of late 2022.

  1. what is the standard way to debug in Julia? What are the differences between the various packages?

For what I understood:

  • Debugger.jl is the most complete and “standard-interface” debugger. It provides usual commands (a-la-GDB), suffers from poor performance, and it’s the one used by VSCode and Juno. This is the “official” Julia debugger
  • Infiltrate.jl is used to quickly inspect the state of the program at a given point, but can do only that (no stepping, evaluation, watching, breakpoints)
  • Rebugger.jl is basically dead

… how many did I manage to get wrong? :sweat_smile:

  1. Also, naive question, if Julia is using LLVM, can’t LLDB be used to debug?

Thanks!

7 Likes

You are pretty much correct. And yes, you can use gdb or lldb or rr to debug julia as well, and they play reasonably well with JIT code, though it’s that easy to parse what’s going on from these debuggers if you don’t know what’s going.

1 Like

Using VSCode, Debugger.jl is reasonably fast in my experience. The trick is to make sure that packages you don’t plan to need to step into are running in compiled mode. Often this just means that Base should be run in compiled mode (which is true by default). In practice just having base in compiled mode seems to be sufficiently fast. (In the past Base was not run in compiled mode by default, and if you use Debugger.jl outside of VScode I think this is still the case, but haven’t checked recently). But it can also be good to mark large well used and supported packed as being in compiled mode.

In spite of that, I often find myself just using Infiltrator since it is easy to use and suffices for many purposes, especially with @exfiltrate. This is a better interface I’d you want to mess around a lot with things in the REPL.

Many times I will often just use @debug or @show, that can work better for debugging, e.g. when evaluating an algorithms data flow.

3 Likes

Thanks to both!

To @gbaraldi : sorry, did you mean that LLDB output is not easy to parse?

To @haberdashPI thanks for your suggestions. The documentation of Debugger unfortunately is not very clear: can you tell me how can I turn on compiled mode only for specific modules?

Yeah :smile: . It’s not anything out of the ordinary, but knowing a bit of julia internals will go a long way if you want to use it. I wouldn’t use it for debugging normal julia code though.

1 Like

Got it, thanks! :+1:

In VSCode, you need to do F5 Start Debugging, then in the bottom of the Run and Debug panel, a subpanel called “Julia: Compiled Code” appears.

In the latest version, it seems all code is compiled by default. You need to explicitly set modules you want to step through as interpreted. Click + button in the Julia subpanel, and prefix your module name with -.

There does seem to lack a central source for the most common way to debug. The tools are changing all the time.

4 Likes

Thanks! But actually I am not currently using VSCode. When using another editor/CLI, when you say “explicitly set the modules to be interpreted” are you referring to the @enter macro, or to something else?

At the bottom of the Debugger.jl readme, you will see this section, describing how to turn on compiled mode.

Compiled mode

In order to fully support breakpoints, the debugger interprets all code, even code that is stepped over. Currently, there are cases where the interpreter is too slow for this to be feasible. A workaround is to use “compiled mode” which is toggled by pressing C in the debug REPL mode (note the change of prompt color). When using compiled mode, code that is stepped over will be executed by the normal julia compiler and run just as fast as normally. The drawback is of course that breakpoints in code that is stepped over are missed.

5 Likes

That doesn’t seem to be the case for me here in May 2024. Almost nothing is precompiled and it makes debugging unusable. Even Debugger.jl is often too slow because my variables are huge arrays that are hundreds of megabytes. I am unable to debug those with smaller sized arrays.

Another problem is that you can’t jump into a called function because it calls a bunch of other UNCOMPILED functions to evaluate the input arguments. Thus @infiltrate is what you are left with.

1 Like

It’s hard to tell exactly because you didn’t give any specifics, but this seems like a mix of “skill issue” and bad UI. For example:

You can step over those, instead of stepping in. Then step into what you want to step into only after you stepped over what you don’t want to step into. Yeah, the UI is not ideal.

Another tip to make debbuging easier is to use simpler statements in your source code, e.g., instead of f(g(h(x), c)), use

a = h(x)
b = g(a, c)
f(b)

Also, I try to use SSA form as a code style. E.g., instead of this:

a = 3.1
a = sin(a)
a = 1 - a

Use this:

a = 3.1
b = sin(a)
c = 1 - b
2 Likes

I understand that the transition of the LLVM JIT backend from MCJIT to ORC made it more difficult for programming languages to debug JIT code. Has the situation improved lately?

1 Like

It mostly just works in my experience, even though the JITed code doesn’t have too much information (that is because of julia + LLVM not being great at debug info).

1 Like

I don’t think stepping over uncompiled code would make it run faster.

Out of curiosity, does DWARF 5 support Julia well enough or should DWARF 6 be extended to better support Julia?

I believe nsajko was referencing Debugger.jl’s compiled mode, as described in the README:

In order to fully support breakpoints, the debugger interprets all code, even code that is stepped over. Currently, there are cases where the interpreter is too slow for this to be feasible. A workaround is to use “compiled mode” which is toggled by pressing C in the debug REPL mode (note the change of prompt color). When using compiled mode, code that is stepped over will be executed by the normal julia compiler and run just as fast as normally. The drawback is of course that breakpoints in code that is stepped over are missed.

That last line hints at it, but source-level debuggers cannot work after compile-time optimizations distort or elide code. You can only step into unoptimized code.

In my experience debugging in Julia is horribly slow, even with the newer versions (1.11.1)

1 Like

I usually use Infiltrator.jl, and add @infiltrate before the code that crashes. It works a bit like pythons pdb.

If I need more real debugging I use Debugger.jl and the @enter before the function that crashes. The first thing to do before anything else, when inside the function, is to go to Compiled mode. Press C!, otherwise the debugging will be really slow. Then do next or add a breakpoint.

I would like Infiltrator to have a next function.

5 Likes

Debugger.jl seems OK in moving thru the source during debugging. Issue is with array or vector variables, there doesn’t seem to be any simple way to query their sizes. The built- infr option results in just a condensed display of every array’s contents, without mention of its size. This is bothersome when a size mismatch is precisely the error reported and one is trying to spot it.

Any ideas here? Thanks!

It sounds like a watch expression would help. Add it like w add EXPR. I suppose EXPR can be any expression, and the watchpoint triggers when the value changes. So I guess you’d want to involve length(array) in the expression.