[ANN] earlybird 1.2.0 – revival of a debugger

Hi, everyone!

The lacking state of OCaml debuggers has been a rather hot topic recently, so I’m particularly excited to announce the release of earlybird 1.2.0.

Many of you might be familiar with OCaml earlybird: a debugger which supports Debug Adapter Protocol, allowing it to be used directly from VS Code’s builtin debugging GUI (among possibly other IDEs). This phenomenal tool was developed by @hackwaly.

Unfortunately, for a long time it wasn’t updated to support newer versions of OCaml and fell into despair. Since there’s still (or even moreso) need for OCaml debugging tools, it would be a real shame if earlybird was dead forever. Therefore, recently I volunteered to pick up its maintenance and get it up and running again.

I’m hereby announcing the release of earlybird 1.2.0 which supports OCaml 4.12, 4.13, 4.14 and 5.0. Moreover, I extended the VS Code OCaml Platform extension (since version 1.13) to directly be able to launch earlybird: GitHub - ocamllabs/vscode-ocaml-platform: Visual Studio Code extension for OCaml. This brings the handling of a DAP for OCaml into the same extension which handles the LSP for OCaml. Instructions are available at the above link!

Note that the OCaml Platform integration is still experimental. I invite everyone to try out the new release of earlybird via the new VS Code integration. The main purpose of this announcement is to get real-world testing for both sides and hopefully also get more people interested/involved.

Beware that this revival of earlybird comes purely out of my free time, so don’t expect any big developments now. So far I did what I could to get earlybird reasonably working again and I’ll do my best to keep it from degrading. I think it’s still valuable to have earlybird in the ecosystem until maybe something better comes along. However, if working on earlybird sparks anyone’s interest, I’ll gladly accept bug fixes and improvements, all towards a better OCaml ecosystem.


A final note about earlybird: it uses the (undocumented) protocol of ocamldebug to communicate with a bytecode executable. Thus, it comes with all the limitations that ocamldebug and its protocol have.
Notably, don’t get too excited about OCaml 5.0 support: debugging is supported only until a domain is spawned (https://github.com/ocaml/ocaml/pull/11065#pullrequestreview-975383464).

55 Likes

I followed the tutorial at GitHub - ocamllabs/vscode-ocaml-platform: Visual Studio Code extension for OCaml and managed to get ocamlearlybird up and running. However, I cannot figure out how to pass commandline arguments to the program being debugged. I tried adding the “args” property to the launch.json configuration file, to no avail. In fact, there was a hover text which informed me that “Property args is not allowed”.

I also tried adding the arguments directly after the program executable in the “program” property of the configuration. That caused a “Unix.Unix_error” when launching the debug. That seemed to be an error in parsing the config json.

Using arguments property instead of args should work.

1 Like

Ah, thanks a lot! Is there some place that documents all such options supported by the config json?

Not in an user-friendly way. But they’re defined in VS Code OCaml Platform extension metadata: vscode-ocaml-platform/package.json at 2f63a8915a423f7086d041e7df94bf6bf2d1845a · ocamllabs/vscode-ocaml-platform · GitHub.
Code completion in launch.json should also suggest them.

1 Like

Can not set breakpoints for dune build bc executable file in vscode.
issue

Folks, which is the status of EarlyBird these days? Do we have custom printers for watched values? Can we execute a custom printer for a value when the program is paused? Any list of low hanging fruits and more ambitious improvements?

It’s best described as working with some known issues and could use attention from someone with time on their hands. I’m using EarlyBird with DAP as described in #66 it work fine for what I need. The Emacs integration with DAP could use some improvements.

The issues list on the repo is accurate @Kakadu . Personally the little bugs like #62, #74 and #59 would make a difference to my usage.

More ambitious improvements like Step Back #78 and custom value printers, should be possible if ocamldebug supports it.

What other “ambitious improvements” did you have in mind? Open an issue for them.

Is watching an expression a feature of EarlyBird? Is it also a feature of ocamldebug? I haven’t used EarlyBird, but I don’t recall knowing of a way to watch an expression in ocamldebug. But if there is a way, I would like to know it. Thanks!

I have been maintaining it to quite minimal extent: making it work with new versions of OCaml as they’re released. Earlybird relies on some compiler internals, so there tend to be incompatibilities.

Other than that, I have not worked on developing new features.
As to custom printers, there’s two levels:

  1. Custom printers to just text, like those supported by toplevel. The infrastructure for that should more-or-less exist, although when I looked into it a while ago, it still wasn’t as straightforward as I had hoped. Unlike in the toplevel, where everything is in one process in one runtime, the printers have to be loaded into the debugger’s runtime itself and cannot call functions in the debuggee: OCaml - The debugger (ocamldebug). In the toplevel/ocamldebug, these printers are more-or-less set up manually from the prompt, but in earlybird that wouldn’t be too convenient. However, I don’t have a good idea how user-friendly setup would look like.
  2. In DAP the values of variables can be more than just text: they can be nested values (a la JSON, but loaded somewhat lazily). Ideally, one could also use that entire expressiveness: custom “treeifier” functions instead of the classical toplevel printers.

I use from time to time and its very useful, but it could definitely be improved.

However I don’t think being able to define custom is the next thing to do: the default printers could be improved, for now the tree is way too “deep”: you often have to unfold things that could have been printed flat. I’m pretty sure a list of ints is printed in way which forces you to unfold something for every integer, which makes senses in terms of memory representation but is really annoying in practice.

Also, improvements could be made on the dune side: having to enable bytes mode is annoying because it makes the bytecode executable “available” for everyone so to speak, when you only want it for debugging. In my opinion there should be a command to force byte code builds for this usecase.

Looks like for list this is what’s explicitly being done: ocamlearlybird/src/debugger/inspect/value_list.ml at 7dcc21ad09388e705e94d5344d1493c04b83a68c · hackwaly/ocamlearlybird · GitHub.

But even ignoring that, there’s a limitation which has prevented me from using earlybird on anything large: using abstraction causes type information to be forgotten (which makes sense in the compiler for separate compilation, etc) but very inconvenient for earlybird, which tries to choose representations based on type information it can recover. Without the type information, it falls back to just directly showing the low-level OCaml blocks.

I also thought of that, but it looks like installing custom printers (by providing a .cmo file as load argument of the debugger) could fix that: we can pick right printer/tree-like-visualizer using type name. More than that, (I think) we can write these functions in a manner that it will study runtime representation and put quesion marks in places where actual type representation doesn’t match the type.

Do you know why this limitation exists? I remember doing that (calling debuggee code) in context of .NET 15 years ago.

I can only speculate.
The low-level protocol between ocamldebug and the debugee is very old and primitive (and nowhere documented) and simply doesn’t provide a command to do that. If I remember correctly, inspection of values happens by essentially using the Marhsal module to copy it from the debugee to the debugger.

I’m not sure if there’s any particular reason for not having such RPC possibility, other than that nobody ever bothered to implement any.
I may be wrong, but perhaps the current ocamldebug protocol has been designed to just observe (and step back and forth), but not influence the debugee in any way. So no modification of memory or calling functions which could have such side effects.

Note that ocamldebug.exe is, at least out-of-the-box, a native compiled executable so loading cmo is not possible. I tried re-doing the internals of ocamldebug pretty-printer loading in ocamlearlybird but failed to make it work, because cmxs files do not contain the needed type-information of the pretty-printers. At least this is my conclusion from my limited understanding of the internals of cmxs files etc.

We have pretty-printer support in an internal patch which is setup as follows:

  • Introduce a new library, earlybird_pp which allows registration of pretty printers.
  • A pretty printer is registered by specifying the type it prints, as a string, and the actual function, Format.formatter -> Obj.t -> unit. This is far from ideal of course and there might be a better way.
  • Extend the Earlybird options with a “pretty_printers” list of strings. This is a list of cmxs files.
  • These are dynamically loaded by ocamlearlybird.exe upon startup.
  • When inspecting values, check if a pretty-printer is available and use it if so.

Any chance you could make it public?