Last active
January 11, 2023 13:41
-
-
Save pcornier/1570cc742e8059cefa6887fc74b5353c to your computer and use it in GitHub Desktop.
debug simple JS file with GTKWave
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"dependencies": { | |
"escodegen": "^2.0.0", | |
"esprima": "^4.0.1", | |
"estraverse": "^5.3.0", | |
"minimist": "^1.2.7" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
./vcd.js -s 500 test.js |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
const fs = require('fs'); | |
const path = require('path'); | |
const inspector = require('inspector'); | |
const esprima = require('esprima'); | |
const estraverse = require('estraverse'); | |
const escodegen = require('escodegen'); | |
const argv = require('minimist')(process.argv.slice(2)); | |
const session = new inspector.Session(); | |
// command line arguments & usage | |
if (!argv.s) usage(); | |
if (argv._.length != 1) usage(); | |
function usage() { | |
console.log('Usage: vcd.js -s <steps> <script.js>'); | |
return process.exit(1); | |
} | |
let cycles = 0; | |
let maxCycles = argv.s || 100; | |
let symbols = {}; | |
let vcd = []; | |
let symbolCount = 0; | |
// connect to the session and start Runtime | |
session.connect(); | |
session.post('Runtime.enable'); | |
// read script file | |
let scriptName = argv._[0]; | |
let scriptPath = path.resolve(__dirname, scriptName); | |
let script = fs.readFileSync(scriptPath, 'utf8'); | |
// parse the script and replace 'var' with 'let' | |
// this part could have an impact on script execution but it seems the only | |
// way(?) to expose 'var' declarations. | |
const ast = esprima.parse(script); | |
estraverse.replace(ast, { | |
enter: function(node) { | |
if (node.type === 'VariableDeclaration' && node.kind === 'var') { | |
node.kind = 'let'; | |
} | |
} | |
}); | |
script = escodegen.generate(ast); | |
// write the VCD file at the end of the processing | |
let writeVCD = function() { | |
// VCD header | |
fs.writeFileSync(`${scriptPath}.vcd`, '$timescale 1ns $end\n', 'utf8'); | |
// Write declarations | |
Object.keys(symbols).forEach(name => { | |
let size = 64; | |
switch (symbols[name].type) { | |
case 'number': size = 64; break; | |
case 'string': size = 128*8; break; | |
case 'boolean': size = 1; break; | |
} | |
let type = symbols[name].type == 'string' ? 'reg' : 'wire'; | |
fs.appendFileSync(`${scriptPath}.vcd`, `$var ${type} ${size} ${symbols[name].s} ${name} $end\n`, 'utf8'); | |
}); | |
fs.appendFileSync(`${scriptPath}.vcd`, '$enddefinitions $end\n', 'utf8'); | |
// vardump | |
vcd.forEach((frame, t) => { | |
fs.appendFileSync(`${scriptPath}.vcd`, `#${t}\n`, 'utf8'); | |
frame.forEach(v => { | |
let value = 0; | |
if (symbols[v[0]].type == 'number') { | |
value = v[1].toString(2); | |
} | |
else if (symbols[v[0]].type == 'string') { | |
if (typeof v[1] === symbols[v[0]].type) { | |
value = v[1] | |
.split('') | |
.reverse() | |
.map((c, i) => c.charCodeAt(0) << (i*8)) | |
.reduce((a, c) => a | c, 0) | |
.toString(2); | |
} | |
} | |
else if (symbols[v[0]].type == 'boolean') { | |
value = v[1] ? 1 : 0; | |
} | |
fs.appendFileSync(`${scriptPath}.vcd`, `b${value} ${symbols[v[0]].s}\n`, 'utf8'); | |
}) | |
}); | |
console.log(`Generated VCD file: ${scriptName}.vcd`); | |
} | |
// Add a symbol to the lib and assign a letter to it, for now it is limited to 256-33 symbols max | |
let addSymbol = function(name, type) { | |
let symbol = String.fromCharCode(33+symbolCount); | |
symbols[name] = { s: symbol, v: -194839894982, type: type }; | |
symbolCount++; | |
console.log(`\nFound symbol "${name}" of type ${type}`); | |
} | |
// compile script | |
session.post('Runtime.compileScript', { | |
expression: `{${script}}`, | |
sourceURL: scriptPath, | |
persistScript: true | |
}, (err, res) => { | |
// script compiled, start debugger | |
session.post('Debugger.enable'); | |
// add a breakpoint at line 0 | |
session.post('Debugger.setBreakpoint', { location: { scriptId: res.scriptId, lineNumber: 0 } }); | |
// breakpoint event | |
session.on('Debugger.paused', params => { | |
// paused | |
process.stdout.write(`.`); | |
// get callframe and scope id | |
const callFrame = params.params.callFrames[0]; | |
const scopeId = callFrame.scopeChain[0].object.objectId; | |
// frame will store values for this execution cycle | |
let frame = []; | |
session.post('Runtime.getProperties', { | |
objectId: scopeId, | |
ownProperties: true, | |
accessorPropertiesOnly: false, | |
generatePreview: true | |
}, (err, res) => { | |
// loop each variable in the scope | |
res.result.forEach(obj => { | |
// what is the correct condition? | |
// add the symbol if it's a new one and value is defined | |
if (obj.value?.value && obj.value.value != undefined && obj.writable && !Object.keys(symbols).includes(obj.name)) { | |
addSymbol(obj.name, obj.value.type); | |
} | |
// save the value if it's in the list of 'watched' symbols | |
if (Object.keys(symbols).includes(obj.name)) { | |
let value = obj.value.value || 0; | |
// save only if value is different than the previous one | |
if (symbols[obj.name].v != value) { | |
frame.push([obj.name, value]); | |
symbols[obj.name].v = value; | |
} | |
} | |
}); | |
// push frame to the list of frames | |
vcd.push(frame); | |
}); | |
cycles++; | |
if (cycles < maxCycles) { | |
session.post('Debugger.stepOver'); | |
} | |
else { | |
console.log(`\nDone after ${cycles} cycles`); | |
writeVCD(); | |
} | |
}); | |
session.post('Runtime.runScript', { scriptId: res.scriptId }); | |
}); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment