A Perl module with a broken XS build usually fell over at xsubpp, the tool that turns XS glue code into plain C. xsubpp supplies the wiring that lets Perl call XSUBs; a C compiler and the rest of the module build still come afterwards. You will turn a small XS source file into generated C, inspect the result, and keep the original source safe throughout.
perl package, and a writable working directory. None of this needs sudo.Start by checking which executable will actually run, and which Perl release owns it:
$ command -v xsubpp
/usr/bin/xsubpp
$ perl -v
This is perl 5, version 38, subversion 2 (v5.38.2)
$ xsubpp -v
xsubpp version 3.51
That output is from the installed perl 5.38.2 package. Other Perl releases can ship a different ExtUtils::ParseXS version, so record this check whenever a generated file needs to be reproducible elsewhere.
Checkpoint: if command -v prints nothing, stop. Install or repair Perl through your normal package management, rather than guessing at an alternate path.
In an empty working directory, save this as sample.xs. The MODULE and PACKAGE lines name the Perl-facing module, and the XSUB itself just adds two integers:
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
MODULE = Demo PACKAGE = Demo
PROTOTYPES: ENABLE
int
add(int left, int right)
CODE:
RETVAL = left + right;
OUTPUT:
RETVAL
PROTOTYPES: ENABLE makes the generated binding carry a Perl prototype for this XSUB. Keep this file: it is the thing you review and maintain. The generated C file is just an intermediate build product, not source of truth.
For a real extension, use the XS file and typemap that extension's own build system selects. Do not paste this tiny example into an existing module without first checking its package name and build metadata.
Run xsubpp with the XS path as its final argument. With no output option, the generated C goes to standard output, so redirect it to a new, disposable name first:
$ xsubpp sample.xs > sample.c.new
$ test -s sample.c.new
$ mv sample.c.new sample.c
The first command can create or truncate sample.c.new, which is exactly why that name should be one you can lose. The mv only happens once the command and the non-empty check both succeed. If generation fails, inspect the diagnostic and remove the incomplete temporary file once you are sure you do not need it. Never delete an older sample.c as part of a blind recovery attempt.
On this machine, the generated file opens with an automatic-generation notice and includes the original headers. Confirm the expected XSUB wrapper is actually there:
$ rg -n 'XS_Demo_add|#line' sample.c
160:XS_EUPXS(XS_Demo_add); /* prototype to pass -Wmissing-prototypes */
161:XS_EUPXS(XS_Demo_add)
The exact line numbers shift with the input and the installed Perl release. What matters is a generated wrapper for Demo::add, not the specific line it lands on.
By default, xsubpp adds C preprocessor #line directives. They let later compiler diagnostics point straight back at the XS input, which is handy while debugging. Keep them, unless another tool downstream specifically needs generated C without them.
To write directly to a chosen file, use -output:
$ xsubpp -output sample.c.new sample.xs
$ test -s sample.c.new
$ mv sample.c.new sample.c
To suppress the line directives, add -nolinenumbers:
$ xsubpp -nolinenumbers -output sample-no-lines.c sample.xs
$ test -s sample-no-lines.c
$ rg -n '^#line' sample-no-lines.c || echo 'no #line directives found'
no #line directives found
Do not mistake any of this for compiling the C. xsubpp never links a shared object or installs a Perl module; a normal XS build then hands off to the module's own build rules and a C compiler.
xsubpp uses typemaps to translate between C arguments and Perl values. It searches for default files in this relative order, with the rightmost match taking precedence:
../../../typemap:../../typemap:../typemap:typemap
It also falls back to Perl's own installed default typemap. A custom typemap file can quietly change how an otherwise identical XS file gets converted. If an argument or return value is not mapped the way you expect, check the module's build directory first, then pass a deliberate file with -typemap PATH:
$ xsubpp -typemap /path/to/project/typemap -output sample.c.new sample.xs
You can repeat -typemap; the last one wins. Check the path before running this, because selecting the wrong typemap produces valid-looking C with the wrong Perl/C conversions baked in.
xsubpp -v and compare the installed tool against the build instructions. Options are version-specific; do not paste a flag from another Perl release without checking first.PROTOTYPES: declaration that matches it. Do not silence the warning by hiding the generated output instead.-noargtypes or -noinout disables recognised syntax; it does not fix a bad mapping.-output value. xsubpp reads the file you name and writes either the requested file or standard output, nothing else.Options such as -except, -prototypes, -noversioncheck, and -nooptimize all change generated behaviour. Add them only when the module's build instructions actually require them, then read the diff in the generated C to see what changed.
#line directives and any custom typemap are in play.