Skip to content

Commit

Permalink
Stateless debugging server, Clean cli
Browse files Browse the repository at this point in the history
  • Loading branch information
nbbeeken committed Sep 30, 2018
1 parent 58942f1 commit b7bb93c
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 144 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ __pycache__
.vs/
test.mips.json
.idea/
*.egg-info
18 changes: 14 additions & 4 deletions dashmips/__main__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
"""dashmips program."""
import json
from dashmips.preprocessor import preprocess, MipsProgram
from dashmips.debugserver import debug_mips


def main_compile(args):
"""Compile/Exec mips code."""
program = preprocess(args.FILE)
if args.out:
program.dump(args.out)
json.dump(dict(program), args.out)

if args.json:
print(program.dumps(), end='\r\n\r\n') # Ending matches socket comm
# Ending matches socket communication
print(json.dumps(dict(program)), end='\r\n\r\n')

if args.run:
from dashmips.run import run
Expand All @@ -23,11 +26,14 @@ def main_debug(args):
debug_mips(host=args.host, port=args.port)


if __name__ == "__main__":
def main():
"""Entry function for Dashmips."""
import argparse
import sys
parser = argparse.ArgumentParser('dashmips')

parser.add_argument('-v', '--version', action='version', version='0.0.1')

sbp = parser.add_subparsers(title='commands', dest='command', required=True)
compileparse = sbp.add_parser('compile', aliases=['c'])
debugparse = sbp.add_parser('debug', aliases=['d'])
Expand All @@ -52,9 +58,13 @@ def main_debug(args):
'-p', '--port', type=int, default=9999, help='run debugger on port'
)
debugparse.add_argument(
'-i', '--host', default='localhost', help='run debugger on host'
'-i', '--host', default='0.0.0.0', help='run debugger on host'
)
debugparse.set_defaults(func=main_debug)

prog_args = parser.parse_args()
sys.exit(prog_args.func(prog_args))


if __name__ == "__main__":
main()
62 changes: 23 additions & 39 deletions dashmips/debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from typing import Dict, Callable, Optional

from dashmips.debugserver import DebugMessage
from dashmips.instructions import Instructions
from dashmips.run import next_instruction, run
from dashmips.mips import MipsException


def debug_start(msg: DebugMessage) -> Optional[DebugMessage]:
Expand All @@ -14,57 +15,40 @@ def debug_start(msg: DebugMessage) -> Optional[DebugMessage]:

def debug_step(msg: DebugMessage) -> DebugMessage:
"""Debug step."""
current_pc = msg.program.registers['pc']
if len(msg.program.source) < current_pc:
# We jumped or executed beyond available text
msg.message = 'pc is greater than len(source)'
try:
next_instruction(msg.program)
# TODO: Should be doing something with breakpoints here
except MipsException as exc:
msg.error = True
return msg

lineofcode = msg.program.source[current_pc].line # Current line of execution
instruction = lineofcode.split(' ')[0] # Grab the instruction name

instruction_fn = Instructions[instruction] # relevant Instruction()

match = re.match(instruction_fn.regex, lineofcode)
if match:
# Instruction has the correct format
args = instruction_fn.parser(match)
instruction_fn(msg.program, args)
else:
# Bad arguments to instruction
msg.message = f"{lineofcode} is malformed for {instruction}"
msg.error = True
return msg
msg.message = exc.message

return msg


def debug_continue(msg: DebugMessage) -> DebugMessage:
"""Debug continue."""
msg.message = 'Not Implemented'
msg.error = True
return msg


def debug_stepreverse(msg: DebugMessage) -> DebugMessage:
"""Debug stepreverse."""
msg.message = 'Not Implemented'
msg.error = True
return msg

starting_pc = msg.program.registers['pc']

def breaking_condition(program):
nonlocal starting_pc
if program.registers['pc'] == starting_pc:
# current instruction will execute even if on breakpoint
# b/c we would have broken on it last time.
return True
if program.registers['pc'] in msg.breakpoints:
return False
return True
try:
run(msg.program, breaking_condition)
except MipsException as exc:
msg.error = True
msg.message = exc.message

def debug_restart(msg: DebugMessage) -> DebugMessage:
"""Debug restart."""
msg.message = 'Not Implemented'
msg.error = True
return msg


Commands: Dict[str, Callable] = {
'start': debug_start,
'restart': debug_restart,
'step': debug_step,
'stepreverse': debug_stepreverse,
'continue': debug_continue,
}
92 changes: 48 additions & 44 deletions dashmips/debugserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,6 @@

from dashmips.preprocessor import MipsProgram

BAD_MSG = json.dumps({
'command': 'stop',
'message': 'malformed message',
'program': None,
'error': True
})
ERR_MSG = json.dumps({
'command': 'stop',
'message': '500 internal error',
'program': None,
'error': True,
})


@dataclass
class DebugMessage:
Expand All @@ -33,55 +20,67 @@ class DebugMessage:
message: str = ''
error: bool = False

def dumps(self):
"""Dump Json formatted Debug message"""
msg = asdict(self)
return json.dumps(msg)
def __post_init__(self):
# set to remove duplicates and sort
self.breakpoints = sorted(set(self.breakpoints))

def __iter__(self):
return iter(asdict(self).items())

@staticmethod
def loads(string):
def from_dict(payload: dict):
"""Deserialize from json to DebugMessage."""
from dashmips.debugger import Commands
try:
payload = json.loads(string)

if ('command' not in payload and
payload['command'] not in Commands):
# Json doesn't contain a valid command nor program
return None

if 'program' in payload:
payload['program'] = MipsProgram(
**MipsProgram.from_dict(payload.get('program', {}))
)
else:
payload['program'] = None # command stop doesn't need a program
return DebugMessage(**payload)
except json.JSONDecodeError:

if ('command' not in payload and
payload['command'] not in Commands):
# Json doesn't contain a valid command nor program
return None

if 'program' in payload:
payload['program'] = MipsProgram.from_dict(
payload.get('program', {})
)
else:
payload['program'] = None
return DebugMessage(**payload)


class MipsDebugRequestHandler(StreamRequestHandler):
"""Mips Debug Client Request Handler."""

def respond(self, msg: DebugMessage):
"""Send response."""
msg_to_send = msg.dumps().encode('utf8')
msg_to_send = json.dumps(dict(msg)).encode('utf8')
self.wfile.write(msg_to_send + b'\r\n\r\n')
self.wfile.flush()
print(f"{self.client_address}: Respond {msg}")

def receive(self) -> Optional[DebugMessage]:
"""Receive Client Command."""
return DebugMessage.loads(self.rfile.readline().strip())
try:
msg = DebugMessage.from_dict(
json.loads(self.rfile.readline().strip())
)
print(f"{self.client_address}: Receive {msg}")
return msg
except json.JSONDecodeError:
return None

def handle(self):
"""Handle Client Req."""
from dashmips.debugger import Commands
try:

print(f"{self.client_address}: Connected")
msg = self.receive()
if msg is None:
self.respond(BAD_MSG)
self.respond(DebugMessage(**{
'command': 'stop',
'message': 'malformed message',
'program': None,
'breakpoints': [],
'error': True,
}))
return # End this party now
self.respond(Commands[msg.command](msg))

Expand All @@ -97,26 +96,31 @@ def handle(self):
'program': None,
'error': True,
})
self.respond(err_msg)
# Arbitrarily large bad exit code to signal it was serious
self.server.shutdown()
self.server.server_close()
exit(24)
print(f'{fname}:{exc_tb.tb_lineno} {exc_type} {ex}')
try:
self.respond(err_msg)
finally:
# Arbitrarily large bad exit code to signal it was serious
self.server.shutdown()
self.server.server_close()
exit(24)


class MipsDebugServer(TCPServer):
"""Mips Debug Server."""

def __init__(self,
server_address=('localhost', 9999),
server_address=('0.0.0.0', 9999),
RequestHandlerClass=MipsDebugRequestHandler,
bind_and_activate=True) -> None:
"""Create Mips Debug Server."""
self.allow_reuse_address = True
super().__init__(
server_address,
RequestHandlerClass,
bind_and_activate
)
print(f"Server is listening on {self.socket.getsockname()}")

def server_bind(self):
"""Set reusable address opt."""
Expand Down
17 changes: 9 additions & 8 deletions dashmips/hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ class Memory(list):
def __init__(self, listish=None):
"""Create 2KB of MIPS RAM."""
self._freespace = 0x4
if isinstance(listish, bytes) or isinstance(listish, str):
listish = list(a85decode(listish, foldspaces=True))
elif listish is None:
# if isinstance(listish, bytes) or isinstance(listish, str):
# listish = list(a85decode(listish, foldspaces=True))
if listish is None:
listish = []
else:
listish = list(listish)
Expand All @@ -105,7 +105,8 @@ def __init__(self, listish=None):
def __setitem__(self, key, value):
"""Bounds checking on access."""
if 0x0 == key <= 0x3:
raise Exception('NULL-ish pointer')
from dashmips.mips import MipsException
raise MipsException('NULL-ish pointer')
try:
for idx, val in enumerate(value):
# val &= 0xFF
Expand All @@ -116,7 +117,7 @@ def __setitem__(self, key, value):
return super().__setitem__(key, value)

def __repr__(self):
"""Compated Memory string."""
"""Compacted Memory string."""
s = '['
zero_ct = 0
for v in self:
Expand All @@ -138,6 +139,6 @@ def malloc(self, size: int) -> int:
self._freespace += size
return old_freespace

def encoded_str(self):
"""Base85 encoding of memory."""
return a85encode(bytes(self), foldspaces=True).decode('utf8')
# def encoded_str(self):
# """Base85 encoding of memory."""
# return a85encode(bytes(self), foldspaces=True).decode('utf8')
9 changes: 9 additions & 0 deletions dashmips/mips.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
import dashmips.directives as directives
from dashmips.hardware import Memory


class MipsException(Exception):
"""Mips related errors"""

def __init__(self, message):
super().__init__(message)
self.message = message


Directives: Dict[str, Callable[[str, Any, Memory], int]] = {
directive: fn
for directive, fn in
Expand Down
Loading

0 comments on commit b7bb93c

Please sign in to comment.