Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safefree() in a win32 “subprocess” blows up the process #17407

Open
FGasper opened this issue Jan 5, 2020 · 15 comments
Open

Safefree() in a win32 “subprocess” blows up the process #17407

FGasper opened this issue Jan 5, 2020 · 15 comments

Comments

@FGasper
Copy link
Contributor

FGasper commented Jan 5, 2020

Description
Safefree() in a win32 “subprocess” misbehaves when memory was first allocated in “parent”.

Steps to Reproduce
On Win32: Clone https://github.com/FGasper/p5-win32-safefree-bug, then perl Makefile.PL && gmake && perl -Mblib t\detect_memory_leaks.t.

Expected behavior
The test should finish, as it does in Linux. Instead it fails with “Free to wrong pool …”.

Perl configuration
Strawberry 5.30.1 64-bit …

Summary of my perl5 (revision 5 version 30 subversion 1) configuration:

  Platform:
    osname=MSWin32
    osvers=10.0.18363.476
    archname=MSWin32-x64-multi-thread
    uname='Win32 strawberry-perl 5.30.1.1 #1 Fri Nov 22 02:24:29 2019 x64'
    config_args='undef'
    hint=recommended
    useposix=true
    d_sigaction=undef
    useithreads=define
    usemultiplicity=define
    use64bitint=define
    use64bitall=undef
    uselongdouble=undef
    usemymalloc=n
    default_inc_excludes_dot=define
    bincompat5005=undef
  Compiler:
    cc='gcc'
    ccflags =' -s -O2 -DWIN32 -DWIN64 -DCONSERVATIVE -D__USE_MINGW_ANSI_STDIO -DPERL_TEXTMODE_SCRIPTS -DPERL_IMPLICIT_CONTEXT -DPERL_IMPLICIT_SYS -DUSE_PERLIO -fwrapv -fno-strict-aliasing -mms-bitfields'
    optimize='-s -O2'
    cppflags='-DWIN32'
    ccversion=''
    gccversion='8.3.0'
    gccosandvers=''
    intsize=4
    longsize=4
    ptrsize=8
    doublesize=8
    byteorder=12345678
    doublekind=3
    d_longlong=define
    longlongsize=8
    d_longdbl=define
    longdblsize=16
    longdblkind=3
    ivtype='long long'
    ivsize=8
    nvtype='double'
    nvsize=8
    Off_t='long long'
    lseeksize=8
    alignbytes=8
    prototype=define
  Linker and Libraries:
    ld='g++.exe'
    ldflags ='-s -L"C:\Users\cpanel\Desktop\strawberry-perl-5.30.1.1-64bit-portable\perl\lib\CORE" -L"C:\Users\cpanel\Desktop\strawberry-perl-5.30.1.1-64bit-portable\c\lib"'
    libpth=C:\Users\cpanel\Desktop\strawberry-perl-5.30.1.1-64bit-portable\c\lib C:\Users\cpanel\Desktop\strawberry-perl-5.30.1.1-64bit-portable\c\x86_64-w64-mingw32\lib C:\Users\cpanel\Desktop\strawberry-perl-5.30.1.1-64bit-portable\c\lib\gcc\x86_64-w64-mingw32\8.3.0
    libs= -lmoldname -lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -lnetapi32 -luuid -lws2_32 -lmpr -lwinmm -lversion -lodbc32 -lodbccp32 -lcomctl32
    perllibs= -lmoldname -lkernel32 -luser32 -lgdi32 -lwinspool -lcomdlg32 -ladvapi32 -lshell32 -lole32 -loleaut32 -lnetapi32 -luuid -lws2_32 -lmpr -lwinmm -lversion -lodbc32 -lodbccp32 -lcomctl32
    libc=
    so=dll
    useshrplib=true
    libperl=libperl530.a
    gnulibc_version=''
  Dynamic Linking:
    dlsrc=dl_win32.xs
    dlext=xs.dll
    d_dlsymun=undef
    ccdlflags=' '
    cccdlflags=' '
    lddlflags='-mdll -s -L"C:\Users\cpanel\Desktop\strawberry-perl-5.30.1.1-64bit-portable\perl\lib\CORE" -L"C:\Users\cpanel\Desktop\strawberry-perl-5.30.1.1-64bit-portable\c\lib"'


Characteristics of this binary (from libperl):
  Compile-time options:
    HAS_TIMES
    HAVE_INTERP_INTERN
    MULTIPLICITY
    PERLIO_LAYERS
    PERL_COPY_ON_WRITE
    PERL_DONT_CREATE_GVSV
    PERL_IMPLICIT_CONTEXT
    PERL_IMPLICIT_SYS
    PERL_MALLOC_WRAP
    PERL_OP_PARENT
    PERL_PRESERVE_IVUV
    USE_64_BIT_INT
    USE_ITHREADS
    USE_LARGE_FILES
    USE_LOCALE
    USE_LOCALE_COLLATE
    USE_LOCALE_CTYPE
    USE_LOCALE_NUMERIC
    USE_LOCALE_TIME
    USE_PERLIO
    USE_PERL_ATOF
  Built under MSWin32
  Compiled at Nov 22 2019 02:30:43
  @INC:
    C:/Users/cpanel/Desktop/strawberry-perl-5.30.1.1-64bit-portable/perl/site/lib
    C:/Users/cpanel/Desktop/strawberry-perl-5.30.1.1-64bit-portable/perl/vendor/lib
    C:/Users/cpanel/Desktop/strawberry-perl-5.30.1.1-64bit-portable/perl/lib
@xenu
Copy link
Member

xenu commented Jan 5, 2020

It's not a bug, it was implemented that way on purpose. The whole point of the "Free to wrong pool" error is to tell you that a wrong thread is freeing the memory.

Whether it makes sense or not is a different question. Personally, I'm not familiar enough with that code to have an opinion.

@FGasper
Copy link
Contributor Author

FGasper commented Jan 5, 2020

@xenu So are all XSUBs then on the hook to detect when they’re in a pseudo-subprocess and forgo Safefree(), then?

I thought the notion of Safefree() was to abstract away details like that …

@Leont
Copy link
Contributor

Leont commented Jan 5, 2020

Whether it makes sense or not is a different question. Personally, I'm not familiar enough with that code to have an opinion.

I don't think that the people who wrote this still around. I have also been bitten by this, and resorted to using PerlMemShared_malloc and friends, but it's quite unhelpful.

I had assumed it's because perl is using LocalAlloc, but that doesn't appear to be the case. Quite frankly it rather looks to me like we could remove this logic and nothing stops working.

My second guess is that it prevents memory leaks from pseudo-fork processes to affect the other "processes", but in that case it's really not worth the hassle IMO.

You could try to compile perl with this patch, and see if anything breaks (I suspect not)

Author: Leon Timmermans <fawaka@gmail.com>
Date:   Sun Jan 5 16:28:38 2020 +0100

    Don't use thread specific memory pools

diff --git win32/vmem.h win32/vmem.h
index 3fd7e169fc..372661944f 100644
--- win32/vmem.h
+++ win32/vmem.h
@@ -22,7 +22,7 @@
 #define ___VMEM_H_INC___
 
 #define _USE_MSVCRT_MEM_ALLOC
-#define _USE_LINKED_LIST
+// #define _USE_LINKED_LIST
 
 // #define _USE_BUDDY_BLOCKS

@xenu
Copy link
Member

xenu commented Jan 5, 2020

BTW, note that this behavior is not specific to Windows. Unix perls built with -DDEBUGGING do the same thing (of course, that only affects threads because non-win32 perls don't have pseudoprocesses).

@FGasper
Copy link
Contributor Author

FGasper commented Jan 5, 2020

@xenu OK, thank you; that was my next question: whether this avoid-Safefree-if-in-subprocess behavior is specific to win32, or if there’s a broader category of “pseudoprocess-using perls”.

@Leont Off-hand I don’t know how to compile a win32 perl. I’ll look, though. Thank you!

@xenu
Copy link
Member

xenu commented Jan 5, 2020

if there’s a broader category of “pseudoprocess-using perls”.

I believe AmigaOS port has pseudoprocesses too, but their implementation is completely separate from the Windows one and I have no idea how it works.

@tonycoz
Copy link
Contributor

tonycoz commented Jan 5, 2020

I think this needs documentation rather than a code change. I had thought this was documented, but I don't see anything.

In general if you want memory that isn't tied to the current thread you should be using safesysmalloc()/safesysrealloc()/safesysfree().

@Leont
Copy link
Contributor

Leont commented Jan 5, 2020

I think this needs documentation rather than a code change.

But is there any reason for this behavior? Because quite frankly I've only seen it get in the way of things.

@FGasper
Copy link
Contributor Author

FGasper commented Jan 6, 2020

In general if you want memory that isn't tied to the current thread you should be using safesysmalloc()/safesysrealloc()/safesysfree().

@tonycoz These functions are undocumented … should they be added to perlapi/perlclib?

@tonycoz
Copy link
Contributor

tonycoz commented Jan 6, 2020

it has bitten me before too (which is why most of Imager avoids the perl headers, since malloc() is redefined to PerlMem_malloc() on Win32).

I guess it depends on why it was added, if we do look at removing it I think it needs to be discussed on the list rather than just in this ticket, so whoever added it has an opportunity to defend it.

@tonycoz
Copy link
Contributor

tonycoz commented Jan 6, 2020

@tonycoz These functions are undocumented … should they be added to perlapi/perlclib?

Yes.

@iabyn
Copy link
Contributor

iabyn commented Jan 6, 2020 via email

@Leont
Copy link
Contributor

Leont commented Jan 6, 2020

My recollection is that (at the time, at least: 2005), Windows would give a
"free to wrong pool" error if memory malloc()ed by one thread was free()d
by another thread.

TBH that sounds to me like a case of " No, it is not a compiler error. It is never a compiler error." I really don't believe that malloc can be so unhelpful without this widely being known and documented. Google tells me msvc malloc isn't thread safe unless one links with the thread-safe c library, which might be the problem observed back then.

@tonycoz
Copy link
Contributor

tonycoz commented Jan 20, 2020

My recollection is that (at the time, at least: 2005), Windows would give a "free to wrong pool" error if memory malloc()ed by one thread was free()d by another thread. ...

This is produced by perl, not the underlying malloc() implementation, see https://github.com/Perl/perl5/blob/blead/win32/vmem.h#L199

Both shared and "system" memory end up being allocated/freed with malloc()/free(), with shared memory having a VMem object and each thread having their own VMem objects.

I suspect the intent was to allow fork emulation to clean up all of the (perl) allocated memory from a child thread, but I'm only guessing here.

This comes with a fair amount of overhead on Win32, even on non-debug builds, each memory block has a next and prev pointers for the double-linked list (8-16 bytes), and extra locking (which the underlying malloc() also presumably already does.)

I'm inclined to remove this, but I'd rather not do it blindly.

@bulk88
Copy link
Contributor

bulk88 commented Nov 16, 2024

My recollection is that (at the time, at least: 2005), Windows would give a "free to wrong pool" error if memory malloc()ed by one thread was free()d by another thread. ...

This is produced by perl, not the underlying malloc() implementation, see https://github.com/Perl/perl5/blob/blead/win32/vmem.h#L199

Both shared and "system" memory end up being allocated/freed with malloc()/free(), with shared memory having a VMem object and each thread having their own VMem objects.

I suspect the intent was to allow fork emulation to clean up all of the (perl) allocated memory from a child thread, but I'm only guessing here.

This comes with a fair amount of overhead on Win32, even on non-debug builds, each memory block has a next and prev pointers for the double-linked list (8-16 bytes), and extra locking (which the underlying malloc() also presumably already does.)

I'm inclined to remove this, but I'd rather not do it blindly.

Yep, libperl is "supposed" to support being unloaded without leaking. "supposed" to is because I've never heard of anyone embedding perl on win32 but i didnt google it.

There are 3 memory pools in VMem.h one is per my_perl, other 2 are shared, IDK why there are 2 shared pools. Perl interp and CPAN (a blackhole) supposedly leak constantly, so even if are a vanilla perl user, if you launch ithreads or psuedo forks, it could (i never researched it), get very ugly in that OS process ID after 100s of ithreads launched/exited.

Next Win32 Perl memory "design requirement". There are or were dumptrucks worth of CPAN XS code, 100s of modules, that are perl thread-unaware/my_perl ptr unaware/OS thread pool unaware. Perl 5.0 5.6 5.8 era, specifically where perl thread was a concept and 5.005 threads or whatever it was called. Even in 5.10-5.12 era, fresh CPAN modules get written, pass their C func ptr "callback" to a foreign C lib, usually Microsoft or OS vendor DLL. And then their C func starts being called from "thread pools". It doesnt crash on his single thread linux perl (crashes instant multi thread Linux perl), so that module author is clueless about threads and perl. module author on his bug tracker often is shocked to hear about "threads" and perl. There are devs who wrote large 30 screenfull long XS libs, and had no clue about my_perl ptr or threads for months or years until a Win32 user came by.

4th design requirement, WinOS has 12 different incompatible msvcrts/"libc"s. with upto 12 incompatible memory pools worst case. Its always UB to pass malloc blocks from "one pool" as defined by upstream official API docs. to free those malloc blocks with the free() fnc of another same proc same Vm space pool. Even I use a decompiler on my own system and see what malloc backends are technical/engineering identical on my system.

Any random monthly OS security update can replace a malloc backend at random in any DLL. OS vendor privilege because it was API doced as not allowed to pass ptrs between memory pools.

Perl's Vmem.h is overengineered, bloated by wrapping 16 byte headers on each mem block. Win NT solved the problem 30 years ago with https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapcreate API. Anyone, any dll, has their own private "copy paste" safe/well engineered/not hand made malloc pool any time they want. Now everyone has the API features to do perfect clean leak free DLL runtime unloading.

https://learn.microsoft.com/en-us/windows/win32/api/heapapi/nf-heapapi-heapdestroy

BOOM

Now your code is leak free even if that dev is incompetent and the process linear grows 10 MB a minute until OOM and that dev is apathetic or unaware. Just centally nuke your malloc pool on a timer.

Perl really really needs to use the raw native MS malloc apis and stop this nonsense of in-house less than perfect feature emulation of Heap* native MS APIs. Vmem.h is from Sarathy era, and probably "Win32S" (~WINE) for Win16 was missing Heap*() APIs. I just checked my Kernel32.dll from Win95. I have the full WinNt Heap*() API available in Win95. I truly don't know why native HeapAlloc(), wasn't NOT used by Sarathy.

So Win32 PL's terrible malloc design is 50% "assert" -DDEBUGGING code accidentally left on forever.

25% power struggle inside P5P over Hungarian notation variables

25% billable hours to your employer

Note Vmem.h is almost completely re implemented with lower case POSIX variables at

https://github.com/Perl/perl5/blob/blead/util.c#L189C3-L189C26

IDK who code came first, but thats clearly an ego battle inside p5p jkjk

Now, I finally I did benchmark Perl and UCRT/MSVCRT bloat vs native Win32 OS malloc. Confirms what I've known for years. I'm shocked.

Perl's Win32 malloc func call/FRONTEND is 2.8X SLOWER than the OS's front end. Win32 QueryPerfCounter was used.
Yes Perl malloc ALWAYS reaches HeapAlloc eventually on the C call stack. But perl's vmem.h code TRIPLES the time!!!!!

perl official malloc 279 us Ln 1249
ucrt dll 117 us Ln 1258
Native malloc 97 us Ln 1267
void
ZZ()
PREINIT:
    LARGE_INTEGER Frequency;
    void * arr [1000];
    int i;
CODE:
    QueryPerformanceFrequency((LARGE_INTEGER*)&Frequency);
    BTIMESTART;
    for(i=0;i<1000;i++) {
    Newx(arr[i], 6, char);
    }
    for(i=0;i<1000;i++) {
    Safefree(arr[i], 6, char);
    }
    BTIMEEND("perl official malloc");
    
    BTIMESTART;
    for(i=0;i<1000;i++) {
    arr[i] = win32_malloc(6);
    }
    for(i=0;i<1000;i++) {
    win32_free(arr[i]);
    }
    BTIMEEND("ucrt dll");
    HANDLE heap = GetProcessHeap();
    BTIMESTART;
    for(i=0;i<1000;i++) {
    arr[i] = HeapAlloc(heap,0,6);
    }
    for(i=0;i<1000;i++) {
        HeapFree(heap, 0, arr[i]);
    }
    BTIMEEND("Native malloc");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants