<aside> 🌐 https://ghostscript.com/r/Ghostscript-with-Valgrind

</aside>

How to use Ghostscript with Valgrind

Dumb usage

Valgrind can be used direct on pretty much any binary, but it is prone to false positives, and the backtraces it gives may not be hugely helpful. It is nonetheless a good first thing to try if you have a command line that appears to be misbehaving. Simply do:

$ valgrind --track-origins=yes <command line that was failing>

so, for example:

$ valgrind --track-origins=yes bin/gs -sDEVICE=ppmraw -r 72 -Z: -o out.ppm examples/tiger.eps

Problems with dumb usage

clist false positives

The clist works by having a buffer that is filled up with serialised commands, and written out to a file. These commands are padded to a certain size to fit within the clist, resulting in uninitialised padding bytes. At the point at which the clist writes these to a file, valgrind is forced to assume that the values in these bytes are significant, and so flags them as errors.

One solution to this would be to initialise the buffer by setting it all to known values at the start, but this would a) take extra time, and b) mask real problems.

The best solution is to use memory clists rather than file ones. The bytes are written into the memory list, and valgrind carries over the defined/undefined status of those bytes with them. When the bytes are then played back, we can correctly be informed of undefined value usage.

SSE false positives

The SSE code (for halftoning etc) writes bytes into ‘wide aligned’ buffers (say 32 for the purposes of argument), and then operates on them to map bytes down to bits. If we are operating on a region that is not a multiple of 32 pixels wide, we have a suffix of bytes that are left uninitialised. When the SSE code loads all 32 bytes at once and operates on them, valgrind complains that it is working on uninitialised values.

To fix this we have extra code in the system to ensure that the 32byte buffers are labelled as being defined. This code is triggered by building with the define PACIFY_VALGRIND.

Memory manager blocks

GS uses a series of memory managers within it - rather than every block going to the system memory allocator, they are typically allocated through a gs memory manager - either for the purposes of object type tracking, or for allocating within a chunk. This means that “end user visible” memory objects are typically part of larger system blocks.

Valgrind can spot over/underruns of system blocks, but without specific help, cannot spot over/underruns of such wrapped blocks. For example, If we underrun a wrapped block, we run into the extra bytes put there by the wrapping. That is enough to crash the program at some stage, but not enough to trip valgrinds detection.

To improve this, we can add some extra code into the compiled program that calls Valgrind to tell it that the wrapped blocks should be distrusted/trusted as we run. This code is triggered by building with the define HAVE_VALGRIND (as to do it all the time would fail on systems that don’t have valgrind installed).

Not all allocators have currently been updated to work with this. Memento has, but other allocators may need to be looked at in future. This problem will not cause false positives, but might cause valgrind to miss things that it shouldn’t.