peon/src/util/debugger.nim

259 lines
9.6 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 ../frontend/meta/bytecode
import multibyte
import std/strformat
import std/strutils
import std/terminal
type
CFIElement = ref object
start, stop, bottom, argc: int
name: string
started, stopped: bool
Debugger* = ref object
chunk: Chunk
cfiData: seq[CFIElement]
current: int
proc newDebugger*: Debugger =
## Initializes a new, empty
## debugger object
new(result)
result.cfiData = @[]
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 checkFrameStart(self: Debugger, n: int) =
## Checks if a call frame begins at the given
## bytecode offset
for i, e in self.cfiData:
if n == e.start and not (e.started or e.stopped):
e.started = true
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Begin Frame ", 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
proc checkFrameEnd(self: Debugger, n: int) =
## Checks if a call frame ends at the given
## bytecode offset
for i, e in self.cfiData:
if n == e.stop and e.started and not e.stopped:
e.stopped = true
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - End Frame ", fgYellow, &"'{e.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
self.checkFrameEnd(self.current - 2)
self.checkFrameEnd(self.current - 1)
self.checkFrameEnd(self.current)
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) =
## 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 functionInstruction(self: Debugger, instruction: OpCode) =
## Debugs function calls
var address = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
printInstruction(instruction)
styledEcho fgGreen, &", loads function at address ", fgYellow, $address
self.current += 4
proc loadAddressInstruction(self: Debugger, instruction: OpCode) =
## Debugs LoadReturnAddress instructions
var address = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3],
self.chunk.code[self.current + 4]].fromQuad()
printInstruction(instruction)
styledEcho fgGreen, &", loads address ", fgYellow, $address
self.current += 5
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)
for i in countup(orig, self.current + 1):
self.checkFrameStart(i)
proc disassembleInstruction*(self: Debugger) =
## Takes one bytecode instruction and prints it
printDebug("Offset: ")
stdout.styledWriteLine(fgYellow, $(self.current))
printDebug("Line: ")
stdout.styledWriteLine(fgYellow, &"{self.chunk.getLine(self.current)}")
var opcode = OpCode(self.chunk.code[self.current])
case opcode:
of simpleInstructions:
self.simpleInstruction(opcode)
of constantInstructions:
self.constantInstruction(opcode)
of stackDoubleInstructions:
self.stackDoubleInstruction(opcode)
of stackTripleInstructions:
self.stackTripleInstruction(opcode)
of argumentDoubleInstructions:
self.argumentDoubleInstruction(opcode)
of argumentTripleInstructions:
self.argumentTripleInstruction(opcode)
of Call:
self.callInstruction(opcode)
of jumpInstructions:
self.jumpInstruction(opcode)
of LoadFunction:
self.functionInstruction(opcode)
of LoadReturnAddress:
self.loadAddressInstruction(opcode)
else:
echo &"DEBUG - Unknown opcode {opcode} at index {self.current}"
self.current += 1
proc parseCFIData(self: Debugger) =
## Parses CFI information in the chunk
var
start, stop, argc: int
name: string
idx = 0
size = 0
while idx < len(self.chunk.cfi) - 1:
start = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
idx += 3
stop = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
idx += 3
argc = int(self.chunk.cfi[idx])
inc(idx)
size = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1]].fromDouble())
idx += 2
name = self.chunk.cfi[idx..<idx + size].fromBytes()
inc(idx, size)
self.cfiData.add(CFIElement(start: start, stop: stop,
argc: argc, 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 Debugger: Session starting - Chunk '{name}' ====\n"
self.current = 0
self.parseCFIData()
while self.current < self.chunk.code.len:
self.disassembleInstruction()
echo ""
styledEcho fgBlue, &"==== Peon Bytecode Debugger: Session ended - Chunk '{name}' ===="