Skip to content

Commit

Permalink
Initial commit of l80
Browse files Browse the repository at this point in the history
  • Loading branch information
ibara committed Jun 8, 2021
0 parents commit 275dbe2
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 0 deletions.
15 changes: 15 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Copyright (c) 2021 Brian Callahan <bcallah@openbsd.org>

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.
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# l80 Makefile

all:
${MAKE} -C source

clean:
${MAKE} -C source clean
63 changes: 63 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
9 changes: 9 additions & 0 deletions dub.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"authors": [
"Brian Callahan"
],
"copyright": "Copyright © 2021, Brian Callahan",
"description": "Intel 8080/Zilog Z80 linker.",
"license": "ISC",
"name": "l80"
}
12 changes: 12 additions & 0 deletions source/Makefile
Original file line number Diff line number Diff line change
@@ -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}
248 changes: 248 additions & 0 deletions source/app.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
/**
* Copyright (c) 2021 Brian Callahan <bcallah@openbsd.org>
*
* 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;
}

0 comments on commit 275dbe2

Please sign in to comment.