2

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.

7
  • How do you compile this program? And can you include the exact error message/stack trace you get? Commented Apr 14 at 8:21
  • Is your Perl code forking at all? Commented Apr 14 at 15:02
  • @HåkonHægland Edited the question to answer your questions. Commented Apr 14 at 18:28
  • @MarcelV. Does your Perl code use Win32::OLE? It is known to not be thread-safe. You can't run multiple instances of it in multiple threads/multiple Perl interpreters. Commented Apr 14 at 18:54
  • @RawleyFowler I indeed have a few scripts that use the Parallel::ForkManager to do some things in parallel. Running muliple scripts in parallel, but making sure none of those scripts is using Parallel::ForkManager (or directly fork) either directly or indirectly via a used module seems to elimate the access violation crashes (I will try to see what happens if I set the number of parallel processes to 0). But because of this I also found out that I still have occasional crashes/exits of my entire application that result from a croak in the Perl code. Can I prevent that? Commented Apr 14 at 19:05

1 Answer 1

2

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.