From: Lucas Bajolet Date: Fri, 9 Aug 2013 21:36:46 +0000 (-0400) Subject: nitdbg : Added remote debugger to nit standard make program. X-Git-Tag: v0.6.1~44^2 X-Git-Url: http://nitlanguage.org nitdbg : Added remote debugger to nit standard make program. Factorized common code between nit.nit and netdbg.nit under nitdbg_commons.nit. Signed-off-by: Lucas Bajolet --- diff --git a/src/Makefile b/src/Makefile index 1801a9b..e5ca02f 100644 --- a/src/Makefile +++ b/src/Makefile @@ -16,7 +16,7 @@ NITCOPT= -all: ../bin/nitc ../bin/nitdoc ../bin/nits ../bin/nitmetrics ../bin/nitg ../bin/nit +all: ../bin/nitc ../bin/nitdoc ../bin/nits ../bin/nitmetrics ../bin/nitg ../bin/nit ../bin/netdbg ../bin/dbgcli ../bin/nitc: ../c_src/nitc parser/parser.nit @echo '***************************************************************' @@ -60,6 +60,20 @@ all: ../bin/nitc ../bin/nitdoc ../bin/nits ../bin/nitmetrics ../bin/nitg ../bin/ ./git-gen-version.sh ../bin/nitc ${NITCOPT} -o ../bin/nit -O -v nit.nit +../bin/netdbg : ../bin/nitc + @echo '***************************************************************' + @echo '* Compile netdbg from NIT source files *' + @echo '***************************************************************' + ./git-gen-version.sh + ../bin/nitc ${NITCOPT} -o ../bin/netdbg -O -v netdbg.nit + +../bin/dbgcli : ../bin/nitc + @echo '***************************************************************' + @echo '* Compile dbgcli from NIT source files *' + @echo '***************************************************************' + ./git-gen-version.sh + ../bin/nitc ${NITCOPT} -o ../bin/dbgcli -O -v dbgcli.nit + ../c_src/nitc: ../c_src/*.c ../c_src/*.h ../c_src/nitc._build.sh ../c_src/Makefile @echo '***************************************************************' @echo '* Compile nitc from C source files *' diff --git a/src/dbgcli.nit b/src/dbgcli.nit new file mode 100755 index 0000000..6dafabf --- /dev/null +++ b/src/dbgcli.nit @@ -0,0 +1,164 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2013 Lucas Bajolet +# +# 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. + +# Client for the nit debugger nitdbg +# +# Can send commands to the debugger +module dbgcli + +import socket +import toolcontext + +redef class ToolContext + + var opt_host_address: OptionString = new OptionString("Sets the host to debug from, use IPV4 only (Defaults to 127.0.0.1)", "--host") + var opt_debug_port: OptionInt = new OptionInt("Sets the debug port (Defaults to 22125) - Must be contained between 0 and 65535", 22125, "--port") + + redef init + do + super + self.option_context.add_option(self.opt_host_address) + self.option_context.add_option(self.opt_debug_port) + end + +end + +redef class String + + # Checks if the actual string is a valid IPv4 address + # That is, if the pattern is int.int.int.int where each int must be between 0 and 255 + fun is_valid_ipv4_address: Bool + do + var components = self.split_with(".") + if components.length != 4 then return false + for i in components do + if not i.is_numeric or not (i.to_i <= 255 and i.to_i >= 0) then return false + end + return true + end + +end + +# Persistant connection to the debugger +# Default port = 22125 +# +class DebugClient + + var debugger_connection: Socket + + init (host: String, port: Int) + do + self.debugger_connection = new Socket.stream_with_host(host, port) + print "[HOST ADDRESS] : "+debugger_connection.address + print "[HOST] : "+debugger_connection.host.as(not null) + print "[PORT] : "+debugger_connection.port.to_s + print "Connecting ... "+debugger_connection.connect.to_s + end + + init with_port (host: String, port: Int) + do + debugger_connection = new Socket.stream_with_host(host, port) + end + + fun send_command(command: String) + do + debugger_connection.write(command+"\n") + end + + fun connected: Bool + do + return self.debugger_connection.connected + end + + fun ready: Bool + do + return debugger_connection.ready_to_read(40) + end + + fun read_command: String + do + var buff = new Buffer + while debugger_connection.ready_to_read(40) do buff.append(debugger_connection.read) + return buff.to_s + end + + fun disconnect + do + debugger_connection.close + end + +end + +# Create a tool context to handle options and paths +var toolcontext = new ToolContext +toolcontext.process_options + +var debug: DebugClient + +if toolcontext.opt_help.value then + toolcontext.option_context.usage + return +end + +# If the port value is not an Int between 0 and 65535 (Mandatory according to the norm) +# Print the usage +if toolcontext.opt_debug_port.value < 0 or toolcontext.opt_debug_port.value > 65535 then + toolcontext.option_context.usage + return +end + +# An IPV4 address does always complies to this form : x.x.x.x +# Where x is an integer whose value is >=0 and <= 255 +if toolcontext.opt_host_address.value != null then + if toolcontext.opt_host_address.value.is_valid_ipv4_address then + debug = new DebugClient(toolcontext.opt_host_address.value.as(not null), toolcontext.opt_debug_port.value) + else + toolcontext.option_context.usage + return + end +else + debug = new DebugClient("127.0.0.1", toolcontext.opt_debug_port.value) +end + +var recv_cmd: String + +var written_cmd: String + +var over = false + +while not over do + if stdin.poll_in then + written_cmd = gets + debug.send_command(written_cmd) + if written_cmd == "kill" then + over = true + end + end + + if not over and debug.ready then + recv_cmd = debug.read_command + var command_parts = recv_cmd.split("\n") + for i in command_parts do + if i == "DBG DONE WORK ON SELF" then + debug.send_command("CLIENT DBG DONE ACK") + over = true + end + print i + end + end +end + +debug.disconnect diff --git a/src/debugger.nit b/src/debugger.nit index 87c9a17..8e17340 100644 --- a/src/debugger.nit +++ b/src/debugger.nit @@ -713,6 +713,8 @@ class Debugger # If it is a primitive type, its value is directly printed fun print_instance(instance: Instance) do + print "Printing innards of a variable" + if instance isa MutableInstance then var attributes = instance.attributes print "Object : {instance}" @@ -723,6 +725,8 @@ class Debugger else print "Found variable {instance}" end + + print "Stopping printing innards of a variable" end # Prints the attributes demanded in a SequenceRead diff --git a/src/netdbg.nit b/src/netdbg.nit new file mode 100755 index 0000000..edfe1af --- /dev/null +++ b/src/netdbg.nit @@ -0,0 +1,38 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2013 Lucas Bajolet +# +# 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. + +# Network debugger for a nit program +module netdbg + +import network_debugger +import nitdbg_commons + +redef class InterpretCommons + + redef fun launch + do + super + if toolcontext.opt_debug_port.value < 0 or toolcontext.opt_debug_port.value > 65535 then + toolcontext.option_context.usage + return + end + + modelbuilder.run_debugger_network_mode(mainmodule.as(not null),arguments.as(not null),toolcontext.opt_debug_port.value) + end + +end + +(new InterpretCommons).launch diff --git a/src/network_debugger.nit b/src/network_debugger.nit new file mode 100755 index 0000000..fc6dc36 --- /dev/null +++ b/src/network_debugger.nit @@ -0,0 +1,231 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2013 Lucas Bajolet +# +# 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. + +# Network debugger for a nit program, based on the original debugger +# Replaces access to stdin/stdout to send data on the network to the client concerned +module network_debugger + +import socket +intrude import debugger + +redef class ToolContext + + var opt_debug_port: OptionInt = new OptionInt("Sets the debug port (Defaults to 22125) - Must be contained between 0 and 65535", 22125, "--port") + + redef init + do + super + self.option_context.add_option(self.opt_debug_port) + end + +end + +redef class ModelBuilder + fun run_debugger_network_mode(mainmodule: MModule, arguments: Array[String], port: Int) + do + var time0 = get_time + self.toolcontext.info("*** START INTERPRETING ***", 1) + + var interpreter = new NetworkDebugger(self, mainmodule, arguments, port) + + init_naive_interpreter(interpreter, mainmodule) + + var time1 = get_time + self.toolcontext.info("*** END INTERPRETING: {time1-time0} ***", 2) + + interpreter.disconnect_routine + + interpreter.ns.close + interpreter.socket.close + end +end + +# Extends the debugger by adding new network capabilities for remote debugging +class NetworkDebugger + super Debugger + + # Represents the connexion with a particular client (Actually the only one accepted at the moment) + private var ns: Socket + + # Socket for the server, supposed to listen for incoming request from a client + private var socket: Socket + + # Initializes the debugger, waits for a client to connect + # Then starts debugging as usual + init(modelbuilder: ModelBuilder, mainmodule: MModule, arguments: Array[String], port: Int) + do + print "Listening on port {port}" + + socket = new Socket.stream_with_port(port) + + socket.bind + socket.listen(1) + + ns = socket.accept + + socket.close + + print "Client connected" + + stdin.connection = ns + + if stdout isa Stdout then + (stdout.as(Stdout)).connection = ns + else + ns.close + abort + end + + super + end + + # Provokes the disconnection of the client + # Used when debugging is over + fun disconnect_routine + do + print "DBG DONE WORK ON SELF" + + var cliOverResp = "" + + while cliOverResp != "CLIENT DBG DONE ACK" do + cliOverResp = gets + end + end + + # Checks on every call if the client has sent a command before continuing debugging + redef fun stmt(n) + do + if stdin.poll_in then + var command = gets + if command == "stop" then + n.debug("Stopped by client") + while process_debug_command(gets) do end + else + process_debug_command(command) + end + end + + super(n) + end + +end + +redef class ANode + + # Breaks automatically when encountering an error + # Permits the injunction of commands before crashing + # Disconnects from the client before crashing + redef private fun fatal(v: NaiveInterpreter, message: String) + do + if v isa Debugger then + print "An error was encountered, the program will stop now." + self.debug(message) + while v.process_debug_command(gets) do end + end + + if v isa NetworkDebugger then + v.disconnect_routine + stdin.connection.close + end + + super + end +end + +# Replaces Stdin to read on the network +redef class Stdin + + # Represents the connection with a client + var connection: nullable Socket = null + + # Used to store data that has been read from the connection + var buf: Buffer = new Buffer + var buf_pos: Int = 0 + + # Checks if data is available for reading + redef fun poll_in + do + return connection.ready_to_read(0) + end + + # Reads the whole content of the buffer + # Blocking if the buffer is empty + redef fun read_all + do + var loc_buf = new Buffer + if connection.ready_to_read(0) then buf.append(connection.read) + for i in [buf_pos .. buf.length-1] do loc_buf.add(buf[i]) + buf.clear + buf_pos = 0 + return loc_buf.to_s + end + + # Reads a single char on the incoming buffer + # If the buffer is empty, the call is blocking + redef fun read_char + do + if connection.ready_to_read(0) then buf.append(connection.read) + if buf_pos >= buf.length then + buf.clear + buf_pos = 0 + #Blocking call + buf.append(connection.read) + end + buf_pos += 1 + return buf[buf_pos-1].ascii + end + + # Reads a line on the network if available + # Stops at the first encounter of a \n character + # If the buffer is empty, the read_line call is blocking + redef fun read_line + do + var line_buf = new Buffer + if connection.ready_to_read(0) then buf.append(connection.read) + var has_found_eol: Bool = false + loop + if buf_pos >= buf.length then + buf.clear + buf_pos = 0 + # Blocking call + buf.append(connection.read) + end + buf_pos += 1 + if buf[buf_pos-1] == '\n' then break + line_buf.add(buf[buf_pos-1]) + end + return line_buf.to_s + end +end + +# Replaces Stdout to write on the network +redef class Stdout + + # Connection with the client object + var connection: nullable Socket = null + + # Writes a string on the network if available, else + # it is written in the standard output (Terminal) + redef fun write(s) + do + if connection != null then + connection.write(s) + else + super + end + end + +end diff --git a/src/nit.nit b/src/nit.nit index eedb11d..f02d457 100644 --- a/src/nit.nit +++ b/src/nit.nit @@ -17,46 +17,26 @@ # A naive Nit interpreter module nit -import modelbuilder -import exprbuilder import naive_interpreter import debugger -#import interpretor_type_test +import nitdbg_commons + +redef class InterpretCommons + + redef fun launch + do + super + var self_mm = mainmodule.as(not null) + var self_args = arguments.as(not null) + if toolcontext.opt_debugger_autorun.value then + modelbuilder.run_debugger_autorun(self_mm, self_args) + else if toolcontext.opt_debugger_mode.value then + modelbuilder.run_debugger(self_mm, self_args) + else + modelbuilder.run_naive_interpreter(self_mm, self_args) + end + end -# Create a tool context to handle options and paths -var toolcontext = new ToolContext -# Add an option "-o" to enable compatibilit with the tests.sh script -var opt = new OptionString("compatibility (does noting)", "-o") -toolcontext.option_context.add_option(opt) -# We do not add other options, so process them now! -toolcontext.process_options - -# We need a model to collect stufs -var model = new Model -# An a model builder to parse files -var modelbuilder = new ModelBuilder(model, toolcontext) - -var arguments = toolcontext.option_context.rest -if arguments.is_empty or toolcontext.opt_help.value then - toolcontext.option_context.usage - return end -var progname = arguments.first - -# Here we load an process all modules passed on the command line -var mmodules = modelbuilder.parse_and_build([progname]) -modelbuilder.full_propdef_semantic_analysis -if toolcontext.opt_only_metamodel.value then exit(0) - -# Here we launch the interpreter on the main module -assert mmodules.length == 1 -var mainmodule = mmodules.first - -if toolcontext.opt_debugger_autorun.value then - modelbuilder.run_debugger_autorun(mainmodule, arguments) -else if toolcontext.opt_debugger_mode.value then - modelbuilder.run_debugger(mainmodule, arguments) -else - modelbuilder.run_naive_interpreter(mainmodule, arguments) -end +(new InterpretCommons).launch diff --git a/src/nitdbg_commons.nit b/src/nitdbg_commons.nit new file mode 100644 index 0000000..6995db5 --- /dev/null +++ b/src/nitdbg_commons.nit @@ -0,0 +1,67 @@ +# This file is part of NIT ( http://www.nitlanguage.org ). +# +# Copyright 2013 Lucas Bajolet +# +# 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. + +module nitdbg_commons + +import modelbuilder +import exprbuilder + +class InterpretCommons + + var model: nullable Model + var modelbuilder: nullable ModelBuilder + var toolcontext: nullable ToolContext + var mainmodule: nullable MModule + var arguments: nullable Array[String] + + init + do + end + + fun launch + do + # Create a tool context to handle options and paths + toolcontext = new ToolContext + # Add an option "-o" to enable compatibilit with the tests.sh script + var opt = new OptionString("compatibility (does noting)", "-o") + toolcontext.option_context.add_option(opt) + # We do not add other options, so process them now! + toolcontext.process_options + + # We need a model to collect stufs + model = new Model + # An a model builder to parse files + modelbuilder = new ModelBuilder(model.as(not null), toolcontext.as(not null)) + + arguments = toolcontext.option_context.rest + if arguments.is_empty or toolcontext.opt_help.value then + toolcontext.option_context.usage + return + end + var progname = arguments.first + + # Here we load an process all modules passed on the command line + var mmodules = modelbuilder.parse_and_build([progname]) + modelbuilder.full_propdef_semantic_analysis + + if toolcontext.opt_only_metamodel.value then exit(0) + + # Here we launch the interpreter on the main module + assert mmodules.length == 1 + mainmodule = mmodules.first + end + +end