mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2026-10-25 00:02:16 +00:00
cc65: parse symbolsizes from --dbgfile, memory map tweaks
This commit is contained in:
1 parent
83f15d8627
commit
8161ae1fa3
10 files changed
+203
-13
No files matched your search
@@ -618,6 +618,7 @@ export class ControllerPoller {
|
||||
poll() {
|
||||
if (!this.active) return;
|
||||
var gamepads = navigator.getGamepads && navigator.getGamepads();
|
||||
if (!gamepads) return;
|
||||
for (var gpi = 0; gpi < gamepads.length; gpi++) {
|
||||
let state = this.state[gpi];
|
||||
let lastState = this.lastState[gpi];
|
||||
|
||||
+3
-3
@@ -9,13 +9,13 @@ which most linker-based toolchains provide.
|
||||
|
||||
## Columns
|
||||
|
||||
- **Memory** shows the platform's native memory map (RAM, ROM, I/O).
|
||||
- **System** shows the platform's native memory map (RAM, ROM, I/O).
|
||||
Gaps between areas appear as dashed "unmapped" bars, so the column
|
||||
covers the whole address space.
|
||||
- **Segments** shows the segments the linker produced. Free space
|
||||
between segments appears as an empty bar.
|
||||
- **Variables** shows large variables and arrays (16 bytes or more) in
|
||||
RAM. Sizes come from the compiler when it reports them (oscar64);
|
||||
- **Objects** shows large variables and arrays (16 bytes or more) in
|
||||
RAM. Sizes come from the toolchain when it reports them (cc65, oscar64);
|
||||
otherwise they are estimated from the distance to the next symbol.
|
||||
|
||||
A column is hidden when it has nothing to show. Overlapping areas (for
|
||||
|
||||
@@ -94,7 +94,7 @@ function isHiddenSymbol(name: string) {
|
||||
// since its extent is unknown.
|
||||
export function findLargeVariables(symbolmap: { [sym: string]: number }, segments: Segment[], opts?: MemMapOptions,
|
||||
symbolsizes?: { [sym: string]: number }): MemMapBlock[] {
|
||||
const minSize = opts?.minVarSize ?? 16;
|
||||
const minSize = opts?.minVarSize ?? 128;
|
||||
const maxVars = opts?.maxVars ?? 64;
|
||||
const ramsegs = segments.filter(s => s.type == 'ram' && s.size > 0);
|
||||
if (!symbolmap || !ramsegs.length) return [];
|
||||
@@ -150,7 +150,7 @@ export function computeMemoryMapLayout(segments: Segment[], symbolmap?: { [sym:
|
||||
}
|
||||
const vars = findLargeVariables(symbolmap, segments, opts, symbolsizes);
|
||||
if (vars.length) {
|
||||
columns.push(makeColumn('vars', 'Variables', vars, maxLanes));
|
||||
columns.push(makeColumn('vars', 'Objects', vars, maxLanes));
|
||||
}
|
||||
const bset = new Set<number>();
|
||||
for (const col of columns)
|
||||
|
||||
@@ -207,6 +207,8 @@ export var PLATFORM_PARAMS = {
|
||||
extra_link_files: ['crt0.o', 'neslib2.lib', 'neslib2.cfg', 'nesbanked.cfg'],
|
||||
// source link symbol -> linker config (was hardcoded in fixParamsWithDefines)
|
||||
symbolConfigs: { NES_MAPPER: { '4': 'nesbanked.cfg' } },
|
||||
// iNES header and CHR data are not in CPU address space
|
||||
ignore_segments: ['HEADER', 'CHARS'],
|
||||
wiz_rom_ext: '.nes',
|
||||
},
|
||||
'apple2': {
|
||||
@@ -231,6 +233,8 @@ export var PLATFORM_PARAMS = {
|
||||
cfgfile: 'atari.cfg',
|
||||
libargs: ['atari.lib'],
|
||||
fastbasic_cfgfile: 'fastbasic-cart.cfg',
|
||||
// XEX file headers and trailer are not in CPU address space
|
||||
ignore_segments: ['EXEHDR', 'SYSCHKHDR', 'SYSCHKTRL', 'MAINHDR', 'AUTOSTRT'],
|
||||
},
|
||||
'atari8-800xl': {
|
||||
arch: '6502',
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getRootBasePlatform } from "../../common/util";
|
||||
import { CodeListingMap, WorkerError } from "../../common/workertypes";
|
||||
import { BuildStep, BuildStepResult, gatherFiles, staleFiles, populateFiles, fixParamsWithDefines, applyAsmProjectParams, putWorkFile, populateExtraFiles, store, populateEntry, anyTargetChanged, processEmbedDirective } from "../builder";
|
||||
import { re_crlf, makeErrorMatcher } from "../listingutils";
|
||||
import { parseCC65DbgSizes } from "./cc65dbg";
|
||||
import { loadNative, moduleInstFn, print_fn, setupFS, execMain, execToFile, emglobal, EmscriptenModule } from "../wasmutils";
|
||||
|
||||
|
||||
@@ -190,7 +191,7 @@ export function linkLD65(step: BuildStep): BuildStepResult {
|
||||
'--lib-path', '/share/lib',
|
||||
'-C', cfgfile,
|
||||
'-Ln', 'main.vice',
|
||||
//'--dbgfile', 'main.dbg', // TODO: get proper line numbers
|
||||
'--dbgfile', 'main.dbg',
|
||||
'-o', 'main',
|
||||
'-m', 'main.map'].concat(step.args, libargs);
|
||||
// //#symbol ld (symbols not already merged into libargs) and //#flag ld
|
||||
@@ -210,7 +211,6 @@ export function linkLD65(step: BuildStep): BuildStepResult {
|
||||
newrom.set(aout.slice(0, aout.length - 0x2000), 0x2000);
|
||||
aout = newrom;
|
||||
}
|
||||
//var dbgout = FS.readFile("main.dbg", {encoding:'utf8'});
|
||||
putWorkFile("main", aout);
|
||||
putWorkFile("main.map", mapout);
|
||||
putWorkFile("main.vice", viceout);
|
||||
@@ -229,6 +229,16 @@ export function linkLD65(step: BuildStep): BuildStepResult {
|
||||
}
|
||||
}
|
||||
}
|
||||
// symbol sizes from the linker debug file
|
||||
var symbolsizes = {};
|
||||
try {
|
||||
let dbgsyms = parseCC65DbgSizes(FS.readFile("main.dbg", { encoding: 'utf8' }), params.ignore_segments);
|
||||
symbolsizes = dbgsyms.sizes;
|
||||
// labels outside CPU address space (e.g. NES CHR) would alias real addresses
|
||||
for (let name of dbgsyms.ignored) delete symbolmap[name];
|
||||
} catch (e) {
|
||||
console.log("could not parse main.dbg", e);
|
||||
}
|
||||
var segments = [];
|
||||
// TODO: CHR, banks, etc
|
||||
let re_seglist = /(\w+)\s+([0-9A-F]+)\s+([0-9A-F]+)\s+([0-9A-F]+)\s+([0-9A-F]+)/;
|
||||
@@ -237,6 +247,7 @@ export function linkLD65(step: BuildStep): BuildStepResult {
|
||||
for (let s of mapout.split('\n')) {
|
||||
if (parseseglist && (m = re_seglist.exec(s))) {
|
||||
let seg = m[1];
|
||||
if (params.ignore_segments?.includes(seg)) continue;
|
||||
let start = parseInt(m[2], 16);
|
||||
let size = parseInt(m[4], 16);
|
||||
let type = '';
|
||||
@@ -280,6 +291,7 @@ export function linkLD65(step: BuildStep): BuildStepResult {
|
||||
listings: listings,
|
||||
errors: errors,
|
||||
symbolmap: symbolmap,
|
||||
symbolsizes: symbolsizes,
|
||||
segments: segments
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// Parser for the ld65 debug info file (--dbgfile).
|
||||
// Each record is "<type>\t<key>=<value>,..." e.g.
|
||||
// seg id=2,name="BSS",start=0x00032A,size=0x0136,...
|
||||
// span id=2146,seg=2,start=32,size=48
|
||||
// line id=3477,file=0,line=2273,span=2146
|
||||
// sym id=313,name="_attackers",...,val=0x34A,seg=2,type=lab
|
||||
|
||||
type DbgRecord = { [key: string]: string };
|
||||
|
||||
function parseRecord(text: string): DbgRecord {
|
||||
const rec: DbgRecord = {};
|
||||
const re = /(\w+)=("[^"]*"|[^,]*)/g;
|
||||
let m;
|
||||
while (m = re.exec(text)) {
|
||||
rec[m[1]] = m[2].startsWith('"') ? m[2].slice(1, -1) : m[2];
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
function num(s: string): number {
|
||||
return s == null ? NaN : Number(s); // handles 0x prefix
|
||||
}
|
||||
|
||||
export interface CC65DbgSymbols {
|
||||
sizes: { [sym: string]: number };
|
||||
ignored: string[]; // labels defined in ignored segments
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the size in bytes of each label in the debug file.
|
||||
* Uses the label's own size when ca65 records one (e.g. .proc scopes);
|
||||
* otherwise sums the consecutive line spans that follow the label, up to
|
||||
* the next label or a gap. Names defined more than once are omitted.
|
||||
* Labels in ignoreSegments (e.g. NES CHR data) are listed in 'ignored'.
|
||||
*/
|
||||
export function parseCC65DbgSizes(dbg: string, ignoreSegments?: string[]): CC65DbgSymbols {
|
||||
const segstart = new Map<string, number>();
|
||||
const ignoredsegs = new Set<string>();
|
||||
const ignored: string[] = [];
|
||||
const spans = new Map<string, { seg: string, start: number, size: number }>();
|
||||
const linespans = new Set<string>();
|
||||
const syms: DbgRecord[] = [];
|
||||
for (const line of dbg.split('\n')) {
|
||||
const tab = line.indexOf('\t');
|
||||
if (tab < 0) continue;
|
||||
const type = line.substring(0, tab);
|
||||
if (type != 'seg' && type != 'span' && type != 'line' && type != 'sym') continue;
|
||||
const rec = parseRecord(line.substring(tab + 1));
|
||||
switch (type) {
|
||||
case 'seg':
|
||||
segstart.set(rec.id, num(rec.start));
|
||||
if (ignoreSegments?.includes(rec.name)) ignoredsegs.add(rec.id);
|
||||
break;
|
||||
case 'span': spans.set(rec.id, { seg: rec.seg, start: num(rec.start), size: num(rec.size) }); break;
|
||||
case 'line': if (rec.span) rec.span.split('+').forEach(id => linespans.add(id)); break;
|
||||
case 'sym':
|
||||
if (rec.type != 'lab' || rec.seg == null || rec.val == null) break;
|
||||
if (ignoredsegs.has(rec.seg)) ignored.push(rec.name);
|
||||
else syms.push(rec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// span size by segment offset, from line spans only (scope spans cover whole ranges)
|
||||
const spanat = new Map<string, Map<number, number>>();
|
||||
for (const [id, span] of spans) {
|
||||
if (!linespans.has(id) || !(span.size > 0)) continue;
|
||||
let segmap = spanat.get(span.seg);
|
||||
if (!segmap) spanat.set(span.seg, segmap = new Map());
|
||||
segmap.set(span.start, Math.max(span.size, segmap.get(span.start) || 0));
|
||||
}
|
||||
// label offsets by segment, to stop at the next label
|
||||
const labelat = new Map<string, Set<number>>();
|
||||
for (const sym of syms) {
|
||||
const ofs = num(sym.val) - segstart.get(sym.seg);
|
||||
let set = labelat.get(sym.seg);
|
||||
if (!set) labelat.set(sym.seg, set = new Set());
|
||||
set.add(ofs);
|
||||
}
|
||||
const sizes: { [sym: string]: number } = {};
|
||||
const dups = new Set<string>();
|
||||
for (const sym of syms) {
|
||||
if (sym.name in sizes) { dups.add(sym.name); continue; }
|
||||
let size = num(sym.size);
|
||||
if (isNaN(size)) {
|
||||
const segmap = spanat.get(sym.seg);
|
||||
const labels = labelat.get(sym.seg);
|
||||
const ofs = num(sym.val) - segstart.get(sym.seg);
|
||||
let cur = ofs;
|
||||
let n: number;
|
||||
while (segmap && (n = segmap.get(cur)) > 0) {
|
||||
cur += n;
|
||||
if (labels.has(cur)) break;
|
||||
}
|
||||
size = cur - ofs;
|
||||
}
|
||||
sizes[sym.name] = size;
|
||||
}
|
||||
for (const name of dups) delete sizes[name];
|
||||
// unknown sizes (no spans) are left out
|
||||
for (const name in sizes) if (!(sizes[name] > 0)) delete sizes[name];
|
||||
return { sizes, ignored };
|
||||
}
|
||||
@@ -415,6 +415,7 @@ describe('Platform Replay', () => {
|
||||
platform.nextFrame();
|
||||
booted = platform.readAddress(0x0600) == 0x42;
|
||||
}
|
||||
platform.pause();
|
||||
assert.ok(booted, "ATR boot code did not run");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import assert from "assert";
|
||||
import fs from "fs";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import { before, describe, it } from "mocha";
|
||||
import { after, before, describe, it } from "mocha";
|
||||
import { store, fixParamsWithDefines, applyAsmProjectParams, parseBuildDirectives, applyBuildDirectives } from "../../src/worker/builder";
|
||||
import { defineArgs, linkSymbolArgs, extraArgsFor, getPlatformToolConfig, TOOL_META } from "../../src/common/toolmeta";
|
||||
import { compileSourceFile, preload } from "../../src/tools/testlib";
|
||||
@@ -372,8 +373,9 @@ describe("applyAsmProjectParams (backwards compatibility)", function () {
|
||||
|
||||
describe("build directive wiring (integration)", function () {
|
||||
this.timeout(120000);
|
||||
const tmpdir = "/tmp/pi-agent/buildparams";
|
||||
before(function () { fs.mkdirSync(tmpdir, { recursive: true }); });
|
||||
let tmpdir: string;
|
||||
before(function () { tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "buildparams-")); });
|
||||
after(function () { fs.rmSync(tmpdir, { recursive: true, force: true }); });
|
||||
|
||||
function writeSource(name: string, code: string): string {
|
||||
const p = path.join(tmpdir, name);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import assert from "assert";
|
||||
import { describe, it } from "mocha";
|
||||
import { parseCC65DbgSizes } from "../../src/worker/tools/cc65dbg";
|
||||
|
||||
const DBG = [
|
||||
'version\tmajor=2,minor=0',
|
||||
'seg\tid=0,name="CODE",start=0x008500,size=0x0100,addrsize=absolute,type=ro',
|
||||
'seg\tid=2,name="BSS",start=0x00032A,size=0x0136,addrsize=absolute,type=rw',
|
||||
'seg\tid=3,name="DATA",start=0x000300,size=0x0010,addrsize=absolute,type=rw',
|
||||
'seg\tid=8,name="CHARS",start=0x000000,size=0x2000,addrsize=absolute,type=rw',
|
||||
'span\tid=20,seg=8,start=0,size=4096',
|
||||
'span\tid=0,seg=2,start=0,size=32',
|
||||
'span\tid=1,seg=2,start=32,size=48',
|
||||
'span\tid=2,seg=2,start=80,size=1',
|
||||
'span\tid=3,seg=3,start=0,size=4',
|
||||
'span\tid=4,seg=3,start=4,size=4',
|
||||
'span\tid=5,seg=3,start=8,size=2',
|
||||
'span\tid=9,seg=2,start=0,size=300', // scope span: ignored
|
||||
'line\tid=0,file=0,line=10,span=0',
|
||||
'line\tid=1,file=0,line=12,span=1',
|
||||
'line\tid=2,file=0,line=14,span=2',
|
||||
'line\tid=3,file=0,line=20,span=3+4',
|
||||
'line\tid=4,file=0,line=21,span=5',
|
||||
'line\tid=20,file=1,line=1,span=20',
|
||||
'scope\tid=0,name="",mod=0,size=300,span=9',
|
||||
'sym\tid=0,name="_formation",addrsize=absolute,scope=0,def=0,val=0x32A,seg=2,type=lab',
|
||||
'sym\tid=1,name="_attackers",addrsize=absolute,scope=0,def=1,val=0x34A,seg=2,type=lab',
|
||||
'sym\tid=2,name="_last",addrsize=absolute,scope=0,def=2,val=0x37A,seg=2,type=lab',
|
||||
'sym\tid=3,name="_table",addrsize=absolute,scope=0,def=3,val=0x300,seg=3,type=lab',
|
||||
'sym\tid=4,name="_next",addrsize=absolute,scope=0,def=4,val=0x308,seg=3,type=lab',
|
||||
'sym\tid=5,name="_main",addrsize=absolute,size=41,scope=0,def=5,val=0x8500,seg=0,type=lab',
|
||||
'sym\tid=6,name="L0002",addrsize=absolute,size=2,scope=1,def=6,val=0x8510,seg=0,type=lab',
|
||||
'sym\tid=7,name="L0002",addrsize=absolute,size=3,scope=2,def=7,val=0x8520,seg=0,type=lab',
|
||||
'sym\tid=8,name="_nobytes",addrsize=absolute,scope=0,def=8,val=0x8600,seg=0,type=lab',
|
||||
'sym\tid=10,name="_chr_tiles",addrsize=absolute,scope=0,def=20,val=0x0,seg=8,type=lab',
|
||||
'sym\tid=9,name="FOO",addrsize=zeropage,scope=0,def=9,val=0x10,type=equ',
|
||||
].join('\n');
|
||||
|
||||
describe('cc65 debug file', function () {
|
||||
const { sizes, ignored } = parseCC65DbgSizes(DBG, ['HEADER', 'CHARS']);
|
||||
|
||||
it('sizes labels from the line spans that follow them', function () {
|
||||
assert.equal(sizes._formation, 32);
|
||||
assert.equal(sizes._attackers, 48);
|
||||
assert.equal(sizes._last, 1);
|
||||
});
|
||||
|
||||
it('sums multiple spans up to the next label', function () {
|
||||
assert.equal(sizes._table, 8);
|
||||
assert.equal(sizes._next, 2);
|
||||
});
|
||||
|
||||
it('uses sizes recorded by ca65', function () {
|
||||
assert.equal(sizes._main, 41);
|
||||
});
|
||||
|
||||
it('omits duplicates, equates and labels without spans', function () {
|
||||
assert.ok(!('L0002' in sizes));
|
||||
assert.ok(!('FOO' in sizes));
|
||||
assert.ok(!('_nobytes' in sizes));
|
||||
});
|
||||
|
||||
it('reports labels in ignored segments instead of sizing them', function () {
|
||||
assert.deepEqual(ignored, ['_chr_tiles']);
|
||||
assert.ok(!('_chr_tiles' in sizes));
|
||||
assert.equal(parseCC65DbgSizes(DBG).sizes._chr_tiles, 4096);
|
||||
});
|
||||
});
|
||||
@@ -70,7 +70,7 @@ describe('Memory map layout', function () {
|
||||
_small: 0x300, _buffer: 0x302, _last: 0x380,
|
||||
__BSS_RUN__: 0x300, _code: 0x8000, _zp: 0x10,
|
||||
};
|
||||
const vars = findLargeVariables(symbols, native.concat(linker));
|
||||
const vars = findLargeVariables(symbols, native.concat(linker), {minVarSize:16});
|
||||
assert.deepEqual(vars.map(v => [v.name, v.start, v.end]), [
|
||||
['_last', 0x380, 0x400],
|
||||
['_buffer', 0x302, 0x380],
|
||||
@@ -79,14 +79,14 @@ describe('Memory map layout', function () {
|
||||
});
|
||||
|
||||
it('skips the last symbol of a native-only RAM segment', function () {
|
||||
const vars = findLargeVariables({ _a: 0x100, _b: 0x200 }, native);
|
||||
const vars = findLargeVariables({ _a: 0x100, _b: 0x200 }, native, {minVarSize:16});
|
||||
assert.deepEqual(vars.map(v => v.name), ['_a']);
|
||||
});
|
||||
|
||||
it('uses reported symbol sizes over gap estimates', function () {
|
||||
const symbols = { _a: 0x300, _b: 0x340, ZeroStart: 0x380, _c: 0x380 };
|
||||
const sizes = { _a: 20, _b: 8, ZeroStart: 0, _c: 0x40 };
|
||||
const vars = findLargeVariables(symbols, native.concat(linker), {}, sizes);
|
||||
const vars = findLargeVariables(symbols, native.concat(linker), {minVarSize:16}, sizes);
|
||||
assert.deepEqual(vars.map(v => [v.name, v.start, v.end, !!v.approx]), [
|
||||
['_c', 0x380, 0x3c0, false],
|
||||
['_a', 0x300, 0x314, false],
|
||||
|
||||
Reference in new issue
Block a user