Debug OCaml code

Hi to all here!

I am very excited to start exploring the wonderful world of OCaml(and just bough the RWO book, 2nd edition).

I am using VS Code with the OCaml official plugin but my worries is the absence of a serious debugger for the language. The ocamlearlybird VS Code plugin does not work with the latest language version (breakpoints do not ever got hit, no matter what I do) and the language’s toolchain’s debugger is very difficult and not very practical to use. I come from environments like Visual Studio and for the latest 5 years GoLand (for Go development) where the debuggers there are really state of the art and help you from simple programs to heavily concurrent ones.

My question is if I am missing some combo of IDE/editor + good debugger or if I am not, how the most experienced OCaml programmers work without it? And also, because I have spent a lot of time debugging concurrent code in Go, how one could debug such code in the upcoming Concurrent OCaml 5.0 ?

Thank you for any help on this!

Anthony

4 Likes

I’m afraid not; the closest thing there is to a visual debugger is ocamlearlybird, but unfortunately its original author is no longer maintaining it and no-one else has stepped up to the plate to do it so far, so it hasn’t been updated to work with the latest version of the language.

You can also use ocamldebug of course, but 1) does not work in native (neither does ocamlearlybird, by the way), and 2) it will probably feel rather hard-to-use when compared with the visual debuggers found in other languages.

Among the experienced programmers I know, the use of “printf” debugging is pervasive. But this is also a chicken-and-egg issue: probably more people would use a debugger if an easier-to-use debugger was available.

Incidentally, my feeling is that as a general rule there is less need for debugging tools when writing OCaml than in other languages because the language helps you to have fewer bugs to begin with. But of course “fewer” does not mean “zero”…

Cheers,
Nicolas

2 Likes

Hey Nicolas,

thanks a lot or your help!!

ocamlearlybird does seem able to step in library code but not in your own. Anyway, I understand what you are saying about the language help for fewer bugs although using a real debugger is a must (at my opinion). printf commands everywhere will send me back at my 90’s :smile:

Maybe the official plugin will be enhanced for debugging? Let’s hope.

Anthony

Hi there,

very, very long time ago (I think before Ocaml was even called Ocaml), I wrote an Emacs extension that provided mostly the same keyboard bindings as the Emacs Grand Unified Debugger for C and related languages. I am very sure that it wouldn’t work with the debugger as it is today, but nonetheless I’ll take a cursory look to confirm that. :slight_smile:

As for Vscode, no no no. Sorry. I have anxiety, I cannot look at moving pictures.


Ian

earlybird is/was great, and it almost hit excellent, it just required a lot of bootstrapping to setup. further, it was constantly one tick behind from the compiler version i was using. to have it in the ocaml platform first class would transform my ocaml workflow, as a subjectively terrible ocaml programmer :laughing:

2 Likes

I think this is right. And in truth, outside of Perl, I no longer use debuggers (b/c they never work for concurrent code, nor for really complex datastructures). Printf/logging it is. But in OCaml, there is a nice shortcut, that I’m sure many of us use: #trace.

Between #trace and specifying custom printers with #install_printer, you can get pretty good control over tracing output, and iteratively adjust it as you narrow-in on a bug. And then, putting all your toplevel directives in a file that you #use to run your testcase, you can get repeatability as you slowly iterate.

It’s true that actually putting logging directives in the code is useful, too. But that can be “too little, too late”; instead, I put in no-op functions like let watch_zz (x : t) = () and then call it at the point that I want to see what the value of some variable is (of type t, obvs). Then I can #trace watch_zz and when I run, I see that variable. Obvs. only works if that variable’s type t is monomorphic.

Hey cdaringe,

thank you for the reply!

I am very interested to learn how it is working for you (and not for me). I have followed the instructions I found in the internet but breakpoints do not get hit (and yes, I compile to bytecode). And I see other having the same problem. Is the problem that I have installed the lates of OCaml and earlybird is not maintained for 2 years ?

Anthony

Hi Chet_Murthy,

I think I need to dive more deeply in to the language to fully understand what you mean but when I do, I will follow your advice.

As for debuggers, in Go the one we have (Delve) is working very well in concurrent code, at least the version that JetBrains include in GoLand. And I think that I will miss that in OCaml.

1 Like

here’s an example: camlp5/include_ml at master · camlp5/camlp5 · GitHub

There’s a lot there, but:

  1. I load a “topfind” file, so I can use findlib commands to load packages.
  2. then I load a bunch of packages.
  3. then I define some printer functions for various types, and make them known to the toplevel with #install_printer

And then, at the bottom, I’d put some code that elicited a bug. So then I can just #use "include_ml";; in a fresh toplevel, to repro the bug. Add some #trace commands to trace individual functions, repeat. etc.

Defining a printer function is easy, and it’s well-documented, but basicaly, you use the Format module to print out something to stdout, so it’s a value of type t -> unit – that’s a printer for type t. If you get in the habit of using ppx.deriving.show to define pp and show functions on your types, then this becomes really easy (OCaml will infer a printing function for any type it knows, but this is definitely slower than specifying your own printer functions). I also often define pp_hum functions with the same signature as pp, but with “human-readable” output, and then use those in the printer functions I define at toplevel.

Interesting. So the reason I never use a debugger on concurrent code, is … my experience has been (going back to the early days of Java) that a debugger changes the dynamics of a concurrent program, and hence scares the bug away. It’s always been better for problem-determination to incrementally add log-lines, slowly inching toward the bug, until you have it trapped. But who knows … maybe things have improved.

I will note that this was almost always in large commercial systems, so even attaching a debugger to a system that is being stressed under-load is … often problematic and very often forbidden. So maybe I’ve just developed these habits b/c if you can’t use debuggers (for business reasons) you learn how to live without. There is (after all) an entire school of thought for using copious logging as a substitute for debuggers (via “time-travel”). There were even companies that sold tooling for this purpose.

There’s a striking difference between the reality of the unfinished and difficult to use debuggers presented in this thread and the OCaml marketing posts from Tarides: Six Surprising Reasons the OCaml Programming Language is Good for Business | Tarides

It is also worth noting that OCaml offers several strong methods for debugging its programs. From the fast, interactive, REPL to the powerful symbolic relay debugger, OCaml lets you eliminate bugs at compile time and avoid them at runtime. This, in combination with how effective the debugging programs are, makes OCaml an easy language to debug.

2 Likes

Oh, it is still documented in the manual:

https://v2.ocaml.org/releases/4.14/htmlman/debugger.html#s:inf-debugger

so it probably works to some degree :slight_smile: Maybe you can try it. But the manual only mentions caml-mode, not tuareg. I don’t know what , if anything tuareg does to support this. Anyone?


Ian

ocamldebug works fine and is maintained, but it is not a “visual” debugger and does not have good integration with VS Code or similar editors. (Though it does have some integration with emacs, but I haven’t used it myself, so I don’t know how good it is).

Cheers,
Nicolas

I will certainly give ocamldebug a try although being used for so many years to visual debuggers that integrates with the ide/editor (i.e. you debug in the same place that you write your code) will be something weird for me…

But thank you all for your help, at least I realised not to give any more time to ocamlearlybird to figure out what is going wrong and focus on ocamldebug.

Hello,
I generally use gdb. The debug information generated by the compiler allows basic source mapping, which does the job for setting breakpoints and stepping through the code. Unfortunately, in this setting there is no nice printing of the variables’ values. Personally, I open a window with the assembly code next to the OCaml code, and manage with more or less effort to map the OCaml values to registers or memory locations, which allows me to inspect them if needed. But it does require some knowledge about the internal representation of values in OCaml, and about the way functions are compiled.

To debug concurrent programs I (and several other people working on the OCaml internals) use rr, which is a layer above gdb. rr allows you to first record an execution, then step back and forth in this execution without modifying how the program behaved during the recording. It is really a huge time-saver in finding concurrency bugs.

There was quite a bit of work to try and add DWARF debug format support to OCaml. Unfortunately this work was halted, with the result being that we can’t easily debug OCaml programs.
IMO this is the highest engineering priority after multicore is integrated: a modern language has to be easily debuggable.

10 Likes

@bluddy I could not agree more with your last sentence. And it slows down adoption because this is something clearly missing.

3 Likes

I have also been feeling this pain recently

My first couple of attempts to use ocamldebug only resulted in cryptic errors and confusion

A small part of the problem is the docs for it tend more to “a list of things that exist” side, rather than “how to use this thing” (a common problem…).

I tried a bit harder over the weekend and managed to use it a bit.

Some thoughts:

  • it should be integrated into dune somehow, e.g. I should be able to dune exec --debug or dune test --debug and dune will output bytecode (regardless of existing project settings) and run it under ocamldebug with all necessary paths configured
  • as ever with dune docs it’s hard to know which stanzas are valid where, but adding (modes byte exe) under (tests ...) worked
  • I set a breakpoint and it worked:
(ocd) break @ Oktree 319
Loading program... done.
Breakpoint 1 at 0:569496: file lib/oktree.ml, line 318, characters 19-59
(ocd) run
Time: 71153 - pc: 0:569500 - module Oktree
Breakpoint: 1
319           <|b|>(oct, d)
  • but not entirely:
(ocd) print d
Cannot find module Oktree__.
  • it turns out I have to manually pass a lib path, so my invocation looks like: ocamldebug -I _build/install/default/lib/oktree/ _build/default/tests/test_oktree.bc … these are all tedious details that dune could handle for me
  • readline support (i.e. proper cursor navigation, up-arrow history) and colours (i.e. syntax highlighting etc) would be nice

Debugging in other langs that I’m most familiar with is Python with ipdb, where you get many of the niceties of the IPython shell (readline, syntax highlighting, tab completion menus, pretty-printing shortcuts etc). Python also has the breakpoint() function to set breakpoints from the code itself, will drop into debugger when run. And typical test framework like pytest can drop into debugger for any unhandled exception or failed assertion when running the test suite.

Also for JS in VS Code, which can be fiddly to configure to configure properly but great once it’s working, being able to set breakpoints and watch expressions and browse the stack in the IDE GUI.

4 Likes

I’ve been working on something to enable the dead simple import pdb; pdb.set_trace() debugging workflow from Python. It starts a toplevel in context, so you get to call functions using values in scope, mutate refs, etc. Let me know if you try it out and have suggestions.

(You might also be interested in ppx_debug, which I presented at the OCaml workshop this year. It has a more involved build setup, but could be a good investment for exploring an unfamiliar codebase, or just visualizing execution in different ways.)

14 Likes

Holey moley! I gotta try this! I’ll take a look at your presentation. I use Camlp5 for all my PPX extensions, so in order to use your stuff, I think I’m going to have to reimplement your PPX rewriter, but should be able to keep all your runtime infrastructure. Boy howdy, this looks interesting!