282 lines
10 KiB
Nim
282 lines
10 KiB
Nim
# Copyright 2022 Mattia Giambirtone & All Contributors
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import ../opcodes
|
|
import multibyte
|
|
|
|
|
|
import std/strformat
|
|
import std/strutils
|
|
import std/terminal
|
|
|
|
|
|
type
|
|
Function = object
|
|
start, stop, argc: int
|
|
name: string
|
|
Module = object
|
|
start, stop: int
|
|
name: string
|
|
Debugger* = ref object
|
|
chunk: Chunk
|
|
modules: seq[Module]
|
|
functions: seq[Function]
|
|
current: int
|
|
|
|
|
|
proc newDebugger*: Debugger =
|
|
## Initializes a new, empty
|
|
## debugger object
|
|
new(result)
|
|
result.functions = @[]
|
|
|
|
|
|
proc nl = stdout.write("\n")
|
|
|
|
|
|
proc printDebug(s: string, newline: bool = false) =
|
|
stdout.styledWrite(fgMagenta, "DEBUG - Disassembler -> ")
|
|
stdout.styledWrite(fgGreen, s)
|
|
if newline:
|
|
nl()
|
|
|
|
|
|
proc printName(opcode: OpCode, newline: bool = false) =
|
|
stdout.styledWrite(fgRed, $opcode, " (", fgYellow, $uint8(opcode), fgRed, ")")
|
|
if newline:
|
|
nl()
|
|
|
|
|
|
proc printInstruction(instruction: OpCode, newline: bool = false) =
|
|
printDebug("Instruction: ")
|
|
printName(instruction)
|
|
if newline:
|
|
nl()
|
|
|
|
|
|
proc checkFunctionStart(self: Debugger, n: int) =
|
|
## Checks if a function begins at the given
|
|
## bytecode offset
|
|
for i, e in self.functions:
|
|
# Avoids duplicate output
|
|
if n == e.start:
|
|
styledEcho fgBlue, "\n==== Peon Bytecode Disassembler - Function Start ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
|
|
styledEcho fgGreen, "\t- Start offset: ", fgYellow, $e.start
|
|
styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop
|
|
styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc, "\n"
|
|
|
|
|
|
proc checkFunctionEnd(self: Debugger, n: int) =
|
|
## Checks if a function ends at the given
|
|
## bytecode offset
|
|
for i, e in self.functions:
|
|
if n == e.stop:
|
|
styledEcho fgBlue, "\n==== Peon Bytecode Disassembler - Function End ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
|
|
|
|
|
|
proc checkModuleStart(self: Debugger, n: int) =
|
|
## Checks if a module begins at the given
|
|
## bytecode offset
|
|
for i, m in self.modules:
|
|
if m.start == n:
|
|
styledEcho fgBlue, "\n==== Peon Bytecode Disassembler - Module Start ", fgYellow, &"'{m.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
|
|
styledEcho fgGreen, "\t- Start offset: ", fgYellow, $m.start
|
|
styledEcho fgGreen, "\t- End offset: ", fgYellow, $m.stop, "\n"
|
|
|
|
|
|
proc checkModuleEnd(self: Debugger, n: int) =
|
|
## Checks if a module ends at the given
|
|
## bytecode offset
|
|
for i, m in self.modules:
|
|
if m.stop == n:
|
|
styledEcho fgBlue, "\n==== Peon Bytecode Disassembler - Module End ", fgYellow, &"'{m.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
|
|
|
|
|
|
proc simpleInstruction(self: Debugger, instruction: OpCode) =
|
|
## Debugs simple instructions
|
|
printInstruction(instruction, true)
|
|
self.current += 1
|
|
if instruction == Return:
|
|
printDebug("Void: ")
|
|
if self.chunk.code[self.current] == 0:
|
|
stdout.styledWriteLine(fgYellow, "Yes")
|
|
else:
|
|
stdout.styledWriteLine(fgYellow, "No")
|
|
self.current += 1
|
|
|
|
|
|
proc stackTripleInstruction(self: Debugger, instruction: OpCode) =
|
|
## Debugs instructions that operate on a single value on the stack using a 24-bit operand
|
|
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
|
printInstruction(instruction)
|
|
stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot)
|
|
self.current += 4
|
|
|
|
|
|
proc stackDoubleInstruction(self: Debugger, instruction: OpCode) =
|
|
## Debugs instructions that operate on a single value on the stack using a 16-bit operand
|
|
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble()
|
|
printInstruction(instruction)
|
|
stdout.write(&", points to index ")
|
|
stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot)
|
|
self.current += 3
|
|
|
|
|
|
proc argumentDoubleInstruction(self: Debugger, instruction: OpCode) =
|
|
## Debugs instructions that operate on a hardcoded value on the stack using a 16-bit operand
|
|
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble()
|
|
printInstruction(instruction)
|
|
stdout.styledWriteLine(fgGreen, &", has argument ", fgYellow, $slot)
|
|
self.current += 3
|
|
|
|
|
|
proc argumentTripleInstruction(self: Debugger, instruction: OpCode) {.used.} =
|
|
## Debugs instructions that operate on a hardcoded value on the stack using a 24-bit operand
|
|
var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
|
printInstruction(instruction)
|
|
stdout.styledWriteLine(fgGreen, ", has argument ", fgYellow, $slot)
|
|
self.current += 4
|
|
|
|
|
|
proc callInstruction(self: Debugger, instruction: OpCode) =
|
|
## Debugs function calls
|
|
var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
|
self.current += 3
|
|
printInstruction(instruction)
|
|
styledEcho fgGreen, &", creates frame of size ", fgYellow, $(size + 2), fgGreen
|
|
self.current += 1
|
|
|
|
|
|
proc constantInstruction(self: Debugger, instruction: OpCode) =
|
|
## Debugs instructions that operate on the constant table
|
|
var size: uint
|
|
if instruction == LoadString:
|
|
size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
|
self.current += 3
|
|
var constant = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
|
|
printInstruction(instruction)
|
|
stdout.styledWrite(fgGreen, &", points to constant at position ", fgYellow, $constant)
|
|
self.current += 4
|
|
if instruction == LoadString:
|
|
stdout.styledWriteLine(fgGreen, " of length ", fgYellow, $size)
|
|
else:
|
|
stdout.write("\n")
|
|
|
|
|
|
proc jumpInstruction(self: Debugger, instruction: OpCode) =
|
|
## Debugs jumps
|
|
var orig = self.current
|
|
var jump = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple().int()
|
|
printInstruction(instruction, true)
|
|
printDebug("Jump size: ")
|
|
stdout.styledWrite(fgYellow, $jump)
|
|
nl()
|
|
self.current += 4
|
|
while self.chunk.code[self.current] == NoOp.uint8:
|
|
inc(self.current)
|
|
|
|
|
|
proc disassembleInstruction*(self: Debugger) =
|
|
## Takes one bytecode instruction and prints it
|
|
let opcode = OpCode(self.chunk.code[self.current])
|
|
self.checkModuleStart(self.current)
|
|
self.checkFunctionStart(self.current)
|
|
printDebug("Offset: ")
|
|
stdout.styledWriteLine(fgYellow, $(self.current))
|
|
printDebug("Line: ")
|
|
stdout.styledWriteLine(fgYellow, &"{self.chunk.getLine(self.current)}")
|
|
case opcode:
|
|
of simpleInstructions:
|
|
self.simpleInstruction(opcode)
|
|
# Functions (and modules) only have a single return statement at the
|
|
# end of their body, so we never execute this more than once per module/function
|
|
if opcode == Return:
|
|
# -2 to skip the hardcoded argument to return
|
|
# and the increment by simpleInstruction()
|
|
self.checkFunctionEnd(self.current - 2)
|
|
self.checkModuleEnd(self.current - 1)
|
|
of constantInstructions:
|
|
self.constantInstruction(opcode)
|
|
of stackDoubleInstructions:
|
|
self.stackDoubleInstruction(opcode)
|
|
of stackTripleInstructions:
|
|
self.stackTripleInstruction(opcode)
|
|
of argumentDoubleInstructions:
|
|
self.argumentDoubleInstruction(opcode)
|
|
of Call:
|
|
self.callInstruction(opcode)
|
|
of jumpInstructions:
|
|
self.jumpInstruction(opcode)
|
|
else:
|
|
echo &"DEBUG - Unknown opcode {opcode} at index {self.current}"
|
|
self.current += 1
|
|
|
|
|
|
|
|
|
|
proc parseFunctions(self: Debugger) =
|
|
## Parses function information in the chunk
|
|
var
|
|
start, stop, argc: int
|
|
name: string
|
|
idx = 0
|
|
size = 0
|
|
while idx < self.chunk.functions.high():
|
|
start = int([self.chunk.functions[idx], self.chunk.functions[idx + 1], self.chunk.functions[idx + 2]].fromTriple())
|
|
idx += 3
|
|
stop = int([self.chunk.functions[idx], self.chunk.functions[idx + 1], self.chunk.functions[idx + 2]].fromTriple())
|
|
idx += 3
|
|
argc = int(self.chunk.functions[idx])
|
|
inc(idx)
|
|
size = int([self.chunk.functions[idx], self.chunk.functions[idx + 1]].fromDouble())
|
|
idx += 2
|
|
name = self.chunk.functions[idx..<idx + size].fromBytes()
|
|
inc(idx, size)
|
|
self.functions.add(Function(start: start, stop: stop, argc: argc, name: name))
|
|
|
|
|
|
proc parseModules(self: Debugger) =
|
|
## Parses module information in the chunk
|
|
var
|
|
start, stop: int
|
|
name: string
|
|
idx = 0
|
|
size = 0
|
|
while idx < self.chunk.modules.high():
|
|
start = int([self.chunk.modules[idx], self.chunk.modules[idx + 1], self.chunk.modules[idx + 2]].fromTriple())
|
|
idx += 3
|
|
stop = int([self.chunk.modules[idx], self.chunk.modules[idx + 1], self.chunk.modules[idx + 2]].fromTriple())
|
|
idx += 3
|
|
size = int([self.chunk.modules[idx], self.chunk.modules[idx + 1]].fromDouble())
|
|
idx += 2
|
|
name = self.chunk.modules[idx..<idx + size].fromBytes()
|
|
inc(idx, size)
|
|
self.modules.add(Module(start: start, stop: stop, name: name))
|
|
|
|
|
|
proc disassembleChunk*(self: Debugger, chunk: Chunk, name: string) =
|
|
## Takes a chunk of bytecode and prints it
|
|
self.chunk = chunk
|
|
styledEcho fgBlue, &"==== Peon Bytecode Disassembler - Chunk '{name}' ====\n"
|
|
self.current = 0
|
|
self.parseFunctions()
|
|
self.parseModules()
|
|
while self.current < self.chunk.code.len:
|
|
self.disassembleInstruction()
|
|
echo ""
|
|
|
|
styledEcho fgBlue, &"==== Peon Bytecode Disassembler - Chunk '{name}' ===="
|
|
|
|
|