stuffy 6502
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
; count.asm - prints "0123456789" then a newline and halts.
|
||||||
|
; Exercises ADC, CMP, branches and a loop.
|
||||||
|
|
||||||
|
.export start
|
||||||
|
|
||||||
|
start:
|
||||||
|
lda #'0'
|
||||||
|
loop:
|
||||||
|
sta $F001
|
||||||
|
clc
|
||||||
|
adc #$01
|
||||||
|
cmp #$3A ; '9' + 1
|
||||||
|
bne loop
|
||||||
|
lda #$0A ; newline
|
||||||
|
sta $F001
|
||||||
|
lda #$00
|
||||||
|
sta $F003
|
||||||
|
rts
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
; hello.asm - single-object demo.
|
||||||
|
; Prints "Hello, World!" through the memory-mapped terminal at $F001,
|
||||||
|
; then writes to the halt register at $F003.
|
||||||
|
|
||||||
|
.export start
|
||||||
|
.org $0600
|
||||||
|
|
||||||
|
jmp start
|
||||||
|
|
||||||
|
msg:
|
||||||
|
.asciiz "Hello, World!"
|
||||||
|
|
||||||
|
start:
|
||||||
|
ldx #0
|
||||||
|
loop:
|
||||||
|
lda msg,x
|
||||||
|
beq done
|
||||||
|
sta $F001
|
||||||
|
inx
|
||||||
|
jmp loop
|
||||||
|
done:
|
||||||
|
lda #$00
|
||||||
|
sta $F003
|
||||||
|
rts
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
; main.asm - cross-object demo, part 2.
|
||||||
|
; Imports `putc` from putc.wo and prints "Hello, 6502!".
|
||||||
|
|
||||||
|
.export start
|
||||||
|
.import putc
|
||||||
|
|
||||||
|
jmp start
|
||||||
|
|
||||||
|
msg:
|
||||||
|
.asciiz "Hello, 6502!"
|
||||||
|
|
||||||
|
start:
|
||||||
|
ldx #0
|
||||||
|
loop:
|
||||||
|
lda msg,x
|
||||||
|
beq done
|
||||||
|
jsr putc
|
||||||
|
inx
|
||||||
|
jmp loop
|
||||||
|
done:
|
||||||
|
lda #$00
|
||||||
|
sta $F003
|
||||||
|
rts
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
; putc.asm - cross-object demo, part 1.
|
||||||
|
; Exports `putc`: write the byte in A to the terminal and return.
|
||||||
|
; This object is relocatable; the linker places it at the load address.
|
||||||
|
|
||||||
|
.export putc
|
||||||
|
|
||||||
|
putc:
|
||||||
|
sta $F001
|
||||||
|
rts
|
||||||
File diff suppressed because it is too large
Load Diff
+72
-3
@@ -1,6 +1,75 @@
|
|||||||
import std.stdio;
|
module assembler.main;
|
||||||
|
|
||||||
void main()
|
import std.format : format;
|
||||||
|
import std.getopt : getopt;
|
||||||
|
import std.path : setExtension;
|
||||||
|
import std.stdio : File, stderr, stdout, writeln;
|
||||||
|
import assembler.assembler : assembleFile;
|
||||||
|
import wcore.objfmt : Object, writeObject;
|
||||||
|
|
||||||
|
private void printHelp()
|
||||||
{
|
{
|
||||||
writeln("same deal");
|
writeln("wiasm - small 6502 assembler");
|
||||||
|
writeln();
|
||||||
|
writeln("Usage: wiasm [options] <input.asm>");
|
||||||
|
writeln();
|
||||||
|
writeln(" -o, --output <file> output object file (default: input with .wo)");
|
||||||
|
writeln(" -l, --listing <file> write a listing file");
|
||||||
|
writeln(" -h, --help show this help");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeListing(string path, string input, ref Object obj)
|
||||||
|
{
|
||||||
|
auto f = File(path, "w");
|
||||||
|
f.writeln("; listing for ", input);
|
||||||
|
foreach (s; obj.segments)
|
||||||
|
{
|
||||||
|
f.writeln(format("; segment %s flags=$%02X origin=$%04X size=%d",
|
||||||
|
s.name, s.flags, s.origin, s.data.length));
|
||||||
|
for (size_t i = 0; i < s.data.length; i += 16)
|
||||||
|
{
|
||||||
|
size_t end = (i + 16 < s.data.length) ? i + 16 : s.data.length;
|
||||||
|
string hexs;
|
||||||
|
foreach (b; s.data[i .. end]) hexs ~= format("%02X ", b);
|
||||||
|
f.writeln(format(" %04X %s", i, hexs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.writeln("; symbols");
|
||||||
|
foreach (sym; obj.symbols)
|
||||||
|
f.writeln(format(" %-24s = $%04X flags=$%02X seg=%d",
|
||||||
|
sym.name, sym.value, sym.flags, sym.seg == ushort.max ? -1 : cast(int)sym.seg));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(string[] args)
|
||||||
|
{
|
||||||
|
string outPath;
|
||||||
|
string listPath;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto helpInfo = getopt(args,
|
||||||
|
"o|output", "output object file", &outPath,
|
||||||
|
"l|listing", "listing file", &listPath,
|
||||||
|
);
|
||||||
|
if (helpInfo.helpWanted)
|
||||||
|
{
|
||||||
|
printHelp();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (args.length < 2)
|
||||||
|
{
|
||||||
|
printHelp();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
string input = args[1];
|
||||||
|
if (outPath.length == 0) outPath = setExtension(input, ".wo");
|
||||||
|
auto obj = assembleFile(input);
|
||||||
|
writeObject(outPath, obj);
|
||||||
|
if (listPath.length) writeListing(listPath, input, obj);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
stderr.writeln("wiasm: ", e.msg);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* 6502 system bus: 64 KiB RAM plus memory-mapped terminal I/O.
|
||||||
|
*
|
||||||
|
* Memory-mapped I/O registers:
|
||||||
|
* $F001 write: emit byte to stdout (raw char); read: 0
|
||||||
|
* $F002 read: next byte of buffered stdin (0 when exhausted); write: ignored
|
||||||
|
* $F003 write: set halted flag; read: 0
|
||||||
|
* $F004 read: 0x80 when buffered input remains, else 0; write: ignored
|
||||||
|
*/
|
||||||
|
module emulator.bus;
|
||||||
|
|
||||||
|
import std.stdio : stdout;
|
||||||
|
|
||||||
|
/// Transmit register: writing a byte prints it to stdout.
|
||||||
|
enum ushort IO_TX = 0xF001;
|
||||||
|
/// Receive register: reading consumes the next buffered input byte (0 when empty).
|
||||||
|
enum ushort IO_RX = 0xF002;
|
||||||
|
/// Halt register: writing any byte sets the halted flag.
|
||||||
|
enum ushort IO_HALT = 0xF003;
|
||||||
|
/// Receive-ready register: 0x80 when input is still available.
|
||||||
|
enum ushort IO_RXRDY = 0xF004;
|
||||||
|
|
||||||
|
class Bus
|
||||||
|
{
|
||||||
|
/// 64 KiB of RAM.
|
||||||
|
ubyte[65536] ram;
|
||||||
|
|
||||||
|
/// Set when the program writes to IO_HALT.
|
||||||
|
bool halted = false;
|
||||||
|
|
||||||
|
private ubyte[] input;
|
||||||
|
private size_t inputPos = 0;
|
||||||
|
|
||||||
|
/// Load a byte array at an address (wraps within 64 KiB).
|
||||||
|
void loadAt(ushort addr, const(ubyte)[] data)
|
||||||
|
{
|
||||||
|
foreach (i, ubyte b; data)
|
||||||
|
ram[(addr + i) & 0xFFFF] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the buffered input stream consumed by reads of IO_RX.
|
||||||
|
void setInput(const(ubyte)[] data)
|
||||||
|
{
|
||||||
|
input = data.dup;
|
||||||
|
inputPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True when more buffered input remains.
|
||||||
|
bool inputAvailable() const
|
||||||
|
{
|
||||||
|
return inputPos < input.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a byte from memory or an I/O register.
|
||||||
|
ubyte read(ushort addr)
|
||||||
|
{
|
||||||
|
switch (addr)
|
||||||
|
{
|
||||||
|
case IO_TX:
|
||||||
|
return 0;
|
||||||
|
case IO_RX:
|
||||||
|
if (inputPos < input.length)
|
||||||
|
return input[inputPos++];
|
||||||
|
return 0;
|
||||||
|
case IO_HALT:
|
||||||
|
return 0;
|
||||||
|
case IO_RXRDY:
|
||||||
|
return inputAvailable() ? 0x80 : 0;
|
||||||
|
default:
|
||||||
|
return ram[addr];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a byte to memory or an I/O register.
|
||||||
|
void write(ushort addr, ubyte val)
|
||||||
|
{
|
||||||
|
switch (addr)
|
||||||
|
{
|
||||||
|
case IO_TX:
|
||||||
|
{
|
||||||
|
ubyte[1] buf;
|
||||||
|
buf[0] = val;
|
||||||
|
stdout.rawWrite(buf[]);
|
||||||
|
stdout.flush();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IO_RX:
|
||||||
|
break; // ignored
|
||||||
|
case IO_HALT:
|
||||||
|
halted = true;
|
||||||
|
break;
|
||||||
|
case IO_RXRDY:
|
||||||
|
break; // ignored
|
||||||
|
default:
|
||||||
|
ram[addr] = val;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// unit tests
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
auto b = new Bus();
|
||||||
|
|
||||||
|
// plain RAM round trip
|
||||||
|
b.write(0x0200, 0xAB);
|
||||||
|
assert(b.read(0x0200) == 0xAB);
|
||||||
|
|
||||||
|
// loadAt writes a block
|
||||||
|
b.loadAt(0x0300, [0x01, 0x02, 0x03, 0x04]);
|
||||||
|
assert(b.read(0x0300) == 0x01);
|
||||||
|
assert(b.read(0x0301) == 0x02);
|
||||||
|
assert(b.read(0x0303) == 0x04);
|
||||||
|
|
||||||
|
// $F003 write sets halted
|
||||||
|
assert(b.halted == false);
|
||||||
|
b.write(0xF003, 0x00);
|
||||||
|
assert(b.halted == true);
|
||||||
|
|
||||||
|
// $F001 / $F003 / $F004 reads return 0 / 0 / status
|
||||||
|
b.write(0xF001, 0x00);
|
||||||
|
assert(b.read(0xF001) == 0);
|
||||||
|
assert(b.read(0xF003) == 0);
|
||||||
|
|
||||||
|
// input buffer handling
|
||||||
|
assert(b.inputAvailable() == false);
|
||||||
|
assert(b.read(0xF004) == 0x00);
|
||||||
|
assert(b.read(0xF002) == 0x00); // exhausted -> 0
|
||||||
|
|
||||||
|
b.setInput([0x41, 0x42]);
|
||||||
|
assert(b.inputAvailable() == true);
|
||||||
|
assert(b.read(0xF004) == 0x80);
|
||||||
|
assert(b.read(0xF002) == 0x41);
|
||||||
|
assert(b.read(0xF004) == 0x80);
|
||||||
|
assert(b.read(0xF002) == 0x42);
|
||||||
|
assert(b.read(0xF004) == 0x00);
|
||||||
|
assert(b.read(0xF002) == 0x00);
|
||||||
|
}
|
||||||
+1014
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,230 @@
|
|||||||
|
/**
|
||||||
|
* weirdcpu — NMOS 6502 emulator CLI.
|
||||||
|
*
|
||||||
|
* Loads a .wimg image (via wcore.objfmt.readImage) or a raw binary, runs it
|
||||||
|
* until the program halts ($F003 write) or the cycle budget is exhausted.
|
||||||
|
*
|
||||||
|
* Usage: weirdcpu [options] <file>
|
||||||
|
*/
|
||||||
|
module emulator.main;
|
||||||
|
|
||||||
|
import std.stdio : stderr, stdout, stdin, write, writef, writefln, writeln;
|
||||||
|
import std.file : read;
|
||||||
|
import std.conv : to, ConvException;
|
||||||
|
|
||||||
|
import core.sys.posix.unistd : isatty;
|
||||||
|
|
||||||
|
import wcore.objfmt;
|
||||||
|
import emulator.bus;
|
||||||
|
import emulator.cpu;
|
||||||
|
|
||||||
|
private void usage()
|
||||||
|
{
|
||||||
|
stderr.writeln("weirdcpu — NMOS 6502 emulator");
|
||||||
|
stderr.writeln("Usage: weirdcpu [options] <file>");
|
||||||
|
stderr.writeln();
|
||||||
|
stderr.writeln("Options:");
|
||||||
|
stderr.writeln(" --raw Load <file> as a raw binary (default: parse .wimg)");
|
||||||
|
stderr.writeln(" --origin <n> Load address for --raw (default 0x0600)");
|
||||||
|
stderr.writeln(" --entry <n> Entry point (default: image entry, or origin for raw)");
|
||||||
|
stderr.writeln(" --max-cycles <n> Cycle budget (default 100000000)");
|
||||||
|
stderr.writeln(" --trace Disassemble each instruction to stderr");
|
||||||
|
stderr.writeln(" --dump Hex-dump memory around load area after halt");
|
||||||
|
stderr.writeln(" -h, --help Show this help");
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool parseAddr(string s, out ushort v, string opt)
|
||||||
|
{
|
||||||
|
long n;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (s.length > 2 && (s[0 .. 2] == "0x" || s[0 .. 2] == "0X"))
|
||||||
|
n = to!long(s[2 .. $], 16);
|
||||||
|
else
|
||||||
|
n = to!long(s, 10);
|
||||||
|
}
|
||||||
|
catch (ConvException e)
|
||||||
|
{
|
||||||
|
stderr.writeln("error: invalid value for ", opt, ": ", s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (n < 0 || n > 0xFFFF)
|
||||||
|
{
|
||||||
|
stderr.writeln("error: ", opt, " out of range (0..65535): ", s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
v = cast(ushort) n;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool parseULong(string s, out ulong v, string opt)
|
||||||
|
{
|
||||||
|
ulong n;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
n = to!ulong(s, 10);
|
||||||
|
}
|
||||||
|
catch (ConvException e)
|
||||||
|
{
|
||||||
|
stderr.writeln("error: invalid value for ", opt, ": ", s);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
v = n;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hexDump(Bus bus, ushort start)
|
||||||
|
{
|
||||||
|
const uint count = 256;
|
||||||
|
for (uint i = 0; i < count; i += 16)
|
||||||
|
{
|
||||||
|
ushort a = cast(ushort) (start + i);
|
||||||
|
writef("%04X ", a);
|
||||||
|
foreach (j; 0 .. 16)
|
||||||
|
{
|
||||||
|
ushort aa = cast(ushort) (a + j);
|
||||||
|
writef("%02X ", bus.read(aa));
|
||||||
|
}
|
||||||
|
write(" ");
|
||||||
|
foreach (j; 0 .. 16)
|
||||||
|
{
|
||||||
|
ushort aa = cast(ushort) (a + j);
|
||||||
|
ubyte b = bus.read(aa);
|
||||||
|
char ch = (b >= 32 && b < 127) ? cast(char) b : '.';
|
||||||
|
write(ch);
|
||||||
|
}
|
||||||
|
writeln();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(string[] args)
|
||||||
|
{
|
||||||
|
bool raw = false;
|
||||||
|
bool trace = false;
|
||||||
|
bool dump = false;
|
||||||
|
bool hasEntry = false;
|
||||||
|
ushort origin = 0x0600;
|
||||||
|
ushort entry = 0;
|
||||||
|
ulong maxCycles = 100_000_000;
|
||||||
|
string file;
|
||||||
|
|
||||||
|
for (size_t i = 1; i < args.length; i++)
|
||||||
|
{
|
||||||
|
string a = args[i];
|
||||||
|
if (a == "-h" || a == "--help")
|
||||||
|
{
|
||||||
|
usage();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if (a == "--raw")
|
||||||
|
raw = true;
|
||||||
|
else if (a == "--trace")
|
||||||
|
trace = true;
|
||||||
|
else if (a == "--dump")
|
||||||
|
dump = true;
|
||||||
|
else if (a == "--origin")
|
||||||
|
{
|
||||||
|
if (++i >= args.length || !parseAddr(args[i], origin, "--origin"))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (a == "--entry")
|
||||||
|
{
|
||||||
|
if (++i >= args.length || !parseAddr(args[i], entry, "--entry"))
|
||||||
|
return 1;
|
||||||
|
hasEntry = true;
|
||||||
|
}
|
||||||
|
else if (a == "--max-cycles")
|
||||||
|
{
|
||||||
|
if (++i >= args.length || !parseULong(args[i], maxCycles, "--max-cycles"))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else if (a.length > 1 && a[0] == '-')
|
||||||
|
{
|
||||||
|
stderr.writeln("error: unknown option: ", a);
|
||||||
|
usage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
file = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.length == 0)
|
||||||
|
{
|
||||||
|
usage();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bus bus = new Bus();
|
||||||
|
ushort loadAddr;
|
||||||
|
ushort entryPoint;
|
||||||
|
ubyte[] data;
|
||||||
|
|
||||||
|
if (raw)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
data = cast(ubyte[]) read(file);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
stderr.writeln("error: cannot read ", file, ": ", e.msg);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
loadAddr = origin;
|
||||||
|
entryPoint = hasEntry ? entry : origin;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ubyte[] bytes;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bytes = cast(ubyte[]) read(file);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
stderr.writeln("error: cannot read ", file, ": ", e.msg);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (bytes.length < 4 || bytes[0] != 'W' || bytes[1] != 'I' || bytes[2] != 'M' || bytes[3] != 'G')
|
||||||
|
{
|
||||||
|
stderr.writeln("error: ", file, " is not a .wimg image (missing WIMG magic); use --raw for raw binaries");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
Image img = readImage(file);
|
||||||
|
loadAddr = img.loadAddr;
|
||||||
|
entryPoint = hasEntry ? entry : img.entry;
|
||||||
|
data = img.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bus.loadAt(loadAddr, data);
|
||||||
|
|
||||||
|
// Read buffered stdin lazily; never block on a TTY.
|
||||||
|
if (isatty(0) == 0)
|
||||||
|
{
|
||||||
|
ubyte[] input;
|
||||||
|
foreach (ubyte[] chunk; stdin.byChunk(4096))
|
||||||
|
input ~= chunk;
|
||||||
|
bus.setInput(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
Cpu cpu = new Cpu(bus);
|
||||||
|
cpu.reset();
|
||||||
|
cpu.setPC(entryPoint);
|
||||||
|
|
||||||
|
while (!bus.halted)
|
||||||
|
{
|
||||||
|
if (cpu.cycles >= maxCycles)
|
||||||
|
{
|
||||||
|
stderr.writeln("error: cycle budget exceeded (max ", maxCycles, " cycles)");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (trace)
|
||||||
|
stderr.writefln("%04X: %-12s %s", cpu.PC, cpu.disasm(cpu.PC), cpu.regs());
|
||||||
|
cpu.step();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dump)
|
||||||
|
hexDump(bus, loadAddr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,442 @@
|
|||||||
|
/**
|
||||||
|
* Small 6502 linker for the weirdcpu toolchain.
|
||||||
|
*
|
||||||
|
* Reads one or more `.wo` objects (see `wcore.objfmt`), lays out their
|
||||||
|
* segments, resolves cross-object symbols, applies relocations, and produces
|
||||||
|
* a `.wimg` loadable image plus an optional human-readable symbol map.
|
||||||
|
*
|
||||||
|
* Everything here operates on the in-memory `Object`/`Image` structs defined
|
||||||
|
* in `wcore.objfmt`; the CLI wrapper in `main.d` handles file I/O and argument
|
||||||
|
* parsing.
|
||||||
|
*/
|
||||||
|
module linker.linker;
|
||||||
|
|
||||||
|
import wcore.objfmt;
|
||||||
|
import std.algorithm : sort;
|
||||||
|
import std.array : join;
|
||||||
|
import std.string : format;
|
||||||
|
|
||||||
|
/// Disambiguate the object-file `Object` from the root `object.Object` class.
|
||||||
|
alias Object = wcore.objfmt.Object;
|
||||||
|
|
||||||
|
/// Options controlling layout and entry resolution.
|
||||||
|
struct LinkOptions
|
||||||
|
{
|
||||||
|
ushort origin = 0x0600; /// base address for relocatable segments
|
||||||
|
string entrySymbol; /// optional: -e symbol
|
||||||
|
bool verbose = false; /// extra diagnostics (currently unused)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result of a link: the loadable image plus a printable symbol map.
|
||||||
|
struct LinkResult
|
||||||
|
{
|
||||||
|
Image image;
|
||||||
|
string mapText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A segment (or bss reservation) after layout.
|
||||||
|
private struct Placed
|
||||||
|
{
|
||||||
|
uint base;
|
||||||
|
uint size;
|
||||||
|
string name; /// for diagnostics
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A symbol line in the map, for sorting.
|
||||||
|
private struct SymLine
|
||||||
|
{
|
||||||
|
uint address;
|
||||||
|
string name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Link `objects` into a loadable image.
|
||||||
|
LinkResult link(Object[] objects, LinkOptions opts = LinkOptions.init)
|
||||||
|
{
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// 1. Place segments.
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
uint cursor = opts.origin;
|
||||||
|
Placed[] placed;
|
||||||
|
uint[][] segBase = new uint[][objects.length];
|
||||||
|
|
||||||
|
foreach (objIdx, ref obj; objects)
|
||||||
|
{
|
||||||
|
segBase[objIdx] = new uint[obj.segments.length];
|
||||||
|
foreach (segIdx, ref seg; obj.segments)
|
||||||
|
{
|
||||||
|
uint size = cast(uint) seg.data.length;
|
||||||
|
uint base;
|
||||||
|
|
||||||
|
if (seg.origin != 0)
|
||||||
|
base = seg.origin;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base = cursor;
|
||||||
|
cursor += size;
|
||||||
|
if (cursor > 0x10000)
|
||||||
|
throw new Exception(format(
|
||||||
|
"image exceeds 64 KiB while placing segment '%s'", seg.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (base + size > 0x10000)
|
||||||
|
throw new Exception(format(
|
||||||
|
"segment '%s' exceeds 64 KiB (0x%04X..0x%04X)", seg.name, base, base + size));
|
||||||
|
|
||||||
|
foreach (p; placed)
|
||||||
|
{
|
||||||
|
if (size > 0 && base < p.base + p.size && p.base < base + size)
|
||||||
|
throw new Exception(format(
|
||||||
|
"segment '%s' at 0x%04X overlaps segment '%s' at 0x%04X",
|
||||||
|
seg.name, base, p.name, p.base));
|
||||||
|
}
|
||||||
|
|
||||||
|
placed ~= Placed(base, size, format("obj%d:%s", objIdx, seg.name));
|
||||||
|
segBase[objIdx][segIdx] = base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// 2. Compute defined-symbol addresses; build the global symbol table.
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
uint[][] symAddr = new uint[][objects.length];
|
||||||
|
uint[string] globalAddr; /// name -> address for defined global symbols
|
||||||
|
uint[string] allDefined; /// name -> address for any defined symbol (entry fallback)
|
||||||
|
|
||||||
|
foreach (objIdx, ref obj; objects)
|
||||||
|
{
|
||||||
|
symAddr[objIdx] = new uint[obj.symbols.length];
|
||||||
|
foreach (symIdx, ref sym; obj.symbols)
|
||||||
|
{
|
||||||
|
if (!sym.isDefined())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (sym.seg != NoIndex && sym.seg >= obj.segments.length)
|
||||||
|
throw new Exception(format(
|
||||||
|
"symbol '%s' references invalid segment %d", sym.name, sym.seg));
|
||||||
|
|
||||||
|
uint a = (sym.seg != NoIndex)
|
||||||
|
? segBase[objIdx][sym.seg] + cast(uint) sym.value
|
||||||
|
: cast(uint) sym.value;
|
||||||
|
symAddr[objIdx][symIdx] = a;
|
||||||
|
|
||||||
|
if (sym.isGlobal())
|
||||||
|
{
|
||||||
|
if (auto p = sym.name in globalAddr)
|
||||||
|
{
|
||||||
|
if (*p != a)
|
||||||
|
throw new Exception(format(
|
||||||
|
"duplicate global symbol '%s' defined at 0x%04X and 0x%04X",
|
||||||
|
sym.name, *p, a));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
globalAddr[sym.name] = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sym.name !in allDefined)
|
||||||
|
allDefined[sym.name] = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// 3. Resolve relocations.
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
foreach (objIdx, ref obj; objects)
|
||||||
|
{
|
||||||
|
foreach (rel; obj.relocs)
|
||||||
|
{
|
||||||
|
if (rel.seg >= obj.segments.length)
|
||||||
|
throw new Exception(format("relocation references invalid segment %d", rel.seg));
|
||||||
|
if (rel.sym >= obj.symbols.length)
|
||||||
|
throw new Exception(format("relocation references invalid symbol %d", rel.sym));
|
||||||
|
|
||||||
|
Segment* seg = &obj.segments[rel.seg];
|
||||||
|
uint base = segBase[objIdx][rel.seg];
|
||||||
|
Symbol* sym = &obj.symbols[rel.sym];
|
||||||
|
|
||||||
|
uint target;
|
||||||
|
if (sym.isDefined())
|
||||||
|
target = symAddr[objIdx][rel.sym];
|
||||||
|
else if (auto p = sym.name in globalAddr)
|
||||||
|
target = *p;
|
||||||
|
else
|
||||||
|
throw new Exception(format(
|
||||||
|
"undefined symbol '%s' referenced by object %d", sym.name, objIdx));
|
||||||
|
|
||||||
|
uint off = rel.offset;
|
||||||
|
ubyte[] data = seg.data;
|
||||||
|
|
||||||
|
final switch (cast(RelocType) rel.type)
|
||||||
|
{
|
||||||
|
case RelocType.abs16:
|
||||||
|
if (off + 2 > data.length)
|
||||||
|
throw new Exception(format(
|
||||||
|
"abs16 relocation out of range in segment '%s'", seg.name));
|
||||||
|
data[off] = cast(ubyte) (target & 0xFF);
|
||||||
|
data[off + 1] = cast(ubyte) ((target >> 8) & 0xFF);
|
||||||
|
break;
|
||||||
|
case RelocType.lo8:
|
||||||
|
if (off + 1 > data.length)
|
||||||
|
throw new Exception(format(
|
||||||
|
"lo8 relocation out of range in segment '%s'", seg.name));
|
||||||
|
data[off] = cast(ubyte) (target & 0xFF);
|
||||||
|
break;
|
||||||
|
case RelocType.hi8:
|
||||||
|
if (off + 1 > data.length)
|
||||||
|
throw new Exception(format(
|
||||||
|
"hi8 relocation out of range in segment '%s'", seg.name));
|
||||||
|
data[off] = cast(ubyte) ((target >> 8) & 0xFF);
|
||||||
|
break;
|
||||||
|
case RelocType.rel8:
|
||||||
|
if (off + 1 > data.length)
|
||||||
|
throw new Exception(format(
|
||||||
|
"rel8 relocation out of range in segment '%s'", seg.name));
|
||||||
|
{
|
||||||
|
int disp = cast(int) target - cast(int) (base + off + 1);
|
||||||
|
if (disp < -128 || disp > 127)
|
||||||
|
throw new Exception(format(
|
||||||
|
"rel8 displacement %d out of range for symbol '%s'", disp, sym.name));
|
||||||
|
data[off] = cast(ubyte) (disp & 0xFF);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// 4. Determine entry address.
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
uint load = opts.origin;
|
||||||
|
uint high = opts.origin;
|
||||||
|
bool any = false;
|
||||||
|
foreach (p; placed)
|
||||||
|
{
|
||||||
|
if (!any || p.base < load)
|
||||||
|
load = p.base;
|
||||||
|
uint h = p.base + p.size;
|
||||||
|
if (!any || h > high)
|
||||||
|
high = h;
|
||||||
|
any = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort entry;
|
||||||
|
if (opts.entrySymbol.length > 0)
|
||||||
|
{
|
||||||
|
if (auto p = opts.entrySymbol in globalAddr)
|
||||||
|
entry = cast(ushort) *p;
|
||||||
|
else if (auto q = opts.entrySymbol in allDefined)
|
||||||
|
entry = cast(ushort) *q;
|
||||||
|
else
|
||||||
|
throw new Exception(format("entry symbol '%s' not defined", opts.entrySymbol));
|
||||||
|
}
|
||||||
|
else if (auto p = "start" in globalAddr)
|
||||||
|
entry = cast(ushort) *p;
|
||||||
|
else if (any)
|
||||||
|
entry = cast(ushort) load;
|
||||||
|
else
|
||||||
|
entry = opts.origin;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// 5. Build the output image.
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
ubyte[] imgdata = new ubyte[any ? (high - load) : 0];
|
||||||
|
foreach (objIdx, ref obj; objects)
|
||||||
|
{
|
||||||
|
foreach (segIdx, ref seg; obj.segments)
|
||||||
|
{
|
||||||
|
if (seg.isBss())
|
||||||
|
continue;
|
||||||
|
uint base = segBase[objIdx][segIdx];
|
||||||
|
uint off = base - load;
|
||||||
|
imgdata[off .. off + seg.data.length] = seg.data[];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image image;
|
||||||
|
image.loadAddr = cast(ushort) load;
|
||||||
|
image.entry = entry;
|
||||||
|
image.data = imgdata;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
// 6. Build the symbol map.
|
||||||
|
// ------------------------------------------------------------------
|
||||||
|
string[] lines;
|
||||||
|
lines ~= format("load = $%04X", load);
|
||||||
|
lines ~= format("entry = $%04X", entry);
|
||||||
|
lines ~= "";
|
||||||
|
lines ~= "segments:";
|
||||||
|
foreach (objIdx, ref obj; objects)
|
||||||
|
{
|
||||||
|
foreach (segIdx, ref seg; obj.segments)
|
||||||
|
{
|
||||||
|
uint base = segBase[objIdx][segIdx];
|
||||||
|
uint size = cast(uint) seg.data.length;
|
||||||
|
lines ~= format(" $%04X-$%04X %s%s", base, base + size,
|
||||||
|
(seg.isBss() ? "[bss] " : ""), seg.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lines ~= "";
|
||||||
|
lines ~= "symbols:";
|
||||||
|
|
||||||
|
SymLine[] syms;
|
||||||
|
foreach (objIdx, ref obj; objects)
|
||||||
|
{
|
||||||
|
foreach (symIdx, sym; obj.symbols)
|
||||||
|
{
|
||||||
|
if (sym.isDefined())
|
||||||
|
syms ~= SymLine(symAddr[objIdx][symIdx], sym.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syms.sort!((a, b) => a.address != b.address ? a.address < b.address : a.name < b.name);
|
||||||
|
foreach (s; syms)
|
||||||
|
lines ~= format("$%04X %s", s.address, s.name);
|
||||||
|
|
||||||
|
LinkResult res;
|
||||||
|
res.image = image;
|
||||||
|
res.mapText = lines.join("\n");
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===========================================================================
|
||||||
|
// tests
|
||||||
|
// ===========================================================================
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
// --- abs16 across objects: A exports `foo`, B patches an abs16 to it ---
|
||||||
|
Object a;
|
||||||
|
a.segments ~= Segment(".code", cast(ubyte) (SegFlag.exec | SegFlag.alloc),
|
||||||
|
0, cast(ubyte[]) [0xEA, 0xEA, 0xEA]);
|
||||||
|
a.symbols ~= Symbol("foo", 0, cast(ubyte) (SymFlag.defined | SymFlag.global), 0);
|
||||||
|
|
||||||
|
Object b;
|
||||||
|
b.segments ~= Segment(".code", cast(ubyte) (SegFlag.exec | SegFlag.alloc),
|
||||||
|
0, cast(ubyte[]) [0x00, 0x00]);
|
||||||
|
b.symbols ~= Symbol("foo", 0, cast(ubyte) (SymFlag.imported | SymFlag.global), NoIndex);
|
||||||
|
b.relocs ~= Reloc(0, 0, 0, RelocType.abs16);
|
||||||
|
|
||||||
|
LinkResult r = link([a, b]);
|
||||||
|
|
||||||
|
assert(r.image.loadAddr == 0x0600);
|
||||||
|
// foo -> 0x0600; B's segment placed at 0x0603, patched little-endian.
|
||||||
|
assert(r.image.data.length == 5);
|
||||||
|
assert(r.image.data[3] == 0x00, "abs16 low byte");
|
||||||
|
assert(r.image.data[4] == 0x06, "abs16 high byte");
|
||||||
|
// no entrySymbol and no `start` -> lowest placed base.
|
||||||
|
assert(r.image.entry == 0x0600);
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
// --- rel8 computes signed displacement; out-of-range is rejected ---
|
||||||
|
Object o;
|
||||||
|
o.segments ~= Segment(".code", cast(ubyte) (SegFlag.exec | SegFlag.alloc),
|
||||||
|
0, cast(ubyte[]) [0x00]);
|
||||||
|
o.symbols ~= Symbol("here", 0, cast(ubyte) (SymFlag.defined | SymFlag.global), 0);
|
||||||
|
o.relocs ~= Reloc(0, 0, 0, RelocType.rel8);
|
||||||
|
|
||||||
|
LinkResult r = link([o]);
|
||||||
|
// disp = 0x0600 - (0x0600 + 0 + 1) = -1 -> 0xFF
|
||||||
|
assert(r.image.data[0] == 0xFF, "rel8 -1 displacement");
|
||||||
|
|
||||||
|
// out of range: target 0x8000 absolute
|
||||||
|
Object p;
|
||||||
|
p.segments ~= Segment(".code", cast(ubyte) (SegFlag.exec | SegFlag.alloc),
|
||||||
|
0, cast(ubyte[]) [0x00]);
|
||||||
|
p.symbols ~= Symbol("far", 0x8000, cast(ubyte) (SymFlag.defined | SymFlag.global), NoIndex);
|
||||||
|
p.relocs ~= Reloc(0, 0, 0, RelocType.rel8);
|
||||||
|
|
||||||
|
bool threw = false;
|
||||||
|
try
|
||||||
|
link([p]);
|
||||||
|
catch (Exception e)
|
||||||
|
threw = true;
|
||||||
|
assert(threw, "out-of-range rel8 must be rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
// --- fixed-origin placement and overlap detection ---
|
||||||
|
Object o;
|
||||||
|
o.segments ~= Segment(".fixed", cast(ubyte) (SegFlag.exec | SegFlag.alloc),
|
||||||
|
0x4000, cast(ubyte[]) [0x01, 0x02]);
|
||||||
|
o.symbols ~= Symbol("target", 1, cast(ubyte) (SymFlag.defined | SymFlag.global), 0);
|
||||||
|
|
||||||
|
LinkResult r = link([o]);
|
||||||
|
assert(r.image.loadAddr == 0x4000);
|
||||||
|
assert(r.image.data.length == 2);
|
||||||
|
// symbol address = segment base + value = 0x4000 + 1
|
||||||
|
assert(r.image.entry == 0x4000); // no entrySymbol, no start -> lowest base
|
||||||
|
|
||||||
|
// two fixed-origin segments sharing an address must overlap
|
||||||
|
Object ov;
|
||||||
|
ov.segments ~= Segment(".a", cast(ubyte) SegFlag.alloc, 0x2000, cast(ubyte[]) [0x01]);
|
||||||
|
ov.segments ~= Segment(".b", cast(ubyte) SegFlag.alloc, 0x2000, cast(ubyte[]) [0x02]);
|
||||||
|
|
||||||
|
bool threw = false;
|
||||||
|
try
|
||||||
|
link([ov]);
|
||||||
|
catch (Exception e)
|
||||||
|
threw = true;
|
||||||
|
assert(threw, "overlapping fixed-origin segments must be rejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
// --- entry resolution via `start` ---
|
||||||
|
Object o;
|
||||||
|
o.segments ~= Segment(".code", cast(ubyte) (SegFlag.exec | SegFlag.alloc),
|
||||||
|
0, cast(ubyte[]) [0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||||
|
o.symbols ~= Symbol("start", 5, cast(ubyte) (SymFlag.defined | SymFlag.global), 0);
|
||||||
|
|
||||||
|
LinkResult r = link([o]);
|
||||||
|
assert(r.image.entry == 0x0605, "entry should come from `start`");
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
// --- undefined symbol produces an error ---
|
||||||
|
Object o;
|
||||||
|
o.segments ~= Segment(".code", cast(ubyte) (SegFlag.exec | SegFlag.alloc),
|
||||||
|
0, cast(ubyte[]) [0x00, 0x00]);
|
||||||
|
o.symbols ~= Symbol("missing", 0, cast(ubyte) (SymFlag.imported | SymFlag.global), NoIndex);
|
||||||
|
o.relocs ~= Reloc(0, 0, 0, RelocType.abs16);
|
||||||
|
|
||||||
|
bool threw = false;
|
||||||
|
try
|
||||||
|
link([o]);
|
||||||
|
catch (Exception e)
|
||||||
|
threw = true;
|
||||||
|
assert(threw, "undefined symbol must be an error");
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
// --- writeObject/readObject round-trip ---
|
||||||
|
Object o;
|
||||||
|
o.segments ~= Segment(".code", cast(ubyte) (SegFlag.exec | SegFlag.alloc),
|
||||||
|
0x1000, cast(ubyte[]) [0xA9, 0x01, 0xEA]);
|
||||||
|
o.segments ~= Segment(".bss", cast(ubyte) (SegFlag.alloc | SegFlag.bss),
|
||||||
|
0, cast(ubyte[]) [0, 0, 0, 0]);
|
||||||
|
o.symbols ~= Symbol("main", 0, cast(ubyte) (SymFlag.defined | SymFlag.global), 0);
|
||||||
|
o.relocs ~= Reloc(0, 1, 0, RelocType.lo8);
|
||||||
|
o.entry = 0x1000;
|
||||||
|
o.hasEntry = true;
|
||||||
|
|
||||||
|
string path = "/tmp/link_roundtrip_test.wo";
|
||||||
|
writeObject(path, o);
|
||||||
|
Object back = readObject(path);
|
||||||
|
|
||||||
|
assert(back.segments.length == 2);
|
||||||
|
assert(back.segments[0].name == ".code");
|
||||||
|
assert(back.segments[0].origin == 0x1000);
|
||||||
|
assert(back.segments[0].data == cast(ubyte[]) [0xA9, 0x01, 0xEA]);
|
||||||
|
assert(back.segments[1].isBss());
|
||||||
|
assert(back.segments[1].data.length == 4);
|
||||||
|
assert(back.symbols.length == 1);
|
||||||
|
assert(back.symbols[0].name == "main");
|
||||||
|
assert(back.symbols[0].isGlobal());
|
||||||
|
assert(back.relocs.length == 1);
|
||||||
|
assert(back.relocs[0].type == RelocType.lo8);
|
||||||
|
assert(back.hasEntry);
|
||||||
|
assert(back.entry == 0x1000);
|
||||||
|
}
|
||||||
+113
-3
@@ -1,6 +1,116 @@
|
|||||||
import std.stdio;
|
/**
|
||||||
|
* CLI wrapper for the weirdcpu linker.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* wlinker [options] -o <out.wimg> <a.wo> [b.wo ...]
|
||||||
|
*/
|
||||||
|
module linker.main;
|
||||||
|
|
||||||
void main()
|
import linker.linker;
|
||||||
|
import wcore.objfmt;
|
||||||
|
import std.conv : to;
|
||||||
|
import std.file;
|
||||||
|
import std.stdio;
|
||||||
|
import core.stdc.stdlib : exit;
|
||||||
|
|
||||||
|
/// Disambiguate the object-file `Object` from the root `object.Object` class.
|
||||||
|
alias Object = wcore.objfmt.Object;
|
||||||
|
|
||||||
|
/// Parse an address argument: `0x`-prefixed hex or plain decimal.
|
||||||
|
int parseAddr(string s)
|
||||||
{
|
{
|
||||||
writefln("stub for now until i get the actual emulator done");
|
if (s.length > 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
|
||||||
|
{
|
||||||
|
int v = 0;
|
||||||
|
foreach (c; s[2 .. $])
|
||||||
|
{
|
||||||
|
v <<= 4;
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
v |= (c - '0');
|
||||||
|
else if (c >= 'a' && c <= 'f')
|
||||||
|
v |= (c - 'a' + 10);
|
||||||
|
else if (c >= 'A' && c <= 'F')
|
||||||
|
v |= (c - 'A' + 10);
|
||||||
|
else
|
||||||
|
throw new Exception("invalid hex digit in address '" ~ s ~ "'");
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
return to!int(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the argument following `args[i]`, advancing `i`; error if absent.
|
||||||
|
string needArg(string[] args, ref size_t i)
|
||||||
|
{
|
||||||
|
if (i + 1 >= args.length)
|
||||||
|
throw new Exception("missing argument after '" ~ args[i] ~ "'");
|
||||||
|
i++;
|
||||||
|
return args[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
void usage()
|
||||||
|
{
|
||||||
|
writeln("usage: wlinker [options] -o <out.wimg> <a.wo> [b.wo ...]");
|
||||||
|
writeln(" -o <out.wimg> output image (required)");
|
||||||
|
writeln(" --origin <addr> base address for relocatable segments (default 0x0600)");
|
||||||
|
writeln(" -e, --entry <sym> entry symbol");
|
||||||
|
writeln(" --map <file> write the symbol map");
|
||||||
|
writeln(" -h, --help");
|
||||||
|
}
|
||||||
|
|
||||||
|
void main(string[] args)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string outPath;
|
||||||
|
string mapPath;
|
||||||
|
LinkOptions opts;
|
||||||
|
string[] inputs;
|
||||||
|
bool wantHelp = false;
|
||||||
|
|
||||||
|
size_t i = 1;
|
||||||
|
while (i < args.length)
|
||||||
|
{
|
||||||
|
string a = args[i];
|
||||||
|
if (a == "-h" || a == "--help")
|
||||||
|
wantHelp = true;
|
||||||
|
else if (a == "-o")
|
||||||
|
outPath = needArg(args, i);
|
||||||
|
else if (a == "--origin")
|
||||||
|
opts.origin = cast(ushort) parseAddr(needArg(args, i));
|
||||||
|
else if (a == "-e" || a == "--entry")
|
||||||
|
opts.entrySymbol = needArg(args, i);
|
||||||
|
else if (a == "--map")
|
||||||
|
mapPath = needArg(args, i);
|
||||||
|
else if (a.length > 0 && a[0] == '-')
|
||||||
|
throw new Exception("unknown option: " ~ a);
|
||||||
|
else
|
||||||
|
inputs ~= a;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wantHelp)
|
||||||
|
{
|
||||||
|
usage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (outPath.length == 0)
|
||||||
|
throw new Exception("no output file specified (use -o)");
|
||||||
|
if (inputs.length == 0)
|
||||||
|
throw new Exception("no input object files");
|
||||||
|
|
||||||
|
Object[] objs;
|
||||||
|
foreach (f; inputs)
|
||||||
|
objs ~= readObject(f);
|
||||||
|
|
||||||
|
LinkResult res = link(objs, opts);
|
||||||
|
writeImage(outPath, res.image);
|
||||||
|
if (mapPath.length)
|
||||||
|
std.file.write(mapPath, res.mapText ~ "\n");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
stderr.writeln("wlinker: error: " ~ e.msg);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import std.stdio;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
writeln("hello world!");
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,420 @@
|
|||||||
|
/**
|
||||||
|
* Shared object-file / loadable-image formats for the weirdcpu toolchain.
|
||||||
|
*
|
||||||
|
* The assembler (wiasm) emits `.wo` objects. The linker (wlinker) reads one or
|
||||||
|
* more `.wo` objects and emits a `.wimg` loadable image. The emulator
|
||||||
|
* (weirdcpu) loads `.wimg` images (or raw binaries with an explicit origin).
|
||||||
|
*
|
||||||
|
* Everything is little-endian.
|
||||||
|
*
|
||||||
|
* Object format "WO01"
|
||||||
|
* --------------------
|
||||||
|
* Header (16 bytes):
|
||||||
|
* char[4] magic = "WO01"
|
||||||
|
* u16 segCount
|
||||||
|
* u16 symCount
|
||||||
|
* u16 relCount
|
||||||
|
* u16 entry ; entry address, meaningless unless flags bit0 set
|
||||||
|
* u16 flags ; bit0 = hasEntry
|
||||||
|
* u16 reserved
|
||||||
|
*
|
||||||
|
* Segment record:
|
||||||
|
* char[8] name ; NUL padded, e.g. ".code"
|
||||||
|
* u8 flags ; SegFlag bits
|
||||||
|
* u8 reserved
|
||||||
|
* u16 origin ; requested origin, 0 = relocatable
|
||||||
|
* u32 size ; byte length of data
|
||||||
|
* u8[size] data ; omitted when SegFlag.bss is set
|
||||||
|
*
|
||||||
|
* Symbol record:
|
||||||
|
* char[32] name ; NUL padded
|
||||||
|
* u16 value
|
||||||
|
* u8 flags ; SymFlag bits
|
||||||
|
* u8 reserved
|
||||||
|
* u16 seg ; segment index, 0xFFFF if none
|
||||||
|
*
|
||||||
|
* Relocation record:
|
||||||
|
* u16 seg ; segment index the patch lives in
|
||||||
|
* u16 offset ; offset within that segment's data
|
||||||
|
* u16 sym ; symbol index
|
||||||
|
* u8 type ; RelocType
|
||||||
|
* u8 reserved
|
||||||
|
*
|
||||||
|
* Image format "WIMG"
|
||||||
|
* -------------------
|
||||||
|
* char[4] magic = "WIMG"
|
||||||
|
* u16 loadAddr
|
||||||
|
* u16 entry
|
||||||
|
* u32 length
|
||||||
|
* u8[length] data
|
||||||
|
*/
|
||||||
|
module wcore.objfmt;
|
||||||
|
|
||||||
|
import std.file : read, write;
|
||||||
|
import std.conv : to;
|
||||||
|
import std.string : format;
|
||||||
|
|
||||||
|
/// Object file magic.
|
||||||
|
enum ObjMagic = "WO01";
|
||||||
|
/// Loadable image magic.
|
||||||
|
enum ImgMagic = "WIMG";
|
||||||
|
/// No segment / no symbol sentinel.
|
||||||
|
enum NoIndex = ushort.max;
|
||||||
|
|
||||||
|
/// Segment attribute bits.
|
||||||
|
enum SegFlag : ubyte
|
||||||
|
{
|
||||||
|
alloc = 1, /// occupies memory at runtime
|
||||||
|
exec = 2, /// executable
|
||||||
|
write = 4, /// writable
|
||||||
|
bss = 8, /// zero-initialised, no bytes stored on disk
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Symbol attribute bits.
|
||||||
|
enum SymFlag : ubyte
|
||||||
|
{
|
||||||
|
defined = 1, /// has a value in this object
|
||||||
|
global = 2, /// visible to / referenceable by other objects
|
||||||
|
imported = 4, /// undefined here, resolved by the linker
|
||||||
|
zeropage = 8, /// value fits in one byte (0x00..0xFF)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Relocation kinds.
|
||||||
|
enum RelocType : ubyte
|
||||||
|
{
|
||||||
|
abs16 = 0, /// patch a 16-bit little-endian absolute address
|
||||||
|
lo8 = 1, /// patch the low byte of an address
|
||||||
|
hi8 = 2, /// patch the high byte of an address
|
||||||
|
rel8 = 3, /// patch an 8-bit signed PC-relative displacement
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A named chunk of bytes (or bss reservation).
|
||||||
|
struct Segment
|
||||||
|
{
|
||||||
|
string name; /// e.g. ".code"
|
||||||
|
ubyte flags; /// SegFlag bits
|
||||||
|
ushort origin; /// requested origin, 0 = relocatable
|
||||||
|
ubyte[] data; /// contents; empty for bss
|
||||||
|
|
||||||
|
/// True when the segment reserves memory but stores no bytes.
|
||||||
|
bool isBss() const pure nothrow @nogc
|
||||||
|
{
|
||||||
|
return (flags & SegFlag.bss) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A symbol defined in, imported by, or exported from an object.
|
||||||
|
struct Symbol
|
||||||
|
{
|
||||||
|
string name;
|
||||||
|
ushort value;
|
||||||
|
ubyte flags; /// SymFlag bits
|
||||||
|
ushort seg = NoIndex;
|
||||||
|
|
||||||
|
/// True when this object provides the symbol's value.
|
||||||
|
bool isDefined() const pure nothrow @nogc
|
||||||
|
{
|
||||||
|
return (flags & SymFlag.defined) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True when the symbol is visible outside its object.
|
||||||
|
bool isGlobal() const pure nothrow @nogc
|
||||||
|
{
|
||||||
|
return (flags & SymFlag.global) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A request for the linker to patch bytes in a segment.
|
||||||
|
struct Reloc
|
||||||
|
{
|
||||||
|
ushort seg; /// segment index the patch lives in
|
||||||
|
ushort offset; /// byte offset inside the segment
|
||||||
|
ushort sym; /// symbol index
|
||||||
|
ubyte type; /// RelocType
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A parsed `.wo` object file.
|
||||||
|
struct Object
|
||||||
|
{
|
||||||
|
Segment[] segments;
|
||||||
|
Symbol[] symbols;
|
||||||
|
Reloc[] relocs;
|
||||||
|
ushort entry;
|
||||||
|
bool hasEntry;
|
||||||
|
|
||||||
|
/// Find a symbol by name, or NoIndex when absent.
|
||||||
|
int findSymbol(string name) const
|
||||||
|
{
|
||||||
|
foreach (i, s; symbols)
|
||||||
|
if (s.name == name)
|
||||||
|
return cast(int) i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A loadable image produced by the linker.
|
||||||
|
struct Image
|
||||||
|
{
|
||||||
|
ushort loadAddr;
|
||||||
|
ushort entry;
|
||||||
|
ubyte[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// little-endian serialisation helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
private void putU8(ref ubyte[] b, ubyte v)
|
||||||
|
{
|
||||||
|
b ~= v;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putU16(ref ubyte[] b, ushort v)
|
||||||
|
{
|
||||||
|
b ~= cast(ubyte) (v & 0xFF);
|
||||||
|
b ~= cast(ubyte) ((v >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putU32(ref ubyte[] b, uint v)
|
||||||
|
{
|
||||||
|
b ~= cast(ubyte) (v & 0xFF);
|
||||||
|
b ~= cast(ubyte) ((v >> 8) & 0xFF);
|
||||||
|
b ~= cast(ubyte) ((v >> 16) & 0xFF);
|
||||||
|
b ~= cast(ubyte) ((v >> 24) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void putFixed(ref ubyte[] b, string s, size_t n)
|
||||||
|
{
|
||||||
|
foreach (i; 0 .. n)
|
||||||
|
b ~= (i < s.length) ? cast(ubyte) s[i] : cast(ubyte) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract a NUL-terminated fixed-width name from a byte slice.
|
||||||
|
string cstr(const(ubyte)[] buf)
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
while (n < buf.length && buf[n] != 0)
|
||||||
|
n++;
|
||||||
|
return cast(string) buf[0 .. n].dup;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Reader
|
||||||
|
{
|
||||||
|
const(ubyte)[] buf;
|
||||||
|
size_t pos;
|
||||||
|
|
||||||
|
void need(size_t n) const
|
||||||
|
{
|
||||||
|
if (pos + n > buf.length)
|
||||||
|
throw new Exception("unexpected end of file in object/image data");
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte u8()
|
||||||
|
{
|
||||||
|
need(1);
|
||||||
|
return buf[pos++];
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort u16()
|
||||||
|
{
|
||||||
|
need(2);
|
||||||
|
ushort v = cast(ushort) (buf[pos] | (buf[pos + 1] << 8));
|
||||||
|
pos += 2;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint u32()
|
||||||
|
{
|
||||||
|
need(4);
|
||||||
|
uint v = cast(uint) buf[pos]
|
||||||
|
| (cast(uint) buf[pos + 1] << 8)
|
||||||
|
| (cast(uint) buf[pos + 2] << 16)
|
||||||
|
| (cast(uint) buf[pos + 3] << 24);
|
||||||
|
pos += 4;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte[] bytes(size_t n)
|
||||||
|
{
|
||||||
|
need(n);
|
||||||
|
auto v = buf[pos .. pos + n].dup;
|
||||||
|
pos += n;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
string fixed(size_t n)
|
||||||
|
{
|
||||||
|
need(n);
|
||||||
|
auto v = cstr(buf[pos .. pos + n]);
|
||||||
|
pos += n;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// object I/O
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Serialise an object to its `.wo` byte representation.
|
||||||
|
ubyte[] encodeObject(const ref Object obj)
|
||||||
|
{
|
||||||
|
ubyte[] b;
|
||||||
|
putFixed(b, ObjMagic, 4);
|
||||||
|
putU16(b, cast(ushort) obj.segments.length);
|
||||||
|
putU16(b, cast(ushort) obj.symbols.length);
|
||||||
|
putU16(b, cast(ushort) obj.relocs.length);
|
||||||
|
putU16(b, obj.entry);
|
||||||
|
putU16(b, obj.hasEntry ? 1 : 0);
|
||||||
|
putU16(b, 0);
|
||||||
|
|
||||||
|
foreach (seg; obj.segments)
|
||||||
|
{
|
||||||
|
putFixed(b, seg.name, 8);
|
||||||
|
putU8(b, seg.flags);
|
||||||
|
putU8(b, 0);
|
||||||
|
putU16(b, seg.origin);
|
||||||
|
putU32(b, cast(uint) seg.data.length);
|
||||||
|
if (!seg.isBss())
|
||||||
|
b ~= seg.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (sym; obj.symbols)
|
||||||
|
{
|
||||||
|
putFixed(b, sym.name, 32);
|
||||||
|
putU16(b, sym.value);
|
||||||
|
putU8(b, sym.flags);
|
||||||
|
putU8(b, 0);
|
||||||
|
putU16(b, sym.seg);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (r; obj.relocs)
|
||||||
|
{
|
||||||
|
putU16(b, r.seg);
|
||||||
|
putU16(b, r.offset);
|
||||||
|
putU16(b, r.sym);
|
||||||
|
putU8(b, r.type);
|
||||||
|
putU8(b, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode a `.wo` object from bytes.
|
||||||
|
Object decodeObject(const(ubyte)[] bytes)
|
||||||
|
{
|
||||||
|
Reader r = Reader(bytes, 0);
|
||||||
|
string magic = cast(string) r.bytes(4);
|
||||||
|
if (magic != ObjMagic)
|
||||||
|
throw new Exception(format("bad object magic %s (expected %s)", magic, ObjMagic));
|
||||||
|
|
||||||
|
ushort segCount = r.u16();
|
||||||
|
ushort symCount = r.u16();
|
||||||
|
ushort relCount = r.u16();
|
||||||
|
ushort entry = r.u16();
|
||||||
|
ushort flags = r.u16();
|
||||||
|
r.u16(); // reserved
|
||||||
|
|
||||||
|
Object obj;
|
||||||
|
obj.entry = entry;
|
||||||
|
obj.hasEntry = (flags & 1) != 0;
|
||||||
|
|
||||||
|
foreach (_; 0 .. segCount)
|
||||||
|
{
|
||||||
|
Segment seg;
|
||||||
|
seg.name = r.fixed(8);
|
||||||
|
seg.flags = r.u8();
|
||||||
|
r.u8();
|
||||||
|
seg.origin = r.u16();
|
||||||
|
uint size = r.u32();
|
||||||
|
if ((seg.flags & SegFlag.bss) != 0)
|
||||||
|
{
|
||||||
|
seg.data = new ubyte[size];
|
||||||
|
seg.data[] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
seg.data = r.bytes(size);
|
||||||
|
}
|
||||||
|
obj.segments ~= seg;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (_; 0 .. symCount)
|
||||||
|
{
|
||||||
|
Symbol sym;
|
||||||
|
sym.name = r.fixed(32);
|
||||||
|
sym.value = r.u16();
|
||||||
|
sym.flags = r.u8();
|
||||||
|
r.u8();
|
||||||
|
sym.seg = r.u16();
|
||||||
|
obj.symbols ~= sym;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (_; 0 .. relCount)
|
||||||
|
{
|
||||||
|
Reloc rel;
|
||||||
|
rel.seg = r.u16();
|
||||||
|
rel.offset = r.u16();
|
||||||
|
rel.sym = r.u16();
|
||||||
|
rel.type = r.u8();
|
||||||
|
r.u8();
|
||||||
|
obj.relocs ~= rel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write an object to `path`.
|
||||||
|
void writeObject(string path, const ref Object obj)
|
||||||
|
{
|
||||||
|
write(path, encodeObject(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read an object from `path`.
|
||||||
|
Object readObject(string path)
|
||||||
|
{
|
||||||
|
return decodeObject(cast(const(ubyte)[]) read(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// image I/O
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Serialise a loadable image to its `.wimg` byte representation.
|
||||||
|
ubyte[] encodeImage(const ref Image img)
|
||||||
|
{
|
||||||
|
ubyte[] b;
|
||||||
|
putFixed(b, ImgMagic, 4);
|
||||||
|
putU16(b, img.loadAddr);
|
||||||
|
putU16(b, img.entry);
|
||||||
|
putU32(b, cast(uint) img.data.length);
|
||||||
|
b ~= img.data;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode a `.wimg` image from bytes.
|
||||||
|
Image decodeImage(const(ubyte)[] bytes)
|
||||||
|
{
|
||||||
|
Reader r = Reader(bytes, 0);
|
||||||
|
string magic = cast(string) r.bytes(4);
|
||||||
|
if (magic != ImgMagic)
|
||||||
|
throw new Exception(format("bad image magic %s (expected %s)", magic, ImgMagic));
|
||||||
|
|
||||||
|
Image img;
|
||||||
|
img.loadAddr = r.u16();
|
||||||
|
img.entry = r.u16();
|
||||||
|
uint len = r.u32();
|
||||||
|
img.data = r.bytes(len);
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a loadable image to `path`.
|
||||||
|
void writeImage(string path, const ref Image img)
|
||||||
|
{
|
||||||
|
write(path, encodeImage(img));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a loadable image from `path`.
|
||||||
|
Image readImage(string path)
|
||||||
|
{
|
||||||
|
return decodeImage(cast(const(ubyte)[]) read(path));
|
||||||
|
}
|
||||||
Executable
+58
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# End-to-end test: assemble -> link -> emulate, and check program output.
|
||||||
|
# Usage: tests/e2e.sh
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
BIN="build/linux/x86_64/debug"
|
||||||
|
OUT="build/e2e"
|
||||||
|
mkdir -p "$OUT"
|
||||||
|
|
||||||
|
echo "== building =="
|
||||||
|
xmake -y >/dev/null
|
||||||
|
|
||||||
|
WASM="$BIN/wiasm"
|
||||||
|
WLINK="$BIN/wlinker"
|
||||||
|
WCPU="$BIN/weirdcpu"
|
||||||
|
|
||||||
|
for t in "$WASM" "$WLINK" "$WCPU"; do
|
||||||
|
[ -x "$t" ] || { echo "missing binary: $t"; exit 1; }
|
||||||
|
done
|
||||||
|
|
||||||
|
fail=0
|
||||||
|
check() { # name expected actual
|
||||||
|
if [ "$2" = "$3" ]; then
|
||||||
|
echo "PASS $1"
|
||||||
|
else
|
||||||
|
echo "FAIL $1"
|
||||||
|
echo " expected: $(printf '%q' "$2")"
|
||||||
|
echo " actual: $(printf '%q' "$3")"
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "== single object: hello =="
|
||||||
|
"$WASM" -o "$OUT/hello.wo" examples/hello.asm
|
||||||
|
"$WLINK" -o "$OUT/hello.wimg" --map "$OUT/hello.map" "$OUT/hello.wo"
|
||||||
|
check "hello" "Hello, World!" "$("$WCPU" "$OUT/hello.wimg")"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "== cross object: putc + main =="
|
||||||
|
"$WASM" -o "$OUT/putc.wo" examples/putc.asm
|
||||||
|
"$WASM" -o "$OUT/main.wo" examples/main.asm
|
||||||
|
"$WLINK" -o "$OUT/main.wimg" -e start --map "$OUT/main.map" "$OUT/putc.wo" "$OUT/main.wo"
|
||||||
|
check "cross-object" "Hello, 6502!" "$("$WCPU" "$OUT/main.wimg")"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "== arithmetic loop: count =="
|
||||||
|
"$WASM" -o "$OUT/count.wo" examples/count.asm
|
||||||
|
"$WLINK" -o "$OUT/count.wimg" "$OUT/count.wo"
|
||||||
|
check "count" "0123456789" "$("$WCPU" "$OUT/count.wimg")"
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [ "$fail" -ne 0 ]; then
|
||||||
|
echo "e2e: FAILED"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "e2e: OK"
|
||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Build and run the D unit tests for the toolchain modules.
|
||||||
|
# Usage: tests/run.sh
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
DMD="${DMD:-dmd}"
|
||||||
|
OUT="build/wtests"
|
||||||
|
|
||||||
|
mkdir -p build
|
||||||
|
|
||||||
|
"$DMD" -unittest -main -Isrc -of"$OUT" \
|
||||||
|
src/wcore/objfmt.d \
|
||||||
|
src/emulator/cpu.d \
|
||||||
|
src/emulator/bus.d \
|
||||||
|
src/assembler/assembler.d \
|
||||||
|
src/linker/linker.d
|
||||||
|
|
||||||
|
"$OUT"
|
||||||
@@ -8,88 +8,32 @@ else
|
|||||||
set_strip("all")
|
set_strip("all")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Shared formats used by more than one tool (object files, images).
|
||||||
|
local core = "src/wcore/*.d"
|
||||||
|
|
||||||
|
-- 6502 emulator: loads a .wimg image (or raw binary) and runs it.
|
||||||
target("weirdcpu")
|
target("weirdcpu")
|
||||||
do
|
do
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_files("src/*.d")
|
add_includedirs("src")
|
||||||
end
|
add_files("src/emulator/*.d")
|
||||||
|
add_files(core)
|
||||||
target("wlinker")
|
|
||||||
do
|
|
||||||
set_kind("binary")
|
|
||||||
add_files("src/linker/*.d")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Assembler: .asm source -> .wo object.
|
||||||
target("wiasm")
|
target("wiasm")
|
||||||
do
|
do
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
|
add_includedirs("src")
|
||||||
add_files("src/assembler/*.d")
|
add_files("src/assembler/*.d")
|
||||||
|
add_files(core)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Linker: one or more .wo objects -> .wimg image + .map.
|
||||||
|
target("wlinker")
|
||||||
|
do
|
||||||
|
set_kind("binary")
|
||||||
|
add_includedirs("src")
|
||||||
|
add_files("src/linker/*.d")
|
||||||
|
add_files(core)
|
||||||
end
|
end
|
||||||
--
|
|
||||||
-- If you want to known more usage about xmake, please see https://xmake.io
|
|
||||||
--
|
|
||||||
-- ## FAQ
|
|
||||||
--
|
|
||||||
-- You can enter the project directory firstly before building project.
|
|
||||||
--
|
|
||||||
-- $ cd projectdir
|
|
||||||
--
|
|
||||||
-- 1. How to build project?
|
|
||||||
--
|
|
||||||
-- $ xmake
|
|
||||||
--
|
|
||||||
-- 2. How to configure project?
|
|
||||||
--
|
|
||||||
-- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release]
|
|
||||||
--
|
|
||||||
-- 3. Where is the build output directory?
|
|
||||||
--
|
|
||||||
-- The default output directory is `./build` and you can configure the output directory.
|
|
||||||
--
|
|
||||||
-- $ xmake f -o outputdir
|
|
||||||
-- $ xmake
|
|
||||||
--
|
|
||||||
-- 4. How to run and debug target after building project?
|
|
||||||
--
|
|
||||||
-- $ xmake run [targetname]
|
|
||||||
-- $ xmake run -d [targetname]
|
|
||||||
--
|
|
||||||
-- 5. How to install target to the system directory or other output directory?
|
|
||||||
--
|
|
||||||
-- $ xmake install
|
|
||||||
-- $ xmake install -o installdir
|
|
||||||
--
|
|
||||||
-- 6. Add some frequently-used compilation flags in xmake.lua
|
|
||||||
--
|
|
||||||
-- @code
|
|
||||||
-- -- add debug and release modes
|
|
||||||
-- add_rules("mode.debug", "mode.release")
|
|
||||||
--
|
|
||||||
-- -- add macro definition
|
|
||||||
-- add_defines("NDEBUG", "_GNU_SOURCE=1")
|
|
||||||
--
|
|
||||||
-- -- set warning all as error
|
|
||||||
-- set_warnings("all", "error")
|
|
||||||
--
|
|
||||||
-- -- set language: c99, c++11
|
|
||||||
-- set_languages("c99", "c++11")
|
|
||||||
--
|
|
||||||
-- -- set optimization: none, faster, fastest, smallest
|
|
||||||
-- set_optimize("fastest")
|
|
||||||
--
|
|
||||||
-- -- add include search directories
|
|
||||||
-- add_includedirs("/usr/include", "/usr/local/include")
|
|
||||||
--
|
|
||||||
-- -- add link libraries and search directories
|
|
||||||
-- add_links("tbox")
|
|
||||||
-- add_linkdirs("/usr/local/lib", "/usr/lib")
|
|
||||||
--
|
|
||||||
-- -- add system link libraries
|
|
||||||
-- add_syslinks("z", "pthread")
|
|
||||||
--
|
|
||||||
-- -- add compilation and link flags
|
|
||||||
-- add_cxflags("-stdnolib", "-fno-strict-aliasing")
|
|
||||||
-- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true})
|
|
||||||
--
|
|
||||||
-- @endcode
|
|
||||||
--
|
|
||||||
|
|||||||
Reference in New Issue
Block a user