Author image Paul Evans

NAME

Devel::MAT::Dumper::Helper - give XS modules extensions for memory dumping

SYNOPSIS

In Build.PL

   if( eval { require Devel::MAT::Dumper::Helper } ) {
      Devel::MAT::Dumper::Helper->extend_module_build( $build );
   }

In your module's XS source:

   #ifdef HAVE_DMD_HELPER
   #  define WANT_DMD_API_044
   #  include "DMD_helper.h"
   #endif

   ...

   #ifdef HAVE_DMD_HELPER
   static int dumpstruct(pTHX_ DMDContext *ctx, const SV *sv)
   {
     int ret = 0;

     ret += DMD_ANNOTATE_SV(sv, another_sv,
       "the description of this field");
     ...

     return ret;
   }

   static int dumpmagic(pTHX_ DMDContext *ctx, const SV *sv, MAGIC *mg)
   {
     int ret = 0;

     ret += DMD_ANNOTATE_SV(sv, another_sv,
       "the description of this field");
     ...

     return ret;
   }
   #endif

   ...

   BOOT:
   #ifdef HAVE_DMD_HELPER
     DMD_SET_PACKAGE_HELPER("My::Package", dumpstruct);
     DMD_SET_MAGIC_HELPER(&vtbl, dumpmagic);
   #endif

DESCRIPTION

This module provides a build-time helper to assist in writing XS modules that can provide extra information to a Devel::MAT heap dump file when dumping data structures relating to that module.

Following the example in the "SYNOPSIS" section above, the dumpstruct function is called whenever Devel::MAT::Dumper finds an SV blessed into the given package, and the dumpmagic function is called whenever Devel::MAT::Dumper finds an SV with extension magic matching the given magic virtual table pointer. These functions may then inspect the module's state from the SV or MAGIC pointers, and invoke the DMD_ANNOTATE_SV macro to provide extra annotations into the heap dump file about how this SV is related to another one.

The WANT_DMD_API_044 macro is required before #includeing the file, so as to enable the API structure described here. Without that, an earlier version of the module is provided instead, which will eventually be removed in some later version.

Under this code structure, a module will cleanly build, install and run just fine if Devel::MAT::Dumper::Helper is not available at build time, so it is not necessary to list that as a configure_requires or build_requires requirement.

Additionally, the way the inserted code is structured does not cause the XS module to load Devel::MAT::Dumper itself, so there is no runtime dependency either, even if the support was made available. The newly inserted code is only invoked if both Devel::MAT::Dumper and this XS module are actually loaded.

Note that this entire mechanism is currently experimental.

FUNCTIONS

write_DMD_helper_h

   Devel::MAT::Dumper::Helper->write_DMD_helper_h

Writes the DMD_helper.h file to the current working directory. To cause the compiler to actually find this file, see extra_compiler_flags.

extra_compiler_flags

   @flags = Devel::MAT::Dumper::Helper->extra_compiler_flags

Returns a list of extra flags that the build scripts should add to the compiler invocation. This enables the C compiler to find the DMD_helper.h file, and also defines a symbol HAVE_DMD_HELPER which the XS code can then use in #ifdef guards:

   #ifdef HAVE_DMD_HELPER
   ...
   #endif

extend_module_build

   Devel::MAT::Dumper::Helper->extend_module_build( $build )

A convenient shortcut for performing all the tasks necessary to make a Module::Build-based distribution use the helper.

XS MACROS

The header file provides the following macros, which may be used by the XS module.

DMD_SET_PACKAGE_HELPER

   typedef int DMD_Helper(pTHX_ DMDContext *ctx, const SV *sv);

   DMD_SET_PACKAGE_HELPER(char *packagename, DMD_Helper *helper);

This macro should be called from the BOOT section of the XS module to associate a helper function with a named package. Whenever an instance of an object blessed into that package is encountered by the dumper, the helper function will be called to provide extra information about it.

When invoked, the helper function is passed a pointer to the blessed SV directly - remember this will be the underlying object storage and not the RV that the Perl code uses to refer to it. It should return an integer that is the sum total of the return values of all the calls to DMD_ANNOTATE_SV that it made, or 0 if it did not make any.

The ctx pointer to the helper function points at an opaque structure internal to the Devel::MAT::Dumper module. Helper functions are not expected to interact with it, except to pass it on any DMD_DUMP_STRUCT calls it may make.

DMD_SET_MAGIC_HELPER

   typedef int DMD_MagicHelper(pTHX_ DMDContext *ctx, const SV *sv, MAGIC *mg);

   DMD_SET_MAGIC_HELPER(MGVTBL *vtbl, DMD_MagicHelper *helper);

This macro should be called from the BOOT section of the XS module to associate a helper function with a given magic virtual method table. Whenever an SV with that kind of magic is encountered by the dumper, the helper function will be called to provide extra information about it.

When invoked, the helper function is passed a pointer to the magical SV as well as the specific MAGIC instance responsible for this call. It should return an integer that is the sum total of the return values of all the calls to DMD_ANNOTATE_SV that it made, or 0 if it did not make any.

The ctx pointer to the helper function points at an opaque structure internal to the Devel::MAT::Dumper module. Helper functions are not expected to interact with it, except to pass it on any DMD_DUMP_STRUCT calls it may make.

DMD_ADD_ROOT

   DMD_ADD_ROOT(SV *sv, const char *name);

This macro should be called from the BOOT section of the XS module to add another root SV pointer to be added to the root SVs table. This is useful for annotating static SV pointers or other storage that can refer to SVs or memory structures within the module, but which would not be discovered by a normal heap walk.

The name argument is also used as the description string within the Devel::MAT UI. It should begin with either a + or - character to annotate that the root contains a strong or weak reference, respectively.

DMD_ANNOTATE_SV

   DMD_ANNOTATE_SV(const SV *referrer, const SV *referrant, const char *label);

This macro should be called by a helper function, in order to provide extra information about the SV it has encountered. The macro notes that a pointer exists from the SV given by referrer, pointing at the SV given by referrant, described by the given string label.

Each call to this macro returns an integer, which the helper function must accumulate the total of, and return that number to the caller.

Not that it is not necessary that either the referrer nor the referrant actually are the SV that the helper function encountered. Arbitrary annotations between SVs are permitted. Additionally, it is permitted that the SV addresses do not in fact point at Perl SVs, but instead point to arbitarary data structures, which should be written about using DMD_DUMP_STRUCT.

DMD_DUMP_STRUCT

   typedef struct {
     const char *name;
     enum {
       DMD_FIELD_PTR,
       DMD_FIELD_BOOL,
       DMD_FIELD_U8,
       DMD_FIELD_U32,
       DMD_FIELD_UINT,
     } type;

     void *ptr;  /* for type=PTR */
     bool  b;    /* for type=BOOL */
     long  n;    /* for the remaining numerical types */
   } DMDNamedField;

   DMD_DUMP_STRUCT(DMDContext *ctx, const char *name, void *addr, size_t size,
      size_t nfields, const DMDNamedField fields[]);

This macro should be called by a helper function, in order to provide extra information about a memory structure that is not a Perl SV. By using this macro, the module can write information into the dumpfile about the memory structure types and values that it operates on, allowing the Devel::MAT tooling to operate on it - such as by following pointers and finding or identifying the contents.

The code invoked by this macro at runtime actually does two separate tasks, which are closely related. The first time a call is made for any particular string value in name, the function will write metadata information into the dumpfile which gives the name and type of each of the fields. Every call, including this first one, will write the values of the fields associated with a single instance of the structure, by reusing the information provided to the first call.

The ctx argument must be the value given to the helper function. addr gives the pointer address of the structure itself. size should give its total size in bytes (often sizeof(*ptr) is sufficient here).

The name, nfields, and fields parameters between them are used both by the initial metadata call, and for every structure instance. name gives a unique name to this type of structure - it should be composed of the base name of the XS module, and a local name within the module, separated by /. nfields gives the number of individual field instances given in the fields array, which itself provides a label name, a type, and an actual value.

The first two fields of the DMDNamedField structure give its name and type, and one subsequent field should be set to give the value for it. Which field to use depends on the type.

Note that it is very important, once a structure name has been seen the first time, that every subsequent call for the same must have exactly the same count of fields, and the types of each of them. The values of the fields, as well as the size of the structure overall, are recorded for every call, but the typing information is stored only once on that first call. It is best to ensure that the module source contains only a single instance of this macro for a given structure name, thus ensuring the type information will always be consistent.

HANDLING C-LEVEL STRUCTURES

For example, given a C struct definition such as:

  struct MyData {
    SV *buf;
    int state;

    AV *more_stuff;
  };

A call to provide this to the dumpfile could look like:

  struct MyData *dat = ...;

  DMD_DUMP_STRUCT(ctx, "Module::Name/MyData", dat, sizeof(struct MyData),
    3, ((const DMDNamedField []){
      {"the buf SV",        DMD_FIELD_PTR,  .ptr = dat->buf},
      {"the state",         DMD_FIELD_UINT, .n   = dat->state},
      {"the more_stuff AV", DMD_FIELD_PTR,  .ptr = dat->more_stuff},
    })
  );

Conventionally, names of unique fields all begin "the ...". Fields that point to other Perl SVs should explain what kind of SV they point to, so any discrepencies can be observed in the tooling later on.

A call to this macro alone is likely not enough to fully link the information in the dumpfile, however. It is unlikely that any pointer value that the dumper itself will encounter would point to this data structure - if so, Perl would not know how to deal with it. It's likely that the module would use some technique such as storing a pointer in the UV field of a blessed SCALAR SV, as a way to retain it. In that typical example, a helper function should be attached to the package name that SV would be blessed into. When the dumper encounters that blessed SV it will invoke the helper function, which can then call DMD_DUMP_STRUCT and also use DMD_ANNOTATE_SV to provide a linkage between the blessed SV containing the UV value, and this structure.

  static int dumppackage_mydata(pTHX_ DMDContext *ctx, const SV *sv)
  {
    int ret = 0;

    struct MyData *dat = NUM2PTR(struct MyData *, SvUV((SV *)sv));
    DMD_DUMP_STRUCT(...);

    ret += DMD_ANNOTATE_SV(sv, (SV *)dat, "the MyData structure");

    return ret;
  }

  BOOT:

There is no ordering requirement between these two - the annotation linking the pointers can be made before, or after, the structure itself has been written. In fact, there are no ordering constraints at all; feel free to write the data structures and annotations in whatever order is most natural to the dumper code,

AUTHOR

Paul Evans <leonerd@leonerd.org.uk>