diff --git a/Makefile b/Makefile index c7a6b8a..66c941f 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ -.PHONY: all clean run +.PHONY: all clean run test test-batch test-mem test-cli test-cli-matrix test-static test-sanitizers test-valgrind test-tooling .SUFFIXES: MAKEFLAGS += -r ALL = kvikdos guest.com slowp.com malloct.com mallocs.com printenv.com cat.com waitkey.com # -Werror=int-conversion: GCC 4.8.4 fails. -CFLAGS = -ansi -pedantic -s -O2 -W -Wall -Wextra -Werror=implicit-function-declaration -fno-strict-aliasing -Wno-overlength-strings $(XCFLAGS) +CFLAGS = -ansi -pedantic -s -O2 -W -Wall -Wextra -Wuninitialized -Wmaybe-uninitialized -Werror -fno-strict-aliasing -Wno-overlength-strings $(XCFLAGS) XCFLAGS = # To be overridden from the command-line. SRCDEPS = kvikdos.c mini_kvm.h @@ -18,6 +18,31 @@ clean: run: kvikdos guest.com ./kvikdos guest.com hello world +test: test-batch test-mem test-cli test-cli-matrix + +test-batch: kvikdos + ./tests/test_batch.sh ./kvikdos + +test-mem: kvikdos + ./tests/test_mem_services.sh ./kvikdos + +test-cli: kvikdos + ./tests/test_cli_parse.sh ./kvikdos + +test-cli-matrix: kvikdos + ./tests/test_cli_matrix.sh ./kvikdos + +test-static: + ./tests/test_static.sh + +test-sanitizers: + ./tests/test_sanitizers.sh + +test-valgrind: kvikdos + ./tests/test_valgrind.sh ./kvikdos + +test-tooling: test-static test-sanitizers test-valgrind + %.com: %.nasm nasm -O0 -f bin -o $@ $< diff --git a/README.txt b/README.txt index f5de331..3924f38 100644 --- a/README.txt +++ b/README.txt @@ -46,9 +46,9 @@ Limitations: (last 768 bytes), BIOS Data Area, helper code, the user code written for Linux. - kvikdos doesn't support memory types UMB (up to 384 KiB more), HMA (63 - KiB more), XMS (up to hundreds of megabytes more) or EMS (also up to - hundres of megabytes, overlapping with XMS). + kvikdos has partial compatibility support for UMB/XMS/EMS probes and + common allocator APIs used by DOS toolchains, but it is not a full + memory-manager implementation (no full DPMI/VCPI stack). If your DOS programs need mre memory, use udosrun or DOSBox instead. @@ -72,6 +72,12 @@ Limitations: already work, see the compatibility list below. To get good chances for running any random DOS program, use udosrun or DOSBox instead. +* If the target file is Linux-native (ELF or shebang script), kvikdos + executes it natively with Linux `fork+execvp` instead of DOS emulation. + +* If the target file is a Windows executable format (PE/NE/LE/LX), kvikdos + delegates execution to `wine` automatically. + Features and advantages: * kvikdos is very fast, it runs CPU-intensive code at almost native speeds @@ -146,6 +152,123 @@ How to install kvikdos: If it displays the dot and the `Hello, World!' message, then it's OK. +Command-line options (structured): + +General: + +* `--kvm-check' checks KVM only. +* `--strict' enables strict unsupported-interrupt behavior. +* `--permissive' enables compatibility fallback behavior (default). + +DOS runtime: + +* `--toolchain=' applies toolchain presets: + `msc4', `msc5', `msc6', `masm5', `bc2', `bcpp1', `bc5', `ic86'. +* `--env==' adds one DOS environment variable. +* `--env-file=' loads DOS environment variables from file + (`NAME=VALUE' lines). +* `--path-dos=' sets DOS PATH explicitly. +* `--prog=' sets DOS pathname of the running program. +* `--cwd-dos=' sets initial DOS current directory. + +Mounts: + +* `--mount=/' mounts Linux directory to DOS drive. +* `--mount=0' hides DOS drive. +* `--drive=' sets initial DOS drive. + +Compatibility: + +* `--case-fallback=off|prog|all' controls case-insensitive executable + lookup fallback. Default: `all'. + +Diagnostics: + +* `--diag=compat|exec|int|fs|all|off' controls runtime diagnostics. + Default: `compat'. +* `--diag-file=' writes diagnostics to a file. + +I/O and memory: + +* `--tty-in=' chooses input source (`-3', `-2', `-1', `>=0'). +* `--mem-mb=' DOS memory size in MiB (currently only `1'). +* `--hlt-ok' and `--hlt-dump=' are low-level debug options. + +Best defaults: + +* `--permissive' +* `--case-fallback=all' +* `--diag=compat' +* no toolchain preset unless requested + +Examples: + +* Microsoft C 5.1: + + $ ./kvikdos --toolchain=msc5 /home/xor/inertia_player/dos_compilers/Microsoft\ C\ v5/CL.EXE HELLO.C + +* Borland C 2 with explicit DOS PATH and cwd: + + $ ./kvikdos --path-dos=C:\\ --cwd-dos=C:\\ /home/xor/inertia_player/dos_compilers/Borland\ Turbo\ C\ v2/TCC.EXE HELLO.C + +* Diagnostics to file: + + $ ./kvikdos --diag=all --diag-file=kvikdos.diag /home/xor/inertia_player/masm/BIN/LINK.EXE + +Batch regression tests: + +* Run all local tests: + + $ make test + +* Current batch suite (`tests/test_batch.sh`) covers commands/patterns used + by DOS compiler scripts: `set`, `%VAR%`, `%1..%9`, `shift`, `call`, + `if` (`==`, `not`, `errorlevel`, `exist`), `goto`, `mkdir`, `copy`, `del`. + +Static analysis and runtime checks: + +* cppcheck: + + $ cppcheck --enable=warning,style,performance,portability --std=c89 --force kvikdos.c + +* clang static analyzer: + + $ clang --analyze -Xanalyzer -analyzer-output=text -std=c89 -Wall -Wextra kvikdos.c + +* ASan+UBSan build and compiler smoke: + + $ gcc -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer -fno-strict-aliasing -o kvikdos_asan kvikdos.c + $ /home/xor/kvikdos/kvikdos_asan /home/xor/inertia_player/dos_compilers/Microsoft\ C\ v5/CL.EXE /c HELLO0.C + $ /home/xor/kvikdos/kvikdos_asan /home/xor/inertia_player/dos_compilers/Microsoft\ MASM\ v5/BIN/MASM.EXE HELLO.ASM,HELLO.OBJ,NUL.LST,NUL.CRF + +* Valgrind smoke: + + $ valgrind --tool=memcheck --leak-check=full --error-exitcode=101 ./kvikdos /home/xor/inertia_player/dos_compilers/Microsoft\ C\ v5/CL.EXE /c HELLO0.C + $ valgrind --tool=memcheck --leak-check=full --error-exitcode=101 ./kvikdos /home/xor/inertia_player/dos_compilers/Microsoft\ MASM\ v5/BIN/MASM.EXE HELLO.ASM,HELLO.OBJ,NUL.LST,NUL.CRF + +Readability and stability improvement roadmap (recommended): + +* Priority 1 (safety): + * Initialize all state structs (`DirState`, `ParsedCmdArgs`, runtime locals) + before first use; avoid partial-init patterns. + * Keep parser regression tests for option combinations (`--prog`, mounts, + drive/cwd/path interactions) and assert "no crash" behavior. + * Prefer bounded copy helpers (`copy_cstr0`) over ad-hoc `strncpy` usage. + +* Priority 2 (clarity): + * Split large functions (`parse_args`, `run_dos_prog`, `run_dos_batch`) + into smaller helpers by responsibility: + argument parsing, DOS path translation, int21 dispatch, batch command eval. + * Replace ambiguous locals (`p`, `q`, `r`) with intent names in new code + (`requested_drive`, `active_drive`, `resolved_prog`). + * Keep compatibility fallbacks local and commented at point of use. + +* Priority 3 (analysis depth): + * Add a fast static target in CI: `cppcheck` + `clang --analyze`. + * Add sanitizer targets (ASan+UBSan) and valgrind jobs as separate steps. + * Add negative tests for malformed paths/env/flags to verify deterministic + error messages and exit codes. + About making Linux files available for DOS programs: * kvikdos emulates DOS drives A: .. F: by exposing directories on the Linux @@ -217,6 +340,27 @@ About uppercase and lowercase filenames: Software compatibility, i.e. DOS programs known to work in kvikdos: +Quick start for DOS compilers and assemblers: + +* For practical, copy-paste command lines, see TOOLCHAIN.md in this + repository. + +* Recommended layout rules: + * Use 8.3 filenames for source/object/output files (e.g. HELLO.C). + * If a toolchain has BIN/LIB/INCLUDE directories, mount the toolchain + root as C: and run the executable from C:\BIN\... + * Set DOS environment explicitly with --env=LIB=... and + --env=INCLUDE=... + +* Recently validated under kvikdos (compile, link, run of a tiny HELLO): + * Microsoft C 4.0 + * Microsoft C 5.1 + * Microsoft C 6ax (from toolchain root, C:\BIN\CL.EXE, LIB=C:\LIB) + * Intel iC-86 Compiler 4.5 + * Borland Turbo C 2.0 + * Borland Turbo C++ 1.01 (from toolchain root, C:\BIN\TCC.EXE, LIB=C:\LIB) + * Microsoft MASM 5.0 (MASM direct, LINK via prompt answers or stdin) + * Turbo Pascal 7.0 compiler tpc.exe. It produces .exe program files directly. diff --git a/TOOLCHAIN.md b/TOOLCHAIN.md new file mode 100644 index 0000000..33a770f --- /dev/null +++ b/TOOLCHAIN.md @@ -0,0 +1,102 @@ +# kvikdos Toolchain Guide + +This document shows known-good ways to run DOS C/ASM toolchains under `kvikdos`. + +## Key rules + +- Use DOS 8.3 names for source/object/output files when possible. +- Mount the correct directory as `C:` with `--mount=C:.` from that directory. +- For toolchains with separate `BIN`, `LIB`, `INCLUDE`, mount their parent root and call executables as `C:\BIN\...`. +- Set `LIB` and `INCLUDE` explicitly with `--env=...`. + +## Minimal C source + +Use this for widest compatibility: + +```c +main(){return 0;} +``` + +## Microsoft C 4.0 + +```bash +cd '/home/xor/inertia_player/dos_compilers/Microsoft C v4' +printf 'main(){return 0;}\n' > HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. --env=LIB=lib --env=INCLUDE=inc C:\CL.EXE /c HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. --env=LIB=lib C:\LINK.EXE HELLO,,,SLIBFP +/home/xor/kvikdos/kvikdos --mount=C:. C:\HELLO.EXE +``` + +## Microsoft C 5.1 + +```bash +cd '/home/xor/inertia_player/dos_compilers/Microsoft C v5' +printf 'main(){return 0;}\n' > HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. --env=LIB=lib --env=INCLUDE=inc C:\CL.EXE /c HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. --env=LIB=lib C:\LINK.EXE HELLO,,,SLIBCE +/home/xor/kvikdos/kvikdos --mount=C:. C:\HELLO.EXE +``` + +## Microsoft C 6ax + +```bash +cd '/home/xor/inertia_player/dos_compilers/Microsoft C v6ax' +printf 'main(){return 0;}\n' > HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. --env=LIB=C:\LIB --env=INCLUDE=C:\INCLUDE C:\BIN\CL.EXE /c /AS HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. --env=LIB=C:\LIB C:\BIN\LINK.EXE HELLO,,,SLIBCE +/home/xor/kvikdos/kvikdos --mount=C:. C:\HELLO.EXE +``` + +## Intel iC-86 Compiler v4.5 + +```bash +cd '/home/xor/inertia_player/dos_compilers/Intel iC-86 Compiler v4.5' +printf 'main(){return 0;}\n' > HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. C:\IC86.EXE HELLO.C small optimize\(3\) mod86 diagnostic\(2\) define\(INTELC\) +/home/xor/kvikdos/kvikdos --mount=C:. C:\LINK86.EXE libs\cstdoss.obj,HELLO.obj,c:\libs\cdoss.lib,u\cel87.lib,u\e8087.lib,u\de8087,libs\clib87.lib TO HELLO.EXE exe +/home/xor/kvikdos/kvikdos --mount=C:. C:\HELLO.EXE +``` + +## Borland Turbo C 2.0 + +```bash +cd '/home/xor/inertia_player/dos_compilers/Borland Turbo C v2' +printf 'main(){return 0;}\n' > HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. C:\TCC.EXE -ms -Z -O -G -Iinclude -Llib -eHELLO.EXE HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. C:\HELLO.EXE +``` + +## Borland Turbo C++ 1.01 + +```bash +cd '/home/xor/inertia_player/dos_compilers/Borland Turbo C++ v1' +printf 'main(){return 0;}\n' > HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. --env=LIB=C:\LIB --env=INCLUDE=C:\INCLUDE C:\BIN\TCC.EXE -ms -Z -O -G -IC:\INCLUDE -LC:\LIB -eHELLO.EXE HELLO.C +/home/xor/kvikdos/kvikdos --mount=C:. C:\HELLO.EXE +``` + +## Microsoft MASM 5.0 + +`MASM.EXE` works directly; `LINK.EXE` is easiest via stdin prompts. + +```bash +cd '/home/xor/inertia_player/dos_compilers/Microsoft MASM v5/BIN' +cat > HELLO.ASM <<'EOA' +.MODEL SMALL +.STACK 100h +.CODE +start: + mov ax,4C00h + int 21h +END start +EOA +/home/xor/kvikdos/kvikdos --mount=C:. C:\MASM.EXE HELLO.ASM,HELLO.OBJ,NUL,NUL +printf 'HELLO.OBJ\nHELLO.EXE\nNUL\n\n\n' | /home/xor/kvikdos/kvikdos --mount=C:. C:\LINK.EXE +/home/xor/kvikdos/kvikdos --mount=C:. C:\HELLO.EXE +``` + +## Notes + +- If compile succeeds but link cannot find CRT objects/libs, your `LIB` path is wrong for the mounted layout. +- If includes are missing, adjust `INCLUDE` to match the mounted layout. +- For old compilers, avoid modern signatures (`int main(void)`) and rely on K&R-style `main()` for compatibility. diff --git a/alink/.clang-format b/alink/.clang-format new file mode 100644 index 0000000..7c3609a --- /dev/null +++ b/alink/.clang-format @@ -0,0 +1,12 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +TabWidth: 8 +UseTab: Always +BreakBeforeBraces: Allman +ColumnLimit: 100 +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +PointerAlignment: Right +SortIncludes: false +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false diff --git a/alink/.clang-tidy b/alink/.clang-tidy new file mode 100644 index 0000000..aa4c442 --- /dev/null +++ b/alink/.clang-tidy @@ -0,0 +1,4 @@ +Checks: '-*,clang-analyzer-core.uninitialized.*' +WarningsAsErrors: '*' +HeaderFilterRegex: '.*ALINK\.H' +FormatStyle: file diff --git a/alink/.editorconfig b/alink/.editorconfig new file mode 100644 index 0000000..76f637f --- /dev/null +++ b/alink/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*.{C,H}] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +indent_style = tab +indent_size = 8 +tab_width = 8 +trim_trailing_whitespace = true diff --git a/alink/.github/workflows/ci.yml b/alink/.github/workflows/ci.yml new file mode 100644 index 0000000..5fa0c21 --- /dev/null +++ b/alink/.github/workflows/ci.yml @@ -0,0 +1,17 @@ +name: ci + +on: + push: + pull_request: + +jobs: + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install toolchain + run: | + sudo apt-get update + sudo apt-get install -y build-essential clang clang-tidy clang-format clang-tools cppcheck valgrind + - name: Run CI checks + run: make ci diff --git a/alink/.gitignore b/alink/.gitignore new file mode 100644 index 0000000..97ff909 --- /dev/null +++ b/alink/.gitignore @@ -0,0 +1,3 @@ +*.o +alink +alink.san diff --git a/alink/AGENTS.md b/alink/AGENTS.md new file mode 100644 index 0000000..ba696d5 --- /dev/null +++ b/alink/AGENTS.md @@ -0,0 +1,21 @@ +# Repository Notes + +## Build + +- `make` builds the Linux host binary `alink` +- `make sanitizers` builds `alink.san` with AddressSanitizer and UBSan + +## Checks + +- `make format-check` +- `make tidy` +- `make cppcheck` +- `make analyze` +- `make valgrind` +- `make ci` + +## Conventions + +- Source files use uppercase DOS-era filenames and must be compiled as C, not C++ +- Linux path handling must continue to accept both `/` and `\` +- Compiler warnings are treated as errors in the maintained build targets diff --git a/alink/ALINK.C b/alink/ALINK.C new file mode 100644 index 0000000..a670e1a --- /dev/null +++ b/alink/ALINK.C @@ -0,0 +1,2028 @@ +#include "ALINK.H" + +char case_sensitive = 1; +char padsegments = 0; +char mapfile = 0; +PCHAR mapname = 0; +unsigned short maxalloc = 0xffff; +int output_type = OUTPUT_EXE; +PCHAR outname = 0; + +FILE *afile = 0; +UINT filepos = 0; +long reclength = 0; +unsigned char rectype = 0; +char li_le = 0; +UINT prevofs = 0; +long prevseg = 0; +long gotstart = 0; +RELOC startaddr; +UINT imageBase = 0; +UINT fileAlign = 1; +UINT objectAlign = 1; +UINT stackSize; +UINT stackCommitSize; +UINT heapSize; +UINT heapCommitSize; +unsigned char osMajor, osMinor; +unsigned char subsysMajor, subsysMinor; +unsigned int subSystem; +int buildDll = FALSE; +PUCHAR stubName = NULL; + +long errcount = 0; + +unsigned char buf[65536]; +PDATABLOCK lidata; + +PPCHAR namelist = NULL; +PPSEG seglist = NULL; +PPSEG outlist = NULL; +PPGRP grplist = NULL; +PSORTENTRY publics = NULL; +PEXTREC externs = NULL; +PPCOMREC comdefs = NULL; +PPRELOC relocs = NULL; +PIMPREC impdefs = NULL; +PEXPREC expdefs = NULL; +PLIBFILE libfiles = NULL; +PRESOURCE resource = NULL; +PSORTENTRY comdats = NULL; +UINT comdatcount = 0; +PPCHAR modname; +PPCHAR filename; +UINT namecount = 0, namemin = 0, pubcount = 0, pubmin = 0, segcount = 0, segmin = 0, outcount = 0, + grpcount = 0, grpmin = 0, extcount = 0, extmin = 0, comcount = 0, commin = 0, fixcount = 0, + fixmin = 0, impcount = 0, impmin = 0, impsreq = 0, expcount = 0, expmin = 0, nummods = 0, + filecount = 0, libcount = 0, rescount = 0; +UINT libPathCount = 0; +PCHAR *libPath = NULL; +char *entryPoint = NULL; + +void processArgs(int argc, char **argv) +{ + long i, j; + int consumed; + int helpRequested = FALSE; + UINT setbase, setfalign, setoalign; + UINT setstack, setstackcommit, setheap, setheapcommit; + int setsubsysmajor, setsubsysminor, setosmajor, setosminor; + unsigned char setsubsys; + int gotbase = FALSE, gotfalign = FALSE, gotoalign = FALSE, gotsubsys = FALSE; + int gotstack = FALSE, gotstackcommit = FALSE, gotheap = FALSE, gotheapcommit = FALSE; + int gotsubsysver = FALSE, gotosver = FALSE; + char *p; + PCHAR *newLibPath; + int c; + char **newargs; + FILE *argFile; + + for (i = 1; i < argc; i++) + { + /* cater for response files */ + if (argv[i][0] == '@') + { + argFile = fopen(argv[i] + 1, "rt"); + if (!argFile) + { + printf("Unable to open response file \"%s\"\n", argv[i] + 1); + exit(1); + } + newargs = (char **)checkMalloc(argc * sizeof(char *)); + for (j = 0; j < argc; j++) + { + newargs[j] = argv[j]; + } + p = NULL; + j = 0; + while ((c = fgetc(argFile)) != EOF) + { + if (c == ';') /* allow comments, starting with ; */ + { + while (((c = fgetc(argFile)) != EOF) && (c != '\n')) + ; /* loop until end of line */ + /* continue main loop */ + continue; + } + if (isspace(c)) + { + if (p) /* if we've got an argument, add to list */ + { + newargs = (char **)checkRealloc(newargs, (argc + 1) * sizeof(char *)); + newargs[argc] = p; + argc++; + /* clear pointer and length indicator */ + p = NULL; + j = 0; + } + /* and continue */ + continue; + } + if (c == '"') + { + /* quoted strings */ + while (((c = fgetc(argFile)) != EOF) && + (c != '"')) /* loop until end of string */ + { + if (c == '\\') + { + c = fgetc(argFile); + if (c == EOF) + { + printf("Missing character to escape in quoted string, unexpected " + "end of file found\n"); + exit(1); + } + } + + p = (char *)checkRealloc(p, j + 2); + p[j] = c; + j++; + p[j] = 0; + } + if (c == EOF) + { + printf("Unexpected end of file encountered in quoted string\n"); + exit(1); + } + + /* continue main loop */ + continue; + } + /* if no special case, then add to string */ + p = (char *)checkRealloc(p, j + 2); + p[j] = c; + j++; + p[j] = 0; + } + if (p) + { + newargs = (char **)checkRealloc(newargs, (argc + 1) * sizeof(char *)); + newargs[argc] = p; + argc++; + } + fclose(argFile); + argv = newargs; + } + else if (argv[i][0] == SWITCHCHAR) + { + if (strlen(argv[i]) < 2) + { + printf("Invalid argument \"%s\"\n", argv[i]); + exit(1); + } + switch (argv[i][1]) + { + case 'c': + if (strlen(argv[i]) == 2) + { + case_sensitive = 1; + break; + } + else if (strlen(argv[i]) == 3) + { + if (argv[i][2] == '+') + { + case_sensitive = 1; + break; + } + else if (argv[i][2] == '-') + { + case_sensitive = 0; + break; + } + } + printf("Invalid switch %s\n", argv[i]); + exit(1); + break; + case 'p': + switch (strlen(argv[i])) + { + case 2: + padsegments = 1; + break; + case 3: + if (argv[i][2] == '+') + { + padsegments = 1; + break; + } + else if (argv[i][2] == '-') + { + padsegments = 0; + break; + } + printf("Invalid switch %s\n", argv[i]); + exit(1); + default: + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + case 'm': + switch (strlen(argv[i])) + { + case 2: + mapfile = 1; + break; + case 3: + if (argv[i][2] == '+') + { + mapfile = 1; + break; + } + else if (argv[i][2] == '-') + { + mapfile = 0; + break; + } + printf("Invalid switch %s\n", argv[i]); + exit(1); + default: + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + case 'o': + switch (strlen(argv[i])) + { + case 2: + if (i < (argc - 1)) + { + i++; + if (!outname) + { + outname = checkMalloc(strlen(argv[i]) + 1 + + 4); /* space for added .EXT if none given */ + strcpy(outname, argv[i]); + } + else + { + printf("Can't specify two output names\n"); + exit(1); + } + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + default: + if (!strcmp(argv[i] + 2, "EXE")) + { + output_type = OUTPUT_EXE; + imageBase = 0; + fileAlign = 1; + objectAlign = 1; + stackSize = 0; + stackCommitSize = 0; + heapSize = 0; + heapCommitSize = 0; + } + else if (!strcmp(argv[i] + 2, "COM")) + { + output_type = OUTPUT_COM; + imageBase = 0; + fileAlign = 1; + objectAlign = 1; + stackSize = 0; + stackCommitSize = 0; + heapSize = 0; + heapCommitSize = 0; + } + else if (!strcmp(argv[i] + 2, "PE")) + { + output_type = OUTPUT_PE; + imageBase = WIN32_DEFAULT_BASE; + fileAlign = WIN32_DEFAULT_FILEALIGN; + objectAlign = WIN32_DEFAULT_OBJECTALIGN; + stackSize = WIN32_DEFAULT_STACKSIZE; + stackCommitSize = WIN32_DEFAULT_STACKCOMMITSIZE; + heapSize = WIN32_DEFAULT_HEAPSIZE; + heapCommitSize = WIN32_DEFAULT_HEAPCOMMITSIZE; + subSystem = WIN32_DEFAULT_SUBSYS; + subsysMajor = WIN32_DEFAULT_SUBSYSMAJOR; + subsysMinor = WIN32_DEFAULT_SUBSYSMINOR; + osMajor = WIN32_DEFAULT_OSMAJOR; + osMinor = WIN32_DEFAULT_OSMINOR; + } + else if (!strcmp(argv[i] + 1, "objectalign")) + { + if (i < (argc - 1)) + { + i++; + setoalign = strtoul(argv[i], &p, 0); + if (p[0]) /* if not at end of arg */ + { + printf("Bad object alignment\n"); + exit(1); + } + if ((setoalign < 512) || (setoalign > (256 * 1048576)) || + (getBitCount(setoalign) > 1)) + { + printf("Bad object alignment\n"); + exit(1); + } + gotoalign = TRUE; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + } + else if (!strcmp(argv[i] + 1, "osver")) + { + if (i < (argc - 1)) + { + i++; + if (sscanf(argv[i], "%d.%d%n", &setosmajor, &setosminor, &consumed) != + 2) + { + printf("Invalid version number %s\n", argv[i]); + exit(1); + } + if (argv[i][consumed] || (setosmajor < 0) || (setosminor < 0) || + (setosmajor > 65535) || (setosminor > 65535)) + { + printf("Invalid version number %s\n", argv[i]); + exit(1); + } + gotosver = TRUE; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + break; + case 'L': + if (strlen(argv[i]) == 2) + { + if (i < (argc - 1)) + { + i++; + libPathCount++; + newLibPath = (PCHAR *)checkRealloc(libPath, libPathCount * sizeof(PCHAR)); + libPath = newLibPath; + j = strlen(argv[i]); + if (!IS_PATH_CHAR(argv[i][j - 1])) + { + /* append a path separator if not present */ + libPath[libPathCount - 1] = (char *)checkMalloc(j + 2); + strcpy(libPath[libPathCount - 1], argv[i]); + libPath[libPathCount - 1][j] = PATH_CHAR; + libPath[libPathCount - 1][j + 1] = 0; + } + else + { + libPath[libPathCount - 1] = checkStrdup(argv[i]); + } + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + printf("Invalid switch %s\n", argv[i]); + exit(1); + break; + case 'h': + case 'H': + case '?': + if (strlen(argv[i]) == 2) + { + helpRequested = TRUE; + } + else if (!strcmp(argv[i] + 1, "heapsize")) + { + if (i < (argc - 1)) + { + i++; + setheap = strtoul(argv[i], &p, 0); + if (p[0]) /* if not at end of arg */ + { + printf("Bad heap size\n"); + exit(1); + } + gotheap = TRUE; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + else if (!strcmp(argv[i] + 1, "heapcommitsize")) + { + if (i < (argc - 1)) + { + i++; + setheapcommit = strtoul(argv[i], &p, 0); + if (p[0]) /* if not at end of arg */ + { + printf("Bad heap commit size\n"); + exit(1); + } + gotheapcommit = TRUE; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + break; + case 'b': + if (!strcmp(argv[i] + 1, "base")) + { + if (i < (argc - 1)) + { + i++; + setbase = strtoul(argv[i], &p, 0); + if (p[0]) /* if not at end of arg */ + { + printf("Bad image base\n"); + exit(1); + } + if (setbase & 0xffff) + { + printf("Bad image base\n"); + exit(1); + } + gotbase = TRUE; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + case 's': + if (!strcmp(argv[i] + 1, "subsys")) + { + if (i < (argc - 1)) + { + i++; + if (!strcmp(argv[i], "gui") || !strcmp(argv[i], "windows") || + !strcmp(argv[i], "win")) + { + setsubsys = PE_SUBSYS_WINDOWS; + gotsubsys = TRUE; + } + else if (!strcmp(argv[i], "char") || !strcmp(argv[i], "console") || + !strcmp(argv[i], "con")) + { + setsubsys = PE_SUBSYS_CONSOLE; + gotsubsys = TRUE; + } + else if (!strcmp(argv[i], "native")) + { + setsubsys = PE_SUBSYS_NATIVE; + gotsubsys = TRUE; + } + else if (!strcmp(argv[i], "posix")) + { + setsubsys = PE_SUBSYS_POSIX; + gotsubsys = TRUE; + } + else + { + printf("Invalid subsystem id %s\n", argv[i]); + exit(1); + } + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + else if (!strcmp(argv[i] + 1, "subsysver")) + { + if (i < (argc - 1)) + { + i++; + if (sscanf(argv[i], "%d.%d%n", &setsubsysmajor, &setsubsysminor, + &consumed) != 2) + { + printf("Invalid version number %s\n", argv[i]); + exit(1); + } + if (argv[i][consumed] || (setsubsysmajor < 0) || (setsubsysminor < 0) || + (setsubsysmajor > 65535) || (setsubsysminor > 65535)) + { + printf("Invalid version number %s\n", argv[i]); + exit(1); + } + gotsubsysver = TRUE; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + else if (!strcmp(argv[i] + 1, "stacksize")) + { + if (i < (argc - 1)) + { + i++; + setstack = strtoul(argv[i], &p, 0); + if (p[0]) /* if not at end of arg */ + { + printf("Bad stack size\n"); + exit(1); + } + gotstack = TRUE; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + else if (!strcmp(argv[i] + 1, "stackcommitsize")) + { + if (i < (argc - 1)) + { + i++; + setstackcommit = strtoul(argv[i], &p, 0); + if (p[0]) /* if not at end of arg */ + { + printf("Bad stack commit size\n"); + exit(1); + } + gotstackcommit = TRUE; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + else if (!strcmp(argv[i] + 1, "stub")) + { + if (i < (argc - 1)) + { + i++; + stubName = (PUCHAR)argv[i]; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + case 'f': + if (!strcmp(argv[i] + 1, "filealign")) + { + if (i < (argc - 1)) + { + i++; + setfalign = strtoul(argv[i], &p, 0); + if (p[0]) /* if not at end of arg */ + { + printf("Bad file alignment\n"); + exit(1); + } + if ((setfalign < 512) || (setfalign > 65536) || + (getBitCount(setfalign) > 1)) + { + printf("Bad file alignment\n"); + exit(1); + } + gotfalign = TRUE; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + case 'd': + if (!strcmp(argv[i] + 1, "dll")) + { + buildDll = TRUE; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + case 'e': + if (!strcmp(argv[i] + 1, "entry")) + { + if (i < (argc - 1)) + { + i++; + entryPoint = argv[i]; + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + } + else + { + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + break; + + default: + printf("Invalid switch %s\n", argv[i]); + exit(1); + } + } + else + { + filename = checkRealloc(filename, (filecount + 1) * sizeof(PCHAR)); + filename[filecount] = checkMalloc(strlen(argv[i]) + 1); + memcpy(filename[filecount], argv[i], strlen(argv[i]) + 1); + for (j = strlen(filename[filecount]); + j && (filename[filecount][j] != '.') && !IS_PATH_CHAR(filename[filecount][j]); j--) + ; + if ((j < 0) || (filename[filecount][j] != '.')) + { + j = strlen(filename[filecount]); + /* add default extension if none specified */ + filename[filecount] = checkRealloc(filename[filecount], strlen(argv[i]) + 5); + strcpy(filename[filecount] + j, DEFAULT_EXTENSION); + } + filecount++; + } + } + if (helpRequested || !filecount) + { + printf("Usage: ALINK [file [file [...]]] [options]\n"); + printf("\n"); + printf(" Each file may be an object file, a library, or a Win32 resource\n"); + printf(" file. If no extension is specified, .obj is assumed. Modules are\n"); + printf(" only loaded from library files if they are required to match an\n"); + printf(" external reference.\n"); + printf(" Options and files may be listed in any order, all mixed together.\n"); + printf("\n"); + printf("The following options are permitted:\n"); + printf("\n"); + printf(" @name Load additional options from response file name\n"); + printf(" -c Enable Case sensitivity\n"); + printf(" -c+ Enable Case sensitivity\n"); + printf(" -c- Disable Case sensitivity\n"); + printf(" -p Enable segment padding\n"); + printf(" -p+ Enable segment padding\n"); + printf(" -p- Disable segment padding\n"); + printf(" -m Enable map file\n"); + printf(" -m+ Enable map file\n"); + printf(" -m- Disable map file\n"); + printf("----Press Enter to continue---"); + while (((c = getchar()) != '\n') && (c != EOF)) + ; + printf("\n"); + printf(" -h Display this help list\n"); + printf(" -H \"\n"); + printf(" -? \"\n"); + printf(" -L ddd Add directory ddd to search list\n"); + printf(" -o name Choose output file name\n"); + printf(" -oXXX Choose output format XXX\n"); + printf(" Available options are:\n"); + printf(" COM - MSDOS COM file\n"); + printf(" EXE - MSDOS EXE file\n"); + printf(" PE - Win32 PE Executable\n"); + printf(" -entry name Use public symbol name as the entry point\n"); + printf("----Press Enter to continue---"); + while (((c = getchar()) != '\n') && (c != EOF)) + ; + printf("\nOptions for PE files:\n"); + printf(" -base addr Set base address of image\n"); + printf(" -filealign addr Set section alignment in file\n"); + printf(" -objectalign addr Set section alignment in memory\n"); + printf(" -subsys xxx Set subsystem used\n"); + printf(" Available options are:\n"); + printf(" console Select character mode\n"); + printf(" con \"\n"); + printf(" char \"\n"); + printf(" windows Select windowing mode\n"); + printf(" win \"\n"); + printf(" gui \"\n"); + printf(" native Select native mode\n"); + printf(" posix Select POSIX mode\n"); + printf(" -subsysver x.y Select subsystem version x.y\n"); + printf(" -osver x.y Select OS version x.y\n"); + printf(" -stub xxx Use xxx as the MSDOS stub\n"); + printf(" -dll Build DLL instead of EXE\n"); + printf(" -stacksize xxx Set stack size to xxx\n"); + printf(" -stackcommitsize xxx Set stack commit size to xxx\n"); + printf(" -heapsize xxx Set heap size to xxx\n"); + printf(" -heapcommitsize xxx Set heap commit size to xxx\n"); + exit(0); + } + if ((output_type != OUTPUT_PE) && + (gotoalign || gotfalign || gotbase || gotsubsys || gotstack || gotstackcommit || gotheap || + gotheapcommit || buildDll || stubName || gotsubsysver || gotosver)) + { + printf("Option not supported for non-PE output formats\n"); + exit(1); + } + if (gotstack) + { + stackSize = setstack; + } + if (gotstackcommit) + { + stackCommitSize = setstackcommit; + } + if (stackCommitSize > stackSize) + { + printf("Stack commit size is greater than stack size, committing whole stack\n"); + stackCommitSize = stackSize; + } + if (gotheap) + { + heapSize = setheap; + } + if (gotheapcommit) + { + heapCommitSize = setheapcommit; + } + if (heapCommitSize > heapSize) + { + printf("Heap commit size is greater than heap size, committing whole heap\n"); + heapCommitSize = heapSize; + } + if (gotoalign) + { + objectAlign = setoalign; + } + if (gotfalign) + { + fileAlign = setfalign; + } + if (gotbase) + { + imageBase = setbase; + } + if (gotsubsys) + { + subSystem = setsubsys; + } + if (gotsubsysver) + { + subsysMajor = setsubsysmajor; + subsysMinor = setsubsysminor; + } + if (gotosver) + { + osMajor = setosmajor; + osMinor = setosminor; + } +} + +static char *SegmentName(long segnum) +{ + if ((segnum < 0) || !seglist[segnum] || (seglist[segnum]->nameindex < 0)) + return NULL; + return namelist[seglist[segnum]->nameindex]; +} + +static char *GroupName(long grpnum) +{ + if ((grpnum < 0) || !grplist[grpnum] || (grplist[grpnum]->nameindex < 0)) + return NULL; + return namelist[grplist[grpnum]->nameindex]; +} + +static int SameName(char *left, char *right) +{ + if (left == right) + return TRUE; + if (!left || !right) + return FALSE; + return strcmp(left, right) == 0; +} + +static int SameComdatRef(long left, long right, int type, long selfLeft, long selfRight) +{ + if ((type == REL_SEGDISP) || (type == REL_SEGONLY) || (type == REL_SEGFRAME) || + (type == REL_LILEFRAME)) + { + if ((left == selfLeft) && (right == selfRight)) + return TRUE; + return SameName(SegmentName(left), SegmentName(right)); + } + if ((type == REL_GRPDISP) || (type == REL_GRPONLY) || (type == REL_GRPFRAME)) + { + return SameName(GroupName(left), GroupName(right)); + } + if ((type == REL_EXTDISP) || (type == REL_EXTONLY) || (type == REL_EXTFRAME)) + { + if ((left < 0) || (right < 0)) + return left == right; + return SameName(externs[left].name, externs[right].name) && + (externs[left].modnum == externs[right].modnum); + } + return left == right; +} + +static int SameComdatRelocs(long leftSeg, long rightSeg) +{ + UINT i, leftCount, rightCount, leftIndex, rightIndex; + + leftCount = 0; + rightCount = 0; + for (i = 0; i < fixcount; i++) + { + if (relocs[i]->segnum == leftSeg) + leftCount++; + if (relocs[i]->segnum == rightSeg) + rightCount++; + } + if (leftCount != rightCount) + return FALSE; + + leftIndex = 0; + rightIndex = 0; + while ((leftIndex < fixcount) && (rightIndex < fixcount)) + { + while ((leftIndex < fixcount) && (relocs[leftIndex]->segnum != leftSeg)) + leftIndex++; + while ((rightIndex < fixcount) && (relocs[rightIndex]->segnum != rightSeg)) + rightIndex++; + if ((leftIndex >= fixcount) || (rightIndex >= fixcount)) + break; + if ((relocs[leftIndex]->rtype != relocs[rightIndex]->rtype) || + (relocs[leftIndex]->ofs != relocs[rightIndex]->ofs) || + (relocs[leftIndex]->ftype != relocs[rightIndex]->ftype) || + (relocs[leftIndex]->ttype != relocs[rightIndex]->ttype) || + (relocs[leftIndex]->disp != relocs[rightIndex]->disp) || + !SameComdatRef(relocs[leftIndex]->frame, relocs[rightIndex]->frame, + relocs[leftIndex]->ftype, leftSeg, rightSeg) || + !SameComdatRef(relocs[leftIndex]->target, relocs[rightIndex]->target, + relocs[leftIndex]->ttype, leftSeg, rightSeg)) + { + return FALSE; + } + leftIndex++; + rightIndex++; + } + return TRUE; +} + +static void resolveComdats(void) +{ + UINT i, j, k; + + for (i = 0; i < comdatcount; i++) + { + PSORTENTRY listnode; + + listnode = &comdats[i]; + for (j = 0; j < listnode->count; j++) + { + PCOMDAT winner; + + winner = (PCOMDAT)listnode->object[j]; + if (!winner || !seglist[winner->segnum]) + continue; + for (k = j + 1; k < listnode->count; k++) + { + PCOMDAT loser; + + loser = (PCOMDAT)listnode->object[k]; + if (!loser || !seglist[loser->segnum]) + continue; + if (winner->modnum != loser->modnum) + continue; + if (winner->combineType != loser->combineType) + { + printf("Combine types for duplicate COMDAT symbol %s do not match\n", + listnode->id); + exit(1); + } + switch (winner->combineType) + { + case COMDAT_MATCH_NONE: + printf("Duplicate COMDAT symbol %s\n", listnode->id); + exit(1); + case COMDAT_MATCH_ANY: + break; + case COMDAT_MATCH_SAME: + if (seglist[winner->segnum]->length != seglist[loser->segnum]->length) + { + printf("COMDAT %s has mismatched sizes\n", listnode->id); + exit(1); + } + break; + case COMDAT_MATCH_EXACT: + if ((seglist[winner->segnum]->length != seglist[loser->segnum]->length) || + memcmp(seglist[winner->segnum]->data, seglist[loser->segnum]->data, + seglist[winner->segnum]->length) || + memcmp(seglist[winner->segnum]->datmask, seglist[loser->segnum]->datmask, + (seglist[winner->segnum]->length + 7) / 8) || + !SameComdatRelocs(winner->segnum, loser->segnum)) + { + printf("COMDAT %s does not match exactly\n", listnode->id); + exit(1); + } + break; + default: + printf("Unsupported COMDAT combine type %u for %s\n", winner->combineType, + listnode->id); + exit(1); + } + if (loser->pubdef) + { + *(loser->pubdef) = *(winner->pubdef); + } + redirect_segment(winner->segnum, loser->segnum); + } + } + } +} + +void matchExterns() +{ + long i, j, k, old_nummods; + PSORTENTRY listnode; + PCHAR name; + PPUBLIC pubdef; + + do + { + for (i = 0; i < expcount; i++) + { + if (expdefs[i].pubdef) + continue; + if ((listnode = binarySearch(publics, pubcount, expdefs[i].int_name)) != NULL) + { + for (k = 0; k < listnode->count; k++) + { + /* exports can only match global publics */ + if (((PPUBLIC)listnode->object[k])->modnum == 0) + { + expdefs[i].pubdef = (PPUBLIC)listnode->object[k]; + break; + } + } + } + } + for (i = 0; i < extcount; i++) + { + /* skip if we've already matched a public symbol */ + /* as they override all others */ + if (externs[i].flags == EXT_MATCHEDPUBLIC) + continue; + externs[i].flags = EXT_NOMATCH; + if ((listnode = binarySearch(publics, pubcount, externs[i].name)) != NULL) + { + for (k = 0; k < listnode->count; k++) + { + /* local publics can only match externs in same module */ + /* and global publics can only match global externs */ + if (((PPUBLIC)listnode->object[k])->modnum == externs[i].modnum) + { + externs[i].pubdef = (PPUBLIC)listnode->object[k]; + externs[i].flags = EXT_MATCHEDPUBLIC; + break; + } + } + } + if (externs[i].flags == EXT_NOMATCH) + { + for (j = 0; j < impcount; j++) + { + if (!strcmp(externs[i].name, impdefs[j].int_name) || + ((case_sensitive == 0) && !stricmp(externs[i].name, impdefs[j].int_name))) + { + externs[i].flags = EXT_MATCHEDIMPORT; + externs[i].impnum = j; + impsreq++; + } + } + } + if (externs[i].flags == EXT_NOMATCH) + { + for (j = 0; j < expcount; j++) + { + if (!expdefs[j].pubdef) + continue; + if (!strcmp(externs[i].name, expdefs[j].exp_name) || + ((case_sensitive == 0) && !stricmp(externs[i].name, expdefs[j].exp_name))) + { + externs[i].pubdef = expdefs[j].pubdef; + externs[i].flags = EXT_MATCHEDPUBLIC; + } + } + } + } + + old_nummods = nummods; + for (i = 0; (i < expcount) && (nummods == old_nummods); i++) + { + if (!expdefs[i].pubdef) + { + for (k = 0; k < libcount; ++k) + { + name = checkStrdup(expdefs[i].int_name); + if (!(libfiles[k].flags & LIBF_CASESENSITIVE)) + { + strupr(name); + } + + if ((listnode = binarySearch(libfiles[k].symbols, libfiles[k].numsyms, name)) != + NULL) + { + loadlibmod(k, listnode->count); + break; + } + free(name); + } + } + } + for (i = 0; (i < extcount) && (nummods == old_nummods); i++) + { + if (externs[i].flags == EXT_NOMATCH) + { + for (k = 0; k < libcount; ++k) + { + name = checkStrdup(externs[i].name); + if (!(libfiles[k].flags & LIBF_CASESENSITIVE)) + { + strupr(name); + } + + if ((listnode = binarySearch(libfiles[k].symbols, libfiles[k].numsyms, name)) != + NULL) + { + loadlibmod(k, listnode->count); + break; + } + free(name); + } + } + } + for (i = 0; (i < pubcount) && (nummods == old_nummods); ++i) + { + for (k = 0; k < publics[i].count; ++k) + { + pubdef = (PPUBLIC)publics[i].object[k]; + if (!pubdef->aliasName) + continue; + if ((listnode = binarySearch(publics, pubcount, pubdef->aliasName)) != NULL) + { + for (j = 0; j < listnode->count; j++) + { + if ((((PPUBLIC)listnode->object[j])->modnum == pubdef->modnum) && + !((PPUBLIC)listnode->object[j])->aliasName) + { + /* if we've found a match for the alias, then kill the alias */ + free(pubdef->aliasName); + (*pubdef) = (*((PPUBLIC)listnode->object[j])); + break; + } + } + } + if (!pubdef->aliasName) + continue; + for (k = 0; k < libcount; ++k) + { + name = checkStrdup(pubdef->aliasName); + if (!(libfiles[k].flags & LIBF_CASESENSITIVE)) + { + strupr(name); + } + + if ((listnode = binarySearch(libfiles[k].symbols, libfiles[k].numsyms, name)) != + NULL) + { + loadlibmod(k, listnode->count); + break; + } + free(name); + } + } + } + + } while (old_nummods != nummods); +} + +void matchComDefs() +{ + int i, j; + int comseg; + int comfarseg; + PSORTENTRY listnode; + PPUBLIC pubdef; + + if (!comcount) + return; + + for (i = 0; i < comcount; i++) + { + if (!comdefs[i]) + continue; + for (j = 0; j < i; j++) + { + if (!comdefs[j]) + continue; + if (comdefs[i]->modnum != comdefs[j]->modnum) + continue; + if (strcmp(comdefs[i]->name, comdefs[j]->name) == 0) + { + if (comdefs[i]->isFar != comdefs[j]->isFar) + { + printf("Mismatched near/far type for COMDEF %s\n", comdefs[i]->name); + exit(1); + } + if (comdefs[i]->length > comdefs[j]->length) + comdefs[j]->length = comdefs[i]->length; + free(comdefs[i]->name); + free(comdefs[i]); + comdefs[i] = 0; + break; + } + } + } + + for (i = 0; i < comcount; i++) + { + if (!comdefs[i]) + continue; + if ((listnode = binarySearch(publics, pubcount, comdefs[i]->name)) != NULL) + { + for (j = 0; j < listnode->count; j++) + { + /* local publics can only match externs in same module */ + /* and global publics can only match global externs */ + if ((((PPUBLIC)listnode->object[j])->modnum == comdefs[i]->modnum) && + !((PPUBLIC)listnode->object[j])->aliasName) + { + free(comdefs[i]->name); + free(comdefs[i]); + comdefs[i] = 0; + break; + } + } + } + } + + seglist = (PPSEG)checkRealloc(seglist, (segcount + 1) * sizeof(PSEG)); + seglist[segcount] = (PSEG)checkMalloc(sizeof(SEG)); + namelist = (PPCHAR)checkRealloc(namelist, (namecount + 1) * sizeof(PCHAR)); + namelist[namecount] = checkStrdup("COMDEFS"); + seglist[segcount]->nameindex = namecount; + seglist[segcount]->classindex = -1; + seglist[segcount]->overlayindex = -1; + seglist[segcount]->length = 0; + seglist[segcount]->data = NULL; + seglist[segcount]->datmask = NULL; + seglist[segcount]->attr = SEG_PRIVATE | SEG_PARA; + seglist[segcount]->winFlags = WINF_READABLE | WINF_WRITEABLE | WINF_NEG_FLAGS; + comseg = segcount; + segcount++; + namecount++; + + for (i = 0; i < grpcount; i++) + { + if (!grplist[i]) + continue; + if (grplist[i]->nameindex < 0) + continue; + if (!strcmp("DGROUP", namelist[grplist[i]->nameindex])) + { + if (grplist[i]->numsegs == 0) + continue; /* don't add to an emtpy group */ + /* because empty groups are special */ + /* else add to group */ + grplist[i]->segindex[grplist[i]->numsegs] = comseg; + grplist[i]->numsegs++; + break; + } + } + + seglist = (PPSEG)checkRealloc(seglist, (segcount + 1) * sizeof(PSEG)); + seglist[segcount] = (PSEG)checkMalloc(sizeof(SEG)); + namelist = (PPCHAR)checkRealloc(namelist, (namecount + 1) * sizeof(PCHAR)); + namelist[namecount] = checkStrdup("FARCOMDEFS"); + seglist[segcount]->nameindex = namecount; + seglist[segcount]->classindex = -1; + seglist[segcount]->overlayindex = -1; + seglist[segcount]->length = 0; + seglist[segcount]->data = NULL; + seglist[segcount]->datmask = NULL; + seglist[segcount]->attr = SEG_PRIVATE | SEG_PARA; + seglist[segcount]->winFlags = WINF_READABLE | WINF_WRITEABLE | WINF_NEG_FLAGS; + namecount++; + comfarseg = segcount; + segcount++; + + for (i = 0; i < comcount; i++) + { + if (!comdefs[i]) + continue; + pubdef = (PPUBLIC)checkMalloc(sizeof(PUBLIC)); + if (comdefs[i]->isFar) + { + if (comdefs[i]->length > 65536) + { + seglist = (PPSEG)checkRealloc(seglist, (segcount + 1) * sizeof(PSEG)); + seglist[segcount] = (PSEG)checkMalloc(sizeof(SEG)); + namelist = (PPCHAR)checkRealloc(namelist, (namecount + 1) * sizeof(PCHAR)); + namelist[namecount] = checkStrdup("FARCOMDEFS"); + seglist[segcount]->nameindex = namecount; + seglist[segcount]->classindex = -1; + seglist[segcount]->overlayindex = -1; + seglist[segcount]->length = comdefs[i]->length; + seglist[segcount]->data = NULL; + seglist[segcount]->datmask = (PUCHAR)checkMalloc((comdefs[i]->length + 7) / 8); + for (j = 0; j < (comdefs[i]->length + 7) / 8; j++) + seglist[segcount]->datmask[j] = 0; + seglist[segcount]->attr = SEG_PRIVATE | SEG_PARA; + seglist[segcount]->winFlags = WINF_READABLE | WINF_WRITEABLE | WINF_NEG_FLAGS; + namecount++; + pubdef->segnum = segcount; + segcount++; + pubdef->ofs = 0; + } + else if ((comdefs[i]->length + seglist[comfarseg]->length) > 65536) + { + seglist[comfarseg]->datmask = + (PUCHAR)checkMalloc((seglist[comfarseg]->length + 7) / 8); + for (j = 0; j < (seglist[comfarseg]->length + 7) / 8; j++) + seglist[comfarseg]->datmask[j] = 0; + + seglist = (PPSEG)checkRealloc(seglist, (segcount + 1) * sizeof(PSEG)); + seglist[segcount] = (PSEG)checkMalloc(sizeof(SEG)); + namelist = (PPCHAR)checkRealloc(namelist, (namecount + 1) * sizeof(PCHAR)); + namelist[namecount] = checkStrdup("FARCOMDEFS"); + seglist[segcount]->nameindex = namecount; + seglist[segcount]->classindex = -1; + seglist[segcount]->overlayindex = -1; + seglist[segcount]->length = comdefs[i]->length; + seglist[segcount]->data = NULL; + seglist[segcount]->datmask = NULL; + seglist[segcount]->attr = SEG_PRIVATE | SEG_PARA; + seglist[segcount]->winFlags = WINF_READABLE | WINF_WRITEABLE | WINF_NEG_FLAGS; + comfarseg = segcount; + segcount++; + namecount++; + pubdef->segnum = comfarseg; + pubdef->ofs = 0; + } + else + { + pubdef->segnum = comfarseg; + pubdef->ofs = seglist[comfarseg]->length; + seglist[comfarseg]->length += comdefs[i]->length; + } + } + else + { + pubdef->segnum = comseg; + pubdef->ofs = seglist[comseg]->length; + seglist[comseg]->length += comdefs[i]->length; + } + pubdef->modnum = comdefs[i]->modnum; + pubdef->grpnum = -1; + pubdef->typenum = 0; + pubdef->aliasName = NULL; + if ((listnode = binarySearch(publics, pubcount, comdefs[i]->name)) != NULL) + { + for (j = 0; j < listnode->count; ++j) + { + if (((PPUBLIC)listnode->object[j])->modnum == pubdef->modnum) + { + if (!((PPUBLIC)listnode->object[j])->aliasName) + { + printf("Duplicate public symbol %s\n", comdefs[i]->name); + exit(1); + } + free(((PPUBLIC)listnode->object[j])->aliasName); + (*((PPUBLIC)listnode->object[j])) = (*pubdef); + pubdef = NULL; + break; + } + } + } + if (pubdef) + { + sortedInsert(&publics, &pubcount, comdefs[i]->name, pubdef); + } + } + seglist[comfarseg]->datmask = (PUCHAR)checkMalloc((seglist[comfarseg]->length + 7) / 8); + for (j = 0; j < (seglist[comfarseg]->length + 7) / 8; j++) + seglist[comfarseg]->datmask[j] = 0; + + seglist[comseg]->datmask = (PUCHAR)checkMalloc((seglist[comseg]->length + 7) / 8); + for (j = 0; j < (seglist[comseg]->length + 7) / 8; j++) + seglist[comseg]->datmask[j] = 0; + + for (i = 0; i < expcount; i++) + { + if (expdefs[i].pubdef) + continue; + if ((listnode = binarySearch(publics, pubcount, expdefs[i].int_name)) != NULL) + { + for (j = 0; j < listnode->count; j++) + { + /* global publics only can match exports */ + if (((PPUBLIC)listnode->object[j])->modnum == 0) + { + expdefs[i].pubdef = (PPUBLIC)listnode->object[j]; + break; + } + } + } + } + for (i = 0; i < extcount; i++) + { + if (externs[i].flags != EXT_NOMATCH) + continue; + if ((listnode = binarySearch(publics, pubcount, externs[i].name)) != NULL) + { + for (j = 0; j < listnode->count; j++) + { + /* global publics only can match exports */ + if (((PPUBLIC)(listnode->object[j]))->modnum == externs[i].modnum) + { + externs[i].pubdef = (PPUBLIC)(listnode->object[j]); + externs[i].flags = EXT_MATCHEDPUBLIC; + break; + } + } + } + } +} + +void sortSegments() +{ + long i, j, k; + UINT base, align; + long baseSeg = -1; + + for (i = 0; i < segcount; i++) + { + if (seglist[i]) + { + if ((seglist[i]->attr & SEG_ALIGN) != SEG_ABS) + { + seglist[i]->absframe = 0; + } + } + } + + outcount = 0; + base = 0; + outlist = checkMalloc(sizeof(PSEG) * segcount); + for (i = 0; i < grpcount; i++) + { + if (grplist[i]) + { + grplist[i]->segnum = -1; + for (j = 0; j < grplist[i]->numsegs; j++) + { + k = grplist[i]->segindex[j]; + if (!seglist[k]) + { + printf("Error - group %s contains non-existent segment\n", + namelist[grplist[i]->nameindex]); + exit(1); + } + /* don't add removed sections */ + if (seglist[k]->winFlags & WINF_REMOVE) + { + continue; + } + /* add non-absolute segment */ + if ((seglist[k]->attr & SEG_ALIGN) != SEG_ABS) + { + switch (seglist[k]->attr & SEG_ALIGN) + { + case SEG_WORD: + align = 2; + break; + case SEG_DWORD: + align = 4; + break; + case SEG_8BYTE: + align = 0x8; + break; + case SEG_PARA: + align = 0x10; + break; + case SEG_32BYTE: + align = 0x20; + break; + case SEG_64BYTE: + align = 0x40; + break; + case SEG_PAGE: + align = 0x100; + break; + case SEG_MEMPAGE: + align = 0x1000; + break; + case SEG_BYTE: + default: + align = 1; + break; + } + if (align < objectAlign) + { + align = objectAlign; + } + base = (base + align - 1) & (0xffffffff - (align - 1)); + seglist[k]->base = base; + if (seglist[k]->length > 0) + { + base += seglist[k]->length; + if (seglist[k]->absframe != 0) + { + printf("Error - Segment %s part of more than one group\n", + namelist[seglist[k]->nameindex]); + exit(1); + } + seglist[k]->absframe = 1; + seglist[k]->absofs = i + 1; + if (grplist[i]->segnum < 0) + { + grplist[i]->segnum = k; + } + if (outcount == 0) + { + baseSeg = k; + } + else + { + outlist[outcount - 1]->virtualSize = + seglist[k]->base - outlist[outcount - 1]->base; + } + outlist[outcount] = seglist[k]; + outcount++; + } + } + } + } + } + for (i = 0; i < segcount; i++) + { + if (seglist[i]) + { + /* don't add removed sections */ + if (seglist[i]->winFlags & WINF_REMOVE) + { + continue; + } + /* add non-absolute segment, not already dealt with */ + if (((seglist[i]->attr & SEG_ALIGN) != SEG_ABS) && !seglist[i]->absframe) + { + switch (seglist[i]->attr & SEG_ALIGN) + { + case SEG_WORD: + case SEG_BYTE: + case SEG_DWORD: + case SEG_PARA: + align = 0x10; + break; + case SEG_PAGE: + align = 0x100; + break; + case SEG_MEMPAGE: + align = 0x1000; + break; + default: + align = 1; + break; + } + if (align < objectAlign) + { + align = objectAlign; + } + base = (base + align - 1) & (0xffffffff - (align - 1)); + seglist[i]->base = base; + if (seglist[i]->length > 0) + { + base += seglist[i]->length; + seglist[i]->absframe = 1; + seglist[i]->absofs = 0; + if (outcount == 0) + { + baseSeg = i; + } + else + { + outlist[outcount - 1]->virtualSize = + seglist[i]->base - outlist[outcount - 1]->base; + } + outlist[outcount] = seglist[i]; + outcount++; + } + } + else if ((seglist[i]->attr & SEG_ALIGN) == SEG_ABS) + { + seglist[i]->base = (seglist[i]->absframe << 4) + seglist[i]->absofs; + } + } + } + /* build size of last segment in output list */ + if (outcount) + { + outlist[outcount - 1]->virtualSize = + (outlist[outcount - 1]->length + objectAlign - 1) & (0xffffffff - (objectAlign - 1)); + } + for (i = 0; i < grpcount; i++) + { + if (grplist[i] && (grplist[i]->segnum < 0) && (baseSeg >= 0)) + grplist[i]->segnum = baseSeg; + } +} + +void loadFiles() +{ + long i, j, k; + char *name; + + for (i = 0; i < filecount; i++) + { + afile = fopen(filename[i], "rb"); + if (!strchr(filename[i], PATH_CHAR) && !strchr(filename[i], ALT_PATH_CHAR)) + { + /* if no path specified, search library path list */ + for (j = 0; !afile && j < libPathCount; j++) + { + name = (char *)checkMalloc(strlen(libPath[j]) + strlen(filename[i]) + 1); + strcpy(name, libPath[j]); + strcat(name, filename[i]); + afile = fopen(name, "rb"); + if (afile) + { + free(filename[i]); + filename[i] = name; + name = NULL; + } + else + { + free(name); + name = NULL; + } + } + } + if (!afile) + { + printf("Error opening file %s\n", filename[i]); + exit(1); + } + for (k = 0; k < i; ++k) + { + if (!strcmp(filename[i], filename[k])) + break; + } + if (k != i) + { + fclose(afile); + continue; + } + + filepos = 0; + printf("Loading file %s\n", filename[i]); + j = fgetc(afile); + fseek(afile, 0, SEEK_SET); + switch (j) + { + case LIBHDR: + loadlib(afile, filename[i]); + break; + case THEADR: + case LHEADR: + loadmod(afile); + break; + case 0: + loadres(afile); + break; + case 0x4c: + case 0x4d: + case 0x4e: + loadcoff(afile); + break; + case 0x21: + loadCoffLib(afile, filename[i]); + break; + default: + printf("Unknown file type\n"); + fclose(afile); + exit(1); + } + fclose(afile); + } +} + +void generateMap() +{ + long i, j; + PPUBLIC q; + + afile = fopen(mapname, "wt"); + if (!afile) + { + printf("Error opening map file %s\n", mapname); + exit(1); + } + printf("Generating map file %s\n", mapname); + + for (i = 0; i < segcount; i++) + { + if (seglist[i]) + { + fprintf(afile, "SEGMENT %s ", + (seglist[i]->nameindex >= 0) ? namelist[seglist[i]->nameindex] : ""); + switch (seglist[i]->attr & SEG_COMBINE) + { + case SEG_PRIVATE: + fprintf(afile, "PRIVATE "); + break; + case SEG_PUBLIC: + fprintf(afile, "PUBLIC "); + break; + case SEG_PUBLIC2: + fprintf(afile, "PUBLIC(2) "); + break; + case SEG_STACK: + fprintf(afile, "STACK "); + break; + case SEG_COMMON: + fprintf(afile, "COMMON "); + break; + case SEG_PUBLIC3: + fprintf(afile, "PUBLIC(3) "); + break; + default: + fprintf(afile, "unknown "); + break; + } + if (seglist[i]->attr & SEG_USE32) + { + fprintf(afile, "USE32 "); + } + else + { + fprintf(afile, "USE16 "); + } + switch (seglist[i]->attr & SEG_ALIGN) + { + case SEG_ABS: + fprintf(afile, "AT 0%04Xh ", seglist[i]->absframe); + break; + case SEG_BYTE: + fprintf(afile, "BYTE "); + break; + case SEG_WORD: + fprintf(afile, "WORD "); + break; + case SEG_PARA: + fprintf(afile, "PARA "); + break; + case SEG_PAGE: + fprintf(afile, "PAGE "); + break; + case SEG_DWORD: + fprintf(afile, "DWORD "); + break; + case SEG_MEMPAGE: + fprintf(afile, "MEMPAGE "); + break; + default: + fprintf(afile, "unknown "); + } + if (seglist[i]->classindex >= 0) + fprintf(afile, "'%s'\n", namelist[seglist[i]->classindex]); + else + fprintf(afile, "\n"); + fprintf(afile, " at %08X, length %08X\n", seglist[i]->base, seglist[i]->length); + } + } + for (i = 0; i < grpcount; i++) + { + if (!grplist[i]) + continue; + fprintf(afile, "\nGroup %s:\n", namelist[grplist[i]->nameindex]); + for (j = 0; j < grplist[i]->numsegs; j++) + { + fprintf(afile, " %s\n", namelist[seglist[grplist[i]->segindex[j]]->nameindex]); + } + } + + if (pubcount) + { + fprintf(afile, "\npublics:\n"); + } + for (i = 0; i < pubcount; ++i) + { + for (j = 0; j < publics[i].count; ++j) + { + q = (PPUBLIC)publics[i].object[j]; + if (q->modnum) + continue; + fprintf(afile, "%s at %s:%08X\n", publics[i].id, + (q->segnum >= 0) ? namelist[seglist[q->segnum]->nameindex] : "Absolute", + q->ofs); + } + } + + if (expcount) + { + fprintf(afile, "\n %u exports:\n", expcount); + for (i = 0; i < expcount; i++) + { + fprintf(afile, "%s(%u)=%s\n", expdefs[i].exp_name, expdefs[i].ordinal, + expdefs[i].int_name); + } + } + if (impcount) + { + fprintf(afile, "\n %u imports:\n", impcount); + for (i = 0; i < impcount; i++) + { + fprintf(afile, "%s=%s:%s(%i)\n", impdefs[i].int_name, impdefs[i].mod_name, + impdefs[i].flags == 0 ? impdefs[i].imp_name : "", + impdefs[i].flags == 0 ? 0 : impdefs[i].ordinal); + } + } + fclose(afile); +} + +int main(int argc, char *argv[]) +{ + long i, j; + int isend; + char *libList; + char *libListCopy; + PCHAR *newLibPath; + printf("ALINK v1.6 (C) Copyright 1998-9 Anthony A.J. Williams.\n"); + printf("All Rights Reserved\n\n"); + + libList = getenv("LIB"); + if (libList) + { + libListCopy = checkStrdup(libList); + for (i = 0, j = 0;; i++) + { + isend = (!libListCopy[i]); + if (libListCopy[i] == ';' || !libListCopy[i]) + { + if (i - j) + { + newLibPath = (PCHAR *)checkRealloc(libPath, (libPathCount + 1) * sizeof(PCHAR)); + libListCopy[i] = 0; + if (IS_PATH_CHAR(libListCopy[i - 1])) + { + newLibPath[libPathCount] = checkStrdup(libListCopy + j); + } + else + { + newLibPath[libPathCount] = (PCHAR)checkMalloc(i - j + 2); + strcpy(newLibPath[libPathCount], libListCopy + j); + newLibPath[libPathCount][i - j] = PATH_CHAR; + newLibPath[libPathCount][i - j + 1] = 0; + } + libPath = newLibPath; + libPathCount++; + } + j = i + 1; + } + if (isend) + break; + } + free(libListCopy); + } + + processArgs(argc, argv); + + if (!filecount) + { + printf("No files specified\n"); + exit(1); + } + + if (!outname) + { + outname = checkMalloc(strlen(filename[0]) + 1 + 4); + strcpy(outname, filename[0]); + i = strlen(outname); + while ((i >= 0) && (outname[i] != '.') && !IS_PATH_CHAR(outname[i]) && (outname[i] != ':')) + { + i--; + } + if (outname[i] == '.') + { + outname[i] = 0; + } + } + i = strlen(outname); + while ((i >= 0) && (outname[i] != '.') && !IS_PATH_CHAR(outname[i]) && (outname[i] != ':')) + { + i--; + } + if (outname[i] != '.') + { + switch (output_type) + { + case OUTPUT_EXE: + case OUTPUT_PE: + if (!buildDll) + { + strcat(outname, ".exe"); + } + else + { + strcat(outname, ".dll"); + } + break; + case OUTPUT_COM: + strcat(outname, ".com"); + break; + default: + break; + } + } + + if (mapfile) + { + if (!mapname) + { + mapname = checkMalloc(strlen(outname) + 1 + 4); + strcpy(mapname, outname); + i = strlen(mapname); + while ((i >= 0) && (mapname[i] != '.') && !IS_PATH_CHAR(mapname[i]) && + (mapname[i] != ':')) + { + i--; + } + if (mapname[i] != '.') + { + i = strlen(mapname); + } + strcpy(mapname + i, ".map"); + } + } + else + { + if (mapname) + { + free(mapname); + mapname = 0; + } + } + + loadFiles(); + + if (!nummods) + { + printf("No required modules specified\n"); + exit(1); + } + + if (rescount && (output_type != OUTPUT_PE)) + { + printf("Cannot link resources into a non-PE application\n"); + exit(1); + } + + if (entryPoint) + { + if (!case_sensitive) + { + strupr(entryPoint); + } + + if (gotstart) + { + printf("Warning, overriding entry point from Command Line\n"); + } + /* define an external reference for entry point */ + externs = checkRealloc(externs, (extcount + 1) * sizeof(EXTREC)); + externs[extcount].name = entryPoint; + externs[extcount].typenum = -1; + externs[extcount].pubdef = NULL; + externs[extcount].flags = EXT_NOMATCH; + externs[extcount].modnum = 0; + + /* point start address to this external */ + startaddr.ftype = REL_EXTDISP; + startaddr.frame = extcount; + startaddr.ttype = REL_EXTONLY; + startaddr.target = extcount; + + extcount++; + gotstart = TRUE; + } + + resolveComdats(); + printf("resolved COMDATs\n"); + + matchExterns(); + printf("matched Externs\n"); + + matchComDefs(); + printf("matched ComDefs\n"); + + for (i = 0; i < expcount; i++) + { + if (!expdefs[i].pubdef) + { + printf("Unresolved export %s=%s\n", expdefs[i].exp_name, expdefs[i].int_name); + errcount++; + } + else if (expdefs[i].pubdef->aliasName) + { + printf("Unresolved export %s=%s, with alias %s\n", expdefs[i].exp_name, + expdefs[i].int_name, expdefs[i].pubdef->aliasName); + errcount++; + } + } + + for (i = 0; i < extcount; i++) + { + if (externs[i].flags == EXT_NOMATCH) + { + printf("Unresolved external %s\n", externs[i].name); + errcount++; + } + else if (externs[i].flags == EXT_MATCHEDPUBLIC) + { + if (externs[i].pubdef->aliasName) + { + printf("Unresolved external %s with alias %s\n", externs[i].name, + externs[i].pubdef->aliasName); + errcount++; + } + } + } + + if (errcount != 0) + { + exit(1); + } + + combineBlocks(); + sortSegments(); + + if (mapfile) + generateMap(); + switch (output_type) + { + case OUTPUT_COM: + OutputCOMfile(outname); + break; + case OUTPUT_EXE: + OutputEXEfile(outname); + break; + case OUTPUT_PE: + OutputWin32file(outname); + break; + default: + printf("Invalid output type\n"); + exit(1); + break; + } + return 0; +} diff --git a/alink/ALINK.H b/alink/ALINK.H new file mode 100644 index 0000000..407e328 --- /dev/null +++ b/alink/ALINK.H @@ -0,0 +1,640 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRUE (1 == 1) +#define FALSE (1 == 0) + +#define SWITCHCHAR '-' +#ifdef _WIN32 +#define PATH_CHAR '\\' +#define ALT_PATH_CHAR '/' +#else +#define PATH_CHAR '/' +#define ALT_PATH_CHAR '\\' +#endif +#define IS_PATH_CHAR(c) ((c) == PATH_CHAR || (c) == ALT_PATH_CHAR) +#define DEFAULT_EXTENSION ".obj" + +#if defined(__GNUC__) || defined(__clang__) +#define ALINK_RETURNS_NONNULL __attribute__((returns_nonnull)) +#else +#define ALINK_RETURNS_NONNULL +#endif + +#define ERR_EXTRA_DATA 1 +#define ERR_NO_HEADER 2 +#define ERR_NO_RECDATA 3 +#define ERR_NO_MEM 4 +#define ERR_INV_DATA 5 +#define ERR_INV_SEG 6 +#define ERR_BAD_FIXUP 7 +#define ERR_BAD_SEGDEF 8 +#define ERR_ABS_SEG 9 +#define ERR_DUP_PUBLIC 10 +#define ERR_NO_MODEND 11 +#define ERR_EXTRA_HEADER 12 +#define ERR_UNKNOWN_RECTYPE 13 +#define ERR_SEG_TOO_LARGE 14 +#define ERR_MULTIPLE_STARTS 15 +#define ERR_BAD_GRPDEF 16 +#define ERR_OVERWRITE 17 +#define ERR_INVALID_COMENT 18 +#define ERR_ILLEGAL_IMPORTS 19 + +#define PREV_LE 1 +#define PREV_LI 2 +#define PREV_LI32 3 + +#define THEADR 0x80 +#define LHEADR 0x82 +#define COMENT 0x88 +#define MODEND 0x8a +#define MODEND32 0x8b +#define EXTDEF 0x8c +#define TYPDEF 0x8e +#define PUBDEF 0x90 +#define PUBDEF32 0x91 +#define LINNUM 0x94 +#define LINNUM32 0x95 +#define LNAMES 0x96 +#define SEGDEF 0x98 +#define SEGDEF32 0x99 +#define GRPDEF 0x9a +#define FIXUPP 0x9c +#define FIXUPP32 0x9d +#define LEDATA 0xa0 +#define LEDATA32 0xa1 +#define LIDATA 0xa2 +#define LIDATA32 0xa3 +#define COMDEF 0xb0 +#define BAKPAT 0xb2 +#define BAKPAT32 0xb3 +#define LEXTDEF 0xb4 +#define LEXTDEF32 0xb5 +#define LPUBDEF 0xb6 +#define LPUBDEF32 0xb7 +#define LCOMDEF 0xb8 +#define CEXTDEF 0xbc +#define COMDAT 0xc2 +#define COMDAT32 0xc3 +#define LINSYM 0xc4 +#define LINSYM32 0xc5 +#define ALIAS 0xc6 +#define NBKPAT 0xc8 +#define NBKPAT32 0xc9 +#define LLNAMES 0xca +#define LIBHDR 0xf0 +#define LIBEND 0xf1 + +#define COMENT_TRANSLATOR 0x00 +#define COMENT_INTEL_COPYRIGHT 0x01 +#define COMENT_LIB_SPEC 0x81 +#define COMENT_MSDOS_VER 0x9c +#define COMENT_MEMMODEL 0x9d +#define COMENT_DOSSEG 0x9e +#define COMENT_DEFLIB 0x9f +#define COMENT_OMFEXT 0xa0 +#define COMENT_NEWOMF 0xa1 +#define COMENT_LINKPASS 0xa2 +#define COMENT_LIBMOD 0xa3 +#define COMENT_EXESTR 0xa4 +#define COMENT_INCERR 0xa6 +#define COMENT_NOPAD 0xa7 +#define COMENT_WKEXT 0xa8 +#define COMENT_LZEXT 0xa9 +#define COMENT_PHARLAP 0xaa +#define COMENT_IBM386 0xb0 +#define COMENT_RECORDER 0xb1 +#define COMENT_COMMENT 0xda +#define COMENT_COMPILER 0xdb +#define COMENT_DATE 0xdc +#define COMENT_TIME 0xdd +#define COMENT_USER 0xdf +#define COMENT_DEPFILE 0xe9 +#define COMENT_COMMANDLINE 0xff +#define COMENT_PUBTYPE 0xe1 +#define COMENT_COMPARAM 0xea +#define COMENT_TYPDEF 0xe3 +#define COMENT_STRUCTMEM 0xe2 +#define COMENT_OPENSCOPE 0xe5 +#define COMENT_LOCAL 0xe6 +#define COMENT_ENDSCOPE 0xe7 +#define COMENT_SOURCEFILE 0xe8 + +#define EXT_IMPDEF 0x01 +#define EXT_EXPDEF 0x02 + +#define SEG_ALIGN 0x3e0 +#define SEG_COMBINE 0x1c +#define SEG_BIG 0x02 +#define SEG_USE32 0x01 + +#define SEG_ABS 0x00 +#define SEG_BYTE 0x20 +#define SEG_WORD 0x40 +#define SEG_PARA 0x60 +#define SEG_PAGE 0x80 +#define SEG_DWORD 0xa0 +#define SEG_MEMPAGE 0xc0 +#define SEG_BADALIGN 0xe0 +#define SEG_8BYTE 0x100 +#define SEG_32BYTE 0x200 +#define SEG_64BYTE 0x300 + +#define SEG_PRIVATE 0x00 +#define SEG_PUBLIC 0x08 +#define SEG_PUBLIC2 0x10 +#define SEG_STACK 0x14 +#define SEG_COMMON 0x18 +#define SEG_PUBLIC3 0x1c + +#define REL_SEGDISP 0 +#define REL_EXTDISP 2 +#define REL_GRPDISP 1 +#define REL_EXPFRAME 3 +#define REL_SEGONLY 4 +#define REL_EXTONLY 6 +#define REL_GRPONLY 5 + +#define REL_SEGFRAME 0 +#define REL_GRPFRAME 1 +#define REL_EXTFRAME 2 +#define REL_LILEFRAME 4 +#define REL_TARGETFRAME 5 + +#define FIX_SELFREL 0x10 +#define FIX_MASK (0x0f + FIX_SELFREL) + +#define FIX_THRED 0x08 +#define THRED_MASK 0x07 + +#define FIX_LBYTE 0 +#define FIX_OFS16 1 +#define FIX_BASE 2 +#define FIX_PTR1616 3 +#define FIX_HBYTE 4 +#define FIX_OFS16_2 5 +#define FIX_OFS32 9 +#define FIX_PTR1632 11 +#define FIX_OFS32_2 13 +/* RVA32 fixups are not supported by OMF, so has an out-of-range number */ +#define FIX_RVA32 256 + +#define FIX_SELF_LBYTE (FIX_LBYTE + FIX_SELFREL) +#define FIX_SELF_OFS16 (FIX_OFS16 + FIX_SELFREL) +#define FIX_SELF_OFS16_2 (FIX_OFS16_2 + FIX_SELFREL) +#define FIX_SELF_OFS32 (FIX_OFS32 + FIX_SELFREL) +#define FIX_SELF_OFS32_2 (FIX_OFS32_2 + FIX_SELFREL) + +#define LIBF_CASESENSITIVE 1 + +#define EXT_NOMATCH 0 +#define EXT_MATCHEDPUBLIC 1 +#define EXT_MATCHEDIMPORT 2 + +#define PE_SIGNATURE 0x00 +#define PE_MACHINEID 0x04 +#define PE_NUMOBJECTS 0x06 +#define PE_DATESTAMP 0x08 +#define PE_SYMBOLPTR 0x0c +#define PE_NUMSYMBOLS 0x10 +#define PE_HDRSIZE 0x14 +#define PE_FLAGS 0x16 +#define PE_MAGIC 0x18 +#define PE_LMAJOR 0x1a +#define PE_LMINOR 0x1b +#define PE_CODESIZE 0x1c +#define PE_INITDATASIZE 0x20 +#define PE_UNINITDATASIZE 0x24 +#define PE_ENTRYPOINT 0x28 +#define PE_CODEBASE 0x2c +#define PE_DATABASE 0x30 +#define PE_IMAGEBASE 0x34 +#define PE_OBJECTALIGN 0x38 +#define PE_FILEALIGN 0x3c +#define PE_OSMAJOR 0x40 +#define PE_OSMINOR 0x42 +#define PE_USERMAJOR 0x44 +#define PE_USERMINOR 0x46 +#define PE_SUBSYSMAJOR 0x48 +#define PE_SUBSYSMINOR 0x4a +#define PE_IMAGESIZE 0x50 +#define PE_HEADERSIZE 0x54 +#define PE_CHECKSUM 0x58 +#define PE_SUBSYSTEM 0x5c +#define PE_DLLFLAGS 0x5e +#define PE_STACKSIZE 0x60 +#define PE_STACKCOMMSIZE 0x64 +#define PE_HEAPSIZE 0x68 +#define PE_HEAPCOMMSIZE 0x6c +#define PE_LOADERFLAGS 0x70 +#define PE_NUMRVAS 0x74 +#define PE_EXPORTRVA 0x78 +#define PE_EXPORTSIZE 0x7c +#define PE_IMPORTRVA 0x80 +#define PE_IMPORTSIZE 0x84 +#define PE_RESOURCERVA 0x88 +#define PE_RESOURCESIZE 0x8c +#define PE_EXCEPTIONRVA 0x90 +#define PE_EXCEPTIONSIZE 0x94 +#define PE_SECURITYRVA 0x98 +#define PE_SECURITYSIZE 0x9c +#define PE_FIXUPRVA 0xa0 +#define PE_FIXUPSIZE 0xa4 +#define PE_DEBUGRVA 0xa8 +#define PE_DEBUGSIZE 0xac +#define PE_DESCRVA 0xb0 +#define PE_DESCSIZE 0xb4 +#define PE_MSPECRVA 0xb8 +#define PE_MSPECSIZE 0xbc +#define PE_TLSRVA 0xc0 +#define PE_TLSSIZE 0xc4 +#define PE_LOADCONFIGRVA 0xc8 +#define PE_LOADCONFIGSIZE 0xcc +#define PE_BOUNDIMPRVA 0xd0 +#define PE_BOUNDIMPSIZE 0xd4 +#define PE_IATRVA 0xd8 +#define PE_IATSIZE 0xdc + +#define PE_OBJECT_NAME 0x00 +#define PE_OBJECT_VIRTSIZE 0x08 +#define PE_OBJECT_VIRTADDR 0x0c +#define PE_OBJECT_RAWSIZE 0x10 +#define PE_OBJECT_RAWPTR 0x14 +#define PE_OBJECT_RELPTR 0x18 +#define PE_OBJECT_LINEPTR 0x1c +#define PE_OBJECT_NUMREL 0x20 +#define PE_OBJECT_NUMLINE 0x22 +#define PE_OBJECT_FLAGS 0x24 + +#define PE_BASE_HEADER_SIZE 0x18 +#define PE_OPTIONAL_HEADER_SIZE 0xe0 +#define PE_OBJECTENTRY_SIZE 0x28 +#define PE_HEADBUF_SIZE (PE_BASE_HEADER_SIZE + PE_OPTIONAL_HEADER_SIZE) +#define PE_IMPORTDIRENTRY_SIZE 0x14 +#define PE_NUM_VAS 0x10 +#define PE_EXPORTHEADER_SIZE 0x28 +#define PE_RESENTRY_SIZE 0x08 +#define PE_RESDIR_SIZE 0x10 +#define PE_RESDATAENTRY_SIZE 0x10 +#define PE_SYMBOL_SIZE 0x12 +#define PE_RELOC_SIZE 0x0a + +#define PE_ORDINAL_FLAG 0x80000000 +#define PE_INTEL386 0x014c +#define PE_MAGICNUM 0x010b +#define PE_FILE_EXECUTABLE 0x0002 +#define PE_FILE_32BIT 0x0100 +#define PE_FILE_LIBRARY 0x2000 + +#define PE_REL_LOW16 0x2000 +#define PE_REL_OFS32 0x3000 + +#define PE_SUBSYS_NATIVE 1 +#define PE_SUBSYS_WINDOWS 2 +#define PE_SUBSYS_CONSOLE 3 +#define PE_SUBSYS_POSIX 7 + +#define WINF_UNDEFINED 0x00000000 +#define WINF_CODE 0x00000020 +#define WINF_INITDATA 0x00000040 +#define WINF_UNINITDATA 0x00000080 +#define WINF_DISCARDABLE 0x02000000 +#define WINF_NOPAGE 0x08000000 +#define WINF_SHARED 0x10000000 +#define WINF_EXECUTE 0x20000000 +#define WINF_READABLE 0x40000000 +#define WINF_WRITEABLE 0x80000000 +#define WINF_ALIGN_NOPAD 0x00000008 +#define WINF_ALIGN_BYTE 0x00100000 +#define WINF_ALIGN_WORD 0x00200000 +#define WINF_ALIGN_DWORD 0x00300000 +#define WINF_ALIGN_8 0x00400000 +#define WINF_ALIGN_PARA 0x00500000 +#define WINF_ALIGN_32 0x00600000 +#define WINF_ALIGN_64 0x00700000 +#define WINF_ALIGN (WINF_ALIGN_64) +#define WINF_COMMENT 0x00000200 +#define WINF_REMOVE 0x00000800 +#define WINF_COMDAT 0x00001000 +#define WINF_NEG_FLAGS (WINF_DISCARDABLE | WINF_NOPAGE) +#define WINF_IMAGE_FLAGS 0xfa0008e0 + +#define COMDAT_CONTINUE 0x01 +#define COMDAT_ITERATED 0x02 +#define COMDAT_LOCAL 0x04 + +#define COMDAT_ALLOC_MASK 0x0f +#define COMDAT_EXPLICIT 0x00 +#define COMDAT_FAR_CODE 0x01 +#define COMDAT_FAR_DATA 0x02 +#define COMDAT_CODE32 0x03 +#define COMDAT_DATA32 0x04 + +#define COMDAT_MATCH_MASK 0xf0 +#define COMDAT_MATCH_NONE 0x00 +#define COMDAT_MATCH_ANY 0x10 +#define COMDAT_MATCH_SAME 0x20 +#define COMDAT_MATCH_EXACT 0x30 + +#define COMDAT_ALIGN_SEG 0x00 +#define COMDAT_ALIGN_BYTE 0x01 +#define COMDAT_ALIGN_WORD 0x02 +#define COMDAT_ALIGN_PARA 0x03 +#define COMDAT_ALIGN_4K 0x04 +#define COMDAT_ALIGN_DWORD 0x05 + +#define COFF_SYM_EXTERNAL 2 +#define COFF_SYM_STATIC 3 +#define COFF_SYM_LABEL 6 +#define COFF_SYM_FUNCTION 101 +#define COFF_SYM_FILE 103 +#define COFF_SYM_SECTION 104 + +#define COFF_FIX_DIR32 6 +#define COFF_FIX_RVA32 7 +#define COFF_FIX_REL32 0x14 + +#define OUTPUT_COM 1 +#define OUTPUT_EXE 2 +#define OUTPUT_PE 3 + +#define WIN32_DEFAULT_BASE 0x00400000 +#define WIN32_DEFAULT_FILEALIGN 0x00000200 +#define WIN32_DEFAULT_OBJECTALIGN 0x00001000 +#define WIN32_DEFAULT_STACKSIZE 0x00100000 +#define WIN32_DEFAULT_STACKCOMMITSIZE 0x00001000 +#define WIN32_DEFAULT_HEAPSIZE 0x00100000 +#define WIN32_DEFAULT_HEAPCOMMITSIZE 0x00001000 +#define WIN32_DEFAULT_SUBSYS PE_SUBSYS_WINDOWS +#define WIN32_DEFAULT_SUBSYSMAJOR 4 +#define WIN32_DEFAULT_SUBSYSMINOR 0 +#define WIN32_DEFAULT_OSMAJOR 1 +#define WIN32_DEFAULT_OSMINOR 0 + +#define EXP_ORD 0x80 + +typedef char *PCHAR, **PPCHAR; +typedef unsigned char *PUCHAR; +typedef unsigned int UINT; + +typedef struct __sortentry +{ + char *id; + void **object; + UINT count; +} SORTENTRY, *PSORTENTRY; + +typedef struct __seg +{ + long nameindex; + long classindex; + long overlayindex; + long orderindex; + UINT length; + UINT virtualSize; + UINT absframe; + UINT absofs; + UINT base; + UINT winFlags; + unsigned short attr; + PUCHAR data; + PUCHAR datmask; +} SEG, *PSEG, **PPSEG; + +typedef struct __datablock +{ + long count; + long blocks; + long dataofs; + void *data; +} DATABLOCK, *PDATABLOCK, **PPDATABLOCK; + +typedef struct __pubdef +{ + long segnum; + long grpnum; + long typenum; + UINT ofs; + UINT modnum; + PCHAR aliasName; +} PUBLIC, *PPUBLIC, **PPPUBLIC; + +typedef struct __extdef +{ + PCHAR name; + long typenum; + PPUBLIC pubdef; + long impnum; + long flags; + UINT modnum; +} EXTREC, *PEXTREC, **PPEXTREC; + +typedef struct __imprec +{ + PCHAR int_name; + PCHAR mod_name; + PCHAR imp_name; + unsigned short ordinal; + char flags; + long segnum; + UINT ofs; +} IMPREC, *PIMPREC, **PPIMPREC; + +typedef struct __exprec +{ + PCHAR int_name; + PCHAR exp_name; + UINT ordinal; + char flags; + PPUBLIC pubdef; + UINT modnum; +} EXPREC, *PEXPREC, **PPEXPREC; + +typedef struct __comdef +{ + PCHAR name; + UINT length; + int isFar; + UINT modnum; +} COMREC, *PCOMREC, **PPCOMREC; + +typedef struct __reloc +{ + UINT ofs; + long segnum; + unsigned char ftype, ttype; + unsigned short rtype; + long target; + UINT disp; + long frame; + UINT outputPos; +} RELOC, *PRELOC, **PPRELOC; + +typedef struct __grp +{ + long nameindex; + long numsegs; + long segindex[256]; + long segnum; +} GRP, *PGRP, **PPGRP; + +typedef struct __libfile +{ + PCHAR filename; + unsigned short blocksize; + unsigned short numdicpages; + UINT dicstart; + char flags; + char libtype; + int modsloaded; + UINT *modlist; + PUCHAR longnames; + PSORTENTRY symbols; + UINT numsyms; +} LIBFILE, *PLIBFILE, **PPLIBFILE; + +typedef struct __libentry +{ + UINT libfile; + UINT modpage; +} LIBENTRY, *PLIBENTRY, **PPLIBENTRY; + +typedef struct __resource +{ + PUCHAR typename; + PUCHAR name; + PUCHAR data; + UINT length; + unsigned short typeid; + unsigned short id; + unsigned short languageid; +} RESOURCE, *PRESOURCE; + +typedef struct __coffsym +{ + PUCHAR name; + UINT value; + short section; + unsigned short type; + unsigned char class; + long extnum; + UINT numAuxRecs; + PUCHAR auxRecs; + int isComDat; +} COFFSYM, *PCOFFSYM; + +typedef struct __comdatrec +{ + UINT segnum; + UINT combineType; + UINT linkwith; + UINT modnum; + UINT flags; + PPUBLIC pubdef; +} COMDATREC, *PCOMDAT; + +int sortCompare(const void *x1, const void *x2); +void processArgs(int argc, char *argv[]); +void combine_groups(long i, long j); +void combine_common(long i, long j); +void combine_segments(long i, long j); +void combineBlocks(); +void redirect_segment(long dest, long src); +void OutputWin32file(PCHAR outname); +void OutputEXEfile(PCHAR outname); +void OutputCOMfile(PCHAR outname); +void GetFixupTarget(PRELOC r, long *tseg, UINT *tofs, int isFlat); +void loadlibmod(UINT libnum, UINT modpage); +void loadlib(FILE *libfile, PCHAR libname); +void loadCoffLib(FILE *libfile, PCHAR libname); +void loadcofflibmod(PLIBFILE p, FILE *libfile); +long loadmod(FILE *objfile); +void loadres(FILE *resfile); +void loadcoff(FILE *objfile); +void loadCoffImport(FILE *objfile); +void LoadFIXUP(PRELOC r, PUCHAR buf, long *p); +void RelocLIDATA(PDATABLOCK p, long *ofs, PRELOC r); +void EmitLiData(PDATABLOCK p, long segnum, long *ofs); +PDATABLOCK BuildLiData(long *bufofs); +void DestroyLIDATA(PDATABLOCK p); +void ReportError(long errnum); +long GetIndex(PUCHAR buf, long *index); +void ClearNbit(PUCHAR mask, long i); +void SetNbit(PUCHAR mask, long i); +char GetNbit(PUCHAR mask, long i); +int wstricmp(const char *s1, const char *s2); +int wstrlen(const char *s); +unsigned short wtoupper(unsigned short a); +int getBitCount(UINT a); +void *checkMalloc(size_t x) ALINK_RETURNS_NONNULL; +void *checkRealloc(void *p, size_t x) ALINK_RETURNS_NONNULL; +char *checkStrdup(const char *s) ALINK_RETURNS_NONNULL; +int stricmp(const char *s1, const char *s2); +char *strupr(char *s); +char *_strdup(const char *s); +PSORTENTRY binarySearch(PSORTENTRY list, UINT count, char *key); +void sortedInsert(PSORTENTRY *plist, UINT *pcount, char *key, void *object); +#define strdup _strdup + +extern char case_sensitive; +extern char padsegments; +extern char mapfile; +extern PCHAR mapname; +extern unsigned short maxalloc; +extern int output_type; +extern PCHAR outname; + +extern FILE *afile; +extern UINT filepos; +extern long reclength; +extern unsigned char rectype; +extern char li_le; +extern UINT prevofs; +extern long prevseg; +extern long gotstart; +extern RELOC startaddr; +extern UINT imageBase; +extern UINT fileAlign; +extern UINT objectAlign; +extern UINT stackSize; +extern UINT stackCommitSize; +extern UINT heapSize; +extern UINT heapCommitSize; +extern unsigned char osMajor, osMinor; +extern unsigned char subsysMajor, subsysMinor; +extern unsigned int subSystem; + +extern long errcount; + +extern unsigned char buf[65536]; +extern PDATABLOCK lidata; + +extern PPCHAR namelist; +extern PPSEG seglist; +extern PPSEG outlist; +extern PPGRP grplist; +extern PSORTENTRY publics; +extern PEXTREC externs; +extern PPCOMREC comdefs; +extern PPRELOC relocs; +extern PIMPREC impdefs; +extern PEXPREC expdefs; +extern PLIBFILE libfiles; +extern PRESOURCE resource; +extern PPCHAR modname; +extern PPCHAR filename; +extern PSORTENTRY comdats; +extern UINT comdatcount; +extern UINT namecount, namemin, pubcount, pubmin, segcount, segmin, outcount, grpcount, grpmin, + extcount, extmin, comcount, commin, fixcount, fixmin, impcount, impmin, impsreq, expcount, + expmin, nummods, filecount, libcount, rescount; + +extern int buildDll; +extern PUCHAR stubName; diff --git a/alink/Alinksrc.txt b/alink/Alinksrc.txt new file mode 100644 index 0000000..6fa3d16 --- /dev/null +++ b/alink/Alinksrc.txt @@ -0,0 +1,23 @@ +ALINK Source Code, V1.6, Copyright 1998-9 Anthony A.J. Williams. +All Rights Reserved. + +This source code is free for all to use, compile and distribute for any +purpose, provided it remains unmodified, and I am credited. You may not +charge for this source code, or any executables compiled from it, but you +may charge a small distribution fee to cover costs, and you may charge +for any warranty or support you may wish to give your customers. + +As this source is free, it comes with no warranty whatsoever - use it +entirely at your own risk. + +makefile contains the makefile for use with GCC (RSXNT) + +alink.mak contains the makefile for use with GCC (RSXNT), linking with the +ALINK libaries. Set C_INCLUDE_PATH to point to the ALINK library headers. + +alink2.mak contains the makefile for use with MSVC, linking with the ALINK +libraries. Set INCLUDE to point to the ALINK library headers. + +Any comments or bug reports, please email me at + +anthony_w@geocities.com diff --git a/alink/COFF.C b/alink/COFF.C new file mode 100644 index 0000000..7e1fef3 --- /dev/null +++ b/alink/COFF.C @@ -0,0 +1,741 @@ +#include "ALINK.H" + +void loadcoff(FILE *objfile) +{ + unsigned char headbuf[20]; + unsigned char buf[100]; + PUCHAR bigbuf; + PUCHAR stringList; + UINT thiscpu; + UINT numSect; + UINT headerSize; + UINT symbolPtr; + UINT numSymbols; + UINT stringPtr; + UINT stringSize; + UINT stringOfs; + UINT i, j, k; + UINT fileStart; + UINT minseg; + UINT numrel; + UINT relofs; + UINT relshift; + UINT sectname; + long sectorder; + PCOFFSYM sym = NULL; + UINT combineType; + PPUBLIC pubdef; + PCOMDAT comdat; + PCHAR comdatsym; + PSORTENTRY listnode; + + nummods++; + minseg = segcount; + fileStart = ftell(objfile); + + if (fread(headbuf, 1, 20, objfile) != 20) + { + printf("Unable to read from file\n"); + exit(1); + } + thiscpu = headbuf[0] + 256 * headbuf[1]; + if (!thiscpu) + { + /* if we've got an import module, start at the beginning */ + fseek(objfile, fileStart, SEEK_SET); + /* and load it */ + loadCoffImport(objfile); + return; + } + + if ((thiscpu < 0x14c) || (thiscpu > 0x14e)) + { + printf("Unsupported CPU type for module\n"); + exit(1); + } + numSect = + headbuf[PE_NUMOBJECTS - PE_MACHINEID] + 256 * headbuf[PE_NUMOBJECTS - PE_MACHINEID + 1]; + + symbolPtr = headbuf[PE_SYMBOLPTR - PE_MACHINEID] + + (headbuf[PE_SYMBOLPTR - PE_MACHINEID + 1] << 8) + + (headbuf[PE_SYMBOLPTR - PE_MACHINEID + 2] << 16) + + (headbuf[PE_SYMBOLPTR - PE_MACHINEID + 3] << 24); + + numSymbols = headbuf[PE_NUMSYMBOLS - PE_MACHINEID] + + (headbuf[PE_NUMSYMBOLS - PE_MACHINEID + 1] << 8) + + (headbuf[PE_NUMSYMBOLS - PE_MACHINEID + 2] << 16) + + (headbuf[PE_NUMSYMBOLS - PE_MACHINEID + 3] << 24); + + if (headbuf[PE_HDRSIZE - PE_MACHINEID] | headbuf[PE_HDRSIZE - PE_MACHINEID + 1]) + { + printf("warning, optional header discarded\n"); + headerSize = + headbuf[PE_HDRSIZE - PE_MACHINEID] + 256 * headbuf[PE_HDRSIZE - PE_MACHINEID + 1]; + } + else + headerSize = 0; + headerSize += PE_BASE_HEADER_SIZE - PE_MACHINEID; + + stringPtr = symbolPtr + numSymbols * PE_SYMBOL_SIZE; + if (stringPtr) + { + fseek(objfile, fileStart + stringPtr, SEEK_SET); + if (fread(buf, 1, 4, objfile) != 4) + { + printf("Invalid COFF object file, unable to read string table size\n"); + exit(1); + } + stringSize = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); + if (!stringSize) + stringSize = 4; + if (stringSize < 4) + { + printf("Invalid COFF object file, bad string table size %u\n", stringSize); + exit(1); + } + stringPtr += 4; + stringSize -= 4; + } + else + { + stringSize = 0; + } + if (stringSize) + { + stringList = (PUCHAR)checkMalloc(stringSize); + if (fread(stringList, 1, stringSize, objfile) != stringSize) + { + printf("Invalid COFF object file, unable to read string table\n"); + exit(1); + } + if (stringList[stringSize - 1]) + { + printf("Invalid COFF object file, last string unterminated\n"); + exit(1); + } + } + else + { + stringList = NULL; + } + + if (symbolPtr && numSymbols) + { + fseek(objfile, fileStart + symbolPtr, SEEK_SET); + sym = (PCOFFSYM)checkMalloc(sizeof(COFFSYM) * numSymbols); + for (i = 0; i < numSymbols; i++) + { + if (fread(buf, 1, PE_SYMBOL_SIZE, objfile) != PE_SYMBOL_SIZE) + { + printf("Invalid COFF object file, unable to read symbols\n"); + exit(1); + } + if (buf[0] | buf[1] | buf[2] | buf[3]) + { + sym[i].name = (PUCHAR)checkMalloc(9); + memcpy(sym[i].name, buf, 8); + sym[i].name[8] = 0; + } + else + { + stringOfs = buf[4] + (buf[5] << 8) + (buf[6] << 16) + (buf[7] << 24); + if (stringOfs < 4) + { + printf("Invalid COFF object file bad symbol location\n"); + exit(1); + } + stringOfs -= 4; + if (stringOfs >= stringSize) + { + printf("Invalid COFF object file bad symbol location\n"); + exit(1); + } + sym[i].name = checkStrdup(stringList + stringOfs); + } + if (!case_sensitive) + { + strupr(sym[i].name); + } + + sym[i].value = buf[8] + (buf[9] << 8) + (buf[10] << 16) + (buf[11] << 24); + sym[i].section = buf[12] + (buf[13] << 8); + sym[i].type = buf[14] + (buf[15] << 8); + sym[i].class = buf[16]; + sym[i].extnum = -1; + sym[i].numAuxRecs = buf[17]; + sym[i].isComDat = FALSE; + + switch (sym[i].class) + { + case COFF_SYM_SECTION: /* section symbol */ + if (sym[i].section < -1) + { + break; + } + /* section symbols declare an extern always, so can use in relocs */ + /* they may also include a PUBDEF */ + externs = (PEXTREC)checkRealloc(externs, (extcount + 1) * sizeof(EXTREC)); + externs[extcount].name = sym[i].name; + externs[extcount].pubdef = NULL; + externs[extcount].modnum = 0; + externs[extcount].flags = EXT_NOMATCH; + sym[i].extnum = extcount; + extcount++; + if (sym[i].section != 0) /* if the section is defined here, make public */ + { + pubdef = (PPUBLIC)checkMalloc(sizeof(PUBLIC)); + pubdef->grpnum = -1; + pubdef->typenum = 0; + pubdef->modnum = 0; + pubdef->aliasName = NULL; + pubdef->ofs = sym[i].value; + + if (sym[i].section == -1) + { + pubdef->segnum = -1; + } + else + { + pubdef->segnum = minseg + sym[i].section - 1; + } + if ((listnode = binarySearch(publics, pubcount, sym[i].name)) != NULL) + { + for (j = 0; j < listnode->count; ++j) + { + if (((PPUBLIC)listnode->object[j])->modnum == pubdef->modnum) + { + if (!((PPUBLIC)listnode->object[j])->aliasName) + { + printf("Duplicate public symbol %s\n", sym[i].name); + exit(1); + } + free(((PPUBLIC)listnode->object[j])->aliasName); + (*((PPUBLIC)listnode->object[j])) = (*pubdef); + pubdef = NULL; + break; + } + } + } + if (pubdef) + { + sortedInsert(&publics, &pubcount, sym[i].name, pubdef); + } + } + case COFF_SYM_STATIC: /* allowed, but ignored for now as we only want to process if + required */ + case COFF_SYM_LABEL: + case COFF_SYM_FILE: + case COFF_SYM_FUNCTION: + case COFF_SYM_EXTERNAL: + break; + default: + printf("unsupported symbol class %02X for symbol %s\n", sym[i].class, sym[i].name); + exit(1); + } + if (sym[i].numAuxRecs) + { + sym[i].auxRecs = (PUCHAR)checkMalloc(sym[i].numAuxRecs * PE_SYMBOL_SIZE); + } + else + { + sym[i].auxRecs = NULL; + } + + /* read in the auxillary records for this symbol */ + for (j = 0; j < sym[i].numAuxRecs; j++) + { + if (fread(sym[i].auxRecs + j * PE_SYMBOL_SIZE, 1, PE_SYMBOL_SIZE, objfile) != + PE_SYMBOL_SIZE) + { + printf("Invalid COFF object file\n"); + exit(1); + } + sym[i + j + 1].name = NULL; + sym[i + j + 1].numAuxRecs = 0; + sym[i + j + 1].value = 0; + sym[i + j + 1].section = -1; + sym[i + j + 1].type = 0; + sym[i + j + 1].class = 0; + sym[i + j + 1].extnum = -1; + } + i += j; + } + } + for (i = 0; i < numSect; i++) + { + fseek(objfile, fileStart + headerSize + i * PE_OBJECTENTRY_SIZE, SEEK_SET); + if (fread(buf, 1, PE_OBJECTENTRY_SIZE, objfile) != PE_OBJECTENTRY_SIZE) + { + printf("Invalid COFF object file, unable to read section header\n"); + exit(1); + } + /* virtual size is also the offset of the data into the segment */ + /* + if(buf[PE_OBJECT_VIRTSIZE]|buf[PE_OBJECT_VIRTSIZE+1]|buf[PE_OBJECT_VIRTSIZE+2] + |buf[PE_OBJECT_VIRTSIZE+3]) + { + printf("Invalid COFF object file, section has non-zero virtual size\n"); + exit(1); + } + */ + buf[8] = 0; /* null terminate name */ + /* get shift value for relocs */ + relshift = buf[PE_OBJECT_VIRTADDR] + (buf[PE_OBJECT_VIRTADDR + 1] << 8) + + (buf[PE_OBJECT_VIRTADDR + 2] << 16) + (buf[PE_OBJECT_VIRTADDR + 3] << 24); + + if (buf[0] == '/') + { + sectname = strtoul(buf + 1, (char **)&bigbuf, 10); + if (*bigbuf) + { + printf("Invalid COFF object file, invalid number %s\n", buf + 1); + exit(1); + } + if (sectname < 4) + { + printf("Invalid COFF object file\n"); + exit(1); + } + sectname -= 4; + if (sectname >= stringSize) + { + printf("Invalid COFF object file\n"); + exit(1); + } + namelist = (PPCHAR)checkRealloc(namelist, (namecount + 1) * sizeof(PCHAR)); + namelist[namecount] = checkStrdup(stringList + sectname); + sectname = namecount; + namecount++; + } + else + { + namelist = (PPCHAR)checkRealloc(namelist, (namecount + 1) * sizeof(PCHAR)); + namelist[namecount] = checkStrdup(buf); + + sectname = namecount; + namecount++; + } + if (strchr(namelist[sectname], '$')) + { + /* if we have a grouped segment, sort by original name */ + sectorder = sectname; + /* and get real name, without $ sort section */ + namelist = (PPCHAR)checkRealloc(namelist, (namecount + 1) * sizeof(PCHAR)); + namelist[namecount] = checkStrdup(namelist[sectname]); + *(strchr(namelist[namecount], '$')) = 0; + sectname = namecount; + namecount++; + } + else + { + sectorder = -1; + } + + numrel = buf[PE_OBJECT_NUMREL] + (buf[PE_OBJECT_NUMREL + 1] << 8); + relofs = buf[PE_OBJECT_RELPTR] + (buf[PE_OBJECT_RELPTR + 1] << 8) + + (buf[PE_OBJECT_RELPTR + 2] << 16) + (buf[PE_OBJECT_RELPTR + 3] << 24); + + seglist = (PPSEG)checkRealloc(seglist, (segcount + 1) * sizeof(PSEG)); + seglist[segcount] = (PSEG)checkMalloc(sizeof(SEG)); + + seglist[segcount]->nameindex = sectname; + seglist[segcount]->orderindex = sectorder; + seglist[segcount]->classindex = -1; + seglist[segcount]->overlayindex = -1; + seglist[segcount]->length = buf[PE_OBJECT_RAWSIZE] + (buf[PE_OBJECT_RAWSIZE + 1] << 8) + + (buf[PE_OBJECT_RAWSIZE + 2] << 16) + + (buf[PE_OBJECT_RAWSIZE + 3] << 24); + + seglist[segcount]->attr = SEG_PUBLIC | SEG_USE32; + seglist[segcount]->winFlags = buf[PE_OBJECT_FLAGS] + (buf[PE_OBJECT_FLAGS + 1] << 8) + + (buf[PE_OBJECT_FLAGS + 2] << 16) + + (buf[PE_OBJECT_FLAGS + 3] << 24); + seglist[segcount]->base = buf[PE_OBJECT_RAWPTR] + (buf[PE_OBJECT_RAWPTR + 1] << 8) + + (buf[PE_OBJECT_RAWPTR + 2] << 16) + + (buf[PE_OBJECT_RAWPTR + 3] << 24); + + if (seglist[segcount]->winFlags & WINF_ALIGN_NOPAD) + { + seglist[segcount]->winFlags &= (0xffffffff - WINF_ALIGN); + seglist[segcount]->winFlags |= WINF_ALIGN_BYTE; + } + + switch (seglist[segcount]->winFlags & WINF_ALIGN) + { + case WINF_ALIGN_BYTE: + seglist[segcount]->attr |= SEG_BYTE; + break; + case WINF_ALIGN_WORD: + seglist[segcount]->attr |= SEG_WORD; + break; + case WINF_ALIGN_DWORD: + seglist[segcount]->attr |= SEG_DWORD; + break; + case WINF_ALIGN_8: + seglist[segcount]->attr |= SEG_8BYTE; + break; + case WINF_ALIGN_PARA: + seglist[segcount]->attr |= SEG_PARA; + break; + case WINF_ALIGN_32: + seglist[segcount]->attr |= SEG_32BYTE; + break; + case WINF_ALIGN_64: + seglist[segcount]->attr |= SEG_64BYTE; + break; + case 0: + seglist[segcount]->attr |= SEG_PARA; /* default */ + break; + default: + printf("Invalid COFF object file, bad section alignment %08X\n", + seglist[segcount]->winFlags); + exit(1); + } + + /* invert all negative-logic flags */ + seglist[segcount]->winFlags ^= WINF_NEG_FLAGS; + /* remove .debug sections */ + if (!stricmp(namelist[sectname], ".debug")) + { + seglist[segcount]->winFlags |= WINF_REMOVE; + seglist[segcount]->length = 0; + numrel = 0; + } + + if (seglist[segcount]->winFlags & WINF_COMDAT) + { + printf("COMDAT section %s\n", namelist[sectname]); + comdat = (PCOMDAT)checkMalloc(sizeof(COMDATREC)); + combineType = 0; + comdat->linkwith = 0; + for (j = 0; j < numSymbols; j++) + { + if (!sym[j].name) + continue; + if (sym[j].section == (i + 1)) + { + if (sym[j].numAuxRecs != 1) + { + printf("Invalid COMDAT section reference\n"); + exit(1); + } + printf("Section %s ", sym[j].name); + combineType = sym[j].auxRecs[14]; + comdat->linkwith = sym[j].auxRecs[12] + (sym[j].auxRecs[13] << 8) + minseg - 1; + printf("Combine type %i ", sym[j].auxRecs[14]); + printf("Link alongside section %u", comdat->linkwith); + + break; + } + } + if (j == numSymbols) + { + printf("Invalid COMDAT section\n"); + exit(1); + } + for (j++; j < numSymbols; j++) + { + if (!sym[j].name) + continue; + if (sym[j].section == (i + 1)) + { + if (sym[j].numAuxRecs) + { + printf("Invalid COMDAT symbol\n"); + exit(1); + } + + printf("COMDAT Symbol %s\n", sym[j].name); + comdatsym = sym[j].name; + sym[j].isComDat = TRUE; + break; + } + } + /* associative sections don't have a name */ + if (j == numSymbols) + { + if (combineType != 5) + { + printf("\nInvalid COMDAT section\n"); + exit(1); + } + else + { + printf("\n"); + } + comdatsym = ""; /* dummy name */ + } + comdat->segnum = segcount; + comdat->combineType = combineType; + + printf("COMDATs not yet supported\n"); + exit(1); + + printf("Combine types for duplicate COMDAT symbol %s do not match\n", comdatsym); + exit(1); + } + + if (seglist[segcount]->length) + { + seglist[segcount]->data = (PUCHAR)checkMalloc(seglist[segcount]->length); + + seglist[segcount]->datmask = (PUCHAR)checkMalloc((seglist[segcount]->length + 7) / 8); + + if (seglist[segcount]->base) + { + fseek(objfile, fileStart + seglist[segcount]->base, SEEK_SET); + if (fread(seglist[segcount]->data, 1, seglist[segcount]->length, objfile) != + seglist[segcount]->length) + { + printf("Invalid COFF object file\n"); + exit(1); + } + for (j = 0; j < (seglist[segcount]->length + 7) / 8; j++) + seglist[segcount]->datmask[j] = 0xff; + } + else + { + for (j = 0; j < (seglist[segcount]->length + 7) / 8; j++) + seglist[segcount]->datmask[j] = 0; + } + } + else + { + seglist[segcount]->data = NULL; + seglist[segcount]->datmask = NULL; + } + + if (numrel) + fseek(objfile, fileStart + relofs, SEEK_SET); + for (j = 0; j < numrel; j++) + { + if (fread(buf, 1, PE_RELOC_SIZE, objfile) != PE_RELOC_SIZE) + { + printf("Invalid COFF object file, unable to read reloc table\n"); + exit(1); + } + relocs = (PPRELOC)checkRealloc(relocs, (fixcount + 1) * sizeof(PRELOC)); + relocs[fixcount] = (PRELOC)checkMalloc(sizeof(RELOC)); + /* get address to relocate */ + relocs[fixcount]->ofs = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); + relocs[fixcount]->ofs -= relshift; + /* get segment */ + relocs[fixcount]->segnum = i + minseg; + relocs[fixcount]->disp = 0; + /* get relocation target external index */ + relocs[fixcount]->target = buf[4] + (buf[5] << 8) + (buf[6] << 16) + (buf[7] << 24); + if (relocs[fixcount]->target >= numSymbols) + { + printf("Invalid COFF object file, undefined symbol\n"); + exit(1); + } + k = relocs[fixcount]->target; + relocs[fixcount]->ttype = REL_EXTONLY; /* assume external reloc */ + if (sym[k].extnum < 0) + { + switch (sym[k].class) + { + case COFF_SYM_EXTERNAL: + /* global symbols declare an extern when used in relocs */ + externs = (PEXTREC)checkRealloc(externs, (extcount + 1) * sizeof(EXTREC)); + externs[extcount].name = sym[k].name; + externs[extcount].pubdef = NULL; + externs[extcount].modnum = 0; + externs[extcount].flags = EXT_NOMATCH; + sym[k].extnum = extcount; + extcount++; + /* they may also include a COMDEF or a PUBDEF */ + /* this is dealt with after all sections loaded, to cater for COMDAT symbols */ + break; + case COFF_SYM_STATIC: /* static symbol */ + case COFF_SYM_LABEL: /* code label symbol */ + if (sym[k].section < -1) + { + printf("cannot relocate against a debug info symbol\n"); + exit(1); + break; + } + if (sym[k].section == 0) + { + if (sym[k].value) + { + externs = + (PEXTREC)checkRealloc(externs, (extcount + 1) * sizeof(EXTREC)); + externs[extcount].name = sym[k].name; + externs[extcount].pubdef = NULL; + externs[extcount].modnum = nummods; + externs[extcount].flags = EXT_NOMATCH; + sym[k].extnum = extcount; + extcount++; + + comdefs = + (PPCOMREC)checkRealloc(comdefs, (comcount + 1) * sizeof(PCOMREC)); + comdefs[comcount] = (PCOMREC)checkMalloc(sizeof(COMREC)); + comdefs[comcount]->length = sym[k].value; + comdefs[comcount]->isFar = FALSE; + comdefs[comcount]->name = sym[k].name; + comdefs[comcount]->modnum = nummods; + comcount++; + } + else + { + printf("Undefined symbol %s\n", sym[k].name); + exit(1); + } + } + else + { + /* update relocation information to reflect symbol */ + relocs[fixcount]->ttype = REL_SEGDISP; + relocs[fixcount]->disp = sym[k].value; + if (sym[k].section == -1) + { + /* absolute symbols have section=-1 */ + relocs[fixcount]->target = -1; + } + else + { + /* else get real number of section */ + relocs[fixcount]->target = sym[k].section + minseg - 1; + } + } + break; + default: + printf("undefined symbol class 0x%02X for symbol %s\n", sym[k].class, + sym[k].name); + exit(1); + } + } + if (relocs[fixcount]->ttype == REL_EXTONLY) + { + /* set relocation target to external if sym is external */ + relocs[fixcount]->target = sym[k].extnum; + } + + /* frame is current segment (only relevant for non-FLAT output) */ + relocs[fixcount]->ftype = REL_SEGFRAME; + relocs[fixcount]->frame = i + minseg; + /* set relocation type */ + switch (buf[8] + (buf[9] << 8)) + { + case COFF_FIX_DIR32: + relocs[fixcount]->rtype = FIX_OFS32; + break; + case COFF_FIX_RVA32: + relocs[fixcount]->rtype = FIX_RVA32; + break; + /* + case 0x0a: - + break; + case 0x0b: + break; + */ + case COFF_FIX_REL32: + relocs[fixcount]->rtype = FIX_SELF_OFS32; + break; + default: + printf("unsupported COFF relocation type %04X\n", buf[8] + (buf[9] << 8)); + exit(1); + } + fixcount++; + } + + segcount++; + } + /* build PUBDEFs or COMDEFs for external symbols defined here that aren't COMDAT symbols. */ + for (i = 0; i < numSymbols; i++) + { + if (sym[i].class != COFF_SYM_EXTERNAL) + continue; + if (sym[i].isComDat) + continue; + if (sym[i].section < -1) + { + break; + } + if (sym[i].section == 0) + { + if (sym[i].value) + { + comdefs = (PPCOMREC)checkRealloc(comdefs, (comcount + 1) * sizeof(PCOMREC)); + comdefs[comcount] = (PCOMREC)checkMalloc(sizeof(COMREC)); + comdefs[comcount]->length = sym[i].value; + comdefs[comcount]->isFar = FALSE; + comdefs[comcount]->name = sym[i].name; + comdefs[comcount]->modnum = 0; + comcount++; + } + } + else + { + pubdef = (PPUBLIC)checkMalloc(sizeof(PUBLIC)); + pubdef->grpnum = -1; + pubdef->typenum = 0; + pubdef->modnum = 0; + pubdef->aliasName = NULL; + pubdef->ofs = sym[i].value; + + if (sym[i].section == -1) + { + pubdef->segnum = -1; + } + else + { + pubdef->segnum = minseg + sym[i].section - 1; + } + if ((listnode = binarySearch(publics, pubcount, sym[i].name)) != NULL) + { + for (j = 0; j < listnode->count; ++j) + { + if (((PPUBLIC)listnode->object[j])->modnum == pubdef->modnum) + { + if (!((PPUBLIC)listnode->object[j])->aliasName) + { + printf("Duplicate public symbol %s\n", sym[i].name); + exit(1); + } + free(((PPUBLIC)listnode->object[j])->aliasName); + (*((PPUBLIC)listnode->object[j])) = (*pubdef); + pubdef = NULL; + break; + } + } + } + if (pubdef) + { + sortedInsert(&publics, &pubcount, sym[i].name, pubdef); + } + } + } + + if (symbolPtr && numSymbols) + free(sym); + if (stringList) + free(stringList); +} + +void loadCoffImport(FILE *objfile) +{ + UINT thiscpu; + + if (fread(buf, 1, 20, objfile) != 20) + { + printf("Unable to read from file\n"); + exit(1); + } + + if (buf[0] || buf[1] || (buf[2] != 0xff) || (buf[3] != 0xff)) + { + printf("Invalid Import entry\n"); + exit(1); + } + /* get CPU type */ + thiscpu = buf[6] + 256 * buf[7]; + printf("Import CPU=%04X\n", thiscpu); + + if ((thiscpu < 0x14c) || (thiscpu > 0x14e)) + { + printf("Unsupported CPU type for module\n"); + exit(1); + } +} diff --git a/alink/COFFLIB.C b/alink/COFFLIB.C new file mode 100644 index 0000000..1bf5eaa --- /dev/null +++ b/alink/COFFLIB.C @@ -0,0 +1,323 @@ +#include "ALINK.H" + +void loadCoffLib(FILE *libfile, PCHAR libname) +{ + UINT i, j; + UINT numsyms; + UINT modpage; + UINT memberSize; + UINT startPoint; + PUCHAR endptr; + PLIBFILE p; + PCHAR name; + PUCHAR modbuf; + PSORTENTRY symlist = NULL; + int x; + + libfiles = checkRealloc(libfiles, (libcount + 1) * sizeof(LIBFILE)); + p = &libfiles[libcount]; + p->filename = checkMalloc(strlen(libname) + 1); + strcpy(p->filename, libname); + startPoint = ftell(libfile); + + if (fread(buf, 1, 8, libfile) != 8) + { + printf("Error reading from file\n"); + exit(1); + } + buf[8] = 0; + /* complain if file header is wrong */ + if (strcmp(buf, "!\n")) + { + printf("Invalid library file format - bad file header\n"); + printf("\"%s\"\n", buf); + + exit(1); + } + /* read archive member header */ + if (fread(buf, 1, 60, libfile) != 60) + { + printf("Error reading from file\n"); + exit(1); + } + if ((buf[58] != 0x60) || (buf[59] != '\n')) + { + printf("Invalid library file format - bad member signature\n"); + exit(1); + } + buf[16] = 0; + /* check name of first linker member */ + if (strcmp(buf, "/ ")) /* 15 spaces */ + { + printf("Invalid library file format - bad member name\n"); + exit(1); + } + buf[58] = 0; + + /* strip trailing spaces from size */ + endptr = buf + 57; + while ((endptr > (buf + 48)) && isspace(*endptr)) + { + *endptr = 0; + endptr--; + } + + /* get size */ + errno = 0; + memberSize = strtoul(buf + 48, (PPCHAR)&endptr, 10); + if (errno || (*endptr)) + { + printf("Invalid library file format - bad member size\n"); + exit(1); + } + if ((memberSize < 4) && memberSize) + { + printf("Invalid library file format - bad member size\n"); + exit(1); + } + if (!memberSize) + { + numsyms = 0; + } + else + { + if (fread(buf, 1, 4, libfile) != 4) + { + printf("Error reading from file\n"); + exit(1); + } + numsyms = buf[3] + (buf[2] << 8) + (buf[1] << 16) + (buf[0] << 24); + } + printf("%u symbols\n", numsyms); + modbuf = (PUCHAR)checkMalloc(numsyms * 4); + + if (numsyms) + { + if (fread(modbuf, 1, 4 * numsyms, libfile) != 4 * numsyms) + { + printf("Error reading from file\n"); + exit(1); + } + symlist = (PSORTENTRY)checkMalloc(sizeof(SORTENTRY) * numsyms); + } + + for (i = 0; i < numsyms; i++) + { + modpage = modbuf[3 + i * 4] + (modbuf[2 + i * 4] << 8) + (modbuf[1 + i * 4] << 16) + + (modbuf[i * 4] << 24); + + name = NULL; + for (j = 0; TRUE; j++) + { + if ((x = getc(libfile)) == EOF) + { + printf("Error reading from file\n"); + exit(1); + } + if (!x) + break; + name = (char *)checkRealloc(name, j + 2); + name[j] = x; + name[j + 1] = 0; + } + if (!name) + { + printf("NULL name for symbol %u\n", i); + exit(1); + } + if (!case_sensitive) + { + strupr(name); + } + + symlist[i].id = name; + symlist[i].count = modpage; + } + + if (numsyms) + { + qsort(symlist, numsyms, sizeof(SORTENTRY), sortCompare); + p->symbols = symlist; + p->numsyms = numsyms; + + free(modbuf); + } + else + { + p->symbols = NULL; + p->numsyms = 0; + } + + /* move to an even byte boundary in the file */ + if (ftell(libfile) & 1) + { + fseek(libfile, 1, SEEK_CUR); + } + + if (ftell(libfile) != (startPoint + 68 + memberSize)) + { + printf("Invalid first linker member\n"); + printf("Pos=%08lX, should be %08X\n", ftell(libfile), startPoint + 68 + memberSize); + + exit(1); + } + + printf("Loaded first linker member\n"); + + startPoint = ftell(libfile); + + /* read archive member header */ + if (fread(buf, 1, 60, libfile) != 60) + { + printf("Error reading from file\n"); + exit(1); + } + if ((buf[58] != 0x60) || (buf[59] != '\n')) + { + printf("Invalid library file format - bad member signature\n"); + exit(1); + } + buf[16] = 0; + /* check name of second linker member */ + if (!strcmp(buf, "/ ")) /* 15 spaces */ + { + /* OK, so we've found it, now skip over */ + buf[58] = 0; + + /* strip trailing spaces from size */ + endptr = buf + 57; + while ((endptr > (buf + 48)) && isspace(*endptr)) + { + *endptr = 0; + endptr--; + } + + /* get size */ + errno = 0; + memberSize = strtoul(buf + 48, (PPCHAR)&endptr, 10); + if (errno || (*endptr)) + { + printf("Invalid library file format - bad member size\n"); + exit(1); + } + if ((memberSize < 8) && memberSize) + { + printf("Invalid library file format - bad member size\n"); + exit(1); + } + + /* move over second linker member */ + fseek(libfile, startPoint + 60 + memberSize, SEEK_SET); + + /* move to an even byte boundary in the file */ + if (ftell(libfile) & 1) + { + fseek(libfile, 1, SEEK_CUR); + } + } + else + { + fseek(libfile, startPoint, SEEK_SET); + } + + startPoint = ftell(libfile); + p->longnames = NULL; + + /* read archive member header */ + if (fread(buf, 1, 60, libfile) != 60) + { + printf("Error reading from file\n"); + exit(1); + } + if ((buf[58] != 0x60) || (buf[59] != '\n')) + { + printf("Invalid library file format - bad 3rd member signature\n"); + exit(1); + } + buf[16] = 0; + /* check name of long names linker member */ + if (!strcmp(buf, "// ")) /* 14 spaces */ + { + buf[58] = 0; + + /* strip trailing spaces from size */ + endptr = buf + 57; + while ((endptr > (buf + 48)) && isspace(*endptr)) + { + *endptr = 0; + endptr--; + } + + /* get size */ + errno = 0; + memberSize = strtoul(buf + 48, (PPCHAR)&endptr, 10); + if (errno || (*endptr)) + { + printf("Invalid library file format - bad member size\n"); + exit(1); + } + if (memberSize) + { + p->longnames = (PUCHAR)checkMalloc(memberSize); + if (fread(p->longnames, 1, memberSize, libfile) != memberSize) + { + printf("Error reading from file\n"); + exit(1); + } + } + } + else + { + /* if no long names member, move back to member header */ + fseek(libfile, startPoint, SEEK_SET); + } + + p->modsloaded = 0; + p->modlist = checkMalloc(sizeof(unsigned short) * numsyms); + p->libtype = 'C'; + p->blocksize = 1; + p->flags = LIBF_CASESENSITIVE; + libcount++; +} + +void loadcofflibmod(PLIBFILE p, FILE *libfile) +{ + char *name; + UINT ofs; + + if (fread(buf, 1, 60, libfile) != 60) + { + printf("Error reading from file\n"); + exit(1); + } + if ((buf[58] != 0x60) || (buf[59] != '\n')) + { + printf("Invalid library member header\n"); + exit(1); + } + buf[16] = 0; + if (buf[0] == '/') + { + ofs = 15; + while (isspace(buf[ofs])) + { + buf[ofs] = 0; + ofs--; + } + + ofs = strtoul(buf + 1, &name, 10); + if (!buf[1] || *name) + { + printf("Invalid string number \n"); + exit(1); + } + name = p->longnames + ofs; + } + else + { + name = buf; + } + + printf("Loading module %s\n", name); + loadcoff(libfile); +} diff --git a/alink/COMBINE.C b/alink/COMBINE.C new file mode 100644 index 0000000..0f40daf --- /dev/null +++ b/alink/COMBINE.C @@ -0,0 +1,595 @@ +#include "ALINK.H" + +void fixpubsegs(int src, int dest, UINT shift) +{ + UINT i, j; + PPUBLIC q; + + for (i = 0; i < pubcount; ++i) + { + for (j = 0; j < publics[i].count; ++j) + { + q = (PPUBLIC)publics[i].object[j]; + if (q->segnum == src) + { + q->segnum = dest; + q->ofs += shift; + } + } + } +} + +void fixpubgrps(int src, int dest) +{ + UINT i, j; + PPUBLIC q; + + for (i = 0; i < pubcount; ++i) + { + for (j = 0; j < publics[i].count; ++j) + { + q = (PPUBLIC)publics[i].object[j]; + if (q->grpnum == src) + { + q->grpnum = dest; + } + } + } +} + +void redirect_segment(long dest, long src) +{ + UINT k, n; + + fixpubsegs(src, dest, 0); + + for (k = 0; k < fixcount; k++) + { + if (relocs[k]->segnum == src) + { + relocs[k]->segnum = dest; + } + if ((relocs[k]->ttype == REL_SEGDISP) || (relocs[k]->ttype == REL_SEGONLY)) + { + if (relocs[k]->target == src) + { + relocs[k]->target = dest; + } + } + if ((relocs[k]->ftype == REL_SEGFRAME) || (relocs[k]->ftype == REL_LILEFRAME)) + { + if (relocs[k]->frame == src) + { + relocs[k]->frame = dest; + } + } + } + + if (gotstart) + { + if ((startaddr.ttype == REL_SEGDISP) || (startaddr.ttype == REL_SEGONLY)) + { + if (startaddr.target == src) + { + startaddr.target = dest; + } + } + if ((startaddr.ftype == REL_SEGFRAME) || (startaddr.ftype == REL_LILEFRAME)) + { + if (startaddr.frame == src) + { + startaddr.frame = dest; + } + } + } + + for (k = 0; k < grpcount; k++) + { + if (!grplist[k]) + continue; + for (n = 0; n < grplist[k]->numsegs; n++) + { + if (grplist[k]->segindex[n] != src) + continue; + grplist[k]->segindex[n] = dest; + } + } + + free(seglist[src]->data); + free(seglist[src]->datmask); + free(seglist[src]); + seglist[src] = 0; +} + +void combine_segments(long dest, long src) +{ + UINT k, n; + PUCHAR p, q; + long a1, a2; + + k = seglist[dest]->length; + switch (seglist[src]->attr & SEG_ALIGN) + { + case SEG_WORD: + a2 = 2; + k = (k + 1) & 0xfffffffe; + break; + case SEG_PARA: + a2 = 16; + k = (k + 0xf) & 0xfffffff0; + break; + case SEG_PAGE: + a2 = 0x100; + k = (k + 0xff) & 0xffffff00; + break; + case SEG_DWORD: + a2 = 4; + k = (k + 3) & 0xfffffffc; + break; + case SEG_MEMPAGE: + a2 = 0x1000; + k = (k + 0xfff) & 0xfffff000; + break; + case SEG_8BYTE: + a2 = 8; + k = (k + 7) & 0xfffffff8; + break; + case SEG_32BYTE: + a2 = 32; + k = (k + 31) & 0xffffffe0; + break; + case SEG_64BYTE: + a2 = 64; + k = (k + 63) & 0xffffffc0; + break; + default: + a2 = 1; + break; + } + switch (seglist[dest]->attr & SEG_ALIGN) + { + case SEG_WORD: + a1 = 2; + break; + case SEG_DWORD: + a1 = 4; + break; + case SEG_8BYTE: + a1 = 8; + break; + case SEG_PARA: + a1 = 16; + break; + case SEG_32BYTE: + a1 = 32; + break; + case SEG_64BYTE: + a1 = 64; + break; + case SEG_PAGE: + a1 = 0x100; + break; + case SEG_MEMPAGE: + a1 = 0x1000; + break; + default: + a1 = 1; + break; + } + seglist[src]->base = k; + p = checkMalloc(k + seglist[src]->length); + q = checkMalloc((k + seglist[src]->length + 7) / 8); + for (k = 0; k < seglist[dest]->length; k++) + { + if (GetNbit(seglist[dest]->datmask, k)) + { + SetNbit(q, k); + p[k] = seglist[dest]->data[k]; + } + else + { + ClearNbit(q, k); + } + } + for (; k < seglist[src]->base; k++) + { + ClearNbit(q, k); + } + for (; k < (seglist[src]->base + seglist[src]->length); k++) + { + if (GetNbit(seglist[src]->datmask, k - seglist[src]->base)) + { + p[k] = seglist[src]->data[k - seglist[src]->base]; + SetNbit(q, k); + } + else + { + ClearNbit(q, k); + } + } + seglist[dest]->length = k; + if (a2 > a1) + seglist[dest]->attr = seglist[src]->attr; + seglist[dest]->winFlags |= seglist[src]->winFlags; + free(seglist[dest]->data); + free(seglist[src]->data); + free(seglist[dest]->datmask); + free(seglist[src]->datmask); + seglist[dest]->data = p; + seglist[dest]->datmask = q; + + fixpubsegs(src, dest, seglist[src]->base); + + for (k = 0; k < fixcount; k++) + { + if (relocs[k]->segnum == src) + { + relocs[k]->segnum = dest; + relocs[k]->ofs += seglist[src]->base; + } + if (relocs[k]->ttype == REL_SEGDISP) + { + if (relocs[k]->target == src) + { + relocs[k]->target = dest; + relocs[k]->disp += seglist[src]->base; + } + } + else if (relocs[k]->ttype == REL_SEGONLY) + { + if (relocs[k]->target == src) + { + relocs[k]->target = dest; + relocs[k]->ttype = REL_SEGDISP; + relocs[k]->disp = seglist[src]->base; + } + } + if ((relocs[k]->ftype == REL_SEGFRAME) || (relocs[k]->ftype == REL_LILEFRAME)) + { + if (relocs[k]->frame == src) + { + relocs[k]->frame = dest; + } + } + } + + if (gotstart) + { + if (startaddr.ttype == REL_SEGDISP) + { + if (startaddr.target == src) + { + startaddr.target = dest; + startaddr.disp += seglist[src]->base; + } + } + else if (startaddr.ttype == REL_SEGONLY) + { + if (startaddr.target == src) + { + startaddr.target = dest; + startaddr.disp = seglist[src]->base; + startaddr.ttype = REL_SEGDISP; + } + } + if ((startaddr.ftype == REL_SEGFRAME) || (startaddr.ftype == REL_LILEFRAME)) + { + if (startaddr.frame == src) + { + startaddr.frame = dest; + } + } + } + + for (k = 0; k < grpcount; k++) + { + if (grplist[k]) + { + for (n = 0; n < grplist[k]->numsegs; n++) + { + if (grplist[k]->segindex[n] == src) + { + grplist[k]->segindex[n] = dest; + } + } + } + } + + free(seglist[src]); + seglist[src] = 0; +} + +void combine_common(long i, long j) +{ + UINT k, n; + PUCHAR p, q; + + if (seglist[j]->length > seglist[i]->length) + { + k = seglist[i]->length; + seglist[i]->length = seglist[j]->length; + seglist[j]->length = k; + p = seglist[i]->data; + q = seglist[i]->datmask; + seglist[i]->data = seglist[j]->data; + seglist[i]->datmask = seglist[j]->datmask; + } + else + { + p = seglist[j]->data; + q = seglist[j]->datmask; + } + for (k = 0; k < seglist[j]->length; k++) + { + if (GetNbit(q, k)) + { + if (GetNbit(seglist[i]->datmask, k)) + { + if (seglist[i]->data[k] != p[k]) + { + ReportError(ERR_OVERWRITE); + } + } + else + { + SetNbit(seglist[i]->datmask, k); + seglist[i]->data[k] = p[k]; + } + } + } + free(p); + free(q); + + fixpubsegs(j, i, 0); + + for (k = 0; k < fixcount; k++) + { + if (relocs[k]->segnum == j) + { + relocs[k]->segnum = i; + } + if (relocs[k]->ttype == REL_SEGDISP) + { + if (relocs[k]->target == j) + { + relocs[k]->target = i; + } + } + else if (relocs[k]->ttype == REL_SEGONLY) + { + if (relocs[k]->target == j) + { + relocs[k]->target = i; + } + } + if ((relocs[k]->ftype == REL_SEGFRAME) || (relocs[k]->ftype == REL_LILEFRAME)) + { + if (relocs[k]->frame == j) + { + relocs[k]->frame = i; + } + } + } + + if (gotstart) + { + if (startaddr.ttype == REL_SEGDISP) + { + if (startaddr.target == j) + { + startaddr.target = i; + } + } + else if (startaddr.ttype == REL_SEGONLY) + { + if (startaddr.target == j) + { + startaddr.target = i; + } + } + if ((startaddr.ftype == REL_SEGFRAME) || (startaddr.ftype == REL_LILEFRAME)) + { + if (startaddr.frame == j) + { + startaddr.frame = i; + } + } + } + + for (k = 0; k < grpcount; k++) + { + if (grplist[k]) + { + for (n = 0; n < grplist[k]->numsegs; n++) + { + if (grplist[k]->segindex[n] == j) + { + grplist[k]->segindex[n] = i; + } + } + } + } + + free(seglist[j]); + seglist[j] = 0; +} + +void combine_groups(long i, long j) +{ + long n, m; + char match; + + for (n = 0; n < grplist[j]->numsegs; n++) + { + match = 0; + for (m = 0; m < grplist[i]->numsegs; m++) + { + if (grplist[j]->segindex[n] == grplist[i]->segindex[m]) + { + match = 1; + } + } + if (!match) + { + grplist[i]->numsegs++; + grplist[i]->segindex[grplist[i]->numsegs] = grplist[j]->segindex[n]; + } + } + free(grplist[j]); + grplist[j] = 0; + + fixpubgrps(j, i); + + for (n = 0; n < fixcount; n++) + { + if (relocs[n]->ftype == REL_GRPFRAME) + { + if (relocs[n]->frame == j) + { + relocs[n]->frame = i; + } + } + if ((relocs[n]->ttype == REL_GRPONLY) || (relocs[n]->ttype == REL_GRPDISP)) + { + if (relocs[n]->target == j) + { + relocs[n]->target = i; + } + } + } + + if (gotstart) + { + if ((startaddr.ttype == REL_GRPDISP) || (startaddr.ttype == REL_GRPONLY)) + { + if (startaddr.target == j) + { + startaddr.target = i; + } + } + if (startaddr.ftype == REL_GRPFRAME) + { + if (startaddr.frame == j) + { + startaddr.frame = i; + } + } + } +} + +void combineBlocks() +{ + long i, j, k; + char *name; + long attr; + UINT count; + UINT *slist; + UINT curseg; + + for (i = 0; i < segcount; i++) + { + if (seglist[i] && ((seglist[i]->attr & SEG_ALIGN) != SEG_ABS)) + { + if (seglist[i]->winFlags & WINF_COMDAT) + continue; /* don't combine COMDAT segments */ + name = namelist[seglist[i]->nameindex]; + attr = seglist[i]->attr & (SEG_COMBINE | SEG_USE32); + switch (attr & SEG_COMBINE) + { + case SEG_STACK: + for (j = i + 1; j < segcount; j++) + { + if (!seglist[j]) + continue; + if (seglist[j]->winFlags & WINF_COMDAT) + continue; + if ((seglist[j]->attr & SEG_ALIGN) == SEG_ABS) + continue; + if ((seglist[j]->attr & SEG_COMBINE) != SEG_STACK) + continue; + combine_segments(i, j); + } + break; + case SEG_PUBLIC: + case SEG_PUBLIC2: + case SEG_PUBLIC3: + slist = (UINT *)checkMalloc(sizeof(UINT)); + slist[0] = i; + /* get list of segments to combine */ + for (j = i + 1, count = 1; j < segcount; j++) + { + if (!seglist[j]) + continue; + if (seglist[j]->winFlags & WINF_COMDAT) + continue; + if ((seglist[j]->attr & SEG_ALIGN) == SEG_ABS) + continue; + if (attr != (seglist[j]->attr & (SEG_COMBINE | SEG_USE32))) + continue; + if (strcmp(name, namelist[seglist[j]->nameindex]) != 0) + continue; + slist = (UINT *)checkRealloc(slist, (count + 1) * sizeof(UINT)); + slist[count] = j; + count++; + } + /* sort them by sortorder */ + for (j = 1; j < count; j++) + { + curseg = slist[j]; + for (k = j - 1; k >= 0; k--) + { + if (seglist[slist[k]]->orderindex < 0) + break; + if (seglist[curseg]->orderindex >= 0) + { + if (strcmp(namelist[seglist[curseg]->orderindex], + namelist[seglist[slist[k]]->orderindex]) >= 0) + break; + } + slist[k + 1] = slist[k]; + } + k++; + slist[k] = curseg; + } + /* then combine in that order */ + for (j = 1; j < count; j++) + { + combine_segments(i, slist[j]); + } + free(slist); + break; + case SEG_COMMON: + for (j = i + 1; j < segcount; j++) + { + if ((seglist[j] && ((seglist[j]->attr & SEG_ALIGN) != SEG_ABS)) && + ((seglist[i]->attr & (SEG_ALIGN | SEG_COMBINE | SEG_USE32)) == + (seglist[j]->attr & (SEG_ALIGN | SEG_COMBINE | SEG_USE32))) && + (strcmp(name, namelist[seglist[j]->nameindex]) == 0) && + !(seglist[j]->winFlags & WINF_COMDAT)) + { + combine_common(i, j); + } + } + break; + default: + break; + } + } + } + + for (i = 0; i < grpcount; i++) + { + if (grplist[i]) + { + for (j = i + 1; j < grpcount; j++) + { + if (!grplist[j]) + continue; + if (strcmp(namelist[grplist[i]->nameindex], namelist[grplist[j]->nameindex]) == 0) + { + combine_groups(i, j); + } + } + } + } +} diff --git a/alink/Makefile b/alink/Makefile new file mode 100644 index 0000000..30a2a31 --- /dev/null +++ b/alink/Makefile @@ -0,0 +1,69 @@ +CC ?= gcc +CLANG ?= clang +SAN_CC ?= gcc +CLANG_TIDY ?= clang-tidy +CLANG_FORMAT ?= clang-format +SCAN_BUILD ?= scan-build +CPPCHECK ?= cppcheck +VALGRIND ?= valgrind + +CPPFLAGS ?= +CSTD ?= -std=gnu89 +BASE_CFLAGS ?= -O2 +COMMON_WARNINGS := -Wall -Werror -Wformat=2 -Werror=uninitialized -Wno-pointer-sign +GCC_WARNINGS := -Werror=maybe-uninitialized -Winit-self +CLANG_WARNINGS := -Wconditional-uninitialized +CFLAGS ?= $(BASE_CFLAGS) $(CSTD) $(COMMON_WARNINGS) $(GCC_WARNINGS) +LDFLAGS ?= +LDLIBS ?= + +TARGET ?= alink +SAN_TARGET ?= alink.san +SOURCES := ALINK.C COMBINE.C UTIL.C OUTPUT.C OBJLOAD.C COFF.C COFFLIB.C +OBJECTS := $(SOURCES:.C=.o) +FORMAT_FILES := $(SOURCES) ALINK.H +SANITIZER_FLAGS := -O1 -g3 -fno-omit-frame-pointer -fsanitize=address,undefined +CLANG_ANALYZE_FLAGS := -Xanalyzer -analyzer-output=text -Xanalyzer -analyzer-checker=core.uninitialized.Assign,core.uninitialized.Branch + +.PHONY: all clean format format-check tidy cppcheck scan-build analyze sanitizers valgrind ci + +all: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +%.o: %.C ALINK.H + $(CC) $(CPPFLAGS) $(CFLAGS) -x c -c -o $@ $< + +$(SAN_TARGET): $(SOURCES) ALINK.H + $(SAN_CC) $(CPPFLAGS) $(CSTD) $(COMMON_WARNINGS) $(GCC_WARNINGS) $(SANITIZER_FLAGS) -x c $(SOURCES) -o $@ $(LDLIBS) + +format: + $(CLANG_FORMAT) -i $(FORMAT_FILES) + +format-check: + $(CLANG_FORMAT) --dry-run --Werror $(FORMAT_FILES) + +tidy: + $(CLANG_TIDY) $(SOURCES) -- $(CPPFLAGS) $(CSTD) $(COMMON_WARNINGS) $(CLANG_WARNINGS) -x c + +cppcheck: + $(CPPCHECK) --enable=warning,performance,portability --std=c89 --language=c --force --inconclusive --error-exitcode=1 $(SOURCES) + +scan-build: + $(SCAN_BUILD) --status-bugs $(MAKE) clean all CC=$(CLANG) + +analyze: + @tmp=$$(mktemp); \ + $(CLANG) $(CPPFLAGS) $(CSTD) $(BASE_CFLAGS) $(COMMON_WARNINGS) $(CLANG_WARNINGS) $(CLANG_ANALYZE_FLAGS) --analyze -x c $(SOURCES) 2>&1 | tee $$tmp; \ + ! grep -E 'core\\.uninitialized' $$tmp + +sanitizers: $(SAN_TARGET) + +valgrind: $(SAN_TARGET) + $(VALGRIND) --error-exitcode=1 --leak-check=full --show-leak-kinds=all ./$(TARGET) -h + +ci: clean all format-check tidy cppcheck analyze sanitizers valgrind + +clean: + rm -f $(OBJECTS) $(TARGET) $(SAN_TARGET) diff --git a/alink/OBJLOAD.C b/alink/OBJLOAD.C new file mode 100644 index 0000000..3bba531 --- /dev/null +++ b/alink/OBJLOAD.C @@ -0,0 +1,1909 @@ +#include "ALINK.H" + +char t_thred[4]; +char f_thred[4]; +int t_thredindex[4]; +int f_thredindex[4]; +static PCOMDAT currentComdat = NULL; + +static UINT LoadComdefNumber(long *index) +{ + UINT value; + unsigned char leaf; + + leaf = buf[*index]; + (*index)++; + switch (leaf) + { + case 0x81: + value = buf[*index] + 256U * buf[*index + 1]; + (*index) += 2; + break; + case 0x84: + value = buf[*index] + 256U * buf[*index + 1] + 65536U * buf[*index + 2]; + (*index) += 3; + break; + case 0x88: + value = buf[*index] + 256U * buf[*index + 1] + 65536U * buf[*index + 2] + + ((UINT)buf[*index + 3] << 24); + (*index) += 4; + break; + default: + value = leaf; + break; + } + return value; +} + +static void LoadComdefRecord(int isLocal) +{ + long i, j, k; + + for (j = 0; j < reclength;) + { + externs = (PEXTREC)checkRealloc(externs, (extcount + 1) * sizeof(EXTREC)); + externs[extcount].name = checkMalloc(buf[j] + 1); + k = buf[j]; + j++; + for (i = 0; i < k; i++, j++) + { + externs[extcount].name[i] = buf[j]; + } + externs[extcount].name[i] = 0; + if (!case_sensitive) + { + strupr(externs[extcount].name); + } + externs[extcount].typenum = GetIndex(buf, &j); + externs[extcount].pubdef = NULL; + externs[extcount].flags = EXT_NOMATCH; + externs[extcount].modnum = isLocal ? nummods : 0; + if (buf[j] == 0x61) + { + UINT count; + UINT element_size; + + j++; + count = LoadComdefNumber(&j); + element_size = LoadComdefNumber(&j); + i = count * element_size; + k = 1; + } + else if (buf[j] == 0x62) + { + j++; + i = LoadComdefNumber(&j); + k = 0; + } + else + { + printf("Unknown COMDEF data type %02X\n", buf[j]); + exit(1); + } + comdefs = (PPCOMREC)checkRealloc(comdefs, (comcount + 1) * sizeof(PCOMREC)); + comdefs[comcount] = (PCOMREC)checkMalloc(sizeof(COMREC)); + comdefs[comcount]->length = i; + comdefs[comcount]->isFar = k; + comdefs[comcount]->modnum = isLocal ? nummods : 0; + comdefs[comcount]->name = checkStrdup(externs[extcount].name); + extcount++; + comcount++; + } +} + +static UINT LiDataSize(PDATABLOCK p) +{ + UINT i; + UINT total; + + if (!p) + return 0; + if (!p->blocks) + return p->count * ((PUCHAR)p->data)[0]; + + total = 0; + for (i = 0; i < (UINT)p->blocks; i++) + { + total += LiDataSize(((PPDATABLOCK)p->data)[i]); + } + return p->count * total; +} + +static void EnsureSegmentSize(long segnum, UINT size) +{ + UINT i; + UINT oldbytes, newbytes; + + if (size <= seglist[segnum]->length) + return; + + oldbytes = (seglist[segnum]->length + 7) / 8; + newbytes = (size + 7) / 8; + seglist[segnum]->data = (PUCHAR)checkRealloc(seglist[segnum]->data, size); + seglist[segnum]->datmask = (PUCHAR)checkRealloc(seglist[segnum]->datmask, newbytes); + for (i = seglist[segnum]->length; i < size; i++) + { + seglist[segnum]->data[i] = 0; + } + for (i = oldbytes; i < newbytes; i++) + { + seglist[segnum]->datmask[i] = 0; + } + seglist[segnum]->length = size; +} + +static unsigned short GetComdatAlign(unsigned char align, long assocSeg) +{ + switch (align) + { + case COMDAT_ALIGN_SEG: + if (assocSeg >= 0) + return seglist[assocSeg]->attr & SEG_ALIGN; + return SEG_BYTE; + case COMDAT_ALIGN_WORD: + return SEG_WORD; + case COMDAT_ALIGN_PARA: + return SEG_PARA; + case COMDAT_ALIGN_4K: + return SEG_MEMPAGE; + case COMDAT_ALIGN_DWORD: + return SEG_DWORD; + case COMDAT_ALIGN_BYTE: + default: + return SEG_BYTE; + } +} + +static PCOMDAT FindLatestComdat(char *name, UINT modnum) +{ + UINT i; + PSORTENTRY listnode; + + listnode = binarySearch(comdats, comdatcount, name); + if (!listnode) + return NULL; + for (i = listnode->count; i > 0; i--) + { + PCOMDAT comdat; + + comdat = (PCOMDAT)listnode->object[i - 1]; + if (comdat->modnum == modnum) + return comdat; + } + return NULL; +} + +void DestroyLIDATA(PDATABLOCK p) +{ + long i; + if (p->blocks) + { + for (i = 0; i < p->blocks; i++) + { + DestroyLIDATA(((PPDATABLOCK)(p->data))[i]); + } + } + free(p->data); + free(p); +} + +PDATABLOCK BuildLiData(long *bufofs) +{ + PDATABLOCK p; + long i, j; + + p = checkMalloc(sizeof(DATABLOCK)); + i = *bufofs; + p->dataofs = i - lidata->dataofs; + p->count = buf[i] + 256 * buf[i + 1]; + i += 2; + if (rectype == LIDATA32) + { + p->count += (buf[i] + 256 * buf[i + 1]) << 16; + i += 2; + } + p->blocks = buf[i] + 256 * buf[i + 1]; + i += 2; + if (p->blocks) + { + p->data = checkMalloc(p->blocks * sizeof(PDATABLOCK)); + for (j = 0; j < p->blocks; j++) + { + ((PPDATABLOCK)p->data)[j] = BuildLiData(&i); + } + } + else + { + p->data = checkMalloc(buf[i] + 1); + ((char *)p->data)[0] = buf[i]; + i++; + for (j = 0; j < ((PUCHAR)p->data)[0]; j++, i++) + { + ((PUCHAR)p->data)[j + 1] = buf[i]; + } + } + *bufofs = i; + return p; +} + +void EmitLiData(PDATABLOCK p, long segnum, long *ofs) +{ + long i, j; + + for (i = 0; i < p->count; i++) + { + if (p->blocks) + { + for (j = 0; j < p->blocks; j++) + { + EmitLiData(((PPDATABLOCK)p->data)[j], segnum, ofs); + } + } + else + { + for (j = 0; j < ((PUCHAR)p->data)[0]; j++, (*ofs)++) + { + if ((*ofs) >= seglist[segnum]->length) + { + ReportError(ERR_INV_DATA); + } + if (GetNbit(seglist[segnum]->datmask, *ofs)) + { + if (seglist[segnum]->data[*ofs] != ((PUCHAR)p->data)[j + 1]) + { + ReportError(ERR_OVERWRITE); + } + } + seglist[segnum]->data[*ofs] = ((PUCHAR)p->data)[j + 1]; + SetNbit(seglist[segnum]->datmask, *ofs); + } + } + } +} + +void RelocLIDATA(PDATABLOCK p, long *ofs, PRELOC r) +{ + long i, j; + + for (i = 0; i < p->count; i++) + { + if (p->blocks) + { + for (j = 0; j < p->blocks; j++) + { + RelocLIDATA(((PPDATABLOCK)p->data)[j], ofs, r); + } + } + else + { + j = r->ofs - p->dataofs; + if (j >= 0) + { + if ((j < 5) || ((li_le == PREV_LI32) && (j < 7))) + { + ReportError(ERR_BAD_FIXUP); + } + relocs = (PPRELOC)checkRealloc(relocs, (fixcount + 1) * sizeof(PRELOC)); + relocs[fixcount] = checkMalloc(sizeof(RELOC)); + memcpy(relocs[fixcount], r, sizeof(RELOC)); + relocs[fixcount]->ofs = *ofs + j; + fixcount++; + *ofs += ((PUCHAR)p->data)[0]; + } + } + } +} + +void LoadFIXUP(PRELOC r, PUCHAR buf, long *p) +{ + long j; + int thrednum; + + j = *p; + + r->ftype = buf[j] >> 4; + r->ttype = buf[j] & 0xf; + r->disp = 0; + j++; + if (r->ftype & FIX_THRED) + { + thrednum = r->ftype & THRED_MASK; + if (thrednum > 3) + { + ReportError(ERR_BAD_FIXUP); + return; + } + r->ftype = (f_thred[thrednum] >> 2) & 7; + switch (r->ftype) + { + case REL_SEGFRAME: + case REL_GRPFRAME: + case REL_EXTFRAME: + r->frame = f_thredindex[thrednum]; + if (!r->frame) + { + ReportError(ERR_BAD_FIXUP); + } + break; + case REL_LILEFRAME: + case REL_TARGETFRAME: + break; + default: + ReportError(ERR_BAD_FIXUP); + } + switch (r->ftype) + { + case REL_SEGFRAME: + r->frame += (long)segmin - 1; + break; + case REL_GRPFRAME: + r->frame += (long)grpmin - 1; + break; + case REL_EXTFRAME: + r->frame += (long)extmin - 1; + break; + case REL_LILEFRAME: + r->frame = prevseg; + break; + default: + break; + } + } + else + { + switch (r->ftype) + { + case REL_SEGFRAME: + case REL_GRPFRAME: + case REL_EXTFRAME: + r->frame = GetIndex(buf, &j); + if (!r->frame) + { + ReportError(ERR_BAD_FIXUP); + } + break; + case REL_LILEFRAME: + case REL_TARGETFRAME: + break; + default: + ReportError(ERR_BAD_FIXUP); + } + switch (r->ftype) + { + case REL_SEGFRAME: + r->frame += (long)segmin - 1; + break; + case REL_GRPFRAME: + r->frame += (long)grpmin - 1; + break; + case REL_EXTFRAME: + r->frame += (long)extmin - 1; + break; + case REL_LILEFRAME: + r->frame = prevseg; + break; + default: + break; + } + } + if (r->ttype & FIX_THRED) + { + thrednum = r->ttype & 3; + if ((r->ttype & 4) == 0) /* P bit not set? */ + { + r->ttype = (t_thred[thrednum] >> 2) & 3; /* DISP present */ + } + else + { + r->ttype = ((t_thred[thrednum] >> 2) & 3) | 4; /* no disp */ + } + r->target = t_thredindex[thrednum]; + switch (r->ttype) + { + case REL_SEGDISP: + case REL_GRPDISP: + case REL_EXTDISP: + case REL_SEGONLY: + case REL_GRPONLY: + case REL_EXTONLY: + if (!r->target) + { + ReportError(ERR_BAD_FIXUP); + } + break; + case REL_EXPFRAME: + break; + default: + ReportError(ERR_BAD_FIXUP); + } + switch (r->ttype) + { + case REL_SEGDISP: + r->target += (long)segmin - 1; + break; + case REL_GRPDISP: + r->target += (long)grpmin - 1; + break; + case REL_EXTDISP: + r->target += (long)extmin - 1; + break; + case REL_EXPFRAME: + break; + case REL_SEGONLY: + r->target += (long)segmin - 1; + break; + case REL_GRPONLY: + r->target += (long)grpmin - 1; + break; + case REL_EXTONLY: + r->target += (long)extmin - 1; + break; + } + } + else + { + r->target = GetIndex(buf, &j); + switch (r->ttype) + { + case REL_SEGDISP: + case REL_GRPDISP: + case REL_EXTDISP: + case REL_SEGONLY: + case REL_GRPONLY: + case REL_EXTONLY: + if (!r->target) + { + ReportError(ERR_BAD_FIXUP); + } + break; + case REL_EXPFRAME: + break; + default: + ReportError(ERR_BAD_FIXUP); + } + switch (r->ttype) + { + case REL_SEGDISP: + r->target += (long)segmin - 1; + break; + case REL_GRPDISP: + r->target += (long)grpmin - 1; + break; + case REL_EXTDISP: + r->target += (long)extmin - 1; + break; + case REL_EXPFRAME: + break; + case REL_SEGONLY: + r->target += (long)segmin - 1; + break; + case REL_GRPONLY: + r->target += (long)grpmin - 1; + break; + case REL_EXTONLY: + r->target += (long)extmin - 1; + break; + } + } + switch (r->ttype) + { + case REL_SEGDISP: + case REL_GRPDISP: + case REL_EXTDISP: + case REL_EXPFRAME: + r->disp = buf[j] + buf[j + 1] * 256; + j += 2; + if (rectype == FIXUPP32) + { + r->disp += (buf[j] + buf[j + 1] * 256) << 16; + j += 2; + } + break; + default: + break; + } + if ((r->ftype == REL_TARGETFRAME) && ((r->ttype & FIX_THRED) == 0)) + { + switch (r->ttype) + { + case REL_SEGDISP: + case REL_GRPDISP: + case REL_EXTDISP: + case REL_EXPFRAME: + r->ftype = r->ttype; + r->frame = r->target; + break; + case REL_SEGONLY: + case REL_GRPONLY: + case REL_EXTONLY: + r->ftype = r->ttype - 4; + r->frame = r->target; + break; + } + } + + *p = j; +} + +long loadmod(FILE *objfile) +{ + long modpos; + long done; + long i, j, k; + long segnum, grpnum; + PRELOC r; + PPUBLIC pubdef; + PCHAR name, aliasName; + PSORTENTRY listnode; + + modpos = 0; + done = 0; + li_le = 0; + lidata = 0; + + while (!done) + { + if (fread(buf, 1, 3, objfile) != 3) + { + ReportError(ERR_NO_MODEND); + } + rectype = buf[0]; + reclength = buf[1] + 256 * buf[2]; + if (fread(buf, 1, reclength, afile) != reclength) + { + ReportError(ERR_NO_RECDATA); + } + reclength--; /* remove checksum */ + if ((!modpos) && (rectype != THEADR) && (rectype != LHEADR)) + { + ReportError(ERR_NO_HEADER); + } + switch (rectype) + { + case THEADR: + case LHEADR: + if (modpos) + { + ReportError(ERR_EXTRA_HEADER); + } + modname = checkRealloc(modname, (nummods + 1) * sizeof(PCHAR)); + modname[nummods] = checkMalloc(buf[0] + 1); + for (i = 0; i < buf[0]; i++) + { + modname[nummods][i] = buf[i + 1]; + } + modname[nummods][i] = 0; + strupr(modname[nummods]); + /* printf("Loading module %s\n",modname[nummods]);*/ + if ((buf[0] + 1) != reclength) + { + ReportError(ERR_EXTRA_DATA); + } + namemin = namecount; + segmin = segcount; + extmin = extcount; + fixmin = fixcount; + grpmin = grpcount; + impmin = impcount; + expmin = expcount; + commin = comcount; + currentComdat = NULL; + nummods++; + break; + case COMENT: + li_le = 0; + if (lidata) + { + DestroyLIDATA(lidata); + lidata = 0; + } + if (reclength >= 2) + { + switch (buf[1]) + { + case COMENT_LIB_SPEC: + case COMENT_DEFLIB: + filename = checkRealloc(filename, (filecount + 1) * sizeof(PCHAR)); + filename[filecount] = (PCHAR)checkMalloc(reclength - 1 + 4); + /* get filename */ + for (i = 0; i < reclength - 2; i++) + { + filename[filecount][i] = buf[i + 2]; + } + filename[filecount][reclength - 2] = 0; + for (i = strlen(filename[filecount]) - 1; + (i >= 0) && !IS_PATH_CHAR(filename[filecount][i]); i--) + { + if (filename[filecount][i] == '.') + break; + } + if (((i >= 0) && (filename[filecount][i] != '.')) || (i < 0)) + { + strcat(filename[filecount], ".lib"); + } + /* add default library to file list */ + filecount++; + break; + case COMENT_OMFEXT: + if (reclength < 4) + { + ReportError(ERR_INVALID_COMENT); + } + switch (buf[2]) + { + case EXT_IMPDEF: + j = 4; + if (reclength < (j + 4)) + { + ReportError(ERR_INVALID_COMENT); + } + impdefs = checkRealloc(impdefs, (impcount + 1) * sizeof(IMPREC)); + impdefs[impcount].flags = buf[3]; + impdefs[impcount].int_name = checkMalloc(buf[j] + 1); + for (i = 0; i < buf[j]; i++) + { + impdefs[impcount].int_name[i] = buf[j + i + 1]; + } + j += buf[j] + 1; + impdefs[impcount].int_name[i] = 0; + if (!case_sensitive) + { + strupr(impdefs[impcount].int_name); + } + impdefs[impcount].mod_name = checkMalloc(buf[j] + 1); + for (i = 0; i < buf[j]; i++) + { + impdefs[impcount].mod_name[i] = buf[j + i + 1]; + } + j += buf[j] + 1; + impdefs[impcount].mod_name[i] = 0; + if (!case_sensitive) + { + strupr(impdefs[impcount].mod_name); + } + if (impdefs[impcount].flags) + { + impdefs[impcount].ordinal = buf[j] + 256 * buf[j + 1]; + j += 2; + } + else + { + if (buf[j]) + { + impdefs[impcount].imp_name = checkMalloc(buf[j] + 1); + for (i = 0; i < buf[j]; i++) + { + impdefs[impcount].imp_name[i] = buf[j + i + 1]; + } + j += buf[j] + 1; + impdefs[impcount].imp_name[i] = 0; + } + else + { + impdefs[impcount].imp_name = + checkMalloc(strlen(impdefs[impcount].int_name) + 1); + strcpy(impdefs[impcount].imp_name, impdefs[impcount].int_name); + } + } + impcount++; + break; + case EXT_EXPDEF: + expdefs = checkRealloc(expdefs, (expcount + 1) * sizeof(EXPREC)); + j = 4; + expdefs[expcount].flags = buf[3]; + expdefs[expcount].pubdef = NULL; + expdefs[expcount].exp_name = checkMalloc(buf[j] + 1); + for (i = 0; i < buf[j]; i++) + { + expdefs[expcount].exp_name[i] = buf[j + i + 1]; + } + expdefs[expcount].exp_name[buf[j]] = 0; + if (!case_sensitive) + { + strupr(expdefs[expcount].exp_name); + } + j += buf[j] + 1; + if (buf[j]) + { + expdefs[expcount].int_name = checkMalloc(buf[j] + 1); + for (i = 0; i < buf[j]; i++) + { + expdefs[expcount].int_name[i] = buf[j + i + 1]; + } + expdefs[expcount].int_name[buf[j]] = 0; + if (!case_sensitive) + { + strupr(expdefs[expcount].int_name); + } + } + else + { + expdefs[expcount].int_name = + checkMalloc(strlen(expdefs[expcount].exp_name) + 1); + strcpy(expdefs[expcount].int_name, expdefs[expcount].exp_name); + } + j += buf[j] + 1; + if (expdefs[expcount].flags & EXP_ORD) + { + expdefs[expcount].ordinal = buf[j] + 256 * buf[j + 1]; + } + else + { + expdefs[expcount].ordinal = 0; + } + expcount++; + break; + default: + ReportError(ERR_INVALID_COMENT); + } + break; + case COMENT_DOSSEG: + break; + case COMENT_TRANSLATOR: + case COMENT_INTEL_COPYRIGHT: + case COMENT_MSDOS_VER: + case COMENT_MEMMODEL: + case COMENT_NEWOMF: + case COMENT_LINKPASS: + case COMENT_LIBMOD: + case COMENT_EXESTR: + case COMENT_INCERR: + case COMENT_NOPAD: + case COMENT_WKEXT: + case COMENT_LZEXT: + case COMENT_PHARLAP: + case COMENT_IBM386: + case COMENT_RECORDER: + case COMENT_COMMENT: + case COMENT_COMPILER: + case COMENT_DATE: + case COMENT_TIME: + case COMENT_USER: + case COMENT_DEPFILE: + case COMENT_COMMANDLINE: + case COMENT_PUBTYPE: + case COMENT_COMPARAM: + case COMENT_TYPDEF: + case COMENT_STRUCTMEM: + case COMENT_OPENSCOPE: + case COMENT_LOCAL: + case COMENT_ENDSCOPE: + case COMENT_SOURCEFILE: + break; + default: + printf("COMENT Record (unknown type %02X)\n", buf[1]); + break; + } + } + break; + case TYPDEF: + case LIBEND: + break; + case LLNAMES: + case LNAMES: + j = 0; + while (j < reclength) + { + namelist = (PPCHAR)checkRealloc(namelist, (namecount + 1) * sizeof(PCHAR)); + namelist[namecount] = checkMalloc(buf[j] + 1); + for (i = 0; i < buf[j]; i++) + { + namelist[namecount][i] = buf[j + i + 1]; + } + namelist[namecount][buf[j]] = 0; + if (!case_sensitive) + { + strupr(namelist[namecount]); + } + j += buf[j] + 1; + namecount++; + } + break; + case SEGDEF: + case SEGDEF32: + seglist = (PPSEG)checkRealloc(seglist, (segcount + 1) * sizeof(PSEG)); + seglist[segcount] = checkMalloc(sizeof(SEG)); + seglist[segcount]->attr = buf[0]; + j = 1; + if ((seglist[segcount]->attr & SEG_ALIGN) == SEG_ABS) + { + seglist[segcount]->absframe = buf[j] + 256 * buf[j + 1]; + seglist[segcount]->absofs = buf[j + 2]; + j += 3; + } + seglist[segcount]->length = buf[j] + 256 * buf[j + 1]; + j += 2; + if (rectype == SEGDEF32) + { + seglist[segcount]->length += (buf[j] + 256 * buf[j + 1]) << 16; + j += 2; + } + if (seglist[segcount]->attr & SEG_BIG) + { + if (rectype == SEGDEF) + { + seglist[segcount]->length += 65536; + } + else + { + if ((seglist[segcount]->attr & SEG_ALIGN) != SEG_ABS) + { + ReportError(ERR_SEG_TOO_LARGE); + } + } + } + seglist[segcount]->nameindex = GetIndex(buf, &j) - 1; + seglist[segcount]->classindex = GetIndex(buf, &j) - 1; + seglist[segcount]->overlayindex = GetIndex(buf, &j) - 1; + seglist[segcount]->orderindex = -1; + if (seglist[segcount]->nameindex >= 0) + { + seglist[segcount]->nameindex += namemin; + } + if (seglist[segcount]->classindex >= 0) + { + seglist[segcount]->classindex += namemin; + } + if (seglist[segcount]->overlayindex >= 0) + { + seglist[segcount]->overlayindex += namemin; + } + if ((seglist[segcount]->attr & SEG_ALIGN) != SEG_ABS) + { + seglist[segcount]->data = checkMalloc(seglist[segcount]->length); + seglist[segcount]->datmask = checkMalloc((seglist[segcount]->length + 7) / 8); + for (i = 0; i < (seglist[segcount]->length + 7) / 8; i++) + { + seglist[segcount]->datmask[i] = 0; + } + } + else + { + seglist[segcount]->data = 0; + seglist[segcount]->datmask = 0; + seglist[segcount]->attr &= (0xffff - SEG_COMBINE); + seglist[segcount]->attr |= SEG_PRIVATE; + } + switch (seglist[segcount]->attr & SEG_COMBINE) + { + case SEG_PRIVATE: + case SEG_PUBLIC: + case SEG_PUBLIC2: + case SEG_COMMON: + case SEG_PUBLIC3: + break; + case SEG_STACK: + /* stack segs are always byte aligned */ + seglist[segcount]->attr &= (0xffff - SEG_ALIGN); + seglist[segcount]->attr |= SEG_BYTE; + break; + default: + ReportError(ERR_BAD_SEGDEF); + break; + } + if ((seglist[segcount]->attr & SEG_ALIGN) == SEG_BADALIGN) + { + ReportError(ERR_BAD_SEGDEF); + } + if ((seglist[segcount]->classindex >= 0) && + (!stricmp(namelist[seglist[segcount]->classindex], "CODE") || + !stricmp(namelist[seglist[segcount]->classindex], "TEXT"))) + { + /* code segment */ + seglist[segcount]->winFlags = + WINF_CODE | WINF_INITDATA | WINF_EXECUTE | WINF_READABLE | WINF_NEG_FLAGS; + } + else /* data segment */ + seglist[segcount]->winFlags = + WINF_INITDATA | WINF_READABLE | WINF_WRITEABLE | WINF_NEG_FLAGS; + + if (!stricmp(namelist[seglist[segcount]->nameindex], "$$SYMBOLS") || + !stricmp(namelist[seglist[segcount]->nameindex], "$$TYPES")) + { + seglist[segcount]->winFlags |= WINF_REMOVE; + } + segcount++; + break; + case LEDATA: + case LEDATA32: + j = 0; + prevseg = GetIndex(buf, &j) - 1; + if (prevseg < 0) + { + ReportError(ERR_INV_SEG); + } + prevseg += segmin; + if ((seglist[prevseg]->attr & SEG_ALIGN) == SEG_ABS) + { + ReportError(ERR_ABS_SEG); + } + prevofs = buf[j] + (buf[j + 1] << 8); + j += 2; + if (rectype == LEDATA32) + { + prevofs += (buf[j] + (buf[j + 1] << 8)) << 16; + j += 2; + } + for (k = 0; j < reclength; j++, k++) + { + if ((prevofs + k) >= seglist[prevseg]->length) + { + ReportError(ERR_INV_DATA); + } + if (GetNbit(seglist[prevseg]->datmask, prevofs + k)) + { + if (seglist[prevseg]->data[prevofs + k] != buf[j]) + { + printf("%08lX: %08lX: %i, %u,%u,%li\n", prevofs + k, j, + GetNbit(seglist[prevseg]->datmask, prevofs + k), segcount, segmin, + prevseg); + ReportError(ERR_OVERWRITE); + } + } + seglist[prevseg]->data[prevofs + k] = buf[j]; + SetNbit(seglist[prevseg]->datmask, prevofs + k); + } + li_le = PREV_LE; + break; + case LIDATA: + case LIDATA32: + if (lidata) + { + DestroyLIDATA(lidata); + } + j = 0; + prevseg = GetIndex(buf, &j) - 1; + if (prevseg < 0) + { + ReportError(ERR_INV_SEG); + } + prevseg += segmin; + if ((seglist[prevseg]->attr & SEG_ALIGN) == SEG_ABS) + { + ReportError(ERR_ABS_SEG); + } + prevofs = buf[j] + (buf[j + 1] << 8); + j += 2; + if (rectype == LIDATA32) + { + prevofs += (buf[j] + (buf[j + 1] << 8)) << 16; + j += 2; + } + lidata = checkMalloc(sizeof(DATABLOCK)); + lidata->data = checkMalloc(sizeof(PDATABLOCK) * (1024 / sizeof(DATABLOCK) + 1)); + lidata->blocks = 0; + lidata->dataofs = j; + for (i = 0; j < reclength; i++) + { + ((PPDATABLOCK)lidata->data)[i] = BuildLiData(&j); + } + lidata->blocks = i; + lidata->count = 1; + + k = prevofs; + EmitLiData(lidata, prevseg, &k); + li_le = (rectype == LIDATA) ? PREV_LI : PREV_LI32; + break; + case LPUBDEF: + case LPUBDEF32: + case PUBDEF: + case PUBDEF32: + j = 0; + grpnum = GetIndex(buf, &j) - 1; + if (grpnum >= 0) + { + grpnum += grpmin; + } + segnum = GetIndex(buf, &j) - 1; + if (segnum < 0) + { + j += 2; + } + else + { + segnum += segmin; + } + for (; j < reclength;) + { + pubdef = (PPUBLIC)checkMalloc(sizeof(PUBLIC)); + pubdef->aliasName = NULL; + pubdef->grpnum = grpnum; + pubdef->segnum = segnum; + name = checkMalloc(buf[j] + 1); + k = buf[j]; + j++; + for (i = 0; i < k; i++) + { + name[i] = buf[j]; + j++; + } + name[i] = 0; + if (!case_sensitive) + { + strupr(name); + } + pubdef->ofs = buf[j] + 256 * buf[j + 1]; + j += 2; + if ((rectype == PUBDEF32) || (rectype == LPUBDEF32)) + { + pubdef->ofs += (buf[j] + 256 * buf[j + 1]) << 16; + j += 2; + } + pubdef->typenum = GetIndex(buf, &j); + if (rectype == LPUBDEF || rectype == LPUBDEF32) + { + pubdef->modnum = nummods; + } + else + { + pubdef->modnum = 0; + } + if ((listnode = binarySearch(publics, pubcount, name)) != NULL) + { + for (i = 0; i < listnode->count; i++) + { + if (((PPUBLIC)listnode->object[i])->modnum == pubdef->modnum) + { + if (!((PPUBLIC)listnode->object[i])->aliasName) + { + printf("Duplicate public symbol %s\n", name); + exit(1); + } + free(((PPUBLIC)listnode->object[i])->aliasName); + (*((PPUBLIC)listnode->object[i])) = (*pubdef); + pubdef = NULL; + break; + } + } + } + if (pubdef) + { + sortedInsert(&publics, &pubcount, name, pubdef); + } + free(name); + } + break; + case LEXTDEF: + case LEXTDEF32: + case EXTDEF: + for (j = 0; j < reclength;) + { + externs = (PEXTREC)checkRealloc(externs, (extcount + 1) * sizeof(EXTREC)); + externs[extcount].name = checkMalloc(buf[j] + 1); + k = buf[j]; + j++; + for (i = 0; i < k; i++, j++) + { + externs[extcount].name[i] = buf[j]; + } + externs[extcount].name[i] = 0; + if (!case_sensitive) + { + strupr(externs[extcount].name); + } + externs[extcount].typenum = GetIndex(buf, &j); + externs[extcount].pubdef = NULL; + externs[extcount].flags = EXT_NOMATCH; + if ((rectype == LEXTDEF) || (rectype == LEXTDEF32)) + { + externs[extcount].modnum = nummods; + } + else + { + externs[extcount].modnum = 0; + } + extcount++; + } + break; + case GRPDEF: + grplist = checkRealloc(grplist, (grpcount + 1) * sizeof(PGRP)); + grplist[grpcount] = checkMalloc(sizeof(GRP)); + j = 0; + grplist[grpcount]->nameindex = GetIndex(buf, &j) - 1 + namemin; + if (grplist[grpcount]->nameindex < namemin) + { + ReportError(ERR_BAD_GRPDEF); + } + grplist[grpcount]->numsegs = 0; + while (j < reclength) + { + if (buf[j] == 0xff) + { + j++; + i = GetIndex(buf, &j) - 1 + segmin; + if (i < segmin) + { + ReportError(ERR_BAD_GRPDEF); + } + grplist[grpcount]->segindex[grplist[grpcount]->numsegs] = i; + grplist[grpcount]->numsegs++; + } + else + { + ReportError(ERR_BAD_GRPDEF); + } + } + grpcount++; + break; + case FIXUPP: + case FIXUPP32: + j = 0; + while (j < reclength) + { + if (buf[j] & 0x80) + { + /* FIXUP subrecord */ + if (!li_le) + { + ReportError(ERR_BAD_FIXUP); + } + r = checkMalloc(sizeof(RELOC)); + r->rtype = (buf[j] >> 2); + r->ofs = buf[j] * 256 + buf[j + 1]; + j += 2; + r->ofs &= 0x3ff; + r->rtype ^= FIX_SELFREL; + r->rtype &= FIX_MASK; + switch (r->rtype) + { + case FIX_LBYTE: + case FIX_OFS16: + case FIX_BASE: + case FIX_PTR1616: + case FIX_HBYTE: + case FIX_OFS16_2: + case FIX_OFS32: + case FIX_PTR1632: + case FIX_OFS32_2: + case FIX_SELF_LBYTE: + case FIX_SELF_OFS16: + case FIX_SELF_OFS16_2: + case FIX_SELF_OFS32: + case FIX_SELF_OFS32_2: + break; + default: + ReportError(ERR_BAD_FIXUP); + } + LoadFIXUP(r, buf, &j); + + if (li_le == PREV_LE) + { + r->ofs += prevofs; + r->segnum = prevseg; + relocs = (PPRELOC)checkRealloc(relocs, (fixcount + 1) * sizeof(PRELOC)); + relocs[fixcount] = r; + fixcount++; + } + else + { + r->segnum = prevseg; + i = prevofs; + RelocLIDATA(lidata, &i, r); + free(r); + } + } + else + { + /* THRED subrecord */ + i = buf[j]; /* get thred number */ + j++; + if (i & 0x40) /* Frame? */ + { + f_thred[i & 3] = i; + /* get index if required */ + if ((i & 0x1c) < 0xc) + { + f_thredindex[i & 3] = GetIndex(buf, &j); + } + i &= 3; + } + else + { + t_thred[i & 3] = i; + /* target always has index */ + t_thredindex[i & 3] = GetIndex(buf, &j); + } + } + } + break; + case BAKPAT: + case BAKPAT32: + j = 0; + i = 0; + if (j < reclength) + i = GetIndex(buf, &j); + i += segmin - 1; + if (j < reclength) + { + k = buf[j]; + j++; + } + while (j < reclength) + { + relocs = (PPRELOC)checkRealloc(relocs, (fixcount + 1) * sizeof(PRELOC)); + relocs[fixcount] = checkMalloc(sizeof(RELOC)); + switch (k) + { + case 0: + relocs[fixcount]->rtype = FIX_SELF_LBYTE; + break; + case 1: + relocs[fixcount]->rtype = FIX_SELF_OFS16; + break; + case 2: + relocs[fixcount]->rtype = FIX_SELF_OFS32; + break; + default: + printf("Bad BAKPAT record\n"); + exit(1); + } + relocs[fixcount]->ofs = buf[j] + 256 * buf[j + 1]; + j += 2; + if (rectype == BAKPAT32) + { + relocs[fixcount]->ofs += (buf[j] + 256 * buf[j + 1]) << 16; + j += 2; + } + relocs[fixcount]->segnum = i; + relocs[fixcount]->target = i; + relocs[fixcount]->frame = i; + relocs[fixcount]->ttype = REL_SEGDISP; + relocs[fixcount]->ftype = REL_SEGFRAME; + relocs[fixcount]->disp = buf[j] + 256 * buf[j + 1]; + j += 2; + if (rectype == BAKPAT32) + { + relocs[fixcount]->disp += (buf[j] + 256 * buf[j + 1]) << 16; + j += 2; + } + relocs[fixcount]->disp += relocs[fixcount]->ofs; + switch (k) + { + case 0: + relocs[fixcount]->disp++; + break; + case 1: + relocs[fixcount]->disp += 2; + break; + case 2: + relocs[fixcount]->disp += 4; + break; + default: + printf("Bad BAKPAT record\n"); + exit(1); + } + fixcount++; + } + break; + case LINNUM: + case LINNUM32: + case LINSYM: + case LINSYM32: + printf("LINNUM record\n"); + break; + case MODEND: + case MODEND32: + done = 1; + if (buf[0] & 0x40) + { + if (gotstart) + { + ReportError(ERR_MULTIPLE_STARTS); + } + gotstart = 1; + j = 1; + LoadFIXUP(&startaddr, buf, &j); + if (startaddr.ftype == REL_LILEFRAME) + { + ReportError(ERR_BAD_FIXUP); + } + } + break; + case COMDEF: + LoadComdefRecord(FALSE); + break; + case LCOMDEF: + LoadComdefRecord(TRUE); + break; + case CEXTDEF: + for (j = 0; j < reclength;) + { + long nameindex; + + externs = (PEXTREC)checkRealloc(externs, (extcount + 1) * sizeof(EXTREC)); + nameindex = GetIndex(buf, &j) - 1; + if (nameindex < 0) + { + ReportError(ERR_INV_DATA); + } + nameindex += namemin; + if ((UINT)nameindex >= namecount) + { + ReportError(ERR_INV_DATA); + } + externs[extcount].name = checkStrdup(namelist[nameindex]); + externs[extcount].typenum = GetIndex(buf, &j); + externs[extcount].pubdef = NULL; + externs[extcount].flags = EXT_NOMATCH; + externs[extcount].modnum = 0; + extcount++; + } + break; + case NBKPAT: + case NBKPAT32: + j = 0; + if (j < reclength) + { + k = buf[j]; + j++; + } + else + { + printf("Bad NBKPAT record\n"); + exit(1); + } + if (j < reclength) + { + i = GetIndex(buf, &j); + } + else + { + printf("Bad NBKPAT record\n"); + exit(1); + } + i += extmin - 1; + while (j < reclength) + { + UINT width; + + relocs = (PPRELOC)checkRealloc(relocs, (fixcount + 1) * sizeof(PRELOC)); + relocs[fixcount] = checkMalloc(sizeof(RELOC)); + switch (k) + { + case 0: + relocs[fixcount]->rtype = FIX_SELF_LBYTE; + width = 1; + break; + case 1: + relocs[fixcount]->rtype = FIX_SELF_OFS16; + width = 2; + break; + case 2: + case 9: + relocs[fixcount]->rtype = FIX_SELF_OFS32; + width = 4; + break; + default: + printf("Bad NBKPAT record\n"); + exit(1); + } + relocs[fixcount]->ofs = buf[j] + 256 * buf[j + 1]; + j += 2; + if (rectype == NBKPAT32) + { + relocs[fixcount]->ofs += (buf[j] + 256 * buf[j + 1]) << 16; + j += 2; + } + relocs[fixcount]->segnum = prevseg; + relocs[fixcount]->target = i; + relocs[fixcount]->frame = i; + relocs[fixcount]->ttype = REL_EXTDISP; + relocs[fixcount]->ftype = REL_EXTFRAME; + relocs[fixcount]->disp = buf[j] + 256 * buf[j + 1]; + j += 2; + if (rectype == NBKPAT32) + { + relocs[fixcount]->disp += (buf[j] + 256 * buf[j + 1]) << 16; + j += 2; + } + relocs[fixcount]->disp += relocs[fixcount]->ofs + width; + fixcount++; + } + break; + case COMDAT: + case COMDAT32: + { + unsigned char flags; + unsigned char attr; + unsigned char align; + UINT offset; + UINT modnum; + long grpindex; + long assocseg; + long nameindex; + PCOMDAT comdat; + + j = 0; + flags = buf[j++]; + attr = buf[j++]; + align = buf[j++]; + offset = buf[j] + 256 * buf[j + 1]; + j += 2; + if (rectype == COMDAT32) + { + offset += (buf[j] + 256 * buf[j + 1]) << 16; + j += 2; + } + (void)GetIndex(buf, &j); + grpindex = -1; + assocseg = -1; + if ((attr & COMDAT_ALLOC_MASK) == COMDAT_EXPLICIT) + { + grpindex = GetIndex(buf, &j) - 1; + assocseg = GetIndex(buf, &j) - 1; + if (grpindex >= 0) + { + grpindex += grpmin; + } + if (assocseg >= 0) + { + assocseg += segmin; + } + else if (grpindex < 0) + { + j += 2; + } + } + nameindex = GetIndex(buf, &j) - 1; + if (nameindex < 0) + { + ReportError(ERR_INV_DATA); + } + nameindex += namemin; + if ((UINT)nameindex >= namecount) + { + ReportError(ERR_INV_DATA); + } + + modnum = (flags & COMDAT_LOCAL) ? nummods : 0; + if (flags & COMDAT_CONTINUE) + { + comdat = FindLatestComdat(namelist[nameindex], modnum); + if (!comdat) + { + printf("COMDAT continuation without start for %s\n", namelist[nameindex]); + exit(1); + } + } + else + { + unsigned short segattr; + + seglist = (PPSEG)checkRealloc(seglist, (segcount + 1) * sizeof(PSEG)); + seglist[segcount] = (PSEG)checkMalloc(sizeof(SEG)); + memset(seglist[segcount], 0, sizeof(SEG)); + segattr = SEG_PRIVATE | GetComdatAlign(align, assocseg); + if ((attr & COMDAT_ALLOC_MASK) == COMDAT_CODE32 || + (attr & COMDAT_ALLOC_MASK) == COMDAT_DATA32) + { + segattr |= SEG_USE32; + } + seglist[segcount]->attr = segattr; + seglist[segcount]->nameindex = nameindex; + seglist[segcount]->classindex = + (assocseg >= 0) ? seglist[assocseg]->classindex : -1; + seglist[segcount]->overlayindex = -1; + seglist[segcount]->orderindex = -1; + if ((attr & COMDAT_ALLOC_MASK) == COMDAT_EXPLICIT && assocseg >= 0) + { + seglist[segcount]->winFlags = seglist[assocseg]->winFlags; + } + else if ((attr & COMDAT_ALLOC_MASK) == COMDAT_FAR_CODE || + (attr & COMDAT_ALLOC_MASK) == COMDAT_CODE32) + { + seglist[segcount]->winFlags = + WINF_CODE | WINF_INITDATA | WINF_EXECUTE | WINF_READABLE | WINF_NEG_FLAGS; + } + else + { + seglist[segcount]->winFlags = + WINF_INITDATA | WINF_READABLE | WINF_WRITEABLE | WINF_NEG_FLAGS; + } + seglist[segcount]->winFlags |= WINF_COMDAT; + if (grpindex >= 0) + { + grplist[grpindex]->segindex[grplist[grpindex]->numsegs] = segcount; + grplist[grpindex]->numsegs++; + } + + pubdef = (PPUBLIC)checkMalloc(sizeof(PUBLIC)); + pubdef->segnum = segcount; + pubdef->grpnum = grpindex; + pubdef->typenum = -1; + pubdef->ofs = 0; + pubdef->modnum = modnum; + pubdef->aliasName = NULL; + sortedInsert(&publics, &pubcount, namelist[nameindex], pubdef); + + comdat = (PCOMDAT)checkMalloc(sizeof(COMDATREC)); + comdat->segnum = segcount; + comdat->combineType = attr & COMDAT_MATCH_MASK; + comdat->linkwith = 0; + comdat->modnum = modnum; + comdat->flags = flags; + comdat->pubdef = pubdef; + sortedInsert(&comdats, &comdatcount, namelist[nameindex], comdat); + segcount++; + } + + currentComdat = comdat; + prevseg = comdat->segnum; + prevofs = offset; + if (flags & COMDAT_ITERATED) + { + UINT span; + + if (lidata) + { + DestroyLIDATA(lidata); + } + lidata = checkMalloc(sizeof(DATABLOCK)); + lidata->data = checkMalloc(sizeof(PDATABLOCK) * (1024 / sizeof(DATABLOCK) + 1)); + lidata->blocks = 0; + lidata->dataofs = j; + for (i = 0; j < reclength; i++) + { + ((PPDATABLOCK)lidata->data)[i] = BuildLiData(&j); + } + lidata->blocks = i; + lidata->count = 1; + span = LiDataSize(lidata); + EnsureSegmentSize(comdat->segnum, offset + span); + k = offset; + EmitLiData(lidata, comdat->segnum, &k); + li_le = (rectype == COMDAT32) ? PREV_LI32 : PREV_LI; + } + else + { + EnsureSegmentSize(comdat->segnum, offset + reclength - j); + for (k = 0; j < reclength; j++, k++) + { + if (GetNbit(seglist[comdat->segnum]->datmask, offset + k)) + { + if (seglist[comdat->segnum]->data[offset + k] != buf[j]) + { + ReportError(ERR_OVERWRITE); + } + } + seglist[comdat->segnum]->data[offset + k] = buf[j]; + SetNbit(seglist[comdat->segnum]->datmask, offset + k); + } + li_le = PREV_LE; + } + } + break; + case ALIAS: + printf("ALIAS record\n"); + j = 0; + name = checkMalloc(buf[j] + 1); + k = buf[j]; + j++; + for (i = 0; i < k; i++) + { + name[i] = buf[j]; + j++; + } + name[i] = 0; + if (!case_sensitive) + { + strupr(name); + } + printf("ALIAS name:%s\n", name); + aliasName = checkMalloc(buf[j] + 1); + k = buf[j]; + j++; + for (i = 0; i < k; i++) + { + aliasName[i] = buf[j]; + j++; + } + aliasName[i] = 0; + if (!case_sensitive) + { + strupr(aliasName); + } + printf("Substitute name:%s\n", aliasName); + if (!strlen(name)) + { + printf("Cannot use alias a blank name\n"); + exit(1); + } + if (!strlen(aliasName)) + { + printf("No Alias name specified for %s\n", name); + exit(1); + } + pubdef = (PPUBLIC)checkMalloc(sizeof(PUBLIC)); + pubdef->segnum = -1; + pubdef->grpnum = -1; + pubdef->typenum = -1; + pubdef->ofs = 0; + pubdef->modnum = 0; + pubdef->aliasName = aliasName; + if ((listnode = binarySearch(publics, pubcount, name)) != NULL) + { + for (i = 0; i < listnode->count; i++) + { + if (((PPUBLIC)listnode->object[i])->modnum == pubdef->modnum) + { + if (((PPUBLIC)listnode->object[i])->aliasName) + { + printf("Warning, two aliases for %s, using %s\n", name, + ((PPUBLIC)listnode->object[i])->aliasName); + } + free(pubdef->aliasName); + free(pubdef); + pubdef = NULL; + break; + } + } + } + if (pubdef) + { + sortedInsert(&publics, &pubcount, name, pubdef); + } + free(name); + break; + default: + ReportError(ERR_UNKNOWN_RECTYPE); + } + filepos += 4 + reclength; + modpos += 4 + reclength; + } + if (lidata) + { + DestroyLIDATA(lidata); + } + return 0; +} + +void loadlib(FILE *libfile, PCHAR libname) +{ + unsigned int i, j, k, n; + PCHAR name; + unsigned short modpage; + PLIBFILE p; + UINT numsyms; + PSORTENTRY symlist; + + libfiles = checkRealloc(libfiles, (libcount + 1) * sizeof(LIBFILE)); + p = &libfiles[libcount]; + + p->filename = checkMalloc(strlen(libname) + 1); + strcpy(p->filename, libname); + + if (fread(buf, 1, 3, libfile) != 3) + { + printf("Error reading from file\n"); + exit(1); + } + p->blocksize = buf[1] + 256 * buf[2]; + if (fread(buf, 1, p->blocksize, libfile) != p->blocksize) + { + printf("Error reading from file\n"); + exit(1); + } + p->blocksize += 3; + p->dicstart = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); + p->numdicpages = buf[4] + 256 * buf[5]; + p->flags = buf[6]; + p->libtype = 'O'; + + fseek(libfile, p->dicstart, SEEK_SET); + + symlist = (PSORTENTRY)checkMalloc(p->numdicpages * 37 * sizeof(SORTENTRY)); + + numsyms = 0; + for (i = 0; i < p->numdicpages; i++) + { + if (fread(buf, 1, 512, libfile) != 512) + { + printf("Error reading from file\n"); + exit(1); + } + for (j = 0; j < 37; j++) + { + k = buf[j] * 2; + if (k) + { + name = checkMalloc(buf[k] + 1); + for (n = 0; n < buf[k]; n++) + { + name[n] = buf[n + k + 1]; + } + name[buf[k]] = 0; + k += buf[k] + 1; + modpage = buf[k] + 256 * buf[k + 1]; + if (!(p->flags & LIBF_CASESENSITIVE) || !case_sensitive) + { + strupr(name); + } + if (name[strlen(name) - 1] == '!') + { + free(name); + } + else + { + symlist[numsyms].id = name; + symlist[numsyms].count = modpage; + ++numsyms; + } + } + } + } + + qsort(symlist, numsyms, sizeof(SORTENTRY), sortCompare); + p->symbols = symlist; + p->numsyms = numsyms; + p->modsloaded = 0; + p->modlist = checkMalloc(sizeof(unsigned short) * numsyms); + libcount++; +} + +void loadlibmod(UINT libnum, UINT modpage) +{ + PLIBFILE p; + FILE *libfile; + UINT i; + + p = &libfiles[libnum]; + + /* don't open a module we've loaded already */ + for (i = 0; i < p->modsloaded; i++) + { + if (p->modlist[i] == modpage) + return; + } + + libfile = fopen(p->filename, "rb"); + if (!libfile) + { + printf("Error opening file %s\n", p->filename); + exit(1); + } + fseek(libfile, modpage * p->blocksize, SEEK_SET); + switch (p->libtype) + { + case 'O': + loadmod(libfile); + break; + case 'C': + loadcofflibmod(p, libfile); + break; + default: + printf("Unknown library file format\n"); + exit(1); + } + + p->modlist[p->modsloaded] = modpage; + p->modsloaded++; + fclose(libfile); +} + +void loadres(FILE *f) +{ + unsigned char buf[32]; + static unsigned char buf2[32] = {0, 0, 0, 0, 0x20, 0, 0, 0, 0xff, 0xff, 0, 0, 0xff, 0xff, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + UINT i, j; + UINT hdrsize, datsize; + PUCHAR data; + PUCHAR hdr; + + if (fread(buf, 1, 32, f) != 32) + { + printf("Invalid resource file\n"); + exit(1); + } + if (memcmp(buf, buf2, 32)) + { + printf("Invalid resource file\n"); + exit(1); + } + printf("Loading Win32 Resource File\n"); + while (!feof(f)) + { + i = ftell(f); + if (i & 3) + { + fseek(f, 4 - (i & 3), SEEK_CUR); + } + i = fread(buf, 1, 8, f); + if (i == 0 && feof(f)) + return; + if (i != 8) + { + printf("Invalid resource file, no header\n"); + exit(1); + } + datsize = buf[0] + (buf[1] << 8) + (buf[2] << 16) + (buf[3] << 24); + hdrsize = buf[4] + (buf[5] << 8) + (buf[6] << 16) + (buf[7] << 24); + if (hdrsize < 16) + { + printf("Invalid resource file, bad header\n"); + exit(1); + } + hdr = (PUCHAR)checkMalloc(hdrsize); + if (fread(hdr, 1, hdrsize - 8, f) != (hdrsize - 8)) + { + printf("Invalid resource file, missing header\n"); + exit(1); + } + /* if this is a NULL resource, then skip */ + if (!datsize && (hdrsize == 32) && !memcmp(buf2 + 8, hdr, 24)) + { + free(hdr); + continue; + } + if (datsize) + { + data = (PUCHAR)checkMalloc(datsize); + if (fread(data, 1, datsize, f) != datsize) + { + printf("Invalid resource file, no data\n"); + exit(1); + } + } + else + data = NULL; + resource = (PRESOURCE)checkRealloc(resource, (rescount + 1) * sizeof(RESOURCE)); + resource[rescount].data = data; + resource[rescount].length = datsize; + i = 0; + hdrsize -= 8; + if ((hdr[i] == 0xff) && (hdr[i + 1] == 0xff)) + { + resource[rescount].typename = NULL; + resource[rescount].typeid = hdr[i + 2] + 256 * hdr[i + 3]; + i += 4; + } + else + { + for (j = i; (j < (hdrsize - 1)) && (hdr[j] | hdr[j + 1]); j += 2) + ; + if (hdr[j] | hdr[j + 1]) + { + printf("Invalid resource file, bad name\n"); + exit(1); + } + resource[rescount].typename = (PUCHAR)checkMalloc(j - i + 2); + memcpy(resource[rescount].typename, hdr + i, j - i + 2); + i = j + 5; + i &= 0xfffffffc; + } + if (i > hdrsize) + { + printf("Invalid resource file, overflow\n"); + exit(1); + } + if ((hdr[i] == 0xff) && (hdr[i + 1] == 0xff)) + { + resource[rescount].name = NULL; + resource[rescount].id = hdr[i + 2] + 256 * hdr[i + 3]; + i += 4; + } + else + { + for (j = i; (j < (hdrsize - 1)) && (hdr[j] | hdr[j + 1]); j += 2) + ; + if (hdr[j] | hdr[j + 1]) + { + printf("Invalid resource file,bad name (2)\n"); + exit(1); + } + resource[rescount].name = (PUCHAR)checkMalloc(j - i + 2); + memcpy(resource[rescount].name, hdr + i, j - i + 2); + i = j + 5; + i &= 0xfffffffc; + } + i += 6; /* point to Language ID */ + if (i > hdrsize) + { + printf("Invalid resource file, overflow(2)\n"); + exit(1); + } + resource[rescount].languageid = hdr[i] + 256 * hdr[i + 1]; + rescount++; + free(hdr); + } +} diff --git a/alink/OUTPUT.C b/alink/OUTPUT.C new file mode 100644 index 0000000..b2f8aeb --- /dev/null +++ b/alink/OUTPUT.C @@ -0,0 +1,2945 @@ +#include "ALINK.H" + +static unsigned char defaultStub[] = { + 0x4D, 0x5A, 0x6C, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x11, 0x00, 0xFF, 0xFF, 0x03, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0E, 0x1F, 0xBA, 0x0E, 0x00, 0xB4, 0x09, 0xCD, 0x21, 0xB8, 0x00, 0x4C, 0xCD, 0x21, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, + 0x72, 0x65, 0x73, 0x20, 0x57, 0x69, 0x6E, 0x33, 0x32, 0x0D, 0x0A, 0x24}; + +static UINT defaultStubSize = sizeof(defaultStub); + +static long EnsureAbsolutePublicSegment(UINT value) +{ + UINT i; + + for (i = 0; i < segcount; i++) + { + if (!seglist[i]) + continue; + if ((seglist[i]->attr & SEG_ALIGN) != SEG_ABS) + continue; + if (seglist[i]->base != value) + continue; + return i; + } + + seglist = (PPSEG)checkRealloc(seglist, (segcount + 1) * sizeof(PSEG)); + seglist[segcount] = (PSEG)checkMalloc(sizeof(SEG)); + memset(seglist[segcount], 0, sizeof(SEG)); + seglist[segcount]->nameindex = -1; + seglist[segcount]->classindex = -1; + seglist[segcount]->overlayindex = -1; + seglist[segcount]->orderindex = -1; + seglist[segcount]->attr = SEG_PRIVATE | SEG_ABS; + seglist[segcount]->absframe = value >> 4; + seglist[segcount]->absofs = value & 0xf; + seglist[segcount]->base = value; + return segcount++; +} + +static PCHAR GetCOMFallbackEXEName(PCHAR outname) +{ + int i; + PCHAR exename; + + exename = checkMalloc(strlen(outname) + 5); + strcpy(exename, outname); + i = strlen(exename) - 1; + while ((i >= 0) && (exename[i] != '.') && !IS_PATH_CHAR(exename[i]) && (exename[i] != ':')) + { + i--; + } + if ((i >= 0) && (exename[i] == '.') && !stricmp(exename + i, ".com")) + { + strcpy(exename + i, ".exe"); + } + else if ((i < 0) || (exename[i] != '.')) + { + strcat(exename, ".exe"); + } + return exename; +} + +void GetFixupTarget(PRELOC r, long *bseg, UINT *tofs, int isFlat) +{ + long baseseg = -1; + long targseg = -1; + UINT targofs = 0; + + r->outputPos = seglist[r->segnum]->base + r->ofs; + switch (r->ftype) + { + case REL_SEGFRAME: + case REL_LILEFRAME: + baseseg = r->frame; + break; + case REL_GRPFRAME: + baseseg = grplist[r->frame]->segnum; + break; + case REL_EXTFRAME: + switch (externs[r->frame].flags) + { + case EXT_MATCHEDPUBLIC: + if (!externs[r->frame].pubdef) + { + printf("Reloc:Unmatched extern %s\n", externs[r->frame].name); + errcount++; + break; + } + + if (externs[r->frame].pubdef->segnum < 0) + { + baseseg = EnsureAbsolutePublicSegment(externs[r->frame].pubdef->ofs); + } + else + { + baseseg = externs[r->frame].pubdef->segnum; + } + break; + case EXT_MATCHEDIMPORT: + baseseg = impdefs[externs[r->frame].impnum].segnum; + break; + default: + printf("Reloc:Unmatched external referenced in frame\n"); + errcount++; + break; + } + break; + default: + printf("Reloc:Unsupported FRAME type %i\n", r->ftype); + errcount++; + } + if (baseseg < 0) + { + printf("Undefined base seg\n"); + exit(1); + } /* this is a fix for TASM FLAT model, where FLAT group has no segments */ + + switch (r->ttype) + { + case REL_EXTDISP: + switch (externs[r->target].flags) + { + case EXT_MATCHEDPUBLIC: + if (!externs[r->target].pubdef) + { + printf("Reloc:Unmatched extern %s\n", externs[r->frame].name); + errcount++; + break; + } + + if (externs[r->target].pubdef->segnum < 0) + { + targseg = EnsureAbsolutePublicSegment(externs[r->target].pubdef->ofs); + targofs = 0; + } + else + { + targseg = externs[r->target].pubdef->segnum; + targofs = externs[r->target].pubdef->ofs; + } + break; + case EXT_MATCHEDIMPORT: + targseg = impdefs[externs[r->target].impnum].segnum; + targofs = impdefs[externs[r->target].impnum].ofs; + break; + default: + printf("Reloc:Unmatched external referenced in frame\n"); + errcount++; + break; + } + targofs += r->disp; + break; + case REL_EXTONLY: + switch (externs[r->target].flags) + { + case EXT_MATCHEDPUBLIC: + if (!externs[r->target].pubdef) + { + printf("Reloc:Unmatched extern %s\n", externs[r->target].name); + errcount++; + break; + } + + if (externs[r->target].pubdef->segnum < 0) + { + targseg = EnsureAbsolutePublicSegment(externs[r->target].pubdef->ofs); + targofs = 0; + } + else + { + targseg = externs[r->target].pubdef->segnum; + targofs = externs[r->target].pubdef->ofs; + } + break; + case EXT_MATCHEDIMPORT: + targseg = impdefs[externs[r->target].impnum].segnum; + targofs = impdefs[externs[r->target].impnum].ofs; + break; + default: + printf("Reloc:Unmatched external referenced in frame\n"); + errcount++; + break; + } + break; + case REL_SEGONLY: + targseg = r->target; + targofs = 0; + break; + case REL_SEGDISP: + targseg = r->target; + targofs = r->disp; + break; + case REL_GRPONLY: + targseg = grplist[r->target]->segnum; + targofs = 0; + break; + case REL_GRPDISP: + targseg = grplist[r->target]->segnum; + targofs = r->disp; + break; + default: + printf("Reloc:Unsupported TARGET type %i\n", r->ttype); + errcount++; + } + if (targseg < 0) + { + printf("undefined seg\n"); + exit(1); + } + if ((!errcount) && (!seglist[targseg])) + { + printf("Reloc: no target segment\n"); + + errcount++; + } + if ((!errcount) && (!seglist[baseseg])) + { + printf("reloc: no base segment\n"); + + errcount++; + } + + if (!errcount) + { + /* + if(((seglist[targseg]->attr&SEG_ALIGN)!=SEG_ABS) && + ((seglist[baseseg]->attr&SEG_ALIGN)!=SEG_ABS)) + { + if(seglist[baseseg]->base>seglist[targseg]->base) + { + printf("Reloc:Negative base address\n"); + errcount++; + } + targofs+=seglist[targseg]->base-seglist[baseseg]->base; + } + */ + if ((seglist[baseseg]->attr & SEG_ALIGN) == SEG_ABS) + { + printf("Warning: Reloc frame is absolute segment\n"); + targseg = baseseg; + } + else if ((seglist[targseg]->attr & SEG_ALIGN) == SEG_ABS) + { + printf("Warning: Reloc target is in absolute segment\n"); + targseg = baseseg; + } + if (!isFlat || ((seglist[baseseg]->attr & SEG_ALIGN) == SEG_ABS)) + { + if (seglist[baseseg]->base > (seglist[targseg]->base + targofs)) + { + printf("Error: target address out of frame\n"); + printf("Base=%08X,target=%08X\n", seglist[baseseg]->base, + seglist[targseg]->base + targofs); + errcount++; + } + targofs += seglist[targseg]->base - seglist[baseseg]->base; + *bseg = baseseg; + *tofs = targofs; + } + else + { + *bseg = -1; + *tofs = targofs + seglist[targseg]->base; + } + } + else + { + printf("relocation error occurred\n"); + *bseg = 0; + *tofs = 0; + } +} + +void OutputCOMfile(PCHAR outname) +{ + long i, j; + UINT started; + UINT lastout; + long targseg; + UINT targofs; + FILE *outfile; + unsigned short temps; + unsigned long templ; + + if (impsreq) + { + ReportError(ERR_ILLEGAL_IMPORTS); + } + + errcount = 0; + if (gotstart) + { + GetFixupTarget(&startaddr, &startaddr.segnum, &startaddr.ofs, FALSE); + if (errcount) + { + printf("Invalid start address record\n"); + exit(1); + } + + if ((startaddr.ofs + seglist[startaddr.segnum]->base) != 0x100) + { + printf("Warning, start address not 0100h as required for COM file\n"); + } + } + else + { + printf("Warning, no entry point specified\n"); + } + + for (i = 0; i < fixcount; i++) + { + GetFixupTarget(relocs[i], &targseg, &targofs, FALSE); + switch (relocs[i]->rtype) + { + case FIX_BASE: + case FIX_PTR1616: + case FIX_PTR1632: + if ((seglist[targseg]->attr & SEG_ALIGN) != SEG_ABS) + { + PCHAR exename; + + exename = GetCOMFallbackEXEName(outname); + printf("Warning, COM output needs runtime segment relocations; writing EXE file %s\n", + exename); + OutputEXEfile(exename); + free(exename); + return; + } + break; + default: + break; + } + } + + for (i = 0; i < fixcount; i++) + { + GetFixupTarget(relocs[i], &targseg, &targofs, FALSE); + switch (relocs[i]->rtype) + { + case FIX_BASE: + case FIX_PTR1616: + case FIX_PTR1632: + if ((seglist[targseg]->attr & SEG_ALIGN) != SEG_ABS) + { + printf("Reloc %li:Segment selector relocations are not supported in COM files\n", + i); + errcount++; + } + else + { + j = relocs[i]->ofs; + if (relocs[i]->rtype == FIX_PTR1616) + { + if (targofs > 0xffff) + { + printf("Relocs %li:Offset out of range\n", i); + errcount++; + } + temps = seglist[relocs[i]->segnum]->data[j]; + temps += seglist[relocs[i]->segnum]->data[j + 1] << 8; + temps += targofs; + temps += seglist[targseg]->base & 0xf; /* non-para seg */ + seglist[relocs[i]->segnum]->data[j] = temps & 0xff; + seglist[relocs[i]->segnum]->data[j + 1] = (temps >> 8) & 0xff; + j += 2; + } + else if (relocs[i]->rtype == FIX_PTR1632) + { + templ = seglist[relocs[i]->segnum]->data[j]; + templ += seglist[relocs[i]->segnum]->data[j + 1] << 8; + templ += seglist[relocs[i]->segnum]->data[j + 2] << 16; + templ += seglist[relocs[i]->segnum]->data[j + 3] << 24; + templ += targofs; + templ += seglist[targseg]->base & 0xf; /* non-para seg */ + seglist[relocs[i]->segnum]->data[j] = templ & 0xff; + seglist[relocs[i]->segnum]->data[j + 1] = (templ >> 8) & 0xff; + seglist[relocs[i]->segnum]->data[j + 2] = (templ >> 16) & 0xff; + seglist[relocs[i]->segnum]->data[j + 3] = (templ >> 24) & 0xff; + j += 4; + } + temps = seglist[relocs[i]->segnum]->data[j]; + temps += seglist[relocs[i]->segnum]->data[j + 1] << 8; + temps += seglist[targseg]->absframe; + seglist[relocs[i]->segnum]->data[j] = temps & 0xff; + seglist[relocs[i]->segnum]->data[j + 1] = (temps >> 8) & 0xff; + } + break; + case FIX_OFS32: + case FIX_OFS32_2: + templ = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] << 16; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] << 24; + templ += targofs; + templ += seglist[targseg]->base & 0xf; /* non-para seg */ + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = templ & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (templ >> 8) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] = (templ >> 16) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] = (templ >> 24) & 0xff; + break; + case FIX_OFS16: + case FIX_OFS16_2: + if (targofs > 0xffff) + { + printf("Relocs %li:Offset out of range\n", i); + errcount++; + } + temps = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + temps += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + temps += targofs; + temps += seglist[targseg]->base & 0xf; /* non-para seg */ + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = temps & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (temps >> 8) & 0xff; + break; + case FIX_LBYTE: + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] += targofs & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] += + seglist[targseg]->base & 0xf; /* non-para seg */ + break; + case FIX_HBYTE: + templ = targofs + (seglist[targseg]->base & 0xf); /* non-para seg */ + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] += (templ >> 8) & 0xff; + break; + case FIX_SELF_LBYTE: + if ((seglist[targseg]->attr & SEG_ALIGN) == SEG_ABS) + { + printf("Error: Absolute Reloc target not supported for self-relative fixups\n"); + errcount++; + } + else + { + j = seglist[targseg]->base + targofs; + j -= seglist[relocs[i]->segnum]->base + relocs[i]->ofs + 1; + if ((j < -128) || (j > 127)) + { + printf("Error: Reloc %li out of range\n", i); + } + else + { + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] += j; + } + } + break; + case FIX_SELF_OFS16: + case FIX_SELF_OFS16_2: + if ((seglist[targseg]->attr & SEG_ALIGN) == SEG_ABS) + { + printf("Error: Absolute Reloc target not supported for self-relative fixups\n"); + errcount++; + } + else + { + j = seglist[targseg]->base + targofs; + j -= seglist[relocs[i]->segnum]->base + relocs[i]->ofs + 2; + if ((j < -32768) || (j > 32767)) + { + printf("Error: Reloc %li out of range\n", i); + } + else + { + temps = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + temps += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + temps += j; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = temps & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (temps >> 8) & 0xff; + } + } + break; + case FIX_SELF_OFS32: + case FIX_SELF_OFS32_2: + if ((seglist[targseg]->attr & SEG_ALIGN) == SEG_ABS) + { + printf("Error: Absolute Reloc target not supported for self-relative fixups\n"); + errcount++; + } + else + { + j = seglist[targseg]->base + targofs; + j -= seglist[relocs[i]->segnum]->base + relocs[i]->ofs + 4; + templ = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] << 16; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] << 24; + templ += j; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = templ & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (templ >> 8) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] = (templ >> 16) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] = (templ >> 24) & 0xff; + } + break; + default: + printf("Reloc %li:Relocation type %i not supported\n", i, relocs[i]->rtype); + errcount++; + } + } + + if (errcount != 0) + { + exit(1); + } + outfile = fopen(outname, "wb"); + if (!outfile) + { + printf("Error writing to file %s\n", outname); + exit(1); + } + + started = lastout = 0; + + for (i = 0; i < outcount; i++) + { + if (outlist[i] && ((outlist[i]->attr & SEG_ALIGN) != SEG_ABS)) + { + if (started > outlist[i]->base) + { + printf("Segment overlap\n"); + fclose(outfile); + exit(1); + } + if (padsegments) + { + while (started < outlist[i]->base) + { + fputc(0, outfile); + started++; + } + } + else + { + started = outlist[i]->base; + } + for (j = 0; j < outlist[i]->length; j++) + { + if (started >= 0x100) + { + if (GetNbit(outlist[i]->datmask, j)) + { + for (; lastout < started; lastout++) + { + fputc(0, outfile); + } + fputc(outlist[i]->data[j], outfile); + lastout = started + 1; + } + else if (padsegments) + { + fputc(0, outfile); + lastout = started + 1; + } + } + else + { + lastout = started + 1; + if (GetNbit(outlist[i]->datmask, j)) + { + printf("Warning - data at offset %08X (%s:%08lX) discarded\n", started, + namelist[outlist[i]->nameindex], j); + } + } + started++; + } + } + } + + fclose(outfile); +} + +void OutputEXEfile(PCHAR outname) +{ + long i, j; + UINT started, lastout; + long targseg; + UINT targofs; + FILE *outfile; + PUCHAR headbuf; + long relcount; + int gotstack; + UINT totlength; + unsigned short temps; + unsigned long templ; + + if (impsreq) + { + ReportError(ERR_ILLEGAL_IMPORTS); + } + + errcount = 0; + gotstack = 0; + headbuf = checkMalloc(0x40 + 4 * fixcount); + relcount = 0; + + for (i = 0; i < 0x40; i++) + { + headbuf[i] = 0; + } + + headbuf[0x00] = 'M'; /* sig */ + headbuf[0x01] = 'Z'; + headbuf[0x0c] = maxalloc & 0xff; + headbuf[0x0d] = maxalloc >> 8; + headbuf[0x18] = 0x40; + + if (gotstart) + { + GetFixupTarget(&startaddr, &startaddr.segnum, &startaddr.ofs, FALSE); + if (errcount) + { + printf("Invalid start address record\n"); + exit(1); + } + + i = seglist[startaddr.segnum]->base; + startaddr.ofs += i & 0xf; + i >>= 4; + if ((startaddr.ofs > 65535) || (i > 65535) || + ((seglist[startaddr.segnum]->attr & SEG_ALIGN) == SEG_ABS)) + { + printf("Invalid start address\n"); + errcount++; + } + else + { + headbuf[0x14] = startaddr.ofs & 0xff; + headbuf[0x15] = startaddr.ofs >> 8; + + headbuf[0x16] = i & 0xff; + headbuf[0x17] = i >> 8; + } + } + else + { + printf("Warning, no entry point specified\n"); + } + + totlength = 0; + + for (i = 0; i < outcount; i++) + { + if ((outlist[i]->attr & SEG_ALIGN) != SEG_ABS) + { + totlength = outlist[i]->base + outlist[i]->length; + if ((outlist[i]->attr & SEG_COMBINE) == SEG_STACK) + { + if (gotstack) + { + printf("Internal error - stack segments not combined\n"); + exit(1); + } + gotstack = 1; + if ((outlist[i]->length > 65536) || (outlist[i]->length == 0)) + { + printf("SP value out of range\n"); + errcount++; + } + if ((outlist[i]->base > 0xfffff) || ((outlist[i]->base & 0xf) != 0)) + { + printf("SS value out of range\n"); + errcount++; + } + if (!errcount) + { + headbuf[0x0e] = (outlist[i]->base >> 4) & 0xff; + headbuf[0x0f] = outlist[i]->base >> 12; + headbuf[0x10] = outlist[i]->length & 0xff; + headbuf[0x11] = (outlist[i]->length >> 8) & 0xff; + } + } + } + } + + if (!gotstack) + { + printf("Warning - no stack\n"); + } + + for (i = 0; i < fixcount; i++) + { + GetFixupTarget(relocs[i], &targseg, &targofs, FALSE); + switch (relocs[i]->rtype) + { + case FIX_BASE: + case FIX_PTR1616: + case FIX_PTR1632: + j = relocs[i]->ofs; + if (relocs[i]->rtype == FIX_PTR1616) + { + if (targofs > 0xffff) + { + printf("Relocs %li:Offset out of range\n", i); + errcount++; + } + temps = seglist[relocs[i]->segnum]->data[j]; + temps += seglist[relocs[i]->segnum]->data[j + 1] << 8; + temps += targofs; + temps += seglist[targseg]->base & 0xf; /* non-para seg */ + seglist[relocs[i]->segnum]->data[j] = temps & 0xff; + seglist[relocs[i]->segnum]->data[j + 1] = (temps >> 8) & 0xff; + j += 2; + } + else if (relocs[i]->rtype == FIX_PTR1632) + { + templ = seglist[relocs[i]->segnum]->data[j]; + templ += seglist[relocs[i]->segnum]->data[j + 1] << 8; + templ += seglist[relocs[i]->segnum]->data[j + 2] << 16; + templ += seglist[relocs[i]->segnum]->data[j + 3] << 24; + templ += targofs; + templ += seglist[targseg]->base & 0xf; /* non-para seg */ + seglist[relocs[i]->segnum]->data[j] = templ & 0xff; + seglist[relocs[i]->segnum]->data[j + 1] = (templ >> 8) & 0xff; + seglist[relocs[i]->segnum]->data[j + 2] = (templ >> 16) & 0xff; + seglist[relocs[i]->segnum]->data[j + 3] = (templ >> 24) & 0xff; + j += 4; + } + if ((seglist[targseg]->attr & SEG_ALIGN) != SEG_ABS) + { + if (seglist[targseg]->base > 0xfffff) + { + printf("Relocs %li:Segment base out of range\n", i); + errcount++; + } + temps = seglist[relocs[i]->segnum]->data[j]; + temps += seglist[relocs[i]->segnum]->data[j + 1] << 8; + temps += (seglist[targseg]->base >> 4); + seglist[relocs[i]->segnum]->data[j] = temps & 0xff; + seglist[relocs[i]->segnum]->data[j + 1] = (temps >> 8) & 0xff; + templ = seglist[relocs[i]->segnum]->base >> 4; + headbuf[0x40 + relcount * 4 + 2] = templ & 0xff; + headbuf[0x40 + relcount * 4 + 3] = (templ >> 8) & 0xff; + templ = (seglist[relocs[i]->segnum]->base & 0xf) + j; + headbuf[0x40 + relcount * 4] = (templ) & 0xff; + headbuf[0x40 + relcount * 4 + 1] = (templ >> 8) & 0xff; + relcount++; + } + else + { + temps = seglist[relocs[i]->segnum]->data[j]; + temps += seglist[relocs[i]->segnum]->data[j + 1] << 8; + temps += seglist[targseg]->absframe; + seglist[relocs[i]->segnum]->data[j] = temps & 0xff; + seglist[relocs[i]->segnum]->data[j + 1] = (temps >> 8) & 0xff; + } + break; + case FIX_OFS32: + case FIX_OFS32_2: + templ = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] << 16; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] << 24; + templ += targofs; + templ += seglist[targseg]->base & 0xf; /* non-para seg */ + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = templ & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (templ >> 8) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] = (templ >> 16) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] = (templ >> 24) & 0xff; + break; + case FIX_OFS16: + case FIX_OFS16_2: + if (targofs > 0xffff) + { + printf("Relocs %li:Offset out of range\n", i); + errcount++; + } + temps = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + temps += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + temps += targofs; + temps += seglist[targseg]->base & 0xf; /* non-para seg */ + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = temps & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (temps >> 8) & 0xff; + break; + case FIX_LBYTE: + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] += targofs & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] += + seglist[targseg]->base & 0xf; /* non-para seg */ + break; + case FIX_HBYTE: + templ = targofs + (seglist[targseg]->base & 0xf); /* non-para seg */ + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] += (templ >> 8) & 0xff; + break; + case FIX_SELF_LBYTE: + if ((seglist[targseg]->attr & SEG_ALIGN) == SEG_ABS) + { + printf("Error: Absolute Reloc target not supported for self-relative fixups\n"); + errcount++; + } + else + { + j = seglist[targseg]->base + targofs; + j -= seglist[relocs[i]->segnum]->base + relocs[i]->ofs + 1; + if ((j < -128) || (j > 127)) + { + printf("Error: Reloc %li out of range\n", i); + } + else + { + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] += j; + } + } + break; + case FIX_SELF_OFS16: + case FIX_SELF_OFS16_2: + if ((seglist[targseg]->attr & SEG_ALIGN) == SEG_ABS) + { + printf("Error: Absolute Reloc target not supported for self-relative fixups\n"); + errcount++; + } + else + { + j = seglist[targseg]->base + targofs; + j -= seglist[relocs[i]->segnum]->base + relocs[i]->ofs + 2; + if ((j < -32768) || (j > 32767)) + { + printf("Error: Reloc %li out of range\n", i); + } + else + { + temps = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + temps += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + temps += j; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = temps & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (temps >> 8) & 0xff; + } + } + break; + case FIX_SELF_OFS32: + case FIX_SELF_OFS32_2: + if ((seglist[targseg]->attr & SEG_ALIGN) == SEG_ABS) + { + printf("Error: Absolute Reloc target not supported for self-relative fixups\n"); + errcount++; + } + else + { + j = seglist[targseg]->base + targofs; + j -= seglist[relocs[i]->segnum]->base + relocs[i]->ofs + 4; + templ = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] << 16; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] << 24; + templ += j; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = templ & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (templ >> 8) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] = (templ >> 16) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] = (templ >> 24) & 0xff; + } + break; + default: + printf("Reloc %li:Relocation type %i not supported\n", i, relocs[i]->rtype); + errcount++; + } + } + + if (relcount > 65535) + { + printf("Too many relocations\n"); + exit(1); + } + + headbuf[0x06] = relcount & 0xff; + headbuf[0x07] = relcount >> 8; + i = relcount * 4 + 0x4f; + i >>= 4; + totlength += i << 4; + headbuf[0x08] = i & 0xff; + headbuf[0x09] = i >> 8; + i = totlength % 512; + headbuf[0x02] = i & 0xff; + headbuf[0x03] = i >> 8; + i = (totlength + 0x1ff) >> 9; + if (i > 65535) + { + printf("File too large\n"); + exit(1); + } + headbuf[0x04] = i & 0xff; + headbuf[0x05] = i >> 8; + + if (errcount != 0) + { + exit(1); + } + + outfile = fopen(outname, "wb"); + if (!outfile) + { + printf("Error writing to file %s\n", outname); + exit(1); + } + + i = (headbuf[0x08] + 256 * headbuf[0x09]) * 16; + if (fwrite(headbuf, 1, i, outfile) != i) + { + printf("Error writing to file %s\n", outname); + exit(1); + } + + started = 0; + lastout = 0; + + for (i = 0; i < outcount; i++) + { + if (outlist[i] && ((outlist[i]->attr & SEG_ALIGN) != SEG_ABS)) + { + if (started > outlist[i]->base) + { + printf("Segment overlap\n"); + fclose(outfile); + exit(1); + } + if (padsegments) + { + while (started < outlist[i]->base) + { + fputc(0, outfile); + started++; + } + } + else + { + started = outlist[i]->base; + } + for (j = 0; j < outlist[i]->length; j++) + { + if (GetNbit(outlist[i]->datmask, j)) + { + for (; lastout < started; lastout++) + { + fputc(0, outfile); + } + fputc(outlist[i]->data[j], outfile); + lastout = started + 1; + } + else if (padsegments) + { + fputc(0, outfile); + lastout = started + 1; + } + started++; + } + } + } + + if (lastout != started) + { + fseek(outfile, 0, SEEK_SET); + lastout += (headbuf[8] + 256 * headbuf[9]) << 4; + i = lastout % 512; + headbuf[0x02] = i & 0xff; + headbuf[0x03] = i >> 8; + i = (lastout + 0x1ff) >> 9; + headbuf[0x04] = i & 0xff; + headbuf[0x05] = i >> 8; + i = ((totlength - lastout) + 0xf) >> 4; + if (i > 65535) + { + printf("Memory requirements too high\n"); + } + headbuf[0x0a] = i & 0xff; + headbuf[0x0b] = i >> 8; + if (fwrite(headbuf, 1, 12, outfile) != 12) + { + printf("Error writing to file\n"); + exit(1); + } + } + fclose(outfile); +} + +long createOutputSection(char *name, UINT winFlags) +{ + UINT j; + + outlist = (PPSEG)checkRealloc(outlist, sizeof(PSEG) * (outcount + 1)); + outlist[outcount] = (PSEG)checkMalloc(sizeof(SEG)); + seglist = (PPSEG)checkRealloc(seglist, sizeof(PSEG) * (segcount + 1)); + seglist = (PPSEG)checkRealloc(seglist, (segcount + 1) * sizeof(PSEG)); + seglist[segcount] = outlist[outcount]; + namelist = (PPCHAR)checkRealloc(namelist, (namecount + 1) * sizeof(PCHAR)); + namelist[namecount] = checkStrdup(name); + outlist[outcount]->nameindex = namecount; + outlist[outcount]->classindex = -1; + outlist[outcount]->overlayindex = -1; + namecount++; + j = outlist[outcount - 1]->base + outlist[outcount - 1]->length; + j += (objectAlign - 1); + j &= (0xffffffff - (objectAlign - 1)); + outlist[outcount]->base = j; + outlist[outcount]->length = 0; + outlist[outcount]->data = outlist[outcount]->datmask = NULL; + outlist[outcount]->absofs = segcount; + outlist[outcount]->attr = SEG_BYTE | SEG_PRIVATE; + outlist[outcount]->winFlags = winFlags ^ WINF_NEG_FLAGS; + segcount++; + outcount++; + return outcount - 1; +} + +void BuildPEImports(long impsectNum, PUCHAR objectTable) +{ + long i, j, k; + UINT *reqimps = NULL, reqcount = 0; + char **dllNames = NULL; + int *dllNumImps = NULL; + int *dllImpsDone = NULL; + int *dllImpNameSize = NULL; + UINT dllCount = 0, dllNameSize = 0, namePos; + SEG *impsect; + UINT thunkPos, thunk2Pos, impNamePos; + + if (impsectNum < 0) + return; + for (i = 0; i < extcount; i++) + { + if (externs[i].flags != EXT_MATCHEDIMPORT) + continue; + for (j = 0; j < reqcount; j++) + { + if (reqimps[j] == externs[i].impnum) + break; + } + if (j != reqcount) + continue; + reqimps = (UINT *)checkRealloc(reqimps, (reqcount + 1) * sizeof(UINT)); + reqimps[reqcount] = externs[i].impnum; + reqcount++; + for (j = 0; j < dllCount; j++) + { + if (!strcmp(impdefs[externs[i].impnum].mod_name, dllNames[j])) + break; + } + if (j == dllCount) + { + dllNames = (char **)checkRealloc(dllNames, (dllCount + 1) * sizeof(char *)); + dllNumImps = (int *)checkRealloc(dllNumImps, (dllCount + 1) * sizeof(int)); + dllImpsDone = (int *)checkRealloc(dllImpsDone, (dllCount + 1) * sizeof(int)); + dllImpNameSize = (int *)checkRealloc(dllImpNameSize, (dllCount + 1) * sizeof(int)); + dllNames[dllCount] = impdefs[externs[i].impnum].mod_name; + dllNumImps[dllCount] = 0; + dllImpsDone[dllCount] = 0; + dllImpNameSize[dllCount] = 0; + dllNameSize += strlen(dllNames[dllCount]) + 1; + if (dllNameSize & 1) + dllNameSize++; + dllCount++; + } + dllNumImps[j]++; + if (impdefs[externs[i].impnum].flags == 0) /* import by name? */ + { + dllImpNameSize[j] += strlen(impdefs[externs[i].impnum].imp_name) + 3; + /* the +3 ensure room for 2-byte hint and null terminator */ + if (dllImpNameSize[j] & 1) + dllImpNameSize[j]++; + } + } + + if (!reqcount || !dllCount) + return; + + objectTable += PE_OBJECTENTRY_SIZE * impsectNum; /* point to import object entry */ + k = objectTable[-PE_OBJECTENTRY_SIZE + 20]; /* get physical start of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 21] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 22] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 23] << 24; + + k += objectTable[-PE_OBJECTENTRY_SIZE + 16]; /* add physical length of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 17] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 18] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 19] << 24; + + k += fileAlign - 1; + k &= (0xffffffff - (fileAlign - 1)); /* aligned */ + + /* k is now physical location of this object */ + + objectTable[20] = (k) & 0xff; /* store physical file offset */ + objectTable[21] = (k >> 8) & 0xff; + objectTable[22] = (k >> 16) & 0xff; + objectTable[23] = (k >> 24) & 0xff; + + k = objectTable[-PE_OBJECTENTRY_SIZE + 12]; /* get virtual start of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 13] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 14] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 15] << 24; + + k += objectTable[-PE_OBJECTENTRY_SIZE + 8]; /* add virtual length of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 9] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 10] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 11] << 24; + + /* store base address (RVA) of section */ + objectTable[12] = (k) & 0xff; + objectTable[13] = (k >> 8) & 0xff; + objectTable[14] = (k >> 16) & 0xff; + objectTable[15] = (k >> 24) & 0xff; + + impsect = outlist[impsectNum]; + impsect->base = k + imageBase; /* get base address of section */ + + impsect->length = (dllCount + 1) * PE_IMPORTDIRENTRY_SIZE + dllNameSize; + if (impsect->length & 3) + impsect->length += 4 - (impsect->length & 3); /* align to 4-byte boundary */ + thunkPos = impsect->base + impsect->length; + for (j = 0, i = 0; j < dllCount; j++) + { + i += dllNumImps[j] + 1; /* add number of entries in DLL thunk table */ + } + /* now i= number of entries in Thunk tables for all DLLs */ + /* get address of name tables, which follow thunk tables */ + impNamePos = thunkPos + i * 2 * 4; /* 2 thunk tables per DLL, 4 bytes per entry */ + impsect->length += i * 2 * 4; /* update length of section too. */ + + for (j = 0, i = 0; j < dllCount; j++) + { + i += dllImpNameSize[j]; /* add size of import names and hints */ + } + + impsect->length += i; + + impsect->data = (PUCHAR)checkMalloc(impsect->length); + impsect->datmask = (PUCHAR)checkMalloc((impsect->length + 7) / 8); + for (i = 0; i < (impsect->length + 7) / 8; i++) + { + impsect->datmask[i] = 0xff; + } + + /* end of directory entries=name table */ + namePos = impsect->base + (dllCount + 1) * PE_IMPORTDIRENTRY_SIZE; + for (i = 0; i < dllCount; i++) + { + /* add directory entry */ + j = i * PE_IMPORTDIRENTRY_SIZE; + impsect->data[j + 0] = (thunkPos - imageBase) & 0xff; /* address of first thunk table */ + impsect->data[j + 1] = ((thunkPos - imageBase) >> 8) & 0xff; + impsect->data[j + 2] = ((thunkPos - imageBase) >> 16) & 0xff; + impsect->data[j + 3] = ((thunkPos - imageBase) >> 24) & 0xff; + impsect->data[j + 4] = 0; /* zero out time stamp */ + impsect->data[j + 5] = 0; + impsect->data[j + 6] = 0; + impsect->data[j + 7] = 0; + impsect->data[j + 8] = 0; /* zero out version number */ + impsect->data[j + 9] = 0; + impsect->data[j + 10] = 0; + impsect->data[j + 11] = 0; + impsect->data[j + 12] = (namePos - imageBase) & 0xff; /* address of DLL name */ + impsect->data[j + 13] = ((namePos - imageBase) >> 8) & 0xff; + impsect->data[j + 14] = ((namePos - imageBase) >> 16) & 0xff; + impsect->data[j + 15] = ((namePos - imageBase) >> 24) & 0xff; + thunk2Pos = thunkPos + (dllNumImps[i] + 1) * 4; /* address of second thunk table */ + impsect->data[j + 16] = (thunk2Pos - imageBase) & 0xff; /* store it */ + impsect->data[j + 17] = ((thunk2Pos - imageBase) >> 8) & 0xff; + impsect->data[j + 18] = ((thunk2Pos - imageBase) >> 16) & 0xff; + impsect->data[j + 19] = ((thunk2Pos - imageBase) >> 24) & 0xff; + /* add name to table */ + strcpy((char *)(impsect->data + namePos - impsect->base), dllNames[i]); + namePos += strlen(dllNames[i]) + 1; + if (namePos & 1) + { + impsect->data[namePos - impsect->base] = 0; + namePos++; + } + /* add imported names to table */ + for (k = 0; k < reqcount; k++) + { + if (strcmp(impdefs[reqimps[k]].mod_name, dllNames[i]) != 0) + continue; + if (impdefs[reqimps[k]].flags == 0) + { + /* store pointers to name entry in thunk tables */ + impsect->data[thunkPos - impsect->base] = (impNamePos - imageBase) & 0xff; + impsect->data[thunkPos - impsect->base + 1] = + ((impNamePos - imageBase) >> 8) & 0xff; + impsect->data[thunkPos - impsect->base + 2] = + ((impNamePos - imageBase) >> 16) & 0xff; + impsect->data[thunkPos - impsect->base + 3] = + ((impNamePos - imageBase) >> 24) & 0xff; + + impsect->data[thunk2Pos - impsect->base] = (impNamePos - imageBase) & 0xff; + impsect->data[thunk2Pos - impsect->base + 1] = + ((impNamePos - imageBase) >> 8) & 0xff; + impsect->data[thunk2Pos - impsect->base + 2] = + ((impNamePos - imageBase) >> 16) & 0xff; + impsect->data[thunk2Pos - impsect->base + 3] = + ((impNamePos - imageBase) >> 24) & 0xff; + + /* no hint */ + impsect->data[impNamePos - impsect->base] = 0; + impsect->data[impNamePos - impsect->base + 1] = 0; + impNamePos += 2; + /* store name */ + strcpy((char *)(impsect->data + impNamePos - impsect->base), + impdefs[reqimps[k]].imp_name); + impNamePos += strlen(impdefs[reqimps[k]].imp_name) + 1; + if (impNamePos & 1) + { + impsect->data[impNamePos - impsect->base] = 0; + impNamePos++; + } + } + else + { + /* store ordinal number in thunk tables */ + j = impdefs[reqimps[k]].ordinal + PE_ORDINAL_FLAG; + impsect->data[thunkPos - impsect->base] = (j) & 0xff; + impsect->data[thunkPos - impsect->base + 1] = (j >> 8) & 0xff; + impsect->data[thunkPos - impsect->base + 2] = (j >> 16) & 0xff; + impsect->data[thunkPos - impsect->base + 3] = (j >> 24) & 0xff; + + impsect->data[thunk2Pos - impsect->base] = (j) & 0xff; + impsect->data[thunk2Pos - impsect->base + 1] = (j >> 8) & 0xff; + impsect->data[thunk2Pos - impsect->base + 2] = (j >> 16) & 0xff; + impsect->data[thunk2Pos - impsect->base + 3] = (j >> 24) & 0xff; + } + impdefs[reqimps[k]].segnum = impsect->absofs; + impdefs[reqimps[k]].ofs = thunk2Pos - impsect->base; + thunkPos += 4; + thunk2Pos += 4; + } + /* zero out end of thunk tables */ + impsect->data[thunkPos - impsect->base] = 0; + impsect->data[thunkPos - impsect->base + 1] = 0; + impsect->data[thunkPos - impsect->base + 2] = 0; + impsect->data[thunkPos - impsect->base + 3] = 0; + impsect->data[thunk2Pos - impsect->base] = 0; + impsect->data[thunk2Pos - impsect->base + 1] = 0; + impsect->data[thunk2Pos - impsect->base + 2] = 0; + impsect->data[thunk2Pos - impsect->base + 3] = 0; + thunkPos = thunk2Pos + 4; + } + /* zero out the final entry to mark the end of the table */ + j = i * PE_IMPORTDIRENTRY_SIZE; + for (i = 0; i < PE_IMPORTDIRENTRY_SIZE; i++, j++) + { + impsect->data[j] = 0; + } + + k = impsect->length; + k += objectAlign - 1; + k &= (0xffffffff - (objectAlign - 1)); + impsect->virtualSize = k; + objectTable[8] = k & 0xff; /* store virtual size (in memory) of segment */ + objectTable[9] = (k >> 8) & 0xff; + objectTable[10] = (k >> 16) & 0xff; + objectTable[11] = (k >> 24) & 0xff; + + k = impsect->length; + objectTable[16] = (k) & 0xff; /* store initialised data size */ + objectTable[17] = (k >> 8) & 0xff; + objectTable[18] = (k >> 16) & 0xff; + objectTable[19] = (k >> 16) & 0xff; + + return; +} + +void BuildPERelocs(long relocSectNum, PUCHAR objectTable) +{ + int i, j; + PRELOC r; + PSEG relocSect; + UINT curStartPos; + UINT curBlockPos; + UINT k; + long targseg; + UINT targofs; + unsigned long templ; + unsigned short temps; + + /* do fixups */ + for (i = 0; i < fixcount; i++) + { + GetFixupTarget(relocs[i], &targseg, &targofs, TRUE); + switch (relocs[i]->rtype) + { + case FIX_BASE: + case FIX_PTR1616: + case FIX_PTR1632: + if (targseg < 0) + { + printf("Reloc %i:Segment selector relocations are not supported in PE files\n", i); + printf("rtype=%02X, frame=%04lX, target=%04lX, ftype=%02X, ttype=%02X\n", + relocs[i]->rtype, relocs[i]->frame, relocs[i]->target, relocs[i]->ftype, + relocs[i]->ttype); + + errcount++; + } + else + { + j = relocs[i]->ofs; + if (relocs[i]->rtype == FIX_PTR1616) + { + if (targofs > 0xffff) + { + printf("Relocs %i:Warning 32 bit offset in 16 bit field\n", i); + } + targofs &= 0xffff; + temps = seglist[relocs[i]->segnum]->data[j]; + temps += seglist[relocs[i]->segnum]->data[j + 1] << 8; + temps += targofs; + seglist[relocs[i]->segnum]->data[j] = temps & 0xff; + seglist[relocs[i]->segnum]->data[j + 1] = (temps >> 8) & 0xff; + j += 2; + } + else if (relocs[i]->rtype == FIX_PTR1632) + { + templ = seglist[relocs[i]->segnum]->data[j]; + templ += seglist[relocs[i]->segnum]->data[j + 1] << 8; + templ += seglist[relocs[i]->segnum]->data[j + 2] << 16; + templ += seglist[relocs[i]->segnum]->data[j + 3] << 24; + templ += targofs; + seglist[relocs[i]->segnum]->data[j] = templ & 0xff; + seglist[relocs[i]->segnum]->data[j + 1] = (templ >> 8) & 0xff; + seglist[relocs[i]->segnum]->data[j + 2] = (templ >> 16) & 0xff; + seglist[relocs[i]->segnum]->data[j + 3] = (templ >> 24) & 0xff; + j += 4; + } + temps = seglist[relocs[i]->segnum]->data[j]; + temps += seglist[relocs[i]->segnum]->data[j + 1] << 8; + temps += seglist[targseg]->absframe; + seglist[relocs[i]->segnum]->data[j] = temps & 0xff; + seglist[relocs[i]->segnum]->data[j + 1] = (temps >> 8) & 0xff; + } + break; + case FIX_OFS32: + case FIX_OFS32_2: + templ = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] << 16; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] << 24; + templ += targofs; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = templ & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (templ >> 8) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] = (templ >> 16) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] = (templ >> 24) & 0xff; + break; + case FIX_RVA32: + templ = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] << 16; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] << 24; + templ += targofs - imageBase; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = templ & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (templ >> 8) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] = (templ >> 16) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] = (templ >> 24) & 0xff; + break; + case FIX_OFS16: + case FIX_OFS16_2: + if (targofs > 0xffff) + { + printf("Relocs %i:Warning 32 bit offset in 16 bit field\n", i); + } + targofs &= 0xffff; + temps = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + temps += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + temps += targofs; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = temps & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (temps >> 8) & 0xff; + break; + case FIX_LBYTE: + case FIX_HBYTE: + printf("Error: Byte relocations not supported in PE files\n"); + errcount++; + break; + case FIX_SELF_LBYTE: + if (targseg >= 0) + { + printf("Error: Absolute Reloc target not supported for self-relative fixups\n"); + errcount++; + } + else + { + j = targofs; + j -= (seglist[relocs[i]->segnum]->base + relocs[i]->ofs + 1); + if ((j < -128) || (j > 127)) + { + printf("Error: Reloc %i out of range\n", i); + } + else + { + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] += j; + } + } + break; + case FIX_SELF_OFS16: + case FIX_SELF_OFS16_2: + if (targseg >= 0) + { + printf("Error: Absolute Reloc target not supported for self-relative fixups\n"); + errcount++; + } + else + { + j = targofs; + j -= (seglist[relocs[i]->segnum]->base + relocs[i]->ofs + 2); + if ((j < -32768) || (j > 32767)) + { + printf("Error: Reloc %i out of range\n", i); + } + else + { + temps = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + temps += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + temps += j; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = temps & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (temps >> 8) & 0xff; + } + } + break; + case FIX_SELF_OFS32: + case FIX_SELF_OFS32_2: + if (targseg >= 0) + { + printf("Error: Absolute Reloc target not supported for self-relative fixups\n"); + errcount++; + } + else + { + j = targofs; + j -= (seglist[relocs[i]->segnum]->base + relocs[i]->ofs + 4); + templ = seglist[relocs[i]->segnum]->data[relocs[i]->ofs]; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] << 8; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] << 16; + templ += seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] << 24; + templ += j; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs] = templ & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 1] = (templ >> 8) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 2] = (templ >> 16) & 0xff; + seglist[relocs[i]->segnum]->data[relocs[i]->ofs + 3] = (templ >> 24) & 0xff; + } + break; + default: + printf("Reloc %i:Relocation type %i not supported\n", i, relocs[i]->rtype); + errcount++; + } + } + + /* get reloc section */ + relocSect = outlist[relocSectNum]; /* get section structure */ + + /* sort relocations into order of increasing address */ + for (i = 1; i < fixcount; i++) + { + r = relocs[i]; /* save current reloc */ + for (j = i - 1; j >= 0; j--) /* search backwards through table */ + { + /* stop once we've found a target before current */ + if (r->outputPos >= relocs[j]->outputPos) + break; + /* otherwise move reloc entry up */ + relocs[j + 1] = relocs[j]; + } + j++; /* point to first entry after non-match */ + relocs[j] = r; /* put current reloc in position */ + } + + for (i = 0, curStartPos = 0, curBlockPos = 0; i < fixcount; i++) + { + switch (relocs[i]->rtype) + { + case FIX_SELF_OFS32: + case FIX_SELF_OFS32_2: + case FIX_SELF_OFS16: + case FIX_SELF_OFS16_2: + case FIX_SELF_LBYTE: + case FIX_RVA32: + continue; /* self-relative fixups and RVA fixups don't relocate */ + default: + break; + } + if (relocs[i]->outputPos >= (curStartPos + 0x1000)) /* more than 4K past block start? */ + { + j = relocSect->length & 3; + if (j) /* unaligned block position */ + { + relocSect->length += 4 - j; /* update length to align block */ + /* and block memory */ + relocSect->data = (PUCHAR)checkRealloc(relocSect->data, relocSect->length); + /* update size of current reloc block */ + k = relocSect->data[curBlockPos + 4]; + k += relocSect->data[curBlockPos + 5] << 8; + k += relocSect->data[curBlockPos + 6] << 16; + k += relocSect->data[curBlockPos + 7] << 24; + k += 4 - j; + relocSect->data[curBlockPos + 4] = k & 0xff; + relocSect->data[curBlockPos + 5] = (k >> 8) & 0xff; + relocSect->data[curBlockPos + 6] = (k >> 16) & 0xff; + relocSect->data[curBlockPos + 7] = (k >> 24) & 0xff; + for (j = 4 - j; j > 0; j--) + { + relocSect->data[relocSect->length - j] = 0; + } + } + curBlockPos = relocSect->length; /* get address in section of current block */ + relocSect->length += 8; /* 8 bytes block header */ + /* increase size of block */ + relocSect->data = (PUCHAR)checkRealloc(relocSect->data, relocSect->length); + /* store reloc base address, and block size */ + curStartPos = relocs[i]->outputPos & 0xfffff000; /* start of mem page */ + + /* start pos is relative to image base */ + relocSect->data[curBlockPos] = (curStartPos - imageBase) & 0xff; + relocSect->data[curBlockPos + 1] = ((curStartPos - imageBase) >> 8) & 0xff; + relocSect->data[curBlockPos + 2] = ((curStartPos - imageBase) >> 16) & 0xff; + relocSect->data[curBlockPos + 3] = ((curStartPos - imageBase) >> 24) & 0xff; + + relocSect->data[curBlockPos + 4] = 8; /* start size is 8 bytes */ + relocSect->data[curBlockPos + 5] = 0; + relocSect->data[curBlockPos + 6] = 0; + relocSect->data[curBlockPos + 7] = 0; + } + relocSect->data = (PUCHAR)checkRealloc(relocSect->data, relocSect->length + 2); + + j = relocs[i]->outputPos - curStartPos; /* low 12 bits of address */ + switch (relocs[i]->rtype) + { + case FIX_PTR1616: + case FIX_OFS16: + case FIX_OFS16_2: + j |= PE_REL_LOW16; + break; + case FIX_PTR1632: + case FIX_OFS32: + case FIX_OFS32_2: + j |= PE_REL_OFS32; + } + /* store relocation */ + relocSect->data[relocSect->length] = j & 0xff; + relocSect->data[relocSect->length + 1] = (j >> 8) & 0xff; + /* update block length */ + relocSect->length += 2; + /* update size of current reloc block */ + k = relocSect->data[curBlockPos + 4]; + k += relocSect->data[curBlockPos + 5] << 8; + k += relocSect->data[curBlockPos + 6] << 16; + k += relocSect->data[curBlockPos + 7] << 24; + k += 2; + relocSect->data[curBlockPos + 4] = k & 0xff; + relocSect->data[curBlockPos + 5] = (k >> 8) & 0xff; + relocSect->data[curBlockPos + 6] = (k >> 16) & 0xff; + relocSect->data[curBlockPos + 7] = (k >> 24) & 0xff; + } + /* if no fixups, then build NOP fixups, to make Windows NT happy */ + /* when it trys to relocate image */ + if (relocSect->length == 0) + { + /* 12 bytes for dummy section */ + relocSect->length = 12; + relocSect->data = (PUCHAR)checkMalloc(12); + /* zero it out for now */ + for (i = 0; i < 12; i++) + relocSect->data[i] = 0; + relocSect->data[4] = 12; /* size of block */ + } + + relocSect->datmask = (PUCHAR)checkMalloc((relocSect->length + 7) / 8); + for (i = 0; i < (relocSect->length + 7) / 8; i++) + { + relocSect->datmask[i] = 0xff; + } + + objectTable += PE_OBJECTENTRY_SIZE * relocSectNum; /* point to reloc object entry */ + k = relocSect->length; + k += objectAlign - 1; + k &= (0xffffffff - (objectAlign - 1)); + relocSect->virtualSize = k; + objectTable[8] = k & 0xff; /* store virtual size (in memory) of segment */ + objectTable[9] = (k >> 8) & 0xff; + objectTable[10] = (k >> 16) & 0xff; + objectTable[11] = (k >> 24) & 0xff; + + k = relocSect->length; + objectTable[16] = (k) & 0xff; /* store initialised data size */ + objectTable[17] = (k >> 8) & 0xff; + objectTable[18] = (k >> 16) & 0xff; + objectTable[19] = (k >> 16) & 0xff; + + k = objectTable[-PE_OBJECTENTRY_SIZE + 20]; /* get physical start of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 21] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 22] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 23] << 24; + + k += objectTable[-PE_OBJECTENTRY_SIZE + 16]; /* add physical length of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 17] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 18] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 19] << 24; + + k += fileAlign - 1; + k &= (0xffffffff - (fileAlign - 1)); /* aligned */ + + /* k is now physical location of this object */ + + objectTable[20] = (k) & 0xff; /* store physical file offset */ + objectTable[21] = (k >> 8) & 0xff; + objectTable[22] = (k >> 16) & 0xff; + objectTable[23] = (k >> 24) & 0xff; + + k = objectTable[-PE_OBJECTENTRY_SIZE + 12]; /* get virtual start of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 13] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 14] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 15] << 24; + + k += objectTable[-PE_OBJECTENTRY_SIZE + 8]; /* add virtual length of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 9] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 10] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 11] << 24; + + /* store base address (RVA) of section */ + objectTable[12] = (k) & 0xff; + objectTable[13] = (k >> 8) & 0xff; + objectTable[14] = (k >> 16) & 0xff; + objectTable[15] = (k >> 24) & 0xff; + + relocSect->base = k + imageBase; /* relocate section */ + + return; +} + +void BuildPEExports(long SectNum, PUCHAR objectTable, PCHAR name) +{ + long i, j; + UINT k; + PSEG expSect; + UINT namelen; + UINT numNames = 0; + UINT RVAStart; + UINT nameRVAStart; + UINT ordinalStart; + UINT nameSpaceStart; + UINT minOrd; + UINT maxOrd; + UINT numOrds; + PPEXPREC nameList; + PEXPREC curName; + + if (!expcount || (SectNum < 0)) + return; /* return if no exports */ + expSect = outlist[SectNum]; + + if (name) + { + namelen = strlen(name); + /* search backwards for path separator */ + for (i = namelen - 1; (i >= 0) && !IS_PATH_CHAR(name[i]); i--) + ; + if (i >= 0) /* if found path separator */ + { + name += (i + 1); /* update name pointer past path */ + namelen -= (i + 1); /* and reduce length */ + } + } + else + namelen = 0; + + expSect->length = PE_EXPORTHEADER_SIZE + 4 * expcount + namelen + 1; + /* min section size= header size + num exports * pointer size */ + /* plus space for null-terminated name */ + + minOrd = 0xffffffff; /* max ordinal num */ + maxOrd = 0; + + for (i = 0; i < expcount; i++) + { + /* check we've got an exported name */ + if (expdefs[i].exp_name && strlen(expdefs[i].exp_name)) + { + /* four bytes for name pointer */ + /* two bytes for ordinal, 1 for null terminator */ + expSect->length += strlen(expdefs[i].exp_name) + 7; + numNames++; + } + + if (expdefs[i].flags & EXP_ORD) /* ordinal? */ + { + if (expdefs[i].ordinal < minOrd) + minOrd = expdefs[i].ordinal; + if (expdefs[i].ordinal > maxOrd) + maxOrd = expdefs[i].ordinal; + } + } + + numOrds = expcount; /* by default, number of RVAs=number of exports */ + if (maxOrd >= minOrd) /* actually got some ordinal references? */ + { + i = maxOrd - minOrd + 1; /* get number of ordinals */ + if (i > expcount) /* if bigger range than number of exports */ + { + expSect->length += 4 * (i - expcount); /* up length */ + numOrds = i; /* get new num RVAs */ + } + } + else + { + minOrd = 1; /* if none defined, min is set to 1 */ + } + + expSect->data = (PUCHAR)checkMalloc(expSect->length); + + objectTable += PE_OBJECTENTRY_SIZE * SectNum; /* point to reloc object entry */ + k = expSect->length; + k += objectAlign - 1; + k &= (0xffffffff - (objectAlign - 1)); + expSect->virtualSize = k; + objectTable[8] = k & 0xff; /* store virtual size (in memory) of segment */ + objectTable[9] = (k >> 8) & 0xff; + objectTable[10] = (k >> 16) & 0xff; + objectTable[11] = (k >> 24) & 0xff; + + k = expSect->length; + objectTable[16] = (k) & 0xff; /* store initialised data size */ + objectTable[17] = (k >> 8) & 0xff; + objectTable[18] = (k >> 16) & 0xff; + objectTable[19] = (k >> 16) & 0xff; + + k = objectTable[-PE_OBJECTENTRY_SIZE + 20]; /* get physical start of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 21] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 22] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 23] << 24; + + k += objectTable[-PE_OBJECTENTRY_SIZE + 16]; /* add physical length of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 17] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 18] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 19] << 24; + + k += fileAlign - 1; + k &= (0xffffffff - (fileAlign - 1)); /* aligned */ + + /* k is now physical location of this object */ + + objectTable[20] = (k) & 0xff; /* store physical file offset */ + objectTable[21] = (k >> 8) & 0xff; + objectTable[22] = (k >> 16) & 0xff; + objectTable[23] = (k >> 24) & 0xff; + + k = objectTable[-PE_OBJECTENTRY_SIZE + 12]; /* get virtual start of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 13] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 14] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 15] << 24; + + k += objectTable[-PE_OBJECTENTRY_SIZE + 8]; /* add virtual length of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 9] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 10] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 11] << 24; + + /* store base address (RVA) of section */ + objectTable[12] = (k) & 0xff; + objectTable[13] = (k >> 8) & 0xff; + objectTable[14] = (k >> 16) & 0xff; + objectTable[15] = (k >> 24) & 0xff; + + expSect->base = k + imageBase; /* relocate section */ + + /* start with buf=all zero */ + for (i = 0; i < expSect->length; i++) + expSect->data[i] = 0; + + /* store creation time of export data */ + k = (UINT)time(NULL); + expSect->data[4] = k & 0xff; + expSect->data[5] = (k >> 8) & 0xff; + expSect->data[6] = (k >> 16) & 0xff; + expSect->data[7] = (k >> 24) & 0xff; + + expSect->data[16] = (minOrd) & 0xff; /* store ordinal base */ + expSect->data[17] = (minOrd >> 8) & 0xff; + expSect->data[18] = (minOrd >> 16) & 0xff; + expSect->data[19] = (minOrd >> 24) & 0xff; + + /* store number of RVAs */ + expSect->data[20] = numOrds & 0xff; + expSect->data[21] = (numOrds >> 8) & 0xff; + expSect->data[22] = (numOrds >> 16) & 0xff; + expSect->data[23] = (numOrds >> 24) & 0xff; + + RVAStart = PE_EXPORTHEADER_SIZE; /* start address of RVA table */ + nameRVAStart = RVAStart + numOrds * 4; /* start of name table entries */ + ordinalStart = nameRVAStart + numNames * 4; /* start of associated ordinal entries */ + nameSpaceStart = ordinalStart + numNames * 2; /* start of actual names */ + + /* store number of named exports */ + expSect->data[24] = numNames & 0xff; + expSect->data[25] = (numNames >> 8) & 0xff; + expSect->data[26] = (numNames >> 16) & 0xff; + expSect->data[27] = (numNames >> 24) & 0xff; + + /* store address of address table */ + expSect->data[28] = (RVAStart + expSect->base - imageBase) & 0xff; + expSect->data[29] = ((RVAStart + expSect->base - imageBase) >> 8) & 0xff; + expSect->data[30] = ((RVAStart + expSect->base - imageBase) >> 16) & 0xff; + expSect->data[31] = ((RVAStart + expSect->base - imageBase) >> 24) & 0xff; + + /* store address of name table */ + expSect->data[32] = (nameRVAStart + expSect->base - imageBase) & 0xff; + expSect->data[33] = ((nameRVAStart + expSect->base - imageBase) >> 8) & 0xff; + expSect->data[34] = ((nameRVAStart + expSect->base - imageBase) >> 16) & 0xff; + expSect->data[35] = ((nameRVAStart + expSect->base - imageBase) >> 24) & 0xff; + + /* store address of ordinal table */ + expSect->data[36] = (ordinalStart + expSect->base - imageBase) & 0xff; + expSect->data[37] = ((ordinalStart + expSect->base - imageBase) >> 8) & 0xff; + expSect->data[38] = ((ordinalStart + expSect->base - imageBase) >> 16) & 0xff; + expSect->data[39] = ((ordinalStart + expSect->base - imageBase) >> 24) & 0xff; + + /* process numbered exports */ + for (i = 0; i < expcount; i++) + { + if (expdefs[i].flags & EXP_ORD) + { + /* get current RVA */ + k = expSect->data[RVAStart + 4 * (expdefs[i].ordinal - minOrd)]; + k += expSect->data[RVAStart + 4 * (expdefs[i].ordinal - minOrd) + 1] << 8; + k += expSect->data[RVAStart + 4 * (expdefs[i].ordinal - minOrd) + 2] << 16; + k += expSect->data[RVAStart + 4 * (expdefs[i].ordinal - minOrd) + 3] << 24; + if (k) /* error if already used */ + { + printf("Duplicate export ordinal %u\n", expdefs[i].ordinal); + exit(1); + } + /* get RVA of export entry */ + k = expdefs[i].pubdef->ofs + seglist[expdefs[i].pubdef->segnum]->base - imageBase; + /* store it */ + expSect->data[RVAStart + 4 * (expdefs[i].ordinal - minOrd)] = k & 0xff; + expSect->data[RVAStart + 4 * (expdefs[i].ordinal - minOrd) + 1] = (k >> 8) & 0xff; + expSect->data[RVAStart + 4 * (expdefs[i].ordinal - minOrd) + 2] = (k >> 16) & 0xff; + expSect->data[RVAStart + 4 * (expdefs[i].ordinal - minOrd) + 3] = (k >> 24) & 0xff; + } + } + + /* process non-numbered exports */ + for (i = 0, j = RVAStart; i < expcount; i++) + { + if (!(expdefs[i].flags & EXP_ORD)) + { + do + { + k = expSect->data[j]; + k += expSect->data[j + 1] << 8; + k += expSect->data[j + 2] << 16; + k += expSect->data[j + 3] << 24; + if (k) + j += 4; + } while (k); /* move through table until we find a free spot */ + /* get RVA of export entry */ + k = expdefs[i].pubdef->ofs; + k += seglist[expdefs[i].pubdef->segnum]->base; + k -= imageBase; + /* store RVA */ + expSect->data[j] = k & 0xff; + expSect->data[j + 1] = (k >> 8) & 0xff; + expSect->data[j + 2] = (k >> 16) & 0xff; + expSect->data[j + 3] = (k >> 24) & 0xff; + expdefs[i].ordinal = (j - RVAStart) / 4 + minOrd; /* store ordinal */ + j += 4; + } + } + + if (numNames) /* sort name table if present */ + { + nameList = (PPEXPREC)checkMalloc(numNames * sizeof(PEXPREC)); + j = 0; /* no entries yet */ + for (i = 0; i < expcount; i++) + { + if (expdefs[i].exp_name && expdefs[i].exp_name[0]) + { + /* make entry in name list */ + nameList[j] = &expdefs[i]; + j++; + } + } + /* sort them into order */ + for (i = 1; i < numNames; i++) + { + curName = nameList[i]; + for (j = i - 1; j >= 0; j--) + { + /* break out if we're above previous entry */ + if (strcmp(curName->exp_name, nameList[j]->exp_name) >= 0) + { + break; + } + /* else move entry up */ + nameList[j + 1] = nameList[j]; + } + j++; /* move to one after better entry */ + nameList[j] = curName; /* insert current entry into position */ + } + /* and store */ + for (i = 0; i < numNames; i++) + { + /* store ordinal */ + expSect->data[ordinalStart] = (nameList[i]->ordinal - minOrd) & 0xff; + expSect->data[ordinalStart + 1] = ((nameList[i]->ordinal - minOrd) >> 8) & 0xff; + ordinalStart += 2; + /* store name RVA */ + expSect->data[nameRVAStart] = (nameSpaceStart + expSect->base - imageBase) & 0xff; + expSect->data[nameRVAStart + 1] = + ((nameSpaceStart + expSect->base - imageBase) >> 8) & 0xff; + expSect->data[nameRVAStart + 2] = + ((nameSpaceStart + expSect->base - imageBase) >> 16) & 0xff; + expSect->data[nameRVAStart + 3] = + ((nameSpaceStart + expSect->base - imageBase) >> 24) & 0xff; + nameRVAStart += 4; + /* store name */ + for (j = 0; nameList[i]->exp_name[j]; j++, nameSpaceStart++) + { + expSect->data[nameSpaceStart] = nameList[i]->exp_name[j]; + } + /* store NULL */ + expSect->data[nameSpaceStart] = 0; + nameSpaceStart++; + } + } + + /* store library name */ + for (j = 0; j < namelen; j++) + { + expSect->data[nameSpaceStart + j] = name[j]; + } + if (namelen) + { + expSect->data[nameSpaceStart + j] = 0; + /* store name RVA */ + expSect->data[12] = (nameSpaceStart + expSect->base - imageBase) & 0xff; + expSect->data[13] = ((nameSpaceStart + expSect->base - imageBase) >> 8) & 0xff; + expSect->data[14] = ((nameSpaceStart + expSect->base - imageBase) >> 16) & 0xff; + expSect->data[15] = ((nameSpaceStart + expSect->base - imageBase) >> 24) & 0xff; + } + + expSect->datmask = (PUCHAR)checkMalloc((expSect->length + 7) / 8); + for (i = 0; i < (expSect->length + 7) / 8; i++) + { + expSect->datmask[i] = 0xff; + } + + return; +} + +void BuildPEResources(long sectNum, PUCHAR objectTable) +{ + long i, j; + UINT k; + SEG *ressect; + RESOURCE curres; + int numtypes, numnamedtypes; + int numPairs, numnames, numids; + UINT nameSize, dataSize; + UINT tableSize, dataListSize; + UINT namePos, dataPos, tablePos, dataListPos; + UINT curTypePos = 0, curNamePos = 0, curLangPos = 0; + char *curTypeName = NULL, *curName = NULL; + int curTypeId = -1, curId = -1; + + if ((sectNum < 0) || !rescount) + return; + + objectTable += PE_OBJECTENTRY_SIZE * sectNum; /* point to import object entry */ + k = objectTable[-PE_OBJECTENTRY_SIZE + 20]; /* get physical start of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 21] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 22] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 23] << 24; + + k += objectTable[-PE_OBJECTENTRY_SIZE + 16]; /* add physical length of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 17] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 18] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 19] << 24; + + k += fileAlign - 1; + k &= (0xffffffff - (fileAlign - 1)); /* aligned */ + + /* k is now physical location of this object */ + + objectTable[20] = (k) & 0xff; /* store physical file offset */ + objectTable[21] = (k >> 8) & 0xff; + objectTable[22] = (k >> 16) & 0xff; + objectTable[23] = (k >> 24) & 0xff; + + k = objectTable[-PE_OBJECTENTRY_SIZE + 12]; /* get virtual start of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 13] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 14] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 15] << 24; + + k += objectTable[-PE_OBJECTENTRY_SIZE + 8]; /* add virtual length of prev object */ + k += objectTable[-PE_OBJECTENTRY_SIZE + 9] << 8; + k += objectTable[-PE_OBJECTENTRY_SIZE + 10] << 16; + k += objectTable[-PE_OBJECTENTRY_SIZE + 11] << 24; + + /* store base address (RVA) of section */ + objectTable[12] = (k) & 0xff; + objectTable[13] = (k >> 8) & 0xff; + objectTable[14] = (k >> 16) & 0xff; + objectTable[15] = (k >> 24) & 0xff; + + ressect = outlist[sectNum]; + ressect->base = k; /* get base RVA of section */ + + /* sort into type-id order */ + for (i = 1; i < rescount; i++) + { + curres = resource[i]; + for (j = i - 1; j >= 0; j--) + { + if (resource[j].typename) + { + if (!curres.typename) + break; + if (wstricmp(curres.typename, resource[j].typename) > 0) + break; + if (wstricmp(curres.typename, resource[j].typename) == 0) + { + if (resource[j].name) + { + if (!curres.name) + break; + if (wstricmp(curres.name, resource[j].name) > 0) + break; + if (wstricmp(curres.name, resource[j].name) == 0) + { + if (resource[j].languageid > curres.languageid) + break; + if (resource[j].languageid == curres.languageid) + { + printf("Error duplicate resource ID\n"); + exit(1); + } + } + } + else + { + if (!curres.name) + { + if (curres.id > resource[j].id) + break; + if (curres.id == resource[j].id) + { + if (resource[j].languageid > curres.languageid) + break; + if (resource[j].languageid == curres.languageid) + { + printf("Error duplicate resource ID\n"); + exit(1); + } + } + } + } + } + } + else + { + if (!curres.typename) + { + if (curres.typeid > resource[j].typeid) + break; + if (curres.typeid == resource[j].typeid) + { + if (resource[j].name) + { + if (!curres.name) + break; + if (wstricmp(curres.name, resource[j].name) > 0) + break; + if (wstricmp(curres.name, resource[j].name) == 0) + { + if (resource[j].languageid > curres.languageid) + break; + if (resource[j].languageid == curres.languageid) + { + printf("Error duplicate resource ID\n"); + exit(1); + } + } + } + else + { + if (!curres.name) + { + if (curres.id > resource[j].id) + break; + if (curres.id == resource[j].id) + { + if (resource[j].languageid > curres.languageid) + break; + if (resource[j].languageid == curres.languageid) + { + printf("Error duplicate resource ID\n"); + exit(1); + } + } + } + } + } + } + } + resource[j + 1] = resource[j]; + } + j++; + resource[j] = curres; + } + + nameSize = 0; + dataSize = 0; + for (i = 0; i < rescount; i++) + { + if (resource[i].typename) + { + nameSize += 2 + 2 * wstrlen(resource[i].typename); + } + if (resource[i].name) + { + nameSize += 2 + 2 * wstrlen(resource[i].name); + } + dataSize += resource[i].length + 3; + dataSize &= 0xfffffffc; + } + + /* count named types */ + numnamedtypes = 0; + numPairs = rescount; + for (i = 0; i < rescount; i++) + { + if (!resource[i].typename) + break; + if ((i > 0) && !wstricmp(resource[i].typename, resource[i - 1].typename)) + { + if (resource[i].name) + { + if (wstricmp(resource[i].name, resource[i - 1].name) == 0) + numPairs--; + } + else + { + if (!resource[i - 1].name && (resource[i].id == resource[i - 1].id)) + numPairs--; + } + continue; + } + numnamedtypes++; + } + numtypes = numnamedtypes; + for (; i < rescount; i++) + { + if ((i > 0) && !resource[i - 1].typename && (resource[i].typeid == resource[i - 1].typeid)) + { + if (resource[i].name) + { + if (wstricmp(resource[i].name, resource[i - 1].name) == 0) + numPairs--; + } + else + { + if (!resource[i - 1].name && (resource[i].id == resource[i - 1].id)) + numPairs--; + } + continue; + } + numtypes++; + } + + tableSize = (rescount + numtypes + numPairs) * PE_RESENTRY_SIZE + + (numtypes + numPairs + 1) * PE_RESDIR_SIZE; + dataListSize = rescount * PE_RESDATAENTRY_SIZE; + + dataListPos = tableSize; + namePos = tableSize + dataListSize; + dataPos = tableSize + nameSize + dataListSize + 3; + dataPos &= 0xfffffffc; + + ressect->length = dataPos + dataSize; + + ressect->data = (PUCHAR)checkMalloc(ressect->length); + + ressect->datmask = (PUCHAR)checkMalloc((ressect->length + 7) / 8); + + /* empty section to start with */ + for (i = 0; i < ressect->length; i++) + ressect->data[i] = 0; + + /* build master directory */ + /* store time/date of creation */ + k = (UINT)time(NULL); + ressect->data[4] = k & 0xff; + ressect->data[5] = (k >> 8) & 0xff; + ressect->data[6] = (k >> 16) & 0xff; + ressect->data[7] = (k >> 24) & 0xff; + + ressect->data[12] = numnamedtypes & 0xff; + ressect->data[13] = (numnamedtypes >> 8) & 0xff; + ressect->data[14] = (numtypes - numnamedtypes) & 0xff; + ressect->data[15] = ((numtypes - numnamedtypes) >> 8) & 0xff; + + tablePos = 16 + numtypes * PE_RESENTRY_SIZE; + curTypePos = 16; + curTypeName = NULL; + curTypeId = -1; + for (i = 0; i < rescount; i++) + { + if (!(resource[i].typename && curTypeName && + !wstricmp(resource[i].typename, curTypeName)) && + !(!resource[i].typename && !curTypeName && curTypeId == resource[i].typeid)) + { + if (resource[i].typename) + { + ressect->data[curTypePos] = (namePos) & 0xff; + ressect->data[curTypePos + 1] = ((namePos) >> 8) & 0xff; + ressect->data[curTypePos + 2] = ((namePos) >> 16) & 0xff; + ressect->data[curTypePos + 3] = (((namePos) >> 24) & 0xff) | 0x80; + curTypeName = resource[i].typename; + k = wstrlen(curTypeName); + ressect->data[namePos] = k & 0xff; + ressect->data[namePos + 1] = (k >> 8) & 0xff; + namePos += 2; + memcpy(ressect->data + namePos, curTypeName, k * 2); + namePos += k * 2; + curTypeId = -1; + } + else + { + curTypeName = NULL; + curTypeId = resource[i].typeid; + ressect->data[curTypePos] = curTypeId & 0xff; + ressect->data[curTypePos + 1] = (curTypeId >> 8) & 0xff; + ressect->data[curTypePos + 2] = (curTypeId >> 16) & 0xff; + ressect->data[curTypePos + 3] = (curTypeId >> 24) & 0xff; + } + ressect->data[curTypePos + 4] = (tablePos) & 0xff; + ressect->data[curTypePos + 5] = ((tablePos) >> 8) & 0xff; + ressect->data[curTypePos + 6] = ((tablePos) >> 16) & 0xff; + ressect->data[curTypePos + 7] = (((tablePos) >> 24) & 0x7f) | 0x80; + + numnames = numids = 0; + for (j = i; j < rescount; j++) + { + if (resource[i].typename) + { + if (!resource[j].typename) + break; + if (wstricmp(resource[i].typename, resource[j].typename) != 0) + break; + } + else + { + if (resource[j].typeid != resource[i].typeid) + break; + } + if (resource[j].name) + { + if (((j > i) && (wstricmp(resource[j].name, resource[j - 1].name) != 0)) || + (j == i)) + numnames++; + } + else + { + if (((j > i) && + (resource[j - 1].name || (resource[j].id != resource[j - 1].id))) || + (j == i)) + numids++; + } + } + /* store time/date of creation */ + k = (UINT)time(NULL); + ressect->data[tablePos + 4] = k & 0xff; + ressect->data[tablePos + 5] = (k >> 8) & 0xff; + ressect->data[tablePos + 6] = (k >> 16) & 0xff; + ressect->data[tablePos + 7] = (k >> 24) & 0xff; + + ressect->data[tablePos + 12] = numnames & 0xff; + ressect->data[tablePos + 13] = (numnames >> 8) & 0xff; + ressect->data[tablePos + 14] = numids & 0xff; + ressect->data[tablePos + 15] = (numids >> 8) & 0xff; + + curNamePos = tablePos + PE_RESDIR_SIZE; + curName = NULL; + curId = -1; + tablePos += PE_RESDIR_SIZE + (numids + numnames) * PE_RESENTRY_SIZE; + curTypePos += PE_RESENTRY_SIZE; + } + if (!(resource[i].name && curName && !wstricmp(resource[i].name, curName)) && + !(!resource[i].name && !curName && curId == resource[i].id)) + { + if (resource[i].name) + { + ressect->data[curNamePos] = (namePos) & 0xff; + ressect->data[curNamePos + 1] = ((namePos) >> 8) & 0xff; + ressect->data[curNamePos + 2] = ((namePos) >> 16) & 0xff; + ressect->data[curNamePos + 3] = (((namePos) >> 24) & 0xff) | 0x80; + curName = resource[i].name; + k = wstrlen(curName); + ressect->data[namePos] = k & 0xff; + ressect->data[namePos + 1] = (k >> 8) & 0xff; + namePos += 2; + memcpy(ressect->data + namePos, curName, k * 2); + namePos += k * 2; + curId = -1; + } + else + { + curName = NULL; + curId = resource[i].id; + ressect->data[curNamePos] = curId & 0xff; + ressect->data[curNamePos + 1] = (curId >> 8) & 0xff; + ressect->data[curNamePos + 2] = (curId >> 16) & 0xff; + ressect->data[curNamePos + 3] = (curId >> 24) & 0xff; + } + ressect->data[curNamePos + 4] = (tablePos) & 0xff; + ressect->data[curNamePos + 5] = ((tablePos) >> 8) & 0xff; + ressect->data[curNamePos + 6] = ((tablePos) >> 16) & 0xff; + ressect->data[curNamePos + 7] = (((tablePos) >> 24) & 0x7f) | 0x80; + + numids = 0; + for (j = i; j < rescount; j++) + { + if (resource[i].typename) + { + if (!resource[j].typename) + break; + if (wstricmp(resource[i].typename, resource[j].typename) != 0) + break; + } + else + { + if (resource[j].typeid != resource[i].typeid) + break; + } + if (resource[i].name) + { + if (!resource[j].name) + break; + if (wstricmp(resource[j].name, resource[i].name) != 0) + break; + } + else + { + if (resource[j].id != resource[i].id) + break; + } + numids++; + } + numnames = 0; /* no names for languages */ + /* store time/date of creation */ + k = (UINT)time(NULL); + ressect->data[tablePos + 4] = k & 0xff; + ressect->data[tablePos + 5] = (k >> 8) & 0xff; + ressect->data[tablePos + 6] = (k >> 16) & 0xff; + ressect->data[tablePos + 7] = (k >> 24) & 0xff; + + ressect->data[tablePos + 12] = numnames & 0xff; + ressect->data[tablePos + 13] = (numnames >> 8) & 0xff; + ressect->data[tablePos + 14] = numids & 0xff; + ressect->data[tablePos + 15] = (numids >> 8) & 0xff; + + curLangPos = tablePos + PE_RESDIR_SIZE; + tablePos += PE_RESDIR_SIZE + numids * PE_RESENTRY_SIZE; + curNamePos += PE_RESENTRY_SIZE; + } + ressect->data[curLangPos] = resource[i].languageid & 0xff; + ressect->data[curLangPos + 1] = (resource[i].languageid >> 8) & 0xff; + ressect->data[curLangPos + 2] = (resource[i].languageid >> 16) & 0xff; + ressect->data[curLangPos + 3] = (resource[i].languageid >> 24) & 0xff; + + ressect->data[curLangPos + 4] = (dataListPos) & 0xff; + ressect->data[curLangPos + 5] = ((dataListPos) >> 8) & 0xff; + ressect->data[curLangPos + 6] = ((dataListPos) >> 16) & 0xff; + ressect->data[curLangPos + 7] = (((dataListPos) >> 24) & 0x7f); + curLangPos += PE_RESENTRY_SIZE; + + ressect->data[dataListPos] = (dataPos + ressect->base) & 0xff; + ressect->data[dataListPos + 1] = ((dataPos + ressect->base) >> 8) & 0xff; + ressect->data[dataListPos + 2] = ((dataPos + ressect->base) >> 16) & 0xff; + ressect->data[dataListPos + 3] = ((dataPos + ressect->base) >> 24) & 0xff; + ressect->data[dataListPos + 4] = resource[i].length & 0xff; + ressect->data[dataListPos + 5] = (resource[i].length >> 8) & 0xff; + ressect->data[dataListPos + 6] = (resource[i].length >> 16) & 0xff; + ressect->data[dataListPos + 7] = (resource[i].length >> 24) & 0xff; + memcpy(ressect->data + dataPos, resource[i].data, resource[i].length); + dataPos += resource[i].length + 3; + dataPos &= 0xfffffffc; + dataListPos += PE_RESDATAENTRY_SIZE; + } + + /* mark whole section as required output */ + for (i = 0; i < (ressect->length + 7) / 8; i++) + ressect->datmask[i] = 0xff; + + /* update object table */ + ressect->base += imageBase; + + k = ressect->length; + k += objectAlign - 1; + k &= (0xffffffff - (objectAlign - 1)); + ressect->virtualSize = k; + objectTable[8] = k & 0xff; /* store virtual size (in memory) of segment */ + objectTable[9] = (k >> 8) & 0xff; + objectTable[10] = (k >> 16) & 0xff; + objectTable[11] = (k >> 24) & 0xff; + + k = ressect->length; + objectTable[16] = (k) & 0xff; /* store initialised data size */ + objectTable[17] = (k >> 8) & 0xff; + objectTable[18] = (k >> 16) & 0xff; + objectTable[19] = (k >> 16) & 0xff; + + return; +} + +void getStub(PUCHAR *pstubData, UINT *pstubSize) +{ + FILE *f; + unsigned char headbuf[0x1c]; + PUCHAR buf; + UINT imageSize; + UINT headerSize; + UINT relocSize; + UINT relocStart; + int i; + + if (stubName) + { + f = fopen(stubName, "rb"); + if (!f) + { + printf("Unable to open stub file %s\n", stubName); + exit(1); + } + if (fread(headbuf, 1, 0x1c, f) != 0x1c) /* try and read 0x1c bytes */ + { + printf("Error reading from file %s\n", stubName); + exit(1); + } + if ((headbuf[0] != 0x4d) || (headbuf[1] != 0x5a)) + { + printf("Stub not valid EXE file\n"); + exit(1); + } + /* get size of image */ + imageSize = headbuf[2] + (headbuf[3] << 8) + ((headbuf[4] + (headbuf[5] << 8)) << 9); + if (imageSize % 512) + imageSize -= 512; + headerSize = (headbuf[8] + (headbuf[9] << 8)) << 4; + relocSize = (headbuf[6] + (headbuf[7] << 8)) << 2; + imageSize -= headerSize; + printf("imageSize=%u\n", imageSize); + printf("header=%u\n", headerSize); + printf("reloc=%u\n", relocSize); + + /* allocate buffer for load image */ + buf = (PUCHAR)checkMalloc(imageSize + 0x40 + ((relocSize + 0xf) & 0xfffffff0)); + /* copy header */ + for (i = 0; i < 0x1c; i++) + buf[i] = headbuf[i]; + + relocStart = headbuf[0x18] + (headbuf[0x19] << 8); + /* load relocs */ + fseek(f, relocStart, SEEK_SET); + if (fread(buf + 0x40, 1, relocSize, f) != relocSize) + { + printf("Error reading from file %s\n", stubName); + exit(1); + } + + /* paragraph align reloc size */ + relocSize += 0xf; + relocSize &= 0xfffffff0; + + /* move to start of data */ + fseek(f, headerSize, SEEK_SET); + /* new header is 4 paragraphs long + relocSize*/ + relocSize >>= 4; + relocSize += 4; + buf[8] = relocSize & 0xff; + buf[9] = (relocSize >> 8) & 0xff; + headerSize = relocSize << 4; + /* load data into correct position */ + if (fread(buf + headerSize, 1, imageSize, f) != imageSize) + { + printf("Error reading from file %s\n", stubName); + exit(1); + } + /* relocations start at 0x40 */ + buf[0x18] = 0x40; + buf[0x19] = 0; + imageSize += headerSize; /* total file size */ + /* store pointer and size */ + (*pstubData) = buf; + (*pstubSize) = imageSize; + i = imageSize % 512; /* size mod 512 */ + imageSize = (imageSize + 511) >> 9; /* number of 512-byte pages */ + buf[2] = i & 0xff; + buf[3] = (i >> 8) & 0xff; + buf[4] = imageSize & 0xff; + buf[5] = (imageSize >> 8) & 0xff; + } + else + { + (*pstubData) = defaultStub; + (*pstubSize) = defaultStubSize; + } +} + +void OutputWin32file(PCHAR outname) +{ + long i, j, k; + UINT started; + UINT lastout; + PUCHAR headbuf; + PUCHAR stubData; + FILE *outfile; + UINT headerSize; + UINT headerVirtSize; + UINT stubSize; + long nameIndex; + UINT sectionStart; + UINT headerStart; + long relocSectNum, importSectNum, exportSectNum, resourceSectNum; + UINT codeBase = 0; + UINT dataBase = 0; + UINT codeSize = 0; + UINT dataSize = 0; + + printf("Generating PE file %s\n", outname); + + errcount = 0; + + /* allocate section entries for imports, exports and relocs if required */ + if (impsreq) + { + importSectNum = createOutputSection("imports", WINF_INITDATA | WINF_SHARED | WINF_READABLE); + } + else + { + importSectNum = -1; + } + + if (expcount) + { + exportSectNum = createOutputSection("exports", WINF_INITDATA | WINF_SHARED | WINF_READABLE); + } + else + { + exportSectNum = -1; + } + + /* Windows NT requires a reloc section to relocate image files, even */ + /* if it contains no actual fixups */ + relocSectNum = createOutputSection("relocs", WINF_INITDATA | WINF_SHARED | WINF_DISCARDABLE | + WINF_READABLE); + + if (rescount) + { + resourceSectNum = + createOutputSection("resource", WINF_INITDATA | WINF_SHARED | WINF_READABLE); + } + else + { + resourceSectNum = -1; + } + + /* build header */ + getStub(&stubData, &stubSize); + + headerStart = stubSize; /* get start of PE header */ + headerStart += 7; + headerStart &= 0xfffffff8; /* align PE header to 8 byte boundary */ + + headerSize = PE_HEADBUF_SIZE + outcount * PE_OBJECTENTRY_SIZE + stubSize; + headerVirtSize = headerSize + (objectAlign - 1); + headerVirtSize &= (0xffffffff - (objectAlign - 1)); + headerSize += (fileAlign - 1); + headerSize &= (0xffffffff - (fileAlign - 1)); + + headbuf = checkMalloc(headerSize); + + for (i = 0; i < headerSize; i++) + { + headbuf[i] = 0; + } + + for (i = 0; i < stubSize; i++) /* copy stub file */ + headbuf[i] = stubData[i]; + + headbuf[0x3c] = headerStart & 0xff; /* store pointer to PE header */ + headbuf[0x3d] = (headerStart >> 8) & 0xff; + headbuf[0x3e] = (headerStart >> 16) & 0xff; + headbuf[0x3f] = (headerStart >> 24) & 0xff; + + headbuf[headerStart + PE_SIGNATURE] = 0x50; /* P */ + headbuf[headerStart + PE_SIGNATURE + 1] = 0x45; /* E */ + headbuf[headerStart + PE_SIGNATURE + 2] = 0x00; /* 0 */ + headbuf[headerStart + PE_SIGNATURE + 3] = 0x00; /* 0 */ + headbuf[headerStart + PE_MACHINEID] = PE_INTEL386 & 0xff; + headbuf[headerStart + PE_MACHINEID + 1] = (PE_INTEL386 >> 8) & 0xff; + /* store time/date of creation */ + k = (UINT)time(NULL); + headbuf[headerStart + PE_DATESTAMP] = k & 0xff; + headbuf[headerStart + PE_DATESTAMP + 1] = (k >> 8) & 0xff; + headbuf[headerStart + PE_DATESTAMP + 2] = (k >> 16) & 0xff; + headbuf[headerStart + PE_DATESTAMP + 3] = (k >> 24) & 0xff; + + headbuf[headerStart + PE_HDRSIZE] = PE_OPTIONAL_HEADER_SIZE & 0xff; + headbuf[headerStart + PE_HDRSIZE + 1] = (PE_OPTIONAL_HEADER_SIZE >> 8) & 0xff; + + i = PE_FILE_EXECUTABLE | PE_FILE_32BIT; /* get flags */ + if (buildDll) + { + i |= PE_FILE_LIBRARY; /* if DLL, flag it */ + } + headbuf[headerStart + PE_FLAGS] = i & 0xff; /* store them */ + headbuf[headerStart + PE_FLAGS + 1] = (i >> 8) & 0xff; + + headbuf[headerStart + PE_MAGIC] = PE_MAGICNUM & 0xff; /* store magic number */ + headbuf[headerStart + PE_MAGIC + 1] = (PE_MAGICNUM >> 8) & 0xff; + + headbuf[headerStart + PE_IMAGEBASE] = imageBase & 0xff; /* store image base */ + headbuf[headerStart + PE_IMAGEBASE + 1] = (imageBase >> 8) & 0xff; + headbuf[headerStart + PE_IMAGEBASE + 2] = (imageBase >> 16) & 0xff; + headbuf[headerStart + PE_IMAGEBASE + 3] = (imageBase >> 24) & 0xff; + + headbuf[headerStart + PE_FILEALIGN] = fileAlign & 0xff; /* store image base */ + headbuf[headerStart + PE_FILEALIGN + 1] = (fileAlign >> 8) & 0xff; + headbuf[headerStart + PE_FILEALIGN + 2] = (fileAlign >> 16) & 0xff; + headbuf[headerStart + PE_FILEALIGN + 3] = (fileAlign >> 24) & 0xff; + + headbuf[headerStart + PE_OBJECTALIGN] = objectAlign & 0xff; /* store image base */ + headbuf[headerStart + PE_OBJECTALIGN + 1] = (objectAlign >> 8) & 0xff; + headbuf[headerStart + PE_OBJECTALIGN + 2] = (objectAlign >> 16) & 0xff; + headbuf[headerStart + PE_OBJECTALIGN + 3] = (objectAlign >> 24) & 0xff; + + headbuf[headerStart + PE_OSMAJOR] = osMajor; + headbuf[headerStart + PE_OSMINOR] = osMinor; + + headbuf[headerStart + PE_SUBSYSMAJOR] = subsysMajor; + headbuf[headerStart + PE_SUBSYSMINOR] = subsysMinor; + + headbuf[headerStart + PE_SUBSYSTEM] = subSystem & 0xff; + headbuf[headerStart + PE_SUBSYSTEM + 1] = (subSystem >> 8) & 0xff; + + headbuf[headerStart + PE_NUMRVAS] = PE_NUM_VAS & 0xff; + headbuf[headerStart + PE_NUMRVAS + 1] = (PE_NUM_VAS >> 8) & 0xff; + + headbuf[headerStart + PE_HEADERSIZE] = headerSize & 0xff; + headbuf[headerStart + PE_HEADERSIZE + 1] = (headerSize >> 8) & 0xff; + headbuf[headerStart + PE_HEADERSIZE + 2] = (headerSize >> 16) & 0xff; + headbuf[headerStart + PE_HEADERSIZE + 3] = (headerSize >> 24) & 0xff; + + headbuf[headerStart + PE_HEAPSIZE] = heapSize & 0xff; + headbuf[headerStart + PE_HEAPSIZE + 1] = (heapSize >> 8) & 0xff; + headbuf[headerStart + PE_HEAPSIZE + 2] = (heapSize >> 16) & 0xff; + headbuf[headerStart + PE_HEAPSIZE + 3] = (heapSize >> 24) & 0xff; + + headbuf[headerStart + PE_HEAPCOMMSIZE] = heapCommitSize & 0xff; + headbuf[headerStart + PE_HEAPCOMMSIZE + 1] = (heapCommitSize >> 8) & 0xff; + headbuf[headerStart + PE_HEAPCOMMSIZE + 2] = (heapCommitSize >> 16) & 0xff; + headbuf[headerStart + PE_HEAPCOMMSIZE + 3] = (heapCommitSize >> 24) & 0xff; + + headbuf[headerStart + PE_STACKSIZE] = stackSize & 0xff; + headbuf[headerStart + PE_STACKSIZE + 1] = (stackSize >> 8) & 0xff; + headbuf[headerStart + PE_STACKSIZE + 2] = (stackSize >> 16) & 0xff; + headbuf[headerStart + PE_STACKSIZE + 3] = (stackSize >> 24) & 0xff; + + headbuf[headerStart + PE_STACKCOMMSIZE] = stackCommitSize & 0xff; + headbuf[headerStart + PE_STACKCOMMSIZE + 1] = (stackCommitSize >> 8) & 0xff; + headbuf[headerStart + PE_STACKCOMMSIZE + 2] = (stackCommitSize >> 16) & 0xff; + headbuf[headerStart + PE_STACKCOMMSIZE + 3] = (stackCommitSize >> 24) & 0xff; + + /* shift segment start addresses up into place and build section headers */ + sectionStart = headerSize; + j = headerStart + PE_HEADBUF_SIZE; + + for (i = 0; i < outcount; i++, j += PE_OBJECTENTRY_SIZE) + { + nameIndex = outlist[i]->nameindex; + if (nameIndex >= 0) + { + for (k = 0; (k < strlen(namelist[nameIndex])) && (k < 8); k++) + { + headbuf[j + k] = namelist[nameIndex][k]; + } + } + k = outlist[i]->virtualSize; /* get virtual size */ + headbuf[j + 8] = k & 0xff; /* store virtual size (in memory) of segment */ + headbuf[j + 9] = (k >> 8) & 0xff; + headbuf[j + 10] = (k >> 16) & 0xff; + headbuf[j + 11] = (k >> 24) & 0xff; + + if (!padsegments) /* if not padding segments, reduce space consumption */ + { + for (k = outlist[i]->length - 1; (k >= 0) && !GetNbit(outlist[i]->datmask, k); k--) + ; + k++; /* k=initialised length */ + } + headbuf[j + 16] = (k) & 0xff; /* store initialised data size */ + headbuf[j + 17] = (k >> 8) & 0xff; + headbuf[j + 18] = (k >> 16) & 0xff; + headbuf[j + 19] = (k >> 24) & 0xff; + + headbuf[j + 20] = (sectionStart) & 0xff; /* store physical file offset */ + headbuf[j + 21] = (sectionStart >> 8) & 0xff; + headbuf[j + 22] = (sectionStart >> 16) & 0xff; + headbuf[j + 23] = (sectionStart >> 24) & 0xff; + + k += fileAlign - 1; + k &= (0xffffffff - (fileAlign - 1)); /* aligned initialised length */ + + sectionStart += k; /* update section start address for next section */ + + outlist[i]->base += headerVirtSize + imageBase; + headbuf[j + 12] = (outlist[i]->base - imageBase) & 0xff; + headbuf[j + 13] = ((outlist[i]->base - imageBase) >> 8) & 0xff; + headbuf[j + 14] = ((outlist[i]->base - imageBase) >> 16) & 0xff; + headbuf[j + 15] = ((outlist[i]->base - imageBase) >> 24) & 0xff; + + k = (outlist[i]->winFlags ^ WINF_NEG_FLAGS) & + WINF_IMAGE_FLAGS; /* get characteristice for section */ + headbuf[j + 36] = (k) & 0xff; /* store characteristics */ + headbuf[j + 37] = (k >> 8) & 0xff; + headbuf[j + 38] = (k >> 16) & 0xff; + headbuf[j + 39] = (k >> 24) & 0xff; + } + + headbuf[headerStart + PE_NUMOBJECTS] = outcount & 0xff; /* store number of sections */ + headbuf[headerStart + PE_NUMOBJECTS + 1] = (outcount >> 8) & 0xff; + + /* build import, export and relocation sections */ + + BuildPEImports(importSectNum, headbuf + headerStart + PE_HEADBUF_SIZE); + BuildPEExports(exportSectNum, headbuf + headerStart + PE_HEADBUF_SIZE, outname); + BuildPERelocs(relocSectNum, headbuf + headerStart + PE_HEADBUF_SIZE); + BuildPEResources(resourceSectNum, headbuf + headerStart + PE_HEADBUF_SIZE); + + if (errcount) + { + exit(1); + } + + /* get start address */ + if (gotstart) + { + GetFixupTarget(&startaddr, &startaddr.segnum, &startaddr.ofs, TRUE); + if (errcount) + { + printf("Invalid start address record\n"); + exit(1); + } + i = startaddr.ofs; + if (startaddr.segnum >= 0) + { + i += seglist[startaddr.segnum]->base; + } + i -= imageBase; /* RVA */ + headbuf[headerStart + PE_ENTRYPOINT] = i & 0xff; + headbuf[headerStart + PE_ENTRYPOINT + 1] = (i >> 8) & 0xff; + headbuf[headerStart + PE_ENTRYPOINT + 2] = (i >> 16) & 0xff; + headbuf[headerStart + PE_ENTRYPOINT + 3] = (i >> 24) & 0xff; + if (buildDll) /* if library */ + { + /* flag that entry point should always be called */ + headbuf[headerStart + PE_DLLFLAGS] = 0xf; + headbuf[headerStart + PE_DLLFLAGS + 1] = 0; + } + } + else + { + printf("Warning, no entry point specified\n"); + } + + if (importSectNum >= 0) /* if imports, add section entry */ + { + headbuf[headerStart + PE_IMPORTRVA] = (outlist[importSectNum]->base - imageBase) & 0xff; + headbuf[headerStart + PE_IMPORTRVA + 1] = + ((outlist[importSectNum]->base - imageBase) >> 8) & 0xff; + headbuf[headerStart + PE_IMPORTRVA + 2] = + ((outlist[importSectNum]->base - imageBase) >> 16) & 0xff; + headbuf[headerStart + PE_IMPORTRVA + 3] = + ((outlist[importSectNum]->base - imageBase) >> 24) & 0xff; + headbuf[headerStart + PE_IMPORTSIZE] = (outlist[importSectNum]->length) & 0xff; + headbuf[headerStart + PE_IMPORTSIZE + 1] = (outlist[importSectNum]->length >> 8) & 0xff; + headbuf[headerStart + PE_IMPORTSIZE + 2] = (outlist[importSectNum]->length >> 16) & 0xff; + headbuf[headerStart + PE_IMPORTSIZE + 3] = (outlist[importSectNum]->length >> 24) & 0xff; + } + if (relocSectNum >= 0) /* if relocs, add section entry */ + { + headbuf[headerStart + PE_FIXUPRVA] = (outlist[relocSectNum]->base - imageBase) & 0xff; + headbuf[headerStart + PE_FIXUPRVA + 1] = + ((outlist[relocSectNum]->base - imageBase) >> 8) & 0xff; + headbuf[headerStart + PE_FIXUPRVA + 2] = + ((outlist[relocSectNum]->base - imageBase) >> 16) & 0xff; + headbuf[headerStart + PE_FIXUPRVA + 3] = + ((outlist[relocSectNum]->base - imageBase) >> 24) & 0xff; + headbuf[headerStart + PE_FIXUPSIZE] = (outlist[relocSectNum]->length) & 0xff; + headbuf[headerStart + PE_FIXUPSIZE + 1] = (outlist[relocSectNum]->length >> 8) & 0xff; + headbuf[headerStart + PE_FIXUPSIZE + 2] = (outlist[relocSectNum]->length >> 16) & 0xff; + headbuf[headerStart + PE_FIXUPSIZE + 3] = (outlist[relocSectNum]->length >> 24) & 0xff; + } + + if (exportSectNum >= 0) /* if relocs, add section entry */ + { + headbuf[headerStart + PE_EXPORTRVA] = (outlist[exportSectNum]->base - imageBase) & 0xff; + headbuf[headerStart + PE_EXPORTRVA + 1] = + ((outlist[exportSectNum]->base - imageBase) >> 8) & 0xff; + headbuf[headerStart + PE_EXPORTRVA + 2] = + ((outlist[exportSectNum]->base - imageBase) >> 16) & 0xff; + headbuf[headerStart + PE_EXPORTRVA + 3] = + ((outlist[exportSectNum]->base - imageBase) >> 24) & 0xff; + headbuf[headerStart + PE_EXPORTSIZE] = (outlist[exportSectNum]->length) & 0xff; + headbuf[headerStart + PE_EXPORTSIZE + 1] = (outlist[exportSectNum]->length >> 8) & 0xff; + headbuf[headerStart + PE_EXPORTSIZE + 2] = (outlist[exportSectNum]->length >> 16) & 0xff; + headbuf[headerStart + PE_EXPORTSIZE + 3] = (outlist[exportSectNum]->length >> 24) & 0xff; + } + + if (resourceSectNum >= 0) /* if relocs, add section entry */ + { + headbuf[headerStart + PE_RESOURCERVA] = (outlist[resourceSectNum]->base - imageBase) & 0xff; + headbuf[headerStart + PE_RESOURCERVA + 1] = + ((outlist[resourceSectNum]->base - imageBase) >> 8) & 0xff; + headbuf[headerStart + PE_RESOURCERVA + 2] = + ((outlist[resourceSectNum]->base - imageBase) >> 16) & 0xff; + headbuf[headerStart + PE_RESOURCERVA + 3] = + ((outlist[resourceSectNum]->base - imageBase) >> 24) & 0xff; + headbuf[headerStart + PE_RESOURCESIZE] = (outlist[resourceSectNum]->length) & 0xff; + headbuf[headerStart + PE_RESOURCESIZE + 1] = (outlist[resourceSectNum]->length >> 8) & 0xff; + headbuf[headerStart + PE_RESOURCESIZE + 2] = + (outlist[resourceSectNum]->length >> 16) & 0xff; + headbuf[headerStart + PE_RESOURCESIZE + 3] = + (outlist[resourceSectNum]->length >> 24) & 0xff; + } + + j = headerStart + PE_HEADBUF_SIZE + (outcount - 1) * PE_OBJECTENTRY_SIZE; + + i = headbuf[j + 12]; /* get base of last section - image base */ + i += headbuf[j + 13] << 8; + i += headbuf[j + 14] << 16; + i += headbuf[j + 15] << 24; + + i += headbuf[j + 8]; /* add virtual size of section */ + i += headbuf[j + 9] << 8; + i += headbuf[j + 10] << 16; + i += headbuf[j + 11] << 24; + + headbuf[headerStart + PE_IMAGESIZE] = i & 0xff; + headbuf[headerStart + PE_IMAGESIZE + 1] = (i >> 8) & 0xff; + headbuf[headerStart + PE_IMAGESIZE + 2] = (i >> 16) & 0xff; + headbuf[headerStart + PE_IMAGESIZE + 3] = (i >> 24) & 0xff; + + headbuf[headerStart + PE_CODEBASE] = codeBase & 0xff; + headbuf[headerStart + PE_CODEBASE + 1] = (codeBase >> 8) & 0xff; + headbuf[headerStart + PE_CODEBASE + 2] = (codeBase >> 16) & 0xff; + headbuf[headerStart + PE_CODEBASE + 3] = (codeBase >> 24) & 0xff; + + headbuf[headerStart + PE_DATABASE] = dataBase & 0xff; + headbuf[headerStart + PE_DATABASE + 1] = (dataBase >> 8) & 0xff; + headbuf[headerStart + PE_DATABASE + 2] = (dataBase >> 16) & 0xff; + headbuf[headerStart + PE_DATABASE + 3] = (dataBase >> 24) & 0xff; + + headbuf[headerStart + PE_CODESIZE] = codeSize & 0xff; + headbuf[headerStart + PE_CODESIZE + 1] = (codeSize >> 8) & 0xff; + headbuf[headerStart + PE_CODESIZE + 2] = (codeSize >> 16) & 0xff; + headbuf[headerStart + PE_CODESIZE + 3] = (codeSize >> 24) & 0xff; + + headbuf[headerStart + PE_INITDATASIZE] = dataSize & 0xff; + headbuf[headerStart + PE_INITDATASIZE + 1] = (dataSize >> 8) & 0xff; + headbuf[headerStart + PE_INITDATASIZE + 2] = (dataSize >> 16) & 0xff; + headbuf[headerStart + PE_INITDATASIZE + 3] = (dataSize >> 24) & 0xff; + + /* zero out section start for all zero-length segments */ + j = headerStart + PE_HEADBUF_SIZE; + for (i = 0; i < outcount; i++, j += PE_OBJECTENTRY_SIZE) + { + /* check if size in file is zero */ + k = headbuf[j + 16] | headbuf[j + 17] | headbuf[j + 18] | headbuf[j + 19]; + if (!k) + { + /* if so, zero section start */ + headbuf[j + 20] = headbuf[j + 21] = headbuf[j + 22] = headbuf[j + 23] = 0; + } + } + + if (errcount != 0) + { + exit(1); + } + + outfile = fopen(outname, "wb"); + if (!outfile) + { + printf("Error writing to file %s\n", outname); + exit(1); + } + + for (i = 0; i < headerSize; i++) + { + fputc(headbuf[i], outfile); + } + + started = lastout = imageBase + headerVirtSize; + + for (i = 0; i < outcount; i++) + { + if (outlist[i] && ((outlist[i]->attr & SEG_ALIGN) != SEG_ABS)) + { + /* ensure section is aligned to file-Align */ + while (ftell(outfile) & (fileAlign - 1)) + { + fputc(0, outfile); + } + if (started > outlist[i]->base) + { + printf("Segment overlap\n"); + printf("Next addr=%08X,base=%08X\n", started, outlist[i]->base); + fclose(outfile); + exit(1); + } + if (padsegments) + { + while (started < outlist[i]->base) + { + fputc(0, outfile); + started++; + } + } + else + { + started = outlist[i]->base; + } + for (j = 0; j < outlist[i]->length; j++) + { + if (GetNbit(outlist[i]->datmask, j)) + { + for (; lastout < started; lastout++) + { + fputc(0, outfile); + } + fputc(outlist[i]->data[j], outfile); + lastout = started + 1; + } + else if (padsegments) + { + fputc(0, outfile); + lastout = started + 1; + } + started++; + } + started = lastout = outlist[i]->base + outlist[i]->virtualSize; + } + } + + fclose(outfile); +} diff --git a/alink/UTIL.C b/alink/UTIL.C new file mode 100644 index 0000000..2978614 --- /dev/null +++ b/alink/UTIL.C @@ -0,0 +1,349 @@ +#include "ALINK.H" + +int stricmp(const char *s1, const char *s2) +{ + unsigned char c1, c2; + + if (!s1 || !s2) + return (s1 != s2); + + while (*s1 && *s2) + { + c1 = (unsigned char)toupper((unsigned char)*s1); + c2 = (unsigned char)toupper((unsigned char)*s2); + if (c1 != c2) + return c1 - c2; + s1++; + s2++; + } + c1 = (unsigned char)toupper((unsigned char)*s1); + c2 = (unsigned char)toupper((unsigned char)*s2); + return c1 - c2; +} + +char *strupr(char *s) +{ + char *p; + + if (!s) + return NULL; + for (p = s; *p; p++) + { + *p = (char)toupper((unsigned char)*p); + } + return s; +} + +char *_strdup(const char *s) +{ + size_t len; + char *p; + + if (!s) + return NULL; + len = strlen(s) + 1; + p = (char *)malloc(len); + if (!p) + return NULL; + memcpy(p, s, len); + return p; +} + +int getBitCount(UINT a) +{ + int count = 0; + + while (a) + { + if (a & 1) + count++; + a >>= 1; + } + return count; +} + +void ClearNbit(PUCHAR mask, long i) { mask[i / 8] &= 0xff - (1 << (i % 8)); } + +void SetNbit(PUCHAR mask, long i) { mask[i / 8] |= (1 << (i % 8)); } + +char GetNbit(PUCHAR mask, long i) { return (mask[i / 8] >> (i % 8)) & 1; } + +long GetIndex(PUCHAR buf, long *index) +{ + long i; + if (buf[*index] & 0x80) + { + i = ((buf[*index] & 0x7f) * 256) + buf[(*index) + 1]; + (*index) += 2; + return i; + } + else + { + return buf[(*index)++]; + } +} + +void ReportError(long errnum) +{ + UINT tot, i; + + printf("\nError in file at %08X", filepos); + switch (errnum) + { + case ERR_EXTRA_DATA: + printf(" - extra data in record\n"); + break; + case ERR_NO_HEADER: + printf(" - no header record\n"); + break; + case ERR_NO_RECDATA: + printf(" - record data not present\n"); + break; + case ERR_NO_MEM: + printf(" - insufficient memory\n"); + break; + case ERR_INV_DATA: + printf(" - invalid data address\n"); + break; + case ERR_INV_SEG: + printf(" - invalid segment number\n"); + break; + case ERR_BAD_FIXUP: + printf(" - invalid fixup record\n"); + break; + case ERR_BAD_SEGDEF: + printf(" - invalid segment definition record\n"); + break; + case ERR_ABS_SEG: + printf(" - data emitted to absolute segment\n"); + break; + case ERR_DUP_PUBLIC: + printf(" - duplicate public definition\n"); + break; + case ERR_NO_MODEND: + printf(" - unexpected end of file (no MODEND record)\n"); + break; + case ERR_EXTRA_HEADER: + printf(" - duplicate module header\n"); + break; + case ERR_UNKNOWN_RECTYPE: + printf(" - unknown object module record type %02X\n", rectype); + break; + case ERR_SEG_TOO_LARGE: + printf(" - 4Gb Non-Absolute segments not supported.\n"); + break; + case ERR_MULTIPLE_STARTS: + printf(" - start address defined in more than one module.\n"); + break; + case ERR_BAD_GRPDEF: + printf(" - illegal group definition\n"); + break; + case ERR_OVERWRITE: + printf(" - overlapping data regions\n"); + break; + case ERR_INVALID_COMENT: + printf(" - COMENT record format invalid\n"); + break; + case ERR_ILLEGAL_IMPORTS: + printf(" - Imports required to link, and not supported by output file type\n"); + break; + default: + printf("\n"); + } + printf("name count = %u\n", namecount); + printf("seg count = %u\n", segcount); + printf("extcount=%u\n", extcount); + printf("grpcount=%u\n", grpcount); + printf("comcount=%u\n", comcount); + printf("fixcount=%u\n", fixcount); + printf("impcount=%u\n", impcount); + printf("expcount=%u\n", expcount); + printf("modcount=%u\n", nummods); + + for (i = 0, tot = 0; i < segcount; i++) + { + if (seglist[i] && seglist[i]->data) + tot += seglist[i]->length; + } + printf("total segment size=%08X\n", tot); + + exit(1); +} + +unsigned short wtoupper(unsigned short a) +{ + if (a >= 256) + return a; + return toupper(a); +} + +int wstricmp(const char *s1, const char *s2) +{ + int i = 0; + unsigned short a, b; + + while (TRUE) + { + a = s1[i] + (s1[i + 1] << 8); + b = s2[i] + (s2[i + 1] << 8); + if (wtoupper(a) < wtoupper(b)) + return -1; + if (wtoupper(a) > wtoupper(b)) + return +1; + if (!a) + return 0; + i += 2; + } +} + +int wstrlen(const char *s) +{ + int i; + for (i = 0; s[i] || s[i + 1]; i += 2) + ; + return i / 2; +} + +int sortCompare(const void *x1, const void *x2) +{ + return strcmp(((PSORTENTRY)x1)->id, ((PSORTENTRY)x2)->id); +} + +void *checkMalloc(size_t x) +{ + void *p; + + p = malloc(x); + if (!p) + { + fprintf(stderr, "Error, Insufficient memory in call to malloc\n"); + exit(1); + } + return p; +} + +void *checkRealloc(void *p, size_t x) +{ + p = realloc(p, x); + if (!p) + { + fprintf(stderr, "Error, Insufficient memory in call to realloc\n"); + exit(1); + } + return p; +} + +char *checkStrdup(const char *s) +{ + char *p; + + if (!s) + { + fprintf(stderr, "Error, Taking duplicate of NULL pointer in call to strdup\n"); + exit(1); + } + p = strdup(s); + if (!p) + { + fprintf(stderr, "Error, Insufficient memory in call to strdup\n"); + exit(1); + } + return p; +} + +PSORTENTRY binarySearch(PSORTENTRY list, UINT count, char *key) +{ + UINT i; + int j; + + if (!list) + return NULL; + if (!count) + return NULL; + if (!key) + return NULL; + + while (count) + { + i = count / 2; /* get mid point */ + j = strcmp(key, list[i].id); /* compare */ + if (!j) + return list + i; /* return match if found */ + if (j < 0) /* if key is less than current id */ + { + count = i; /* only search those before current node */ + } + else /* key is greater than current id */ + { + list += i + 1; /* start search at next node */ + count -= (i + 1); /* count of those remaining after current node */ + } + } + + return NULL; /* return NULL if no match (count=0) */ +} + +void sortedInsert(PSORTENTRY *plist, UINT *pcount, char *key, void *object) +{ + PSORTENTRY list, node; + UINT count, index, i; + int j; + + if (!plist || !pcount) + return; + + count = *pcount; + list = *plist; + + node = binarySearch(list, count, key); + if (node) /* if ID already exists, add object to it */ + { + node->object = (void **)checkRealloc(node->object, (node->count + 1) * sizeof(void *)); + node->object[node->count] = object; + ++(node->count); + return; + } + + /* new node, insert into list */ + /* find index to insert at */ + index = 0; + while (count) + { + i = count / 2; + j = strcmp(key, list[index + i].id); /* compare */ + if (!j) /* match !! */ + { + printf("Something odd is happening\n"); + exit(1); + } + + if (j < 0) /* if key is less than current id */ + { + count = i; /* only search those before current node */ + } + else /* key is greater than current id */ + { + index += i + 1; /* start search at next node */ + count -= (i + 1); /* count of those remaining after current node */ + } + } + /* grow list */ + count = *pcount + 1; + + list = (PSORTENTRY)checkRealloc(list, sizeof(SORTENTRY) * count); + + j = count - index - 1; /* get number of entries after insertion index */ + if (j) /* move them up 1 entry if some */ + { + memmove(list + index + 1, list + index, j * sizeof(SORTENTRY)); + } + + /* put new node in position */ + list[index].id = checkStrdup(key); + list[index].object = (void **)checkMalloc(sizeof(void *)); + list[index].object[0] = object; + list[index].count = 1; + + *pcount = count; + *plist = list; +} diff --git a/alink/alink.mak b/alink/alink.mak new file mode 100644 index 0000000..929758c --- /dev/null +++ b/alink/alink.mak @@ -0,0 +1,11 @@ +CC = gcc -Zomf -c -s -O5 + +%.obj: %.c + $(CC) -o $@ $< + +all: blink.exe + +alink.obj coff.obj cofflib.obj combine.obj output.obj objload.obj util.obj: alink.h + +blink.exe: alink.obj coff.obj cofflib.obj combine.obj output.obj objload.obj util.obj + alink -subsys con @lib/def.rsp -o $@ $^ diff --git a/alink/alink2.mak b/alink/alink2.mak new file mode 100644 index 0000000..ae6b802 --- /dev/null +++ b/alink/alink2.mak @@ -0,0 +1,11 @@ +CC = cl /Zl /c /Ox + +%.obj: %.c + $(CC) /Fo$@ $< + +all: blink.exe + +alink.obj coff.obj cofflib.obj combine.obj output.obj objload.obj util.obj: alink.h + +blink.exe: alink.obj coff.obj cofflib.obj combine.obj output.obj objload.obj util.obj + alink -subsys con @lib/def.rsp -o $@ $^ diff --git a/kvikdos.c b/kvikdos.c index e99b219..9538bdc 100644 --- a/kvikdos.c +++ b/kvikdos.c @@ -22,10 +22,27 @@ * * DOSBox 0.74-4 * (https://github.com/svn2github/dosbox/blob/acd380bcde72db74f3b476253899016f686bc0ef/src/dos/dos_execute.cpp) * * MS-DOS 6.22 (source code not available). + * + * Code Quality Requirements (project policy): + * * Initialize all fields of structs and all local variables before first use. + * Uninitialized reads are treated as correctness bugs, not style issues. + * * For parser state (CLI/env/path), prefer explicit defaults over implicit behavior. + * * Keep compatibility fallbacks narrow and documented next to the code path. + * * New CLI flags and parser branches must include at least one regression test + * that exercises the option combination and asserts "no crash". + * * Keep diagnostics actionable: print the failing DOS path/drive and return a + * deterministic DOS-compatible error when possible. + * + * Recommended ongoing improvements: + * * Add a dedicated parser matrix test (flag combinations) to CI. + * * Run ASan/UBSan and valgrind in separate jobs, plus cppcheck/clang-analyzer. + * * Prefer clearer variable names in new code paths (e.g. requested_drive, + * active_drive) and add short intent comments for compatibility logic. */ #define _GNU_SOURCE 1 /* For MAP_ANONYMOUS and memmem(). */ #include +#include #include #include /* For stdin availability check. */ #include @@ -36,9 +53,32 @@ #include #include #include +#include #include #include +static char *xstrdup(const char *s) { + size_t n = strlen(s) + 1; + char *p = (char*)malloc(n); + if (!p) return NULL; + memcpy(p, s, n); + return p; +} + +/* Bounded C-string copy with guaranteed NUL termination. */ +static void copy_cstr0(char *dst, size_t dst_size, const char *src) { + size_t n; + if (dst_size == 0) return; + n = strlen(src); + if (n >= dst_size) n = dst_size - 1; + memcpy(dst, src, n); + dst[n] = '\0'; +} + + +static unsigned g_case_fallback_mode = 2; /* Best default: all. */ +static FILE *g_diag_file = NULL; + #ifdef USE_MINI_KVM /* For systems with a broken linux/kvm.h. */ # include "mini_kvm.h" #else @@ -121,6 +161,105 @@ static char is_same_ascii_nocase(const char *a, const char *b, unsigned size) { return 1; } + +/* Resolve pathname components case-insensitively. + * If keep_last_case is nonzero, the last component is kept as-is (useful for O_CREAT). + * Returns 1 on success and writes out_path, 0 on failure. + */ +static int resolve_case_fallback_path(const char *in_path, char *out_path, size_t out_size, int keep_last_case) { + const int is_abs = in_path[0] == '/'; + const char *p = in_path + is_abs; + char comp[256]; + if (!in_path[0] || out_size < 2) return 0; + out_path[0] = is_abs ? '/' : '\0'; + out_path[is_abs ? 1 : 0] = '\0'; + while (*p) { + const char *q, *r; + size_t clen, olen; + int is_last; + DIR *dd; + struct dirent *de; + char picked[256]; + int found = 0; + const char *scan_dir; + while (*p == '/') ++p; + if (!*p) break; + q = p; + while (*q && *q != '/') ++q; + clen = (size_t)(q - p); + if (clen == 0 || clen >= sizeof(comp)) return 0; + memcpy(comp, p, clen); + comp[clen] = '\0'; + r = q; + while (*r == '/') ++r; + is_last = *r == '\0'; + if (!(keep_last_case && is_last)) { + scan_dir = out_path[0] ? out_path : "."; + dd = opendir(scan_dir); + if (!dd) return 0; + while ((de = readdir(dd)) != NULL) { + size_t dlen = strlen(de->d_name); + if (dlen == clen && is_same_ascii_nocase(de->d_name, comp, (unsigned)clen)) { + memcpy(picked, de->d_name, dlen + 1); + found = 1; + break; + } + } + closedir(dd); + if (!found) return 0; + } else { + memcpy(picked, comp, clen + 1); + } + olen = strlen(out_path); + if (olen && out_path[olen - 1] != '/') { + if (olen + 1 >= out_size) return 0; + out_path[olen++] = '/'; + out_path[olen] = '\0'; + } + if (olen + strlen(picked) >= out_size) return 0; + strcpy(out_path + olen, picked); + p = q; + } + return out_path[0] != '\0'; +} + +static int open_with_case_fallback(const char *linux_filename, int flags, mode_t mode) { + char resolved[1024]; + int fd = open(linux_filename, flags, mode); + if (fd >= 0) return fd; + if (errno == ENOENT && g_case_fallback_mode == 2 && linux_filename[0] && + resolve_case_fallback_path(linux_filename, resolved, sizeof(resolved), (flags & O_CREAT) != 0)) { + fd = open(resolved, flags, mode); + } + return fd; +} + +static int stat_with_case_fallback(const char *linux_filename, struct stat *st, int keep_last_case) { + char resolved[1024]; + if (stat(linux_filename, st) == 0) return 0; + if (errno == ENOENT && g_case_fallback_mode == 2 && linux_filename[0] && + resolve_case_fallback_path(linux_filename, resolved, sizeof(resolved), keep_last_case)) { + return stat(resolved, st); + } + return -1; +} + +static DIR *opendir_with_case_fallback(const char *linux_dir, char *resolved_out, size_t resolved_out_size) { + DIR *dd; + if (resolved_out_size) resolved_out[0] = '\0'; + dd = opendir(linux_dir); + if (dd) { + if (resolved_out_size) { + strncpy(resolved_out, linux_dir, resolved_out_size - 1); + resolved_out[resolved_out_size - 1] = '\0'; + } + return dd; + } + if (errno != ENOENT || g_case_fallback_mode != 2 || !linux_dir[0]) return NULL; + if (!resolve_case_fallback_path(linux_dir, resolved_out, resolved_out_size, 0)) return NULL; + return opendir(resolved_out); +} + /* Example name_prefix: "PATH=". */ static const char *getenv_prefix(const char *name_prefix, const char **env, const char **env_end) { const size_t name_prefix_size = strlen(name_prefix); @@ -130,6 +269,44 @@ static const char *getenv_prefix(const char *name_prefix, const char **env, cons return NULL; } +/* Example name_prefix: "PATH=". Scans NULL-terminated envp with case-insensitive key compare. */ +static const char *getenv_prefix_nocase0(const char *name_prefix, const char *const *env) { + size_t i, nps; + if (!env) return NULL; + nps = strlen(name_prefix); + for (; *env; ++env) { + const char *s = *env; + if (!s) continue; + for (i = 0; i < nps; ++i) { + char a = s[i], b = name_prefix[i]; + if (a == '\0' || ((a | 32) != (b | 32))) break; + } + if (i == nps) return s + nps; + } + return NULL; +} + +/* Example name_prefix: "PATH=". Scans NUL-separated DOS env block. */ +static const char *getenv_prefix_block_nocase(const char *name_prefix, const char *env, const char *env_end) { + size_t i, nps; + if (!env || !env_end || env >= env_end) return NULL; + nps = strlen(name_prefix); + while (env < env_end && *env) { + const char *eq = strchr(env, '='); + const char *next = env + strlen(env) + 1; + if (!eq || eq >= env_end) break; + if ((size_t)(eq - env + 1) == nps) { + for (i = 0; i < nps; ++i) { + char a = env[i], b = name_prefix[i]; + if ((a | 32) != (b | 32)) break; + } + if (i == nps) return eq + 1; + } + env = next; + } + return NULL; +} + /* p is a Linux pathname. */ static char get_case_mode_from_last_component(const char *p) { const char *q = p + strlen(p); @@ -156,7 +333,7 @@ static char detect_prog_filename_type(const char *prog_filename) { /* A relatively small struct, no string buffers. */ typedef struct DirState { char drive; /* 'A', 'B', 'C', 'D', ... ('A' + DRIVE_COUNT - 1). */ - char current_dir[DRIVE_COUNT][1]; /* Currently mostly unused. */ /*char current_dir[DRIVE_COUNT][128];*/ /* In DOS syntax. Ends with \, unless empty. If current_dir[2] is FOO\BAR\, then it corresponds to C:\FOO\BAR. */ + char current_dir[DRIVE_COUNT][64]; /* In DOS syntax. Ends with \, unless empty. If current_dir[2] is FOO\BAR\, then it corresponds to C:\FOO\BAR. */ const char *linux_mount_dir[DRIVE_COUNT]; /* Linux directory to which the specific drive has been mounted, with '/' suffix (or empty), or NULL. Owned externally. linux_mount_dir[2] == "/tmp/foo/" maps DOS path C:\MY\FILE.TXT to Linux path /tmp/foo/MY/FILE.TXT . */ char case_mode[DRIVE_COUNT]; /* CASE_MODE_... indicating how letters in DOS filename characters should be converted to Linux (uppercase or lowercase). CASE_MODE_UPPERCASE (0) is the default. We could also call it case_fold. */ const char *dos_prog_abs; /* DOS absolute pathname of the program being run. Externally owned, can be NULL. */ @@ -244,13 +421,16 @@ static const char *get_dos_abs_filename_r(const char *p, char drive, const DirSt static char *get_linux_filename_r(const char *p, const DirState *dir_state, char *out_buf, char **out_lastc_out) { char *out_p = out_buf, *out_pend, *out_lastc = out_buf; const char *in_linux; + const char *linux_prog_base, *slashp; const char *in_dos[2] = { "", "" }; char drive_idx, case_flip = 0, case_mode; if (*p == '\0') goto done; /* Empty pathname is an error. */ if (!dir_state) { /* Convert to relative Linux pathname. */ in_linux = NULL; in_dos[1] = p; - } else if (dir_state->dos_prog_abs && strcmp(p, dir_state->dos_prog_abs) == 0) { + } else if (dir_state->linux_prog && dir_state->dos_prog_abs && strcmp(p, dir_state->dos_prog_abs) == 0 && + (linux_prog_base = ((slashp = strrchr(dir_state->linux_prog, '/')) != NULL ? slashp + 1 : dir_state->linux_prog)) != NULL && + strchr(linux_prog_base, '.') != NULL) { in_linux = dir_state->linux_prog; } else { if (p[0] != '\0' && p[1] == ':') { @@ -340,7 +520,7 @@ static char *get_linux_filename_r(const char *p, const DirState *dir_state, char for (; *p == '\\' || *p == '/'; ++p) {} } } - if (p[-1] == '/' || p[-1] == '\\') goto error; /* If pathname ends with a slash, that's an error. It's safe to check since we've checked already that it's not empty. */ + if (p > in_dos[1] && (p[-1] == '/' || p[-1] == '\\')) goto error; /* If pathname ends with a slash, that's an error. */ } done: *out_p = '\0'; @@ -429,6 +609,26 @@ static char *find_prog_on_path(const char *prog_filename, const DirState *dir_st if (CMD_PARSE_DEBUG) fprintf(stderr, "debug: found prog on drive=%c: %s\n", drive, fnbuf); *drive_out = drive; return fnbuf; /* Found executable program file. */ + } else if (g_case_fallback_mode != 0) { + char *s, *base = fnbuf; + strcpy(fnbuf2, fnbuf); + for (s = fnbuf2; *s; ++s) if (*s == '/') base = s + 1; + for (s = base; *s; ++s) if ((unsigned char)(*s - 'a') <= 'z' - 'a') *s &= ~32; + if (CMD_PARSE_DEBUG) fprintf(stderr, "debug: trying prog case fallback upper: %s\n", fnbuf2); + if (stat(fnbuf2, &st) == 0 && S_ISREG(st.st_mode)) { + strcpy(fnbuf, fnbuf2); + *drive_out = drive; + return fnbuf; + } + strcpy(fnbuf2, fnbuf); + for (s = base = fnbuf2; *s; ++s) if (*s == '/') base = s + 1; + for (s = base; *s; ++s) if ((unsigned char)(*s - 'A') <= 'Z' - 'A') *s |= 32; + if (CMD_PARSE_DEBUG) fprintf(stderr, "debug: trying prog case fallback lower: %s\n", fnbuf2); + if (stat(fnbuf2, &st) == 0 && S_ISREG(st.st_mode)) { + strcpy(fnbuf, fnbuf2); + *drive_out = drive; + return fnbuf; + } } } end_of_pp: @@ -451,6 +651,10 @@ typedef struct EmuParams { char is_hlt_ok; unsigned mem_mb; const char *hlt_dump_filename; + const char *diag_filename; + unsigned diag_mask; + unsigned case_fallback_mode; /* 0=off 1=prog 2=all */ + char strict_mode; /* 0=permissive 1=strict */ } EmuParams; typedef struct ParsedCmdArgs { @@ -460,9 +664,37 @@ typedef struct ParsedCmdArgs { int tty_in_fd; const char* const *args; /* NULL-terminated list of NUL-terminated strings. Overlaps the program main(...) argv. */ const char* const *envp0; /* NULL-terminated list of NUL-terminated strings. Overlaps the program main(...) argv. */ + const char *extra_env[128]; + unsigned extra_env_count; const char *dpmi_prog; } ParsedCmdArgs; +static void init_parsed_cmd_args(ParsedCmdArgs *cmd_args, char *placeholder_for_default) { + unsigned u; + memset(cmd_args, 0, sizeof(*cmd_args)); + for (u = 0; u < DRIVE_COUNT; ++u) { + cmd_args->dir_state.current_dir[u][0] = '\0'; + cmd_args->dir_state.linux_mount_dir[u] = NULL; + } + cmd_args->dir_state.drive = 'C'; + cmd_args->dir_state.dos_prog_abs = NULL; + cmd_args->dir_state.linux_prog = NULL; + cmd_args->dir_state.linux_mount_dir['C' - 'A'] = placeholder_for_default; + cmd_args->dir_state.linux_mount_dir['D' - 'A'] = placeholder_for_default; + cmd_args->dir_state.linux_mount_dir['E' - 'A'] = placeholder_for_default; + memset(cmd_args->dir_state.case_mode, CASE_MODE_UNSPECIFIED, DRIVE_COUNT); + cmd_args->dpmi_prog = NULL; + cmd_args->extra_env_count = 0; + cmd_args->tty_in_fd = -1; + cmd_args->emu_params.mem_mb = 1; + cmd_args->emu_params.is_hlt_ok = 0; + cmd_args->emu_params.hlt_dump_filename = NULL; + cmd_args->emu_params.diag_filename = NULL; + cmd_args->emu_params.diag_mask = 1; /* compat */ + cmd_args->emu_params.case_fallback_mode = 2; /* all */ + cmd_args->emu_params.strict_mode = 0; /* permissive */ +} + static void parse_args(char **argv, struct ParsedCmdArgs *cmd_args_out, const char *pre_msg, const char *usage_extra, const char *post_msg) { char *placeholder_for_default = (char*)pre_msg; ParsedCmdArgs cmd_args; @@ -474,25 +706,44 @@ static void parse_args(char **argv, struct ParsedCmdArgs *cmd_args_out, const ch const char *dos_path; char dos_prog_drive; char is_kvm_check; + const char *path_dos_flag = NULL; + const char *cwd_dos_flag = NULL; argv0 = argv[0]; /* Ignoring instances of the --cmd flage, for pts-fast-dosbox compatibility. */ for (; argv[1] && 0 == strcmp(argv[1], "--cmd"); ++argv) {} if (!argv0 || !argv[1] || 0 == strcmp(argv[1], "--help")) { fprintf(stderr, "%s%s%s [ ...] [ ...]\n%s" - "Flags:\n" - "--kvm-check: Just check that KVM works, run a fake true.com\n" - "--env==: Adds environment variable.\n" - "--prog=: Sets DOS pathname of program.\n" - "--mount=/: Makes Linux dir visible as for DOS program.\n" - " If is :, then mount uppercase. If is -, then mount lowercase.\n" - "--mount=0: Makes sure that : is not visible in DOS.\n" - "--drive=: Sets initial current drive for DOS program.\n" - "--tty-in=: Selects Linux file descriptor for keyboard input.\n" - " -3: fake keys; -2: stdin buffered; -1: /dev/tty; 0: stdin etc.\n" - "--mem-mb=: Use n MiB of memory for DOS. Only 1 is supported.\n" - "--hlt-ok: Allow the hlt instruction.\n" - "--hlt-dump=: Write memory dump upon hlt instruction.\n", + "General:\n" + " --kvm-check Check KVM only (runs fake true.com)\n" + " --strict | --permissive Interrupt/API handling policy (default: --permissive)\n" + "\n" + "DOS Runtime:\n" + " --toolchain= Preset env for msc4|msc5|msc6|masm5|bc2|bcpp1|bc5|ic86\n" + " --env== Add DOS environment variable\n" + " --env-file= Load DOS env vars (NAME=VALUE lines)\n" + " --path-dos= Set DOS PATH directly (e.g. C:\\BIN;C:\\)\n" + " --prog= Set DOS pathname of running program\n" + " --cwd-dos= Set initial DOS current directory (e.g. C:\\BIN)\n" + "\n" + "Mounts:\n" + " --mount=/ Mount Linux dir to DOS drive\n" + " --mount=0 Hide DOS drive\n" + " ':' uppercase, '-' lowercase\n" + " --drive= Set initial DOS drive\n" + "\n" + "Compatibility:\n" + " --case-fallback=off|prog|all Case-insensitive program lookup (default: all)\n" + "\n" + "Diagnostics:\n" + " --diag=compat|exec|int|fs|all|off Runtime diagnostics (default: compat)\n" + " --diag-file= Write diagnostics to file\n" + "\n" + "I/O and Memory:\n" + " --tty-in= -3 fake, -2 buffered stdin, -1 /dev/tty, >=0 fd\n" + " --mem-mb= DOS memory in MiB (currently only 1)\n" + " --hlt-ok Allow hlt instruction\n" + " --hlt-dump= Dump guest memory on hlt\n", pre_msg, argv0, usage_extra, post_msg); exit(argv0 && argv[1] ? 0 : 1); } @@ -501,25 +752,10 @@ static void parse_args(char **argv, struct ParsedCmdArgs *cmd_args_out, const ch exit(0); } - { unsigned u; - for (u = 0; u < DRIVE_COUNT; ++u) { - cmd_args.dir_state.current_dir[u][0] = '\0'; - cmd_args.dir_state.linux_mount_dir[u] = NULL; - } - cmd_args.dir_state.drive = 'C'; - cmd_args.dir_state.dos_prog_abs = NULL; - cmd_args.dir_state.linux_mount_dir['C' - 'A'] = placeholder_for_default; - cmd_args.dir_state.linux_mount_dir['D' - 'A'] = placeholder_for_default; - cmd_args.dir_state.linux_mount_dir['E' - 'A'] = placeholder_for_default; - memset(cmd_args.dir_state.case_mode, CASE_MODE_UNSPECIFIED, DRIVE_COUNT); - } - + init_parsed_cmd_args(&cmd_args, placeholder_for_default); + /* Used by get_linux_filename_r() alias check; must be initialized in parse-only paths. */ + cmd_args.dir_state.linux_prog = NULL; envp = envp0 = ++argv; - cmd_args.dpmi_prog = NULL; - cmd_args.tty_in_fd = -1; - cmd_args.emu_params.mem_mb = 1; - cmd_args.emu_params.is_hlt_ok = 0; - cmd_args.emu_params.hlt_dump_filename = NULL; is_kvm_check = 0; is_drive_specified = 0; while (argv[0]) { @@ -658,6 +894,127 @@ static void parse_args(char **argv, struct ParsedCmdArgs *cmd_args_out, const ch goto do_mem_mb; } else if (0 == strcmp(arg, "--kvm-check")) { is_kvm_check = 1; + } else if (0 == strcmp(arg, "--permissive")) { + cmd_args.emu_params.strict_mode = 0; + } else if (0 == strcmp(arg, "--strict")) { + cmd_args.emu_params.strict_mode = 1; + } else if (0 == strcmp(arg, "--path-dos")) { + if (!argv[0]) goto missing_argument; + path_dos_flag = *argv++; + } else if (0 == strncmp(arg, "--path-dos=", 11)) { + path_dos_flag = arg + 11; + } else if (0 == strcmp(arg, "--cwd-dos")) { + if (!argv[0]) goto missing_argument; + cwd_dos_flag = *argv++; + } else if (0 == strncmp(arg, "--cwd-dos=", 10)) { + cwd_dos_flag = arg + 10; + } else if (0 == strcmp(arg, "--env-file")) { + FILE *f; + char line[4096]; + if (!argv[0]) goto missing_argument; + f = fopen(*argv++, "rb"); + if (!f) { + perror("fatal: cannot open --env-file"); + exit(1); + } + while (fgets(line, sizeof(line), f)) { + char *p = line, *eq, *e; + while (*p == ' ' || *p == '\t') ++p; + if (*p == '\0' || *p == '\n' || *p == '\r' || *p == '#') continue; + for (e = p + strlen(p); e != p && (e[-1] == '\n' || e[-1] == '\r' || e[-1] == ' ' || e[-1] == '\t'); --e) {} + *e = '\0'; + eq = strchr(p, '='); + if (!eq || eq == p) continue; + if (cmd_args.extra_env_count >= sizeof(cmd_args.extra_env) / sizeof(cmd_args.extra_env[0])) { + fprintf(stderr, "fatal: too many extra env vars\n"); + exit(1); + } + cmd_args.extra_env[cmd_args.extra_env_count++] = xstrdup(p); + } + fclose(f); + } else if (0 == strncmp(arg, "--env-file=", 11)) { + FILE *f = fopen(arg + 11, "rb"); + char line[4096]; + if (!f) { + perror("fatal: cannot open --env-file"); + exit(1); + } + while (fgets(line, sizeof(line), f)) { + char *p = line, *eq, *e; + while (*p == ' ' || *p == '\t') ++p; + if (*p == '\0' || *p == '\n' || *p == '\r' || *p == '#') continue; + for (e = p + strlen(p); e != p && (e[-1] == '\n' || e[-1] == '\r' || e[-1] == ' ' || e[-1] == '\t'); --e) {} + *e = '\0'; + eq = strchr(p, '='); + if (!eq || eq == p) continue; + if (cmd_args.extra_env_count >= sizeof(cmd_args.extra_env) / sizeof(cmd_args.extra_env[0])) { + fprintf(stderr, "fatal: too many extra env vars\n"); + exit(1); + } + cmd_args.extra_env[cmd_args.extra_env_count++] = xstrdup(p); + } + fclose(f); + } else if (0 == strcmp(arg, "--diag")) { + if (!argv[0]) goto missing_argument; + arg = *argv++; + if (strcmp(arg, "off") == 0) cmd_args.emu_params.diag_mask = 0; + else if (strcmp(arg, "compat") == 0) cmd_args.emu_params.diag_mask = 1; + else if (strcmp(arg, "exec") == 0) cmd_args.emu_params.diag_mask = 2; + else if (strcmp(arg, "int") == 0) cmd_args.emu_params.diag_mask = 4; + else if (strcmp(arg, "fs") == 0) cmd_args.emu_params.diag_mask = 8; + else if (strcmp(arg, "all") == 0) cmd_args.emu_params.diag_mask = ~0U; + else { fprintf(stderr, "fatal: bad --diag value: %s\n", arg); exit(1); } + } else if (0 == strncmp(arg, "--diag=", 7)) { + arg += 7; + if (strcmp(arg, "off") == 0) cmd_args.emu_params.diag_mask = 0; + else if (strcmp(arg, "compat") == 0) cmd_args.emu_params.diag_mask = 1; + else if (strcmp(arg, "exec") == 0) cmd_args.emu_params.diag_mask = 2; + else if (strcmp(arg, "int") == 0) cmd_args.emu_params.diag_mask = 4; + else if (strcmp(arg, "fs") == 0) cmd_args.emu_params.diag_mask = 8; + else if (strcmp(arg, "all") == 0) cmd_args.emu_params.diag_mask = ~0U; + else { fprintf(stderr, "fatal: bad --diag value: %s\n", arg); exit(1); } + } else if (0 == strcmp(arg, "--diag-file")) { + if (!argv[0]) goto missing_argument; + cmd_args.emu_params.diag_filename = *argv++; + } else if (0 == strncmp(arg, "--diag-file=", 12)) { + cmd_args.emu_params.diag_filename = arg + 12; + } else if (0 == strcmp(arg, "--case-fallback")) { + if (!argv[0]) goto missing_argument; + arg = *argv++; + if (strcmp(arg, "off") == 0) cmd_args.emu_params.case_fallback_mode = 0; + else if (strcmp(arg, "prog") == 0) cmd_args.emu_params.case_fallback_mode = 1; + else if (strcmp(arg, "all") == 0) cmd_args.emu_params.case_fallback_mode = 2; + else { fprintf(stderr, "fatal: bad --case-fallback value: %s\n", arg); exit(1); } + } else if (0 == strncmp(arg, "--case-fallback=", 16)) { + arg += 16; + if (strcmp(arg, "off") == 0) cmd_args.emu_params.case_fallback_mode = 0; + else if (strcmp(arg, "prog") == 0) cmd_args.emu_params.case_fallback_mode = 1; + else if (strcmp(arg, "all") == 0) cmd_args.emu_params.case_fallback_mode = 2; + else { fprintf(stderr, "fatal: bad --case-fallback value: %s\n", arg); exit(1); } + } else if (0 == strcmp(arg, "--toolchain") || 0 == strncmp(arg, "--toolchain=", 12)) { + const char *tc; + if (arg[11] == '\0') { + if (!argv[0]) goto missing_argument; + tc = *argv++; + } else { + tc = arg + 12; + } + if (cmd_args.extra_env_count + 4 >= sizeof(cmd_args.extra_env) / sizeof(cmd_args.extra_env[0])) { + fprintf(stderr, "fatal: too many extra env vars\n"); + exit(1); + } + if (strcmp(tc, "msc6") == 0 || strcmp(tc, "bcpp1") == 0 || strcmp(tc, "bc5") == 0) { + cmd_args.extra_env[cmd_args.extra_env_count++] = xstrdup("PATH=C:\\BIN;C:\\"); + cmd_args.extra_env[cmd_args.extra_env_count++] = xstrdup("LIB=C:\\LIB"); + cmd_args.extra_env[cmd_args.extra_env_count++] = xstrdup("INCLUDE=C:\\INCLUDE"); + } else if (strcmp(tc, "msc4") == 0 || strcmp(tc, "msc5") == 0 || strcmp(tc, "masm5") == 0 || strcmp(tc, "bc2") == 0 || strcmp(tc, "ic86") == 0) { + cmd_args.extra_env[cmd_args.extra_env_count++] = xstrdup("PATH=C:\\"); + cmd_args.extra_env[cmd_args.extra_env_count++] = xstrdup("LIB=LIB"); + cmd_args.extra_env[cmd_args.extra_env_count++] = xstrdup("INCLUDE=INCLUDE"); + } else { + fprintf(stderr, "fatal: unknown --toolchain preset: %s\n", tc); + exit(1); + } } else { fprintf(stderr, "fatal: unknown command-line flag: %s\n", arg); exit(1); @@ -681,6 +1038,19 @@ static void parse_args(char **argv, struct ParsedCmdArgs *cmd_args_out, const ch fprintf(stderr, "GLF (%s)\n", get_linux_filename(".\\aaa\\..")); fprintf(stderr, "GLF (%s)\n", get_linux_filename("C:\\foo\\.\\\\\\.\\bar\\.\\..\\.\\bazzzz\\..")); #endif + if (path_dos_flag) { + char *pd; + size_t n = strlen(path_dos_flag); + pd = (char*)malloc(n + 6); + if (!pd) { perror("fatal: malloc"); exit(252); } + memcpy(pd, "PATH=", 5); + memcpy(pd + 5, path_dos_flag, n + 1); + if (cmd_args.extra_env_count >= sizeof(cmd_args.extra_env) / sizeof(cmd_args.extra_env[0])) { + fprintf(stderr, "fatal: too many extra env vars\n"); + exit(1); + } + cmd_args.extra_env[cmd_args.extra_env_count++] = pd; + } *envp = NULL; /* Remaining arguments in argv will be passed to the DOS program in PSP:0x80. */ dos_path = getenv_prefix("PATH=", (char const**)envp0, (char const**)envp); @@ -771,8 +1141,8 @@ static void parse_args(char **argv, struct ParsedCmdArgs *cmd_args_out, const ch } cmd_args.prog_filename = find_prog_on_path(prog_name_arg, &cmd_args.dir_state, dos_path, &dos_prog_drive); /* Return value is fnbuf or NULL. */ if (!cmd_args.prog_filename) { - fprintf(stderr, "fatal: DOS command not found on %c:\\ or %%PATH%%: %s\n", cmd_args.dir_state.drive, prog_name_arg); - exit(252); + fprintf(stderr, "error: DOS command not found on %c:\\ or %%PATH%%: %s\n", cmd_args.dir_state.drive, prog_name_arg); + exit(1); } if (*cmd_args.prog_filename == '\0') { fprintf(stderr, "fatal: invalid DOS program name: %s\n", prog_name_arg); @@ -786,7 +1156,6 @@ static void parse_args(char **argv, struct ParsedCmdArgs *cmd_args_out, const ch prog_name_arg = NULL; /* Make sure we don't use it later, we've already modified it for cmd_args.dir_state.linux_mount_dir['E' - 'A']. */ if (!cmd_args.dir_state.linux_mount_dir[cmd_args.dir_state.drive - 'A']) { - /*cmd_args.dir_state.drive = 'C';*/ fprintf(stderr, "fatal: no mount point for default drive (specify --mount=...): %c:\n", cmd_args.dir_state.drive); exit(1); } @@ -794,6 +1163,9 @@ static void parse_args(char **argv, struct ParsedCmdArgs *cmd_args_out, const ch cmd_args.dir_state.dos_prog_abs = get_dos_abs_filename_r(cmd_args.prog_filename, dos_prog_drive, &cmd_args.dir_state, dosfnbuf); if (CMD_PARSE_DEBUG) fprintf(stderr, "debug: prog_filename=(%s) dos_prog_abs=(%s) dos_prog_drive=%c\n", cmd_args.prog_filename, cmd_args.dir_state.dos_prog_abs, dos_prog_drive); } + if (!is_drive_specified && prog_filename_type == PFT_LINUX && dos_prog_drive >= 'A' && dos_prog_drive <= 'Z') { + cmd_args.dir_state.drive = dos_prog_drive; /* Resolve relative filenames in the executable directory mount by default. */ + } if (prog_filename_type == PFT_LINUX && cmd_args.dir_state.case_mode['C' - 'A'] == CASE_MODE_UNSPECIFIED && cmd_args.dir_state.linux_mount_dir['C' - 'A']) { const char *mount_c = cmd_args.dir_state.linux_mount_dir['C' - 'A']; const char *q; @@ -807,6 +1179,51 @@ static void parse_args(char **argv, struct ParsedCmdArgs *cmd_args_out, const ch } } + if (!cwd_dos_flag && cmd_args.dir_state.dos_prog_abs && cmd_args.dir_state.dos_prog_abs[0] && + (cmd_args.dir_state.dos_prog_abs[0] & ~32) - 'A' + 0U < DRIVE_COUNT && + cmd_args.dir_state.dos_prog_abs[1] == ':' && cmd_args.dir_state.dos_prog_abs[2] == '\\') { + /* Default DOS CWD to the executable directory, so relative file args resolve like in DOSBox. + * Example: `kvikdos C:\\TOOLS\\UNP.EXE t unp.exe` should search in C:\\TOOLS\\. + */ + char drive = cmd_args.dir_state.dos_prog_abs[0] & ~32; + const char *base = cmd_args.dir_state.dos_prog_abs + strlen(cmd_args.dir_state.dos_prog_abs); + const char *p = cmd_args.dir_state.dos_prog_abs + 3; + char tmp[DOS_PATH_SIZE]; + size_t n; + for (; base > p && base[-1] != '\\' && base[-1] != '/'; --base) {} + if (base > p) { + n = (size_t)(base - p); + if (n >= sizeof(tmp)) n = sizeof(tmp) - 1; + memcpy(tmp, p, n); + if (n > 0 && tmp[n - 1] != '\\') tmp[n++] = '\\'; + tmp[n] = '\0'; + copy_cstr0(cmd_args.dir_state.current_dir[drive - 'A'], sizeof(cmd_args.dir_state.current_dir[drive - 'A']), tmp); + cmd_args.dir_state.drive = drive; + } + } + + if (cwd_dos_flag && cwd_dos_flag[0]) { + char drive = cmd_args.dir_state.drive; + const char *p = cwd_dos_flag; + char tmp[DOS_PATH_SIZE]; + char *q = tmp; + if ((p[0] & ~32) - 'A' + 0U < DRIVE_COUNT && p[1] == ':') { + drive = p[0] & ~32; + p += 2; + if (*p == '\\' || *p == '/') ++p; + cmd_args.dir_state.drive = drive; + } else if (*p == '\\' || *p == '/') { + ++p; + } + while (*p && q + 2 < tmp + sizeof(tmp)) { + char c = *p++; + if (c == '/') c = '\\'; + *q++ = (c - 'a' + 0U <= 'z' - 'a' + 0U) ? (c & ~32) : c; + } + if (q != tmp && q[-1] != '\\') *q++ = '\\'; + *q = '\0'; + copy_cstr0(cmd_args.dir_state.current_dir[drive - 'A'], sizeof(cmd_args.dir_state.current_dir[drive - 'A']), tmp); + } cmd_args.args = (const char* const*)argv; cmd_args.envp0 = (const char* const*)envp0; *cmd_args_out = cmd_args; @@ -1245,7 +1662,7 @@ static char *load_dos_executable_program(int img_fd, const char *filename, void const unsigned headsize = (unsigned)exehdr[EXE_HDRSIZE] << 4; const unsigned image_size = exesize - headsize; unsigned memsize_min_para = (nblocks << 5) - exehdr[EXE_HDRSIZE] + exehdr[EXE_MINALLOC]; /* This includes .bss after the image. Please note that this doesn't depend on exehdr[EXE_LASTSIZE]. Formula is same as in MS-DOS 6.22, FreeDOS 1.2, DOSBox 0.74-4. */ - const unsigned memsize_max_para = (unsigned short)(exehdr[EXE_MAXALLOC] + 1) < 2 ? 0xffff : (nblocks << 5) - exehdr[EXE_HDRSIZE] + exehdr[EXE_MAXALLOC]; + unsigned memsize_max_para = (unsigned short)(exehdr[EXE_MAXALLOC] + 1) < 2 ? 0xffff : (nblocks << 5) - exehdr[EXE_HDRSIZE] + exehdr[EXE_MAXALLOC]; char * const image_addr = (char*)mem + (PSP_PARA << 4) + 0x100; const unsigned image_para = PSP_PARA + 0x10; unsigned reloc_count = exehdr[EXE_NRELOC]; @@ -1271,8 +1688,10 @@ static char *load_dos_executable_program(int img_fd, const char *filename, void } } if (memsize_min_para > memsize_max_para) { - fprintf(stderr, "fatal: DOS .exe minimum memory larger than maximum: %s\n", filename); - exit(252); + /* Some historical toolchains emit malformed MINALLOC/MAXALLOC pairs. + * MS-DOS still loads them by effectively treating MAXALLOC as at least + * MINALLOC, so clamp instead of failing hard. */ + memsize_max_para = memsize_min_para; } if (memsize_min_para > memsize_available_para) { fprintf(stderr, "fatal: DOS .exe uses too much conventional memory: %s\n", filename); @@ -1334,11 +1753,11 @@ static char *load_dos_executable_program(int img_fd, const char *filename, void exepack_stub_plus_reloc_size >= 258 && exepack_stub_plus_reloc_size <= exepack_max_size) { char *after_packhdr = (char*)packhdr + exehdr[EXE_IP]; const char *c = (const char*)memmem(after_packhdr, exepack_stub_plus_reloc_size, "\xcd\x21\xb8\xff\x4c\xcd\x21", 7); - if (DEBUG) fprintf(stderr, "info: detected DOS .exe packed with exepack: header_size=%d exepack_max_size=%d exepack_stub_plus_reloc_size=%d\n", exehdr[EXE_IP], exepack_max_size, exepack_stub_plus_reloc_size); + if (DEBUG) fprintf(stderr, "info: detected DOS .exe packed with exepack: header_size=%d exepack_max_size=%u exepack_stub_plus_reloc_size=%u\n", exehdr[EXE_IP], exepack_max_size, exepack_stub_plus_reloc_size); if (c) { const unsigned exepack_stub_size = (unsigned)(c + 7 + 22 - after_packhdr); if (exepack_stub_size >= 258 && exepack_stub_size <= 290) { - if (DEBUG) fprintf(stderr, "info: detected DOS .exe packed with exepack: header_size=%d exepack_max_size=%d exepack_stub_plus_reloc_size=%d exepack_stub_size=%d\n", exehdr[EXE_IP], exepack_max_size, exepack_stub_plus_reloc_size, exepack_stub_size); + if (DEBUG) fprintf(stderr, "info: detected DOS .exe packed with exepack: header_size=%d exepack_max_size=%u exepack_stub_plus_reloc_size=%u exepack_stub_size=%u\n", exehdr[EXE_IP], exepack_max_size, exepack_stub_plus_reloc_size, exepack_stub_size); /* Fix A20 bug (failure as ``Packed file is corrupt'' because ES * wraps around 0x10000) by replacing the stub. */ @@ -1441,6 +1860,57 @@ static char *load_dos_executable_program(int img_fd, const char *filename, void return psp; } +/* Implements int 21h/4Bh AL=03 (load overlay): load image to load_para, apply + * relocations using reloc_para, and return to caller without executing. + */ +static int load_dos_overlay_program(int img_fd, const char *filename, void *mem, const char *header, int header_size, unsigned short load_para, unsigned short reloc_para) { + if (header_size >= 24 && (('M' | 'Z' << 8) == *(const unsigned short*)header || ('M' << 8 | 'Z') == *(const unsigned short*)header)) { + const unsigned short * const exehdr = (const unsigned short*)header; + const unsigned short nblocks = exehdr[EXE_NBLOCKS] & 0x7ff; + const unsigned exesize = exehdr[EXE_LASTSIZE] ? ((nblocks - 1) << 9) + exehdr[EXE_LASTSIZE] : nblocks << 9; + const unsigned headsize = (unsigned)exehdr[EXE_HDRSIZE] << 4; + const unsigned image_size = exesize - headsize; + const unsigned image_linear = (unsigned)load_para << 4; + unsigned reloc_count = exehdr[EXE_NRELOC]; + if (exehdr[EXE_LASTSIZE] > 0x200 || exesize <= headsize || image_linear + image_size > DOS_MEM_LIMIT) { + errno = ENOMEM; + return -1; + } + if ((unsigned)lseek(img_fd, headsize, SEEK_SET) != headsize) return -1; + if ((unsigned)read(img_fd, (char*)mem + image_linear, image_size) != image_size) return -1; + if (reloc_count) { + unsigned short reloc[1024]; + if (header_size < 26) { errno = EINVAL; return -1; } + if ((unsigned)lseek(img_fd, exehdr[EXE_RELOCPOS], SEEK_SET) != exehdr[EXE_RELOCPOS]) return -1; + while (reloc_count != 0) { + const unsigned to_read = reloc_count > (sizeof(reloc) >> 2) ? sizeof(reloc) : reloc_count << 2; + const unsigned got = read(img_fd, reloc, to_read); + unsigned short *r, *rend; + if (got != to_read) return -1; + reloc_count -= got >> 2; + for (r = reloc, rend = r + (got >> 1); r != rend; r += 2) { + const unsigned linear = image_linear + ((unsigned)r[1] << 4) + r[0]; + if (linear + 2 > DOS_MEM_LIMIT) { errno = EINVAL; return -1; } + *(unsigned short*)((char*)mem + linear) += reloc_para; + } + } + } + } else { + const unsigned image_linear = (unsigned)load_para << 4; + struct stat st; + int got; + if (fstat(img_fd, &st) != 0 || !S_ISREG(st.st_mode) || st.st_size < 0 || (unsigned long)st.st_size > (unsigned long)(DOS_MEM_LIMIT - image_linear)) { + errno = ENOMEM; + return -1; + } + if ((unsigned)lseek(img_fd, 0, SEEK_SET) != 0) return -1; + got = read(img_fd, (char*)mem + image_linear, st.st_size); + if (got != st.st_size) return -1; + } + (void)filename; + return 0; +} + static void dump_regs(const char *prefix, const struct kvm_regs *regs, const struct kvm_sregs *sregs) { #define R16(name) (*(unsigned short*)®s->r##name) #define S16(name) (sregs->name.selector) /* 16 bits. */ @@ -1677,13 +2147,15 @@ static char set_int(unsigned char int_num, unsigned value_seg_ofs, void *mem, ch ((had_get_ints & 1) && (int_num == 0x00 || int_num == 0x02 || int_num - 0x35 + 0U <= 0x3f - 0x35 + 0U)) /* Microsoft BASIC Professional Development System 7.10 compiler pbc.exe. */ || ((had_get_ints & 8) && int_num - 0x34 + 0U <= 0x3d - 0x34 + 0U) /* Microsoft Macro Assembler 1.10 masm.exe */ || ((had_get_ints & 0x10) && (int_num == 0x02 || int_num == 0x1b || int_num == 0x00)) || /* JWasm 2.11a jwasmr.exe */ + int_num >= 0xc0 || /* Toolchain-private vectors (e.g. Intel iC-86 v4.5 IC86.EXE). */ int_num == 0x06 || /* ASM32 1.1 assembler asm32.exe */ 0) { /* FYI kvikdos never sends Ctrl-. */ } else { - fprintf(stderr, "fatal: unsupported set interrupt vector int:%02x to cs:%04x ip:%04x\n", - int_num, (unsigned short)(value_seg_ofs >> 16), (unsigned short)value_seg_ofs); - return 1; + if (DEBUG || DEBUG_INTVEC) { + fprintf(stderr, "debug: permissive set interrupt vector int:%02x to cs:%04x ip:%04x\n", + int_num, (unsigned short)(value_seg_ofs >> 16), (unsigned short)value_seg_ofs); + } } *p = value_seg_ofs; return 0; /* Success. */ @@ -1719,6 +2191,56 @@ static const char *get_linux_basename(const char *fn) { return fnp; } +static char upper_ascii(char c) { + return (c - 'a' + 0U <= 'z' - 'a' + 0U) ? c - 32 : c; +} + +static char has_dos_ext_nocase(const char *fn, const char *ext) { + const char *dot = strrchr(fn, '.'); + size_t n; + if (!dot) return 0; + n = strlen(ext); + if (strlen(dot) != n) return 0; + return is_same_ascii_nocase(dot, ext, n); +} + +static time_t dos_datetime_to_time(unsigned short dos_date, unsigned short dos_time) { + struct tm tm; + memset(&tm, 0, sizeof(tm)); + tm.tm_sec = (dos_time & 31) << 1; + tm.tm_min = (dos_time >> 5) & 63; + tm.tm_hour = (dos_time >> 11) & 31; + tm.tm_mday = dos_date & 31; + tm.tm_mon = ((dos_date >> 5) & 15) - 1; + tm.tm_year = ((dos_date >> 9) & 127) + 80; /* Since 1900. */ + tm.tm_isdst = -1; + if (tm.tm_mon < 0) tm.tm_mon = 0; + if (tm.tm_mday < 1) tm.tm_mday = 1; + return mktime(&tm); +} + +/* Case-insensitive DOS wildcard match for a single 8.3 basename. */ +static char dos_wildcard_match(const char *pattern, const char *name) { + while (*pattern == '*') ++pattern; + if (*pattern == '\0') return 1; + for (;; ++name) { + const char pc = upper_ascii(*pattern); + const char nc = upper_ascii(*name); + if (pc == '*') { + do ++pattern; while (*pattern == '*'); + if (*pattern == '\0') return 1; + for (;; ++name) { + if (*name == '\0') return 0; + if (dos_wildcard_match(pattern, name)) return 1; + } + } + if (pc == '\0') return nc == '\0'; + if (nc == '\0') return 0; + if (pc != '?' && pc != nc) return 0; + ++pattern; + } +} + /* Returns bool indicating whether all components of the specified filename (as a DOS pathname, maybe absolute) are limited to DOS 8.3 characters. */ static char is_dos_filename_83(const char *fn) { unsigned u; @@ -1755,42 +2277,6 @@ static const unsigned char scancodes[128] = { static const unsigned short fake_keys[3] = { 0x011b /* */, 0x4400 /* */, 0x1c0d /* */ }; -/* It's unclear whether running the new program and discarding the current - * one is the right approach in the general case (especially with al == 3). - * So we just whitelist a few programs where we do that. - */ -static char should_skip_exec_program(char const *dos_filename, const char *args, const char *env, const char **env_end_inout, char had_get_first_mcb) { - size_t dos_filename_size; - char had_ml_env = 0; - const char *p, *env_end = *env_end_inout; - /* Detect Microsoft Macro Assembler 6.00B driver masm.exe. */ - dos_filename_size = strlen(dos_filename); - for (p = dos_filename + dos_filename_size; p != dos_filename && p[-1] != '\\'; --p) {} - if (strcmp(p, "ML.EXE") == 0) { - if (!had_get_first_mcb || !args || *args != '\0') return 1; - for (p = env; *p != '\0';) { - char *q = memchr(p, '\0', env_end - p); - if (!q) return 3; /* env too long. */ - if (DEBUG) fprintf(stderr, "debug: load env line: (%s)\n", p); - if (strncmp(p, "ML= ", 4) == 0) had_ml_env = 1; - p = q + 1; - } - if (!had_ml_env) return 4; - if (++p + 4 > env_end) return 5; - if (*(const unsigned short*)p != 1) return 6; - p += 2; - if (p + dos_filename_size >= env_end) return 7; - if (strcmp(p, dos_filename) != 0) return 8; - *env_end_inout = p - 2; /* Not: p + dos_filename_size + 1; */ - return 0; /* exec() it. */ - } else if (strcmp(p, "tlink.exe") == 0) { /* Borland C++ 2.0 compiler bcc.exe executing TLINK 4.0 linker tlink.exe */ - if (!args || strcmp(args, "@turboc.$ln") != 0) return 11; - return -1; /* exec() it, but delete file "turboc.$ln" later. */ - } else { - return 2; - } -} - typedef struct TtyState { int tty_in_fd; char is_tty_in_error; @@ -1805,6 +2291,82 @@ typedef struct EmuState { void *mem; } EmuState; +static int run_dos_child_subprocess(const char *dos_filename, const char *dos_args, const char *env, const char *env_end, const DirState *dir_state, unsigned char *exit_code_out) { + char self_exe[LINUX_PATH_SIZE]; + char drive_arg[16]; + char mount_arg[LINUX_PATH_SIZE + 32]; + char *argv_child[256]; + char *owned_args[200]; + int argc = 0, owned_count = 0, i, status; + pid_t pid; + ssize_t got; + const char *p; + + if (!dos_filename || !*dos_filename || !exit_code_out) { errno = EINVAL; return -1; } + got = readlink("/proc/self/exe", self_exe, sizeof(self_exe) - 1); + if (got <= 0 || got >= (ssize_t)sizeof(self_exe) - 1) { + errno = ENOENT; + return -1; + } + self_exe[got] = '\0'; + + argv_child[argc++] = self_exe; + for (i = 0; i < DRIVE_COUNT; ++i) { + const char *mount = dir_state->linux_mount_dir[i]; + const char case_c = dir_state->case_mode[i] == CASE_MODE_LOWERCASE ? '-' : ':'; + if (!mount) continue; + if (*mount == '\0') snprintf(mount_arg, sizeof(mount_arg), "--mount=%c%c", 'A' + i, case_c); + else snprintf(mount_arg, sizeof(mount_arg), "--mount=%c%c%s", 'A' + i, case_c, mount); + owned_args[owned_count] = xstrdup(mount_arg); + if (!owned_args[owned_count]) goto alloc_fail; + argv_child[argc++] = owned_args[owned_count++]; + } + snprintf(drive_arg, sizeof(drive_arg), "--drive=%c:", dir_state->drive); + owned_args[owned_count] = xstrdup(drive_arg); + if (!owned_args[owned_count]) goto alloc_fail; + argv_child[argc++] = owned_args[owned_count++]; + + if (env && env_end && env < env_end) { + for (p = env; p < env_end && *p != '\0';) { + const char *q = memchr(p, '\0', env_end - p); + size_t n; + char *ea; + if (!q) break; + n = (size_t)(q - p); + ea = (char*)malloc(n + 7); + if (!ea) goto alloc_fail; + memcpy(ea, "--env=", 6); + memcpy(ea + 6, p, n); + ea[n + 6] = '\0'; + owned_args[owned_count++] = ea; + argv_child[argc++] = ea; + p = q + 1; + } + } + + argv_child[argc++] = (char*)dos_filename; + if (dos_args && *dos_args) argv_child[argc++] = (char*)dos_args; + argv_child[argc] = NULL; + + pid = fork(); + if (pid < 0) goto child_fail; + if (pid == 0) { + execv(self_exe, argv_child); + _exit(127); + } + if (waitpid(pid, &status, 0) < 0) goto child_fail; + if (WIFEXITED(status)) *exit_code_out = (unsigned char)WEXITSTATUS(status); + else *exit_code_out = 252; + for (i = 0; i < owned_count; ++i) free(owned_args[i]); + return 0; + + alloc_fail: + errno = ENOMEM; + child_fail: + for (i = 0; i < owned_count; ++i) free(owned_args[i]); + return -1; +} + /* It's a cheap call, the real initialization is done in reset_emu. */ static void init_emu(struct EmuState *emu) { emu->kvm_fds.kvm_fd = -1; @@ -2004,13 +2566,237 @@ static int open_dos_file(const char *dos_filename, const char *dos_prog_abs, int } goto after_open; } - if ((fd = open(linux_filename, flags, 0644)) < 0) return -1; + if ((fd = open_with_case_fallback(linux_filename, flags, 0644)) < 0) { + if (flags3 == O_RDONLY && dos_prog_abs && dos_prog_abs[0] && + strchr(dos_filename, ':') == NULL && strchr(dos_filename, '\\') == NULL && strchr(dos_filename, '/') == NULL) { + /* Fallback: resolve bare filename in program directory as well. + * Needed by tools that expect argv-relative files next to the executable. + */ + const char *base = dos_prog_abs + strlen(dos_prog_abs); + char ovl_dos[260]; + size_t dir_size; + for (; base != dos_prog_abs + 3 && base[-1] != '\\' && base[-1] != '/'; --base) {} + dir_size = (size_t)(base - dos_prog_abs); + if (dir_size > 3 && dir_size + strlen(dos_filename) + 1 < sizeof(ovl_dos)) { + memcpy(ovl_dos, dos_prog_abs, dir_size); + strcpy(ovl_dos + dir_size, dos_filename); + dir_state->dos_prog_abs = dos_prog_abs; + linux_filename = get_linux_filename_r(ovl_dos, dir_state, fnbuf2, &linux_lastc); + dir_state->dos_prog_abs = NULL; + if ((fd = open_with_case_fallback(linux_filename, flags, 0644)) >= 0) goto after_open; + } + } + return -1; + } after_open: return fd; } static char exec_fnbuf[LINUX_PATH_SIZE]; /* Used temporarily by run_dos_prog. */ +enum mz_subformat_t { MZ_SUBFMT_NONE = 0, MZ_SUBFMT_PE, MZ_SUBFMT_NE, MZ_SUBFMT_LE, MZ_SUBFMT_LX }; + +static enum mz_subformat_t detect_mz_subformat(const char *path) { + int fd; + unsigned char mz[64]; + unsigned char sig4[4]; + unsigned char sig2[2]; + unsigned long off; + ssize_t got; + if (!path) return MZ_SUBFMT_NONE; + fd = open(path, O_RDONLY); + if (fd < 0) return MZ_SUBFMT_NONE; + got = read(fd, mz, sizeof(mz)); + if (got < 0 || got < 0x40 || mz[0] != 'M' || mz[1] != 'Z') { close(fd); return MZ_SUBFMT_NONE; } + off = (unsigned long)mz[0x3c] | ((unsigned long)mz[0x3d] << 8) | ((unsigned long)mz[0x3e] << 16) | ((unsigned long)mz[0x3f] << 24); + if ((long)off < 0 || lseek(fd, (off_t)off, SEEK_SET) < 0) { close(fd); return MZ_SUBFMT_NONE; } + got = read(fd, sig4, sizeof(sig4)); + if (got == 4 && sig4[0] == 'P' && sig4[1] == 'E' && sig4[2] == 0 && sig4[3] == 0) { close(fd); return MZ_SUBFMT_PE; } + if (lseek(fd, (off_t)off, SEEK_SET) < 0) { close(fd); return MZ_SUBFMT_NONE; } + got = read(fd, sig2, sizeof(sig2)); + close(fd); + if (got == 2 && sig2[0] == 'N' && sig2[1] == 'E') return MZ_SUBFMT_NE; + if (got == 2 && sig2[0] == 'L' && sig2[1] == 'E') return MZ_SUBFMT_LE; + if (got == 2 && sig2[0] == 'L' && sig2[1] == 'X') return MZ_SUBFMT_LX; + return MZ_SUBFMT_NONE; +} + +static int file_contains_text(const char *path, const char *needle) { + int fd; + struct stat st; + char *buf; + size_t size, nlen; + ssize_t got; + int found = 0; + fd = open(path, O_RDONLY); + if (fd < 0) return 0; + if (fstat(fd, &st) != 0 || st.st_size <= 0) { close(fd); return 0; } + size = st.st_size > (1 << 20) ? (1 << 20) : (size_t)st.st_size; /* Scan up to 1 MiB. */ + nlen = strlen(needle); + if (nlen == 0 || nlen > size) { close(fd); return 0; } + buf = (char*)malloc(size); + if (!buf) { close(fd); return 0; } + got = read(fd, buf, size); + close(fd); + if (got > 0 && (size_t)got >= nlen && memmem(buf, (size_t)got, needle, nlen) != NULL) found = 1; + free(buf); + return found; +} + +static int is_probable_borland_dual_mode_ne(const char *path) { + return file_contains_text(path, "DPMI error (") && + (file_contains_text(path, "TLINK") || file_contains_text(path, "RTM")); +} + +/* Heuristic: tiny DOS stubs in Windows NE/LE/LX binaries often only print + * "This program requires ..." and exit. Real dual-mode DOS tools (e.g. some + * linkers) have substantial DOS code and should not be delegated to Wine. + */ +static int is_probable_windows_message_stub(const char *path) { + int fd; + unsigned char h[64]; + unsigned char *buf = NULL; + size_t stub_size, max_scan; + ssize_t got; + int is_stub = 0; + unsigned long e_lfanew, e_cparhdr; + + fd = open(path, O_RDONLY); + if (fd < 0) return 0; + got = read(fd, h, sizeof(h)); + if (got < (ssize_t)sizeof(h) || h[0] != 'M' || h[1] != 'Z') goto done; + + e_lfanew = (unsigned long)h[0x3c] | ((unsigned long)h[0x3d] << 8) | + ((unsigned long)h[0x3e] << 16) | ((unsigned long)h[0x3f] << 24); + e_cparhdr = (unsigned long)h[0x08] | ((unsigned long)h[0x09] << 8); + if (e_lfanew < 0x40) goto done; + if (e_lfanew <= (e_cparhdr << 4)) goto done; + stub_size = (size_t)(e_lfanew - (e_cparhdr << 4)); + if (stub_size == 0) goto done; + + /* Most message stubs are tiny; anything larger is likely meaningful DOS. */ + if (stub_size > 1024) goto done; + + max_scan = stub_size > 4096 ? 4096 : stub_size; + buf = (unsigned char*)malloc(max_scan); + if (!buf) goto done; + if (lseek(fd, (off_t)(e_cparhdr << 4), SEEK_SET) < 0) goto done; + got = read(fd, buf, max_scan); + if (got <= 0) goto done; + + if (memmem(buf, (size_t)got, "This program", 12) || + memmem(buf, (size_t)got, "requires Microsoft", 18) || + memmem(buf, (size_t)got, "cannot be run in DOS mode", 25)) { + is_stub = 1; + } + + done: + if (buf) free(buf); + close(fd); + return is_stub; +} + +static int run_with_wine(const char *prog_filename, const char *const *args, const char *linux_cwd) { + const char *argv_child[512]; + unsigned argc = 0; + int status; + pid_t pid; + argv_child[argc++] = "wine"; + argv_child[argc++] = prog_filename; + if (args) { + while (*args && argc + 1 < (sizeof(argv_child) / sizeof(argv_child[0]))) argv_child[argc++] = *args++; + } + argv_child[argc] = NULL; + pid = fork(); + if (pid < 0) { + perror("error: fork for wine failed"); + return 1; + } + if (pid == 0) { + if (linux_cwd && *linux_cwd && chdir(linux_cwd) != 0) { + perror("error: failed to chdir for wine"); + _exit(127); + } + execvp("wine", (char * const*)argv_child); + perror("error: failed to execute wine"); + _exit(127); + } + if (waitpid(pid, &status, 0) < 0) { + perror("error: waitpid for wine failed"); + return 1; + } + if (WIFEXITED(status)) return WEXITSTATUS(status); + return 1; +} + +static int has_wine_in_path(void) { + const char *path = getenv("PATH"); + const char *p, *q; + char buf[LINUX_PATH_SIZE]; + size_t n; + if (!path || !*path) return 0; + for (p = path;; p = q + 1) { + q = strchr(p, ':'); + if (!q) q = p + strlen(p); + n = (size_t)(q - p); + if (n == 0) { + if (sizeof(buf) > 5) { + memcpy(buf, "./wine", 7); + if (access(buf, X_OK) == 0) return 1; + } + } else if (n + 1 + 4 + 1 <= sizeof(buf)) { + memcpy(buf, p, n); + buf[n++] = '/'; + memcpy(buf + n, "wine", 5); + if (access(buf, X_OK) == 0) return 1; + } + if (*q == '\0') break; + } + return 0; +} + +static int is_linux_native_executable(const char *path) { + int fd; + unsigned char h[4]; + ssize_t got; + if (!path) return 0; + fd = open(path, O_RDONLY); + if (fd < 0) return 0; + got = read(fd, h, sizeof(h)); + close(fd); + if (got >= 4 && h[0] == 0x7f && h[1] == 'E' && h[2] == 'L' && h[3] == 'F') return 1; + if (got >= 2 && h[0] == '#' && h[1] == '!') return 1; + return 0; +} + +static int run_native_execvp(const char *prog_filename, const char *const *args) { + const char *argv_child[512]; + unsigned argc = 0; + int status; + pid_t pid; + argv_child[argc++] = prog_filename; + if (args) { + while (*args && argc + 1 < (sizeof(argv_child) / sizeof(argv_child[0]))) argv_child[argc++] = *args++; + } + argv_child[argc] = NULL; + pid = fork(); + if (pid < 0) { + perror("error: fork failed"); + return 1; + } + if (pid == 0) { + execvp(prog_filename, (char * const*)argv_child); + perror("error: failed to execute native program"); + _exit(127); + } + if (waitpid(pid, &status, 0) < 0) { + perror("error: waitpid failed"); + return 1; + } + if (WIFEXITED(status)) return WEXITSTATUS(status); + return 1; +} + /* Runs a DOS .com or .exe program in `emu'. Cannot run DOS .bat batch files. * Must be preceded by init_emu(emu). * It calls reset_emu(emu) in the beginning, so DOS programs run in @@ -2021,7 +2807,7 @@ static char exec_fnbuf[LINUX_PATH_SIZE]; /* Used temporarily by run_dos_prog. * * Returns the DOS exit code reported by the program. * As a side effect, sets dir_state->dos_prog_abs = NULL, and may change dir_state and tty_state. */ -static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filename, const char *args_str, const char* const *args, DirState *dir_state, TtyState *tty_state, const EmuParams *emu_params, const char* const *envp0) { +static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filename, const char *args_str, const char* const *args, DirState *dir_state, TtyState *tty_state, const EmuParams *emu_params, const char* const *envp0, const char* const *extra_env, unsigned extra_env_count) { int img_fd; struct kvm_fds kvm_fds; void *mem; @@ -2030,7 +2816,7 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam struct kvm_sregs sregs; char header[PROGRAM_HEADER_SIZE]; unsigned header_size; - char had_get_ints, had_get_first_mcb; + char had_get_ints; unsigned char tasm30_bitset; const char *dos_prog_abs; /* Owned externally: either in args or in dosfnbuf or (after exec) within mem. */ unsigned tick_count; @@ -2045,9 +2831,26 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam const char *stdout_write_p; const char *stdout_write_end; char is_stdout_write_cursor; + char dpmi_warned; + enum { XMS_HANDLE_COUNT = 64 }; + void *xms_blocks[XMS_HANDLE_COUNT]; + unsigned long xms_block_sizes[XMS_HANDLE_COUNT]; /* Bytes. */ + unsigned short xms_lock_counts[XMS_HANDLE_COUNT]; + unsigned short xms_free_kb, xms_total_kb; + char umb_link_state; + enum { EMS_HANDLE_COUNT = 64 }; + unsigned short ems_pages_by_handle[EMS_HANDLE_COUNT]; + unsigned short ems_page_map[4]; + unsigned short ems_free_pages, ems_total_pages; enum malloc_strategy_t { MS_FIRST_FIT = 0, MS_BEST_FIT = 1, MS_LAST_FIT = 2 }; unsigned malloc_strategy; char cleanup_fn[16]; + DIR *find_dirp; + char find_linux_dir[LINUX_PATH_SIZE]; + char find_dos_pattern[13]; + unsigned short find_attrs; + unsigned char last_exec_return_code; + unsigned hlt_spin_count; { struct SA { int StaticAssert_AllocParaLimits : DOS_ALLOC_PARA_LIMIT <= (DOS_MEM_LIMIT >> 4); }; } { struct SA { int StaticAssert_CountryInfoSize : sizeof(country_info) == 0x18; }; } @@ -2056,7 +2859,7 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam if (!prog_filename) { img_fd = -1; - } else if ((img_fd = open(prog_filename, O_RDONLY)) < 0) { + } else if ((img_fd = open_with_case_fallback(prog_filename, O_RDONLY, 0666)) < 0) { fprintf(stderr, "fatal: cannot open DOS executable program: %s: %s\n", prog_filename, strerror(errno)); exit(252); } @@ -2068,7 +2871,22 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam video_byte_written = 0; /* Pacify uninitialized warnings. */ stdout_write_p = NULL; /* Pacify uninitialized warnings. */ stdout_write_end = NULL; /* Pacify uninitialized warnings. */ + dpmi_warned = 0; + memset(xms_blocks, 0, sizeof(xms_blocks)); + memset(xms_block_sizes, 0, sizeof(xms_block_sizes)); + memset(xms_lock_counts, 0, sizeof(xms_lock_counts)); + xms_total_kb = xms_free_kb = 16 * 1024; /* Minimal practical XMS pool for toolchains. */ + umb_link_state = 0; + memset(ems_pages_by_handle, 0, sizeof(ems_pages_by_handle)); + ems_page_map[0] = ems_page_map[1] = ems_page_map[2] = ems_page_map[3] = 0xffff; + ems_total_pages = ems_free_pages = 256; /* 4 MiB EMS in 16 KiB pages. */ cleanup_fn[0] = '\0'; + find_dirp = NULL; + find_linux_dir[0] = '\0'; + find_dos_pattern[0] = '\0'; + find_attrs = 0; + last_exec_return_code = 0; + hlt_spin_count = 0; do_exec: header_size = detect_dos_executable_program(img_fd, prog_filename, header); @@ -2084,6 +2902,9 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam { unsigned u; for (u = 0; u < 0x100; ++u) { ((unsigned*)mem)[u] = MAGIC_INT_VALUE(u); } memset((char*)mem + (INT_HLT_PARA << 4), 0xf4, 0x100); /* 256 hlt instructions, one for each int. TODO(pts): Is hlt+iret faster? */ + ((unsigned char*)mem)[(INT_HLT_PARA << 4) + 0x200] = 0xcd; /* int 0x43 */ + ((unsigned char*)mem)[(INT_HLT_PARA << 4) + 0x201] = 0x43; + ((unsigned char*)mem)[(INT_HLT_PARA << 4) + 0x202] = 0xcb; /* retf */ } /* !! Initialize more BIOS data area until 0x534, move magic interrupt table later. * https://stanislavs.org/helppc/bios_data_area.html @@ -2132,11 +2953,30 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam env = add_env(env, env_end, "heLLo=World!", 1); #endif while (*envp0) { - if (strncmp(*envp0, "PATH=", 5) == 0) do_set_dos_path = 0; + const char *host_var = *envp0++; + const char *eq; + if (!strchr(host_var, '=')) { + if (DEBUG) fprintf(stderr, "debug: skipping malformed host env var without '=': %s\n", host_var); + continue; + } + eq = strchr(host_var, '='); + if (eq && eq - host_var == 4 && strncmp(host_var, "PATH", 4) == 0) { + const char *v = eq + 1; + /* Don't import Unix PATH as DOS PATH. Keep DOS PATH auto-generated + * from executable directory unless user provided a DOS-style PATH. + */ + if (strchr(v, '/')) continue; + } + if (strncmp(host_var, "PATH=", 5) == 0) do_set_dos_path = 0; /* No attempt is made to deduplicate environment variables by name. * The user should supply unique names. */ - env = add_env(env, env_end, *envp0++, 1); + env = add_env(env, env_end, host_var, 1); + } + { unsigned ei; + for (ei = 0; ei < extra_env_count; ++ei) { + if (extra_env[ei]) env = add_env(env, env_end, extra_env[ei], 1); + } } if (do_set_dos_path) { /* Set %PATH% to the directory of dos_prog_abs. Set once. */ size_t size; @@ -2184,7 +3024,6 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam had_get_ints = 0; /* 1 << 0: int 0x00; 1 << 1: int 0x18; 1 << 2: int 0x06, 1 << 3: Get DOS version, 1 << 4: 0x34. */ tasm30_bitset = 0; - had_get_first_mcb = 0; tick_count = 0; sphinx_cmm_flags = 0; ctrl_break_checking = 0; @@ -2227,12 +3066,17 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } if (DEBUG) dump_regs("debug", ®s, &sregs); + if (run->exit_reason != KVM_EXIT_HLT) hlt_spin_count = 0; switch (run->exit_reason) { case KVM_EXIT_IO: { char *p = (char*)run + run->io.data_offset; if (run->io.port == 0x40 && run->io.size == 1 && run->io.direction == 0) { *p = port_0x40_tick++; /* Simulate some timer ticks. */ break; + } else if (!emu_params->strict_mode) { + if (run->io.direction == 0) memset(p, 0, run->io.size * run->io.count); /* IN: return 0. */ + /* OUT: ignore in permissive mode. */ + break; } else { fprintf(stderr, "fatal: IO port: port=0x%02x data=%08x size=%d direction=%s\n", run->io.port, *(const unsigned*)p, run->io.size, run->io.direction ? "out" : "in"); goto fatal; @@ -2275,11 +3119,14 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } else if (int_num == 0x20) { *(unsigned char*)®s.rax = 0; /* EXIT_SUCCESS. */ goto do_exit; + } else if (int_num == 0x22) { /* Termination handler vector. */ + /* Keep execution flow by returning from the interrupt. */ } else if (int_num == 0x21) { /* DOS file and memory sevices. */ /* !! Should we set CF=0 by default? What does MS-DOS do? */ if (ah == 0x4c) { /* Exit to DOS. */ if (cleanup_fn[0] != '\0') unlink(get_linux_filename(cleanup_fn)); do_exit: + if (find_dirp) { closedir(find_dirp); find_dirp = NULL; } return (unsigned char)regs.rax; } else if (ah == 0x06) { /* Direct console I/O. */ func_0x06: @@ -2466,9 +3313,11 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } goto after_open; } - if ((fd = open(linux_filename, flags, 0644)) < 0) { error_from_linux: + if ((fd = open_with_case_fallback(linux_filename, flags, 0644)) < 0) { + if (fd < 0) { error_from_linux: *(unsigned short*)®s.rax = get_dos_error_code(errno, 0x1f); /* By default: General failure. */ goto error_on_21; + } } /*dup2(fd, 20); close(fd); fd = 20;*/ /* This breaks .exe files created by `owcc -bdos', which allows fd < 20. We fix it with map_fd_open(...) below. */ after_open: @@ -2481,6 +3330,54 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam if (DEBUG) fprintf(stderr, "debug: dos_open(%s) dos_fd=%d\n", p, fd); *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ *(unsigned short*)®s.rax = fd; + } else if (ah == 0x6c) { /* Extended open/create (DOS 4.0+). */ + const char * const p = (char*)mem + ((unsigned)sregs.ds.selector << 4) + (*(unsigned short*)®s.rsi); /* DS:SI filename. */ + const unsigned short bx = *(unsigned short*)®s.rbx; /* Open mode. */ + const unsigned short dx = *(unsigned short*)®s.rdx; /* Action flags in low nibble. */ + const unsigned action = dx & 0x0f; + const int flags3 = bx & 3; /* O_RDONLY/O_WRONLY/O_RDWR mapping. */ + int flags = flags3, fd; + struct stat st; + const char *linux_filename; + char *linux_lastc; + int exists; + int action_taken = 0; /* 1=open existing, 2=create new. */ + dir_state->dos_prog_abs = flags3 == O_RDONLY ? dos_prog_abs : NULL; + linux_filename = get_linux_filename_r(p, dir_state, fnbuf, &linux_lastc); + dir_state->dos_prog_abs = NULL; + if (is_same_ascii_nocase(linux_lastc, "nul", 3) && (linux_lastc[3] == '.' || linux_lastc[3] == '\0')) strcpy(fnbuf, "/dev/null"); + exists = stat(linux_filename, &st) == 0; + if (!exists && errno == ENOENT && g_case_fallback_mode == 2 && linux_filename[0] && + resolve_case_fallback_path(linux_filename, fnbuf2, sizeof(fnbuf2), 0) && + stat(fnbuf2, &st) == 0) { + linux_filename = fnbuf2; + exists = 1; + } + if (action == 2) { /* Create new, fail if exists. */ + if (exists) { *(unsigned short*)®s.rax = 0x50; goto error_on_21; } /* File exists. */ + flags |= O_CREAT | O_EXCL; + action_taken = 2; + } else if (action == 1) { /* Open existing, fail if not exists. */ + if (!exists) { *(unsigned short*)®s.rax = 2; goto error_on_21; } /* File not found. */ + action_taken = 1; + } else if (action == 3) { /* Open if exists else create. */ + if (exists) action_taken = 1; + else { flags |= O_CREAT; action_taken = 2; } + } else { /* Default/probe mode: open existing, fail if missing. */ + if (!exists) { *(unsigned short*)®s.rax = 2; goto error_on_21; } + action_taken = 1; + } + fd = open_with_case_fallback(linux_filename, flags, 0644); + if (fd < 0) goto error_from_linux; + if (fd < 5) fd = ensure_fd_is_at_least(fd, 5); + fd = map_fd_open(fd); + if ((fd + 0U) >> 16) { + *(unsigned short*)®s.rax = 4; /* Too many open files. */ + goto error_on_21; + } + *(unsigned short*)®s.rax = fd; + *(unsigned short*)®s.rcx = action_taken; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ } else if (ah == 0x57) { /* Get/set file date and time using handle. */ const unsigned char al = (unsigned char)regs.rax; if (al < 2) { @@ -2495,9 +3392,11 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam *(unsigned short*)®s.rdx = tm->tm_mday | (tm->tm_mon + 1) << 5 | (tm->tm_year - 1980) << 9; tasm30_bitset |= 0x80; } else { /* Set if al == 1. */ - /* !! Implement this with utime(2). */ - fprintf(stderr, "fatal: unimplemented: set file date and time: fd=%d cx:%04x dx:%04x\n", fd, *(unsigned short*)®s.rcx, *(unsigned short*)®s.rdx); - goto fatal; + const time_t ts = dos_datetime_to_time(*(unsigned short*)®s.rdx, *(unsigned short*)®s.rcx); + struct timespec tv[2]; + tv[0].tv_sec = ts; tv[0].tv_nsec = 0; + tv[1].tv_sec = ts; tv[1].tv_nsec = 0; + if (futimens(fd, tv) != 0) goto error_from_linux; } } else { error_invalid_parameter: *(unsigned short*)®s.rax = 0x57; /* Invalid parameter. */ @@ -2529,6 +3428,34 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ *(unsigned short*)®s.rax = fd2; + } else if (ah == 0x46) { /* Force duplicate handle (dup2()). */ + const unsigned short src_handle = *(unsigned short*)®s.rbx; + const unsigned short dst_handle = *(unsigned short*)®s.rcx; + const int src_fd = get_linux_fd(src_handle, &kvm_fds); + if (src_fd < 0) goto error_invalid_handle; + if (src_handle != dst_handle) { + if (dst_handle < 5) { + const int dst_fd = get_linux_fd(dst_handle, &kvm_fds); + if (dst_fd < 0 || dup2(src_fd, dst_fd) != dst_fd) goto error_from_linux; + } else if (dst_handle < 5 + sizeof(mapped_handles) / sizeof(mapped_handles[0])) { + const unsigned dst_idx = dst_handle - 5; + const int old_fd = mapped_handles[dst_idx]; + int new_fd = dup(src_fd); + if (new_fd < 0) { + *(unsigned short*)®s.rax = get_dos_error_code(errno, 4); /* By default: Too many open files. */ + goto error_on_21; + } + if (new_fd < 5) new_fd = ensure_fd_is_at_least(new_fd, 5); + if (old_fd > 0) close(old_fd); + mapped_handles[dst_idx] = new_fd; + } else { + const int dst_fd = (int)(dst_handle - (5 + sizeof(mapped_handles) / sizeof(mapped_handles[0]))); + if (dst_fd == kvm_fds.kvm_fd || dst_fd == kvm_fds.vm_fd || dst_fd == kvm_fds.vcpu_fd) goto error_invalid_handle; + if (dup2(src_fd, dst_fd) != dst_fd) goto error_from_linux; + } + } + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + *(unsigned short*)®s.rax = dst_handle; } else if (ah == 0x39) { /* Create subdirectory (mkdir). */ const char * const p = (char*)mem + ((unsigned)sregs.ds.selector << 4) + (*(unsigned short*)®s.rdx); /* !! Security: check bounds. */ const int result = mkdir(get_linux_filename(p), 0755); @@ -2550,6 +3477,19 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam int fd = rename(get_linux_filename(p_old), get_linux_filename_r(p_new, dir_state, fnbuf2, NULL)); if (fd < 0) goto error_from_linux; *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + } else if (ah == 0x5b) { /* Create new file (fails if exists). */ + const char * const p = (char*)mem + ((unsigned)sregs.ds.selector << 4) + (*(unsigned short*)®s.rdx); /* !! Security: check bounds. */ + const int fd = open(get_linux_filename(p), O_RDWR | O_CREAT | O_EXCL, 0644); + int dos_fd; + if (fd < 0) goto error_from_linux; + dos_fd = fd < 5 ? ensure_fd_is_at_least(fd, 5) : fd; + dos_fd = map_fd_open(dos_fd); + if ((dos_fd + 0U) >> 16) { + *(unsigned short*)®s.rax = 4; /* Too many open files. */ + goto error_on_21; + } + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + *(unsigned short*)®s.rax = dos_fd; } else if (ah == 0x25) { /* Set interrupt vector. */ if (set_int((unsigned char)regs.rax, *(unsigned short*)®s.rdx | sregs.ds.selector << 16, mem, had_get_ints, &tasm30_bitset)) goto fatal; } else if (ah == 0x35) { /* Get interrupt vector. */ @@ -2573,9 +3513,18 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam (*(unsigned short*)®s.rbx) = pp[0]; SET_SREG(es, pp[1]); } else { - fprintf(stderr, "fatal: unsupported get interrupt vector int:%02x\n", get_int_num); - goto fatal; + const unsigned short *pp = (const unsigned short*)((char*)mem + (get_int_num << 2)); + if (DEBUG) fprintf(stderr, "debug: permissive get interrupt vector int:%02x is cs:%04x ip:%04x\n", get_int_num, pp[1], pp[0]); + (*(unsigned short*)®s.rbx) = pp[0]; + SET_SREG(es, pp[1]); } + } else if (ah == 0x34) { /* Get InDOS flag address. */ + ((char*)mem)[0x041a] = 0; /* InDOS = 0 (DOS idle). */ + SET_SREG(es, 0); + *(unsigned short*)®s.rbx = 0x041a; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + } else if (ah == 0x1f || ah == 0x32) { /* DPB probes. */ + goto nonfatal_unknown_int_21_call; } else if (ah == 0x0b) { /* Check input status. */ *(unsigned char*)®s.rax = 0; /* No input ready. 0xff would be input. */ /* If we detect Ctrl-, we should run `int 0x23'. */ @@ -2598,6 +3547,7 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } } else if (ah == 0x44) { /* I/O control (ioctl). */ const unsigned char al = (unsigned char)regs.rax; + char ioctl_ok = 1; if (al == 1 && (*(unsigned short*)®s.rdx >> 8)) goto error_invalid_parameter; if (al < 2) { /* Get device information (1), set device information (2). */ const int fd = get_linux_fd(*(unsigned short*)®s.rbx, &kvm_fds); @@ -2626,6 +3576,23 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam const int fd = get_linux_fd(*(unsigned short*)®s.rbx, &kvm_fds); if (fd < 0) goto error_invalid_handle; *(unsigned short*)®s.rdx = 0; /* Drive is local. */ + } else if (al == 0x06) { /* Get input status. */ + const int fd = get_linux_fd(*(unsigned short*)®s.rbx, &kvm_fds); + struct stat st; + if (fd < 0) goto error_invalid_handle; + if (fstat(fd, &st) != 0) goto error_from_linux; + if (S_ISREG(st.st_mode)) { + *(unsigned short*)®s.rax = 0xff; /* Regular files are ready. */ + } else { + struct pollfd pfd; + int pr; + pfd.fd = fd; + pfd.events = POLLIN; + pfd.revents = 0; + pr = poll(&pfd, 1, 0); + if (pr < 0) goto error_from_linux; + *(unsigned short*)®s.rax = (pr > 0 && (pfd.revents & (POLLIN | POLLHUP))) ? 0xff : 0x00; + } #if 0 } else if (al == 6) { const int fd = get_linux_fd(*(unsigned short*)®s.rbx, &kvm_fds); @@ -2637,10 +3604,11 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam *(unsigned short*)®s.rax = 0xff; /* Input is ready (0xff). */ #endif } else { - fprintf(stderr, "fatal: unsupported DOS ioctl call: call=0x%02x dos_fd=%d\n", al, *(unsigned short*)®s.rbx); - goto fatal; + if (DEBUG) fprintf(stderr, "debug: unsupported DOS ioctl call ignored: call=0x%02x dos_fd=%d\n", al, *(unsigned short*)®s.rbx); + ioctl_ok = 0; } - *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + if (ioctl_ok) *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + else goto nonfatal_unknown_int_21_call; } else if (ah == 0x4a) { /* Modify allocated memory block (inplace_realloc()). */ const unsigned new_size_para = *(unsigned short*)®s.rbx; const unsigned short block_para = sregs.es.selector; @@ -2894,10 +3862,18 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam struct stat st; if (stat(fn, &st) != 0) goto error_from_linux; *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ - *(unsigned short*)®s.rax = (st.st_mode & 0200) ? 0 : 1; /* Indicate DOS read-only flag if owner doesn't have write permissions on Linux. */ + *(unsigned short*)®s.rax = (st.st_mode & 0200) ? 0 : 1; /* readonly */ } else { /* Set. */ - fprintf(stderr, "fatal: unimplemented: set file attributes: attr=0x%04x filename=%s\n", *(unsigned short*)®s.rcx, fn); - goto fatal; + struct stat st; + mode_t mode; + unsigned short attr = *(unsigned short*)®s.rcx; + if (stat(fn, &st) != 0) goto error_from_linux; + mode = st.st_mode; + /* Accept DOS attribute bits: RO(1), H(2), S(4), A(0x20). */ + if (attr & 1) mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + else mode |= S_IWUSR; + if (chmod(fn, mode) != 0) goto error_from_linux; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ } } else if (ah == 0x33) { /* Get/set system values. */ const unsigned char al = (unsigned char)regs.rax; @@ -2916,8 +3892,8 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam *(unsigned short*)®s.rbx = 0x500; /* DOS 5.0. */ *(unsigned short*)®s.rdx = 0x100; /* DL contains DOS revision number 0. */ } else { - fprintf(stderr, "fatal: unimplemented: get/set system values: al:%04x dl:%04x\n", al, dl); - goto fatal; + if (DEBUG) fprintf(stderr, "debug: unsupported get/set system values subcall: al=%02x dl=%02x\n", al, dl); + goto nonfatal_unknown_int_21_call; } } else if (ah == 0x0e) { /* Select disk. */ /* TODO(pts): Use the default drive specified here (dl + 'A') in get_linux_filename_r(...). */ @@ -2939,8 +3915,8 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam memcpy(p, &country_info, 0x18); *(unsigned short*)®s.rax = *(unsigned short*)®s.rbx = 1; } else { - fprintf(stderr, "fatal: unsupported subcall for country: 0x%02x\n", al); - goto fatal; + if (DEBUG) fprintf(stderr, "debug: unsupported country subcall: al=%02x\n", al); + goto nonfatal_unknown_int_21_call; } *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ } else if (ah == 0x37) { /* Get/set switch character (for command-line flags). */ @@ -2951,14 +3927,18 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } else if (al == 0x02) { /* Get device prefix flag. */ *(unsigned char*)®s.rdx = 0xff; /* Device prefix /dev/... not needed. */ } else { - fprintf(stderr, "fatal: unsupported subcall for switch character: 0x%02x\n", al); - goto fatal; + if (DEBUG) fprintf(stderr, "debug: unsupported switch-character subcall: al=%02x\n", al); + goto nonfatal_unknown_int_21_call; } } else if (ah == 0x4e) { /* Find first matching file (findfirst). */ const unsigned short attrs = *(unsigned short*)®s.rcx; const char * const pattern = (char*)mem + ((unsigned)sregs.ds.selector << 4) + (*(unsigned short*)®s.rdx); /* !! Security: check bounds. */ - const char *fn, *fnb; const unsigned dta_linear = (dta_seg_ofs & 0xffff) + (dta_seg_ofs >> 16 << 4); + const char *dos_pat_base; + size_t dos_pat_dir_size; + char dos_pat_prefix[DOS_PATH_SIZE]; + const char *linux_probe, *linux_dir; + struct dirent *de; if (DEBUG) fprintf(stderr, "debug: findfirst pattern=(%s) attrs=0x%04x\n", pattern, attrs); if (!is_linear_byte_user_writable(dta_linear) || !is_linear_byte_user_writable(dta_linear + 0x2b - 1)) goto error_invalid_parameter; if (attrs & 8) { /* Volume label requested. */ @@ -2966,61 +3946,91 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam *(unsigned short*)®s.rax = 0x12; /* No more files. */ goto error_on_21; } - if (strchr(pattern, '*') || strchr(pattern, '?')) { /* TODO(pts): What happens if there are wildcards in earlier pathname components? */ - fprintf(stderr, "fatal: unsupported wildcards in findfirst pattern: %s\n", pattern); - goto fatal; + if (find_dirp) { closedir(find_dirp); find_dirp = NULL; } + dos_pat_base = get_dos_basename(pattern); + if (!is_dos_filename_83(dos_pat_base)) goto no_more_files; + dos_pat_dir_size = dos_pat_base - pattern; + if (dos_pat_dir_size >= sizeof(dos_pat_prefix) - 2) goto no_more_files; + memcpy(dos_pat_prefix, pattern, dos_pat_dir_size); + dos_pat_prefix[dos_pat_dir_size] = 'A'; /* Probe filename to resolve parent directory. */ + dos_pat_prefix[dos_pat_dir_size + 1] = '\0'; + linux_probe = get_linux_filename(dos_pat_prefix); + if (linux_probe[0] == '\0') goto no_more_files; + linux_dir = get_linux_basename(linux_probe); + memcpy(find_linux_dir, linux_probe, linux_dir - linux_probe); + find_linux_dir[linux_dir - linux_probe] = '\0'; + if (find_linux_dir[0] == '\0') strcpy(find_linux_dir, "."); + find_dirp = opendir(find_linux_dir); + if (!find_dirp) { + if (errno == ENOENT) goto no_more_files; + goto error_from_linux; } - if (!is_dos_filename_83(get_dos_basename(pattern))) goto no_more_files; - fn = get_linux_filename(pattern); - fnb = get_linux_basename(fn); - if (DEBUG) fprintf(stderr, "debug: findfirst fn=(%s) fnb=(%s)\n", fn, fnb); - if (strlen(fnb) > 12) { - goto no_more_files; /* is_dos_filename_83 ensures this, but let's double check for security of the strcpy(...) below. */ - } else { + strncpy(find_dos_pattern, dos_pat_base, sizeof(find_dos_pattern) - 1); + find_dos_pattern[sizeof(find_dos_pattern) - 1] = '\0'; + find_attrs = attrs; + while ((de = readdir(find_dirp)) != NULL) { + char fn[LINUX_PATH_SIZE]; + const char *fnb = de->d_name; char *dta; struct stat st; struct tm *tm; - if (stat(fn, &st) != 0) { - if (errno == ENOENT) goto no_more_files; - goto error_from_linux; - } - if (S_ISDIR(st.st_mode) && !(attrs & 0x10)) goto no_more_files; + if (!is_dos_filename_83(fnb) || !dos_wildcard_match(find_dos_pattern, fnb)) continue; + if (snprintf(fn, sizeof(fn), "%s/%s", find_linux_dir, fnb) <= 0 || strlen(fn) >= sizeof(fn)) continue; + if (stat(fn, &st) != 0) continue; + if (S_ISDIR(st.st_mode) && !(find_attrs & 0x10)) continue; dta = (char*)mem + dta_linear; memset(dta, '\0', 0x16); tm = localtime(&st.st_mtime); - *(unsigned*)dta = FINDFIRST_MAGIC; /* Just a random value which findnext can identify. */ + *(unsigned*)dta = FINDFIRST_MAGIC; *(unsigned short*)(dta + 0x16) = tm->tm_sec >> 1 | tm->tm_min << 5 | tm->tm_hour << 11; *(unsigned short*)(dta + 0x18) = tm->tm_mday | (tm->tm_mon + 1) << 5 | (tm->tm_year - 1980) << 9; *(unsigned*)(dta + 0x1a) = (sizeof(st.st_size) > 4 && st.st_size >> (32 * (sizeof(st.st_size) > 4))) ? - 0xffffffffU : st.st_size + (size_t)0; /* Cap file size at 0xffffffff, no way to return more than 32 bits. */ - { const char *p = fnb; - char *q = dta + 0x1e, c; - do { /* Secure because of the strlen(fnb) check above. */ - c = *p++; - *q++ = c - 'a' + 0U <= 'z' - 'a' + 0U ? c - 32 : c; /* Convert to uppercase. */ - } while (c != '\0'); - /*strcpy(dta + 0x1e, fnb);*/ /* Secure because of the strlen(fnb) check above. */ - /* We use up to 0x1e + 13 == 0x2b bytes in dta. */ - if (DEBUG) fprintf(stderr, "debug: found linux_file=(%s) dos_file=(%s)\n", fnb, dta + 0x1e); + 0xffffffffU : st.st_size + (size_t)0; + { const char *p = fnb; char *q = dta + 0x1e, c; + do { c = *p++; *q++ = upper_ascii(c); } while (c != '\0'); } + *(unsigned short*)®s.rax = 0; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + goto done_findnext; } + closedir(find_dirp); + find_dirp = NULL; + goto no_more_files; + done_findnext:; *(unsigned short*)®s.rax = 0; /* Undocumented, but necessary and used as a success indicator by the VAL 1995-05-27 linker val.exe. DOSBox also sets it. */ - *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ } else if (ah == 0x4f) { /* Find next matching file (findnext). */ const unsigned dta_linear = (dta_seg_ofs & 0xffff) + (dta_seg_ofs >> 16 << 4); if (!is_linear_byte_user_writable(dta_linear) || !is_linear_byte_user_writable(dta_linear + 0x2b - 1)) goto error_invalid_parameter; { char * const dta = (char*)mem + dta_linear; - if (*(unsigned*)dta != FINDFIRST_MAGIC) goto error_invalid_parameter; + struct dirent *de; + if (*(unsigned*)dta != FINDFIRST_MAGIC || !find_dirp) goto error_invalid_parameter; + while ((de = readdir(find_dirp)) != NULL) { + char fn[LINUX_PATH_SIZE]; + const char *fnb = de->d_name; + struct stat st; + struct tm *tm; + if (!is_dos_filename_83(fnb) || !dos_wildcard_match(find_dos_pattern, fnb)) continue; + if (snprintf(fn, sizeof(fn), "%s/%s", find_linux_dir, fnb) <= 0 || strlen(fn) >= sizeof(fn)) continue; + if (stat(fn, &st) != 0) continue; + if (S_ISDIR(st.st_mode) && !(find_attrs & 0x10)) continue; + memset(dta, '\0', 0x16); + tm = localtime(&st.st_mtime); + *(unsigned*)dta = FINDFIRST_MAGIC; + *(unsigned short*)(dta + 0x16) = tm->tm_sec >> 1 | tm->tm_min << 5 | tm->tm_hour << 11; + *(unsigned short*)(dta + 0x18) = tm->tm_mday | (tm->tm_mon + 1) << 5 | (tm->tm_year - 1980) << 9; + *(unsigned*)(dta + 0x1a) = (sizeof(st.st_size) > 4 && st.st_size >> (32 * (sizeof(st.st_size) > 4))) ? + 0xffffffffU : st.st_size + (size_t)0; + { const char *p = fnb; char *q = dta + 0x1e, c; + do { c = *p++; *q++ = upper_ascii(c); } while (c != '\0'); + } + *(unsigned short*)®s.rax = 0; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + goto done_findnext2; + } + closedir(find_dirp); + find_dirp = NULL; goto no_more_files; - } - } else if (ah == 0x37) { /* Get/set switch character (for command-line flags). */ - const unsigned char al = (unsigned char)regs.rax; - if (al == 0x00) { /* Get. */ - *(unsigned char*)®s.rax = 0; /* Success. */ - *(unsigned char*)®s.rdx = '/'; - } else { - fprintf(stderr, "fatal: unsupported subcall for switch character: 0x%02x\n", al); - goto fatal; + done_findnext2:; } } else if (ah == 0x51 || ah == 0x62) { /* Get process ID (PSP) (0x51). Get PSP (0x62). */ *(unsigned short*)®s.rbx = PSP_PARA; @@ -3051,22 +4061,96 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam SET_SREG(es, 0xfff0); } else if (ah == 0x29) { /* Parse filename for FCB. */ const char * const p = (char*)mem + ((unsigned)sregs.ds.selector << 4) + (*(unsigned short*)®s.rsi); /* !! Security: check bounds. */ - if (*p == '\0' || *p == '\r' || *p == '\n') { - char *q = (char*)mem + ((unsigned)sregs.es.selector << 4) + (*(unsigned short*)®s.rdi); /* !! Security: check bounds. */ - /* al == 1, *p == '\r' in Microsoft Macro Assembler 6.00B driver masm.exe. */ - /* al == 0, *p == '\n' in Power C 2.2.0 compiler pc.exe. */ - *(unsigned char*)®s.rax = 0; /* No wildchar characters present. */ - *q++ = '\0'; /* Drive: 0 is default. */ - memset(q, ' ', 12); /* Filename (8) and extension (3). */ - /* Don't update SI. */ + char *q = (char*)mem + ((unsigned)sregs.es.selector << 4) + (*(unsigned short*)®s.rdi); /* !! Security: check bounds. */ + const char *s = p; + char *fn = q + 1; + unsigned short new_si_ofs; + char has_wild = 0; + /* Permissive FCB parser for legacy MAKE/MSC tools. */ + while (*s == ' ' || *s == '\t') ++s; + *q = '\0'; /* Drive: 0 (default). */ + memset(fn, ' ', 11); /* Name(8)+Ext(3). */ + if (*s != '\0' && *s != '\r' && *s != '\n') { + unsigned i = 0; + while (*s != '\0' && *s != '\r' && *s != '\n' && *s != ' ' && *s != '\t' && *s != '.' && i < 8) { + if (*s == '*' || *s == '?') has_wild = 1; + fn[i++] = upper_ascii(*s++); + } + if (*s == '.') { + unsigned j = 0; + ++s; + while (*s != '\0' && *s != '\r' && *s != '\n' && *s != ' ' && *s != '\t' && j < 3) { + if (*s == '*' || *s == '?') has_wild = 1; + fn[8 + j++] = upper_ascii(*s++); + } + } + while (*s != '\0' && *s != '\r' && *s != '\n' && *s != ' ' && *s != '\t') ++s; + } + new_si_ofs = (unsigned short)(s - ((char*)mem + ((unsigned)sregs.ds.selector << 4))); + *(unsigned short*)®s.rsi = new_si_ofs; + *(unsigned char*)®s.rax = has_wild ? 1 : 0; /* AL: wildcard present. */ + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + } else if (ah == 0x87) { /* Used by older Microsoft toolchains. */ + goto nonfatal_unknown_int_21_call; /* Report unsupported without fatal abort. */ + } else if (ah == 0x5a) { /* Create temporary file. */ + const char *tmpl = (char*)mem + ((unsigned)sregs.ds.selector << 4) + (*(unsigned short*)®s.rdx); /* !! Security: check bounds. */ + char dos_prefix[DOS_PATH_SIZE + 4], dos_name[DOS_PATH_SIZE + 4]; + size_t tn = 0, pfx_len; + const char *last_slash = NULL, *scan; + int fd = -1; + unsigned i; + for (; tmpl[tn] != '\0' && tn + 1 < sizeof(dos_prefix); ++tn) {} + if (tn == 0) { + strcpy(dos_prefix, "TMP"); } else { - fprintf(stderr, "fatal: unsupported parsing of filename: %s\n", p); /* For ml.exe, this filename is completely broken, it starts with \r, also in DOSBox. */ - goto fatal_int; + memcpy(dos_prefix, tmpl, tn); + dos_prefix[tn] = '\0'; + } + for (scan = dos_prefix; *scan; ++scan) if (*scan == '\\' || *scan == '/' || *scan == ':') last_slash = scan; + pfx_len = last_slash ? (size_t)(last_slash + 1 - dos_prefix) : 0; + if (pfx_len >= sizeof(dos_name)) pfx_len = 0; + memcpy(dos_name, dos_prefix, pfx_len); + dos_name[pfx_len] = '\0'; + for (i = 0; i != 0x10000; ++i) { + char *w = dos_name + pfx_len; + unsigned v = i; + static const char hex[] = "0123456789ABCDEF"; + *w++ = 'K'; *w++ = 'V'; + *w++ = hex[(v >> 12) & 15]; + *w++ = hex[(v >> 8) & 15]; + *w++ = hex[(v >> 4) & 15]; + *w++ = hex[v & 15]; + *w++ = '.'; + *w++ = 'T'; *w++ = 'M'; *w++ = 'P'; + *w = '\0'; + if (*get_linux_filename_r(dos_name, dir_state, fnbuf, NULL) == '\0') { + *(unsigned short*)®s.rax = 3; /* Path not found. */ + goto error_on_21; + } + fd = open(fnbuf, O_RDWR | O_CREAT | O_EXCL, 0644); + if (fd >= 0) break; + if (errno != EEXIST) { + *(unsigned short*)®s.rax = get_dos_error_code(errno, 0x1f); + goto error_on_21; + } + } + if (fd < 0) { + *(unsigned short*)®s.rax = 0x50; /* File exists. */ + goto error_on_21; } + if (fd < 5) fd = ensure_fd_is_at_least(fd, 5); + fd = map_fd_open(fd); + if ((fd + 0U) >> 16) { + *(unsigned short*)®s.rax = 4; /* Too many open files. */ + goto error_on_21; + } + strcpy((char*)mem + ((unsigned)sregs.ds.selector << 4) + (*(unsigned short*)®s.rdx), dos_name); + *(unsigned short*)®s.rax = fd; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ } else if (ah == 0x58) { /* Get/set memory allocation strategy. */ const unsigned char al = (unsigned char)regs.rax; if (al == 0x00) { /* Get. */ - *(unsigned short*)®s.rax = 1; /* Best fit. */ + *(unsigned short*)®s.rax = (unsigned short)malloc_strategy; *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ } else if (al == 0x01) { /* Set. */ /* Programs compiled by Borland C++ 5.02 compiler bcc.exe set it with BX == MS_LAST_FIT, and return ``Out of memory'' if not implemented correctly. */ @@ -3075,6 +4159,22 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ malloc_strategy = *(unsigned short*)®s.rbx; if (DEBUG || DEBUG_ALLOC) fprintf(stderr, "debug: set malloc strategy=%u\n", malloc_strategy); + } else if (al == 0x02) { /* Get UMB link state. */ + *(unsigned short*)®s.rax = (unsigned short)(unsigned char)umb_link_state; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + } else if (al == 0x03) { /* Set UMB link state. */ + const unsigned short bx = *(unsigned short*)®s.rbx; + if (bx > 1) goto error_invalid_parameter; + umb_link_state = (char)bx; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + } else if (al == 0x04) { /* Get strategy + UMB link state. */ + *(unsigned short*)®s.rbx = (unsigned short)(malloc_strategy | ((unsigned)(unsigned char)umb_link_state << 7)); + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + } else if (al == 0x05) { /* Set strategy + UMB link state. */ + const unsigned short bx = *(unsigned short*)®s.rbx; + malloc_strategy = bx & 0x3f; + umb_link_state = !!(bx & 0x80); + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ } else { goto error_invalid_parameter; } @@ -3084,30 +4184,45 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam if (al == 0 || al == 3) { /* Microsoft Macro Assembler 6.00B driver masm.exe uses it with al == 3. */ const char * const params = (char*)mem + ((unsigned)sregs.es.selector << 4) + (*(unsigned short*)®s.rbx); /* !! Security: check bounds. */ const unsigned short load_para = al != 0 ? ((unsigned short*)params)[0] : 0; - /*const unsigned short relocation_factor = *(unsigned short*)(params + 2);*/ + const unsigned short relocation_factor = al != 0 ? ((unsigned short*)params)[1] : 0; char * const psp = (al != 0 && load_para >= PSP_PARA + 0x10 && load_para < DOS_ALLOC_PARA_LIMIT) ? (char*)mem + ((unsigned)(load_para - 0x10) << 4) : NULL; const unsigned short env_para = al == 0 ? (((unsigned short*)params)[0] ? ((unsigned short*)params)[0] : ENV_PARA) : psp ? *(const unsigned short*)(psp + 0x2c) : 0; char * const env = ((al == 0 && env_para == ENV_PARA) || (env_para >= PSP_PARA + 0x10 && env_para < DOS_ALLOC_PARA_LIMIT)) ? (char*)mem + (env_para << 4) : NULL; const char *env_end = env ? env + (((PROGRAM_MCB_PARA - ENV_PARA < DOS_ALLOC_PARA_LIMIT - env_para) ? PROGRAM_MCB_PARA - ENV_PARA : DOS_ALLOC_PARA_LIMIT - env_para) << 4) : NULL; - char * const args = al == 0 ? (char*)mem + (((unsigned short*)params)[2] << 4) + ((unsigned short*)params)[1] + 1 /* (args - 1) is Pascal string with terminating '\r'. */ - : psp ? psp + 0x81 : NULL; - const unsigned char args_size = args ? (unsigned char)args[-1] : 0; - const char is_args_normal = args && (args_size < 0x7f && args[args_size] == '\0'); /* '\0' for al == 0 when Borland C++ 2.0 compiler bcc.exe is running tlink.exe */ - const char is_args_ok = is_args_normal || (args && args_size == '\n' && args[0] == '\n' && args[1] == '.'); /* Power C 2.2.0 compiler pc.exe. Copy all 128 bytes to new PSP. */ + char * const args_raw = al == 0 ? (char*)mem + (((unsigned short*)params)[2] << 4) + ((unsigned short*)params)[1] + : psp ? psp + 0x80 : NULL; /* DOS command tail in PSP format at [0x80]=len. */ + char args_buf[0x80]; + unsigned char args_size = 0; + const char *safe_args = ""; + char is_args_ok = 0; const char is_dos_filename_high = sregs.ds.selector + (*(unsigned short*)®s.rdx >> 4) >= PSP_PARA; /* So that dos_filename won't overlap new_env below. */ char *new_env; char new_prog_drive; - int reason; - if (is_args_normal) args[args_size] = '\0'; /* It was '\r'. */ - if (!(env && is_args_ok && is_dos_filename_high)) { - if (is_args_normal) args[args_size] = '\0'; + int reason = 0; + if (args_raw) { + args_size = (unsigned char)args_raw[0]; + if (args_size < 0x7f && (args_raw[1 + args_size] == '\0' || args_raw[1 + args_size] == '\r' || args_raw[1 + args_size] == '\n')) { /* PSP-style command tail. */ + memcpy(args_buf, args_raw + 1, args_size); + args_buf[args_size] = '\0'; + safe_args = args_buf; + is_args_ok = 1; + } else { /* Fallback: direct CR/NUL-terminated string pointer. */ + const char *p = args_raw; + args_size = 0; + while (args_size < 0x7f && p[args_size] != '\0' && p[args_size] != '\r' && p[args_size] != '\n') ++args_size; + memcpy(args_buf, p, args_size); + args_buf[args_size] = '\0'; + safe_args = args_buf; + is_args_ok = 1; + } + } + if (!((al == 3 && is_dos_filename_high) || (al == 0 && env && is_dos_filename_high))) { fprintf(stderr, "fatal: bounds check failed (env_ok=%d args_ok=%d, fn_ok=%d) env when loading program=(%s) with args=(%s)\n", env != NULL, is_args_ok, is_dos_filename_high, - dos_filename, is_args_normal ? args : NULL); - if (is_args_normal) args[args_size] = '\r'; + dos_filename, safe_args); goto fatal_int; } - if (DEBUG || DEBUG_EXEC) fprintf(stderr, "debug: exec: al:%02x reason=%d program=(%s) args=(%s)\n", al, reason, dos_filename, is_args_normal ? args : NULL); + if (DEBUG || DEBUG_EXEC) fprintf(stderr, "debug: exec: al:%02x reason=%d program=(%s) args=(%s)\n", al, reason, dos_filename, safe_args); if (0 && al == 0) { /* TODO(pts): Why stop? */ /* Power C 2.2.0 compiler pc.exe. */ fprintf(stderr, "fatal: unsupported exec with al:%02d: %s\n", al, dos_filename); @@ -3121,12 +4236,118 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam * file). However, kvikdos is not smart enough for that, so it * just does an exec() and forgets about the parent process. */ - if ((reason = should_skip_exec_program(dos_filename, is_args_normal ? args : NULL, env, &env_end, had_get_first_mcb)) > 0) { - fprintf(stderr, "fatal: unsupported program to load: al:%02x reason=%d program=(%s) args=(%s)\n", al, reason, dos_filename, is_args_normal ? args : NULL); + if (al == 3) { + char ovl_dos[LINUX_PATH_SIZE]; + dir_state->dos_prog_abs = dos_prog_abs; /* Allow loading overlay via mounted alias path. */ + prog_filename = get_linux_filename_r(dos_filename, dir_state, exec_fnbuf, NULL); + dir_state->dos_prog_abs = NULL; /* For security. */ + if (prog_filename[0] == '\0' && dos_prog_abs[0] && + !strchr(dos_filename, ':') && !strchr(dos_filename, '\\') && !strchr(dos_filename, '/')) { + const char *base = dos_prog_abs + strlen(dos_prog_abs); + for (; base != dos_prog_abs + 3 && base[-1] != '\\'; --base) {} + if (base > dos_prog_abs + 3) { + size_t dir_size = base - dos_prog_abs; + size_t fn_size = strlen(dos_filename); + if (dir_size + fn_size + 1 < sizeof(ovl_dos)) { + memcpy(ovl_dos, dos_prog_abs, dir_size); + memcpy(ovl_dos + dir_size, dos_filename, fn_size + 1); + dir_state->dos_prog_abs = dos_prog_abs; + prog_filename = get_linux_filename_r(ovl_dos, dir_state, exec_fnbuf, NULL); + dir_state->dos_prog_abs = NULL; + } + } + } + if (prog_filename[0] == '\0') { + *(unsigned short*)®s.rax = 2; /* File not found. */ + goto error_on_21; + } + if ((img_fd = open_with_case_fallback(prog_filename, O_RDONLY, 0666)) < 0) { + *(unsigned short*)®s.rax = get_dos_error_code(errno, 0x02); + goto error_on_21; + } + header_size = detect_dos_executable_program(img_fd, prog_filename, header); + if (load_dos_overlay_program(img_fd, prog_filename, mem, header, header_size, load_para, relocation_factor) != 0) { + close(img_fd); + *(unsigned short*)®s.rax = get_dos_error_code(errno, 0x1f); + goto error_on_21; + } + close(img_fd); + *(unsigned short*)®s.rax = 0; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + goto done_int_21_call; + } + reason = 0; + if (reason > 0) { + if (al == 3) { + /* Program load probe (without execute): report failure to caller, + * don't terminate parent tool. LINK.EXE may probe optional helpers + * such as DOSXNT.EXE and continue without them. + */ + *(unsigned short*)®s.rax = 2; /* File not found. */ + goto error_on_21; + } + fprintf(stderr, "fatal: unsupported program to load: al:%02x reason=%d program=(%s) args=(%s)\n", al, reason, dos_filename, safe_args); goto fatal_int; } - if (reason == -1 && is_args_normal && cleanup_fn[0] == '\0' && args[0] == '@' && strlen(args) <= sizeof(cleanup_fn)) { - strcpy(cleanup_fn, args + 1); /* Example args: "@turboc.$ln". */ + if (al == 0) { + const char *dos_exec_name = dos_filename; + char dos_exec_buf[DOS_PATH_SIZE + 4]; + if ((dos_filename[0] & ~32) - 'A' + 0U < DRIVE_COUNT && dos_filename[1] == ':' && + (dos_filename[2] == '\\' || dos_filename[2] == '/')) { + char requested_drive = dos_filename[0] & ~32; + if (!dir_state->linux_mount_dir[requested_drive - 'A']) { + char active_drive = dir_state->drive; + if ((active_drive - 'A' + 0U) < DRIVE_COUNT && dir_state->linux_mount_dir[active_drive - 'A']) { + size_t abs_tail_size = strlen(dos_filename + 2); + /* Some toolchains probe absolute paths on an unmapped drive. + * Retry same absolute tail on the active mounted drive. + */ + if (abs_tail_size + 2 < sizeof(dos_exec_buf)) { + dos_exec_buf[0] = active_drive; + memcpy(dos_exec_buf + 1, dos_filename + 1, abs_tail_size + 1); + dos_exec_name = dos_exec_buf; + } + } + } + } + if (!strchr(dos_filename, ':') && !strchr(dos_filename, '\\') && !strchr(dos_filename, '/')) { + char path_copy[1024]; + const char *dos_path_env = getenv_prefix_block_nocase("PATH=", env, env_end); + const char *resolved_prog; + char resolved_drive = '\0'; + path_copy[0] = '\0'; + if (dos_path_env) { + strncpy(path_copy, dos_path_env, sizeof(path_copy) - 1); + path_copy[sizeof(path_copy) - 1] = '\0'; + } + resolved_prog = find_prog_on_path(dos_filename, dir_state, path_copy[0] ? path_copy : NULL, &resolved_drive); + if (resolved_prog && resolved_prog[0]) { + const char *resolved_dos = get_dos_abs_filename_r(resolved_prog, resolved_drive, dir_state, dos_exec_buf); + if (resolved_dos[0]) dos_exec_name = resolved_dos; + } else if (dos_prog_abs[0]) { + const char *base = dos_prog_abs + strlen(dos_prog_abs); + const char *tail = dos_filename; + size_t dir_size, tail_size; + for (; base > dos_prog_abs + 3 && base[-1] != '\\'; --base) {} + dir_size = (size_t)(base - dos_prog_abs); + tail_size = strlen(tail); + if (dir_size && dir_size + tail_size + 1 < sizeof(dos_exec_buf)) { + memcpy(dos_exec_buf, dos_prog_abs, dir_size); + memcpy(dos_exec_buf + dir_size, tail, tail_size + 1); + dos_exec_name = dos_exec_buf; + } + } + } + if (run_dos_child_subprocess(dos_exec_name, safe_args, env, env_end, dir_state, &last_exec_return_code) != 0) { + *(unsigned short*)®s.rax = get_dos_error_code(errno, 0x1f); /* General failure. */ + goto error_on_21; + } + *(unsigned short*)®s.rax = 0; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + goto done_int_21_call; + } + if (reason == -1 && cleanup_fn[0] == '\0' && safe_args[0] == '@' && strlen(safe_args) <= sizeof(cleanup_fn)) { + strcpy(cleanup_fn, safe_args + 1); /* Example args: "@turboc.$ln". */ if (DEBUG) fprintf(stderr, "debug: will remove file at exit: %s\n", cleanup_fn); } dir_state->dos_prog_abs = dos_prog_abs; /* For loading the overlay from prog_filename, even if not mounted. */ @@ -3137,18 +4358,14 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam fprintf(stderr, "fatal: bad program filename for loading: %s\n", dos_filename); goto fatal_int; } - if ((img_fd = open(prog_filename, O_RDONLY)) < 0) { + if ((img_fd = open_with_case_fallback(prog_filename, O_RDONLY, 0666)) < 0) { /*goto error_from_linux;*/ /* We don't know how to report the error properly here (it's not a normal int 0x21 call. */ fprintf(stderr, "fatal: cannot open DOS executable program for loading: %s: %s\n", prog_filename, strerror(errno)); goto fatal_int; } *(char*)env_end = '\0'; /* Hide counter for absolute program pathname. */ memcpy(new_env = (char*)mem + (ENV_PARA << 4), env, env_end + 2 - env); - if (!is_args_normal) { - fprintf(stderr, "fatal: bad args when loading: %s\n", dos_filename); - goto fatal_int; - } - strcpy(fnbuf2, args); /* Large enough to hold 0x7f bytes. */ + strcpy(fnbuf2, safe_args); /* Large enough to hold 0x7f bytes. */ args_str = fnbuf2; dos_prog_abs = get_dos_abs_filename_r(prog_filename, new_prog_drive, dir_state, dosfnbuf); if (DEBUG) fprintf(stderr, "debug: exec prog_filename=(%s) dos_prog_abs=(%s) dos_prog_drive=%c\n", prog_filename, dos_prog_abs, new_prog_drive); @@ -3158,9 +4375,11 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } goto do_exec; } else { - fprintf(stderr, "fatal: unsupported loading of program with al:%02x: %s\n", al, dos_filename); - goto fatal_int; + /* Compatibility: some tools probe other EXEC modes. */ + *(unsigned short*)®s.rax = 1; /* Invalid function number. */ + goto error_on_21; } + done_int_21_call: ; } else if (ah == 0x0a) { /* Buffered keyboard input. */ func_0x0a: { char *p = (char*)mem + ((unsigned)sregs.ds.selector << 4) + (*(unsigned short*)®s.rdx); /* !! Security: check bounds. */ @@ -3209,6 +4428,33 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } else { goto fatal_int; } + } else if (ah == 0x5c) { /* Record lock/unlock. */ + const unsigned char al = (unsigned char)regs.rax; + const int fd = get_linux_fd(*(unsigned short*)®s.rbx, &kvm_fds); + struct flock fl; + if (fd < 0) goto error_invalid_handle; + memset(&fl, 0, sizeof(fl)); + fl.l_type = (al == 0) ? F_WRLCK : (al == 1) ? F_UNLCK : (short)-1; + if (fl.l_type == (short)-1) goto nonfatal_unknown_int_21_call; + fl.l_whence = SEEK_SET; + fl.l_start = ((long)(*(unsigned short*)®s.rcx) << 16) | *(unsigned short*)®s.rdx; + fl.l_len = ((long)(*(unsigned short*)®s.rsi) << 16) | *(unsigned short*)®s.rdi; + if (fcntl(fd, F_SETLK, &fl) == 0) { + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + } else { + *(unsigned short*)®s.rax = 0x21; /* Lock violation. */ + goto error_on_21; + } + } else if (ah == 0x5d || ah == 0x5e || ah == 0x5f) { /* Share/network redirector services. */ + goto nonfatal_unknown_int_21_call; + } else if (ah == 0x54) { /* Get verify flag. */ + *(unsigned char*)®s.rax = 0; /* Verify off. */ + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + } else if (ah == 0x2e) { /* Set verify flag. */ + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ + } else if (ah == 0x4d) { /* Get return code from child process. */ + *(unsigned short*)®s.rax = ((unsigned short)last_exec_return_code) << 8; + *(unsigned short*)®s.rflags &= ~(1 << 0); /* CF=0. */ } else if (ah == 0x66) { /* Get/set global code page. */ const unsigned char al = (unsigned char)regs.rax; if (al == 1) { @@ -3332,17 +4578,41 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam *(unsigned short*)®s.rax = *(const unsigned short*)((const char*)mem + 0x410); } else if (int_num == 0x2f) { /* Installation checks. */ const unsigned char al = (unsigned char)regs.rax; + const unsigned short ax = (unsigned short)regs.rax; if (al == 0x00) { /* Installation check. */ if (ah < 2 || ah == 0x15) goto fatal_uic; /* Doesn't follow the standard format. */ - /* ah == 0x43: XMS. */ - *(unsigned char*)®s.rax = 0; /* Not installed, OK to install. */ + if (ah == 0x43) { + *(unsigned char*)®s.rax = 0x80; /* XMS installed. */ + } else { + *(unsigned char*)®s.rax = 0; /* Not installed, OK to install. */ + } + } else if (ax == 0x4310) { /* Get XMS entry point ES:BX. */ + SET_SREG(es, INT_HLT_PARA); + *(unsigned short*)®s.rbx = 0x0200; /* Far-callable XMS stub: int 43h; retf. */ + } else if (*(unsigned short*)®s.rax == 0x1100 || *(unsigned short*)®s.rax == 0x111e) { /* Redirector/network install checks. */ + *(unsigned short*)®s.rax = 0; /* Not installed. */ + } else if (*(unsigned short*)®s.rax == 0x1600 || *(unsigned short*)®s.rax == 0x160a) { /* Windows enhanced mode / Windows version probes. */ + *(unsigned short*)®s.rax = 0; /* Not running under Windows/386 enhanced services. */ + } else if (*(unsigned short*)®s.rax == 0xed10) { /* LINK.EXE probe in some MASM/MSC toolchains. */ + *(unsigned short*)®s.rax = 0; /* Not installed / no service. */ } else if (*(unsigned short*)®s.rax == 0x1687) { /* DPMI. */ + if (!dpmi_warned && dos_prog_abs && + (strstr(dos_prog_abs, "BCC.EXE") || strstr(dos_prog_abs, "bcc.exe") || + strstr(dos_prog_abs, "32RTM.EXE") || strstr(dos_prog_abs, "32rtm.exe"))) { + fprintf(stderr, "info: DPMI/protected-mode runtime requested, but kvikdos supports real-mode DOS only.\n"); + dpmi_warned = 1; + } /* Keep it as is, DPMI not installed. */ -#if 0 /* TLINK 5.1 tlink.exe loading dpmi16bi.ovl */ } else if (*(unsigned short*)®s.rax == 0xfb42) { + *(unsigned short*)®s.rax = 0; /* Not installed / no service. */ } else if (*(unsigned short*)®s.rax == 0xfb43) { -#endif + *(unsigned short*)®s.rax = 0; /* Not installed / no service. */ } else { fatal_uic: + if (!emu_params->strict_mode) { + *(unsigned short*)®s.rax = 0; + *(unsigned short*)®s.rflags |= 1 << 0; /* CF=1. */ + goto done_int_call; + } fprintf(stderr, "fatal: unsupported int 0x%02x ax:%04x\n", int_num, *(const unsigned short*)®s.rax); goto fatal_int; } @@ -3357,12 +4627,186 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam goto fatal_uic; } } else if (int_num == 0x67) { /* Various. */ - const unsigned short ax = (unsigned short)regs.rax; - if (ax == 0xde00) { /* VCPI installation check. http://mirror.cs.msu.ru/oldlinux.org/Linux.old/docs/interrupts/int-html/rb-7491.htm */ + const unsigned char al = (unsigned char)regs.rax; + if ((unsigned short)regs.rax == 0xde00) { /* VCPI installation check. */ /* Doing nothing means it's not installed. */ + } else if (ah == 0x40) { /* Get EMM status. */ + ((unsigned char*)®s.rax)[1] = 0; /* AH=0 success. */ + } else if (ah == 0x41) { /* Get page frame segment. */ + ((unsigned char*)®s.rax)[1] = 0; /* AH=0 success. */ + *(unsigned short*)®s.rbx = 0xe000; /* Conventional EMS page frame. */ + } else if (ah == 0x42) { /* Get number of pages. */ + ((unsigned char*)®s.rax)[1] = 0; /* AH=0 success. */ + *(unsigned short*)®s.rbx = ems_free_pages; + *(unsigned short*)®s.rdx = ems_total_pages; + } else if (ah == 0x43) { /* Allocate pages. */ + unsigned short req = *(unsigned short*)®s.rbx, hi; + if (!req || req > ems_free_pages) { + ((unsigned char*)®s.rax)[1] = 0x88; /* Insufficient pages. */ + } else { + for (hi = 1; hi < EMS_HANDLE_COUNT; ++hi) if (ems_pages_by_handle[hi] == 0) break; + if (hi >= EMS_HANDLE_COUNT) { + ((unsigned char*)®s.rax)[1] = 0x85; /* No handles. */ + } else { + ems_pages_by_handle[hi] = req; + ems_free_pages -= req; + *(unsigned short*)®s.rdx = hi; + ((unsigned char*)®s.rax)[1] = 0; + } + } + } else if (ah == 0x44) { /* Map page. */ + unsigned short logical = *(unsigned short*)®s.rbx; + unsigned short phys = *(unsigned short*)®s.rdx; + unsigned short handle = *(unsigned short*)®s.rsi; + if (phys >= 4 || handle >= EMS_HANDLE_COUNT || ems_pages_by_handle[handle] == 0 || logical >= ems_pages_by_handle[handle]) { + ((unsigned char*)®s.rax)[1] = 0x83; /* Invalid handle/page. */ + } else { + ems_page_map[phys] = logical; + ((unsigned char*)®s.rax)[1] = 0; + } + } else if (ah == 0x45) { /* Release handle. */ + unsigned short handle = *(unsigned short*)®s.rdx; + if (handle == 0 || handle >= EMS_HANDLE_COUNT || ems_pages_by_handle[handle] == 0) { + ((unsigned char*)®s.rax)[1] = 0x83; + } else { + ems_free_pages += ems_pages_by_handle[handle]; + ems_pages_by_handle[handle] = 0; + ((unsigned char*)®s.rax)[1] = 0; + } + } else if (ah == 0x46) { /* Get EMM version. */ + ((unsigned char*)®s.rax)[1] = 0; /* AH=0 success. */ + ((unsigned char*)®s.rax)[0] = 0x40; /* AL=4.0 */ + } else if (ah == 0x4b) { /* Get number of handles/pages. */ + unsigned short used_handles = 0, h; + for (h = 1; h < EMS_HANDLE_COUNT; ++h) if (ems_pages_by_handle[h]) ++used_handles; + ((unsigned char*)®s.rax)[1] = 0; + *(unsigned short*)®s.rbx = EMS_HANDLE_COUNT - 1 - used_handles; /* free handles */ + *(unsigned short*)®s.rcx = ems_total_pages - ems_free_pages; /* allocated pages */ + *(unsigned short*)®s.rdx = ems_total_pages; + } else if (ah == 0x4c) { /* Get pages for one handle. */ + unsigned short handle = *(unsigned short*)®s.rdx; + if (handle == 0 || handle >= EMS_HANDLE_COUNT || ems_pages_by_handle[handle] == 0) { + ((unsigned char*)®s.rax)[1] = 0x83; + } else { + ((unsigned char*)®s.rax)[1] = 0; + *(unsigned short*)®s.rbx = ems_pages_by_handle[handle]; + } + } else if (ah == 0x58 && al == 0x00) { /* Allocate standard pages / trivial success for probes. */ + ((unsigned char*)®s.rax)[1] = 0; } else { goto fatal_uic; } + } else if (int_num == 0x43) { /* XMS entry point pseudo interrupt (from INT 2F AX=4310 ES:BX). */ + if (ah == 0x00) { /* Get XMS version. */ + *(unsigned short*)®s.rax = 1; + *(unsigned short*)®s.rbx = 0x0300; /* XMS version 3.00. */ + *(unsigned short*)®s.rdx = 0; /* No HMA handle. */ + } else if (ah == 0x08) { /* Query free extended memory. */ + *(unsigned short*)®s.rax = 1; + *(unsigned short*)®s.rdx = xms_free_kb; /* Largest free block in KiB. */ + *(unsigned short*)®s.rbx = xms_total_kb; /* Total free in KiB. */ + } else if (ah == 0x09) { /* Allocate EMB. */ + unsigned short kb = *(unsigned short*)®s.rdx, hi; + unsigned long bytes = (unsigned long)kb << 10; + if (!kb || kb > xms_free_kb) { + *(unsigned short*)®s.rax = 0; + *(unsigned char*)®s.rbx = 0xa0; /* Out of space. */ + } else { + for (hi = 1; hi < XMS_HANDLE_COUNT; ++hi) if (xms_blocks[hi] == NULL) break; + if (hi >= XMS_HANDLE_COUNT || !(xms_blocks[hi] = malloc(bytes))) { + *(unsigned short*)®s.rax = 0; + *(unsigned char*)®s.rbx = 0xa1; /* No handles / alloc failure. */ + } else { + memset(xms_blocks[hi], 0, bytes); + xms_block_sizes[hi] = bytes; + xms_lock_counts[hi] = 0; + xms_free_kb -= kb; + *(unsigned short*)®s.rdx = hi; + *(unsigned short*)®s.rax = 1; + *(unsigned char*)®s.rbx = 0; + } + } + } else if (ah == 0x0a) { /* Free EMB. */ + unsigned short hi = *(unsigned short*)®s.rdx; + if (hi == 0 || hi >= XMS_HANDLE_COUNT || xms_blocks[hi] == NULL) { + *(unsigned short*)®s.rax = 0; + *(unsigned char*)®s.rbx = 0xa2; /* Invalid handle. */ + } else { + free(xms_blocks[hi]); + xms_free_kb += (unsigned short)(xms_block_sizes[hi] >> 10); + xms_blocks[hi] = NULL; + xms_block_sizes[hi] = 0; + xms_lock_counts[hi] = 0; + *(unsigned short*)®s.rax = 1; + *(unsigned char*)®s.rbx = 0; + } + } else if (ah == 0x0b) { /* Move EMB. */ + const char *m = (const char*)mem + ((unsigned)sregs.ds.selector << 4) + (*(unsigned short*)®s.rsi); + unsigned long len = *(const unsigned long*)(const void*)(m + 0); + unsigned short sh = *(const unsigned short*)(const void*)(m + 4); + unsigned long so = *(const unsigned long*)(const void*)(m + 6); + unsigned short dh = *(const unsigned short*)(const void*)(m + 10); + unsigned long doff = *(const unsigned long*)(const void*)(m + 12); + char *sp, *dp; + if (len == 0) { + *(unsigned short*)®s.rax = 1; + *(unsigned char*)®s.rbx = 0; + goto done_int_call; + } + if (sh == 0) sp = (char*)mem + so; + else if (sh < XMS_HANDLE_COUNT && xms_blocks[sh] && so + len <= xms_block_sizes[sh]) sp = (char*)xms_blocks[sh] + so; + else sp = NULL; + if (dh == 0) dp = (char*)mem + doff; + else if (dh < XMS_HANDLE_COUNT && xms_blocks[dh] && doff + len <= xms_block_sizes[dh]) dp = (char*)xms_blocks[dh] + doff; + else dp = NULL; + if (!sp || !dp || sp < (char*)mem || dp < (char*)mem || (sh == 0 && so + len > DOS_MEM_LIMIT) || (dh == 0 && doff + len > DOS_MEM_LIMIT)) { + *(unsigned short*)®s.rax = 0; + *(unsigned char*)®s.rbx = 0xa3; /* Invalid source/dest. */ + } else { + memmove(dp, sp, (size_t)len); + *(unsigned short*)®s.rax = 1; + *(unsigned char*)®s.rbx = 0; + } + } else if (ah == 0x0c) { /* Lock EMB. */ + unsigned short hi = *(unsigned short*)®s.rdx; + if (hi == 0 || hi >= XMS_HANDLE_COUNT || xms_blocks[hi] == NULL) { + *(unsigned short*)®s.rax = 0; + *(unsigned char*)®s.rbx = 0xa2; + } else { + unsigned long addr = (unsigned long)(size_t)xms_blocks[hi]; + if (xms_lock_counts[hi] != 0xffff) ++xms_lock_counts[hi]; + *(unsigned short*)®s.rbx = (unsigned short)addr; + *(unsigned short*)®s.rdx = (unsigned short)(addr >> 16); + *(unsigned short*)®s.rax = 1; + } + } else if (ah == 0x0d) { /* Unlock EMB. */ + unsigned short hi = *(unsigned short*)®s.rdx; + if (hi == 0 || hi >= XMS_HANDLE_COUNT || xms_blocks[hi] == NULL || xms_lock_counts[hi] == 0) { + *(unsigned short*)®s.rax = 0; + *(unsigned char*)®s.rbx = 0xaa; /* Not locked. */ + } else { + --xms_lock_counts[hi]; + *(unsigned short*)®s.rax = 1; + *(unsigned char*)®s.rbx = 0; + } + } else if (ah == 0x0e) { /* Get EMB handle information. */ + unsigned short hi = *(unsigned short*)®s.rdx, free_handles = 0, i; + if (hi == 0 || hi >= XMS_HANDLE_COUNT || xms_blocks[hi] == NULL) { + *(unsigned short*)®s.rax = 0; + *(unsigned char*)®s.rbx = 0xa2; + } else { + for (i = 1; i < XMS_HANDLE_COUNT; ++i) if (xms_blocks[i] == NULL) ++free_handles; + *(unsigned short*)®s.rax = 1; + ((unsigned char*)®s.rbx)[1] = (unsigned char)xms_lock_counts[hi]; /* BH lock count. */ + *(unsigned short*)®s.rdx = (unsigned short)(xms_block_sizes[hi] >> 10); /* Size in KiB. */ + *(unsigned char*)®s.rbx = free_handles > 255 ? 255 : (unsigned char)free_handles; /* BL free handles. */ + } + } else { + *(unsigned short*)®s.rax = 0; + *(unsigned char*)®s.rbx = 0x80; /* Function not implemented. */ + } + } else if (int_num == 0x0d) { /* General protection fault. */ + /* Allow DOS extender probes to fail softly. */ } else if (int_num == 0x00) { /* Division by zero. */ /* This is called only if the program doesn't override the interrupt vector. * Example instructions: `xor ax, ax', `div ax'. @@ -3371,9 +4815,15 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } else if (int_num == 0x03 && tasm30_bitset == 0xff) { /* Turbo Assembler (TASM) 3.0. */ } else { fatal_int: + if (!emu_params->strict_mode) { + *(unsigned char*)®s.rax = 0; /* Function not supported. */ + *(unsigned short*)®s.rflags |= 1 << 0; /* CF=1. */ + goto done_int_call; + } fprintf(stderr, "fatal: unsupported int 0x%02x ah:%02x cs:%04x ip:%04x\n", int_num, ah, int_cs, int_ip); goto fatal; } + done_int_call: /* Return from the interrupt. */ SET_SREG(cs, int_cs); regs.rip = int_ip; @@ -3392,14 +4842,25 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam fprintf(stderr, "error: error opening --hlt-dump=... file for writing: %s\n", emu_params->hlt_dump_filename); } } - if (emu_params->is_hlt_ok && sregs.cs.selector >= PSP_PARA && (*(unsigned short*)®s.rflags & (1 << 9))) { /* IF == 1. */ + if (sregs.cs.selector >= PSP_PARA && (emu_params->is_hlt_ok || !emu_params->strict_mode)) { /* The 8253 timer chip increments the counter in each 1 / 1193182s * causing IRQ0 at each 65536th increment. kvikdos doesn't implement * any of this, but now we wait that approximate amount for `hlt' to * wake up. */ - /* This is not precise enough: poll(&pollfd0, 0, 55);. */ - usleep(54925); /* 54925 =~= 1000000.0 / (1193182.0 / 65536). */ + if (*(unsigned short*)®s.rflags & (1 << 9)) { /* IF == 1. */ + if (++hlt_spin_count >= 400) { + fprintf(stderr, "error: guest stalled in HLT loop, aborting in permissive mode.\n"); + return 1; + } + usleep(54925); /* 54925 =~= 1000000.0 / (1193182.0 / 65536). */ + } else { + if (++hlt_spin_count >= 2000) { + fprintf(stderr, "error: guest stalled in HLT loop (IF=0), aborting in permissive mode.\n"); + return 1; + } + usleep(1000); /* Prevent busy loop if guest issues tight hlt with IF=0. */ + } break; } else { fprintf(stderr, "fatal: unexpected hlt\n"); @@ -3407,10 +4868,14 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } } case KVM_EXIT_MMIO: - { const char mmio_len = run->mmio.len; + { const unsigned mmio_len = run->mmio.len; const unsigned addr = (unsigned)run->mmio.phys_addr; char highmsg[2]; /* CS:IP points to the instruction doing the memory operation (not after). */ + if (!(mmio_len == 1 || mmio_len == 2 || mmio_len == 4 || mmio_len == 8)) { + highmsg[0] = '\0'; + goto bad_memory_access; + } if (sizeof(run->mmio.phys_addr) > 4 && run->mmio.phys_addr >> (32 * (sizeof(run->mmio.phys_addr) > 4))) { /* Physical address is larger than 32 bits. */ highmsg[0] = '+'; highmsg[1] = '\0'; goto bad_memory_access; @@ -3420,13 +4885,18 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam /* Microsoft BASIC Professional Development System 7.10 linker pblink.exe. It overwrites length and program name with program name and args. */ /* This emulation is a little bit slow (because of the ioctl(... KVM_RUN ...) overhead), but it's called only less than 75 times at startup. */ memcpy((char*)mem + addr, run->mmio.data, mmio_len); + } else if (addr - 0xf0000U < 0x10000U) { /* BIOS ROM area probes (F000:0000..FFFF:FFFF). */ + if (run->mmio.is_write) { + /* Ignore writes to ROM area. */ + } else { + memset(run->mmio.data, 0xff, mmio_len); /* Typical ROM default value for unmodeled bytes. */ + } } else if (addr == 0xffffe && !run->mmio.is_write && mmio_len == 1) { /* BASIC programs compiled by Microsoft BASIC Professional Development System 7.10 compiler pbc.exe */ run->mmio.data[0] = 0xfc; /* Machine ID is regular OC (0xfc). Same as default in src/ints/bios.cpp in DOSBox 0.74. */ } else if (addr - 0xffff5U < 8U && !run->mmio.is_write && mmio_len == 1) { /* JWasm 2.11a jwasmr.exe */ run->mmio.data[0] = "01/01/92"[addr - 0xffff5U]; /* System BIOS date, same as default in src/ints/bios.cpp in DOSBox 0.74. */ } else if (addr == 0xfff7e && !run->mmio.is_write && mmio_len == 2) { /* Reading the first MCB pointer in INVARS (see int 0x21 call with ah == 0x52). Used by Microsoft Macro Assembler 6.00B driver masm.exe. */ *(unsigned short*)run->mmio.data = PROGRAM_MCB_PARA; - had_get_first_mcb = 1; } else if (addr < 0x400 && run->mmio.is_write && addr + mmio_len <= 0x400 && ((mmio_len == 2 && (addr & 1) == 0) || (mmio_len == 4 && (addr & 3) == 0))) { /* Set interrupt vector directly (not via int 0x21 call with ah == 0x25). */ /* Microsoft BASIC Professional Development System 7.10 compiler pbc.exe */ const unsigned char set_int_num = addr >> 2; @@ -3460,14 +4930,18 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam } else { highmsg[0] = '\0'; bad_memory_access: - fprintf(stderr, "fatal: KVM memory access denied phys_addr=%08x%s value=%08x%08x size=%d is_write=%d\n", addr, highmsg, ((unsigned*)run->mmio.data)[1], ((unsigned*)run->mmio.data)[0], mmio_len, run->mmio.is_write); + if (!emu_params->strict_mode) { + if (!run->mmio.is_write) memset(run->mmio.data, 0, mmio_len); + break; + } + fprintf(stderr, "fatal: KVM memory access denied phys_addr=%08x%s value=%08x%08x size=%u is_write=%u\n", addr, highmsg, ((unsigned*)run->mmio.data)[1], ((unsigned*)run->mmio.data)[0], mmio_len, run->mmio.is_write); goto fatal; } } ongoing_set_int = 0; /* No set_int operation ongoing. */ break; /* Just continue at following cs:ip. */ case KVM_EXIT_INTERNAL_ERROR: - fprintf(stderr, "fatal: KVM internal error suberror=%d\n", (unsigned)run->internal.suberror); + fprintf(stderr, "fatal: KVM internal error suberror=%u\n", (unsigned)run->internal.suberror); /* We get this for an int call if we don't map * (KVM_SET_USER_MEMORY_REGION) or initialize the interrupt table * properly. However, we can't continue the emulation, because KVM_RUN @@ -3476,7 +4950,7 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam /* if (run->internal.suberror == KVM_INTERNAL_ERROR_DELIVERY_EV && p[0] == (char)0xcd) {...} */ goto fatal; default: - fprintf(stderr, "fatal: unexpected KVM exit: reason=%d\n", run->exit_reason); + fprintf(stderr, "fatal: unexpected KVM exit: reason=%u\n", run->exit_reason); goto fatal; } } @@ -3491,20 +4965,52 @@ static unsigned char run_dos_prog(struct EmuState *emu, const char *prog_filenam return 0; /* Not reached. This is just to pacity owcc. */ } -static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filename, const char* const *args, DirState *dir_state, TtyState *tty_state, const EmuParams *emu_params, const char* const *envp0) { +static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filename, const char* const *args, DirState *dir_state, TtyState *tty_state, const EmuParams *emu_params, const char* const *envp0, const char* const *extra_env, unsigned extra_env_count) { unsigned char exit_code = 0; int batch_fd, got; char buf[4096], *p = buf, *p_line = buf, *q; char do_echo = 1; + char path_override[1024]; + char has_path_override = 0; + char goto_label[128]; + char have_goto = 0; + const char *batch_args[10]; + unsigned batch_argc = 0; + unsigned ai; + char *batch_env[256]; + unsigned batch_env_count = 0; + char *setlocal_env[8][256]; + unsigned setlocal_env_count[8]; + unsigned setlocal_depth = 0; + unsigned bi; + char batch_eof = 0; size_t size; - const char *dos_prog_abs = dir_state->dos_prog_abs; /* Of the .bat file. */ + const char *dos_prog_abs; + for (bi = 0; envp0 && envp0[bi] && batch_env_count < sizeof(batch_env) / sizeof(batch_env[0]); ++bi) { + char *cp = xstrdup(envp0[bi]); + if (!cp) { perror("fatal: xstrdup"); exit(252); } + batch_env[batch_env_count++] = cp; + } + for (bi = 0; extra_env && bi < extra_env_count && batch_env_count < sizeof(batch_env) / sizeof(batch_env[0]); ++bi) { + char *cp = xstrdup(extra_env[bi]); + if (!cp) { perror("fatal: xstrdup"); exit(252); } + batch_env[batch_env_count++] = cp; + } + if (batch_env_count < sizeof(batch_env) / sizeof(batch_env[0])) batch_env[batch_env_count] = NULL; + dos_prog_abs = dir_state->dos_prog_abs; /* Of the .bat file. */ dir_state->dos_prog_abs = NULL; - (void)args; + for (ai = 0; ai < 10; ++ai) batch_args[ai] = ""; + if (args) { + for (ai = 0; args[ai] && batch_argc < 9; ++ai) { + batch_args[++batch_argc] = args[ai]; + } + } if ((batch_fd = open(prog_filename, O_RDONLY)) < 0) { fprintf(stderr, "fatal: cannot open DOS .bat batch file: %s: %s\n", prog_filename, strerror(errno)); exit(252); } for (;;) { + if (batch_eof && p_line == p) break; size = buf + sizeof(buf) - p; if (size == 0) { line_too_long: fprintf(stderr, "fatal: line too long in DOS .bat batch file: %s\n", prog_filename); @@ -3525,7 +5031,7 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena p_line = q; } /* MS-DOS 6.22 doesn't recognize just \n as line terminator, but we do. */ - for (; q != p && *q != '\r' && *q != '\n'; ++q) {} + for (; q != p && *q != '\r' && *q != '\n' && *q != '\x1a'; ++q) {} if (q == p) { /* End-of-line not yet read. */ /* If >= 75% of the buffer is filled with an unfinished line, report an * error. This is to make sure that we're not spending most of our time in @@ -3537,17 +5043,83 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena p = q = buf + (q - p_line); p_line = buf; } - } else { /* End-of-line reached, line is p_line...q. */ - char c, c_endarg, do_echo_line = do_echo; - char *r, *arg, *endarg; - unsigned cmd_size; - *q = '\0'; /* Make it ASCIIZ (terminated by \0). */ - if (DEBUG) fprintf(stderr, "debug: batch line: (%s)\n", p_line); + } else { /* End-of-line reached, line is p_line...q. */ + char c, c_endarg, do_echo_line = do_echo; + char *r, *arg, *endarg; + unsigned cmd_size; + char *q_src = q; + char rewound = 0; + char cmdline[4096]; + char pipe_right[4096]; + int pipe_fd = -1, pipe_save_out = -1, pipe_save_in = -1; + char pipe_stage = 0; + int saved_stdin = -1, saved_stdout = -1, saved_stderr = -1; + char has_redir_in = 0, has_redir_out = 0, has_redir_err = 0, append_out = 0, append_err = 0; + char redir_in[DOS_PATH_SIZE + 4], redir_out[DOS_PATH_SIZE + 4], redir_err[DOS_PATH_SIZE + 4]; + char cleaned[4096]; + redir_in[0] = redir_out[0] = redir_err[0] = '\0'; + if (*q == '\x1a') batch_eof = 1; + *q = '\0'; /* Make it ASCIIZ (terminated by \0). */ + if (DEBUG) fprintf(stderr, "debug: batch line: (%s)\n", p_line); + if (*p_line == ':') { /* label */ + if (have_goto) { + const char *ln = p_line + 1; + while (*ln == ' ' || *ln == '\t') ++ln; + if (is_same_ascii_nocase(ln, goto_label, strlen(goto_label) + 1)) have_goto = 0; + } + goto done_command; + } + if (have_goto) goto done_command; + { /* %VAR% substitution. */ + char subst[4096]; + char *d = subst; + const char *s2 = p_line; + while (*s2 && d + 2 < subst + sizeof(subst)) { + if (*s2 == '%' && s2[1] >= '0' && s2[1] <= '9') { + const char *av = batch_args[s2[1] - '0']; + while (*av && d + 1 < subst + sizeof(subst)) *d++ = *av++; + s2 += 2; + continue; + } else if (*s2 == '%' && s2[1] == '%') { + *d++ = '%'; + s2 += 2; + continue; + } + if (*s2 == '%') { + const char *e2 = strchr(s2 + 1, '%'); + if (e2 && e2 > s2 + 1) { + char name[128]; + size_t ns = e2 - (s2 + 1); + unsigned ei; + if (ns >= sizeof(name)) ns = sizeof(name) - 1; + memcpy(name, s2 + 1, ns); + name[ns] = '\0'; + for (ei = 0; ei < ns; ++ei) if (name[ei] - 'a' + 0U <= 'z' - 'a' + 0U) name[ei] &= ~32; + for (ei = 0; ei < batch_env_count; ++ei) { + const char *ev = batch_env[ei]; + const char *eq = strchr(ev, '='); + if (eq && (size_t)(eq - ev) == ns && memcmp(ev, name, ns) == 0) { + const char *vv = eq + 1; + while (*vv && d + 1 < subst + sizeof(subst)) *d++ = *vv++; + break; + } + } + s2 = e2 + 1; + continue; + } + } + *d++ = *s2++; + } + *d = '\0'; + strncpy(cmdline, subst, sizeof(cmdline) - 1); + cmdline[sizeof(cmdline) - 1] = '\0'; + p_line = cmdline; + q = p_line + strlen(p_line); + } for (; *p_line == ' ' || *p_line == '\t'; ++p_line) {} /* MS-DOS 6.22 doesn't ignore leading whitespace, at least not before `rem'. */ if (*p_line == '@') { do_echo_line = 0; ++p_line; } if (do_echo_line) { - const char *current_dir = dir_state->current_dir[dir_state->drive - 'A']; - fprintf(stdout, "\r\n%c:%s>%s\r\n", dir_state->drive, *current_dir == '\0' ? "\\" : current_dir, p_line); + fprintf(stdout, "%s\r\n", p_line); fflush(stdout); } for (r = p_line; ((c = *r) + 0U > 31U && c != '\x7f') || c == ' ' || c == '\t'; ++r) {} @@ -3555,14 +5127,100 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena fprintf(stderr, "fatal: invalid character in DOS .bat batch file: %s\n", prog_filename); exit(252); } - if (strchr(p_line, '<') || strchr(p_line, '>') || strchr(p_line, '|')) { - fprintf(stderr, "fatal: redirection not supported in DOS .bat batch file: %s\n", prog_filename); - exit(252); - } - if (strchr(p_line, '%')) { - fprintf(stderr, "fatal: percent substitution not supported in DOS .bat batch file: %s\n", prog_filename); - exit(252); /* !! add support */ + { /* Parse simple redirections: <, >, >>, 2>, 2>> */ + const char *rs = p_line; + char *cd = cleaned; + while (*rs && cd + 2 < cleaned + sizeof(cleaned)) { + if ((rs[0] == '2' || rs[0] == '1') && rs[1] == '>' && rs[2] == '&' && (rs[3] == '1' || rs[3] == '2')) { + int from_fd = rs[0] - '0', to_fd = rs[3] - '0'; + int dfd = dup(to_fd); + if (dfd >= 0) { dup2(dfd, from_fd); close(dfd); } + rs += 4; + continue; + } + if ((rs[0] == '1' || rs[0] == '2') && rs[1] == '>') { + char is_err = (rs[0] == '2'); + char *dst = is_err ? redir_err : redir_out; + char *dst_end = (is_err ? redir_err : redir_out) + sizeof(redir_out) - 1; + rs += 2; + if (is_err) append_err = (*rs == '>'); + else append_out = (*rs == '>'); + if (*rs == '>') ++rs; + while (*rs == ' ' || *rs == '\t') ++rs; + if (is_err) has_redir_err = 1; else has_redir_out = 1; + if (*rs == '"') { + ++rs; + while (*rs && *rs != '"' && dst != dst_end) *dst++ = *rs++; + if (*rs == '"') ++rs; + } else { + while (*rs && *rs != ' ' && *rs != '\t' && *rs != '<' && *rs != '>' && dst != dst_end) *dst++ = *rs++; + } + *dst = '\0'; + continue; + } + if (rs[0] == '2' && rs[1] == '>') { + char *dst = redir_err, *dst_end = redir_err + sizeof(redir_err) - 1; + rs += 2; + append_err = (*rs == '>'); + if (append_err) ++rs; + while (*rs == ' ' || *rs == '\t') ++rs; + has_redir_err = 1; + if (*rs == '"') { + ++rs; + while (*rs && *rs != '"' && dst != dst_end) *dst++ = *rs++; + if (*rs == '"') ++rs; + } else { + while (*rs && *rs != ' ' && *rs != '\t' && *rs != '<' && *rs != '>' && dst != dst_end) *dst++ = *rs++; + } + *dst = '\0'; + continue; + } else if (*rs == '>' || *rs == '<') { + char is_out = (*rs == '>'); + char *dst = is_out ? redir_out : redir_in; + char *dst_end = dst + DOS_PATH_SIZE + 4 - 1; + char *app = is_out ? &append_out : &append_err; /* append_err ignored for input */ + ++rs; + if (is_out && *rs == '>') { *app = 1; ++rs; } else *app = 0; + while (*rs == ' ' || *rs == '\t') ++rs; + if (is_out) has_redir_out = 1; else has_redir_in = 1; + if (*rs == '"') { + ++rs; + while (*rs && *rs != '"' && dst != dst_end) *dst++ = *rs++; + if (*rs == '"') ++rs; + } else { + while (*rs && *rs != ' ' && *rs != '\t' && *rs != '<' && *rs != '>' && dst != dst_end) *dst++ = *rs++; + } + *dst = '\0'; + continue; + } + *cd++ = *rs++; + } + *cd = '\0'; + { /* Single pipeline: left | right */ + char *pp = cleaned; + while (*pp && *pp != '|') ++pp; + if (*pp == '|') { + char *ls_end; + char *rs2; + *pp++ = '\0'; + while (*pp == ' ' || *pp == '\t') ++pp; + strncpy(pipe_right, pp, sizeof(pipe_right) - 1); + pipe_right[sizeof(pipe_right) - 1] = '\0'; + ls_end = cleaned + strlen(cleaned); + while (ls_end != cleaned && (ls_end[-1] == ' ' || ls_end[-1] == '\t')) *--ls_end = '\0'; + rs2 = pipe_right; + while (*rs2 == ' ' || *rs2 == '\t') ++rs2; + if (rs2 != pipe_right) memmove(pipe_right, rs2, strlen(rs2) + 1); + pipe_stage = 1; + } + } + strncpy(cmdline, cleaned, sizeof(cmdline) - 1); + cmdline[sizeof(cmdline) - 1] = '\0'; + p_line = cmdline; + q = p_line + strlen(p_line); } + /* Allow unresolved %...% to pass through as literal text. */ + reparse_command: /* MS-DOS 6.22 terminator characters. */ for (r = p_line; (c = *r) != '\0' && c != ' ' && c != '\t' && c != '+' && c != '=' && c != '[' && c != ']' && c != '"' && c != '\\' && c != ':' && c != ';' /* && c != '|' && c != '<' && c != '>' */ && c != ',' && c != '.' && c != '/'; ++r) {} cmd_size = r - p_line; @@ -3574,6 +5232,53 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena for (endarg = q; endarg != r && (endarg[-1] == ' ' || endarg[-1] == '\t'); --endarg) {} c_endarg = *endarg; *endarg = '\0'; /* MS-DOS 6.22 passes trailing spaces to .com or .exe programs, but DOSBox 0.74-4 doesn't. We don't. This also affects the `echo' command in DOSBox 0.74-4, but for that we add trailing spaces. */ + { /* Apply redirections for this command. */ + int fd; + if (pipe_stage == 1) { + char pipe_tmp[] = "/tmp/kvikdos_pipeXXXXXX"; + pipe_fd = mkstemp(pipe_tmp); + if (pipe_fd < 0) { + fprintf(stderr, "Cannot create pipe temp\r\n"); + exit_code = 1; + goto done_command; + } + unlink(pipe_tmp); + pipe_save_out = dup(1); + dup2(pipe_fd, 1); + } + if (has_redir_in) { + if (*get_linux_filename_r(redir_in, dir_state, fnbuf, NULL) == '\0' || (fd = open_with_case_fallback(fnbuf, O_RDONLY, 0666)) < 0) { + fprintf(stderr, "File not found - %s\r\n", redir_in); + exit_code = 1; + goto done_command; + } + saved_stdin = dup(0); + dup2(fd, 0); + close(fd); + } + if (has_redir_out) { + int flags = O_WRONLY | O_CREAT | (append_out ? O_APPEND : O_TRUNC); + if (*get_linux_filename_r(redir_out, dir_state, fnbuf, NULL) == '\0' || (fd = open_with_case_fallback(fnbuf, flags, 0666)) < 0) { + fprintf(stderr, "Access denied - %s\r\n", redir_out); + exit_code = 1; + goto done_command; + } + saved_stdout = dup(1); + dup2(fd, 1); + close(fd); + } + if (has_redir_err) { + int flags = O_WRONLY | O_CREAT | (append_err ? O_APPEND : O_TRUNC); + if (*get_linux_filename_r(redir_err, dir_state, fnbuf, NULL) == '\0' || (fd = open_with_case_fallback(fnbuf, flags, 0666)) < 0) { + fprintf(stderr, "Access denied - %s\r\n", redir_err); + exit_code = 1; + goto done_command; + } + saved_stderr = dup(2); + dup2(fd, 2); + close(fd); + } + } if (cmd_size == 1 && (p_line[0] & ~32) - 'A' + 0U <= 'Z' - 'A' + 0U) { /* Ignore arguments arg...endarg, like MS-DOS 6.22 does. */ char drive = p_line[0] & ~32; @@ -3598,22 +5303,60 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena } else if (0 == memcmp(arg, "off", 4)) { do_echo = 0; } else { - ++r; /* Skip over a single space, '.', ':' etc. */ - fwrite(r, 1, endarg - r, stdout); - fwrite("\r\n", 1, 2, stdout); + fprintf(stdout, "%s\r\n", arg); } } fflush(stdout); exit_code = 0; } else if (0 == memcmp(p_line, "set", cmd_size)) { if (*r != '\0') { - fprintf(stderr, "fatal: changing environment variables not supported: %s\n", r); - exit(252); /* !! TODO(pts): Add support, change a copy of envp0. */ + char *sv = arg; + char *name; + char *eq = strchr(sv, '='); + char *val; + unsigned ei; + char newev[1024]; + while (*sv == ' ' || *sv == '\t') ++sv; + if (!eq || eq == sv) { + fprintf(stderr, "Syntax error - %s\r\n", arg); + exit_code = 1; + goto done_command; + } + name = sv; + val = eq + 1; + *eq = '\0'; + for (; *sv; ++sv) if (*sv - 'a' + 0U <= 'z' - 'a' + 0U) *sv &= ~32; + snprintf(newev, sizeof(newev), "%s=%s", name, val); + for (ei = 0; ei < batch_env_count; ++ei) { + char *ev = batch_env[ei]; + char *ee = strchr(ev, '='); + if (ee && strcmp(ev, sv) == 0) { /* exact with '=' cut, impossible */ } + if (ee && (size_t)(ee - ev) == strlen(name) && memcmp(ev, name, ee - ev) == 0) { + free(batch_env[ei]); + batch_env[ei] = xstrdup(newev); + if (!batch_env[ei]) { perror("fatal: xstrdup"); exit(252); } + break; + } + } + if (ei == batch_env_count) { + if (batch_env_count >= sizeof(batch_env) / sizeof(batch_env[0])) { + fprintf(stderr, "fatal: too many set variables\r\n"); + exit(252); + } + batch_env[batch_env_count] = xstrdup(newev); + if (!batch_env[batch_env_count]) { perror("fatal: xstrdup"); exit(252); } + ++batch_env_count; + } + if (strcmp(name, "PATH") == 0) { + strncpy(path_override, val, sizeof(path_override) - 1); + path_override[sizeof(path_override) - 1] = '\0'; + has_path_override = 1; + } + *eq = '='; } else { - const char* const *envp = envp0; - if (*envp) { - for (; *envp; ++envp) { - fprintf(stdout, "%s\r\n", *envp); + if (batch_env_count) { + for (bi = 0; bi < batch_env_count; ++bi) { + fprintf(stdout, "%s\r\n", batch_env[bi]); } } else { fprintf(stdout, "$=\r\n"); /* See run_dos_prog above. */ @@ -3621,6 +5364,91 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena fflush(stdout); } exit_code = 0; + } else if (0 == memcmp(p_line, "for", cmd_size)) { + char *fp = arg; + char items[1024], body[2048]; + char *ip, *bp; + char for_var = '\0'; + while (*fp == ' ' || *fp == '\t') ++fp; + if (fp[0] == '%' && fp[1] == '%' && fp[2] != '\0') { for_var = fp[2]; fp += 3; } + else if (fp[0] == '%' && fp[1] != '\0') { for_var = fp[1]; fp += 2; } + while (*fp == ' ' || *fp == '\t') ++fp; + if ((fp[0] | 32) != 'i' || (fp[1] | 32) != 'n') { exit_code = 1; goto done_command; } + fp += 2; + while (*fp == ' ' || *fp == '\t') ++fp; + if (*fp != '(') { exit_code = 1; goto done_command; } + ++fp; + ip = items; + while (*fp && *fp != ')' && ip + 1 < items + sizeof(items)) *ip++ = *fp++; + *ip = '\0'; + if (*fp != ')') { exit_code = 1; goto done_command; } + ++fp; + while (*fp == ' ' || *fp == '\t') ++fp; + if ((fp[0] | 32) != 'd' || (fp[1] | 32) != 'o') { exit_code = 1; goto done_command; } + fp += 2; + while (*fp == ' ' || *fp == '\t') ++fp; + strncpy(body, fp, sizeof(body) - 1); + body[sizeof(body) - 1] = '\0'; + bp = items; + exit_code = 0; + while (*bp) { + char tok[256], *tp = tok; + char exp[2048], *ep = exp; + const char *bs = body; + char tmpbat[] = "/tmp/kvikdos_forXXXXXX"; + int tfd; + while (*bp == ' ' || *bp == '\t') ++bp; + if (!*bp) break; + while (*bp && *bp != ' ' && *bp != '\t' && tp + 1 < tok + sizeof(tok)) *tp++ = *bp++; + *tp = '\0'; + while (*bs && ep + 2 < exp + sizeof(exp)) { + if (bs[0] == '%' && bs[1] == '%' && upper_ascii(bs[2]) == upper_ascii(for_var)) { + const char *tv = tok; + while (*tv && ep + 1 < exp + sizeof(exp)) *ep++ = *tv++; + bs += 3; + } else if (bs[0] == '%' && upper_ascii(bs[1]) == upper_ascii(for_var)) { + const char *tv = tok; + while (*tv && ep + 1 < exp + sizeof(exp)) *ep++ = *tv++; + bs += 2; + } else { + *ep++ = *bs++; + } + } + *ep++ = '\n'; + *ep = '\0'; + tfd = mkstemp(tmpbat); + if (tfd < 0) { exit_code = 1; break; } + (void)!write(tfd, exp, strlen(exp)); + close(tfd); + exit_code = run_dos_batch(emu, tmpbat, NULL, dir_state, tty_state, emu_params, envp0, (const char* const*)batch_env, batch_env_count); + unlink(tmpbat); + } + } else if (0 == memcmp(p_line, "setlocal", cmd_size)) { + unsigned ei; + if (setlocal_depth >= sizeof(setlocal_env) / sizeof(setlocal_env[0])) { + exit_code = 1; + } else { + setlocal_env_count[setlocal_depth] = batch_env_count; + for (ei = 0; ei < batch_env_count; ++ei) { + setlocal_env[setlocal_depth][ei] = xstrdup(batch_env[ei]); + if (!setlocal_env[setlocal_depth][ei]) { perror("fatal: xstrdup"); exit(252); } + } + ++setlocal_depth; + exit_code = 0; + } + } else if (0 == memcmp(p_line, "endlocal", cmd_size)) { + unsigned ei; + if (setlocal_depth == 0) { + exit_code = 1; + } else { + while (batch_env_count) free(batch_env[--batch_env_count]); + --setlocal_depth; + for (ei = 0; ei < setlocal_env_count[setlocal_depth]; ++ei) { + batch_env[batch_env_count++] = setlocal_env[setlocal_depth][ei]; + setlocal_env[setlocal_depth][ei] = NULL; + } + exit_code = 0; + } } else if (0 == memcmp(p_line, "ver", cmd_size)) { if (*r != '\0') { fprintf(stderr, "Too many parameters - %s\r\n", r); /* Like: MS-DOS 6.22. */ @@ -3650,30 +5478,287 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena break; } } else if (0 == memcmp(p_line, "cd", cmd_size)) { - if (*r != '\0') { - fprintf(stderr, "fatal: changing current directory not supported: %s\n", r); - exit(252); /* !! TODO(pts): Add support, change a copy of envp0. */ - } else { + if (*r == '\0') { const char *current_dir = dir_state->current_dir[dir_state->drive - 'A']; fprintf(stdout, "%c:%s\r\n", dir_state->drive, *current_dir == '\0' ? "\\" : current_dir); fflush(stdout); exit_code = 0; + } else { + char tmp[DOS_PATH_SIZE]; + char *t = tmp; + const char *s = arg; + char drive = dir_state->drive; + char is_root_abs = 0; + struct stat st; + if ((s[0] & ~32) - 'A' + 0U < DRIVE_COUNT && s[1] == ':') { + drive = s[0] & ~32; + s += 2; + if (*s == '\\' || *s == '/') { ++s; is_root_abs = 1; } + } else if (*s == '\\' || *s == '/') { + ++s; + is_root_abs = 1; + } + if (is_root_abs && t + 1 < tmp + sizeof(tmp)) { + *t++ = '\\'; + } + while (*s && t + 2 < tmp + sizeof(tmp)) { + char c3 = *s++; + if (c3 == '/') c3 = '\\'; + *t++ = (c3 - 'a' + 0U <= 'z' - 'a' + 0U) ? (c3 & ~32) : c3; + } + if (t != tmp && t[-1] != '\\') *t++ = '\\'; + *t = '\0'; + if (tmp[0] == '\0') strcpy(tmp, "\\"); + { + char dos_abs[DOS_PATH_SIZE + 4]; + snprintf(dos_abs, sizeof(dos_abs), "%c:%s", drive, tmp); + if (*get_linux_filename_r(dos_abs, dir_state, fnbuf, NULL) == '\0' || stat(fnbuf, &st) != 0 || !S_ISDIR(st.st_mode)) { + fprintf(stderr, "Invalid directory - %s\r\n", arg); + exit_code = 1; + } else { + copy_cstr0(dir_state->current_dir[drive - 'A'], sizeof(dir_state->current_dir[drive - 'A']), tmp); + dir_state->drive = drive; + exit_code = 0; + } + } } } else if (0 == memcmp(p_line, "path", cmd_size)) { - const char* const *envp; if (*r != '\0') { - fprintf(stderr, "fatal: changing environment variables (PATH) not supported: %s\n", r); - exit(252); /* !! TODO(pts): Add support, change a copy of envp0. */ + while (*arg == ' ' || *arg == '\t') ++arg; + if (*arg == '=') ++arg; + while (*arg == ' ' || *arg == '\t') ++arg; + strncpy(path_override, arg, sizeof(path_override) - 1); + path_override[sizeof(path_override) - 1] = '\0'; + has_path_override = 1; + { /* sync PATH in batch env */ + char pe[sizeof(path_override) + 6]; + unsigned ei; + memcpy(pe, "PATH=", 5); + strcpy(pe + 5, path_override); + for (ei = 0; ei < batch_env_count; ++ei) { + char *eq = strchr(batch_env[ei], '='); + if (eq && (size_t)(eq - batch_env[ei]) == 4 && + ((batch_env[ei][0] | 32) == 'p') && + ((batch_env[ei][1] | 32) == 'a') && + ((batch_env[ei][2] | 32) == 't') && + ((batch_env[ei][3] | 32) == 'h')) { + free(batch_env[ei]); + batch_env[ei] = xstrdup(pe); + break; + } + } + if (ei == batch_env_count && batch_env_count < sizeof(batch_env) / sizeof(batch_env[0])) { + batch_env[batch_env_count++] = xstrdup(pe); + } + if (batch_env_count < sizeof(batch_env) / sizeof(batch_env[0])) batch_env[batch_env_count] = NULL; + } + exit_code = 0; + goto done_command; } - for (envp = envp0; *envp && strncmp(*envp, "PATH=", 5) != 0; ++envp) {} - if (*envp) { - fprintf(stdout, "%s\r\n", *envp); + if (has_path_override) { + fprintf(stdout, "PATH=%s\r\n", path_override); exit_code = 0; } else { - fprintf(stdout, "No Path\r\n\r\n"); /* MS-DOS 6.22. */ - exit_code = 1; + { + const char *path_value = getenv_prefix_nocase0("PATH=", (const char* const*)batch_env); + if (path_value) { + fprintf(stdout, "PATH=%s\r\n", path_value); + exit_code = 0; + } else { + fprintf(stdout, "No Path\r\n\r\n"); /* MS-DOS 6.22. */ + exit_code = 1; + } + } } fflush(stdout); + } else if (0 == memcmp(p_line, "goto", cmd_size)) { + const char *gl = arg; + while (*gl == ' ' || *gl == '\t') ++gl; + if (*gl == ':') ++gl; + if (*gl == '\0') { + fprintf(stderr, "Label not found - %s\r\n", arg); + exit_code = 1; + } else if (is_same_ascii_nocase(gl, "eof", 4)) { + break; /* End current batch context. */ + } else { + size_t gls = strlen(gl); + if (gls >= sizeof(goto_label)) gls = sizeof(goto_label) - 1; + memcpy(goto_label, gl, gls); + goto_label[gls] = '\0'; + have_goto = 1; + lseek(batch_fd, 0, SEEK_SET); + p = p_line = buf; + rewound = 1; + exit_code = 0; + } + } else if (0 == memcmp(p_line, "shift", cmd_size)) { + for (ai = 1; ai < 9; ++ai) batch_args[ai] = batch_args[ai + 1]; + batch_args[9] = ""; + if (batch_argc > 0) --batch_argc; + exit_code = 0; + } else if (0 == memcmp(p_line, "call", cmd_size)) { + while (*arg == ' ' || *arg == '\t') ++arg; + if (*arg) { + memmove(p_line, arg, strlen(arg) + 1); + goto reparse_command; + } + exit_code = 0; + } else if (0 == memcmp(p_line, "del", cmd_size) || 0 == memcmp(p_line, "erase", cmd_size) || 0 == memcmp(p_line, "delete", cmd_size)) { + if (*arg == '\0') { + fprintf(stderr, "Required parameter missing\r\n"); + exit_code = 1; + } else { + char *a = arg; + while (*a) { + char *b = a; + while (*b && *b != ' ' && *b != '\t') ++b; + if (*b) *b++ = '\0'; + if (strchr(a, '*') || strchr(a, '?')) { + const char *pat_base = get_dos_basename(a); + size_t pat_dir_size = pat_base - a; + char dos_dir[DOS_PATH_SIZE + 4], linux_dir[LINUX_PATH_SIZE]; + const char *scan_dir; + DIR *dd; + struct dirent *de; + int removed; + if (pat_dir_size >= sizeof(dos_dir)) pat_dir_size = sizeof(dos_dir) - 1; + memcpy(dos_dir, a, pat_dir_size); + dos_dir[pat_dir_size] = '\0'; + if (dos_dir[0] == '\0') strcpy(dos_dir, "."); + scan_dir = linux_dir; + if (*get_linux_filename_r(dos_dir, dir_state, linux_dir, NULL) == '\0' || !(dd = opendir_with_case_fallback(linux_dir, fnbuf2, sizeof(fnbuf2)))) { + exit_code = 1; + } else { + if (fnbuf2[0] != '\0') scan_dir = fnbuf2; + removed = 0; + while ((de = readdir(dd)) != NULL) { + char full[LINUX_PATH_SIZE]; + const char *nm = de->d_name; + struct stat st; + size_t dlen; + if (nm[0] == '.') continue; + if (!dos_wildcard_match(pat_base, nm)) continue; + dlen = strlen(scan_dir); + if (dlen + 1 + strlen(nm) + 1 >= sizeof(full)) continue; + memcpy(full, scan_dir, dlen); + if (dlen && full[dlen - 1] != '/') full[dlen++] = '/'; + strcpy(full + dlen, nm); + if (stat(full, &st) == 0 && S_ISREG(st.st_mode) && unlink(full) == 0) removed = 1; + } + closedir(dd); + if (!removed) exit_code = 1; + } + } else if (*get_linux_filename_r(a, dir_state, fnbuf, NULL) == '\0' || unlink(fnbuf) != 0) { + exit_code = 1; + } + while (*b == ' ' || *b == '\t') ++b; + a = b; + } + } + } else if (0 == memcmp(p_line, "mkdir", cmd_size) || 0 == memcmp(p_line, "md", cmd_size)) { + if (*arg == '\0') { + fprintf(stderr, "Required parameter missing\r\n"); + exit_code = 1; + } else if (*get_linux_filename_r(arg, dir_state, fnbuf, NULL) == '\0' || mkdir(fnbuf, 0777) != 0) { + exit_code = 1; + } else { + exit_code = 0; + } + } else if (0 == memcmp(p_line, "copy", cmd_size)) { + char *a = arg, *b, *c; + int fd1 = -1, fd2 = -1; + while (*a == ' ' || *a == '\t') ++a; + b = a; + while (*b && *b != ' ' && *b != '\t') ++b; + if (*b) *b++ = '\0'; + while (*b == ' ' || *b == '\t') ++b; + c = b; + while (*c && *c != ' ' && *c != '\t') ++c; + *c = '\0'; + if (*a == '\0' || *b == '\0') { + fprintf(stderr, "Required parameter missing\r\n"); + exit_code = 1; + } else { + if (strchr(a, '*') || strchr(a, '?')) { + const char *pat_base = get_dos_basename(a); + size_t pat_dir_size = pat_base - a; + char dos_dir[DOS_PATH_SIZE + 4], linux_src_dir[LINUX_PATH_SIZE]; + const char *src_scan_dir = linux_src_dir; + DIR *dd = NULL; + struct dirent *de; + struct stat dst_st; + int copied = 0; + if (pat_dir_size >= sizeof(dos_dir)) pat_dir_size = sizeof(dos_dir) - 1; + memcpy(dos_dir, a, pat_dir_size); + dos_dir[pat_dir_size] = '\0'; + if (dos_dir[0] == '\0') strcpy(dos_dir, "."); + if (*get_linux_filename_r(dos_dir, dir_state, linux_src_dir, NULL) == '\0' || + *get_linux_filename_r(b, dir_state, fnbuf2, NULL) == '\0' || + stat_with_case_fallback(fnbuf2, &dst_st, 0) != 0 || !S_ISDIR(dst_st.st_mode) || + (dd = opendir_with_case_fallback(linux_src_dir, exec_fnbuf, sizeof(exec_fnbuf))) == NULL) { + exit_code = 1; + } else { + if (exec_fnbuf[0] != '\0') src_scan_dir = exec_fnbuf; + while ((de = readdir(dd)) != NULL) { + char src_full[LINUX_PATH_SIZE], dst_full[LINUX_PATH_SIZE]; + const char *nm = de->d_name; + struct stat st; + size_t sdl, ddl; + if (nm[0] == '.') continue; + if (!dos_wildcard_match(pat_base, nm)) continue; + sdl = strlen(src_scan_dir); + ddl = strlen(fnbuf2); + if (sdl + 1 + strlen(nm) + 1 >= sizeof(src_full) || ddl + 1 + strlen(nm) + 1 >= sizeof(dst_full)) continue; + memcpy(src_full, src_scan_dir, sdl); + if (sdl && src_full[sdl - 1] != '/') src_full[sdl++] = '/'; + strcpy(src_full + sdl, nm); + if (stat_with_case_fallback(src_full, &st, 0) != 0 || !S_ISREG(st.st_mode)) continue; + memcpy(dst_full, fnbuf2, ddl); + if (ddl && dst_full[ddl - 1] != '/') dst_full[ddl++] = '/'; + strcpy(dst_full + ddl, nm); + fd1 = open_with_case_fallback(src_full, O_RDONLY, 0666); + fd2 = open_with_case_fallback(dst_full, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd1 < 0 || fd2 < 0) { + if (fd1 >= 0) close(fd1); + if (fd2 >= 0) close(fd2); + exit_code = 1; + break; + } else { + char cbuf[4096]; + int n; + while ((n = read(fd1, cbuf, sizeof(cbuf))) > 0) { + if (write(fd2, cbuf, n) != n) { exit_code = 1; break; } + } + if (n < 0) exit_code = 1; + close(fd1); + close(fd2); + fd1 = fd2 = -1; + if (exit_code) break; + copied = 1; + } + } + closedir(dd); + if (!copied) exit_code = 1; + } + } else if (*get_linux_filename_r(a, dir_state, fnbuf, NULL) == '\0' || *get_linux_filename_r(b, dir_state, fnbuf2, NULL) == '\0') { + exit_code = 1; + } else if ((fd1 = open_with_case_fallback(fnbuf, O_RDONLY, 0666)) < 0 || (fd2 = open_with_case_fallback(fnbuf2, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) { + if (fd1 >= 0) close(fd1); + if (fd2 >= 0) close(fd2); + exit_code = 1; + } else { + char cbuf[4096]; + int n; + exit_code = 0; + while ((n = read(fd1, cbuf, sizeof(cbuf))) > 0) { + if (write(fd2, cbuf, n) != n) { exit_code = 1; break; } + } + if (n < 0) exit_code = 1; + close(fd1); + close(fd2); + fd1 = fd2 = -1; + } + } } else if (0 == memcmp(p_line, "pause", cmd_size)) { unsigned short dummy_ax; /* Ignore arguments arg...endarg, like MS-DOS 6.22 does. */ @@ -3686,6 +5771,59 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena if (c2 != '\0') { fprintf(stderr, "Too many parameters - %s\r\n", arg2 + 1); exit_code = 1; + } else if (*arg == '\0') { /* Read from stdin (useful for pipelines). */ + char fbuf[4096]; + while ((got = read(0, fbuf, sizeof(fbuf))) > 0) (void)!write(1, fbuf, got); + exit_code = got < 0; + } else if (strchr(arg, '*') || strchr(arg, '?')) { + const char *pat_base = get_dos_basename(arg); + size_t pat_dir_size = pat_base - arg; + char dos_dir[DOS_PATH_SIZE + 4], linux_dir[LINUX_PATH_SIZE]; + const char *scan_dir; + DIR *dd = NULL; + struct dirent *de; + int any = 0; + if (pat_dir_size >= sizeof(dos_dir)) pat_dir_size = sizeof(dos_dir) - 1; + memcpy(dos_dir, arg, pat_dir_size); + dos_dir[pat_dir_size] = '\0'; + if (dos_dir[0] == '\0') strcpy(dos_dir, "."); + scan_dir = linux_dir; + if (*get_linux_filename_r(dos_dir, dir_state, linux_dir, NULL) == '\0' || (dd = opendir_with_case_fallback(linux_dir, fnbuf2, sizeof(fnbuf2))) == NULL) { + fprintf(stderr, "File not found - %s\r\n", arg); + exit_code = 1; + } else { + if (fnbuf2[0] != '\0') scan_dir = fnbuf2; + exit_code = 0; + while ((de = readdir(dd)) != NULL) { + char full[LINUX_PATH_SIZE]; + struct stat st; + int fd; + size_t dlen; + char fbuf[4096], *ep; + if (de->d_name[0] == '.') continue; + if (!dos_wildcard_match(pat_base, de->d_name)) continue; + dlen = strlen(scan_dir); + if (dlen + 1 + strlen(de->d_name) + 1 >= sizeof(full)) continue; + memcpy(full, scan_dir, dlen); + if (dlen && full[dlen - 1] != '/') full[dlen++] = '/'; + strcpy(full + dlen, de->d_name); + if (stat_with_case_fallback(full, &st, 0) != 0 || !S_ISREG(st.st_mode)) continue; + fd = open_with_case_fallback(full, O_RDONLY, 0666); + if (fd < 0) { exit_code = 1; continue; } + any = 1; + while ((got = read(fd, fbuf, sizeof(fbuf))) > 0) { + if ((ep = memchr(fbuf, '\x1a', got)) != NULL) { (void)!write(1, fbuf, ep - fbuf); break; } + (void)!write(1, fbuf, got); + } + if (got < 0) exit_code = 1; + close(fd); + } + closedir(dd); + if (!any) { + fprintf(stderr, "File not found - %s\r\n", arg); + exit_code = 1; + } + } } else { /* Now filename is in arg. */ int fd = open_dos_file(arg, dos_prog_abs, O_RDONLY, dir_state); if (fd < 0) { @@ -3717,35 +5855,118 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena memcmp(p_line, "call", cmd_size) == 0 || memcmp(p_line, "cd", cmd_size) == 0 || memcmp(p_line, "choice", cmd_size) == 0 || - memcmp(p_line, "copy", cmd_size) == 0 || - memcmp(p_line, "del", cmd_size) == 0 || - memcmp(p_line, "delete", cmd_size) == 0 || - memcmp(p_line, "erase", cmd_size) == 0 || - memcmp(p_line, "goto", cmd_size) == 0 || memcmp(p_line, "help", cmd_size) == 0 || - memcmp(p_line, "if", cmd_size) == 0 || + 0 || memcmp(p_line, "loadhigh", cmd_size) == 0 || memcmp(p_line, "lh", cmd_size) == 0 || - memcmp(p_line, "mkdir", cmd_size) == 0 || - memcmp(p_line, "md", cmd_size) == 0 || memcmp(p_line, "rmdir", cmd_size) == 0 || memcmp(p_line, "rd", cmd_size) == 0 || memcmp(p_line, "rem", cmd_size) == 0 || memcmp(p_line, "rename", cmd_size) == 0 || memcmp(p_line, "ren", cmd_size) == 0 || - memcmp(p_line, "shift", cmd_size) == 0 || memcmp(p_line, "subst", cmd_size) == 0) { *r = '\0'; - fprintf(stderr, "fatal: DOS command not supported: %s\n", p_line); - exit(252); + if (emu_params->strict_mode) { + fprintf(stderr, "fatal: DOS command not supported: %s\n", p_line); + exit(252); + } else { + fprintf(stderr, "Unsupported DOS command: %s\r\n", p_line); + exit_code = 1; + } /**r = c;*/ + } else if (0 == memcmp(p_line, "if", cmd_size)) { + char cond_ok = 0, is_not = 0; + char *tail = arg; + while (*tail == ' ' || *tail == '\t') ++tail; + if ((tail[0] == 'n' || tail[0] == 'N') && (tail[1] == 'o' || tail[1] == 'O') && (tail[2] == 't' || tail[2] == 'T') && (tail[3] == ' ' || tail[3] == '\t')) { + is_not = 1; + tail += 4; + while (*tail == ' ' || *tail == '\t') ++tail; + } + if ((tail[0] == 'e' || tail[0] == 'E') && (tail[1] == 'r' || tail[1] == 'R') && (tail[2] == 'r' || tail[2] == 'R') && + (tail[3] == 'o' || tail[3] == 'O') && (tail[4] == 'r' || tail[4] == 'R') && (tail[5] == 'l' || tail[5] == 'L') && + (tail[6] == 'e' || tail[6] == 'E') && (tail[7] == 'v' || tail[7] == 'V') && (tail[8] == 'e' || tail[8] == 'E') && + (tail[9] == 'l' || tail[9] == 'L') && (tail[10] == ' ' || tail[10] == '\t')) { + unsigned n = 0; + char *pnum = tail + 10; + while (*pnum == ' ' || *pnum == '\t') ++pnum; + while (*pnum >= '0' && *pnum <= '9') { n = n * 10 + (*pnum++ - '0'); } + while (*pnum == ' ' || *pnum == '\t') ++pnum; + cond_ok = exit_code >= n; + tail = pnum; + } else if ((tail[0] == 'e' || tail[0] == 'E') && (tail[1] == 'x' || tail[1] == 'X') && (tail[2] == 'i' || tail[2] == 'I') && (tail[3] == 's' || tail[3] == 'S') && (tail[4] == 't' || tail[4] == 'T') && (tail[5] == ' ' || tail[5] == '\t')) { + char fn[260]; + char *pf = tail + 5; + struct stat st; + size_t fi = 0; + while (*pf == ' ' || *pf == '\t') ++pf; + if (*pf == '"') { + ++pf; + while (*pf && *pf != '"' && fi + 1 < sizeof(fn)) fn[fi++] = *pf++; + if (*pf == '"') ++pf; + } else { + while (*pf && *pf != ' ' && *pf != '\t' && fi + 1 < sizeof(fn)) fn[fi++] = *pf++; + } + fn[fi] = '\0'; + while (*pf == ' ' || *pf == '\t') ++pf; + if (fn[0] && (strchr(fn, '*') || strchr(fn, '?'))) { + const char *pat_base = get_dos_basename(fn); + size_t pat_dir_size = pat_base - fn; + char dos_dir[DOS_PATH_SIZE + 4], linux_dir[LINUX_PATH_SIZE]; + DIR *dd = NULL; + struct dirent *de; + cond_ok = 0; + if (pat_dir_size >= sizeof(dos_dir)) pat_dir_size = sizeof(dos_dir) - 1; + memcpy(dos_dir, fn, pat_dir_size); + dos_dir[pat_dir_size] = '\0'; + if (dos_dir[0] == '\0') strcpy(dos_dir, "."); + if (*get_linux_filename_r(dos_dir, dir_state, linux_dir, NULL) && (dd = opendir(linux_dir)) != NULL) { + while ((de = readdir(dd)) != NULL) { + if (de->d_name[0] == '.') continue; + if (dos_wildcard_match(pat_base, de->d_name)) { cond_ok = 1; break; } + } + closedir(dd); + } + } else { + cond_ok = fn[0] && *get_linux_filename_r(fn, dir_state, fnbuf, NULL) && stat(fnbuf, &st) == 0; + } + tail = pf; + } else { + char *eqeq = strstr(tail, "=="); + if (eqeq) { + char lhs[256], rhs[256]; + char *pc = tail; + size_t li = 0, ri = 0; + while (pc < eqeq && (*pc == ' ' || *pc == '\t')) ++pc; + if (*pc == '"') ++pc; + while (pc < eqeq && *pc != '"' && li + 1 < sizeof(lhs)) lhs[li++] = *pc++; + while (li > 0 && (lhs[li - 1] == ' ' || lhs[li - 1] == '\t')) --li; + lhs[li] = '\0'; + pc = eqeq + 2; + while (*pc == ' ' || *pc == '\t') ++pc; + if (*pc == '"') ++pc; + while (*pc && *pc != '"' && *pc != ' ' && *pc != '\t' && ri + 1 < sizeof(rhs)) rhs[ri++] = *pc++; + while (ri > 0 && (rhs[ri - 1] == ' ' || rhs[ri - 1] == '\t')) --ri; + rhs[ri] = '\0'; + if (*pc == '"') ++pc; + while (*pc == ' ' || *pc == '\t') ++pc; + cond_ok = strcmp(lhs, rhs) == 0; + tail = pc; + } + } + if (is_not) cond_ok = !cond_ok; + if (cond_ok && *tail) { + memmove(p_line, tail, strlen(tail) + 1); + goto reparse_command; + } + /* DOS IF does not update ERRORLEVEL by itself. */ + goto done_command; } else { /* Run .com or .exe program. */ char *args_str = p_line, args_buf[0x80], c2; const char* prog_filename; - const char* const *envp; char prog_drive; size_t size; - for (; (c2 = *args_str) != '\0' && c2 != ' ' && c2 != '\t' && c2 != '=' && c2 != ',' && c2 != '/'; ++args_str) {} /* MS-DOS 6.22. */ + for (; (c2 = *args_str) != '\0' && c2 != ' ' && c2 != '\t' && c2 != '=' && c2 != ','; ++args_str) {} /* MS-DOS 6.22. */ if (args_str == p_line) { fprintf(stderr, "Empty DOS program name to run\r\n"); exit_code = 1; @@ -3755,9 +5976,11 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena } else { memcpy(args_buf, args_str, size + 1); /* Including the trailing '\0'. */ *args_str = '\0'; /* So that p_line becomes terminated by '\0'. */ - for (envp = envp0; *envp && strncmp(*envp, "PATH=", 5) != 0; ++envp) {} - dir_state->dos_prog_abs = dos_prog_abs; /* Of the .bat file. */ - prog_filename = find_prog_on_path(p_line, dir_state, *envp ? *envp + 5 : NULL, &prog_drive); + { + const char *path_value = has_path_override ? path_override : getenv_prefix_nocase0("PATH=", (const char* const*)batch_env); + dir_state->dos_prog_abs = dos_prog_abs; /* Of the .bat file. */ + prog_filename = find_prog_on_path(p_line, dir_state, path_value, &prog_drive); + } if (!prog_filename) { /* DOSBox 0.74-4 prints "Illegal command: %s.\r\n" to stdout, we print our error to stderr. */ /* MS-DOS 6.22 prints this to stderr: "Bad command or file name\r\n". */ @@ -3772,15 +5995,109 @@ static unsigned char run_dos_batch(struct EmuState *emu, const char *prog_filena fprintf(stderr, "Error getting absolute filelename - %s\r\n", p_line); exit_code = 1; } else { - exit_code = run_dos_prog(emu, prog_filename, args_buf, NULL, dir_state, tty_state, emu_params, envp0); + const char *batch_extra_env[300]; + unsigned batch_extra_env_count = 0; + char batch_path_env[1024 + 5]; + unsigned ei; + if (has_path_override) { + memcpy(batch_path_env, "PATH=", 5); + strncpy(batch_path_env + 5, path_override, sizeof(batch_path_env) - 6); + batch_path_env[sizeof(batch_path_env) - 1] = '\0'; + batch_extra_env[batch_extra_env_count++] = batch_path_env; + } + for (ei = 0; ei < batch_env_count && batch_extra_env_count < sizeof(batch_extra_env) / sizeof(batch_extra_env[0]); ++ei) { + if (has_path_override && + ((batch_env[ei][0] | 32) == 'p') && + ((batch_env[ei][1] | 32) == 'a') && + ((batch_env[ei][2] | 32) == 't') && + ((batch_env[ei][3] | 32) == 'h') && + batch_env[ei][4] == '=') continue; + batch_extra_env[batch_extra_env_count++] = batch_env[ei]; + } + if (has_dos_ext_nocase(prog_filename, ".bat")) { + const char *child_args[64]; + char *ab = args_buf, *ae; + unsigned ac = 0; + while (*ab == ' ' || *ab == '\t') ++ab; + while (*ab && ac + 1 < sizeof(child_args) / sizeof(child_args[0])) { + ae = ab; + while (*ae && *ae != ' ' && *ae != '\t') ++ae; + if (*ae) *ae++ = '\0'; + child_args[ac++] = ab; + while (*ae == ' ' || *ae == '\t') ++ae; + ab = ae; + } + child_args[ac] = NULL; + exit_code = run_dos_batch(emu, prog_filename, child_args, dir_state, tty_state, emu_params, envp0, batch_extra_env, batch_extra_env_count); + } else { + const enum mz_subformat_t subfmt = detect_mz_subformat(prog_filename); + if (subfmt == MZ_SUBFMT_PE && has_wine_in_path()) { + const char *child_argv[64]; + char *ab = args_buf, *ae; + unsigned ac = 0; + char dos_cwd[DOS_PATH_SIZE + 4], linux_cwd[LINUX_PATH_SIZE]; + const char *cdp = dir_state->current_dir[dir_state->drive - 'A']; + if (!cdp || !*cdp) cdp = "\\"; + snprintf(dos_cwd, sizeof(dos_cwd), "%c:%s", dir_state->drive, cdp); + get_linux_filename_r(dos_cwd, dir_state, linux_cwd, NULL); + while (*ab == ' ' || *ab == '\t') ++ab; + while (*ab && ac + 1 < sizeof(child_argv) / sizeof(child_argv[0])) { + ae = ab; + while (*ae && *ae != ' ' && *ae != '\t') ++ae; + if (*ae) *ae++ = '\0'; + child_argv[ac++] = ab; + while (*ae == ' ' || *ae == '\t') ++ae; + ab = ae; + } + child_argv[ac] = NULL; + exit_code = run_with_wine(prog_filename, child_argv, linux_cwd); + } else { + exit_code = run_dos_prog(emu, prog_filename, args_buf, NULL, dir_state, tty_state, emu_params, envp0, batch_extra_env, batch_extra_env_count); + } + } } } dir_state->dos_prog_abs = NULL; /* For security. */ } } - done_command: - ++q; /* Skip over the '\0', formerly '\r' or '\n'. */ - goto next_line; + done_command: + if (saved_stderr >= 0) { dup2(saved_stderr, 2); close(saved_stderr); } + if (saved_stdout >= 0) { dup2(saved_stdout, 1); close(saved_stdout); } + if (saved_stdin >= 0) { dup2(saved_stdin, 0); close(saved_stdin); } + if (pipe_stage == 1) { + fflush(stdout); + dup2(pipe_save_out, 1); + close(pipe_save_out); pipe_save_out = -1; + lseek(pipe_fd, 0, SEEK_SET); + pipe_save_in = dup(0); + dup2(pipe_fd, 0); + close(pipe_fd); pipe_fd = -1; + strncpy(cmdline, pipe_right, sizeof(cmdline) - 1); + cmdline[sizeof(cmdline) - 1] = '\0'; + p_line = cmdline; + q = p_line + strlen(p_line); + pipe_stage = 2; + goto reparse_command; + } else if (pipe_stage == 2) { + dup2(pipe_save_in, 0); + close(pipe_save_in); pipe_save_in = -1; + pipe_stage = 0; + } + if (rewound) { + q = p_line = buf; + goto next_line; + } + q = q_src; + ++q; /* Skip over the '\0', formerly '\r' or '\n'. */ + goto next_line; + } + } + while (batch_env_count) free(batch_env[--batch_env_count]); + while (setlocal_depth) { + unsigned ei; + --setlocal_depth; + for (ei = 0; ei < setlocal_env_count[setlocal_depth]; ++ei) { + free(setlocal_env[setlocal_depth][ei]); } } close(batch_fd); @@ -3793,6 +6110,15 @@ static void init_tty_state(TtyState *tty_state, int tty_in_fd) { tty_state->next_fake_key = fake_keys; } +static void free_extra_env_args(ParsedCmdArgs *cmd_args) { + unsigned i; + for (i = 0; i < cmd_args->extra_env_count; ++i) { + free((void*)cmd_args->extra_env[i]); + cmd_args->extra_env[i] = NULL; + } + cmd_args->extra_env_count = 0; +} + /* --- */ int main(int argc, char **argv) { @@ -3802,6 +6128,16 @@ int main(int argc, char **argv) { argv, &cmd_args, "kvikdos: run DOS programs headless (a very fast DOS emulator)\nUsage: ", "", "This is free software, GNU GPL >=2.0. There is NO WARRANTY. Use at your risk.\n"); + g_case_fallback_mode = cmd_args.emu_params.case_fallback_mode; + if (cmd_args.emu_params.diag_filename) { + g_diag_file = fopen(cmd_args.emu_params.diag_filename, "ab"); + if (!g_diag_file) { + perror("fatal: cannot open --diag-file"); + exit(1); + } + } else { + g_diag_file = stderr; + } if (0) { /* Just dump the parsed command-line. */ /* cmd_args.dir_state.linux_prog is still NULL, use cmd_args.prog_filename instead. */ printf("linux prog: %s\n", cmd_args.prog_filename); @@ -3831,14 +6167,39 @@ int main(int argc, char **argv) { printf("end of envs\n"); } printf("tty_in_fd: %d\n", cmd_args.tty_in_fd); - printf("mem_mb: %d\n", cmd_args.emu_params.mem_mb); + printf("mem_mb: %u\n", cmd_args.emu_params.mem_mb); printf("is_hlt_ok: %d\n", cmd_args.emu_params.is_hlt_ok); return 0; } if (cmd_args.dpmi_prog) { /* pts-fast-dosbox does support it, kvikdos doesn't. */ fprintf(stderr, "fatal: DPMI not supported: %s\n", cmd_args.dpmi_prog); + free_extra_env_args(&cmd_args); exit(1); } + if (is_linux_native_executable(cmd_args.prog_filename)) { + int rc = run_native_execvp(cmd_args.prog_filename, cmd_args.args); + free_extra_env_args(&cmd_args); + return rc; + } + { + const enum mz_subformat_t subfmt = detect_mz_subformat(cmd_args.prog_filename); + if (subfmt == MZ_SUBFMT_PE || + ((subfmt == MZ_SUBFMT_NE || subfmt == MZ_SUBFMT_LE || subfmt == MZ_SUBFMT_LX) && + !is_probable_borland_dual_mode_ne(cmd_args.prog_filename) && + is_probable_windows_message_stub(cmd_args.prog_filename))) { + if (!has_wine_in_path()) { + fprintf(stderr, "error: detected Windows executable, but 'wine' is not in PATH: %s\n", cmd_args.prog_filename); + free_extra_env_args(&cmd_args); + return 1; + } + fprintf(stderr, "info: detected Windows executable, delegating to wine: %s\n", cmd_args.prog_filename); + { + int rc = run_with_wine(cmd_args.prog_filename, cmd_args.args, NULL); + free_extra_env_args(&cmd_args); + return rc; + } + } + } { int exit_code; const char *ext = get_linux_ext(cmd_args.prog_filename); EmuState emu; @@ -3846,11 +6207,12 @@ int main(int argc, char **argv) { init_emu(&emu); /* This is lightweight, it doesn't initialize KVM. */ init_tty_state(&tty_state, cmd_args.tty_in_fd); if (is_same_ascii_nocase(ext, "bat", 4)) { - exit_code = run_dos_batch(&emu, cmd_args.prog_filename, cmd_args.args, &cmd_args.dir_state, &tty_state, &cmd_args.emu_params, cmd_args.envp0); + exit_code = run_dos_batch(&emu, cmd_args.prog_filename, cmd_args.args, &cmd_args.dir_state, &tty_state, &cmd_args.emu_params, cmd_args.envp0, (const char* const*)cmd_args.extra_env, cmd_args.extra_env_count); } else { - exit_code = run_dos_prog(&emu, cmd_args.prog_filename, NULL, cmd_args.args, &cmd_args.dir_state, &tty_state, &cmd_args.emu_params, cmd_args.envp0); + exit_code = run_dos_prog(&emu, cmd_args.prog_filename, NULL, cmd_args.args, &cmd_args.dir_state, &tty_state, &cmd_args.emu_params, cmd_args.envp0, (const char* const*)cmd_args.extra_env, cmd_args.extra_env_count); } if (DEBUG) fprintf(stderr, "debug: DOS program exited with code: 0x%02x", exit_code); + free_extra_env_args(&cmd_args); return exit_code; } } diff --git a/tests/test_cli_matrix.sh b/tests/test_cli_matrix.sh new file mode 100755 index 0000000..1c858c5 --- /dev/null +++ b/tests/test_cli_matrix.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +KVD="${1:-./kvikdos}" +KVD="$(readlink -f "$KVD")" +if [[ ! -x "$KVD" ]]; then + echo "error: kvikdos binary not found: $KVD" >&2 + exit 2 +fi + +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT +mkdir -p "$TMP/c/BIN" "$TMP/d" "$TMP/e" "$TMP/f" +printf 'MZ\x00\x00\x00\x00\x00\x00' > "$TMP/c/BIN/LINK.EXE" + +run_no_crash() { + local name="$1"; shift + local out rc + set +e + out="$($KVD "$@" 2>&1)" + rc=$? + set -e + if [[ $rc -eq 139 || $rc -eq 134 ]] || printf '%s\n' "$out" | grep -qiE 'segmentation fault|sanitizer:deadly signal'; then + echo "FAIL: $name crashed (rc=$rc)" >&2 + printf '%s\n' "$out" >&2 + return 1 + fi + echo "PASS: $name (rc=$rc)" +} + +fails=0 + +COMMON=( + --hlt-ok + --mount=C:"$TMP/c"/ + --mount=D:"$TMP/d"/ + --mount=E:"$TMP/e"/ + --mount=F:"$TMP/f"/ +) + +run_no_crash "prog+positional-dos" \ + "${COMMON[@]}" --drive=d '--cwd-dos=D:\\' '--path-dos=C:\\BIN;C:\\' '--env=PATH=C:\\BIN;C:\\' '--prog=C:\\BIN\\LINK.EXE' 'C:\\BIN\\LINK.EXE' /M /I '@D:\\end.rsp' || fails=$((fails+1)) + +run_no_crash "invalid-drive-flag" \ + "${COMMON[@]}" --drive=z 'C:\\BIN\\LINK.EXE' || fails=$((fails+1)) + +run_no_crash "bad-mount-format" \ + --mount=C:"$TMP/c"/ --mount=Q:"$TMP/d"/ 'C:\\BIN\\LINK.EXE' || fails=$((fails+1)) + +run_no_crash "missing-flag-arg" \ + --mount=C:"$TMP/c"/ --env 'C:\\BIN\\LINK.EXE' || fails=$((fails+1)) + +run_no_crash "linux-positional-with-prog" \ + "${COMMON[@]}" --prog='C:\\BIN\\LINK.EXE' "$TMP/c/BIN/LINK.EXE" || fails=$((fails+1)) + +if [[ $fails -ne 0 ]]; then + echo "FAIL: cli-matrix failures=$fails" >&2 + exit 1 +fi + +echo "PASS: cli-matrix" diff --git a/tests/test_cli_parse.sh b/tests/test_cli_parse.sh new file mode 100755 index 0000000..bd8dd22 --- /dev/null +++ b/tests/test_cli_parse.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +KVD="${1:-./kvikdos}" +KVD="$(readlink -f "$KVD")" +if [[ ! -x "$KVD" ]]; then + echo "error: kvikdos binary not found: $KVD" >&2 + exit 2 +fi + +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +mkdir -p "$TMP/c/BIN" "$TMP/d" "$TMP/e" "$TMP/f" +# Minimal MZ file so parser and loader continue past argv processing. +printf 'MZ\x00\x00\x00\x00\x00\x00' > "$TMP/c/BIN/LINK.EXE" + +set +e +OUT="$($KVD \ + --hlt-ok \ + --mount=C:"$TMP/c"/ \ + --mount=D:"$TMP/d"/ \ + --mount=E:"$TMP/e"/ \ + --mount=F:"$TMP/f"/ \ + --drive=d \ + '--cwd-dos=D:\\' \ + '--path-dos=C:\\BIN;C:\\' \ + '--env=PATH=C:\\BIN;C:\\' \ + '--env=TMP=F:\\' \ + '--prog=C:\\BIN\\LINK.EXE' \ + 'C:\\BIN\\LINK.EXE' \ + /M /I '@D:\\end.rsp' 2>&1)" +RC=$? +set -e + +if [[ $RC -eq 139 || $RC -eq 134 ]] || printf '%s\n' "$OUT" | grep -qiE 'segmentation fault|sanitizer:deadly signal'; then + echo "FAIL: crash/signal (rc=$RC)" >&2 + printf '%s\n' "$OUT" >&2 + exit 1 +fi + +# Accept any graceful failure/success, but reject segfault-like behavior. +echo "PASS: cli-parse-no-crash (rc=$RC)" +exit 0 diff --git a/tests/test_mem_services.sh b/tests/test_mem_services.sh new file mode 100755 index 0000000..71ec112 --- /dev/null +++ b/tests/test_mem_services.sh @@ -0,0 +1,146 @@ +#!/usr/bin/env bash +set -euo pipefail + +KVD="${1:-./kvikdos}" +KVD="$(readlink -f "$KVD")" +if [[ ! -x "$KVD" ]]; then + echo "error: kvikdos binary not found or not executable: $KVD" >&2 + exit 2 +fi + +if ! command -v nasm >/dev/null 2>&1; then + echo "error: nasm is required for test_mem_services.sh" >&2 + exit 2 +fi + +TMP="$(mktemp -d)" +trap 'rm -rf "$TMP"' EXIT + +cat > "$TMP/memsvc.nasm" <<'ASM' +bits 16 +org 100h + +start: + xor si, si + + ; INT 2Fh AX=4300h: XMS installation check (AL should be 80h) + mov ax, 4300h + int 2fh + cmp al, 80h + je .xms_present + or si, 0001h +.xms_present: + + ; INT 2Fh AX=4310h: get XMS entry point ES:BX + mov ax, 4310h + int 2fh + mov [xms_off], bx + mov [xms_seg], es + mov ax, es + or ax, bx + jnz .xms_ptr_ok + or si, 0002h +.xms_ptr_ok: + + ; XMS AH=08h: query free memory + mov ah, 08h + call far [xms_off] + cmp ax, 1 + je .xms_q_ok + or si, 0004h +.xms_q_ok: + + ; XMS AH=09h: allocate 64 KiB + mov dx, 64 + mov ah, 09h + call far [xms_off] + cmp ax, 1 + je .xms_alloc_ok + or si, 0008h + jmp short .after_xms_alloc +.xms_alloc_ok: + mov [xms_handle], dx +.after_xms_alloc: + + ; XMS AH=0Ah: free handle (if allocated) + mov dx, [xms_handle] + or dx, dx + jz .xms_free_done + mov ah, 0ah + call far [xms_off] + cmp ax, 1 + je .xms_free_done + or si, 0010h +.xms_free_done: + + ; INT 67h AH=40h: EMS status + mov ah, 40h + int 67h + cmp ah, 00h + je .ems_status_ok + or si, 0020h +.ems_status_ok: + + ; INT 67h AH=42h: get EMS pages + mov ah, 42h + int 67h + cmp ah, 00h + je .ems_pages_ok + or si, 0040h +.ems_pages_ok: + + ; INT 21h AX=5802h: get UMB link state + mov ax, 5802h + int 21h + jc .umb_get_fail + jmp short .umb_set +.umb_get_fail: + or si, 0080h + +.umb_set: + ; INT 21h AX=5803h: set UMB link state=1 + mov ax, 5803h + mov bx, 1 + int 21h + jc .umb_set_fail + jmp short .umb_verify +.umb_set_fail: + or si, 0100h + +.umb_verify: + mov ax, 5802h + int 21h + jc .umb_verify_fail + cmp al, 1 + je .done +.umb_verify_fail: + or si, 0200h + +.done: + mov ax, si + and ax, 00ffh + or ax, 4c00h + int 21h + +xms_off dw 0 +xms_seg dw 0 +xms_handle dw 0 +ASM + +nasm -f bin -o "$TMP/MEMSVC.COM" "$TMP/memsvc.nasm" + +set +e +"$KVD" "$TMP/MEMSVC.COM" >/tmp/kvikdos_memsvc.out 2>/tmp/kvikdos_memsvc.err +rc=$? +set -e + +if [[ "$rc" -ne 0 ]]; then + echo "FAIL: mem-services (exit=$rc)" >&2 + echo "--- stdout ---" >&2 + sed -n '1,80p' /tmp/kvikdos_memsvc.out >&2 + echo "--- stderr ---" >&2 + sed -n '1,120p' /tmp/kvikdos_memsvc.err >&2 + exit 1 +fi + +echo "PASS: mem-services"