Date: Sun, 11 Aug 1996 12:47:10 +0200 (IST) From: Eli Zaretskii To: DJ Delorie Cc: djgpp-workers AT delorie DOT com Subject: go32-v2 from 960725 Message-Id: Mime-Version: 1.0 Content-Type: TEXT/PLAIN; charset=US-ASCII I've succeeded at the last moment to break the one thing that go32-v2 was originally written for: to run un-stubbed COFF images. If you try doing this with go32-v2 from the latest alpha, it will crash your machine. What kind of drugs was I on when I did the last changes there?? And how in the world could it run for me without crashing??? Anyway, here is the corrected version. Please forget you ever saw the previous one. ---------------------------- cut here --------------------------------- /* Copyright (C) 1996 DJ Delorie, see COPYING.DJ for details */ /* Copyright (C) 1995 DJ Delorie, see COPYING.DJ for details */ /* GO32V2 - A quick hack for loading non-stubbed images for V2.0 Charles Sandmann 6/95 NO WARRANTY - build with -ldbg Eli Zaretskii 6/96 fix code which runs V1 EXE and COFF. Eli Zaretskii 7/96 fix code which runs V2 COFF. Eli Zaretskii 8/96 *really* fix code which runs V2 COFF; support for V2.1 long command lines. Bugs: doesn't scan manifest for versions installed yet */ /* If you want to change this, remember to test it with all the various ways `go32' can be called. The following is the list of different cases I know about: go32 is called from DOS prompt without any arguments go32 is called from DOS prompt to run unstubbed COFF image of a v2 program go32 is called from DOS prompt to debug a v1 COFF image (go32 -d) v2's Make calls go32 to run unstubbed COFF image of a v2 program v1's Make calls go32 to run unstubbed COFF image of a v2 program 16-bit Make calls go32 to run unstubbed COFF image of a v2 program v2's Make calls go32 to run unstubbed COFF image of a v1 program v1's Make calls go32 to run unstubbed COFF image of a v1 program 16-bit Make calls go32 to run unstubbed COFF image of a v1 program v1 .EXE program is called from the DOS prompt v1 symlink is called from the DOS prompt v2's Make calls a v1 .EXE program v1's Make calls a v1 .EXE program 16-bit Make calls a v1 .EXE program when v2's Make calls go32, you need to test command lines which are both shorter and longer than the DOS 126-character limit Note that there is nothing special about Make, it just serves as an example of one program that calls another. It is convenient to use Make to test the above cases, because Make is available as both v2 and v1 executable and as a 16-bit program, and because it can be easily used to call different programs with different command lines by tweaking a Makefile. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /*======================================================================*/ #define IMG_ERROR 0 #define IMG_EXE 1 #define IMG_COFF 2 #define IMG_V2 4 #define IMG_UNKNOWN 8 #define IMG_PLAIN_EXE (IMG_EXE | IMG_UNKNOWN) #define IMG_EXE_V1 (IMG_EXE | IMG_COFF) #define IMG_EXE_V2 (IMG_EXE | IMG_COFF | IMG_V2) #define IMG_COFF_V1 (IMG_COFF) #define IMG_COFF_V2 (IMG_COFF | IMG_V2) /* Non-zero if debugging printout is required. */ static int verbose = 0; /* Non-zero if we were called by the !proxy method. */ static int proxy_call = 0; /* If we were called as `go32', holds the length of go32 full pathname. */ static int go32_len = 0; /* Read the given file and determine what kind of program it is. If IS_GO32 is non-zero, we *know* that IMAGE should be v1's go32.exe (therefore it cannot be a v1 symlink). */ static int check_image_version(char *image, int is_go32) { int rv = 0; unsigned short header[3]; unsigned char firstbytes[1]; unsigned long coffhdr[42]; int pf, i; int coff_offset, text_foffset; if (verbose) fprintf (stderr, "Hmmm... `%s\': ", image); errno = 0; pf = open(image, O_RDONLY|O_BINARY); if(pf < 0) { if (verbose) fprintf (stderr, "(error: %s)\n", strerror(errno)); return IMG_ERROR; } /* See if it has an EXE header */ read(pf, header, sizeof(header)); if (header[0] == 0x5a4d) /* MZ exe signature, stubbed? */ { if (verbose) fprintf (stderr, "MZ, "); coff_offset = (long)header[2]*512L; if (header[1]) coff_offset += (long)header[1] - 512L; lseek(pf, coff_offset, 0); read(pf, header, sizeof(header)); rv |= IMG_EXE; } else coff_offset = 0; /* See if it has a COFF header (maybe after exe) */ if (header[0] != 0x014c) /* COFF? */ { /* We need to pretend that v1 symlinks are v1 executables, to avoid endless looping, unless we were called as "go32 !proxy...", in which case we *know* it cannot be a symlink. */ int symlink = proxy_call && !is_go32; close(pf); if (verbose) fprintf (stderr, "without COFF signature%s\n", symlink ? ", V1.x symlink" : ""); if (symlink) return rv | IMG_COFF; else return rv | IMG_UNKNOWN; } if (verbose) fprintf (stderr, "COFF, "); rv |= IMG_COFF; /* Read the COFF header */ errno = 0; lseek(pf, coff_offset, 0); i = read(pf, coffhdr, 0x0a8); if (i != 0x0a8) { close(pf); if (verbose) fprintf (stderr, "(error reading COFF header: %s)\n", strerror(errno)); return rv | IMG_UNKNOWN; } /* See what the first opcode is */ text_foffset = coff_offset + coffhdr[12 + 5]; /* scnptr */ errno = 0; lseek(pf, text_foffset, 0); read(pf, firstbytes, 1); if (errno) { if (verbose) fprintf (stderr, "(error reading 1st opcode: %s)\n", strerror(errno)); return rv | IMG_UNKNOWN; } if (firstbytes[0] == 0xa3) /* Opcode for movl %eax, 0x12345678 (V1) */ { close(pf); if (verbose) fprintf (stderr, "V1.x\n"); return rv; } if (verbose) fprintf (stderr, "V2\n"); return rv | IMG_V2; } /*======================================================================*/ static int far_strlen(int selector, int linear_addr) { int save=linear_addr; _farsetsel(selector); while (_farnspeekb(linear_addr)) linear_addr++; return linear_addr - save; } extern char **environ; /* The original DOS command-line tail. */ char DosCmdLine[128]; /* Points to the first non-blank character of the DOS command-line tail. */ char *argv1_start = &DosCmdLine[1]; char PROXY_STRING[] = "!proxy "; /* We were called when the V1 go32 should have been called. Call it with the same arguments we were passed */ int run_v1_coff(int argc, char **argv) { char *path = getenv("PATH"); char *tokbuf = alloca(strlen(path)+1); char *dir; char *cmdbuf = alloca(strlen(argv1_start)+sizeof(PROXY_STRING)); strcpy(tokbuf, path); if (strncmp(argv1_start, PROXY_STRING, sizeof(PROXY_STRING)-1) == 0) strcpy(cmdbuf, argv1_start); else { /* We should never get here, because v1 programs aren't run with !proxy in the environment. But let's be defensive... */ strcpy(cmdbuf, PROXY_STRING); strcpy(cmdbuf + sizeof(PROXY_STRING) - 1, argv1_start); } /* we don't check "." because if v1's go32 was in "." we would never get a chance to run. */ for (dir=strtok(tokbuf, ";"); dir; dir=strtok(0, ";")) { char *cp; char tmp[300]; strcpy(tmp, dir); cp = tmp + strlen(tmp) - 1; if (*cp != ':' && *cp != '/' && *cp != '\\') *++cp = '/'; strcpy(cp+1, "go32.exe"); if (check_image_version(tmp, 1) == IMG_PLAIN_EXE) { /* We will try to pass the v1's go32 the original command line as we got it (before our startup code built the argv[] array). */ if (!go32_len || !proxy_call) { if (verbose) fprintf (stderr, "Exec: `%s %s\'\n", tmp, argv1_start); return _dos_exec(tmp, argv1_start, environ); } else { int proxy_argc, proxy_seg, proxy_ofs, i; unsigned short *rm_argv; char **arglist; /* Now, for the tricky part. When a v2 program calls us, it sees that we are a v2 image, and constructs the `!proxy' arguments accordingly. This means the arguments pointed to by `!proxy' include the pathname of v2's go32. We need to bump the SEG:OFF pointer into the transfer buffer so that the v2's go32 pathname is not seen by v1's go32. How? see below. */ if (sscanf (cmdbuf + 7, "%x %x %x", &proxy_argc, &proxy_seg, &proxy_ofs) != 3) { fprintf (stderr, "go32/v2: malformed !proxy command line\n"); return -1; } /* Pull in the full command line from the transfer buffer (actually, only needed for verbose operation). */ rm_argv=(unsigned short *)alloca(proxy_argc*sizeof(unsigned short)); arglist = (char **)alloca (proxy_argc * sizeof (char *)); movedata(_dos_ds, proxy_seg * 16 + proxy_ofs, _my_ds(), (int)rm_argv, proxy_argc*sizeof(unsigned short)); for (i = 0; i < proxy_argc; i++) { int al = far_strlen(_dos_ds, proxy_seg*16 + rm_argv[i]); char *arg = (char *)alloca(al+1); movedata(_dos_ds, proxy_seg*16 + rm_argv[i], _my_ds(), (int)(arg), al+1); arglist[i] = arg; if (verbose) fprintf (stderr, "%s ", arg); } if (verbose) fprintf (stderr, "\n"); /* `rm_argv[i]' is the offset into the transfer buffer of the i'th argument from the command line. We need to bump `proxy_ofs' so that it points to `rm_argv[1]' instead of `rm_argv[0]'. This way, the first argument that will be seen by v1's go32 will be the image to run, not the pathname of v2's go32. */ proxy_ofs += sizeof (rm_argv[0]); sprintf (cmdbuf + 7, "%04x %04x %04x", proxy_argc - 1, proxy_seg, proxy_ofs); if (verbose) fprintf (stderr, "Exec: `%s %s\'\n", tmp, cmdbuf); return _dos_exec(tmp, cmdbuf, environ); } } } fprintf(stderr, "go32/v2: cannot find v1's go32.exe\n"); return -1; } /*======================================================================*/ /* It was an unstubbed V2 COFF file. Use v2load to run it */ int run_v2_coff(int argc, char **argv) { jmp_buf start_state; int rm_la; short *rm_argv; char newcmdline[24]; char fullpath[FILENAME_MAX]; int i, sl, q, cmdlen; char *argv0 = argv[0]; int tbuf, max_dos_mem; /* The actual command line might be longer than 126 characters, so we cannot call `v2loadimage' with the full argv[] array as the command line. We also cannot call it with the original DOS command line, because after expansion by the startup code of the image we load it will have the image name as both argv[0] and argv[1] and all other arguments after it. Response file cannot be used either, because the thread never returns to us after we longjmp, so we don't get to delete the response file. The only way I know out of this mess is to pass the argv[] array (sans argv[1]) via the !proxy method. Sigh.. (The code was shamelessly stolen from src/libc/dis/process/dosexec.c). */ /* Make argv[0] explicit (why not?). */ _fixpath (argv[0], fullpath); argv[0] = fullpath; /* Can't use the usual transfer buffer, because `v2loadimage' will overwrite it. Allocate our own buffer, for as much bytes as we need. */ for (cmdlen = sizeof (short), i = 0; i < argc; i++) cmdlen += strlen (argv[i]) + 1 + sizeof (short); tbuf = __dpmi_allocate_dos_memory ((cmdlen + 15) >> 4, &max_dos_mem); if (tbuf == -1) { fprintf (stderr, "Not enough DOS memory to pass args to %s\n", argv[0]); if (verbose) fprintf (stderr, "(Need %d bytes, only %d available)\n", (cmdlen + 15) & 0xfffffff0, max_dos_mem << 4); argv[0] = argv0; return -1; } rm_la = (tbuf << 4) & 0xfffff; q = rm_la + (argc + 1) * sizeof (short); rm_argv = (short *)alloca ((argc + 1) * sizeof (short)); for (i = 0; i < argc; i++) { sl = strlen (argv[i]) + 1; dosmemput (argv[i], sl, q); rm_argv[i] = (q - rm_la) & 0xffff; q += sl; } rm_argv[i] = 0; dosmemput (rm_argv, (argc + 1) * sizeof (short), rm_la); newcmdline[0] = 22; sprintf (newcmdline + 1, " %s%04x %04x 0000", PROXY_STRING, argc, tbuf); if (verbose) fprintf (stderr, "V2Load %s%s\n", argv[0], newcmdline + 1); newcmdline[23] = '\n'; if(v2loadimage(argv0, newcmdline, start_state)) { fprintf(stderr, "Load failed for image %s\n",argv[0]); argv[0] = argv0; return -1; } argv[0] = argv0; longjmp(start_state, 0); return 0; } /*======================================================================*/ /* Save on space, don't expand command-line wildcards. */ char **__crt0_glob_function(char *argument) { return 0; } void __crt0_load_environment_file(char *app) {} int main(int argc, char **argv) { int i; char *tail; char *proxy_ev = getenv(" !proxy"); __djgpp_set_ctrl_c(0); /* Debugging printout, anyone? */ if (getenv ("GO32_V2_DEBUG")) verbose = 1; /* Get the original DOS command-line tail. */ dosmemget(_go32_info_block.linear_address_of_original_psp+128, 128, DosCmdLine); DosCmdLine[1+DosCmdLine[0]] = 0; /* Get past any whitespace in DOS command line. */ argv1_start = &DosCmdLine[1]; while (*argv1_start && isspace(*argv1_start)) argv1_start++; if (verbose) { fprintf (stderr, "Called as `%s\'\n", argv[0]); fprintf (stderr, "DOS CmdTail: `%s\'\n", argv1_start); } /* !proxy in the environment overrides the command line. */ if (proxy_ev) { argv1_start = proxy_ev; proxy_call = 1; if (verbose) { fprintf (stderr, "Environ CmdTail: `%s\'\n", argv1_start); } while (*argv1_start && isspace(*argv1_start)) argv1_start++; } else if (strncmp(argv1_start, PROXY_STRING, sizeof(PROXY_STRING)-1) == 0) { proxy_call = 1; } /* Are we called as GO32? If we are, then the *real* image is in `argv[1]'. */ for (tail = argv[0] + strlen (argv[0]); tail > argv[0]; tail--) if (*tail == '/' || *tail == '\\') { ++tail; break; } /* I don't want to rely too much on the way argv[0] looks like. `go32', `go32.exe', `Go32.EXE'--I think all of these should be OK. */ if (strlen(tail) >= 4 && strnicmp (tail, "go32", 4) == 0 && (tail[4] == '.' || tail[4] == '-' || tail[4] == '\0')) { go32_len = strlen (argv[0]); ++argv; --argc; } /* `go32 -d' means the *real* image (the debugger) is after `-d'. */ if (go32_len && argc > 0 && strcmp (argv[0], "-d") == 0) { ++argv; --argc; } if (argc < 1) { printf("go32/v2 version %s built %s %s\n","2.0",__DATE__,__TIME__); printf("Usage: go32 coff-image [args]\n"); printf("Rename this to go32.exe only if you need a go32 that can run v2 binaries as\n" " well as v1 binaries (old makefiles). Put ahead of the old go32 in your PATH\n" " but do not delete your old go32 - leave it in the PATH after this one.\n"); printf("Set GO32_V2_DEBUG=y in the environment to get verbose output.\n\n"); /* Add the memory that we use for ourselves to the free amount. */ i = (_go32_dpmi_remaining_physical_memory() + (int)sbrk(0))/1024; printf("DPMI memory available: %d Kb\n",i); i = _go32_dpmi_remaining_virtual_memory()/1024-i; if(i < 0) i = 0; printf("DPMI swap space available: %d Kb\n", i); return 1; } switch (check_image_version(argv[0], 0)) { case IMG_UNKNOWN: fprintf(stderr, "go32/v2: Unknown file type: %s\n", argv[0]); return -1; case IMG_ERROR: fprintf(stderr, "go32/v2: Error: %s: %s\n", argv[0], strerror(errno)); return -1; case IMG_COFF_V2: return run_v2_coff(argc, argv); case IMG_COFF_V1: case IMG_EXE_V1: return run_v1_coff(argc, argv); case IMG_PLAIN_EXE: case IMG_EXE_V2: return spawnv(P_WAIT, argv[0], argv); } return 1; }