Deal more correctly with process termination politics.
authorRyan C. Gordon <icculus@icculus.org>
Fri, 28 Oct 2016 02:32:05 -0400
changeset 70 118c53b69851
parent 69 835e691574f0
child 71 1a6a5ecb59de
Deal more correctly with process termination politics.

Now shared libraries run their termination entry points and deallocate,
the DosExitList functions can run, we free our stack selectors, etc.
lx_loader.c
lx_loader.h
native/doscalls.c
--- a/lx_loader.c	Thu Oct 27 11:19:24 2016 -0400
+++ b/lx_loader.c	Fri Oct 28 02:32:05 2016 -0400
@@ -463,7 +463,7 @@
 //  pray that covers it. If we have to tile _every_ thread's stack, we can do
 //  that later.
 // !!! FIXME: if we do this for secondary thread stacks, we'll need to mutex this.
-static void initOs2StackSegments(uint32 addr, uint32 stacklen)
+static void initOs2StackSegments(uint32 addr, uint32 stacklen, const int deinit)
 {
     //printf("base == %p, stacklen == %u\n", (void*)addr, (uint) stacklen);
     const uint32 diff = addr % 0x10000;
@@ -477,9 +477,13 @@
     // !!! FIXME: do we have to allocate these backwards? (stack grows down).
     while (stacklen) {
         //printf("Allocating selector 0x%X for stack %p ... \n", (uint) (addr >> 16), (void *) addr);
-        if (!allocateSelector((uint16) (addr >> 16), addr, MODIFY_LDT_CONTENTS_DATA/*STACK*/, 0)) {
-            FIXME("uhoh, couldn't set up an LDT entry for a stack segment! Might crash later!");
-        } // if
+        if (deinit) {
+            freeSelector((uint16) (addr >> 16));
+        } else {
+            if (!allocateSelector((uint16) (addr >> 16), addr, MODIFY_LDT_CONTENTS_DATA/*STACK*/, 0)) {
+                FIXME("uhoh, couldn't set up an LDT entry for a stack segment! Might crash later!");
+            } // if
+        } // else
         stacklen -= 0x10000;
         addr += 0x10000;
     } // while
@@ -528,9 +532,9 @@
     const long rc = syscall(SYS_set_thread_area, &entry);
     assert(rc == 0);  FIXME("this can legit fail, though!");
 
-    // I have no idea why this needs the "<< 3 | 3", but it's probably
-    //  specified in the Intel manuals. I got this from looking at how
-    //  Wine does it. I still don't know why.
+    // The "<< 3 | 3" makes this a GDT selector at ring 3 permissions.
+    //  If this did "| 7" instead of "| 3", it'd be an LDT selector.
+    //  Use findSelector() or allocateSelector() for LDT entries, though!
     const unsigned int segment = (entry.entry_number << 3) | 3;
     __asm__ __volatile__ ( "movw %%ax, %%fs  \n\t" : : "a" (segment) );
     return (uint16) entry.entry_number;
@@ -549,17 +553,47 @@
     assert(rc == 0);  FIXME("this can legit fail, though!");
 } // deinitOs2Tib
 
+static void freeLxModule(LxModule *lxmod);
+
+static __attribute__((noreturn)) void terminate(const uint32 exitcode)
+{
+    freeLxModule(GLoaderState->main_module);
+
+    // clear out anything that is still loaded...
+    while (GLoaderState->loaded_modules) {
+        LxModule *lowest_mod = GLoaderState->loaded_modules;
+        for (LxModule *i = GLoaderState->loaded_modules; i; i = i->next) {
+            if (i->refcount < lowest_mod->refcount)
+                lowest_mod = i;
+        } // for
+        freeLxModule(lowest_mod);
+    } // while
+
+    // OS/2's docs say this only keeps the lower 16 bits of exitcode.
+    // !!! FIXME: ...but Unix only keeps the lowest 8 bits. Will have to
+    // !!! FIXME:  tapdance to pass larger values back to OS/2 parent processes.
+    if (exitcode > 255)
+        FIXME("deal with process exit codes > 255. We clamped this one!");
+
+    _exit((int) (exitcode & 0xFF));
+} // terminate
+
+static __attribute__((noreturn)) void endLxProcess(const uint32 exitcode)
+{
+    if (GLoaderState->dosExit)
+        GLoaderState->dosExit(1, exitcode);  // let exit lists run. Should call terminate!
+    terminate(exitcode);  // just in case.
+} // endLxProcess
 
 static void missingEntryPointCalled(const char *module, const char *entry)
 {
     fflush(stdout);
     fflush(stderr);
-    fprintf(stderr, "\n\nMissing entry point '%s' in module '%s'!\n", entry, module);
-    fprintf(stderr, "Aborting.\n\n\n");
+    fprintf(stderr, "\n\nMissing entry point '%s' in module '%s'! Aborting.\n\n\n", entry, module);
     //STUBBED("output backtrace");
     fflush(stderr);
-    _exit(1);
-} // missing_ordinal_called
+    terminate(1);
+} // missingEntryPointCalled
 
 static void *generateMissingTrampoline(const char *_module, const char *_entry)
 {
@@ -719,7 +753,7 @@
     uint8 *stack = (uint8 *) ((size_t) lxmod->esp);
 
     // ...and you pass it the pointer to argv0. This is (at least as far as the docs suggest) appended to the environment table.
-    fprintf(stderr, "jumping into LX land for exe '%s'...! eip=%p esp=%p\n", lxmod->name, (void *) lxmod->eip, stack); fflush(stderr);
+    //fprintf(stderr, "jumping into LX land for exe '%s'...! eip=%p esp=%p\n", lxmod->name, (void *) lxmod->eip, stack); fflush(stderr);
 
     GLoaderState->running = 1;
 
@@ -741,11 +775,10 @@
         "xorl %%ebp,%%ebp  \n\t"
         "ret               \n\t"  // go to OS/2 land!
         "1:                \n\t"  //  ...and return here.
-        "andl $-16, %%esp  \n\t"  // align the stack for macOS.
-        "subl $-4, %%esp   \n\t"   // align the stack for macOS.
-        "pushl %%eax       \n\t"  // call _exit() with whatever is in %eax.
-        "call _exit        \n\t"
-            : // no outputs
+        "pushl %%eax       \n\t"  // push exit code from OS/2 app.
+        "call endLxProcess \n\t"  // never returns.
+        // If we returned here, %eax has the exit code from the app.
+            : // no outputs.
             : "a" (GLoaderState->pib.pib_pchcmd),
               "c" (GLoaderState->pib.pib_pchenv),
               "d" (lxmod), "S" (stack), "D" (lxmod->eip)
@@ -763,7 +796,7 @@
     if (!GLoaderState->running)
         stack = (uint8 *) ((size_t) GLoaderState->main_module->esp);
 
-    fprintf(stderr, "jumping into LX land to %s library '%s'...! eip=%p esp=%p\n", isTermination ? "terminate" : "initialize", lxmod->name, (void *) lxmod->eip, stack); fflush(stderr);
+    //fprintf(stderr, "jumping into LX land to %s library '%s'...! eip=%p esp=%p\n", isTermination ? "terminate" : "initialize", lxmod->name, (void *) lxmod->eip, stack); fflush(stderr);
 
     __asm__ __volatile__ (
         "pushal            \n\t"  // save all the current registers.
@@ -797,7 +830,7 @@
             : "memory"
     );
 
-    fprintf(stderr, "...survived time in LX land!\n"); fflush(stderr);
+    //fprintf(stderr, "...survived time in LX land!\n"); fflush(stderr);
 
     // !!! FIXME: this entry point returns a result...do we abort if it reports error?
     // !!! FIXME: (actually, DosLoadModule() can report that failure. Abort if (GLoaderState->running == 0), though!)
@@ -835,11 +868,11 @@
 
     // !!! FIXME: mutex from here
     lxmod->refcount--;
-    fprintf(stderr, "unref'd module '%s' to %u\n", lxmod->name, (uint) lxmod->refcount);
+    //fprintf(stderr, "unref'd module '%s' to %u\n", lxmod->name, (uint) lxmod->refcount);
     if (lxmod->refcount > 0)
         return;  // something is still using it.
 
-    if (lxmod->initialized) {
+    if ((lxmod->initialized) && (lxmod != GLoaderState->main_module)) {
         if (!lxmod->nativelib) {
             runLxLibraryTerm(lxmod);
         } else {
@@ -858,18 +891,23 @@
     if (lxmod == GLoaderState->loaded_modules)
         GLoaderState->loaded_modules = lxmod->next;
 
-    if (GLoaderState->main_module == lxmod)
+    if (GLoaderState->main_module == lxmod) {
         GLoaderState->main_module = NULL;
+        LxMmaps *lxmmap = &lxmod->mmaps[lxmod->lx.esp_object - 1];
+        const uint32 stackbase = (uint32) ((size_t)lxmmap->addr);
+        initOs2StackSegments(stackbase, lxmmap->size, 1);
+        lxmmap->mapped = NULL;  // don't unmap the stack we're probably using!
+    } // if
     // !!! FIXME: mutex to here
 
-    for (uint32 i = 0; i < lxmod->lx.num_import_mod_entries; i++)
-        freeLxModule(lxmod->dependencies[i]);
+    for (uint32 i = lxmod->lx.num_import_mod_entries; i > 0; i--)
+        freeLxModule(lxmod->dependencies[i-1]);
     free(lxmod->dependencies);
 
     for (uint32 i = 0; i < lxmod->lx.module_num_objects; i++) {
         if (lxmod->mmaps[i].alias != 0xFFFF)
             freeSelector(lxmod->mmaps[i].alias);
-        if (lxmod->mmaps[i].addr)
+        if (lxmod->mmaps[i].mapped)
             munmap(lxmod->mmaps[i].mapped, lxmod->mmaps[i].size);
     } // for
     free(lxmod->mmaps);    
@@ -1421,13 +1459,15 @@
     // !!! FIXME: "A zero value in this field indicates that the stack pointer is to be initialized to the highest address/offset in the object"
     if (!isDLL) {
         assert(lx->esp_object != 0);
-        const uint32 stackbase = (uint32) ((size_t)retval->mmaps[lx->esp_object - 1].addr);
+        const LxMmaps *lxmmap = &retval->mmaps[lx->esp_object - 1];
+        const uint32 stackbase = (uint32) ((size_t)lxmmap->addr);
+        const uint32 stacksize = (uint32) lxmmap->size;
         retval->esp = lx->esp + stackbase;
 
         // This needs to be set up now, so it's available to any library
         //  init code that runs in LX land.
-        initOs2Tib(GLoaderState->main_tibspace, (void *) ((size_t) retval->esp), retval->mmaps[retval->lx.esp_object - 1].size, 0);
-        initOs2StackSegments(stackbase, retval->mmaps[retval->lx.esp_object - 1].size);
+        initOs2Tib(GLoaderState->main_tibspace, (void *) ((size_t) retval->esp), stacksize, 0);
+        initOs2StackSegments(stackbase, stacksize, 0);
     } // if
 
     // now we have the stack tiled--and it got the specific selectors it
@@ -2030,6 +2070,7 @@
     GLoaderState->locatePathCaseInsensitive = locatePathCaseInsensitive;
     GLoaderState->makeUnixPath = makeUnixPath;
     GLoaderState->makeOS2Path = makeOS2Path;
+    GLoaderState->terminate = terminate;
 
     const char *modulename = GLoaderState->subprocess ? envr : argv[1];
 
--- a/lx_loader.h	Thu Oct 27 11:19:24 2016 -0400
+++ b/lx_loader.h	Fri Oct 28 02:32:05 2016 -0400
@@ -220,6 +220,7 @@
     uint32 *tlspage;
     uint32 tlsmask;  // one bit for each TLS slot in use.
     uint8 tlsallocs[32];  // number of TLS slots allocated in one block, indexed by starting block (zero if not the starting block).
+    void (*dosExit)(uint32 action, uint32 result);
     uint16 (*initOs2Tib)(uint8 *tibspace, void *_topOfStack, const size_t stacklen, const uint32 tid);
     void (*deinitOs2Tib)(const uint16 selector);
     int (*findSelector)(const uint32 addr, uint16 *outselector, uint16 *outoffset);
@@ -229,6 +230,7 @@
     int (*locatePathCaseInsensitive)(char *buf);
     char *(*makeUnixPath)(const char *os2path, uint32 *err);
     char *(*makeOS2Path)(const char *fname);
+    void __attribute__((noreturn)) (*terminate)(const uint32 exitcode);
 } LxLoaderState;
 
 typedef const LxExport *(*LxNativeModuleInitEntryPoint)(LxLoaderState *lx_state, uint32 *lx_num_exports);
--- a/native/doscalls.c	Thu Oct 27 11:19:24 2016 -0400
+++ b/native/doscalls.c	Fri Oct 28 02:32:05 2016 -0400
@@ -118,6 +118,8 @@
 
 
 LX_NATIVE_MODULE_DEINIT({
+    GLoaderState->dosExit = NULL;
+
     ExitListItem *next = GExitList;
     GExitList = NULL;
 
@@ -139,6 +141,8 @@
 
 static int initDoscalls(void)
 {
+    GLoaderState->dosExit = DosExit;
+
     if (pthread_mutex_init(&GMutexDosCalls, NULL) == -1) {
         fprintf(stderr, "pthread_mutex_init failed!\n");
         return 0;
@@ -465,15 +469,7 @@
     // terminate the process.
     runDosExitList(TC_EXIT);
 
-    // !!! FIXME: finalize OS/2 DLLs before killing the process?
-
-    // OS/2's docs say this only keeps the lower 16 bits of exitcode.
-    // !!! FIXME: ...but Unix only keeps the lowest 8 bits. Will have to
-    // !!! FIXME:  tapdance to pass larger values back to OS/2 parent processes.
-    if (exitcode > 255)
-        FIXME("deal with process exit codes > 255. We clamped this one!");
-
-    _exit((int) (exitcode & 0xFF));
+    GLoaderState->terminate(exitcode);
 } // DosExit
 
 APIRET DosExitList(ULONG ordercode, PFNEXITLIST fn)