UNPKG

pxt-core

Version:

Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors

1,033 lines (1,031 loc) • 36.3 kB
"use strict"; 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;