Skip to content

Commit 275dbe2

Browse files
committed
Initial commit of l80
0 parents  commit 275dbe2

File tree

7 files changed

+367
-0
lines changed

7 files changed

+367
-0
lines changed

.gitignore

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.dub
2+
docs.json
3+
__dummy.html
4+
docs/
5+
/l80
6+
l80.so
7+
l80.dylib
8+
l80.dll
9+
l80.a
10+
l80.lib
11+
l80-test-*
12+
*.exe
13+
*.o
14+
*.obj
15+
*.lst

LICENSE

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Copyright (c) 2021 Brian Callahan <[email protected]>
2+
3+
Permission to use, copy, modify, and distribute this software for any
4+
purpose with or without fee is hereby granted, provided that the above
5+
copyright notice and this permission notice appear in all copies.
6+
7+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

Makefile

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# l80 Makefile
2+
3+
all:
4+
${MAKE} -C source
5+
6+
clean:
7+
${MAKE} -C source clean

README.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
l80
2+
===
3+
`l80` is an Intel 8080/Zilog Z80 linker for CP/M-80.
4+
5+
It reads in object files created by
6+
[`a80`](https://github.com/ibara/a80)
7+
and produces executable CP/M-80 binaries from them.
8+
9+
Building
10+
--------
11+
`l80` should build with any
12+
[D](https://dlang.org/)
13+
compiler for any supported platform. I use
14+
[GDC](https://gdcproject.org/)
15+
on
16+
[OpenBSD](https://www.openbsd.org/)
17+
and that works well.
18+
19+
Running
20+
-------
21+
`usage: l80 file.com file1.obj [file2.obj ...]`
22+
23+
All object files must end in `.obj` or `.lib`.
24+
25+
Object format
26+
-------------
27+
`l80` uses the most simple object format I could devise.
28+
29+
Object files are comprised of control codes and data. There
30+
are three control codes:
31+
* `00`: The following byte is literal data.
32+
* `01`: The following bytes are a symbol declaration.
33+
* `02`: The following bytes are a symbol reference.
34+
35+
`l80` uses two passes to generate the final execuatable
36+
binary. The first pass writes all object files and libraries
37+
into a single buffer and then collects all the symbol
38+
declarations and calculates the address of each symbol. The
39+
second pass writes out the executable, replacing references
40+
with the addresses calculated during the first pass.
41+
42+
Libraries are simply collections of object files. They can
43+
be created with the (upcoming!) ar80 tool.
44+
45+
Caveats
46+
-------
47+
`l80` does not recognize nor remove the code of unused
48+
symbols. Doing so is planned.
49+
50+
Bugs
51+
----
52+
Probably lots. Test and let me know.
53+
54+
License
55+
-------
56+
ISC License. See `LICENSE` for details.
57+
58+
Note
59+
----
60+
This `l80` is in no way related to the linker of the same
61+
name produced by Microsoft, also for CP/M-80.
62+
63+
That one uses a very different file format.

dub.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"authors": [
3+
"Brian Callahan"
4+
],
5+
"copyright": "Copyright © 2021, Brian Callahan",
6+
"description": "Intel 8080/Zilog Z80 linker.",
7+
"license": "ISC",
8+
"name": "l80"
9+
}

source/Makefile

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# l80 Makefile
2+
3+
PROG = l80
4+
OBJS = app.o
5+
6+
DFLAGS = -O2 -pipe -frelease -finline
7+
8+
all: ${OBJS}
9+
${DC} ${LDFLAGS} -o ../${PROG} ${OBJS}
10+
11+
clean:
12+
rm -f ../${PROG} ${OBJS}

source/app.d

+248
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
/**
2+
* Copyright (c) 2021 Brian Callahan <bcallah@openbsd.org>
3+
*
4+
* Permission to use, copy, modify, and distribute this software for any
5+
* purpose with or without fee is hereby granted, provided that the above
6+
* copyright notice and this permission notice appear in all copies.
7+
*
8+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15+
*/
16+
17+
import std.stdio;
18+
import std.file;
19+
import std.conv;
20+
import std.string;
21+
import std.algorithm;
22+
23+
/**
24+
* All object files together in one array.
25+
*/
26+
private ubyte[] rawobjs;
27+
28+
/**
29+
* Final binary.
30+
*/
31+
private ubyte[] binary;
32+
33+
/**
34+
* Address counter.
35+
* Starts at 100h, because CP/M.
36+
*/
37+
private size_t addr = 0x100;
38+
39+
/**
40+
* Struct holding name and matching calculated addresses.
41+
* collect.addr is a size_t to detect address overflow.
42+
*/
43+
struct collect
44+
{
45+
string name; /// Symbol name
46+
size_t addr; /// Symbol address
47+
};
48+
49+
/**
50+
* Collection of addresses.
51+
*/
52+
private collect[] collection;
53+
54+
/**
55+
* Error messages.
56+
*/
57+
private int error(string msg)
58+
{
59+
stderr.writeln("l80: error: " ~ msg);
60+
return 1;
61+
}
62+
63+
/**
64+
* Check if symbol exists in the collection.
65+
*/
66+
private bool dupname(string name)
67+
{
68+
for (size_t i = 0; i < collection.length; i++) {
69+
if (name == collection[i].name)
70+
return true;
71+
}
72+
73+
return false;
74+
}
75+
76+
/**
77+
* Pass 1: Collect symbol names.
78+
*/
79+
private int collect1()
80+
{
81+
for (size_t i = 0; i < rawobjs.length; i++) {
82+
if (rawobjs[i] == '\0') {
83+
/* Skip control byte. */
84+
++i;
85+
86+
/* Increment address counter. */
87+
++addr;
88+
89+
/* Binary has a maximum size of 65,280 bytes (FF00h). */
90+
if (addr > 65280)
91+
return error("final binary exceeds 65,280 bytes");
92+
} else if (rawobjs[i] == '\001') {
93+
string name;
94+
95+
/* Skip control byte. */
96+
++i;
97+
98+
/* Get symbol name, put in collect struct. */
99+
while (rawobjs[i] != '\001') {
100+
name ~= rawobjs[i++];
101+
if (i == rawobjs.length)
102+
return error("unterminated symbol");
103+
}
104+
105+
/* Make sure there is actually a symbol here. */
106+
if (name.empty)
107+
return error("symbol marker with no symbol inside");
108+
109+
/* Make sure name isn't already in the collection. */
110+
if (dupname(name) == true)
111+
return error("duplicate symbol: " ~ name);
112+
113+
/* Add to collection table. */
114+
collect newcollect = { name, addr };
115+
collection ~= newcollect;
116+
} else if (rawobjs[i] == '\002') {
117+
/* Skip control byte. */
118+
++i;
119+
120+
/* Ignore the references during pass 1. */
121+
while (rawobjs[i] != '\002') {
122+
++i;
123+
if (i == rawobjs.length)
124+
return error("unterminated symbol reference");
125+
}
126+
127+
/* Increment address counter. */
128+
addr += 2;
129+
130+
/* Binary has a maximum size of 65,280 bytes (FF00h). */
131+
if (addr > 65280)
132+
return error("final binary exceeds 65,280 bytes");
133+
} else {
134+
/* This should never happen. */
135+
return error("unknown control byte: " ~ to!string(rawobjs[i]));
136+
}
137+
}
138+
139+
return 0;
140+
}
141+
142+
/**
143+
* Pass 2: Write out final binary.
144+
*/
145+
private int process2()
146+
{
147+
for (size_t i = 0; i < rawobjs.length; i++) {
148+
if (rawobjs[i] == '\0') {
149+
/* Skip control byte. */
150+
++i;
151+
152+
/* Write byte to final binary. */
153+
binary ~= rawobjs[i];
154+
} else if (rawobjs[i] == '\001') {
155+
/* Skip control byte. */
156+
++i;
157+
158+
/* Ignore the declarations during pass 2. */
159+
while (rawobjs[i] != '\001') {
160+
++i;
161+
if (i == rawobjs.length)
162+
return error("unterminated symbol");
163+
}
164+
} else if (rawobjs[i] == '\002') {
165+
bool found = false;
166+
string name;
167+
168+
/* Skip control byte. */
169+
++i;
170+
171+
while (rawobjs[i] != '\002') {
172+
name ~= rawobjs[i];
173+
++i;
174+
175+
if (i == rawobjs.length)
176+
return error("unterminated symbol reference");
177+
}
178+
179+
/* Make sure there is actually a symbol here. */
180+
if (name.empty)
181+
return error("symbol marker with no symbol inside");
182+
183+
/* Output symbol. */
184+
for (size_t j = 0; j < collection.length; j++) {
185+
if (name == collection[j].name) {
186+
binary ~= cast(ubyte)(collection[j].addr & 0xff);
187+
binary ~= cast(ubyte)((collection[j].addr >> 8) & 0xff);
188+
found = true;
189+
break;
190+
}
191+
}
192+
193+
/* If symbol was not found, error out. */
194+
if (found == false)
195+
return error("undefined reference: " ~ name);
196+
} else {
197+
/* This should never happen. */
198+
return error("unknown control byte: " ~ to!string(rawobjs[i]));
199+
}
200+
}
201+
202+
return 0;
203+
}
204+
205+
/**
206+
* After all code is emitted, write it out to a file.
207+
*/
208+
private void fileWrite(string outfile)
209+
{
210+
import std.file : write;
211+
212+
write(outfile, binary);
213+
}
214+
215+
int main(string[] args)
216+
{
217+
int ret;
218+
219+
if (args.length < 3) {
220+
stderr.writeln("usage: l80 file.com file1.obj [file2.obj ...]");
221+
return 1;
222+
}
223+
224+
/**
225+
* Write all object files into a single buffer.
226+
*/
227+
foreach (size_t i; 2 .. args.length) {
228+
auto objsplit = args[i].findSplit(".obj");
229+
230+
/* Objects must end in .obj or .lib. */
231+
if (objsplit[1].empty) {
232+
auto libsplit = args[i].findSplit(".lib");
233+
if (libsplit[1].empty)
234+
return error("object files must end in \".obj\" or \".lib\"");
235+
}
236+
237+
rawobjs ~= cast(ubyte[])read(args[i]);
238+
}
239+
240+
ret = collect1();
241+
if (ret == 0) {
242+
ret = process2();
243+
if (ret == 0)
244+
fileWrite(args[1]);
245+
}
246+
247+
return ret;
248+
}

0 commit comments

Comments
 (0)