We’re happy to release an OCaml compiler switch for dynamically detecting naked pointers in the code.
Naked pointers in OCaml
A naked pointer is a pointer outside the OCaml heap without a valid header. A header outside the heap is said to be valid if it is colored black. OCaml does permit naked pointers to word-aligned addresses. However, the presence of naked pointers incurs overhead in the garbage collector (GC). Whenever the GC intends to follow a pointer, it must check that the pointer is indeed in the OCaml heap. The GC consults a page table that maintains the list of pages currently used by the heap and only follows the pointer if it belongs to one of the pages. As you can imagine, this adds some overhead in the GC. For the multicore GC, maintaining a page table that remains consistent when multiple domains are allocating and running GC in parallel would necessitate some synchronization around the page table for reading and writing to it. It is quite likely that this cost will be prohibitive.
Luckily, OCaml already has a no-naked-pointer
mode where the compiler assumes that the code does not have naked pointers, and hence, does not consult the page table for following pointers during GC (except Closure_tag
objects). The no-naked-pointer
mode is a configure-time option, enabled by configuring the compiler with --disable-naked-pointers
. Multicore OCaml compiler does not use a page table in its implementation currently.
Dynamic Check for naked pointers
With the aim of migrating to no-naked-pointer
mode as the default in future releases of OCaml, eventually paving the way for upstreaming multicore support, we’re happy to release a variant of OCaml 4.10.0 with a dynamic checker for the presence of naked pointers in the code. OCaml PR#9534 has the discussion around this checker. This variant can be installed with:
$ opam update
$ opam switch create 4.10.0+nnpcheck
$ eval $(opam env)
Once the variant is installed, you can install your favorite libraries using opam
and run your program to get a report of naked pointers. Let us look at an example. We know that frama-c
has naked pointers.
$ opam install frama-c
$ frama-c
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
The checker prints warnings to standard error with the address that contains the naked pointer, the naked pointer and the reason why the warning was raised.
Finding the sources
While the warnings are useful for indentifying that the program has naked pointer, it does not help with finding the source of the naked pointer in code. For this, we recommend the use of rr
. rr
is record and replay framework that wraps around the familiar gdb
interface. We can debug the error above as follows:
$ rr frama-c
rr: Saving execution to trace directory `/home/kc/.local/share/rr/frama-c-5'.
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e2754d8 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
Out-of-heap pointer at 0x55fc1e275600 of value 0x55fc1e3a0cc0 has non-black head (tag=144)
$ rr replay
(rr) watch *(value*)0x55fc1e2754d8
Hardware watchpoint 1: *(value*)0x55fc1e2754d8
(rr) c
Continuing.
Hardware watchpoint 1: *(value*)0x55fc1e2754d8
Old value = 1
New value = 94541327240384
0x000055fc1dab48f8 in camlUnmarshal__entry () at src/libraries/datatype/unmarshal.ml:72
72 src/libraries/datatype/unmarshal.ml: No such file or directory.
This corresponds to the naked pointer at https://github.com/Frama-C/Frama-C-snapshot/blob/master/src/libraries/datatype/unmarshal.ml#L72.
Fixing naked pointers
The recommended way of fixing naked pointers is to wrap them in an OCaml object with Custom_tag
or Abstract_tag
(as appropriate).
Limitations
The dynamic analysis only work on AMD64 backend with GCC and Clang. It has been known to work on Linux and MacOS. rr
currently requires an Intel CPU with Nehalem (2010) or later microarchitecture.
Credits
The analysis was originally proposed by Mark Shinwell (@mshinwell).