nitdbg : Added remote debugger to nit standard make program.
authorLucas Bajolet <lucas.bajolet@hotmail.com>
Fri, 9 Aug 2013 21:36:46 +0000 (17:36 -0400)
committerLucas Bajolet <lucas.bajolet@hotmail.com>
Fri, 9 Aug 2013 21:36:46 +0000 (17:36 -0400)
Factorized common code between nit.nit and netdbg.nit under nitdbg_commons.nit.

Signed-off-by: Lucas Bajolet <lucas.bajolet@hotmail.com>

src/Makefile
src/dbgcli.nit [new file with mode: 0755]
src/debugger.nit
src/netdbg.nit [new file with mode: 0755]
src/network_debugger.nit [new file with mode: 0755]
src/nit.nit
src/nitdbg_commons.nit [new file with mode: 0644]

index 1801a9b..e5ca02f 100644 (file)
@@ -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 (executable)
index 0000000..6dafabf
--- /dev/null
@@ -0,0 +1,164 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Lucas Bajolet <lucas.bajolet@gmail.com>
+#
+# 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
index 87c9a17..8e17340 100644 (file)
@@ -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 (executable)
index 0000000..edfe1af
--- /dev/null
@@ -0,0 +1,38 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Lucas Bajolet <lucas.bajolet@hotmail.com>
+#
+# 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 (executable)
index 0000000..fc6dc36
--- /dev/null
@@ -0,0 +1,231 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Lucas Bajolet <lucas.bajolet@gmail.com>
+#
+# 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
index eedb11d..f02d457 100644 (file)
 # 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 (file)
index 0000000..6995db5
--- /dev/null
@@ -0,0 +1,67 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013 Lucas Bajolet <lucas.bajolet@hotmail.com>
+#
+# 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