in cli/gdb.ts [436:951]
export async function dumpheapAsync(filename?: string) {
let memStart = findAddr("_sdata", true) || findAddr("__data_start__")
let memEnd = findAddr("_estack", true) || findAddr("__StackTop")
console.log(`memory: ${hex(memStart)} - ${hex(memEnd)}`)
let mem: Buffer
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: pxt.Map<string> = {}
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: pxt.Map<string> = {}
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: pxt.Map<number> = {}
let examples: pxt.Map<number[]> = {}
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: pxt.Map<ClassInfo> = {}
let inIface = false
let classInfo: 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: pxt.Map<HeapObj> = {}
let byCategory: pxt.Map<number> = {}
let numByCategory: pxt.Map<number> = {}
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: any = require("pngjs").PNG;
const visWidth = 256
const visHeight = 256
const heapVis = new PNG({ width: visWidth, height: visHeight })
const visData: Uint8Array = 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: pxt.Map<HeapRef>
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: HeapObj = {
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: pxt.Map<HeapRef[]> = {}
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: string[] = []
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: HeapRef, r: HeapRef) {
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: number, len: number, encoded = false) {
let refs: HeapRef[] = []
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: number): HeapRef {
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: number) {
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: number, blockSize: number) {
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
}
}