Files
2ine-tms34010/lx_loader.c
T
Ryan C. Gordon cbffb2ff60 Initialize the PIB at startup.
Otherwise, it won't be available to DLL init code when run before the
main executable starts.

Fixes Watcom C's wlink.exe crashing because it gets a NULL command line.
2016-10-17 17:00:32 -04:00

1369 lines
49 KiB
C

#define _GNU_SOURCE 1
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <errno.h>
#include <sys/mman.h>
#include <assert.h>
#include <dlfcn.h>
#include <pthread.h>
// 16-bit selector kernel nonsense...
#include <sys/syscall.h>
#include <sys/types.h>
#include <asm/ldt.h>
#include "lx_loader.h"
static LxLoaderState _GLoaderState;
static LxLoaderState *GLoaderState = &_GLoaderState;
static uint8 GMainTibSpace[LXTIBSIZE];
// !!! FIXME: move this into an lx_common.c file.
static int sanityCheckLxModule(uint8 **_exe, uint32 *_exelen)
{
if (*_exelen < 196) {
fprintf(stderr, "not an OS/2 LX module\n");
return 0;
}
const uint32 header_offset = *((uint32 *) (*_exe + 0x3C));
//printf("header offset is %u\n", (uint) header_offset);
if ((header_offset + sizeof (LxHeader)) >= *_exelen) {
fprintf(stderr, "not an OS/2 LX module\n");
return 0;
}
*_exe += header_offset; // skip the DOS stub, etc.
*_exelen -= header_offset;
const LxHeader *lx = (const LxHeader *) *_exe;
if ((lx->magic_l != 'L') || (lx->magic_x != 'X')) {
fprintf(stderr, "not an OS/2 LX module\n");
return 0;
}
if ((lx->byte_order != 0) || (lx->word_order != 0)) {
fprintf(stderr, "Program is not little-endian!\n");
return 0;
}
if (lx->lx_version != 0) {
fprintf(stderr, "Program is unknown LX module version (%u)\n", (uint) lx->lx_version);
return 0;
}
if (lx->cpu_type > 3) { // 1==286, 2==386, 3==486
fprintf(stderr, "Program needs unknown CPU type (%u)\n", (uint) lx->cpu_type);
return 0;
}
if (lx->os_type != 1) { // 1==OS/2, others: dos4, windows, win386, unknown.
fprintf(stderr, "Program needs unknown OS type (%u)\n", (uint) lx->os_type);
return 0;
}
if (lx->page_size != 4096) {
fprintf(stderr, "Program page size isn't 4096 (%u)\n", (uint) lx->page_size);
return 0;
}
if (lx->module_flags & 0x2000) {
fprintf(stderr, "Module is flagged as not-loadable\n");
return 0;
}
// !!! FIXME: check if EIP and ESP are non-zero vs per-process library bits, etc.
return 1;
} // sanityCheckLxModule
static char *makeOS2Path(const char *fname)
{
char *full = realpath(fname, NULL);
if (!full)
return NULL;
char *retval = (char *) malloc(strlen(full) + 3);
if (!retval)
return NULL;
retval[0] = 'C';
retval[1] = ':';
strcpy(retval + 2, full);
free(full);
for (char *ptr = retval + 2; *ptr; ptr++) {
if (*ptr == '/')
*ptr = '\\';
} // for
return retval;
} // makeOS2Path
/* this algorithm is from lxlite 138u. */
static int decompressExePack2(uint8 *dst, const uint32 dstlen, const uint8 *src, const uint32 srclen)
{
uint32 sOf = 0;
uint32 dOf = 0;
uint32 bOf = 0;
uint32 b1 = 0;
uint32 b2 = 0;
#define SRCAVAIL(n) ((sOf + (n)) <= srclen)
#define DSTAVAIL(n) ((dOf + (n)) <= dstlen)
do {
if (!SRCAVAIL(1))
break;
b1 = src[sOf];
switch (b1 & 3) {
case 0:
if (b1 == 0) {
if (SRCAVAIL(2)) {
if (src[sOf + 1] == 0) {
sOf += 2;
break;
} else if (SRCAVAIL(3) && DSTAVAIL(src[sOf + 1])) {
memset(dst + dOf, src[sOf + 2], src[sOf + 1]);
sOf += 3;
dOf += src[sOf - 2];
} else {
return 0;
}
} else {
return 0;
}
} else if (SRCAVAIL((b1 >> 2) + 1) && DSTAVAIL(b1 >> 2)) {
memcpy(dst + dOf, src + (sOf + 1), b1 >> 2);
dOf += b1 >> 2;
sOf += (b1 >> 2) + 1;
} else {
return 0;
}
break;
case 1:
if (!SRCAVAIL(2))
return 0;
bOf = (*((const uint16 *) (src + sOf))) >> 7;
b2 = ((b1 >> 4) & 7) + 3;
b1 = ((b1 >> 2) & 3);
sOf += 2;
if (SRCAVAIL(b1) && DSTAVAIL(b1 + b2) && (dOf + b1 - bOf >= 0)) {
memcpy(dst + dOf, src + sOf, b1);
dOf += b1;
sOf += b1;
memmove(dst + dOf, dst + (dOf - bOf), b2);
dOf += b2;
} else {
return 0;
} // else
break;
case 2:
if (!SRCAVAIL(2))
return 0;
bOf = (*((const uint16 *) (src + sOf))) >> 4;
b1 = ((b1 >> 2) & 3) + 3;
if (DSTAVAIL(b1) && (dOf - bOf >= 0)) {
memmove(dst + dOf, dst + (dOf - bOf), b1);
dOf += b1;
sOf += 2;
} else {
return 0;
} // else
break;
case 3:
if (!SRCAVAIL(3))
return 0;
b2 = ((*((const uint16 *) (src + sOf))) >> 6) & 0x3F;
b1 = (src[sOf] >> 2) & 0x0F;
bOf = (*((const uint16 *) (src + (sOf + 1)))) >> 4;
sOf += 3;
if (SRCAVAIL(b1) && DSTAVAIL(b1 + b2) && (dOf + b1 - bOf >= 0))
{
memcpy(dst + dOf, src + sOf, b1);
dOf += b1;
sOf += b1;
memmove(dst + dOf, dst + (dOf - bOf), b2);
dOf += b2;
} else {
return 0;
} // else
break;
} // switch
} while (dOf < dstlen);
#undef SRCAVAIL
#undef DSTAVAIL
// pad out the rest of the page with zeroes.
memset(dst + dOf, '\0', dstlen - dOf);
return 1;
} // decompressExePack2
static void missingEntryPointCalled(const char *module, const char *entry)
{
void ***ebp = NULL;
__asm__ __volatile__ ( "movl %%ebp, %%eax \n\t" : "=a" (ebp) );
void *caller = ebp[0][1];
fflush(stdout);
fflush(stderr);
fprintf(stderr, "\n\nMissing entry point '%s' in module '%s' called at %p!\n", entry, module, caller);
fprintf(stderr, "Aborting.\n\n\n");
//STUBBED("output backtrace");
fflush(stderr);
_exit(1);
} // missing_ordinal_called
static void *generateMissingTrampoline(const char *_module, const char *_entry)
{
static void *page = NULL;
static uint32 pageused = 0;
static uint32 pagesize = 0;
if (pagesize == 0)
pagesize = getpagesize();
if ((!page) || ((pagesize - pageused) < 32))
{
if (page)
mprotect(page, pagesize, PROT_READ | PROT_EXEC);
page = valloc(pagesize);
mprotect(page, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC);
pageused = 0;
} // if
void *trampoline = page + pageused;
char *ptr = (char *) trampoline;
char *module = strdup(_module);
char *entry = strdup(_entry);
*(ptr++) = 0x55; // pushl %ebp
*(ptr++) = 0x89; // movl %esp,%ebp
*(ptr++) = 0xE5; // ...movl %esp,%ebp
*(ptr++) = 0x68; // pushl immediate
memcpy(ptr, &entry, sizeof (char *));
ptr += sizeof (uint32);
*(ptr++) = 0x68; // pushl immediate
memcpy(ptr, &module, sizeof (char *));
ptr += sizeof (uint32);
*(ptr++) = 0xB8; // movl immediate to %eax
const void *fn = missingEntryPointCalled;
memcpy(ptr, &fn, sizeof (void *));
ptr += sizeof (void *);
*(ptr++) = 0xFF; // call absolute in %eax.
*(ptr++) = 0xD0; // ...call absolute in %eax.
const uint32 trampoline_len = (uint32) (ptr - ((char *) trampoline));
assert(trampoline_len <= 32);
pageused += trampoline_len;
if (pageused % 4) // keep these aligned to 32 bits.
pageused += (4 - (pageused % 4));
//printf("Generated trampoline %p for module '%s' export '%s'\n", trampoline, module, entry);
return trampoline;
} // generateMissingTrampoline
// OS/2 threads keep their Thread Information Block at FS:0x0000, so we have
// to ask the Linux kernel to screw around with 16-bit selectors on our
// behalf so we don't crash out when apps try to access it directly.
// OS/2 provides a C-callable API to obtain the (32-bit linear!) TIB address
// without going directly to the FS register, but lots of programs (including
// the EMX runtime) touch the register directly, so we have to deal with it.
// You must call this once for each thread that will go into LX land, from
// that thread, as soon as possible after starting.
static uint16 initOs2Tib(uint8 *tibspace, void *_topOfStack, const size_t stacklen, const uint32 tid)
{
uint8 *topOfStack = (uint8 *) _topOfStack;
LxTIB *tib = (LxTIB *) tibspace;
LxTIB2 *tib2 = (LxTIB2 *) (tib + 1);
memset(tib, '\0', LXTIBSIZE);
FIXME("This is probably 50% wrong");
tib->tib_pexchain = NULL;
tib->tib_pstack = topOfStack - stacklen;
tib->tib_pstacklimit = (void *) topOfStack;
tib->tib_ptib2 = tib2;
tib->tib_version = 20; // !!! FIXME
tib->tib_ordinal = 79; // !!! FIXME
tib2->tib2_ultid = tid;
tib2->tib2_ulpri = 512;
tib2->tib2_version = 20;
tib2->tib2_usMCCount = 0;
tib2->tib2_fMCForceFlag = 0;
// !!! FIXME: I barely know what I'm doing here, this could all be wrong.
struct user_desc entry;
entry.entry_number = -1;
entry.base_addr = (unsigned int) ((size_t)tib);
entry.limit = LXTIBSIZE;
entry.seg_32bit = 1;
entry.contents = MODIFY_LDT_CONTENTS_DATA;
entry.read_exec_only = 0;
entry.limit_in_pages = 0;
entry.seg_not_present = 0;
entry.useable = 1;
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.
const unsigned int segment = (entry.entry_number << 3) | 3;
__asm__ __volatile__ ( "movw %%ax, %%fs \n\t" : : "a" (segment) );
return (uint16) entry.entry_number;
} // initOs2Tib
static void deinitOs2Tib(const uint16 selector)
{
// !!! FIXME: I barely know what I'm doing here, this could all be wrong.
struct user_desc entry;
memset(&entry, '\0', sizeof (entry));
entry.entry_number = selector;
entry.read_exec_only = 1;
entry.seg_not_present = 1;
const long rc = syscall(SYS_set_thread_area, &entry);
assert(rc == 0); FIXME("this can legit fail, though!");
} // deinitOs2Tib
static __attribute__((noreturn)) void runLxModule(LxModule *lxmod, const int argc, char **argv, char **envp)
{
uint8 *stack = (uint8 *) ((size_t) lxmod->esp);
stack -= sizeof (LxTIB) + sizeof (LxTIB2); // skip the TIB data.
// ...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...! eip=0x%X esp=0x%X\n", (uint) lxmod->eip, (uint) lxmod->esp); fflush(stderr);
GLoaderState->running = 1;
__asm__ __volatile__ (
"movl %%esi,%%esp \n\t" // use the OS/2 process's stack.
"pushl %%eax \n\t" // cmd
"pushl %%ecx \n\t" // env
"pushl $0 \n\t" // reserved
"pushl %%edx \n\t" // module handle
"leal 1f,%%eax \n\t" // address that entry point should return to.
"pushl %%eax \n\t"
"pushl %%edi \n\t" // the OS/2 process entry point (we'll "ret" to it instead of jmp, so stack and registers are all correct).
"xorl %%eax,%%eax \n\t"
"xorl %%ebx,%%ebx \n\t"
"xorl %%ecx,%%ecx \n\t"
"xorl %%edx,%%edx \n\t"
"xorl %%esi,%%esi \n\t"
"xorl %%edi,%%edi \n\t"
"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
: "a" (GLoaderState->pib.pib_pchcmd),
"c" (GLoaderState->pib.pib_pchenv),
"d" (lxmod), "S" (stack), "D" (lxmod->eip)
: "memory"
);
__builtin_unreachable();
} // runLxModule
static void runLxLibraryInitOrTerm(LxModule *lxmod, const int isTermination)
{
uint8 *stack = NULL;
// force us over to OS/2 main module's stack if we aren't already on it.
if (!GLoaderState->running) {
stack = (uint8 *) ((size_t) GLoaderState->main_module->esp);
stack -= sizeof (LxTIB) + sizeof (LxTIB2); // skip the TIB data.
} // if
fprintf(stderr, "jumping into LX land to %s library...! eip=0x%X esp=0x%X\n", isTermination ? "terminate" : "initialize", (uint) lxmod->eip, (uint) GLoaderState->main_module->esp); fflush(stderr);
__asm__ __volatile__ (
"pushal \n\t" // save all the current registers.
"pushfl \n\t" // save all the current flags.
"movl %%esp,%%ecx \n\t" // save our stack to a temporary register.
"testl %%esi,%%esi \n\t" // force the OS/2 process's stack?
"je 1f \n\t" // if 0, nope, we're already good.
"movl %%esi,%%esp \n\t" // use the OS/2 process's stack.
"1: \n\t" //
"pushl %%ecx \n\t" // save original stack pointer for real.
"pushl %%eax \n\t" // isTermination
"pushl %%edx \n\t" // library module handle
"leal 2f,%%eax \n\t" // address that entry point should return to.
"pushl %%eax \n\t"
"pushl %%edi \n\t" // the OS/2 library entry point (we'll "ret" to it instead of jmp, so stack and registers are all correct).
"xorl %%eax,%%eax \n\t"
"xorl %%ebx,%%ebx \n\t"
"xorl %%ecx,%%ecx \n\t"
"xorl %%edx,%%edx \n\t"
"xorl %%esi,%%esi \n\t"
"xorl %%edi,%%edi \n\t"
"xorl %%ebp,%%ebp \n\t"
"ret \n\t" // go to OS/2 land!
"2: \n\t" // ...and return here.
"addl $8,%%esp \n\t" // drop arguments to entry point.
"popl %%esp \n\t" // restore native process stack now.
"popfl \n\t" // restore our original flags.
"popal \n\t" // restore our original registers.
: // no outputs
: "a" (isTermination), "d" (lxmod), "S" (stack), "D" (lxmod->eip)
: "memory"
);
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!)
} // runLxLibraryInitOrTerm
static void runLxLibraryInit(LxModule *lxmod)
{
// we don't check the LIBINIT flag, because if the EIP fields are valid
// but this bit is missing, we're supposed to do "global" initialization,
// which I guess means it runs once for all processes on the system
// instead of once per process...but we aren't really a full OS/2 system
// so maybe it's okay to do global init in isolation...? We'll cross that
// bridge when we come to it, I guess. Either way: this is running here,
// flag or not.
if (1) { //if (lxmod->module_flags & 0x4) { // LIBINIT flag
if (lxmod->lx.eip_object != 0)
runLxLibraryInitOrTerm(lxmod, 0);
} // if
} // runLxLibraryInit
static void runLxLibraryTerm(LxModule *lxmod)
{
// See notes about global initialization in runLxLibraryInit(); it
// applies to deinit here, too.
if (1) { //if (lxmod->module_flags & 0x40000000) { // LIBTERM flag
if (lxmod->lx.eip_object != 0)
runLxLibraryInitOrTerm(lxmod, 1);
} // if
} // runLxLibraryTerm
static void freeLxModule(LxModule *lxmod)
{
if (!lxmod)
return;
// !!! FIXME: mutex from here
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->nativelib) {
runLxLibraryTerm(lxmod);
} else {
LxNativeModuleDeinitEntryPoint fn = (LxNativeModuleDeinitEntryPoint) dlsym(lxmod->nativelib, "lxNativeModuleDeinit");
if (fn)
fn();
} // else
} // if
if (lxmod->next)
lxmod->next->prev = lxmod->prev;
if (lxmod->prev)
lxmod->prev->next = lxmod->next;
if (lxmod == GLoaderState->loaded_modules)
GLoaderState->loaded_modules = lxmod->next;
if (GLoaderState->main_module == lxmod)
GLoaderState->main_module = NULL;
// !!! FIXME: mutex to here
for (uint32 i = 0; i < lxmod->lx.num_import_mod_entries; i++)
freeLxModule(lxmod->dependencies[i]);
free(lxmod->dependencies);
for (uint32 i = 0; i < lxmod->lx.module_num_objects; i++) {
if (lxmod->mmaps[i].addr)
munmap(lxmod->mmaps[i].addr, lxmod->mmaps[i].size);
} // for
free(lxmod->mmaps);
if (lxmod->nativelib)
dlclose(lxmod->nativelib);
else {
for (uint32 i = 0; i < lxmod->num_exports; i++)
free((void *) lxmod->exports[i].name);
free((void *) lxmod->exports);
} // else
free(lxmod);
} // freeLxModule
static LxModule *loadLxModuleByModuleNameInternal(const char *modname, const int dependency_tree_depth);
static void *getModuleProcAddrByOrdinal(const LxModule *module, const uint32 ordinal)
{
//printf("lookup module == '%s', ordinal == %u\n", module->name, (uint) ordinal);
const LxExport *lxexp = module->exports;
for (uint32 i = 0; i < module->num_exports; i++, lxexp++) {
if (lxexp->ordinal == ordinal)
return lxexp->addr;
} // for
#if 1
char entry[128];
snprintf(entry, sizeof (entry), "ORDINAL_%u", (uint) ordinal);
return generateMissingTrampoline(module->name, entry);
#else
return NULL;
#endif
} // getModuleProcAddrByOrdinal
#if 0
static void *getModuleProcAddrByName(const LxModule *module, const char *name)
{
//printf("lookup module == '%s', name == '%s'\n", module->name, name);
const LxExport *lxexp = module->exports;
for (uint32 i = 0; i < module->num_exports; i++, lxexp++) {
if (strcmp(lxexp->name, name) == 0)
return lxexp->addr;
} // for
#if 1
return generateMissingTrampoline(module->name, name);
#else
return NULL;
#endif
} // getModuleProcAddrByName
#endif
static void doFixup(uint8 *page, const sint16 offset, const uint32 finalval, const uint16 finalval2, const uint32 finalsize)
{
#if 0
if (finalsize == 6) {
printf("fixing up %p to 0x%X:0x%X (6 bytes)...\n", page + offset, (uint) finalval2, (uint) finalval);
} else {
printf("fixing up %p to 0x%X (%u bytes)...\n", page + offset, (uint) finalval, (uint) finalsize);
} // else
fflush(stdout);
#endif
switch (finalsize) {
case 1: { uint8 *dst = (uint8 *) (page + offset); *dst = (uint8) finalval; } break;
case 2: { uint16 *dst = (uint16 *) (page + offset); *dst = (uint16) finalval; } break;
case 4: { uint32 *dst = (uint32 *) (page + offset); *dst = (uint32) finalval; } break;
case 6: {
uint16 *dst1 = (uint16 *) (page + offset);
*dst1 = (uint16) finalval2;
uint32 *dst2 = (uint32 *) (page + offset + 2);
*dst2 = (uint32) finalval;
break;
} // case
default:
fprintf(stderr, "BUG! Unexpected fixup final size of %u\n", (uint) finalsize);
exit(1);
} // switch
} // doFixup
static void fixupPage(const uint8 *exe, LxModule *lxmod, const LxObjectTableEntry *obj, const uint32 pagenum, uint8 *page)
{
const LxHeader *lx = &lxmod->lx;
//const LxObjectTableEntry *origobj = ((const LxObjectTableEntry *) (exe + lx->object_table_offset));
const uint32 *fixuppage = (((const uint32 *) (exe + lx->fixup_page_table_offset)) + ((obj->page_table_index - 1) + pagenum));
const uint32 fixupoffset = *fixuppage;
const uint32 fixuplen = fixuppage[1] - fixuppage[0];
const uint8 *fixup = (exe + lx->fixup_record_table_offset) + fixupoffset;
const uint8 *fixupend = fixup + fixuplen;
// int record = 0;
while (fixup < fixupend) {
//record++; printf("FIXUP obj #%u page #%u record #: %d\n", (uint) (obj - origobj), (uint) pagenum, record);
const uint8 srctype = *(fixup++);
const uint8 fixupflags = *(fixup++);
uint8 srclist_count = 0;
sint16 srcoffset = 0;
uint32 finalval = 0;
uint16 finalval2 = 0;
uint32 finalsize = 0;
switch (srctype & 0xF) {
case 0x0: // byte fixup
finalsize = 1;
break;
case 0x2: // 16-bit selector fixup
case 0x5: // 16-bit offset fixup
finalsize = 2;
break;
case 0x3: // 16:16 pointer fixup
case 0x7: // 32-bit offset fixup
case 0x8: // 32-bit self-relative offset fixup
finalsize = 4;
break;
case 0x6: // 16:32 pointer fixup
finalsize = 6;
break;
} // switch
if (srctype & 0x20) { // contains a source list
srclist_count = *(fixup++);
} else {
srcoffset = *((sint16 *) fixup);
fixup += 2;
} // else
switch (fixupflags & 0x3) {
case 0x0: { // Internal fixup record
uint16 objectid = 0;
if (fixupflags & 0x40) { // 16 bit value
objectid = *((uint16 *) fixup); fixup += 2;
} else {
objectid = (uint16) *(fixup++);
} // else
uint32 targetoffset = 0;
// !!! FIXME: where do we use 16-bit selectors? What does this mean in a 32-bit app?
if ((srctype & 0xF) != 0x2) { // not a 16-bit selector fixup?
if (fixupflags & 0x10) { // 32-bit target offset
targetoffset = *((uint32 *) fixup); fixup += 4;
} else { // 16-bit target offset
targetoffset = (uint32) (*((uint16 *) fixup)); fixup += 2;
} // else
} // if
const uint32 base = (uint32) (size_t) lxmod->mmaps[objectid - 1].addr;
finalval = base + targetoffset;
break;
} // case
case 0x1: { // Import by ordinal fixup record
uint16 moduleid = 0; // module ordinal
if (fixupflags & 0x40) { // 16 bit value
moduleid = *((uint16 *) fixup); fixup += 2;
} else {
moduleid = (uint16) *(fixup++);
} // else
uint32 importid = 0; // import ordinal
if (fixupflags & 0x80) { // 8 bit value
importid = (uint32) *(fixup++);
} else if (fixupflags & 0x10) { // 32-bit value
importid = *((uint32 *) fixup); fixup += 4;
} else { // 16-bit value
importid = (uint32) *((uint16 *) fixup); fixup += 2;
} // else
if (moduleid == 0) {
fprintf(stderr, "uhoh, looking for module ordinal 0, which is illegal.\n");
} else if (moduleid > lx->num_import_mod_entries) {
fprintf(stderr, "uhoh, looking for module ordinal %u, but only %u available.\n", (uint) moduleid, (uint) lx->num_import_mod_entries);
} else {
finalval = (uint32) (size_t) getModuleProcAddrByOrdinal(lxmod->dependencies[moduleid-1], importid);
} // else
break;
} // case
case 0x2: { // Import by name fixup record
fprintf(stderr, "FIXUP 0x2 WRITE ME\n");
exit(1);
break;
} // case
case 0x3: { // Internal entry table fixup record
fprintf(stderr, "FIXUP 0x3 WRITE ME\n");
exit(1);
break;
} // case
} // switch
if (fixupflags & 0x4) { // Has additive.
uint32 additive = 0;
if (fixupflags & 0x20) { // 32-bit value
additive = *((uint32 *) fixup); fixup += 4;
} else { // 16-bit value
additive = (uint32) *((uint16 *) fixup);
fixup += 2;
} // else
finalval += additive;
} // if
if (srctype & 0x20) { // source list
for (uint8 i = 0; i < srclist_count; i++) {
const sint16 offset = *((sint16 *) fixup); fixup += 2;
if ((srctype & 0xF) == 0x08) { // self-relative fixup?
assert(finalval2 == 0);
doFixup(page, offset, finalval - (((uint32)page)+offset+4), 0, finalsize);
} else {
doFixup(page, offset, finalval, finalval2, finalsize);
}
} // for
} else {
if ((srctype & 0xF) == 0x08) { // self-relative fixup?
assert(finalval2 == 0);
doFixup(page, srcoffset, finalval - (((uint32)page)+srcoffset+4), 0, finalsize);
} else {
doFixup(page, srcoffset, finalval, finalval2, finalsize);
}
} // else
} // while
} // fixupPage
static int loadLxNameTable(LxModule *lxmod, const uint8 *name_table)
{
for (uint32 i = 0; *name_table; i++) {
const uint8 namelen = *(name_table++);
const uint8 *nameptr = name_table;
name_table += namelen;
const uint16 ordinal = *((const uint16 *) name_table); name_table += 2;
if (ordinal == 0)
continue; // skip this one.
LxExport *export = (LxExport *) lxmod->exports;
const uint32 num_exports = lxmod->num_exports;
for (uint32 i = 0; i < num_exports; i++, export++) {
if (export->ordinal == ordinal) {
char *name = (char *) malloc(namelen + 1);
if (!name) {
fprintf(stderr, "Out of memory!\n");
return 0;
} // if
memcpy(name, nameptr, namelen);
name[namelen] = '\0';
export->name = name;
break;
} // if
} // for
} // for
return 1;
} // loadLxNameTable
// !!! FIXME: break up this function.
static LxModule *loadLxModule(const char *fname, uint8 *exe, uint32 exelen, int dependency_tree_depth)
{
LxModule *retval = NULL;
const uint8 *origexe = exe;
//const uint32 origexelen = exelen;
if (!sanityCheckLxModule(&exe, &exelen))
goto loadlx_failed;
const LxHeader *lx = (const LxHeader *) exe;
const uint32 module_type = lx->module_flags & 0x00038000;
if ((module_type != 0x0000) && (module_type != 0x8000)) {
fprintf(stderr, "This is not a standard .exe or .dll (maybe device driver?)\n");
goto loadlx_failed;
} // if
const int isDLL = (module_type == 0x8000);
if (isDLL && !GLoaderState->main_module) {
fprintf(stderr, "uhoh, need to load an .exe before a .dll!\n");
goto loadlx_failed;
} else if (!isDLL && GLoaderState->main_module) {
fprintf(stderr, "uhoh, loading an .exe after already loading one!\n");
goto loadlx_failed;
} // if else if
retval = (LxModule *) malloc(sizeof (LxModule));
if (!retval) {
fprintf(stderr, "Out of memory!\n");
goto loadlx_failed;
} // if
memset(retval, '\0', sizeof (*retval));
retval->refcount = 1;
retval->dependencies = (LxModule **) malloc(sizeof (LxModule *) * lx->num_import_mod_entries);
retval->mmaps = (LxMmaps *) malloc(sizeof (LxMmaps) * lx->module_num_objects);
if (!retval->dependencies || !retval->mmaps) {
fprintf(stderr, "Out of memory!\n");
goto loadlx_failed;
} // if
memset(retval->dependencies, '\0', sizeof (LxModule *) * lx->num_import_mod_entries);
memset(retval->mmaps, '\0', sizeof (LxMmaps) * lx->module_num_objects);
memcpy(&retval->lx, lx, sizeof (*lx));
if (lx->resident_name_table_offset) {
const uint8 *name_table = exe + lx->resident_name_table_offset;
const uint8 namelen = *(name_table++);
char *ptr = retval->name;
for (uint32 i = 0; i < namelen; i++) {
const char ch = *(name_table++);
*(ptr++) = ((ch >= 'a') && (ch <= 'z')) ? (ch + ('A' - 'a')) : ch;
} // for
*ptr = '\0';
} // if
if (!isDLL) {
GLoaderState->main_module = retval;
GLoaderState->pib.pib_hmte = retval;
} // else if
const char *modname = retval->name;
//printf("ref'd new module '%s' to %u\n", modname, 1);
// !!! FIXME: apparently OS/2 does 1024, but they're not loading the module into RAM each time.
// !!! FIXME: the spec mentions the 1024 thing for "forwarder" records in the entry table,
// !!! FIXME: specifically. _Can_ two DLLs depend on each other? We'll have to load both and
// !!! FIXME: then fix them up without recursing.
if (++dependency_tree_depth > 32) {
fprintf(stderr, "Likely circular dependency in module '%s'\n", modname);
goto loadlx_failed;
} // if
const LxObjectTableEntry *obj = ((const LxObjectTableEntry *) (exe + lx->object_table_offset));
for (uint32 i = 0; i < lx->module_num_objects; i++, obj++) {
if (obj->object_flags & 0x8) // !!! FIXME: resource object; ignore this until resource support is written, later.
continue;
uint32 vsize = obj->virtual_size;
if ((vsize % lx->page_size) != 0)
vsize += lx->page_size - (vsize % lx->page_size);
const int mmapflags = MAP_POPULATE | MAP_ANON | MAP_PRIVATE | (isDLL ? 0 : MAP_FIXED);
void *base = isDLL ? NULL : (void *) ((size_t) obj->reloc_base_addr);
void *mmapaddr = mmap(base, vsize, PROT_READ|PROT_WRITE, mmapflags, -1, 0);
// we'll mprotect() these pages to the proper permissions later.
//fprintf(stderr, "mmap(%p, %u, RW-, ANON|PRIVATE%s, -1, 0) == %p\n", base, (uint) vsize, isDLL ? "" : "|FIXED", mmapaddr);
if (mmapaddr == ((void *) MAP_FAILED)) {
fprintf(stderr, "mmap(%p, %u, RW-, ANON|PRIVATE%s, -1, 0) failed (%d): %s\n",
base, (uint) vsize, isDLL ? "" : "|FIXED", errno, strerror(errno));
goto loadlx_failed;
} // if
retval->mmaps[i].addr = mmapaddr;
retval->mmaps[i].size = vsize;
const LxObjectPageTableEntry *objpage = ((const LxObjectPageTableEntry *) (exe + lx->object_page_table_offset));
objpage += obj->page_table_index - 1;
uint8 *dst = (uint8 *) mmapaddr;
const uint32 numPageTableEntries = obj->num_page_table_entries;
for (uint32 pagenum = 0; pagenum < numPageTableEntries; pagenum++, objpage++) {
const uint8 *src;
switch (objpage->flags) {
case 0x00: // preload
src = origexe + lx->data_pages_offset + (objpage->page_data_offset << lx->page_offset_shift);
memcpy(dst, src, objpage->data_size);
if (objpage->data_size < lx->page_size) {
memset(dst + objpage->data_size, '\0', lx->page_size - objpage->data_size);
}
break;
//case 0x01: // FIXME: write me ... iterated, this is exepack1, I think.
// src = origexe + lx->object_iter_pages_offset + (objpage->page_data_offset << lx->page_offset_shift);
// memcpy(dst, src, objpage->data_size);
// break;
case 0x02: // INVALID, just zero it out.
case 0x03: // ZEROFILL, just zero it out.
memset(dst, '\0', lx->page_size);
break;
case 0x05: // exepack2
src = origexe + lx->data_pages_offset + (objpage->page_data_offset << lx->page_offset_shift);
if (!decompressExePack2(dst, lx->page_size, src, objpage->data_size)) {
fprintf(stderr, "Failed to decompress object page (corrupt file or bug).\n");
goto loadlx_failed;
} // if
break;
case 0x08: // !!! FIXME: this is listed in lxlite, presumably this is exepack3 or something. Maybe this was new to Warp4, like exepack2 was new to Warp3. Pull this algorithm in from lxlite later.
default:
fprintf(stderr, "Don't know how to load an object page of type %u!\n", (uint) objpage->flags);
goto loadlx_failed;
} // switch
dst += lx->page_size;
} // for
// any bytes at the end of this object that we didn't initialize? Zero them out.
const uint32 remain = vsize - ((uint32) (dst - ((uint8 *) mmapaddr)));
if (remain) {
memset(dst, '\0', remain);
} // if
} // for
// All the pages we need from the EXE are loaded into the appropriate spots in memory.
//printf("mmap()'d everything we need!\n");
retval->eip = lx->eip;
if (lx->eip_object != 0) {
const uint32 base = (uint32) ((size_t)retval->mmaps[lx->eip_object - 1].addr);
retval->eip += base;
} // if
// !!! FIXME: esp==0 means something special for programs (and is ignored for library init).
// !!! 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) {
retval->esp = lx->esp;
if (lx->esp_object != 0) {
const uint32 base = (uint32) ((size_t)retval->mmaps[lx->esp_object - 1].addr);
retval->esp += base;
} // if
} // if
if (!isDLL) {
// This needs to be set up now, so it's available to any library
// init code that runs in LX land.
initOs2Tib(GMainTibSpace, (void *) ((size_t) retval->esp), retval->mmaps[retval->lx.esp_object - 1].size, 0);
} // if
// Set up our exports...
uint32 total_ordinals = 0;
const uint8 *entryptr = exe + lx->entry_table_offset;
while (*entryptr) { /* end field has a value of zero. */
const uint8 numentries = *(entryptr++); /* number of entries in this bundle */
const uint8 bundletype = *(entryptr++) & ~0x80;
if (bundletype != 0x00) {
total_ordinals += numentries;
entryptr += 2 + ((bundletype == 0x01) ? 3 : 5) * numentries;
} // if
} // while
LxExport *lxexp = (LxExport *) malloc(sizeof (LxExport) * total_ordinals);
if (!lxexp) {
fprintf(stderr, "Out of memory!\n");
goto loadlx_failed;
} // if
memset(lxexp, '\0', sizeof (LxExport) * total_ordinals);
retval->exports = lxexp;
uint32 ordinal = 1;
entryptr = exe + lx->entry_table_offset;
while (*entryptr) { /* end field has a value of zero. */
const uint8 numentries = *(entryptr++); /* number of entries in this bundle */
const uint8 bundletype = *(entryptr++) & ~0x80;
uint16 objidx = 0;
switch (bundletype) {
case 0x00: // UNUSED
ordinal += numentries;
break;
case 0x01: // 16BIT
objidx = *((const uint16 *) entryptr) - 1;
entryptr += 2;
for (uint8 i = 0; i < numentries; i++) {
entryptr++;
lxexp->ordinal = ordinal++;
lxexp->addr = ((uint8 *) retval->mmaps[objidx].addr) + *((const uint16 *) entryptr);
lxexp++;
entryptr += 2;
} // for
break;
case 0x03: // 32BIT
objidx = *((const uint16 *) entryptr) - 1;
entryptr += 2;
for (uint8 i = 0; i < numentries; i++) {
entryptr++;
lxexp->ordinal = ordinal++;
lxexp->addr = ((uint8 *) retval->mmaps[objidx].addr) + *((const uint32 *) entryptr);
lxexp++;
entryptr += 4;
}
break;
case 0x02: // 286CALLGATE
case 0x04: // FORWARDER
fprintf(stderr, "WRITE ME %s:%d\n", __FILE__, __LINE__);
goto loadlx_failed;
default:
fprintf(stderr, "UNKNOWN ENTRY TYPE (%u)\n\n", (uint) bundletype);
goto loadlx_failed;
} // switch
} // while
retval->num_exports = (uint32) (lxexp - retval->exports);
// Now load named entry points.
if (lx->resident_name_table_offset) {
if (!loadLxNameTable(retval, exe + lx->resident_name_table_offset))
goto loadlx_failed;
} // if
if (lx->non_resident_name_table_offset && lx->non_resident_name_table_len) {
if (!loadLxNameTable(retval, exe + lx->non_resident_name_table_offset))
goto loadlx_failed;
} // if
// Load other dependencies of this module.
const uint8 *import_modules_table = exe + lx->import_module_table_offset;
for (uint32 i = 0; i < lx->num_import_mod_entries; i++) {
const uint8 namelen = *(import_modules_table++);
if (namelen > 127) {
fprintf(stderr, "Import module %u name is > 127 chars (%u). Corrupt file or bug!\n", (uint) (i + 1), (uint) namelen);
goto loadlx_failed;
}
char name[128];
memcpy(name, import_modules_table, namelen);
import_modules_table += namelen;
name[namelen] = '\0';
retval->dependencies[i] = loadLxModuleByModuleNameInternal(name, dependency_tree_depth);
if (!retval->dependencies[i]) {
fprintf(stderr, "Failed to load dependency '%s' for module '%s'\n", name, modname);
goto loadlx_failed;
} // if
} // for
// Run through again and do all the fixups...
obj = ((const LxObjectTableEntry *) (exe + lx->object_table_offset));
for (uint32 i = 0; i < lx->module_num_objects; i++, obj++) {
if (obj->object_flags & 0x8) // !!! FIXME: resource object; ignore this until resource support is written, later.
continue;
uint8 *dst = (uint8 *) retval->mmaps[i].addr;
const uint32 numPageTableEntries = obj->num_page_table_entries;
for (uint32 pagenum = 0; pagenum < numPageTableEntries; pagenum++) {
fixupPage(exe, retval, obj, pagenum, dst);
dst += lx->page_size;
} // for
// !!! FIXME: hack to nop out some 16-bit code in emx.dll startup...
if ((i == 1) && (strcmp(modname, "EMX") == 0)) {
// This is the 16-bit signal handler installer. nop it out.
uint8 *ptr = ((uint8 *) retval->mmaps[1].addr) + 28596;
for (uint32 i = 0; i < 37; i++)
*(ptr++) = 0x90; // nop
} // 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);
if (mprotect(retval->mmaps[i].addr, retval->mmaps[i].size, prot) == -1) {
fprintf(stderr, "mprotect(%p, %u, %s%s%s, ANON|PRIVATE|FIXED, -1, 0) failed (%d): %s\n",
retval->mmaps[i].addr, (uint) retval->mmaps[i].size,
(prot&PROT_READ) ? "R" : "-",
(prot&PROT_WRITE) ? "W" : "-",
(prot&PROT_EXEC) ? "X" : "-",
errno, strerror(errno));
goto loadlx_failed;
} // if
} // for
retval->os2path = makeOS2Path(fname);
if (!retval->os2path)
goto loadlx_failed;
if (!isDLL) {
retval->initialized = 1;
} else {
// call library init code...
assert(GLoaderState->main_module != NULL);
assert(GLoaderState->main_module != retval);
runLxLibraryInit(retval);
retval->initialized = 1;
// module is ready to use, put it in the loaded list.
// !!! FIXME: mutex this
if (GLoaderState->loaded_modules) {
retval->next = GLoaderState->loaded_modules;
GLoaderState->loaded_modules->prev = retval;
} // if
GLoaderState->loaded_modules = retval;
} // if
return retval;
loadlx_failed:
freeLxModule(retval);
return NULL;
} // loadLxModule
static LxModule *loadLxModuleByPathInternal(const char *fname, const int dependency_tree_depth)
{
const char *what = NULL; (void) what;
uint8 *module = NULL;
uint32 modulelen = 0;
FILE *io = NULL;
// !!! FIXME: locate correct file (case-insensitive checking, convert '\\' to '/' etc)...
#define LOADFAIL(x) { what = x; goto loadmod_failed; }
if ((io = fopen(fname, "rb")) == NULL) LOADFAIL("open");
if (fseek(io, 0, SEEK_END) < 0) LOADFAIL("seek");
modulelen = ftell(io);
if ((module = (uint8 *) malloc(modulelen)) == NULL) LOADFAIL("malloc");
rewind(io);
if (fread(module, modulelen, 1, io) != 1) LOADFAIL("read");
fclose(io);
#undef LOADFAIL
LxModule *retval = loadLxModule(fname, module, modulelen, dependency_tree_depth);
free(module);
return retval;
loadmod_failed:
fprintf(stderr, "%s failure on '%s: %s'\n", what, fname, strerror(errno));
if (io)
fclose(io);
free(module);
return NULL;
} // loadLxModuleByPathInternal
static inline LxModule *loadLxModuleByPath(const char *fname)
{
return loadLxModuleByPathInternal(fname, 0);
} // loadLxModuleByPath
static LxModule *loadNativeModule(const char *fname, const char *modname)
{
LxModule *retval = (LxModule *) malloc(sizeof (LxModule));
void *lib = NULL;
LxNativeModuleInitEntryPoint fn = NULL;
const LxExport *exports = NULL;
uint32 num_exports = 0;
char *os2path = makeOS2Path(fname);
if (!retval || !modname || !os2path)
goto loadnative_failed;
memset(retval, '\0', sizeof(*retval));
// !!! FIXME: mutex this.
lib = dlopen(fname, RTLD_LOCAL | RTLD_NOW);
if (lib == NULL) {
fprintf(stderr, "dlopen('%s') failed: %s\n", fname, dlerror());
goto loadnative_failed;
} // if
fn = (LxNativeModuleInitEntryPoint) dlsym(lib, "lxNativeModuleInit");
if (!fn)
goto loadnative_failed;
exports = fn(GLoaderState, &num_exports);
if (!exports)
goto loadnative_failed;
retval->refcount = 1;
strcpy(retval->name, modname);
retval->nativelib = lib;
retval->os2path = os2path;
retval->exports = exports;
retval->num_exports = num_exports;
retval->initialized = 1;
//fprintf(stderr, "Loaded native module '%s' (%u exports).\n", fname, (uint) num_exports);
return retval;
loadnative_failed:
if (lib) dlclose(lib);
free(os2path);
free(retval);
return NULL;
} // loadNativeModule
static LxModule *loadLxModuleByModuleNameInternal(const char *modname, const int dependency_tree_depth)
{
// !!! FIXME: mutex this
for (LxModule *i = GLoaderState->loaded_modules; i != NULL; i = i->next) {
if (strcasecmp(i->name, modname) == 0) {
i->refcount++;
//printf("ref'd module '%s' to %u\n", i->name, (uint) i->refcount);
return i;
} // if
} // for
// !!! FIXME: decide the right path to the file, or if it's a native replacement library.
char fname[256];
snprintf(fname, sizeof (fname), "%s.dll", modname);
LxModule *retval = NULL;
if (access(fname, F_OK) == 0)
retval = loadLxModuleByPathInternal(fname, dependency_tree_depth);
if (!retval) {
snprintf(fname, sizeof (fname), "./lib%s.so", modname);
for (char *ptr = fname; *ptr; ptr++) {
*ptr = (((*ptr >= 'A') && (*ptr <= 'Z')) ? (*ptr - ('A' - 'a')) : *ptr);
} // for
retval = loadNativeModule(fname, modname);
if (retval != NULL) {
// module is ready to use, put it in the loaded list.
// !!! FIXME: mutex this
if (GLoaderState->loaded_modules) {
retval->next = GLoaderState->loaded_modules;
GLoaderState->loaded_modules->prev = retval;
} // if
GLoaderState->loaded_modules = retval;
} // if
} // if
return retval;
} // loadLxModuleByModuleNameInternal
static void initPib(LxPIB *pib, const int argc, char **argv, char **envp)
{
// !!! FIXME: this is incomplete.
memset(pib, '\0', sizeof (*pib));
// Eventually, the environment table looks like this (double-null to terminate list): var1=a\0var2=b\0var3=c\0\0
// The command line looks like this: \0argv0\0argv1 argv2 argvN\0\0
// Between env and cmd is the exe name: argv0\0
size_t len = 1;
for (int i = 0; i < argc; i++) {
int needs_escape = 0;
int num_backslashes = 0;
for (const char *arg = argv[i]; *arg; arg++) {
const char ch = *arg;
if ((ch == ' ') || (ch == '\t'))
needs_escape = 1;
else if ((ch == '\\') || (ch == '\"'))
num_backslashes++;
len++;
} // for
if (needs_escape) {
len += 2 + num_backslashes;
} // if
len++; // terminator
} // for
len += strlen(argv[0]) + 1; // for the exe name.
const char *default_os2path = "PATH=C:\\home\\icculus\\Dropbox\\emx\\bin;C:\\WATCOM\\binp;C:\\home\\icculus"; // !!! FIXME: noooooope.
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, "IS_2INE=", 8) == 0) {
continue;
} // if
len += strlen(str) + 1;
} // for
len += 4; // null terminators.
char *env = (char *) malloc(len);
if (!env) {
fprintf(stderr, "Out of memory\n");
exit(1);
} // if
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, "IS_2INE=", 8) == 0) {
continue;
} // if
strcpy(ptr, str);
ptr += strlen(str) + 1;
}
*(ptr++) = '\0';
// put the exe name between the environment and the command line.
strcpy(ptr, argv[0]);
ptr += strlen(argv[0]) + 1;
char *cmd = ptr;
strcpy(ptr, argv[0]);
ptr += strlen(argv[0]);
*(ptr++) = '\0';
for (int i = 1; i < argc; i++) {
int needs_escape = 0;
for (const char *arg = argv[i]; *arg; arg++) {
const char ch = *arg;
if ((ch == ' ') || (ch == '\t'))
needs_escape = 1;
} // for
if (needs_escape) {
*(ptr++) = '"';
for (const char *arg = argv[i]; *arg; arg++) {
const char ch = *arg;
if ((ch == '\\') || (ch == '\n'))
*(ptr++) = '\\';
*(ptr++) = ch;
} // for
*(ptr++) = '"';
} else {
for (const char *arg = argv[i]; *arg; arg++)
*(ptr++) = *arg;
} // if
if (i < (argc-1))
*(ptr++) = ' ';
} // for
*(ptr++) = '\0';
pib->pib_ulpid = (uint32) getpid();
pib->pib_ulppid = (uint32) getppid();
pib->pib_pchcmd = cmd;
pib->pib_pchenv = env;
//pib->pib_hmte is filled in later during loadLxModule()
// !!! FIXME: uint32 pib_flstatus;
// !!! FIXME: uint32 pib_ultype;
} // initPib
static LxModule *loadLxModuleByModuleName(const char *modname)
{
return loadLxModuleByModuleNameInternal(modname, 0);
} // loadLxModuleByModuleName
int main(int argc, char **argv, char **envp)
{
if (argc < 2) {
fprintf(stderr, "USAGE: %s <program.exe> [...programargs...]\n", argv[0]);
return 1;
}
const char *envr = getenv("IS_2INE");
GLoaderState->subprocess = (envr != NULL);
GLoaderState->initOs2Tib = initOs2Tib;
GLoaderState->deinitOs2Tib = deinitOs2Tib;
GLoaderState->loadModule = loadLxModuleByModuleName;
const char *modulename = GLoaderState->subprocess ? envr : argv[1];
// standalone, drop argv[0]. As a subprocess, keep it.
if (!GLoaderState->subprocess) {
argc--;
argv++;
} // if
initPib(&GLoaderState->pib, argc, argv, envp);
LxModule *lxmod = loadLxModuleByPath(modulename);
if (!lxmod) {
return 1;
}
runLxModule(lxmod, argc, argv, envp);
return 1; // you shouldn't hit this, but if you do, report failure.
} // main
// end of lx_loader.c ...