From 275dbe2641f94f9da41a2f50c8ce249c717ce573 Mon Sep 17 00:00:00 2001 From: Brian Callahan Date: Tue, 8 Jun 2021 19:23:36 -0400 Subject: [PATCH] Initial commit of l80 --- .gitignore | 15 +++ LICENSE | 13 +++ Makefile | 7 ++ README.md | 63 ++++++++++++ dub.json | 9 ++ source/Makefile | 12 +++ source/app.d | 248 ++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 367 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 dub.json create mode 100644 source/Makefile create mode 100644 source/app.d diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ead1cb3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +.dub +docs.json +__dummy.html +docs/ +/l80 +l80.so +l80.dylib +l80.dll +l80.a +l80.lib +l80-test-* +*.exe +*.o +*.obj +*.lst diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a3e4df3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2021 Brian Callahan + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..36b7796 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +# l80 Makefile + +all: + ${MAKE} -C source + +clean: + ${MAKE} -C source clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..9aefe0f --- /dev/null +++ b/README.md @@ -0,0 +1,63 @@ +l80 +=== +`l80` is an Intel 8080/Zilog Z80 linker for CP/M-80. + +It reads in object files created by +[`a80`](https://github.com/ibara/a80) +and produces executable CP/M-80 binaries from them. + +Building +-------- +`l80` should build with any +[D](https://dlang.org/) +compiler for any supported platform. I use +[GDC](https://gdcproject.org/) +on +[OpenBSD](https://www.openbsd.org/) +and that works well. + +Running +------- +`usage: l80 file.com file1.obj [file2.obj ...]` + +All object files must end in `.obj` or `.lib`. + +Object format +------------- +`l80` uses the most simple object format I could devise. + +Object files are comprised of control codes and data. There +are three control codes: +* `00`: The following byte is literal data. +* `01`: The following bytes are a symbol declaration. +* `02`: The following bytes are a symbol reference. + +`l80` uses two passes to generate the final execuatable +binary. The first pass writes all object files and libraries +into a single buffer and then collects all the symbol +declarations and calculates the address of each symbol. The +second pass writes out the executable, replacing references +with the addresses calculated during the first pass. + +Libraries are simply collections of object files. They can +be created with the (upcoming!) ar80 tool. + +Caveats +------- +`l80` does not recognize nor remove the code of unused +symbols. Doing so is planned. + +Bugs +---- +Probably lots. Test and let me know. + +License +------- +ISC License. See `LICENSE` for details. + +Note +---- +This `l80` is in no way related to the linker of the same +name produced by Microsoft, also for CP/M-80. + +That one uses a very different file format. diff --git a/dub.json b/dub.json new file mode 100644 index 0000000..3e6f639 --- /dev/null +++ b/dub.json @@ -0,0 +1,9 @@ +{ + "authors": [ + "Brian Callahan" + ], + "copyright": "Copyright © 2021, Brian Callahan", + "description": "Intel 8080/Zilog Z80 linker.", + "license": "ISC", + "name": "l80" +} diff --git a/source/Makefile b/source/Makefile new file mode 100644 index 0000000..01e96ac --- /dev/null +++ b/source/Makefile @@ -0,0 +1,12 @@ +# l80 Makefile + +PROG = l80 +OBJS = app.o + +DFLAGS = -O2 -pipe -frelease -finline + +all: ${OBJS} + ${DC} ${LDFLAGS} -o ../${PROG} ${OBJS} + +clean: + rm -f ../${PROG} ${OBJS} diff --git a/source/app.d b/source/app.d new file mode 100644 index 0000000..8ff8fed --- /dev/null +++ b/source/app.d @@ -0,0 +1,248 @@ +/** + * Copyright (c) 2021 Brian Callahan + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +import std.stdio; +import std.file; +import std.conv; +import std.string; +import std.algorithm; + +/** + * All object files together in one array. + */ +private ubyte[] rawobjs; + +/** + * Final binary. + */ +private ubyte[] binary; + +/** + * Address counter. + * Starts at 100h, because CP/M. + */ +private size_t addr = 0x100; + +/** + * Struct holding name and matching calculated addresses. + * collect.addr is a size_t to detect address overflow. + */ +struct collect +{ + string name; /// Symbol name + size_t addr; /// Symbol address +}; + +/** + * Collection of addresses. + */ +private collect[] collection; + +/** + * Error messages. + */ +private int error(string msg) +{ + stderr.writeln("l80: error: " ~ msg); + return 1; +} + +/** + * Check if symbol exists in the collection. + */ +private bool dupname(string name) +{ + for (size_t i = 0; i < collection.length; i++) { + if (name == collection[i].name) + return true; + } + + return false; +} + +/** + * Pass 1: Collect symbol names. + */ +private int collect1() +{ + for (size_t i = 0; i < rawobjs.length; i++) { + if (rawobjs[i] == '\0') { + /* Skip control byte. */ + ++i; + + /* Increment address counter. */ + ++addr; + + /* Binary has a maximum size of 65,280 bytes (FF00h). */ + if (addr > 65280) + return error("final binary exceeds 65,280 bytes"); + } else if (rawobjs[i] == '\001') { + string name; + + /* Skip control byte. */ + ++i; + + /* Get symbol name, put in collect struct. */ + while (rawobjs[i] != '\001') { + name ~= rawobjs[i++]; + if (i == rawobjs.length) + return error("unterminated symbol"); + } + + /* Make sure there is actually a symbol here. */ + if (name.empty) + return error("symbol marker with no symbol inside"); + + /* Make sure name isn't already in the collection. */ + if (dupname(name) == true) + return error("duplicate symbol: " ~ name); + + /* Add to collection table. */ + collect newcollect = { name, addr }; + collection ~= newcollect; + } else if (rawobjs[i] == '\002') { + /* Skip control byte. */ + ++i; + + /* Ignore the references during pass 1. */ + while (rawobjs[i] != '\002') { + ++i; + if (i == rawobjs.length) + return error("unterminated symbol reference"); + } + + /* Increment address counter. */ + addr += 2; + + /* Binary has a maximum size of 65,280 bytes (FF00h). */ + if (addr > 65280) + return error("final binary exceeds 65,280 bytes"); + } else { + /* This should never happen. */ + return error("unknown control byte: " ~ to!string(rawobjs[i])); + } + } + + return 0; +} + +/** + * Pass 2: Write out final binary. + */ +private int process2() +{ + for (size_t i = 0; i < rawobjs.length; i++) { + if (rawobjs[i] == '\0') { + /* Skip control byte. */ + ++i; + + /* Write byte to final binary. */ + binary ~= rawobjs[i]; + } else if (rawobjs[i] == '\001') { + /* Skip control byte. */ + ++i; + + /* Ignore the declarations during pass 2. */ + while (rawobjs[i] != '\001') { + ++i; + if (i == rawobjs.length) + return error("unterminated symbol"); + } + } else if (rawobjs[i] == '\002') { + bool found = false; + string name; + + /* Skip control byte. */ + ++i; + + while (rawobjs[i] != '\002') { + name ~= rawobjs[i]; + ++i; + + if (i == rawobjs.length) + return error("unterminated symbol reference"); + } + + /* Make sure there is actually a symbol here. */ + if (name.empty) + return error("symbol marker with no symbol inside"); + + /* Output symbol. */ + for (size_t j = 0; j < collection.length; j++) { + if (name == collection[j].name) { + binary ~= cast(ubyte)(collection[j].addr & 0xff); + binary ~= cast(ubyte)((collection[j].addr >> 8) & 0xff); + found = true; + break; + } + } + + /* If symbol was not found, error out. */ + if (found == false) + return error("undefined reference: " ~ name); + } else { + /* This should never happen. */ + return error("unknown control byte: " ~ to!string(rawobjs[i])); + } + } + + return 0; +} + +/** + * After all code is emitted, write it out to a file. + */ +private void fileWrite(string outfile) +{ + import std.file : write; + + write(outfile, binary); +} + +int main(string[] args) +{ + int ret; + + if (args.length < 3) { + stderr.writeln("usage: l80 file.com file1.obj [file2.obj ...]"); + return 1; + } + + /** + * Write all object files into a single buffer. + */ + foreach (size_t i; 2 .. args.length) { + auto objsplit = args[i].findSplit(".obj"); + + /* Objects must end in .obj or .lib. */ + if (objsplit[1].empty) { + auto libsplit = args[i].findSplit(".lib"); + if (libsplit[1].empty) + return error("object files must end in \".obj\" or \".lib\""); + } + + rawobjs ~= cast(ubyte[])read(args[i]); + } + + ret = collect1(); + if (ret == 0) { + ret = process2(); + if (ret == 0) + fileWrite(args[1]); + } + + return ret; +}