860 lines
31 KiB
C
860 lines
31 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 "lx_loader.h"
|
|
|
|
static LxModule *GLoadedModules = NULL;
|
|
|
|
// !!! 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", (unsigned int) 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", (unsigned int) 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", (unsigned int) 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", (unsigned int) lx->os_type);
|
|
return 0;
|
|
}
|
|
|
|
if (lx->page_size != 4096) {
|
|
fprintf(stderr, "Program page size isn't 4096 (%u)\n", (unsigned int) lx->page_size);
|
|
return 0;
|
|
}
|
|
|
|
// !!! FIXME: check if EIP and ESP are non-zero vs per-process library bits, etc.
|
|
|
|
return 1;
|
|
} // sanityCheckLxModule
|
|
|
|
/* 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)
|
|
{
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
fprintf(stderr, "\n\nMissing entry point '%s' in module '%s' called!\n", entry, module);
|
|
fprintf(stderr, "Aborting.\n\n\n");
|
|
//STUBBED("output backtrace");
|
|
fflush(stderr);
|
|
_exit(1);
|
|
} // missing_ordinal_called
|
|
|
|
static uint32 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 (uint32) (size_t) trampoline;
|
|
} // generateMissingTrampoline
|
|
|
|
static __attribute__((noreturn)) void runLxModule(const LxModule *lxmod, const int argc, char **argv)
|
|
{
|
|
// !!! FIXME: right now, we don't list any environment variables, because they probably don't make sense to drop in (even PATH uses a different separator on Unix).
|
|
// !!! FIXME: eventually, the environment table looks like this (double-null to terminate list): var1=a\0var2=b\0var3=c\0\0
|
|
// The command line, if I'm reading the Open Watcom __OS2Main() implementation correctly, looks like this...
|
|
// \0programname\0argv0\0argv1\0argvN\0
|
|
size_t len = 0;
|
|
for (int i = 0; i < argc; i++) {
|
|
len += strlen(argv[i]) + 1;
|
|
}
|
|
|
|
const char *tmpenv = "IS_2INE=1";
|
|
const size_t envlen = strlen(tmpenv);
|
|
len += envlen;
|
|
len += 4; // null terminators.
|
|
|
|
char *env = (char *) malloc(len);
|
|
if (!env) {
|
|
fprintf(stderr, "Out of memory\n");
|
|
exit(1);
|
|
} // if
|
|
|
|
strcpy(env, tmpenv);
|
|
char *ptr = env + envlen;
|
|
*(ptr++) = '\0';
|
|
*(ptr++) = '\0';
|
|
char *cmd = ptr;
|
|
strcpy(ptr, argv[0]);
|
|
ptr += strlen(argv[0]);
|
|
*(ptr++) = '\0';
|
|
for (int i = 0; i < argc; i++) {
|
|
strcpy(ptr, argv[i]);
|
|
ptr += strlen(argv[i]);
|
|
*(ptr++) = '\0';
|
|
} // for
|
|
*(ptr++) = '\0';
|
|
|
|
// ...and you pass it the pointer to argv0. This is (at least as far as the docs suggest) appended to the environment table.
|
|
printf("jumping into LX land...! eip=0x%X esp=0x%X\n", (unsigned int) lxmod->eip, (unsigned int) lxmod->esp); fflush(stdout);
|
|
|
|
// !!! FIXME: need to set up OS/2 TIB and put it in FS register.
|
|
__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 $0 \n\t" // module handle !!! FIXME
|
|
"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"
|
|
// !!! FIXME: init other registers!
|
|
"ret \n\t" // go to OS/2 land!
|
|
"1: \n\t" // ...and return here.
|
|
"pushl %%eax \n\t" // call _exit() with whatever is in %eax.
|
|
"call _exit \n\t"
|
|
: // no outputs
|
|
: "a" (cmd), "c" (env), "S" (lxmod->esp), "D" (lxmod->eip)
|
|
: "memory"
|
|
);
|
|
|
|
__builtin_unreachable();
|
|
} // runLxModule
|
|
|
|
static void freeLxModule(LxModule *lxmod)
|
|
{
|
|
if (!lxmod)
|
|
return;
|
|
|
|
// !!! FIXME: mutex from here
|
|
lxmod->refcount--;
|
|
printf("unref'd module '%s' to %u\n", lxmod->name, (unsigned int) lxmod->refcount);
|
|
if (lxmod->refcount > 0)
|
|
return; // something is still using it.
|
|
|
|
if (lxmod->next)
|
|
lxmod->next->prev = lxmod->prev;
|
|
|
|
if (lxmod->prev)
|
|
lxmod->prev->next = lxmod->next;
|
|
|
|
if (lxmod == GLoadedModules)
|
|
GLoadedModules = lxmod->next;
|
|
// !!! 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);
|
|
|
|
free(lxmod->exported_names);
|
|
free(lxmod->exported_ordinals);
|
|
|
|
if (lxmod->nativelib)
|
|
dlclose(lxmod->nativelib);
|
|
|
|
free(lxmod);
|
|
} // freeLxModule
|
|
|
|
static LxModule *loadLxModuleByModuleNameInternal(const char *modname, const int dependency_tree_depth);
|
|
|
|
static uint32 getModuleProcAddrByOrdinal(const LxModule *module, const uint32 ordinal)
|
|
{
|
|
fprintf(stderr, "lookup module == '%s', ordinal == %u\n", module->name, (unsigned int) ordinal);
|
|
|
|
const LxExportedOrdinal *expord = module->exported_ordinals;
|
|
for (uint32 i = 0; i < module->num_ordinals; i++, expord++) {
|
|
if (expord->ordinal == ordinal)
|
|
return expord->addr;
|
|
} // for
|
|
|
|
#if 1
|
|
char entry[128];
|
|
snprintf(entry, sizeof (entry), "ORDINAL_%u", (unsigned int) ordinal);
|
|
return generateMissingTrampoline(module->name, entry);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
} // getModuleProcAddrByOrdinal
|
|
|
|
#if 0
|
|
static uint32 getModuleProcAddrByName(const LxModule *module, const char *name)
|
|
{
|
|
fprintf(stderr, "lookup module == '%s', name == '%s'\n", module->name, name);
|
|
|
|
const LxExportedName *expname = module->exported_names;
|
|
for (uint32 i = 0; i < module->num_names; i++, expname++) {
|
|
if (strcmp(expname->name, name) == 0)
|
|
return expname->addr;
|
|
} // for
|
|
|
|
#if 1
|
|
return generateMissingTrampoline(module->name, name);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
} // getModuleProcAddrByName
|
|
#endif
|
|
|
|
static void doFixup(uint8 *page, const uint16 offset, const uint32 finalval, const uint16 finalval2, const uint32 finalsize)
|
|
{
|
|
#if 1
|
|
if (finalsize == 6) {
|
|
printf("fixing up %p to to 0x%X:0x%X (6 bytes)...\n", page + offset, (unsigned int) finalval2, (unsigned int) finalval);
|
|
} else {
|
|
printf("fixing up %p to to 0x%X (%u bytes)...\n", page + offset, (unsigned int) finalval, (unsigned int) 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", (unsigned int) finalsize);
|
|
exit(1);
|
|
} // switch
|
|
} // doFixup
|
|
|
|
static void fixupPage(const uint8 *exe, LxModule *lxmod, const LxObjectTableEntry *obj, uint8 *page)
|
|
{
|
|
const LxHeader *lx = &lxmod->lx;
|
|
const uint32 *fixuppage = (((const uint32 *) (exe + lx->fixup_page_table_offset)) + (obj->page_table_index - 1));
|
|
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;
|
|
|
|
while (fixup < fixupend) {
|
|
const uint8 srctype = *(fixup++);
|
|
const uint8 fixupflags = *(fixup++);
|
|
uint8 srclist_count = 0;
|
|
uint16 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 = *((uint16 *) 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
|
|
|
|
// !!! FIXME: I have no idea if this is right at all.
|
|
// !!! FIXME: Is this guaranteed to not references object pages I haven't loaded/fixed up yet?
|
|
const LxObjectTableEntry *targetobj = ((const LxObjectTableEntry *) (exe + lx->object_table_offset)) + (objectid - 1);
|
|
const uint8 *ptr = (uint8 *) ((size_t) (targetobj->reloc_base_addr + targetoffset));
|
|
switch (finalsize) {
|
|
case 1: finalval = (uint32) *((uint8 *) ptr); break;
|
|
case 2: finalval = (uint32) *((uint16 *) ptr); break;
|
|
case 4: finalval = (uint32) *((uint32 *) ptr); break;
|
|
case 6: finalval2 = (uint32) *((uint16 *) ptr); finalval = (uint32) *((uint32 *) (ptr + 2)); break;
|
|
} // switch
|
|
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", (unsigned int) moduleid, (unsigned int) lx->num_import_mod_entries);
|
|
} else {
|
|
finalval = 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
|
|
|
|
uint32 additive = 0;
|
|
if (fixupflags & 0x4) { // Has additive.
|
|
if (fixupflags & 0x20) { // 32-bit value
|
|
additive = *((uint32 *) fixup); fixup += 4;
|
|
} else { // 16-bit value
|
|
additive = (uint32) *((uint16 *) fixup);
|
|
fixup += 2;
|
|
} // else
|
|
} // if
|
|
finalval += additive;
|
|
|
|
if (srctype & 0x20) { // source list
|
|
for (uint8 i = 0; i < srclist_count; i++) {
|
|
const uint16 offset = *((uint16 *) 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 LxModule *loadLxModule(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;
|
|
|
|
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
|
|
|
|
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.
|
|
if (++dependency_tree_depth > 32) {
|
|
fprintf(stderr, "Likely circular dependency in module '%s'\n", modname);
|
|
goto loadlx_failed;
|
|
} // if
|
|
|
|
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", (unsigned int) (i + 1), (unsigned int) 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
|
|
|
|
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 prot = ((obj->object_flags & 0x1) ? PROT_READ : 0) |
|
|
((obj->object_flags & 0x2) ? PROT_WRITE : 0) |
|
|
((obj->object_flags & 0x4) ? PROT_EXEC : 0);
|
|
|
|
void *base = (void *) ((size_t) obj->reloc_base_addr);
|
|
const int mmapflags = MAP_ANON | MAP_PRIVATE | MAP_FIXED;
|
|
void *mmapaddr = mmap(base, vsize, PROT_READ|PROT_WRITE, mmapflags, -1, 0);
|
|
// we'll mprotect() these pages to the proper permissions later.
|
|
|
|
if (mmapaddr == ((void *) MAP_FAILED)) {
|
|
fprintf(stderr, "mmap(%p, %u, RW-, ANON|PRIVATE|FIXED, -1, 0) failed (%d): %s\n",
|
|
base, (unsigned int) vsize, 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", (unsigned int) objpage->flags);
|
|
goto loadlx_failed;
|
|
} // switch
|
|
|
|
// Now run any fixups for the page...
|
|
fixupPage(exe, retval, obj, dst);
|
|
|
|
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
|
|
|
|
// Now set all the pages of this object to the proper final permissions...
|
|
if (mprotect(mmapaddr, vsize, prot) == -1) {
|
|
fprintf(stderr, "mprotect(%p, %u, %s%s%s, ANON|PRIVATE|FIXED, -1, 0) failed (%d): %s\n",
|
|
base, (unsigned int) vsize,
|
|
(prot&PROT_READ) ? "R" : "-",
|
|
(prot&PROT_WRITE) ? "W" : "-",
|
|
(prot&PROT_EXEC) ? "X" : "-",
|
|
errno, strerror(errno));
|
|
goto loadlx_failed;
|
|
} // 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 LxObjectTableEntry *targetobj = ((const LxObjectTableEntry *) (exe + lx->object_table_offset)) + (lx->eip_object - 1);
|
|
retval->eip += targetobj->reloc_base_addr;
|
|
} // 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"
|
|
retval->esp = lx->esp;
|
|
if (lx->esp_object != 0) { // !!! FIXME: ignore for libraries
|
|
const LxObjectTableEntry *targetobj = ((const LxObjectTableEntry *) (exe + lx->object_table_offset)) + (lx->esp_object - 1);
|
|
retval->esp += targetobj->reloc_base_addr;
|
|
} // if
|
|
|
|
// module is ready to use, put it in the loaded list.
|
|
// !!! FIXME: mutex this
|
|
if (GLoadedModules) {
|
|
retval->next = GLoadedModules;
|
|
GLoadedModules->prev = retval;
|
|
} // if
|
|
GLoadedModules = retval;
|
|
|
|
return retval;
|
|
|
|
loadlx_failed:
|
|
freeLxModule(retval);
|
|
return NULL;
|
|
} // loadLxModule
|
|
|
|
static LxModule *loadLxModuleByPathInternal(const char *fname, const int dependency_tree_depth)
|
|
{
|
|
const char *what = NULL;
|
|
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(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 *loadNativeReplacement(const char *fname)
|
|
{
|
|
// !!! FIXME: mutex this.
|
|
void *lib = dlopen(fname, RTLD_LOCAL | RTLD_NOW);
|
|
if (lib == NULL)
|
|
return NULL;
|
|
|
|
LxNativeReplacementEntryPoint fn = (LxNativeReplacementEntryPoint) dlsym(lib, "loadNativeLxModule");
|
|
if (!fn) {
|
|
dlclose(lib);
|
|
return NULL;
|
|
} // if
|
|
|
|
LxModule *retval = fn();
|
|
if (retval) {
|
|
printf("Loaded native replacement library '%s' (%u exports).\n", fname, (unsigned int) retval->num_ordinals);
|
|
retval->nativelib = lib;
|
|
} // if
|
|
|
|
return retval;
|
|
} // loadNativeReplacement
|
|
|
|
static LxModule *loadLxModuleByModuleNameInternal(const char *modname, const int dependency_tree_depth)
|
|
{
|
|
// !!! FIXME: mutex this
|
|
for (LxModule *i = GLoadedModules; i != NULL; i = i->next) {
|
|
if (strcasecmp(i->name, modname) == 0) {
|
|
i->refcount++;
|
|
printf("ref'd module '%s' to %u\n", i->name, (unsigned int) 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 = loadLxModuleByPathInternal(fname, dependency_tree_depth);
|
|
if (!retval) {
|
|
snprintf(fname, sizeof (fname), "native/%s.so", modname);
|
|
for (char *ptr = fname; *ptr; ptr++) {
|
|
*ptr = (((*ptr >= 'A') && (*ptr <= 'Z')) ? (*ptr - ('A' - 'a')) : *ptr);
|
|
} // for
|
|
retval = loadNativeReplacement(fname);
|
|
} // if
|
|
return retval;
|
|
} // loadLxModuleByModuleNameInternal
|
|
|
|
static inline LxModule *loadLxModuleByModuleName(const char *modname)
|
|
{
|
|
return loadLxModuleByModuleNameInternal(modname, 0);
|
|
} // loadLxModuleByModuleName
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
if (argc != 2) {
|
|
fprintf(stderr, "USAGE: %s <program.exe>\n", argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
const LxModule *lxmod = loadLxModuleByPath(argv[1]);
|
|
if (lxmod) {
|
|
argc--;
|
|
argv++;
|
|
runLxModule(lxmod, argc, argv);
|
|
} // if
|
|
|
|
return 0;
|
|
} // main
|
|
|
|
// end of lx_loader.c ...
|
|
|