Re: Extending /proc/*/maps
Hi Ryan, On May 11 01:27, Ryan Johnson wrote: > Hi all, > > Please find attached three patches which extend the functionality of > /proc/*/maps. Thanks! I applied youyr two first patches with a couple of changes: - Formatting: Setting of curly braces in class and method defintions, a lot of missing spaces, "int *foo" instead of "int* foo", stuff like that. Please compare what I checked in against your patch. That doesn't mean I always get it right myself, but basically that's how it should be. - NT_MAX_PATH is the maximum size of a path including the trailing \0, so a buffer size of NT_MAX_PATH + 1 isn't required. - Please always use sys_wcstombs rather than wcstombs for filenames. - I replaced the call to GetMappedFileNameEx with a call to NtQueryVirtualMemory (MemorySectionName). This avoids to add another dependency to psapi. I intend to get rid of them entirely, if possible. > NOTE 1: I do not attempt to identify PEB, TEB, or thread stacks. The > first could be done easily enough, but the second and third require > venturing into undocumented/private Windows APIs. Go ahead! We certainly don't shy away from calls to NtQueryInformationProcess or NtQueryInformationThread. > NOTE 2: If desired, we could easily extend format_process_maps > further to report section names of mapped images (linux does this > for .so files), [...] Sorry if I'm dense, but isn't that exactly what GetMappedFileNameEx or NtQueryVirtualMemory (MemorySectionName) do? I also don't see any extra information for .so files in the Linux maps output. What detail am I missing? Thanks, Corinna -- Corinna Vinschen Please, send mails regarding Cygwin to Cygwin Project Co-Leader cygwin AT cygwin DOT com Red Hat
Re: Extending /proc/*/maps
On May 11 01:27, Ryan Johnson wrote: > The second (proc-maps-heaps) adds reporting of Windows heaps (or > their bases, at least). Unfortunately there doesn't seem to be any > efficient way to identify all virtual allocations which a heap owns. There's a call RtlQueryDebugInformation which can fetch detailed heap information, and which is used by Heap32First/Heap32Last. Using it directly is much more efficient than using the Heap32 functions. The DEBUG_HEAP_INFORMATION is already in ntdll.h, what's missing is the layout of the Blocks info. I found the info by googling: typedef struct _HEAP_BLOCK { PVOID addr; ULONG size; ULONG flags; ULONG unknown; } HEAP_BLOCK, *PHEAP_BLOCK; If this information is searched until the address falls into the just inspected block of virtual memory, then we would have the information, isn't it? Corinna -- Corinna Vinschen Please, send mails regarding Cygwin to Cygwin Project Co-Leader cygwin AT cygwin DOT com Red Hat
Re: Extending /proc/*/maps
On 11/05/2011 6:31 AM, Corinna Vinschen wrote: Hi Ryan, On May 11 01:27, Ryan Johnson wrote: Hi all, Please find attached three patches which extend the functionality of /proc/*/maps. Thanks! I applied youyr two first patches with a couple of changes: - Formatting: Setting of curly braces in class and method defintions, a lot of missing spaces, "int *foo" instead of "int* foo", stuff like that. Please compare what I checked in against your patch. That doesn't mean I always get it right myself, but basically that's how it should be. Sorry... I did my best to match the surrounding code but didn't notice the '* ' vs ' *' thing. - NT_MAX_PATH is the maximum size of a path including the trailing \0, so a buffer size of NT_MAX_PATH + 1 isn't required. Good to know. Thanks. - I replaced the call to GetMappedFileNameEx with a call to NtQueryVirtualMemory (MemorySectionName). This avoids to add another dependency to psapi. I intend to get rid of them entirely, if possible. Nice. One issue: I tried backporting your change to my local tree, and the compiler complains that PMEMORY_SECTION_NAME isn't defined; the changelog says you updated ntdll.h to add it, but the online definition in w32api was last updated 9 months ago by 'duda.' Did it perhaps slip past the commit? NOTE 1: I do not attempt to identify PEB, TEB, or thread stacks. The first could be done easily enough, but the second and third require venturing into undocumented/private Windows APIs. Go ahead! We certainly don't shy away from calls to NtQueryInformationProcess or NtQueryInformationThread. Ah. I didn't realize this sort of thing was encouraged. The MSDN docs about them are pretty gloomy. The other things that discouraged me before were: - the only obvious way to enumerate the threads in a process is to create a snapshot using TH32CS_SNAPTHREAD, which enumerates all threads in the system. This sounds expensive. - it's not clear whether GetThreadContext returns a reasonable stack pointer if the target thread is active at the time. However, assuming the above do not bother folks, they should be pretty straightforward to use. I won't have time to code this up in the immediate future, though. My real goal was to make fork bearable on my machine and that ended up sucking away all the time I had and then some... NOTE 2: If desired, we could easily extend format_process_maps further to report section names of mapped images (linux does this for .so files), [...] Sorry if I'm dense, but isn't that exactly what GetMappedFileNameEx or NtQueryVirtualMemory (MemorySectionName) do? I also don't see any extra information for .so files in the Linux maps output. What detail am I missing? Interesting... the machine I used for reference a couple weeks ago was running a really old debian, and for each allocation entry of a mapped image it gave the corresponding section name (.text, .bss, .rdata, etc): 346360-346362c000 r-xp 08:01 2097238 /lib64/libpcre.so.0.0.1 .text 346362c000-346382b000 ---p 0002c000 08:01 2097238 /lib64/libpcre.so.0.0.1 346382b000-346382c000 rw-p 0002b000 08:01 2097238 /lib64/libpcre.so.0.0.1 .bss However, the machine was upgraded to a newer kernel this week and the extra information no longer appears. In any case, should somebody want to report section names within a mapped image, that information can be had easily enough using the pefile struct from my fork patches. Ryan
Re: Extending /proc/*/maps
On May 11 08:53, Ryan Johnson wrote: > On 11/05/2011 6:31 AM, Corinna Vinschen wrote: > >- I replaced the call to GetMappedFileNameEx with a call to > > NtQueryVirtualMemory (MemorySectionName). This avoids to add another > > dependency to psapi. I intend to get rid of them entirely, if > > possible. > Nice. One issue: I tried backporting your change to my local tree, > and the compiler complains that PMEMORY_SECTION_NAME isn't defined; > the changelog says you updated ntdll.h to add it, but the online > definition in w32api was last updated 9 months ago by 'duda.' Did it > perhaps slip past the commit? The compiler shouldn't complain because we only use the definitions from ntdll.h and nothing else from w32api/include/ddk (except in rare cases, see fhandler_tape.cc, fhandler_serial.cc). Sometimes I apply new stuff to w32api as well on an "as it comes along" base, but usually it's not the Cygwin project which maintains these files. > >>NOTE 1: I do not attempt to identify PEB, TEB, or thread stacks. The > >>first could be done easily enough, but the second and third require > >>venturing into undocumented/private Windows APIs. > >Go ahead! We certainly don't shy away from calls to > >NtQueryInformationProcess or NtQueryInformationThread. > Ah. I didn't realize this sort of thing was encouraged. The MSDN > docs about them are pretty gloomy. Gary Nebbett, "Windows NT/2000 Native API Reference". It's a bit rusty as far as new stuff is concerned, but it contains the important stuff we can rely upon. > The other things that discouraged me before were: > - the only obvious way to enumerate the threads in a process is to > create a snapshot using TH32CS_SNAPTHREAD, which enumerates all > threads in the system. This sounds expensive. > - it's not clear whether GetThreadContext returns a reasonable stack > pointer if the target thread is active at the time. Per MSDN the thread context is only valid if the thread is not running at the time. However, we could carefully inspect some of the values and see how stable they are. > However, assuming the above do not bother folks, they should be > pretty straightforward to use. I won't have time to code this up in > the immediate future, though. My real goal was to make fork bearable > on my machine and that ended up sucking away all the time I had and > then some... Well, it's not high enough on my agenda to dive into it. Feel free to do that whenever you find a free time slot. > >Sorry if I'm dense, but isn't that exactly what GetMappedFileNameEx or > >NtQueryVirtualMemory (MemorySectionName) do? I also don't see any extra > >information for .so files in the Linux maps output. What detail am I > >missing? > Interesting... the machine I used for reference a couple weeks ago > was running a really old debian, and for each allocation entry of a > mapped image it gave the corresponding section name (.text, .bss, > .rdata, etc): > 346360-346362c000 r-xp 08:01 2097238 > /lib64/libpcre.so.0.0.1 .text > 346362c000-346382b000 ---p 0002c000 08:01 2097238 > /lib64/libpcre.so.0.0.1 > 346382b000-346382c000 rw-p 0002b000 08:01 2097238 > /lib64/libpcre.so.0.0.1 .bss > > However, the machine was upgraded to a newer kernel this week and > the extra information no longer appears. Indeed, this information is not available on my 2.6.35 kernel. > In any case, should somebody want to report section names within a > mapped image, that information can be had easily enough using the > pefile struct from my fork patches. Right. Corinna -- Corinna Vinschen Please, send mails regarding Cygwin to Cygwin Project Co-Leader cygwin AT cygwin DOT com Red Hat
Re: Extending /proc/*/maps
On 11/05/2011 9:20 AM, Corinna Vinschen wrote: On May 11 08:53, Ryan Johnson wrote: On 11/05/2011 6:31 AM, Corinna Vinschen wrote: - I replaced the call to GetMappedFileNameEx with a call to NtQueryVirtualMemory (MemorySectionName). This avoids to add another dependency to psapi. I intend to get rid of them entirely, if possible. Nice. One issue: I tried backporting your change to my local tree, and the compiler complains that PMEMORY_SECTION_NAME isn't defined; the changelog says you updated ntdll.h to add it, but the online definition in w32api was last updated 9 months ago by 'duda.' Did it perhaps slip past the commit? The compiler shouldn't complain because we only use the definitions from ntdll.h and nothing else from w32api/include/ddk (except in rare cases, see fhandler_tape.cc, fhandler_serial.cc). Sometimes I apply new stuff to w32api as well on an "as it comes along" base, but usually it's not the Cygwin project which maintains these files. Ah... there are two ntdll.h, and I looked at the one in w32api/include instead of cygwin. All is well. Ryan
Re: Improvements to fork handling
On 11/05/2011 10:13 AM, Christopher Faylor wrote: On Wed, May 11, 2011 at 09:59:53AM +0200, Corinna Vinschen wrote: On May 11 02:18, Ryan Johnson wrote: Please find attached five patches [...] Oops, wrong mailing list... Btw., it would be nice if you could create patches with the diff -p flag as well. It's not exactly essential, but IMHO it's quite a help when trying to review patches. Another problem is this: While you provide separate patches, you don't provide separate ChangeLogs. That makes it kind of hard to apply them separately. Would you mind to create one ChangeLog per change? Ditto. This really needs to be broken down into easier to review chunks. All right. Let's try this again with the correct mailing list. The patches have been generated with diff -p, and each includes appropriate changelog entries. Hopefully the changes are split up finely enough because I don't know a good way to break them down any further. For posterity's sake I'm including the original message body below. Ryan Please find attached five patches which improve the behavior of forking when Windows isn't cooperating as well we'd like. The results are not as good as I'd originally hoped for, in that it's still entirely possible (even common) for fork attempts to fail, but at least now they are clean failures. Most sources of access violations should be gone, address space clobbers lead to clean child exit, and retries are applied consistently. It will still be important both to rebase and to ASLR-enable dlls, however, because there are too many sources of address space clobbers which we really can't control. One open issue remains: windows dlls, thread stacks, and heaps can and do end up at different locations in the child. This technically breaks fork semantics but I don't know whether we care. Since we currently have no real way to track this or compensate for it in the absence of obvious address space clobbers, the question is probably moot in any case. The first patch (fork-clean-exit) allows a child which failed due to address space clobbers to report cleanly back to the parent. As a result, DLL_LINK which land wrong, DLL_LOAD whose space gets clobbered, and failure to replicate the cygheap, generate retries and dispense with the terminal spam. Handling of unexpected errors should not have changed. Further, the patch fixes several sources of access violations and crashes, including: - accessing invalid state after failing to notice that a statically-linked dll loaded at the wrong location - accessing invalid state while running dtors on a failed forkee. I follow cgf's approach of simply not running any dtors, based on the observation that dlls in the parent (gcc_s!) can store state about other dlls and crash trying to access that state in the child, even if they appeared to map properly in both processes. - attempting to generate a stack trace when somebody in the call chain used alloca(). This one is only sidestepped here, because we eliminate the access violations and api_fatal calls which would have triggered the problematic stack traces. I have a separate patch which allows offending functions to disable stack traces, if folks are interested, but it was kind of noisy so I left it out for now (cygwin uses alloca pretty liberally!). The second (fork-topsort) has the parent sort its dll list topologically by dependencies. Previously, attempts to load a DLL_LOAD dll risked pulling in dependencies automatically, and the latter would then not benefit from the code which "encourages" them to land in the right places. The dependency tracking is achieved using a simple class which allows to introspect a mapped dll image and pull out the dependencies it lists. The code currently rebuilds the dependency list at every fork rather than attempt to update it properly as modules are loaded and unloaded. Note that the topsort optimization affects only cygwin dlls, so any windows dlls which are pulled in dynamically (directly or indirectly) will still impose the usual risk of address space clobbers. The third (fork-reserve-at) fixes a bug in the reserve_at function which caused it to sometimes reserve space needed by the dll it was supposed to help land. This happens when the dll tries to land in a free region which overlaps the desired location. The new code exploits the image introspection to get the dll's image size and avoids the corner cases. The fourth (fork-dll-load) provides a rewrite to dll_list::load_after fork. The new version eliminates reserve_upto() and release_upto(), which were expensive (the process repeats for each dll) and buggy (release_upto could free allocations reserve_upto did not make). Instead, the effect of reserve_upto is achieved by recursively attempting to load each dll in its proper place and calling reserve_at before retrying; each reservation's location is kept on the stack throughout and release_at calls are made only whe
Re: Improvements to fork handling
On Wed, May 11, 2011 at 10:21:14AM -0400, Ryan Johnson wrote: >On 11/05/2011 10:13 AM, Christopher Faylor wrote: >> On Wed, May 11, 2011 at 09:59:53AM +0200, Corinna Vinschen wrote: >>> On May 11 02:18, Ryan Johnson wrote: Please find attached five patches [...] >>> Oops, wrong mailing list... >>> >>> Btw., it would be nice if you could create patches with the diff -p flag >>> as well. It's not exactly essential, but IMHO it's quite a help when >>> trying to review patches. >>> >>> Another problem is this: While you provide separate patches, you don't >>> provide separate ChangeLogs. That makes it kind of hard to apply them >>> separately. Would you mind to create one ChangeLog per change? >> Ditto. This really needs to be broken down into easier to review chunks. >All right. Let's try this again with the correct mailing list. > >The patches have been generated with diff -p, and each includes >appropriate changelog entries. Hopefully the changes are split up finely >enough because I don't know a good way to break them down any further. > >For posterity's sake I'm including the original message body below. Please: One patch per message. One ChangeLog entry per message, not sent as a diff. cgf
Re: Improvements to fork handling
On 11/05/2011 12:16 PM, Christopher Faylor wrote: Please: One patch per message. One ChangeLog entry per message, not sent as a diff. Resend? Or just note for future reference? Ryan
Re: Extending /proc/*/maps
On 11/05/2011 7:14 AM, Corinna Vinschen wrote: On May 11 01:27, Ryan Johnson wrote: The second (proc-maps-heaps) adds reporting of Windows heaps (or their bases, at least). Unfortunately there doesn't seem to be any efficient way to identify all virtual allocations which a heap owns. There's a call RtlQueryDebugInformation which can fetch detailed heap information, and which is used by Heap32First/Heap32Last. Using it directly is much more efficient than using the Heap32 functions. The DEBUG_HEAP_INFORMATION is already in ntdll.h, what's missing is the layout of the Blocks info. I found the info by googling: typedef struct _HEAP_BLOCK { PVOID addr; ULONG size; ULONG flags; ULONG unknown; } HEAP_BLOCK, *PHEAP_BLOCK; If this information is searched until the address falls into the just inspected block of virtual memory, then we would have the information, isn't it? The problem is that the reported heap blocks corresponded to individual malloc() calls when I tested it (unordered list, most sizes < 100B). I really would have preferred a function that returns the list of memory regions owned by the heap. They must track that information -- I highly HeapDestroy uses these heap blocks to decide which memory regions to VirtualFree, but that info seems to live in the kernel... Given that Heap32* has already been reverse-engineered by others, the main challenge would involve sorting the set of heap block addresses and distilling them down to a set of allocation bases. We don't want to do repeated linear searches over 50k+ heap blocks. Also, the cygheap isn't a normal windows heap, is it? I thought it was essentially a statically-allocated array (.cygheap) that gets managed as a heap. I guess since it's part of cygwin1.dll we already do sort of report it... Ryan
Improvements to fork handling (1/5)
Hi all, This is the first of a series of patches, sent in separate emails as requested. The first patch allows a child which failed due to address space clobbers to report cleanly back to the parent. As a result, DLL_LINK which land wrong, DLL_LOAD whose space gets clobbered, and failure to replicate the cygheap, generate retries and dispense with the terminal spam. Handling of unexpected errors should not have changed. Further, the patch fixes several sources of access violations and crashes, including: - accessing invalid state after failing to notice that a statically-linked dll loaded at the wrong location - accessing invalid state while running dtors on a failed forkee. I follow cgf's approach of simply not running any dtors, based on the observation that dlls in the parent (gcc_s!) can store state about other dlls and crash trying to access that state in the child, even if they appeared to map properly in both processes. - attempting to generate a stack trace when somebody in the call chain used alloca(). This one is only sidestepped here, because we eliminate the access violations and api_fatal calls which would have triggered the problematic stack traces. I have a separate patch which allows offending functions to disable stack traces, if folks are interested, but it was kind of noisy so I left it out for now (cygwin uses alloca pretty liberally!). Ryan diff --git a/child_info.h b/child_info.h --- a/child_info.h +++ b/child_info.h @@ -92,6 +92,18 @@ public: void alloc_stack_hard_way (volatile char *); }; +/* Several well-known problems can prevent us from patching up a + forkee; when such errors arise the child should exit cleanly (with + a failure code for the parent) rather than dumping stack. */ +#define fork_api_fatal(fmt, args...) \ + do \ +{ \ + sigproc_printf (fmt,## args);\ + fork_info->handle_failure (-1); \ +} \ + while(0) + + class fhandler_base; class cygheap_exec_info diff --git a/dll_init.cc b/dll_init.cc --- a/dll_init.cc +++ b/dll_init.cc @@ -19,6 +19,7 @@ details. */ #include "dtable.h" #include "cygheap.h" #include "pinfo.h" +#include "child_info.h" #include "cygtls.h" #include "exception.h" #include @@ -131,10 +132,16 @@ dll_list::alloc (HINSTANCE h, per_proces { if (!in_forkee) d->count++; /* Yes. Bump the usage count. */ + else if (d->handle != h) + fork_api_fatal ("Location of %W changed from %p (parent) to %p (child)", + d->name, d->handle, h); d->p = p; } else { + if (in_forkee) + system_printf ("Unexpected dll loaded during fork: %W", name); + /* FIXME: Change this to new at some point. */ d = (dll *) cmalloc (HEAP_2_DLL, sizeof (*d) + (namelen * sizeof (*name))); @@ -371,7 +378,6 @@ dll_list::load_after_fork (HANDLE parent preferred_block = reserve_at (d->name, (DWORD) h); } - in_forkee = false; } struct dllcrt0_info diff --git a/dll_init.h b/dll_init.h --- a/dll_init.h +++ b/dll_init.h @@ -57,7 +57,7 @@ struct dll int init (); void run_dtors () { -if (has_dtors) +if (has_dtors && !in_forkee) { has_dtors = 0; p.run_dtors (); diff --git a/fork.cc b/fork.cc --- a/fork.cc +++ b/fork.cc @@ -233,6 +233,7 @@ frok::child (volatile char * volatile he sync_with_parent ("loaded dlls", true); } + in_forkee = false; init_console_handler (myself->ctty >= 0); ForceCloseHandle1 (fork_info->forker_finished, forker_finished); @@ -393,10 +394,13 @@ frok::parent (volatile char * volatile s if (!exit_code) continue; this_errno = EAGAIN; - /* Not thread safe, but do we care? */ - __small_sprintf (errbuf, "died waiting for longjmp before initialization, " - "retry %d, exit code %p", ch.retry, exit_code); - error = errbuf; + if (exit_code != EXITCODE_FORK_FAILED) + { + /* Not thread safe, but do we care? */ + __small_sprintf (errbuf, "died waiting for longjmp before initialization, " + "retry %d, exit code %p", ch.retry, exit_code); + error = errbuf; + } goto cleanup; } break; @@ -515,7 +519,8 @@ frok::parent (volatile char * volatile s if (!ch.sync (child->pid, pi.hProcess, FORK_WAIT_TIMEOUT)) { this_errno = EAGAIN; - error = "died waiting for dll loading"; + if (ch.exit_code != EXITCODE_FORK_FAILED) + error = "died waiting for dll loading"; goto cleanup; } diff --git a/heap.cc b/heap.cc --
Improvements to fork handling (2/5)
Hi all, This patch has the parent sort its dll list topologically by dependencies. Previously, attempts to load a DLL_LOAD dll risked pulling in dependencies automatically, and the latter would then not benefit from the code which "encourages" them to land in the right places. The dependency tracking is achieved using a simple class which allows to introspect a mapped dll image and pull out the dependencies it lists. The code currently rebuilds the dependency list at every fork rather than attempt to update it properly as modules are loaded and unloaded. Note that the topsort optimization affects only cygwin dlls, so any windows dlls which are pulled in dynamically (directly or indirectly) will still impose the usual risk of address space clobbers. Ryan diff --git a/dll_init.cc b/dll_init.cc --- a/dll_init.cc +++ b/dll_init.cc @@ -116,6 +116,18 @@ dll_list::operator[] (const PWCHAR name) return NULL; } +/* Look for a dll based on is short name only (no path) */ +dll * +dll_list::find_by_modname (const PWCHAR name) +{ + dll *d = &start; + while ((d = d->next) != NULL) +if (!wcscasecmp (name, d->modname)) + return d; + + return NULL; +} + #define RETRIES 1000 /* Allocate space for a dll struct. */ @@ -152,14 +164,13 @@ dll_list::alloc (HINSTANCE h, per_proces d->handle = h; d->has_dtors = true; d->p = p; + d->ndeps = 0; + d->deps = NULL; + d->modname = wcsrchr (d->name, L'\\'); + if (d->modname) + d->modname++; d->type = type; - if (end == NULL) - end = &start; /* Point to "end" of dll chain. */ - end->next = d; /* Standard linked list stuff. */ - d->next = NULL; - d->prev = end; - end = d; - tot++; + append (d); if (type == DLL_LOAD) loaded_dlls++; } @@ -168,6 +179,119 @@ dll_list::alloc (HINSTANCE h, per_proces return d; } +void +dll_list::append (dll* d) +{ + if (end == NULL) +end = &start; /* Point to "end" of dll chain. */ + end->next = d; /* Standard linked list stuff. */ + d->next = NULL; + d->prev = end; + end = d; + tot++; +} + +void dll_list::populate_deps (dll* d) +{ + WCHAR wmodname[NT_MAX_PATH]; + pefile* pef = (pefile*) d->handle; + PIMAGE_DATA_DIRECTORY dd = pef->idata_dir (IMAGE_DIRECTORY_ENTRY_IMPORT); + /* Annoyance: calling crealloc with a NULL pointer will use the + wrong heap and crash, so we have to replicate some code */ + long maxdeps = 4; + d->deps = (dll**) cmalloc (HEAP_2_DLL, maxdeps*sizeof (dll*)); + d->ndeps = 0; + for (PIMAGE_IMPORT_DESCRIPTOR id= + (PIMAGE_IMPORT_DESCRIPTOR) pef->rva (dd->VirtualAddress); + dd->Size && id->Name; + id++) +{ + char* modname = pef->rva (id->Name); + sys_mbstowcs (wmodname, NT_MAX_PATH, modname); + if (dll* dep = find_by_modname (wmodname)) + { + if (d->ndeps >= maxdeps) + { + maxdeps = 2*(1+maxdeps); + d->deps = (dll**) crealloc (d->deps, maxdeps*sizeof (dll*)); + } + d->deps[d->ndeps++] = dep; + } +} + + /* add one to differentiate no deps from unknown */ + d->ndeps++; +} + + +void +dll_list::topsort () +{ + /* Anything to do? */ + if (!end) +return; + + /* make sure we have all the deps available */ + dll* d = &start; + while ((d = d->next)) +if (!d->ndeps) + populate_deps (d); + + /* unlink head and tail pointers so the sort can rebuild the list */ + d = start.next; + start.next = end = NULL; + topsort_visit (d, true); + + /* clear node markings made by the sort */ + d = &start; + while ((d = d->next)) +{ + debug_printf ("%W", d->modname); + for (int i=1; i < -d->ndeps; i++) + debug_printf ("-> %W", d->deps[i-1]->modname); + + /* It would be really nice to be able to keep this information +around for next time, but we don't have an easy way to +invalidate cached dependencies when a module unloads. */ + d->ndeps = 0; + cfree (d->deps); + d->deps = NULL; +} +} + +/* A recursive in-place topological sort. The result is ordered so that + dependencies of a dll appear before it in the list. + + NOTE: this algorithm is guaranteed to terminate with a "partial + order" of dlls but does not do anything smart about cycles: an + arbitrary dependent dll will necessarily appear first. Perhaps not + surprisingly, Windows ships several dlls containing dependency + cycles, including SspiCli/RPCRT4.dll and a lovely tangle involving + USP10/LPK/GDI32/USER32.dll). Fortunately, we don't care about + Windows DLLs here, and cygwin dlls should behave better */ +void +dll_list::topsort_visit (dll* d, bool seek_tail) +{ + /* Recurse to the end of the dll chain, then visit nodes as we + unwind. We do this because once we start visiting nodes we can no + longer trust any _next_ pointers. + + We "mark" visited nodes (to avoid revisiting them) by negatin
Improvements to fork handling (3/5)
Hi all, This patch fixes a bug in the reserve_at function which caused it to sometimes reserve space needed by the dll it was supposed to help land. This happens when the dll tries to land in a free region which overlaps the desired location. The new code exploits the image introspection (patch #2) to get the dll's image size and avoids the corner cases. Ryan diff --git a/dll_init.cc b/dll_init.cc --- a/dll_init.cc +++ b/dll_init.cc @@ -164,6 +164,7 @@ dll_list::alloc (HINSTANCE h, per_proces d->handle = h; d->has_dtors = true; d->p = p; + d->image_size = ((pefile*)h)->optional_hdr ()->SizeOfImage; d->ndeps = 0; d->deps = NULL; d->modname = wcsrchr (d->name, L'\\'); @@ -407,21 +408,33 @@ release_upto (const PWCHAR name, DWORD h } } -/* Mark one page at "here" as reserved. This may force - Windows NT to load a DLL elsewhere. */ +/* Reserve the chunk of free address space starting _here_ and (usually) + covering at least _dll_size_ bytes. However, we must take care not + to clobber the dll's target address range because it often overlaps. + */ static DWORD -reserve_at (const PWCHAR name, DWORD here) +reserve_at (const PWCHAR name, DWORD here, DWORD dll_base, DWORD dll_size) { DWORD size; MEMORY_BASIC_INFORMATION mb; if (!VirtualQuery ((void *) here, &mb, sizeof (mb))) -size = 64 * 1024; - +api_fatal ("couldn't examine memory at %08lx while mapping %W, %E", + here, name); if (mb.State != MEM_FREE) return 0; size = mb.RegionSize; + + // don't clobber the space where we want the dll to land + DWORD end = here + size; + DWORD dll_end = dll_base + dll_size; + if (dll_base < here && dll_end > here) + here = dll_end; // the dll straddles our left edge + else if (dll_base >= here && dll_base < end) + end = dll_base; // the dll overlaps partly or fully to our right + + size = end - here; if (!VirtualAlloc ((void *) here, size, MEM_RESERVE, PAGE_NOACCESS)) api_fatal ("couldn't allocate memory %p(%d) for '%W' alignment, %E\n", here, size, name); @@ -499,7 +512,8 @@ dll_list::load_after_fork (HANDLE parent can in the child, due to differences in the load ordering. Block memory at it's preferred address and try again. */ if ((DWORD) h > (DWORD) d->handle) -preferred_block = reserve_at (d->name, (DWORD) h); +preferred_block = reserve_at (d->name, (DWORD) h, + (DWORD) d->handle, d->image_size); } } diff --git a/dll_init.h b/dll_init.h --- a/dll_init.h +++ b/dll_init.h @@ -52,6 +52,7 @@ struct dll int count; bool has_dtors; dll_type type; + DWORD image_size; long ndeps; dll** deps; PWCHAR modname; * dll_init.cc (dll_list::alloc): initialize dll::image_size. (reserve_at): no longer reserves space needed by the target dll if the latter overlaps the free region to be blocked. (dll_list::load_after_fork): use new version of reserve_at. * dll_init.h (struct dll): add new members to track dll size.
Improvements to fork handling (4/5)
Hi all, This patch rewrites dll_list::load_after fork. The new version eliminates reserve_upto() and release_upto(), which were expensive (the process repeats for each dll) and buggy (release_upto could free allocations reserve_upto did not make). Instead, the effect of reserve_upto is achieved by recursively attempting to load each dll in its proper place and calling reserve_at before retrying; each reservation's location is kept on the stack throughout and release_at calls are made only when the recursion unwinds after all dlls have loaded. Further, the code (again exploiting image introspection from patch #2) pre-reserves all space needed by each DLL_LOAD before starting the normal load process. This allows us to detect early whether Windows clobbered something from the start (allowing retry) and also ensures that the needed address space is not clobbered by later calls to reserve_at or by dlls allocating resources. Ryan diff --git a/dll_init.cc b/dll_init.cc --- a/dll_init.cc +++ b/dll_init.cc @@ -165,6 +165,7 @@ dll_list::alloc (HINSTANCE h, per_proces d->has_dtors = true; d->p = p; d->image_size = ((pefile*)h)->optional_hdr ()->SizeOfImage; + d->preferred_base = (void*) ((pefile*)h)->optional_hdr()->ImageBase; d->ndeps = 0; d->deps = NULL; d->modname = wcsrchr (d->name, L'\\'); @@ -357,56 +358,6 @@ dll_list::init () #define A64K (64 * 1024) -/* Mark every memory address up to "here" as reserved. This may force - Windows NT to load a DLL in the next available, lowest slot. */ -static void -reserve_upto (const PWCHAR name, DWORD here) -{ - DWORD size; - MEMORY_BASIC_INFORMATION mb; - for (DWORD start = 0x1; start < here; start += size) -if (!VirtualQuery ((void *) start, &mb, sizeof (mb))) - size = A64K; -else - { - size = A64K * ((mb.RegionSize + A64K - 1) / A64K); - start = A64K * (((DWORD) mb.BaseAddress + A64K - 1) / A64K); - - if (start + size > here) - size = here - start; - if (mb.State == MEM_FREE && - !VirtualAlloc ((void *) start, size, MEM_RESERVE, PAGE_NOACCESS)) - api_fatal ("couldn't allocate memory %p(%d) for '%W' alignment, %E\n", -start, size, name); - } -} - -/* Release all of the memory previously allocated by "upto" above. - Note that this may also free otherwise reserved memory. If that becomes - a problem, we'll have to keep track of the memory that we reserve above. */ -static void -release_upto (const PWCHAR name, DWORD here) -{ - DWORD size; - MEMORY_BASIC_INFORMATION mb; - for (DWORD start = 0x1; start < here; start += size) -if (!VirtualQuery ((void *) start, &mb, sizeof (mb))) - size = 64 * 1024; -else - { - size = mb.RegionSize; - if (!(mb.State == MEM_RESERVE && mb.AllocationProtect == PAGE_NOACCESS - && (((void *) start < cygheap->user_heap.base -|| (void *) start > cygheap->user_heap.top) -&& ((void *) start < (void *) cygheap -|| (void *) start - > (void *) ((char *) cygheap + CYGHEAPSIZE) - continue; - if (!VirtualFree ((void *) start, 0, MEM_RELEASE)) - api_fatal ("couldn't release memory %p(%d) for '%W' alignment, %E\n", -start, size, name); - } -} /* Reserve the chunk of free address space starting _here_ and (usually) covering at least _dll_size_ bytes. However, we must take care not @@ -450,72 +401,130 @@ release_at (const PWCHAR name, DWORD her here, name); } +/* Step 1: Reserve memory for all DLL_LOAD dlls. This is to prevent + anything else from taking their spot as we compensate for Windows + randomly relocating things. + + NOTE: because we can't depend on LoadLibraryExW to do the right + thing, we have to do a vanilla VirtualAlloc instead. One possible + optimization might attempt a LoadLibraryExW first, in case it lands + in the right place, but then we have to find a way of tracking + which dlls ended up needing VirtualAlloc after all. */ +void +dll_list::reserve_space () +{ + for (dll* d = dlls.istart (DLL_LOAD); d; d = dlls.inext ()) +if (!VirtualAlloc (d->handle, d->image_size, MEM_RESERVE, PAGE_NOACCESS)) + fork_api_fatal ("Address space needed by '%W' (%08lx) is already occupied", + d->modname, d->handle); +} + /* Reload DLLs after a fork. Iterates over the list of dynamically loaded DLLs and attempts to load them in the same place as they were loaded in the parent. */ void dll_list::load_after_fork (HANDLE parent) { - DWORD preferred_block = 0; + // moved to frok::child for performance reasons: + // dll_list::reserve_space(); + + load_after_fork_impl (parent, dlls.istart (DLL_LOAD), 0); +} - for (dll *d = &dlls.start; (d = d->next) != NULL; ) -if (d->type == DLL_LOAD) - for (int i = 0; i < 2; i++) +static i
Improvements to fork handling (5/5)
Hi all, This last patch adds a small optimization which reserves the lower 4MB of address space early in the process's lifetime (even if it's not a forkee). This was motivated by the observation that Windows tends to move things around a lot in that area, increasing the probability of future fork failures if the parent allows cygwin dlls to land there. The patch does not fully address the problem, however, because ASLR clobbers addresses above 4M as well. As a result, this patch may or may not improve fork success rates in practice: most fork failures for me involve DLL_LINK dlls which landed badly in the child. It should be independent of the other patches. Ryan diff --git a/dcrt0.cc b/dcrt0.cc --- a/dcrt0.cc +++ b/dcrt0.cc @@ -792,6 +792,13 @@ dll_crt0_1 (void *) main_vfork = vfork_storage.create (); #endif + /* Windows doesn't use the lower 4MB of address space consistently, + and those uses arise before cygwin1.dll loads. If a dll loads + there we risk being unable to fork later. To avoid the problem, + we just reserve everything that's left in that space -- windows + can still do what it wants since it got there first. */ + dlls.block_bad_address_space (); + cygbench ("pre-forkee"); if (in_forkee) { diff --git a/dll_init.cc b/dll_init.cc --- a/dll_init.cc +++ b/dll_init.cc @@ -359,6 +359,44 @@ dll_list::init () #define A64K (64 * 1024) +void +dll_list::block_bad_address_space () +{ + /* For some reason VirtualQuery doesn't return consistent values of + RegionSize for free space, so we have to compute it manually by + looking for MEM_FREE followed by a not-free region. We ensure not + to leave a danging free region by allowing the loop to examine + 0x0040, which is always the address of the application's + executable image. + */ + MEMORY_BASIC_INFORMATION mb; + DWORD here; + for (DWORD i=A64K; i <= 64*A64K; i += mb.RegionSize) +{ + if ( !VirtualQuery ((void*)i, &mb, sizeof(mb))) + api_fatal ("-> unable to examine address space at %08lx, %E", i); + here = (DWORD) mb.BaseAddress; + + /* this should never happen. If it does we'll need to write some +code to compensate for it */ + if (here != i) + api_fatal ("VirtualQuery returned info for %lx instead of %lx", + here, i); + if (mb.State == MEM_FREE) + { + DWORD size = mb.RegionSize; + if (!VirtualAlloc ((void*) here, size, MEM_RESERVE, PAGE_NOACCESS)) + system_printf ("-> couldn't block out %08lx, %E", here); + } + else if (mb.RegionSize & (A64K-1)) + { + /* skip free space at the end of mapped slices -- they can't +be used by anything else */ + mb.RegionSize = (mb.RegionSize + A64K - 1) & -A64K; + } +} +} + /* Reserve the chunk of free address space starting _here_ and (usually) covering at least _dll_size_ bytes. However, we must take care not to clobber the dll's target address range because it often overlaps. diff --git a/dll_init.h b/dll_init.h --- a/dll_init.h +++ b/dll_init.h @@ -83,6 +83,7 @@ public: int tot; int loaded_dlls; int reload_on_fork; + void block_bad_address_space (); dll *operator [] (const PWCHAR name); dll *alloc (HINSTANCE, per_process *, dll_type); dll *find (void *); * dcrt0.cc (dll_crt0_1): call dll_list::block_bad_address_space * dll_init.cc (dll_list::block_bad_address_space): new function to reserve all free space in the low 4MB. Reduces somewhat the probability of a dynamic dll clashing with windows heaps or thread stacks. * dll_init.h (struct dll_list): declaration for above.
Re: Improvements to fork handling
On 11/05/2011 12:16 PM, Christopher Faylor wrote: On Wed, May 11, 2011 at 10:21:14AM -0400, Ryan Johnson wrote: On 11/05/2011 10:13 AM, Christopher Faylor wrote: On Wed, May 11, 2011 at 09:59:53AM +0200, Corinna Vinschen wrote: On May 11 02:18, Ryan Johnson wrote: Please find attached five patches [...] Oops, wrong mailing list... Btw., it would be nice if you could create patches with the diff -p flag as well. It's not exactly essential, but IMHO it's quite a help when trying to review patches. Another problem is this: While you provide separate patches, you don't provide separate ChangeLogs. That makes it kind of hard to apply them separately. Would you mind to create one ChangeLog per change? Ditto. This really needs to be broken down into easier to review chunks. All right. Let's try this again with the correct mailing list. The patches have been generated with diff -p, and each includes appropriate changelog entries. Hopefully the changes are split up finely enough because I don't know a good way to break them down any further. For posterity's sake I'm including the original message body below. Please: One patch per message. One ChangeLog entry per message, not sent as a diff. Done. Please disregard the older (bundled) versions because I found a couple of glitches in the patches which the the 1-per-email set fixes. Ryan
Re: Extending /proc/*/maps
On May 11 13:46, Ryan Johnson wrote: > On 11/05/2011 7:14 AM, Corinna Vinschen wrote: > >On May 11 01:27, Ryan Johnson wrote: > >>The second (proc-maps-heaps) adds reporting of Windows heaps (or > >>their bases, at least). Unfortunately there doesn't seem to be any > >>efficient way to identify all virtual allocations which a heap owns. > >There's a call RtlQueryDebugInformation which can fetch detailed heap > >information, and which is used by Heap32First/Heap32Last. Using it > >directly is much more efficient than using the Heap32 functions. The > >DEBUG_HEAP_INFORMATION is already in ntdll.h, what's missing is the > >layout of the Blocks info. I found the info by googling: > > > > typedef struct _HEAP_BLOCK > > { > > PVOID addr; > > ULONG size; > > ULONG flags; > > ULONG unknown; > > } HEAP_BLOCK, *PHEAP_BLOCK; > > > >If this information is searched until the address falls into the just > >inspected block of virtual memory, then we would have the information, > >isn't it? > The problem is that the reported heap blocks corresponded to > individual malloc() calls when I tested it (unordered list, most > sizes < 100B). I really would have preferred a function that returns > the list of memory regions owned by the heap. They must track that > information -- I highly HeapDestroy uses these heap blocks to decide > which memory regions to VirtualFree, but that info seems to live in > the kernel... No, it doesn't. The heap allocation functions are implemented entirely in user space. Only virtual memory and shared sections are maintained by the kernel. Looking into the MSDN man page for HeapCreate, I'm wondering if this is really complicated. HeapCreate just allocates a block of memory using VirtualAlloc. Either the Heap is fixed size, then all allocations are within the given heap memory area, or the heap can grow, then subsequent (or big-block) allocations can result in another VirtualAlloc. Having said that, I don't know where this information is stored exactly, but it must be available in the DEBUG_HEAP_INFORMATION block. I guess a bit of experimenting is asked for. > Given that Heap32* has already been reverse-engineered by others, > the main challenge would involve sorting the set of heap block > addresses and distilling them down to a set of allocation bases. We > don't want to do repeated linear searches over 50k+ heap blocks. While the base address of the heap is available in DEBUG_HEAP_INFORMATION, I don't see the size of the heap. Maybe it's in the block of 7 ULONGs marked as "Reserved"? It must be somewhere. Assuming just that, you could scan the list of blocks once and drop those within the orignal heap allocation. The remaining blocks are big blocks which have been allocated by additional calls to VirtualAlloc. However, there's a good chance that the entire information about blocks allocated with VirtualAlloc is kept in the heap's own datastructures which, again, are undocumented. I would assume the start of that heap info is at the heaps base address, but without more information about the internal structure... > Also, the cygheap isn't a normal windows heap, is it? I thought it > was essentially a statically-allocated array (.cygheap) that gets > managed as a heap. I guess since it's part of cygwin1.dll we already > do sort of report it... The cygheap is the last section in the DLL and gets allocated by the Windows loader. The memory management is entirely in Cygwin (cygheap.cc). Cygwin can raise the size of the cygheap, but only if the blocks right after the existing cygheap are not already allocated. Corinna -- Corinna Vinschen Please, send mails regarding Cygwin to Cygwin Project Co-Leader cygwin AT cygwin DOT com Red Hat
Re: [PATCH] Fix /proc/meminfo and /proc/swaps for >4GB
On Mon, 2011-05-09 at 09:55 +0200, Corinna Vinschen wrote: > I'm not sure I understand this new format. Why do you keep the Mem: and > Swap: lines? Linux doesn't have them and top appears to work without > them. And then, why do you print MemShared, HighTotal, and HighFree, > even though they are always 0, but not all the other ~40 lines Linux' > meminfo has, too? Actually, my patch makes no attempt to change the actual format of /proc/meminfo; it changes only what is necessary to handle RAM or swap larger than 4GB by using ULLs instead of ULs. As for modernizing/fixing the format, true, the Mem: and Swap: lines do not exist in modern Linux, nor does the MemShared: line. I would like to actually define at least HighTotal and HighFree; I'll try to look into that further soon. As for the rest of Linux's /proc/meminfo, I'll have to see how many other lines can be reasonably determined (if they would exist at all) on Windows. So with the ULL changes, if I remove the Mem, Swap, and MemShared lines, will that do for now? Yaakov
Re: [PATCH] Fix /proc/meminfo and /proc/swaps for >4GB
On May 11 22:58, Yaakov (Cygwin/X) wrote: > On Mon, 2011-05-09 at 09:55 +0200, Corinna Vinschen wrote: > > I'm not sure I understand this new format. Why do you keep the Mem: and > > Swap: lines? Linux doesn't have them and top appears to work without > > them. And then, why do you print MemShared, HighTotal, and HighFree, > > even though they are always 0, but not all the other ~40 lines Linux' > > meminfo has, too? > > Actually, my patch makes no attempt to change the actual format > of /proc/meminfo; it changes only what is necessary to handle RAM or > swap larger than 4GB by using ULLs instead of ULs. That's fine and thanks for that. I'm only puzzled what's actually printed. > As for modernizing/fixing the format, true, the Mem: and Swap: lines do > not exist in modern Linux, nor does the MemShared: line. I would like > to actually define at least HighTotal and HighFree; I'll try to look > into that further soon. As for the rest of Linux's /proc/meminfo, I'll > have to see how many other lines can be reasonably determined (if they > would exist at all) on Windows. That would be cool, but that wasn't what I meant. I was just puzzled that you added values which are always 0, but left out others which are always 0, too. I was missing a system, kind of. > So with the ULL changes, if I remove the Mem, Swap, and MemShared lines, > will that do for now? Sure. Thanks, Corinna -- Corinna Vinschen Please, send mails regarding Cygwin to Cygwin Project Co-Leader cygwin AT cygwin DOT com Red Hat