pxt-core
Version:
Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors
1,033 lines (1,031 loc) • 36.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.startAsync = exports.hwAsync = exports.dumplogAsync = exports.dumpheapAsync = exports.dumpMemAsync = void 0;
const path = require("path");
const nodeutil = require("./nodeutil");
const child_process = require("child_process");
const fs = require("fs");
const buildengine = require("./buildengine");
const util_1 = require("util");
var U = pxt.Util;
const maxDMesgSize = 4096;
const openAsync = (0, util_1.promisify)(fs.open);
const closeAsync = (0, util_1.promisify)(fs.close);
const writeAsync = (0, util_1.promisify)(fs.write);
let gdbServer;
let bmpMode = false;
const cpExecAsync = (0, util_1.promisify)(child_process.exec);
function getBMPSerialPortsAsync() {
if (process.env["PXT_IGNORE_BMP"])
return Promise.resolve([]);
if (process.platform == "win32") {
return cpExecAsync("wmic PATH Win32_SerialPort get DeviceID, PNPDeviceID")
.then(({ stdout, stderr }) => {
let res = [];
stdout.split(/\n/).forEach(ln => {
let m = /^(COM\d+)\s+USB\\VID_(\w+)&PID_(\w+)&MI_(\w+)/.exec(ln);
if (m) {
const comp = m[1];
const vid = parseInt(m[2], 16);
const pid = parseInt(m[3], 16);
const mi = parseInt(m[4], 16);
if (vid == 0x1d50 && pid == 0x6018 && mi == 0) {
res.push("\\\\.\\" + comp);
}
}
});
return res;
});
}
else if (process.platform == "darwin") {
return cpExecAsync("system_profiler SPUSBDataType")
.then(({ stdout, stderr }) => {
let res = [];
let inBMP = false;
stdout.split(/\n/).forEach(ln => {
if (ln.indexOf(" Black Magic Probe") >= 0)
inBMP = true;
if (!inBMP)
return;
let m = / Serial Number: (\w+)/.exec(ln);
if (m) {
inBMP = false;
res.push("/dev/cu.usbmodem" + m[1] + "1");
}
});
return res;
});
}
else if (process.platform == "linux") {
// TODO
return Promise.resolve([]);
}
else {
return Promise.resolve([]);
}
}
class SerialIO {
constructor(comName) {
this.comName = comName;
this.id = 0;
this.trace = false;
}
connectAsync() {
return this.disconnectAsync()
.then(() => {
pxt.log("open GDB at " + this.comName);
return openAsync(this.comName, "r+");
})
.then(fd => {
this.fd = fd;
const id = ++this.id;
const buf = Buffer.alloc(128);
const loop = () => fs.read(fd, buf, 0, buf.length, null, (err, nb, buf) => {
if (this.id != id)
return;
if (nb > 0) {
let bb = buf.slice(0, nb);
if (this.trace)
pxt.log("R:" + bb.toString("utf8"));
if (this.onData)
this.onData(bb);
loop();
}
else {
let msg = "GDB read error, nb=" + nb + (err ? err.message : "no err");
if (this.trace)
pxt.log(msg);
else
pxt.debug(msg);
setTimeout(loop, 500);
}
});
loop();
});
}
sendPacketAsync(pkt) {
if (this.trace)
pxt.log("W:" + Buffer.from(pkt).toString("utf8"));
return writeAsync(this.fd, pkt)
.then(() => { });
}
error(msg) {
pxt.log(this.comName + ": " + msg);
}
disconnectAsync() {
if (this.fd == null)
return Promise.resolve();
this.id++;
const f = this.fd;
this.fd = null;
pxt.log("close GDB at " + this.comName);
// try to elicit some response from the server, so that the read loop is tickled
// and stops; without this the close() below hangs
fs.write(f, "$?#78", () => { });
return closeAsync(f)
.then(() => {
});
}
}
function fatal(msg) {
U.userError(msg);
}
function getOpenOcdPath(cmds = "", showLog = false) {
function latest(tool) {
let dir = path.join(pkgDir, "tools/", tool, "/");
if (!fs.existsSync(dir))
fatal(dir + " doesn't exists; " + tool + " not installed in Arduino?");
let subdirs = fs.readdirSync(dir);
if (!subdirs.length)
fatal("no sub-directories in " + dir);
subdirs.sort(pxt.semver.strcmp);
subdirs.reverse();
let thePath = path.join(dir, subdirs[0], "/");
if (!fs.existsSync(thePath + "bin"))
fatal("missing bindir in " + thePath);
return thePath;
}
let dirs = [
process.env["HOME"] + "/Library/Arduino",
process.env["USERPROFILE"] + "/AppData/Local/Arduino",
process.env["USERPROFILE"] + "/AppData/Local/Arduino15",
process.env["HOME"] + "/.arduino",
];
let pkgDir = "";
let openocdPath = "";
let openocdBin = "";
let gccPath = "";
let gdbBin = "";
if (fs.existsSync("/usr/bin/openocd")) {
openocdPath = "/usr/";
gccPath = "/usr/";
}
else if (fs.existsSync("/usr/local/bin/openocd")) {
openocdPath = "/usr/local/";
gccPath = "/usr/local/";
}
else {
for (let ardV = 15; ardV < 50; ++ardV) {
for (let d of dirs) {
pkgDir = path.join(d + ardV, "/packages/arduino/");
if (fs.existsSync(pkgDir))
break;
pkgDir = "";
}
if (pkgDir)
break;
}
if (!pkgDir)
fatal("cannot find Arduino packages directory");
openocdPath = bmpMode ? "" : latest("openocd");
gccPath = latest("arm-none-eabi-gcc");
}
openocdBin = path.join(openocdPath, "bin/openocd");
if (process.platform == "win32")
openocdBin += ".exe";
let script = bmpMode ? "N/A" : pxt.appTarget.compile.openocdScript;
if (!script)
fatal("no openocdScript in pxtarget.json");
if (!cmds)
cmds = `
gdb_port pipe
gdb_memory_map disable
$_TARGETNAME configure -event gdb-attach {
echo "Halting target"
halt
}`;
fs.writeFileSync("built/debug.cfg", `
${showLog ? "" : "log_output built/openocd.log"}
${script}
${cmds}
`);
let args = [openocdBin, "-d2",
"-s", path.join(openocdPath, "share/openocd/scripts/"),
"-f", "built/debug.cfg"];
gdbBin = path.join(gccPath, "bin/arm-none-eabi-gdb");
if (process.platform == "win32")
gdbBin += ".exe";
return { args, gdbBin };
}
function codalBin() {
const cs = pxt.appTarget.compileService;
const be = buildengine.thisBuild;
if (be.outputPath)
return be.buildPath + "/" + be.outputPath;
if (cs.codalBinary)
return be.buildPath + "/build/" + cs.codalBinary;
return be.buildPath + "/build/" + (cs.yottaTarget.split("@")[0]) + "/source/" + cs.yottaBinary.replace(/\.hex$/, "").replace(/-combined$/, "");
}
let cachedMap = "";
let addrCache;
function getMap() {
if (!cachedMap)
cachedMap = fs.readFileSync(codalBin().replace(/\.elf$/, "") + ".map", "utf8");
return cachedMap;
}
function mangle(symbolName) {
let m = /(.*)::(.*)/.exec(symbolName);
if (m)
return "_ZN" + m[1].length + m[1] + "L" + m[2].length + m[2] + "E";
return "_ZL" + symbolName.length + symbolName;
}
function findAddr(symbolName, opt = false) {
if (!addrCache) {
addrCache = {};
let bss = "";
for (let line of getMap().split(/\n/)) {
line = line.trim();
let m = /^\.bss\.(\w+)/.exec(line);
if (m)
bss = m[1];
m = /0x0000000([0-9a-f]+)(\s+([:\w]+)\s*(= .*)?)?/.exec(line);
if (m) {
let addr = parseInt(m[1], 16);
if (m[3])
addrCache[m[3]] = addr;
if (bss) {
addrCache[bss] = addr;
bss = "";
}
}
}
}
let addr = addrCache[symbolName];
if (!addr)
addr = addrCache[mangle(symbolName)];
if (addr) {
return addr;
}
else {
if (!opt)
fatal(`Can't find ${symbolName} symbol in map`);
return null;
}
}
async function initGdbServerAsync() {
let ports = await getBMPSerialPortsAsync();
if (ports.length == 0) {
pxt.log(`Black Magic Probe not detected; falling back to openocd`);
return;
}
bmpMode = true;
pxt.log(`detected Black Magic Probe at ${ports[0]}`);
gdbServer = new pxt.GDBServer(new SerialIO(ports[0]));
// gdbServer.trace = true
await gdbServer.io.connectAsync();
await gdbServer.initAsync();
pxt.log(gdbServer.targetInfo);
nodeutil.addCliFinalizer(() => {
if (!gdbServer)
return Promise.resolve();
let g = gdbServer;
gdbServer = null;
return g.io.disconnectAsync();
});
}
async function flashAsync() {
const r = pxtc.UF2.toBin(fs.readFileSync("built/binary.uf2"));
fs.writeFileSync("built/binary.bin", r.buf);
let toolPaths = getOpenOcdPath(`
program built/binary.bin ${r.start} verify reset exit
`, true);
let oargs = toolPaths.args;
await nodeutil.spawnAsync({
cmd: oargs[0],
args: oargs.slice(1)
});
}
async function resetAsync(bootMode) {
let bi = getBootInfo();
if (gdbServer) {
if (bootMode && bi.addr)
await gdbServer.write32Async(bi.addr, bi.boot);
await gdbServer.sendCmdAsync("R00", null);
}
else {
let cmd = "init\nhalt\n";
if (bootMode && bi.addr) {
cmd += `set M(0) ${bi.boot}\narray2mem M 32 ${bi.addr} 1\n`;
}
cmd += `reset run\nshutdown`;
let toolPaths = getOpenOcdPath(cmd, true);
let oargs = toolPaths.args;
await nodeutil.spawnAsync({
cmd: oargs[0],
args: oargs.slice(1)
});
}
}
async function getMemoryAsync(addr, bytes) {
if (gdbServer) {
return gdbServer.readMemAsync(addr, bytes)
.then((b) => Buffer.from(b));
}
const maxMem = 32 * 1024;
if (bytes > maxMem) {
let bufs = [];
for (let ptr = 0; ptr < bytes; ptr += maxMem) {
bufs.push(await getMemoryAsync(addr + ptr, Math.min(maxMem, bytes - ptr)));
}
return Buffer.concat(bufs);
}
let toolPaths = getOpenOcdPath(`
init
halt
set M(0) 0
mem2array M 32 ${addr} ${(bytes + 3) >> 2}
resume
parray M
shutdown
`);
let oargs = toolPaths.args;
let res = await nodeutil.spawnWithPipeAsync({
cmd: oargs[0],
args: oargs.slice(1),
silent: true
});
let buf = Buffer.alloc(bytes);
for (let line of res.toString("utf8").split(/\r?\n/)) {
let m = /^M\((\d+)\)\s*=\s*(\d+)/.exec(line);
if (m) {
pxt.HF2.write32(buf, parseInt(m[1]) << 2, parseInt(m[2]));
}
}
return buf;
}
function hex(n) {
return "0x" + n.toString(16);
}
const FREE_MASK = 0x80000000;
const ARRAY_MASK = 0x40000000;
const PERMA_MASK = 0x20000000;
const MARKED_MASK = 0x00000001;
const ANY_MARKED_MASK = 0x00000003;
function VAR_BLOCK_WORDS(vt) {
return (((vt) << 12) >> (12 + 2));
}
async function dumpMemAsync(args) {
await initGdbServerAsync();
let memStart = findAddr("_sdata", true) || findAddr("__data_start__");
memStart &= ~0xffff;
let memEnd = findAddr("_estack", true) || findAddr("__StackTop");
if (!isNaN(parseInt(args[0]))) {
memStart = parseInt(args[0]);
memEnd = parseInt(args[1]);
args.shift();
args.shift();
}
console.log(`memory: ${hex(memStart)} - ${hex(memEnd)}`);
let mem = await getMemoryAsync(memStart, memEnd - memStart);
fs.writeFileSync(args[0], mem);
}
exports.dumpMemAsync = dumpMemAsync;
async function dumpheapAsync(filename) {
let memStart = findAddr("_sdata", true) || findAddr("__data_start__");
let memEnd = findAddr("_estack", true) || findAddr("__StackTop");
console.log(`memory: ${hex(memStart)} - ${hex(memEnd)}`);
let mem;
if (filename) {
const buf = fs.readFileSync(filename);
mem = buf.slice(memStart & 0xffff);
}
else {
await initGdbServerAsync();
mem = await getMemoryAsync(memStart, memEnd - memStart);
}
let heapDesc = findAddr("heap");
let m = /\.bss\.heap\s+0x000000[a-f0-9]+\s+0x([a-f0-9]+)/.exec(getMap());
let heapSz = 8;
let heapNum = parseInt(m[1], 16) / heapSz;
let vtablePtrs = {};
getMap().replace(/0x0000000([0-9a-f]+)\s+vtable for (.*)/g, (f, a, cl) => {
let n = parseInt(a, 16);
vtablePtrs[hex(n)] = cl;
vtablePtrs[hex(n + 4)] = cl;
vtablePtrs[hex(n + 8)] = cl;
vtablePtrs[hex(n + 16)] = cl;
vtablePtrs[hex(n + 20)] = cl;
return "";
});
let pointerClassification = {};
let numFibers = 0;
let numListeners = 0;
for (let q of ["runQueue", "sleepQueue", "waitQueue", "fiberPool", "idleFiber"]) {
let addr = findAddr("codal::" + q, true) || findAddr(q);
for (let ptr = read32(addr); ptr; ptr = read32(ptr + 6 * 4)) {
pointerClassification[hex(ptr)] = "Fiber/" + q;
pointerClassification[hex(read32(ptr))] = "Fiber/TCB/" + q;
pointerClassification[hex(read32(ptr + 4))] = "Fiber/Stack/" + q;
pointerClassification[hex(read32(ptr + 8 * 4))] = "Fiber/PXT/" + q;
if (q == "idleFiber")
break;
numFibers++;
}
}
let messageBus = read32(findAddr("codal::EventModel::defaultEventBus", true));
if (messageBus) {
for (let ptr = read32(messageBus + 20); ptr; ptr = read32(ptr + 36)) {
numListeners++;
pointerClassification[hex(ptr)] = "codal::Listener";
}
for (let ptr = read32(messageBus + 24); ptr; ptr = read32(ptr + 16)) {
pointerClassification[hex(ptr)] = "codal::EventQueueItem";
}
const handler = findAddr("pxt::handlerBindings", true);
if (handler)
for (let ptr = read32(handler); ptr; ptr = read32(ptr)) {
pointerClassification[hex(ptr)] = "pxt::HandlerBinding";
}
}
console.log(`heaps at ${hex(heapDesc)}, num=${heapNum}`);
let cnts = {};
let examples = {};
let fiberSize = 0;
for (let i = 0; i < heapNum; ++i) {
let heapStart = read32(heapDesc + i * heapSz);
let heapEnd = read32(heapDesc + i * heapSz + 4);
console.log(`*** heap ${hex(heapStart)} ${heapEnd - heapStart} bytes`);
let block = heapStart;
let totalFreeBlock = 0;
let totalUsedBlock = 0;
while (block < heapEnd) {
let bp = read32(block);
let blockSize = bp & 0x7fffffff;
let isFree = (bp & 0x80000000) != 0;
// console.log(`${hex(block)} -> ${hex(bp)} ${blockSize * 4}`)
let classification = classifyCPP(block, blockSize);
if (U.startsWith(classification, "Fiber/"))
fiberSize += blockSize * 4;
let mark = `[${isFree ? "F" : "U"}:${blockSize * 4} / ${classification}]`;
if (!cnts[mark]) {
cnts[mark] = 0;
examples[mark] = [];
}
cnts[mark] += blockSize * 4;
examples[mark].push(block);
if (isFree)
totalFreeBlock += blockSize;
else
totalUsedBlock += blockSize;
block += blockSize * 4;
}
console.log(`free: ${totalFreeBlock * 4}`);
}
{
let keys = Object.keys(cnts);
keys.sort((a, b) => cnts[b] - cnts[a]);
for (let k of keys) {
U.randomPermute(examples[k]);
console.log(`${cnts[k]}\t${k}\t${examples[k].slice(0, 6).map(p => p.toString(16)).join(", ")}`);
}
}
let uf2 = pxtc.UF2.parseFile(new Uint8Array(fs.readFileSync("built/binary.uf2")));
let currClass = "";
let classMap = {};
let inIface = false;
let classInfo;
for (let line of fs.readFileSync("built/binary.asm", "utf8").split(/\n/)) {
let m = /(\w+)__C\d+_VT:/.exec(line);
if (m)
currClass = m[1];
m = /\w+__C\d+_IfaceVT:/.exec(line);
if (m)
inIface = true;
m = /(\d+)\s+;\s+class-id/.exec(line);
if (currClass && m) {
classInfo = {
name: currClass,
fields: []
};
classMap[m[1]] = classInfo;
currClass = "";
}
if (inIface) {
m = /\.short \d+, (\d+) ; (.*)/.exec(line);
if (m) {
if (m[2] == "the end") {
inIface = false;
}
else if (m[1] != "0") {
classInfo.fields.push(m[2]);
}
}
}
}
let objects = {};
let byCategory = {};
let numByCategory = {};
let maxFree = 0;
const string_inline_ascii_vt = findAddr("pxt::string_inline_ascii_vt");
const string_inline_utf8_vt = findAddr("pxt::string_inline_utf8_vt");
const string_cons_vt = findAddr("pxt::string_cons_vt");
const string_skiplist16_vt = findAddr("pxt::string_skiplist16_vt");
const string_skiplist16_packed_vt = findAddr("pxt::string_skiplist16_packed_vt", true);
const PNG = require("pngjs").PNG;
const visWidth = 256;
const visHeight = 256;
const heapVis = new PNG({ width: visWidth, height: visHeight });
const visData = heapVis.data;
for (let i = 0; i < visWidth * visHeight * 4; ++i)
visData[i] = 0xff;
/*
struct VTable {
uint16_t numbytes;
ValType objectType;
uint8_t magic;
PVoid *ifaceTable;
BuiltInType classNo;
};
*/
for (let gcHeap = read32(findAddr("pxt::firstBlock")); gcHeap; gcHeap = read32(gcHeap)) {
let heapSize = read32(gcHeap + 4);
console.log(`*** GC heap ${hex(gcHeap)} size=${heapSize}`);
let objPtr = gcHeap + 8;
let heapEnd = objPtr + heapSize;
let fields;
while (objPtr < heapEnd) {
let vtable = read32(objPtr);
let numbytes = 0;
let category = "";
let addWords = 0;
fields = {};
let color = 0x000000;
if (vtable & FREE_MASK) {
color = 0x00ff00;
category = "free";
numbytes = VAR_BLOCK_WORDS(vtable) << 2;
maxFree = Math.max(numbytes, maxFree);
}
else if (vtable & ARRAY_MASK) {
numbytes = VAR_BLOCK_WORDS(vtable) << 2;
category = "arraybuffer sz=" + (numbytes >> 2);
if (vtable & PERMA_MASK) {
category = "app_alloc sz=" + numbytes;
let classification = classifyCPP(objPtr, numbytes >> 2);
if (classification != "?")
category = classification;
if (U.startsWith(classification, "Fiber/"))
fiberSize += numbytes;
}
else
category = "arraybuffer sz=" + (numbytes >> 2);
}
else {
vtable &= ~ANY_MARKED_MASK;
let vt0 = read32(vtable);
if ((vt0 >>> 24) != pxt.VTABLE_MAGIC) {
console.log(`Invalid vtable: at ${hex(objPtr)} *${hex(vtable)} = ${hex(vt0)}`);
break;
}
numbytes = vt0 & 0xffff;
let objectType = (vt0 >> 16) & 0xff;
let classNoEtc = read32(vtable + 8);
let classNo = classNoEtc & 0xffff;
let word0 = read32(objPtr + 4);
let tmp = 0;
let len = 0;
switch (classNo) {
case pxt.BuiltInType.BoxedString:
if (vtable == string_inline_ascii_vt) {
category = "ascii_string";
numbytes = 4 + 2 + (word0 & 0xffff) + 1;
}
else if (vtable == string_inline_utf8_vt) {
category = "utf8_string";
numbytes = 4 + 2 + (word0 & 0xffff) + 1;
}
else if (vtable == string_skiplist16_vt) {
category = "skip_string";
numbytes = 4 + 2 + 2 + 4;
fields[".data"] = hex(read32(objPtr + 8) - 4);
}
else if (vtable == string_skiplist16_packed_vt) {
category = "skip_string_packed";
const numskip = (word0 >> 16) >> 4;
numbytes = 2 + 2 + numskip * 2 + (word0 & 0xffff) + 1;
}
else if (vtable == string_cons_vt) {
category = "cons_string";
numbytes = 4 + 4 + 4;
fields["left"] = hex(read32(objPtr + 4));
fields["right"] = hex(read32(objPtr + 8));
}
else {
console.log(`Invalid string vtable: ${hex(vtable)}`);
break;
}
break;
case pxt.BuiltInType.BoxedBuffer:
category = "buffer";
numbytes += word0;
break;
case pxt.BuiltInType.RefAction:
category = "action";
len = word0 & 0xffff;
for (let i = 0; i < len; ++i) {
fields["" + i] = readRef(objPtr + (i + 3) * 4);
}
numbytes += len * 4;
break;
case pxt.BuiltInType.RefImage:
category = "image";
if (word0 & 1) {
numbytes += word0 >> 2;
}
break;
case pxt.BuiltInType.BoxedNumber:
category = "number";
break;
case pxt.BuiltInType.RefCollection:
len = read32(objPtr + 8);
category = "array sz=" + (len >>> 16);
len &= 0xffff;
fields["length"] = len;
fields[".data"] = hex(word0 - 4);
for (let i = 0; i < len; ++i) {
fields["" + i] = readRef(word0 + i * 4);
}
break;
case pxt.BuiltInType.RefRefLocal:
category = "reflocal";
fields["value"] = readRef(objPtr + 4);
break;
case pxt.BuiltInType.RefMap:
len = read32(objPtr + 8);
category = "refmap sz=" + (len >>> 16);
len &= 0xffff;
tmp = read32(objPtr + 12);
fields["length"] = len;
fields[".keys"] = hex(word0 - 4);
fields[".values"] = hex(tmp - 4);
for (let i = 0; i < len; ++i) {
fields["k" + i] = readRef(word0 + i * 4);
fields["v" + i] = readRef(tmp + i * 4);
}
break;
default:
if (classMap[classNo + ""]) {
let cinfo = classMap[classNo + ""];
category = cinfo.name;
len = (numbytes - 4) >> 2;
if (len != cinfo.fields.length)
fields["$error"] = "fieldMismatch";
for (let i = 0; i < len; ++i)
fields[cinfo.fields[i] || ".f" + i] = readRef(objPtr + (i + 1) * 4);
}
else {
category = ("C_" + classNo);
len = (numbytes - 4) >> 2;
for (let i = 0; i < len; ++i)
fields[".f" + i] = readRef(objPtr + (i + 1) * 4);
}
break;
}
}
// console.log(`${hex(objPtr)} vt=${hex(vtable)} ${category} bytes=${numbytes}`)
if (!byCategory[category]) {
byCategory[category] = 0;
numByCategory[category] = 0;
}
let numwords = (numbytes + 3) >> 2;
let obj = {
addr: hex(objPtr),
tag: category,
size: (addWords + numwords) * 4,
fields: fields
};
objects[obj.addr] = obj;
byCategory[category] += (addWords + numwords) * 4;
numByCategory[category]++;
for (let i = 0; i < numwords; ++i) {
const j = (objPtr & 0xffffff) + i * 4;
if (category == "free") {
const mask = Math.min(i / 1, 255) | 0;
color = (mask << 16) | 0x00ff00;
}
visData[j] = (color >> 16) & 0xff;
visData[j + 1] = (color >> 8) & 0xff;
visData[j + 2] = (color >> 0) & 0xff;
visData[j + 3] = 0xff; // alpha
}
objPtr += numwords * 4;
}
}
heapVis.pack()
.pipe(fs.createWriteStream('heap.png'))
.on('finish', function () {
console.log('Written heap.png!');
});
let cats = Object.keys(byCategory);
cats.sort((a, b) => byCategory[b] - byCategory[a]);
let fidx = cats.indexOf("free");
cats.splice(fidx, 1);
cats.push("free");
for (let c of cats) {
console.log(`${byCategory[c]}\t${numByCategory[c]}\t${c}`);
}
console.log(`max. free block: ${maxFree} bytes`);
console.log(`fibers: ${fiberSize} bytes, ${numFibers} fibers; ${numListeners} listeners`);
let dmesg = getDmesg();
let roots = {};
dmesg
.replace(/.*--MARK/, "")
.replace(/^R(.*):0x([\da-f]+)\/(\d+)/img, (f, id, ptr, len) => {
roots[id] = getRoots(parseInt(ptr, 16), parseInt(len), id == "P");
return "";
});
for (let rootId of Object.keys(roots)) {
for (let f of roots[rootId])
mark(rootId, f);
}
let unreachable = [];
for (let o of U.values(objects)) {
if (!o.incoming) {
if (o.tag != "free")
unreachable.push(o.addr);
}
}
fs.writeFileSync("dump.json", JSON.stringify({
unreachable,
roots,
dmesg,
objects
}, null, 1));
// dgml output
let dgml = `<DirectedGraph xmlns="http://schemas.microsoft.com/vs/2009/dgml">\n`;
dgml += `<Nodes>\n`;
for (const addr of Object.keys(objects)) {
const obj = objects[addr];
dgml += `<Node Id="${addr}" Label="${obj.tag}" Size="${obj.size}" />\n`;
}
dgml += `</Nodes>\n`;
dgml += `<Links>\n`;
for (const addr of Object.keys(objects)) {
const obj = objects[addr];
for (const fieldaddr of Object.keys(obj.fields)) {
const field = obj.fields[fieldaddr];
dgml += `<Link Source="${addr}" Target="${field}" Label="${fieldaddr}" />\n`;
}
}
dgml += `</Links>\n`;
dgml += `<Properties>
<Property Id="Size" Label="Size" DataType="System.Int32" />
</Properties>\n`;
dgml += `</DirectedGraph>`;
fs.writeFileSync("dump.dgml", dgml, { encoding: "utf8" });
console.log(`written dump.dgml`);
function mark(src, r) {
if (typeof r == "string" && U.startsWith(r, "0x2")) {
let o = objects[r];
if (o) {
if (!o.incoming) {
o.incoming = [src];
for (let f of U.values(o.fields))
mark(r, f);
}
else {
o.incoming.push(src);
}
}
else {
objects[r] = {
addr: r,
size: -1,
tag: "missing",
incoming: [src],
fields: {}
};
}
}
}
function getRoots(start, len, encoded = false) {
let refs = [];
for (let i = 0; i < len; ++i) {
let addr = start + i * 4;
if (encoded) {
let v = read32(addr);
if (v & 1)
addr = v & ~1;
}
refs.push(readRef(addr));
}
return refs;
}
function readRef(addr) {
let v = read32(addr);
if (!v)
return "undefined";
if (v & 1) {
if (0x8000000 <= v && v <= 0x80f0000)
return hex(v);
return v >> 1;
}
if (v & 2) {
if (v == pxtc.taggedFalse)
return "false";
if (v == pxtc.taggedTrue)
return "true";
if (v == pxtc.taggedNaN)
return "NaN";
if (v == pxtc.taggedNull)
return "null";
return "tagged_" + v;
}
return hex(v);
}
function read32(addr) {
if (addr >= memStart)
return pxt.HF2.read32(mem, addr - memStart);
let r = pxtc.UF2.readBytes(uf2, addr, 4);
if (r && r.length == 4)
return pxt.HF2.read32(r, 0);
U.userError(`can't read memory at ${addr}`);
return 0;
}
function getDmesg() {
let addr = findAddr("codalLogStore");
let start = addr + 4 - memStart;
for (let i = 0; i < maxDMesgSize; ++i) {
if (i == maxDMesgSize - 1 || mem[start + i] == 0)
return mem.slice(start, start + i).toString("utf8");
}
return "";
}
function classifyCPP(block, blockSize) {
let w0 = read32(block + 4);
let w1 = read32(block + 8);
let hx = hex(w0);
let classification = pointerClassification[hex(block + 4)];
if (!classification)
classification = vtablePtrs[hx];
if (!classification) {
if (blockSize == 1312 / 4)
classification = "ST7735WorkBuffer";
else if (blockSize == 1184 / 4)
classification = "ZPWM_buffer";
else if (w0 & 1 && (w0 >> 16) == 2)
classification = "codal::BufferData";
else
classification = "?"; // hx
}
return classification;
}
}
exports.dumpheapAsync = dumpheapAsync;
async function dumplogAsync() {
await initGdbServerAsync();
let addr = findAddr("codalLogStore");
let buf = await getMemoryAsync(addr + 4, maxDMesgSize);
for (let i = 0; i < buf.length; ++i) {
if (buf[i] == 0) {
console.log("\n\n" + buf.slice(0, i).toString("utf8"));
break;
}
}
}
exports.dumplogAsync = dumplogAsync;
async function hwAsync(cmds) {
await initGdbServerAsync();
switch (cmds[0]) {
case "rst":
case "reset":
await resetAsync(false);
break;
case "boot":
await resetAsync(true);
break;
case "log":
case "dmesg":
await dumplogAsync();
break;
}
}
exports.hwAsync = hwAsync;
function getBootInfo() {
let r = {
addr: 0,
boot: 0,
app: 0
};
if (/at91samd/.test(pxt.appTarget.compile.openocdScript)) {
let ramSize = pxt.appTarget.compile.ramSize || 0x8000;
r.addr = 0x20000000 + ramSize - 4;
r.app = 0xf02669ef;
r.boot = 0xf01669ef;
}
if (/nrf52/.test(pxt.appTarget.compile.openocdScript)) {
r.addr = 0x20007F7C;
r.app = 0x4ee5677e;
r.boot = 0x5A1AD5;
}
return r;
}
async function startAsync(gdbArgs) {
let elfFile = codalBin();
if (!fs.existsSync(elfFile))
fatal("compiled file not found: " + elfFile);
let bmpPort = (await getBMPSerialPortsAsync())[0];
let trg = "";
let monReset = "monitor reset";
let monResetHalt = "monitor reset halt";
const pyOCD = !!process.env["PXT_PYOCD"];
if (bmpPort) {
bmpMode = true;
trg = "target extended-remote " + bmpPort;
trg += "\nmonitor swdp_scan\nattach 1";
pxt.log("Using Black Magic Probe at " + bmpPort);
monReset = "run";
monResetHalt = "set {int}0xE000ED0C = 0x05fa0004"; // NVIC_SystemReset()
}
let mapsrc = "";
if (/docker/.test(buildengine.thisBuild.buildPath)) {
mapsrc = "set substitute-path /src " + buildengine.thisBuild.buildPath;
}
if (gdbArgs[0] == "flash") {
await flashAsync();
return;
}
let toolPaths = getOpenOcdPath();
if (!bmpMode) {
if (pyOCD) {
trg = "target extended-remote localhost:3333";
pxt.log("will connect to pyocd at localhost:3333");
}
else {
let oargs = toolPaths.args;
trg = "target remote | " + oargs.map(s => `"${s.replace(/\\/g, "/")}"`).join(" ");
pxt.log("starting openocd: " + oargs.join(" "));
}
}
let binfo = getBootInfo();
let goToApp = binfo.addr ? `set {int}(${binfo.addr}) = ${binfo.app}` : "";
let goToBl = binfo.addr ? `set {int}(${binfo.addr}) = ${binfo.boot}` : "";
// use / not \ for paths on Windows; otherwise gdb has issue starting openocd
fs.writeFileSync("built/openocd.gdb", `
${trg}
${mapsrc}
define rst
set confirm off
${goToApp}
${monResetHalt}
continue
set confirm on
end
define boot
set confirm off
${goToBl}
${monReset}
quit
end
define irq
echo "Current IRQ: "
p (*(int*)0xE000ED04 & 0x1f) - 16
end
define exn
echo PC:
p ((void**)$sp)[5]
echo LR:
p ((void**)$sp)[6]
end
define log
set height 0
set width 0
printf "%s", codalLogStore.buffer
end
define bpanic
b target_panic
end
define bfault
b handleHardFault
end
echo \\npxt commands\\n
echo rst: command to re-run program from start (set your breakpoints first!).\\n
echo boot: to go into bootloader\\n
echo log: to dump DMESG\\n
echo exn: to display exception info.\\n
echo bpanic: to break in target_panic\\n
echo bfault: to break on a hard fault, run 'exn' after\\n
echo \\ngdb (basic) commands\\n
echo s: step, n: step over, fin: step out\\n
echo l: line context\\n
echo bt: for stacktrace\\n\\n
echo More help at https://makecode.com/cli/gdb\\n\\n
`);
let gdbargs = ["--command=built/openocd.gdb", elfFile].concat(gdbArgs);
pxt.log("starting gdb with: " + toolPaths.gdbBin + " " + gdbargs.join(" "));
let proc = child_process.spawn(toolPaths.gdbBin, gdbargs, {
stdio: "inherit",
//detached: true,
});
process.on('SIGINT', function () {
// this doesn't actully kill it, it usually just stops the target program
proc.kill('SIGINT');
});
return new Promise((resolve, reject) => {
proc.on("error", (err) => { reject(err); });
proc.on("close", () => {
resolve();
});
});
}
exports.startAsync = startAsync;
;