Bunch of work on getting java.exe to start up. Not there yet.
authorRyan C. Gordon <icculus@icculus.org>
Thu, 27 Oct 2016 03:20:02 -0400
changeset 68 703acbf2086b
parent 67 8bcb1be36798
child 69 835e691574f0
Bunch of work on getting java.exe to start up. Not there yet.

Lots of fixes, improvements, and a few more APIs implemented.
lx_loader.c
lx_loader.h
native/doscalls.c
native/doscalls.h
native/os2native.h
native/os2native16.h
native/viocalls.c
native/viocalls.h
--- a/lx_loader.c	Thu Oct 27 03:17:23 2016 -0400
+++ b/lx_loader.c	Thu Oct 27 03:20:02 2016 -0400
@@ -1470,10 +1470,18 @@
                 *(ptr++) = 0x90; // nop
         } // if
 
+        // !!! FIXME: hack for Java that fails to mark a code page as executable
+        // !!! FIXME:  (on 386 machines, read implies execute, so this error could go unnoticed).
+        uint32 object_flags = obj->object_flags;
+        if ((i == 4) && (strcasecmp(modname, "HPI") == 0)) {
+            fprintf(stderr, "fixed page permissions to workaround bug in Java DLL\n");
+            object_flags |= 0x4;
+        } // if
+
         // Now set all the pages of this object to the proper final permissions...
-        const int prot = ((obj->object_flags & 0x1) ? PROT_READ : 0) |
-                         ((obj->object_flags & 0x2) ? PROT_WRITE : 0) |
-                         ((obj->object_flags & 0x4) ? PROT_EXEC : 0);
+        const int prot = ((object_flags & 0x1) ? PROT_READ : 0) |
+                         ((object_flags & 0x2) ? PROT_WRITE : 0) |
+                         ((object_flags & 0x4) ? PROT_EXEC : 0);
 
         if (mprotect(retval->mmaps[i].mapped, retval->mmaps[i].size, prot) == -1) {
             fprintf(stderr, "mprotect(%p, %u, %s%s%s, ANON|PRIVATE|FIXED, -1, 0) failed (%d): %s\n",
@@ -1692,12 +1700,15 @@
         exit(1);
     } // if
 
+    const char *libpath = NULL;
     char *ptr = env;
     for (int i = 0; envp[i]; i++) {
         const char *str = envp[i];
         if (strncmp(str, "PATH=", 5) == 0) {
             if (!GLoaderState->subprocess)
                 str = default_os2path;
+        } else if (strncmp(str, "LIBPATH=", 8) == 0) {
+            libpath = str + 8;
         } else if (strncmp(str, "IS_2INE=", 8) == 0) {
             continue;
         } // if
@@ -1707,6 +1718,15 @@
     }
     *(ptr++) = '\0';
 
+    // we keep a copy of LIBPATH for undocumented API
+    //  DosQueryHandleInfo(..., QHINF_LIBPATH, ...). I guess in case the app
+    //  has changed the environment var after start? Java uses this.
+    if (libpath == NULL)
+        libpath = "";
+
+    GLoaderState->libpath = strdup(libpath);
+    GLoaderState->libpathlen = strlen(libpath) + 1;
+
     // put the exe name between the environment and the command line.
     strcpy(ptr, argv[0]);
     ptr += strlen(argv[0]) + 1;
@@ -1758,6 +1778,23 @@
     return loadLxModuleByModuleNameInternal(modname, 0);
 } // loadLxModuleByModuleName
 
+static LxModule *loadLxModuleByPathOrModuleName(const char *modname)
+{
+    LxModule *retval = NULL;
+    // !!! FIXME: can a module name be specified as "NAME.DLL" and not be considered a filename?
+    if (strrchr(modname, '.') == NULL) {
+        retval = loadLxModuleByModuleName(modname);
+    } else {
+        uint32 err = 0;
+        char *path = makeUnixPath(modname, &err);
+        if (!path)
+            return NULL;
+        retval = loadLxModuleByPath(path);
+        free(path);
+    } // else
+    return retval;
+} // loadLxModuleByPathOrModuleName
+
 static __attribute__((noreturn)) void handleThreadLocalStorageAccess(const int slot, ucontext_t *uctx)
 {
     greg_t *gregs = uctx->uc_mcontext.gregs;
@@ -1775,7 +1812,7 @@
     uint32 *tls = (uint32 *) (ptib2 + 1);  // The thread's TLS data is stored right after its TIB2 struct.
     tls += slot;
 
-    printf("We wanted to access thread %p TLS slot %d (currently holds %u)\n", tls - slot, slot, (uint) *tls);
+    //printf("We wanted to access thread %p TLS slot %d (currently holds %u)\n", tls - slot, slot, (uint) *tls);
 
     static const int x86RegisterToUContextEnum[] = {
         REG_EAX, REG_ECX, REG_EDX, REG_EBX, REG_ESP, REG_EBP, REG_ESI, REG_EDI
@@ -1786,19 +1823,19 @@
     uint8 *eip = (void *) (size_t) gregs[REG_EIP];  // program counter at point of segfault.
     switch (eip[0]) {
         case 0xC7:  // mov imm16/32 -> r/m
-            printf("setting TLS slot %d to imm %u.\n", slot, (uint) *((uint32 *) (eip + 2)));
+            //printf("setting TLS slot %d to imm %u.\n", slot, (uint) *((uint32 *) (eip + 2)));
             *tls = *((uint32 *) (eip + 2));  // !!! FIXME: verify it's a imm32, not 16
             gregs[REG_EIP] += 6;
             break;
 
         case 0x89:  // mov r -> r/m
-            printf("setting TLS slot %d to reg %d (%u).\n", slot, x86RegisterToUContextEnum[eip[1] >> 3], (uint) gregs[x86RegisterToUContextEnum[eip[1] >> 3]]);
+            //printf("setting TLS slot %d to reg %d (%u).\n", slot, x86RegisterToUContextEnum[eip[1] >> 3], (uint) gregs[x86RegisterToUContextEnum[eip[1] >> 3]]);
             *tls = (uint32) gregs[x86RegisterToUContextEnum[eip[1] >> 3]];
             gregs[REG_EIP] += 2;
             break;
 
         case 0x8B:  // mov r/m -> r
-            printf("setting to reg %d to TLS slot %d (%u).\n", (uint) x86RegisterToUContextEnum[eip[1] >> 3], slot, *tls);
+            //printf("setting reg %d to TLS slot %d (%u).\n", (uint) x86RegisterToUContextEnum[eip[1] >> 3], slot, *tls);
             gregs[x86RegisterToUContextEnum[eip[1] >> 3]] = (greg_t) *tls;
             gregs[REG_EIP] += 2;
             break;
@@ -1813,7 +1850,7 @@
     } else {
         // drop out of signal handler to (hopefully) next instruction in the app,
         //  as if it accessed the TLS slot normally and none of this ever happened.
-        printf("TLS access handler jumping back into app at %p...\n", (void *) gregs[REG_EIP]); fflush(stdout);
+        //printf("TLS access handler jumping back into app at %p...\n", (void *) gregs[REG_EIP]); fflush(stdout);
         setcontext(uctx);
         fprintf(stderr, "panic: setcontext() failed in the TLS access handler! Aborting! (%s)\n", strerror(errno));
     } // else
@@ -1841,6 +1878,7 @@
         // !!! FIXME: case #1 should be a much more detailed crash dump.
         case 1: fprintf(stderr, "SIGSEGV at addr=%p (eip=%p)\n", addr, (void *) ((ucontext_t *) ctx)->uc_mcontext.gregs[REG_EIP]); break;
         case 2: write(2, "SIGSEGV, aborting.\n", 19); break;
+        default: break;
     } // switch
 
     abort();  // cash out.
@@ -1855,7 +1893,7 @@
     action.sa_flags = SA_NODEFER | SA_SIGINFO;
 
     if (sigaction(SIGSEGV, &action, NULL) == -1) {
-        fprintf(stderr, "Couldn't install signal handler! (%s)\n", strerror(errno));
+        fprintf(stderr, "Couldn't install SIGSEGV handler! (%s)\n", strerror(errno));
         return 0;
     } // if
 
@@ -1890,7 +1928,7 @@
     GLoaderState->findSelector = findSelector;
     GLoaderState->freeSelector = freeSelector;
     GLoaderState->convert1616to32 = convert1616to32;
-    GLoaderState->loadModule = loadLxModuleByModuleName;
+    GLoaderState->loadModule = loadLxModuleByPathOrModuleName;
     GLoaderState->locatePathCaseInsensitive = locatePathCaseInsensitive;
     GLoaderState->makeUnixPath = makeUnixPath;
     GLoaderState->makeOS2Path = makeOS2Path;
--- a/lx_loader.h	Thu Oct 27 03:17:23 2016 -0400
+++ b/lx_loader.h	Thu Oct 27 03:20:02 2016 -0400
@@ -215,6 +215,8 @@
     uint16 original_ss;
     uint16 original_ds;
     uint32 ldt[8192];
+    char *libpath;
+    uint32 libpathlen;
     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).
--- a/native/doscalls.c	Thu Oct 27 03:17:23 2016 -0400
+++ b/native/doscalls.c	Thu Oct 27 03:20:02 2016 -0400
@@ -14,7 +14,28 @@
 #include <sys/resource.h>
 #include <sys/stat.h>
 
-static LxLoaderState *GLoaderState = NULL;
+// DosQueryHeaderInfo() is an undocumented OS/2 API that is exported from DOSCALLS. Java uses it.
+//  This isn't mentioned in the docs or the SDK (beyond the ordinal being listed). I got the basic details
+//  from Odin32, which lists it in their os2newapi.h header.
+enum
+{
+    QHINF_EXEINFO = 1,
+    QHINF_READRSRCTBL,
+    QHINF_READFILE,
+    QHINF_LIBPATHLENGTH,
+    QHINF_LIBPATH,
+    QHINF_FIXENTRY,
+    QHINF_STE,
+    QHINF_MAPSEL
+};
+APIRET OS2API DosQueryHeaderInfo(HMODULE hmod, ULONG ulIndex, PVOID pvBuffer, ULONG cbBuffer, ULONG ulSubFunction);
+
+// This is also undocumented (thanks, EDM/2!). Of course, Java uses it.
+APIRET OS2API DosQuerySysState(ULONG func, ULONG arg1, ULONG pid, ULONG _res_, PVOID buf, ULONG bufsz);
+
+// This is also undocumented (no idea about this at all, including function params). Of course, Java uses it.
+APIRET DosR3ExitAddr(void);
+
 
 static pthread_mutex_t GMutexDosCalls;
 
@@ -54,9 +75,10 @@
 
 typedef struct HFileInfo
 {
-    int fd;
-    ULONG type;
-    ULONG attr;
+    int fd;       // unix file descriptor.
+    ULONG type;   // file, pipe, device, etc.
+    ULONG attr;   // for character devices, DAW_*
+    ULONG flags;  // OPEN_FLAGS_*
 } HFileInfo;
 
 static HFileInfo *HFiles = NULL;
@@ -95,11 +117,6 @@
 static DirFinder GHDir1;
 
 
-// !!! FIXME:
-#undef TRACE_NATIVE
-#define TRACE_NATIVE(...) do { if (GLoaderState->trace_native) { fprintf(stderr, "2INE TRACE [%lu]: ", (unsigned long) pthread_self()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, ";\n"); } } while (0)
-
-
 LX_NATIVE_MODULE_DEINIT({
     ExitListItem *next = GExitList;
     GExitList = NULL;
@@ -118,14 +135,10 @@
     MaxHFiles = 0;
 
     pthread_mutex_destroy(&GMutexDosCalls);
-
-    GLoaderState = NULL;
 })
 
-static int initDoscalls(LxLoaderState *lx_state)
+static int initDoscalls(void)
 {
-    GLoaderState = lx_state;
-
     if (pthread_mutex_init(&GMutexDosCalls, NULL) == -1) {
         fprintf(stderr, "pthread_mutex_init failed!\n");
         return 0;
@@ -143,6 +156,7 @@
         info->fd = -1;
         info->type = 0;
         info->attr = 0;
+        info->flags = 0;
     } // for
 
     // launching a Hello World program from CMD.EXE seems to inherit several
@@ -164,7 +178,8 @@
     return 1;
 } // initDoscalls
 
-LX_NATIVE_MODULE_INIT({ if (!initDoscalls(lx_state)) return NULL; })
+LX_NATIVE_MODULE_INIT({ if (!initDoscalls()) return NULL; })
+    LX_NATIVE_EXPORT(DosSetMaxFH, 209),
     LX_NATIVE_EXPORT(DosSetPathInfo, 219),
     LX_NATIVE_EXPORT(DosQueryPathInfo, 223),
     LX_NATIVE_EXPORT(DosQueryHType, 224),
@@ -183,6 +198,7 @@
     LX_NATIVE_EXPORT(DosOpen, 273),
     LX_NATIVE_EXPORT(DosQueryCurrentDir, 274),
     LX_NATIVE_EXPORT(DosQueryCurrentDisk, 275),
+    LX_NATIVE_EXPORT(DosQueryFHState, 276),
     LX_NATIVE_EXPORT(DosQueryFileInfo, 279),
     LX_NATIVE_EXPORT(DosWaitChild, 280),
     LX_NATIVE_EXPORT(DosRead, 281),
@@ -214,6 +230,7 @@
     LX_NATIVE_EXPORT(DosSubFreeMem, 346),
     LX_NATIVE_EXPORT(DosQuerySysInfo, 348),
     LX_NATIVE_EXPORT(DosSetExceptionHandler, 354),
+    LX_NATIVE_EXPORT(DosQuerySysState, 368),
     LX_NATIVE_EXPORT(DosSetSignalExceptionFocus, 378),
     LX_NATIVE_EXPORT(DosEnterMustComplete, 380),
     LX_NATIVE_EXPORT(DosExitMustComplete, 381),
@@ -221,6 +238,10 @@
     LX_NATIVE_EXPORT(DosFlatToSel, 425),
     LX_NATIVE_EXPORT(DosAllocThreadLocalMemory, 454),
     LX_NATIVE_EXPORT(DosFreeThreadLocalMemory, 455),
+    LX_NATIVE_EXPORT(DosR3ExitAddr, 553),
+    LX_NATIVE_EXPORT(DosQueryHeaderInfo, 582),
+    LX_NATIVE_EXPORT(DosQueryExtLIBPATH, 874),
+    LX_NATIVE_EXPORT(DosQueryThreadContext, 877),
     LX_NATIVE_EXPORT(DosOpenL, 981)
 LX_NATIVE_MODULE_INIT_END()
 
@@ -364,7 +385,7 @@
 
     const LxModule *lxmod = (LxModule *) hmod;
     // !!! FIXME: error 6 ERROR_INVALID_HANDLE
-    if (strlen(lxmod->os2path) <= buflen)
+    if (strlen(lxmod->os2path) >= buflen)
         return ERROR_BAD_LENGTH;
     strcpy(buf, lxmod->os2path);
     return NO_ERROR;
@@ -683,6 +704,7 @@
                     info->fd = -1;
                     info->type = 0;
                     info->attr = 0;
+                    info->flags = 0;
                 } // for
                 MaxHFiles += incr;
             } // if
@@ -955,6 +977,8 @@
         __builtin_unreachable();
     } // if
 
+    info->flags = fsOpenFlags;
+
     if (isReplacing)
         *pulAction = FILE_TRUNCATED;
     else if (existed)
@@ -1244,6 +1268,12 @@
         case FIL_STANDARD: return queryPathInfoStandard(unixPath, pInfoBuf, cbInfoBuf);
         case FIL_QUERYEASIZE: return queryPathInfoEaSize(unixPath, pInfoBuf, cbInfoBuf);
         case FIL_QUERYEASFROMLIST: return queryPathInfoEasFromList(unixPath, pInfoBuf, cbInfoBuf);
+
+        // OS/2 has an undocumented info level, 7, that appears to return a case-corrected version
+        //  of the path. (FIL_QUERYFULLNAME doesn't correct the case of what the app queries on
+        //  OS/2). Since we have to case-correct for a Unix filesystem anyhow, we just use the
+        //  usual FIL_QUERYFULLNAME to handle the undocumented level. Java uses this level.
+        case 7:
         case FIL_QUERYFULLNAME: return queryPathInfoFullName(unixPath, pInfoBuf, cbInfoBuf);
         default: break;
     } // switch
@@ -2707,5 +2737,123 @@
     return retval;
 } // DosFreeThreadLocalMemory
 
+APIRET DosQueryFHState(HFILE hFile, PULONG pMode)
+{
+    TRACE_NATIVE("DosQueryFHState(%u, %p)", (uint) hFile, pMode);
+
+    APIRET retval = NO_ERROR;
+    ULONG tmp = 0;
+    if (!pMode)
+        pMode = &tmp;
+
+    grabLock(&GMutexDosCalls);
+    if ((hFile < MaxHFiles) && (HFiles[hFile].fd != -1))
+        *pMode = HFiles[hFile].flags;
+    else
+        retval = ERROR_INVALID_HANDLE;
+    ungrabLock(&GMutexDosCalls);
+
+    return retval;
+} // DosQueryFHState
+
+APIRET DosQueryHeaderInfo(HMODULE hmod, ULONG ulIndex, PVOID pvBuffer, ULONG cbBuffer, ULONG ulSubFunction)
+{
+    TRACE_NATIVE("DosQueryHeaderInfo(%u, %u, %p, %u, %u)", (uint) hmod, (uint) ulIndex, pvBuffer, (uint) cbBuffer, (uint) ulSubFunction);
+
+    switch (ulSubFunction) {
+        //case QHINF_EXEINFO:
+        //case QHINF_READRSRCTBL:
+        //case QHINF_READFILE:
+
+        case QHINF_LIBPATHLENGTH:
+            if (cbBuffer < sizeof (ULONG))
+                return ERROR_BUFFER_OVERFLOW;
+            *((ULONG *) pvBuffer) = GLoaderState->libpathlen;
+            return NO_ERROR;
+
+        case QHINF_LIBPATH:
+            if (cbBuffer < GLoaderState->libpathlen)
+                return ERROR_BUFFER_OVERFLOW;
+            strcpy((char *) pvBuffer, GLoaderState->libpath);
+            return NO_ERROR;
+
+        //case QHINF_FIXENTRY:
+        //case QHINF_STE:
+        //case QHINF_MAPSEL:
+
+        default: FIXME("I don't know what this query wants"); break;
+    } // switch
+
+    return ERROR_INVALID_PARAMETER;
+} // DosQueryHeaderInfo
+
+APIRET OS2API DosQueryExtLIBPATH(PSZ pszExtLIBPATH, ULONG flags)
+{
+    TRACE_NATIVE("DosQueryExtLIBPATH('%s', %u)", pszExtLIBPATH, (uint) flags);
+
+    // !!! FIXME: this is mostly a stub for now.
+
+    if ((flags != BEGIN_LIBPATH) && (flags != END_LIBPATH))
+        return ERROR_INVALID_PARAMETER;
+
+    if (pszExtLIBPATH)
+        *pszExtLIBPATH = '\0';
+
+    return NO_ERROR;
+} // DosQueryExtLIBPATH
+
+APIRET DosSetMaxFH(ULONG cFH)
+{
+    grabLock(&GMutexDosCalls);
+
+    if (cFH < MaxHFiles) {
+        ungrabLock(&GMutexDosCalls);
+        return ERROR_INVALID_PARAMETER;  // strictly speaking, we could shrink, but I'm not doing it.
+    } // if
+
+    if (cFH == MaxHFiles) {
+        ungrabLock(&GMutexDosCalls);
+        return NO_ERROR;
+    } // if
+
+    HFileInfo *info = (HFileInfo *) realloc(HFiles, sizeof (HFileInfo) * (cFH));
+    if (info != NULL) {
+        HFiles = info;
+        info += MaxHFiles;
+        for (ULONG i = MaxHFiles; i < cFH; i++, info++) {
+            info->fd = -1;
+            info->type = 0;
+            info->attr = 0;
+            info->flags = 0;
+        } // for
+        MaxHFiles = cFH;
+    } // if
+
+    ungrabLock(&GMutexDosCalls);
+
+    return NO_ERROR;
+} // DosSetMaxFH
+
+APIRET DosQuerySysState(ULONG func, ULONG arg1, ULONG pid, ULONG _res_, PVOID buf, ULONG bufsz)
+{
+    TRACE_NATIVE("DosQuerySysState(%u, %u, %u, %u, %p, %u)", (uint) func, (uint) arg1, (uint) pid, (uint) _res_, buf, (uint) bufsz);
+    FIXME("implement me");
+    return ERROR_INVALID_PARAMETER;
+} // DosQuerySysState
+
+APIRET DosR3ExitAddr(void)
+{
+    TRACE_NATIVE("DosR3ExitAddr()");
+    FIXME("I have no idea what this is");  // but...JAVA USES IT.
+    return ERROR_INVALID_PARAMETER;
+} // DosR3ExitAddr
+
+APIRET DosQueryThreadContext(TID tid, ULONG level, PCONTEXTRECORD pcxt)
+{
+    TRACE_NATIVE("DosQueryThreadContext(%u, %u, %p)", (uint) tid, (uint) level, pcxt);
+    FIXME("Need to be able to suspend threads first");
+    return ERROR_INVALID_PARAMETER;
+} // DosQueryThreadContext
+
 // end of doscalls.c ...
 
--- a/native/doscalls.h	Thu Oct 27 03:17:23 2016 -0400
+++ b/native/doscalls.h	Thu Oct 27 03:20:02 2016 -0400
@@ -371,6 +371,13 @@
     FAPPTYP_32BIT = 0x4000
 };
 
+enum
+{
+    BEGIN_LIBPATH = 1,
+    END_LIBPATH = 2
+};
+
+typedef void *PCONTEXTRECORD;  // !!! FIXME
 
 // !!! FIXME: these should probably get sorted alphabetically and/or grouped
 // !!! FIXME:  into areas of functionality, but for now, I'm just listing them
@@ -434,6 +441,10 @@
 APIRET OS2API DosQueryAppType(PSZ pszName, PULONG pFlags);
 APIRET OS2API DosAllocThreadLocalMemory(ULONG cb, PULONG *p);
 APIRET OS2API DosFreeThreadLocalMemory(ULONG *p);
+APIRET OS2API DosQueryFHState(HFILE hFile, PULONG pMode);
+APIRET OS2API DosQueryExtLIBPATH(PSZ pszExtLIBPATH, ULONG flags);
+APIRET OS2API DosSetMaxFH(ULONG cFH);
+APIRET OS2API DosQueryThreadContext(TID tid, ULONG level, PCONTEXTRECORD pcxt);
 
 #ifdef __cplusplus
 }
--- a/native/os2native.h	Thu Oct 27 03:17:23 2016 -0400
+++ b/native/os2native.h	Thu Oct 27 03:20:02 2016 -0400
@@ -16,8 +16,11 @@
 #include "os2errors.h"
 #include "../lx_loader.h"
 
-#if 0
-#define TRACE_NATIVE(...) do { fprintf(stderr, "2INE TRACE [%lu]: ", (unsigned long) pthread_self()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, ";\n"); } while (0)
+// note that this is defined in _every_ module that includes this file!
+static LxLoaderState *GLoaderState = NULL;
+
+#if 1
+#define TRACE_NATIVE(...) do { if (GLoaderState->trace_native) { fprintf(stderr, "2INE TRACE [%lu]: ", (unsigned long) pthread_self()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, ";\n"); } } while (0)
 #else
 #define TRACE_NATIVE(...) do {} while (0)
 #endif
@@ -26,10 +29,14 @@
 void OS2EXPORT lxNativeModuleDeinit(void);
 
 #define LX_NATIVE_MODULE_DEINIT(deinitcode) \
-    void lxNativeModuleDeinit(void) { deinitcode; }
+    void lxNativeModuleDeinit(void) { \
+        deinitcode; \
+        GLoaderState = NULL; \
+    }
 
 #define LX_NATIVE_MODULE_INIT(initcode) \
     const LxExport *lxNativeModuleInit(LxLoaderState *lx_state, uint32 *lx_num_exports) { \
+        GLoaderState = lx_state; \
         initcode; \
         static const LxExport lx_native_exports[] = {
 
--- a/native/os2native16.h	Thu Oct 27 03:17:23 2016 -0400
+++ b/native/os2native16.h	Thu Oct 27 03:20:02 2016 -0400
@@ -6,7 +6,6 @@
 // !!! FIXME: _lots_ of macro salsa in here.
 
 #define LX_NATIVE_MODULE_16BIT_SUPPORT() \
-    static LxLoaderState *GLoaderState = NULL; \
     static LxMmaps obj16;
 
 // These are the 16-bit entry points, which are exported to the LX loader
@@ -26,11 +25,8 @@
     obj16.mapped = obj16.addr = NULL; \
     obj16.size = 0; \
     obj16.alias = 0xFFFF; \
-    GLoaderState = NULL;
 
 #define LX_NATIVE_MODULE_INIT_16BIT_SUPPORT() \
-    GLoaderState = lx_state; \
-    \
     obj16.mapped = obj16.addr = NULL; \
     obj16.size = 0; \
     obj16.alias = 0xFFFF; \
@@ -39,7 +35,6 @@
     void *mmapaddr = mmap(NULL, vsize, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); \
     if (mmapaddr == ((void *) MAP_FAILED)) { \
         fprintf(stderr, "mmap(NULL, 0x20000, RW-, ANON|PRIVATE, -1, 0) failed (%d): %s\n", errno, strerror(errno)); \
-        GLoaderState = NULL; \
         return 0; \
     } \
     \
@@ -62,7 +57,6 @@
         obj16.mapped = obj16.addr = NULL; \
         obj16.size = 0; \
         obj16.alias = 0xFFFF; \
-        GLoaderState = NULL; \
         return 0; \
     } \
     assert(offset == 0); \
@@ -115,7 +109,7 @@
 RETF 0x22   ; ...and back to the (far) caller, clearing the args (Pascal calling convention!) with retval in AX.
 */
 
-#define LX_NATIVE_INIT_16BIT_BRIDGE(fn, argbytes) \
+#define LX_NATIVE_INIT_16BIT_BRIDGE(fn, argbytes) { \
     fn##16 = ptr; \
     \
     /* instructions are in Intel syntax here, not AT&T. */ \
@@ -192,9 +186,9 @@
     *(ptr++) = 0xCA;  /* retf 0x22... */ \
     const uint16 argbytecount = argbytes; \
     memcpy(ptr, &argbytecount, 2); ptr += 2; \
+}
 
-
-#define LX_NATIVE_MODULE_INIT_16BIT_SUPPORT_END() \
+#define LX_NATIVE_MODULE_INIT_16BIT_SUPPORT_END() { \
     assert((((uint32)ptr) - ((uint32)mmapaddr)) < 0x10000);  /* don't be more than 64k. */ \
     if (mprotect(obj16.mapped, vsize, PROT_READ | PROT_EXEC) == -1) { \
         fprintf(stderr, "mprotect() failed for 16-bit bridge code!\n"); \
@@ -203,9 +197,9 @@
         obj16.mapped = obj16.addr = NULL; \
         obj16.size = 0; \
         obj16.alias = 0xFFFF; \
-        GLoaderState = NULL; \
         return 0; \
     } \
+}
 
 #define LX_NATIVE_EXPORT16(fn, ord) { ord, #fn, &fn##16, &obj16 }
 
--- a/native/viocalls.c	Thu Oct 27 03:17:23 2016 -0400
+++ b/native/viocalls.c	Thu Oct 27 03:20:02 2016 -0400
@@ -1,23 +1,35 @@
 #include "os2native16.h"
 #include "viocalls.h"
 
-LX_NATIVE_MODULE_16BIT_SUPPORT()
-    LX_NATIVE_MODULE_16BIT_API(VioGetMode)
-LX_NATIVE_MODULE_16BIT_SUPPORT_END()
-
-// !!! FIXME:
-#undef TRACE_NATIVE
-#define TRACE_NATIVE(...) do { if (GLoaderState->trace_native) { fprintf(stderr, "2INE TRACE [%lu]: ", (unsigned long) pthread_self()); fprintf(stderr, __VA_ARGS__); fprintf(stderr, ";\n"); } } while (0)
-
-LX_NATIVE_MODULE_DEINIT({
-    LX_NATIVE_MODULE_DEINIT_16BIT_SUPPORT();
-})
-
 APIRET16 VioGetMode(PVIOMODEINFO pvioModeInfo, HVIO hvio)
 {
     TRACE_NATIVE("VioGetMode(%p, %u)", pvioModeInfo, (uint) hvio);
-    FIXME("write me");
-    return ERROR_INVALID_PARAMETER;
+
+    if (hvio != 0)
+        return ERROR_VIO_INVALID_HANDLE;  // !!! FIXME: can be non-zero when VioCreatePS() is implemented.
+    else if (pvioModeInfo == NULL)
+        return ERROR_VIO_INVALID_PARMS;
+    else if (pvioModeInfo->cb != sizeof (*pvioModeInfo))
+        return ERROR_VIO_INVALID_LENGTH;
+
+    memset(pvioModeInfo, '\0', sizeof (*pvioModeInfo));
+    pvioModeInfo->cb = sizeof (*pvioModeInfo);
+    pvioModeInfo->fbType = VGMT_OTHER;
+    pvioModeInfo->color = 8;  // bits?
+    pvioModeInfo->col = 80;
+    pvioModeInfo->row = 25;
+    FIXME("I don't know what most of these fields do");
+    //pvioModeInfo->hres = 640;
+    //pvioModeInfo->vres = 480;
+    //UCHAR fmt_ID;
+    //UCHAR attrib;
+    //ULONG buf_addr;
+    //ULONG buf_length;
+    //ULONG full_length;
+    //ULONG partial_length;
+    //PCHAR ext_data_addr;
+
+    return NO_ERROR;
 } // VioGetMode
 
 static APIRET16 bridge16to32_VioGetMode(uint8 *args)
@@ -27,15 +39,53 @@
     return VioGetMode(pvmi, hvio);
 } // bridge16to32_VioGetMode
 
-static int initViocalls(LxLoaderState *lx_state)
+
+APIRET16 VioGetCurPos(PUSHORT pusRow, PUSHORT pusColumn, HVIO hvio)
+{
+    TRACE_NATIVE("VioGetCurPos(%p, %p, %u)", pusRow, pusColumn, (uint) hvio);
+
+    if (hvio != 0)
+        return ERROR_VIO_INVALID_HANDLE;  // !!! FIXME: can be non-zero when VioCreatePS() is implemented.
+
+    FIXME("write me");
+    if (pusRow)
+        *pusRow = 0;
+    if (pusColumn)
+        *pusColumn = 0;
+
+    return NO_ERROR;
+} // VioGetCurPos
+
+static APIRET16 bridge16to32_VioGetCurPos(uint8 *args)
+{
+    const HVIO hvio = *((HVIO *) args); args += 2;
+    PUSHORT pusRow = GLoaderState->convert1616to32(*((uint32*) args)); args += 4;
+    PUSHORT pusColumn = GLoaderState->convert1616to32(*((uint32*) args)); args += 4;
+    return VioGetCurPos(pusRow, pusColumn, hvio);
+} // bridge16to32_VioGetCurPos
+
+
+
+LX_NATIVE_MODULE_16BIT_SUPPORT()
+    LX_NATIVE_MODULE_16BIT_API(VioGetCurPos)
+    LX_NATIVE_MODULE_16BIT_API(VioGetMode)
+LX_NATIVE_MODULE_16BIT_SUPPORT_END()
+
+LX_NATIVE_MODULE_DEINIT({
+    LX_NATIVE_MODULE_DEINIT_16BIT_SUPPORT();
+})
+
+static int initViocalls(void)
 {
     LX_NATIVE_MODULE_INIT_16BIT_SUPPORT()
+        LX_NATIVE_INIT_16BIT_BRIDGE(VioGetCurPos, 6)
         LX_NATIVE_INIT_16BIT_BRIDGE(VioGetMode, 6)
     LX_NATIVE_MODULE_INIT_16BIT_SUPPORT_END()
     return 1;
 } // initViocalls
 
-LX_NATIVE_MODULE_INIT({ if (!initViocalls(lx_state)) return NULL; })
+LX_NATIVE_MODULE_INIT({ if (!initViocalls()) return NULL; })
+    LX_NATIVE_EXPORT16(VioGetCurPos, 9),
     LX_NATIVE_EXPORT16(VioGetMode, 21)
 LX_NATIVE_MODULE_INIT_END()
 
--- a/native/viocalls.h	Thu Oct 27 03:17:23 2016 -0400
+++ b/native/viocalls.h	Thu Oct 27 03:20:02 2016 -0400
@@ -35,6 +35,7 @@
 };
 
 APIRET16 OS2API16 VioGetMode(PVIOMODEINFO pvioModeInfo, HVIO hvio);
+APIRET16 OS2API16 VioGetCurPos(PUSHORT pusRow, PUSHORT pusColumn, HVIO hvio);
 
 #ifdef __cplusplus
 }