Currently I am developing an ASP.NET application that for legacy reasons needs to execute some Perl scripts. For this I wrote a small C++ library that uses the embedded Perl API. This library has one single (C) entry point that allows the C# code to execute a script while passing command line arguments and an environment. This environment allows the C# code to mimic a CGI call for the Perl script. I build against Strawberry Perl 5.40.1. I need to use embedded Perl, as Perl is not installed on the shared hosting service that this application will run on and will not be installed either. In the end all pages will be translated to ASP.NET, but until then I need a solution to run the Perl code too.
Unfortunately, I see random crashes of my application due to an access violation in Perl540.dll. These crashes occur when I only execute one Perl script at a time, and more often when I run more Perl scripts concurrently. I have added quite some logging to my C++ code to see where the execution is. This indicates that the locations where the access violation occurs are quite random. I have seen them occur during calls to perl_run
, perl_parse
and even perl_destruct
. In my opinion, this all points to some way of memory corruption somewhere, but I am lost in how I could try to debug that.
The relevant parts of my execute function look like:
extern "C" __declspec(dllexport) BOOL ExecutePerlScript(PCSTR path)
{
BOOL result(FALSE);
// Create the Perl interpreter
PerlInterpreter* my_perl(perl_alloc());
if (NULL != my_perl)
{
PERL_SET_CONTEXT(my_perl);
PL_perl_destruct_level = 1;
perl_construct(my_perl);
PL_origalen = 1;
PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
// Initialize the Perl interpreter
result = (perl_parse(my_perl,
XsInit,
NR_DEFAULT_ARGUMENTS,
DEFAULT_ARGUMENTS,
NULL) == 0) ? TRUE : FALSE;
// Run the interpreter
if (result)
{
result = (perl_run(my_perl) == 0) ? TRUE : FALSE;
}
if (result)
{
result = LoadFile(path,
my_perl);
}
if (result)
{
// Execute the Perl script
eval_pv("eval \"$" SCRIPT_TO_EVALUATE_VARIABLE_NAME "; 1\" or do { $" SCRIPT_EXECUTION_ERROR_VARIABLE_NAME " = $@; }",
TRUE);
}
// Destruct the interpreter
PL_perl_destruct_level = 1;
perl_destruct(my_perl);
perl_free(my_perl);
}
return result;
}
EDIT: I compile the code using the following Windows batch commands:
:: Get the required configuration options for the compilation
for /f "delims=" %%i IN ('perl -MConfig -e "print $Config{cc}"') do set CC=%%i
for /f "delims=" %%i IN ('perl -MExtUtils::Embed -e ccopts') do set CCOPTS=%%i
for /f "delims=" %%i IN ('perl -MExtUtils::Embed -e ldopts') do set LDOPTS=%%i
:: Compile the DLL
"%CC%" -shared -o PerlInterop.dll PerlInterop.cpp -g -lstdc++ %CCOPTS% %LDOPTS%
When the application crashes during the destruct, the error output is as follows:
Free to wrong pool 2c193e1b9c0 not 2c1941b2d10 during global destruction.
Fatal error. 0xC0000005
Otherwise, the first line is missing, and just the second line is displayed. I never get any stacktrace.
If you use fork()
from Perl in an embedded application, since Perl uses COW (copy on write) you can accidentally have memory access violations, as the Perl interpreter may have freed memory when a child exits (or vice-versa, interpreter is freed and the child process still exists). This memory your program thinks still exists, but in reality has been freed via END
blocks exiting. Since this is a Win32 application, its not truly forking it's actually using multiple threads which is also a mess. Your best bet is to avoid forking entirely, or, try using POSIX::_exit
instead of exit
in your forked processes, from the core POSIX
module, that stops a few things from being freed accidentally in END
blocks.
Note if you use POSIX::_exit
, import it via use POSIX ()
, it exports everything, and has hundreds of items.