Devel:...Helper(3pm)
bestie welcome to the manual page for Devel::MAT::Dumper::Helper(3pm) β it lives in the User Contributed Perl Documentation section and it's lowkey a whole mood π
β¨ NAME β¨
Devel::MAT::Dumper::Helper β the way this one hands XS modules little extensions so they can do memory dumping π§π
π SYNOPSIS π
okay so there are two places you're gonna be typing stuff. First up, in Build.PL:
if( eval { require Devel::MAT::Dumper::Helper } ) {
Devel::MAT::Dumper::Helper->extend_module_build( $build );
}
and then over in your module's XS source, you're doing this whole thing:
#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
like. that's the whole shape of it. slay πΈ
π DESCRIPTION π
so basically this module is a build-time helper β it exists to help you write XS modules that can feed extra information into a Devel::MAT heap dump file when that dump is walking through data structures that belong to your module. no cap, that's the entire job. π
Following the example up there in the SYNOPSIS β the dumpstruct function gets called whenever Devel::MAT::Dumper comes across an SV that's blessed into the package you named, and the dumpmagic function gets called whenever Devel::MAT::Dumper encounters an SV that has extension magic matching the magic virtual table pointer you gave it. Once those functions are running, they can peek at the module's state through the SV or MAGIC pointers, and then call the DMD_ANNOTATE_SV macro to drop extra annotations into the heap dump file describing how that SV relates to some other one. the way that works is genuinely so satisfying π«Ά
Now the important bit: the WANT_DMD_API_044 macro has to be there before you #include the file β that's what switches on the API structure being described on this page. Without it, you get an earlier version of the module instead, and that older version is gonna get removed in some later release. so like, define it. π
Here's the cute part: with this code structure, a module will cleanly build, install and run just fine even if Devel::MAT::Dumper::Helper isn't available at build time. Which means you do not have to list it as a configure_requires or a build_requires requirement. be free, bestie π¦
Also β and this is a vibe β the way the inserted code is structured means it does not make the XS module load Devel::MAT::Dumper itself. So there's no runtime dependency either, even when the support was made available. The newly inserted code only actually runs if both Devel::MAT::Dumper and this XS module are loaded. perfectly balanced π
heads up, bestie: this entire mechanism is currently experimental. we're in our experimental era. π
π FUNCTIONS π
π write_DMD_helper_h
Devel::MAT::Dumper::Helper->write_DMD_helper_h;
Writes the DMD_helper.h file into the current working directory. and if you want the compiler to actually find that file? see extra_compiler_flags below. β¨
π extra_compiler_flags
@flags = Devel::MAT::Dumper::Helper->extra_compiler_flags;
Hands you back a list of extra flags that your build scripts should slap onto the compiler invocation. Two things happen because of this: the C compiler can find the DMD_helper.h file, and it defines a symbol called HAVE_DMD_HELPER that your XS code can then gate on with #ifdef like so:
#ifdef HAVE_DMD_HELPER
...
#endif
π extend_module_build
Devel::MAT::Dumper::Helper->extend_module_build( $build );
A convenient lil shortcut that does all the tasks you need to make a Module::Build-based distribution use the helper. one call, everything handled. iconic. π
π XS MACROS π
The header file gives you the following macros, and your XS module is free to use them.
π¦ DMD_SET_PACKAGE_HELPER
typedef int DMD_Helper(pTHX_ DMDContext *ctx, const SV *sv);
DMD_SET_PACKAGE_HELPER(char *packagename, DMD_Helper *helper);
You call this macro from the BOOT section of the XS module, and it pairs a helper function up with a named package. Then, any time the dumper runs into an instance of an object blessed into that package, it calls your helper function so you can supply extra info about it. π«Ά
When it gets invoked, the helper function is handed a pointer to the blessed SV directly β and bestie, remember: that's the underlying object storage, not the RV that the Perl code uses to refer to it. It should return an integer equal to the sum total of the return values of every DMD_ANNOTATE_SV call it made β or 0 if it didn't make any. β
That ctx pointer the helper function gets points at an opaque structure internal to the Devel::MAT::Dumper module. Helper functions are not expected to interact with it β the only thing you're meant to do with it is pass it along on any DMD_DUMP_STRUCT calls you happen to make. periodt.
π¦ 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);
Same energy, different target: you call this one from the BOOT section of the XS module to pair a helper function with a given magic virtual method table. Whenever the dumper bumps into an SV carrying that kind of magic, your helper function gets called so it can provide extra info. π
When it runs, the helper function receives a pointer to the magical SV and the specific MAGIC instance responsible for that particular call. It should return an integer that's the sum total of the return values from all the DMD_ANNOTATE_SV calls it made β or 0 if it made none. πΈ
And same as before: the ctx pointer points at an opaque structure that lives internal to the Devel::MAT::Dumper module. Helper functions aren't expected to mess with it, except to pass it on to any DMD_DUMP_STRUCT calls they make.
π¦ DMD_ADD_ROOT
DMD_ADD_ROOT(SV *sv, const char *name);
Call this macro from the BOOT section of your XS module to add another root SV pointer into the root SVs table. Why would you want that? because it's genuinely useful for annotating static SV pointers, or any other storage that can refer to SVs or memory structures inside the module but that a normal heap walk would simply never stumble across. π
The name argument doubles as the description string inside the Devel::MAT UI. It should start with either a + or a - character β + to annotate that the root holds a strong reference, - for a weak one. don't skip that character, it's doing real work. π
π¦ DMD_ANNOTATE_SV
DMD_ANNOTATE_SV(const SV *referrer, const SV *referrant, const char *label);
Your helper function calls this macro when it wants to drop extra info about the SV it just encountered. What the macro is doing is noting that a pointer exists going from the SV given by referrer to the SV given by referrant, and it's described by the string label. a little relationship record, if you will π
Each call to this macro returns an integer, and your helper function has to accumulate the total of all of them and return that number back to whoever called it. don't lose count bestie π
Also worth noting: it is not necessary for either the referrer or the referrant to actually be the SV that the helper function encountered. Arbitrary annotations between SVs are completely permitted. On top of that, it's permitted for those SV addresses to not actually point at Perl SVs at all β they can point at arbitrary data structures instead, which you should then write 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[]);
Your helper function calls this macro when it needs to provide extra info about a memory structure that is not a Perl SV. Using this macro, the module can write info into the dumpfile about the memory structure types and values it operates on β which lets the Devel::MAT tooling work with them too, like following pointers and finding or identifying contents. π₯
Here's the thing about what actually runs at runtime: the code this macro invokes genuinely does two separate jobs, and they're closely related. The first time a call is made for any particular string value in name, the function writes metadata into the dumpfile giving the name and type of each of the fields. Then every call, including that first one, writes out the field values for a single instance of the structure β and it does that by reusing the info that was handed to that first call. β
The ctx argument has to be the value that was given to the helper function. addr gives the pointer address of the structure itself. size should give its total size in bytes (very often sizeof(*ptr) is all you need here). π
The name, nfields and fields parameters together get used both by that initial metadata call and by every single structure instance. name gives a unique name to this type of structure β it should be made up of the base name of the XS module plus a local name within the module, separated by a /. nfields gives the number of individual field instances sitting in the fields array, which itself supplies a label name, a type, and an actual value. πΈ
The first two fields of the DMDNamedField structure give its name and its type, and then one of the subsequent fields should be set to give the value. Which field you use depends on the type. π§
okay this next bit is the one you CANNOT fumble: once a structure name has been seen the first time, it is very important that every subsequent call for that same name has exactly the same count of fields, and exactly the same types for each of them. The field values, plus the size of the structure overall, do get recorded on every call β but the typing information is stored only once, on that first call. So the best move is to make sure your module source contains only a single instance of this macro for a given structure name, which guarantees the type info stays consistent forever. no cap. π
ποΈ HANDLING C-LEVEL STRUCTURES ποΈ
Okay bestie, say you've got a C struct, something like this:
struct MyData {
SV *buf;
int state;
AV *more_stuff;
};
And you wanna hand that off to the dumpfile? The call lowkey looks like this:
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},
})
);
Now, the convention β and the group chat is very committed to this β is that unique field names all start with "the ...". And if a field points at some other Perl SV, its name should spill what kind of SV it's pointing at, so that later on the tooling can spot any discrepencies. π
β οΈ But heads up, calling this macro all by itself is probably not enough to fully link up the info in the dumpfile.
Why? Because it's unlikely that any pointer value the dumper itself runs into would point at this data structure β and if it did, Perl would simply not know how to deal with it. Instead, it's likely the module would go for some technique like stashing a pointer in the UV field of a blessed SCALAR SV, as a way to retain it. Classic move honestly.
So in that typical setup, you should attach a helper function to the package name that the SV would be blessed into. The moment the dumper bumps into that blessed SV, it'll invoke the helper function β and that function can then call DMD_DUMP_STRUCT and also use DMD_ANNOTATE_SV to build a linkage between the blessed SV holding 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:
And ngl, there is literally no ordering requirement between these two β the annotation linking the pointers can happen 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>
And that's the whole vibe, bestie β you made it to the end of the page π«Ά this one was documented in perl v5.42.3 and last dated 2026-08-19, so go forth and slay that dumpfile. periodt. πβ¨
π¦ where this page comes from
- ubuntu stonking: libdevel-mat-dumper-perl 0.51-1build1 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms