Stack traces in OCaml


One annoyance of OCaml (compared to Java or most any of the scripting languages) is that it does not automatically print a stack trace when your program throws an exception. To get one you have to compile with -g to include debugging information in the executable, and launch the runtime with -b so that it captures a stack trace on an exception. By default the runtime doesn’t spend time capturing the stack trace; this reflects a design choice to make exceptions very lightweight. Because exceptions are cheap it is good OCaml style to use them freely as a control-flow mechanism, while in most other languages such use is discouraged—exceptions are meant to be used only for error handling.

Once you’ve -g and -b‘d your way to a stack trace you can only get it printed to standard error, and then only if it reaches the top level. This is usually fine for a batch program, but for a long-running program like a web server it is a severe restriction. So we’ve written a small patch to the OCaml runtime that lets you get the stack trace as a string so you can show it in a web page, log it, etc. You can also turn the capturing of backtraces on and off inside the program so you incur the cost only when you need it.

(This patch is similar in spirit to Markus Mottl’s but is simpler and gives the same information that you get from the standard stack trace.)

To install the patch:

  1. download the patch and save it as backtrace.patch
  2. unpack a fresh OCaml 3.10.0 tree
  3. patch -p0 < backtrace.patch in the same directory where you unpacked OCaml.
  4. cd into the OCaml tree
  5. ./configure
  6. make core promote
  7. uncomment the lines in stdlib/printexc.mli that are marked UNCOMMENT
  8. make world.opt or whatever you normally do to build OCaml

(The reason for steps 6 and 7 is that OCaml compiles itself with precompiled tools in boot. These tools don’t know about the new functions in backtrace.c so they barf on printexc.mli. Step 6 builds a new version of the tools and puts them in boot.)

To use the new functions:

  1. Call Printexc.capture_backtrace true (any time before the exception is thrown).
  2. Make a string to write the backtrace into, e.g. with let b = String.create 8192.
  3. When you have caught an exception, immediately write the backtrace with let c = Printexc.sprint_backtrace b. Now c is the number of characters written into b, so you can get the part you want with String.sub b 0 c.
  4. When you no longer need to capture backtraces call Printexc.capture_backtrace false.

(This interface is a little New Jersey-ish—it would be nicer to get a string allocated for you rather than having to guess a big enough size, but it was very simple to implement.)

A few things to watch out for:

  1. If you are using bytecode, the runtime opens the bytecode file to find the debugging information; if your program changes directories (as does the Netplex server in Ocamlnet) it can’t find it unless you start the program with an absolute path. This isn’t a problem with native code.
  2. There is only one buffer into which backtraces are captured, so you must call Printexc.sprint_backtrace before any other exceptions are thrown.
  3. Tail calls don’t add anything to the stack so they don’t show up in the backtrace.
  4. Because of the way the backtrace is built up as the stack is unwound (see caml_stash_backtrace in backtrace.c for the details), it includes only the frames from the point at which the exception was thrown to the next enclosing exception handler. If the exception is re-raised then further frames are added.

Comments

5 Responses to “Stack traces in OCaml”

  • igor on October 3rd, 2007 8:17 am

    external capture_backtrace : bool -> unit = “caml_capture_backtrace”;;
    external sprint_backtrace : string -> int = “caml_sprint_backtrace”;;

    Have this lines to be commented (in the path) and uncommented (in the step 7)?

  • Jake on October 3rd, 2007 10:56 am

    No, those lines (in stdlib/printexc.ml) do not need to be commented/uncommented; since those functions are not used and don’t appear in printexc.mli, they are not added to the table of required primitives, so there is no problem linking with the bootstrap ocamlc.

    Did this not work for you?

  • igor on October 3rd, 2007 12:31 pm

    sorry, it was my mistake.
    it works =)

    this patch is hard enough to be included into the ocaml deb-package…

  • Jake on October 3rd, 2007 12:48 pm

    Cool.

    If you want to create a source tarball that incorporates the patch, do steps 1-6, which will build ocamlc, ocamldep, and ocamllex in boot/. Save these somewhere. Now do steps 1-3 again, copy the new ocamlc, ocamldep, and ocamllex into boot/, then do step 7. The resulting directory may be built in the standard way (make world etc.), so you can tar it up.

    I didn’t do this for the patch release because I didn’t want to redistribute the whole system or a binary patch.

    (You may not need ocamldep and ocamllex; I think it is only ocamlc that actually checks the primitives, but you might as well include them for uniformity.)

  • Skydeck : More stack traces in OCaml on January 3rd, 2008 5:21 pm

    […] up on our earlier patch, here is a patch to show backtraces in the OCaml top level and for dynamically-loaded code (this […]