#define _GNU_SOURCE 1 #include #include #include #include #include #include #include #include #include "lx_loader.h" // !!! FIXME: move this into an lx_common.c file. static int sanityCheckExe(uint8 **_exe, uint32 *_exelen) { if (*_exelen < 196) { fprintf(stderr, "not an OS/2 LX EXE\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 EXE\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 EXE\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 EXE 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; } // sanityCheckExe /* 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 void missing_ordinal_called(const uint32 ordinal) { fflush(stdout); fflush(stderr); fprintf(stderr, "\n\nMissing ordinal '%u' called!\n", ordinal); fprintf(stderr, "Aborting.\n\n\n"); //STUBBED("output backtrace"); fflush(stderr); _exit(1); } // missing_ordinal_called static uint32 DosPutMessage(uint32 handle, uint32 msglen, const char *msg) { // !!! FIXME: this isn't right, but good enough for now. fwrite(msg, msglen, 1, stdout); fflush(stdout); return 0; } // DosPutMessage static uint32 getModuleProcAddrByOrdinal(const uint16 moduleid, const uint32 importid) { // !!! FIXME: write me for real. if (moduleid != 1) { // this is MSG in hello.exe, for testing purposes. fprintf(stderr, "uhoh, looking for module ordinal that isn't #1.\n"); return 0; } // if //fprintf(stderr, "importid == %u\n", (unsigned int) importid); 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 if (importid == 5) // !!! FIXME: this is DosPutMessage in the MSG module. return (uint32) (size_t) DosPutMessage; void *trampoline = page + pageused; char *ptr = (char *) trampoline; const uint32 ordinal = importid; *(ptr++) = 0x55; // pushl %ebp *(ptr++) = 0x89; // movl %esp,%ebp *(ptr++) = 0xE5; // ...movl %esp,%ebp *(ptr++) = 0x68; // pushl immediate memcpy(ptr, &ordinal, sizeof (uint32)); ptr += sizeof (uint32); *(ptr++) = 0xB8; // movl immediate to %eax const void *fn = missing_ordinal_called; 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)); return (uint32) (size_t) trampoline; } // getModuleProcAddrByOrdinal static void doFixup(uint8 *page, const uint16 offset, const uint32 finalval, const uint16 finalval2, const uint32 finalsize) { #if 0 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 LxHeader *lx, const LxObjectTableEntry *obj, uint8 *page) { const uint8 *exe = (const uint8 *) lx; // this is the start of the LX EXE (past the DOS stub, etc). 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 finalval = getModuleProcAddrByOrdinal(moduleid, importid); 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 __attribute__((noreturn)) void loadExe(const char *exefname, uint8 *exe, uint32 exelen) { const uint8 *origexe = exe; //const uint32 origexelen = exelen; if (!sanityCheckExe(&exe, &exelen)) exit(1); const LxHeader *lx = (const LxHeader *) exe; const LxObjectTableEntry *obj = ((const LxObjectTableEntry *) (exe + lx->object_table_offset)); for (uint32 i = 0; i < lx->module_num_objects; i++, obj++) { 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); const int mmapflags = MAP_ANON | MAP_PRIVATE | MAP_FIXED; void *base = (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. 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)); exit(1); } // if 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"); exit(1); } // 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); exit(1); } // switch // Now run any fixups for the page... fixupPage(lx, 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)); exit(1); } // 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"); uint32 eip = lx->eip; if (lx->eip_object != 0) { const LxObjectTableEntry *targetobj = ((const LxObjectTableEntry *) (exe + lx->object_table_offset)) + (lx->eip_object - 1); eip += targetobj->reloc_base_addr; } // if // !!! FIXME: esp==0 means something special for programs (and is ignored for library init). uint32 esp = lx->esp; assert(esp != 0); if (lx->esp_object != 0) { // !!! FIXME: ignore for libraries const LxObjectTableEntry *targetobj = ((const LxObjectTableEntry *) (exe + lx->object_table_offset)) + (lx->esp_object - 1); esp += targetobj->reloc_base_addr; } // if // !!! 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 static char *env = "TEST=abc\0\0hello.exe\0hello.exe\0\0"; char *cmd = env + 10; // !!! FIXME // ...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) eip, (unsigned int) 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" (esp), "D" (eip) : "memory" ); __builtin_unreachable(); } // loadExe int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "USAGE: %s \n", argv[0]); return 1; } const char *exefname = argv[1]; FILE *io = fopen(exefname, "rb"); if (!io) { fprintf(stderr, "can't open '%s: %s'\n", exefname, strerror(errno)); return 2; } if (fseek(io, 0, SEEK_END) < 0) { fprintf(stderr, "can't seek in '%s': %s\n", exefname, strerror(errno)); return 3; } const uint32 exelen = ftell(io); uint8 *exe = (uint8 *) malloc(exelen); if (!exe) { fprintf(stderr, "Out of memory\n"); return 4; } rewind(io); if (fread(exe, exelen, 1, io) != 1) { fprintf(stderr, "read failure on '%s': %s\n", exefname, strerror(errno)); return 5; } fclose(io); loadExe(exefname, exe, exelen); free(exe); return 0; } // main // end of lx_loader.c ...