File Coverage

blib/lib/Devel/MAT/Dumper/Helper.pm
Criterion Covered Total %
statement 8 23 34.7
branch 0 4 0.0
condition n/a
subroutine 3 6 50.0
pod 3 3 100.0
total 14 36 38.8


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2019-2024 -- leonerd@leonerd.org.uk
5              
6             package Devel::MAT::Dumper::Helper;
7              
8 1     1   1744 use v5.10;
  1         3  
9 1     1   7 use strict;
  1         3  
  1         29  
10 1     1   5 use warnings;
  1         2  
  1         499  
11              
12             our $VERSION = '0.51';
13              
14             =head1 NAME
15              
16             C - give XS modules extensions for memory dumping
17              
18             =head1 SYNOPSIS
19              
20             =for highlighter language=perl
21              
22             In F
23              
24             if( eval { require Devel::MAT::Dumper::Helper } ) {
25             Devel::MAT::Dumper::Helper->extend_module_build( $build );
26             }
27              
28             In your module's XS source:
29              
30             =for highlighter language=c
31              
32             #ifdef HAVE_DMD_HELPER
33             # define WANT_DMD_API_044
34             # include "DMD_helper.h"
35             #endif
36              
37             ...
38              
39             #ifdef HAVE_DMD_HELPER
40             static int dumpstruct(pTHX_ DMDContext *ctx, const SV *sv)
41             {
42             int ret = 0;
43              
44             ret += DMD_ANNOTATE_SV(sv, another_sv,
45             "the description of this field");
46             ...
47              
48             return ret;
49             }
50              
51             static int dumpmagic(pTHX_ DMDContext *ctx, const SV *sv, MAGIC *mg)
52             {
53             int ret = 0;
54              
55             ret += DMD_ANNOTATE_SV(sv, another_sv,
56             "the description of this field");
57             ...
58              
59             return ret;
60             }
61             #endif
62              
63             ...
64              
65             BOOT:
66             #ifdef HAVE_DMD_HELPER
67             DMD_SET_PACKAGE_HELPER("My::Package", dumpstruct);
68             DMD_SET_MAGIC_HELPER(&vtbl, dumpmagic);
69             #endif
70              
71             =head1 DESCRIPTION
72              
73             This module provides a build-time helper to assist in writing XS modules that
74             can provide extra information to a L heap dump file when dumping
75             data structures relating to that module.
76              
77             Following the example in the L section above, the C
78             function is called whenever L finds an SV blessed into the
79             given package, and the C function is called whenever
80             L finds an SV with extension magic matching the given
81             magic virtual table pointer. These functions may then inspect the module's
82             state from the SV or MAGIC pointers, and invoke the C macro
83             to provide extra annotations into the heap dump file about how this SV is
84             related to another one.
85              
86             The C macro is required before C<#include>ing the file, so
87             as to enable the API structure described here. Without that, an earlier
88             version of the module is provided instead, which will eventually be removed in
89             some later version.
90              
91             Under this code structure, a module will cleanly build, install and run just
92             fine if L is not available at build time, so it is
93             not necessary to list that as a C or C
94             requirement.
95              
96             Additionally, the way the inserted code is structured does not cause the XS
97             module to load C itself, so there is no runtime dependency
98             either, even if the support was made available. The newly inserted code is
99             only invoked if both C and this XS module are actually
100             loaded.
101              
102             Note that this entire mechanism is currently experimental.
103              
104             =cut
105              
106             my $DMD_helper_h = do {
107             local $/;
108             readline DATA;
109             };
110              
111             =head1 FUNCTIONS
112              
113             =for highlighter language=perl
114              
115             =cut
116              
117             =head2 write_DMD_helper_h
118              
119             Devel::MAT::Dumper::Helper->write_DMD_helper_h;
120              
121             Writes the L file to the current working directory. To cause the
122             compiler to actually find this file, see L.
123              
124             =cut
125              
126             sub write_DMD_helper_h
127             {
128 0     0 1   shift;
129              
130 0 0         open my $out, ">", "DMD_helper.h" or
131             die "Cannot open DMD_helper.h for writing - $!\n";
132              
133 0           $out->print( $DMD_helper_h );
134             }
135              
136             =head2 extra_compiler_flags
137              
138             @flags = Devel::MAT::Dumper::Helper->extra_compiler_flags;
139              
140             Returns a list of extra flags that the build scripts should add to the
141             compiler invocation. This enables the C compiler to find the F
142             file, and also defines a symbol C which the XS code can then
143             use in C<#ifdef> guards:
144              
145             #ifdef HAVE_DMD_HELPER
146             ...
147             #endif
148              
149             =cut
150              
151             sub extra_compiler_flags
152             {
153 0     0 1   shift;
154 0           return "-DHAVE_DMD_HELPER",
155             "-I.";
156             }
157              
158             =head2 extend_module_build
159              
160             Devel::MAT::Dumper::Helper->extend_module_build( $build );
161              
162             A convenient shortcut for performing all the tasks necessary to make a
163             L-based distribution use the helper.
164              
165             =cut
166              
167             sub extend_module_build
168             {
169 0     0 1   my $self = shift;
170 0           my ( $build ) = @_;
171              
172 0 0         eval { $self->write_DMD_helper_h } or do {
  0            
173 0           warn $@;
174 0           return;
175             };
176              
177             # preserve existing flags
178 0           my @flags = @{ $build->extra_compiler_flags };
  0            
179 0           push @flags, $self->extra_compiler_flags;
180              
181 0           $build->extra_compiler_flags( @flags );
182             }
183              
184             =head1 XS MACROS
185              
186             =for highlighter language=c
187              
188             The header file provides the following macros, which may be used by the XS
189             module.
190              
191             =head2 DMD_SET_PACKAGE_HELPER
192              
193             typedef int DMD_Helper(pTHX_ DMDContext *ctx, const SV *sv);
194              
195             DMD_SET_PACKAGE_HELPER(char *packagename, DMD_Helper *helper);
196              
197             This macro should be called from the C section of the XS module to
198             associate a helper function with a named package. Whenever an instance of an
199             object blessed into that package is encountered by the dumper, the helper
200             function will be called to provide extra information about it.
201              
202             When invoked, the helper function is passed a pointer to the blessed SV
203             directly - remember this will be the underlying object storage and not the
204             C that the Perl code uses to refer to it. It should return an integer that
205             is the sum total of the return values of all the calls to C
206             that it made, or 0 if it did not make any.
207              
208             The I pointer to the helper function points at an opaque structure
209             internal to the C module. Helper functions are not
210             expected to interact with it, except to pass it on any C
211             calls it may make.
212              
213             =head2 DMD_SET_MAGIC_HELPER
214              
215             typedef int DMD_MagicHelper(pTHX_ DMDContext *ctx, const SV *sv, MAGIC *mg);
216              
217             DMD_SET_MAGIC_HELPER(MGVTBL *vtbl, DMD_MagicHelper *helper);
218              
219             This macro should be called from the C section of the XS module to
220             associate a helper function with a given magic virtual method table. Whenever
221             an SV with that kind of magic is encountered by the dumper, the helper
222             function will be called to provide extra information about it.
223              
224             When invoked, the helper function is passed a pointer to the magical SV as
225             well as the specific C instance responsible for this call. It should
226             return an integer that is the sum total of the return values of all the calls
227             to C that it made, or 0 if it did not make any.
228              
229             The I pointer to the helper function points at an opaque structure
230             internal to the C module. Helper functions are not
231             expected to interact with it, except to pass it on any C
232             calls it may make.
233              
234             =head2 DMD_ADD_ROOT
235              
236             DMD_ADD_ROOT(SV *sv, const char *name);
237              
238             This macro should be called from the C section of the XS module to add
239             another root SV pointer to be added to the root SVs table. This is useful for
240             annotating static SV pointers or other storage that can refer to SVs or memory
241             structures within the module, but which would not be discovered by a normal
242             heap walk.
243              
244             The I argument is also used as the description string within the
245             C UI. It should begin with either a C<+> or C<-> character to
246             annotate that the root contains a strong or weak reference, respectively.
247              
248             =head2 DMD_ANNOTATE_SV
249              
250             DMD_ANNOTATE_SV(const SV *referrer, const SV *referrant, const char *label);
251              
252             This macro should be called by a helper function, in order to provide extra
253             information about the SV it has encountered. The macro notes that a pointer
254             exists from the SV given by I, pointing at the SV given by
255             I, described by the given string label.
256              
257             Each call to this macro returns an integer, which the helper function must
258             accumulate the total of, and return that number to the caller.
259              
260             Not that it is not necessary that either the referrer nor the referrant
261             actually are the SV that the helper function encountered. Arbitrary
262             annotations between SVs are permitted. Additionally, it is permitted that
263             the SV addresses do not in fact point at Perl SVs, but instead point to
264             arbitarary data structures, which should be written about using
265             C.
266              
267             =head2 DMD_DUMP_STRUCT
268              
269             typedef struct {
270             const char *name;
271             enum {
272             DMD_FIELD_PTR,
273             DMD_FIELD_BOOL,
274             DMD_FIELD_U8,
275             DMD_FIELD_U32,
276             DMD_FIELD_UINT,
277             } type;
278              
279             void *ptr; /* for type=PTR */
280             bool b; /* for type=BOOL */
281             long n; /* for the remaining numerical types */
282             } DMDNamedField;
283              
284             DMD_DUMP_STRUCT(DMDContext *ctx, const char *name, void *addr, size_t size,
285             size_t nfields, const DMDNamedField fields[]);
286              
287             This macro should be called by a helper function, in order to provide extra
288             information about a memory structure that is not a Perl SV. By using this
289             macro, the module can write information into the dumpfile about the memory
290             structure types and values that it operates on, allowing the C
291             tooling to operate on it - such as by following pointers and finding or
292             identifying the contents.
293              
294             The code invoked by this macro at runtime actually does B separate tasks,
295             which are closely related. The first time a call is made for any particular
296             string value in I, the function will write metadata information into the
297             dumpfile which gives the name and type of each of the fields. Every call,
298             including this first one, will write the values of the fields associated with
299             a single instance of the structure, by reusing the information provided to the
300             first call.
301              
302             The I argument must be the value given to the helper function. I
303             gives the pointer address of the structure itself. I should give its
304             total size in bytes (often C is sufficient here).
305              
306             The I, I, and I parameters between them are used both
307             by the initial metadata call, and for every structure instance. I gives
308             a unique name to this type of structure - it should be composed of the base
309             name of the XS module, and a local name within the module, separated by C.
310             I gives the number of individual field instances given in the
311             I array, which itself provides a label name, a type, and an actual
312             value.
313              
314             The first two fields of the C structure give its name and type,
315             and one subsequent field should be set to give the value for it. Which field
316             to use depends on the type.
317              
318             Note that it is very important, once a structure name has been seen the first
319             time, that every subsequent call for the same must have exactly the same count
320             of fields, and the types of each of them. The values of the fields, as well as
321             the size of the structure overall, are recorded for every call, but the typing
322             information is stored only once on that first call. It is best to ensure that
323             the module source contains only a single instance of this macro for a given
324             structure name, thus ensuring the type information will always be consistent.
325              
326             =head1 HANDLING C-LEVEL STRUCTURES
327              
328             For example, given a C struct definition such as:
329              
330             struct MyData {
331             SV *buf;
332             int state;
333              
334             AV *more_stuff;
335             };
336              
337             A call to provide this to the dumpfile could look like:
338              
339             struct MyData *dat = ...;
340              
341             DMD_DUMP_STRUCT(ctx, "Module::Name/MyData", dat, sizeof(struct MyData),
342             3, ((const DMDNamedField []){
343             {"the buf SV", DMD_FIELD_PTR, .ptr = dat->buf},
344             {"the state", DMD_FIELD_UINT, .n = dat->state},
345             {"the more_stuff AV", DMD_FIELD_PTR, .ptr = dat->more_stuff},
346             })
347             );
348              
349             Conventionally, names of unique fields all begin C<"the ...">. Fields that
350             point to other Perl SVs should explain what kind of SV they point to, so any
351             discrepencies can be observed in the tooling later on.
352              
353             A call to this macro alone is likely not enough to fully link the information
354             in the dumpfile, however. It is unlikely that any pointer value that the
355             dumper itself will encounter would point to this data structure - if so, Perl
356             would not know how to deal with it. It's likely that the module would use some
357             technique such as storing a pointer in the UV field of a blessed SCALAR SV, as
358             a way to retain it. In that typical example, a helper function should be
359             attached to the package name that SV would be blessed into. When the dumper
360             encounters that blessed SV it will invoke the helper function, which can then
361             call C and also use C to provide a linkage
362             between the blessed SV containing the UV value, and this structure.
363              
364             static int dumppackage_mydata(pTHX_ DMDContext *ctx, const SV *sv)
365             {
366             int ret = 0;
367              
368             struct MyData *dat = NUM2PTR(struct MyData *, SvUV((SV *)sv));
369             DMD_DUMP_STRUCT(...);
370              
371             ret += DMD_ANNOTATE_SV(sv, (SV *)dat, "the MyData structure");
372              
373             return ret;
374             }
375              
376             BOOT:
377              
378             There is no ordering requirement between these two - the annotation linking
379             the pointers can be made before, or after, the structure itself has been
380             written. In fact, there are no ordering constraints at all; feel free to write
381             the data structures and annotations in whatever order is most natural to the
382             dumper code,
383              
384             =cut
385              
386             =head1 AUTHOR
387              
388             Paul Evans
389              
390             =cut
391              
392             0x55AA;
393              
394             __DATA__