Interfacing a user interface to the ECLiPSe tracer -------------------------------------------------- Author: Joachim Schimpf Date: December 2001 Applies to Eclipse version 5.3 onwards The Eclipse debugger is implemented in 3 layers: 1. Low level: Event generation 2. Intermediate level: Box model port reconstruction 3. High level: Display and user interaction Opium can be interfaced by replacing level 3. 1. Low level: Event generation This is part of the Eclipse runtime system (actually the implementation of the abstract machine). It creates debug events on the following occasions: 253 - call notification 254 - exit notification 255 - redo notification 256 - delay notification 257 - wake notification This level already implements some pre-filtering such that not all notifications need to be generated for selective tracing. 2. Intermediate level: Box model port reconstruction This layer translates the low-level notifications into the ports of the box model. This is necessary because not every port of the box model corresponds to a physical notification (for example, there is no exit-notification for the exit of the last subgoal of a clause), the missing ports must therefore be reconstructed here. This level also completes the pre-filtering of ports. Finally, for every port that passes the pre-filter, an event 252 is raised. In addition, event 250 is generated at the start of a trace. 3. High level: Display and user interaction Currently, there are two interfaces to the tracer: - the tty interface - the tcl/tk based interface both are attached through events 250 and 252. 4. Peer level: Display and user interaction This is what's implemented by lib(tracer_tcl) and the TkEclipse tracer GUI. They use peer-based communication (queues, multitasking and RPCs). # BEGIN LICENSE BLOCK # Version: CMPL 1.1 # # The contents of this file are subject to the Cisco-style Mozilla Public # License Version 1.1 (the "License"); you may not use this file except # in compliance with the License. You may obtain a copy of the License # at www.eclipse-clp.org/license. # # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and limitations # under the License. # # The Original Code is The ECLiPSe Constraint Logic Programming System. # The Initial Developer of the Original Code is Cisco Systems, Inc. # Portions created by the Initial Developer are # Copyright (C) 2006 Cisco Systems, Inc. All Rights Reserved. # # Contributor(s): # # END LICENSE BLOCK Implementing your own tracer interface -------------------------------------- For every box-model port, Eclipse raises the event 252 and calls the assigned handler, e.g. trace_line_handler(252, TraceLine) where TraceLine is a trace_line data structure and looks as follows: trace_line with [ port:Port, % port name frame:Frame % call stack data structure ] here, Port is an atom indicating one of the box model ports: call exit '*exit' nondeterministic exit redo fail leave leave as a result of exit_block exception delay a goal delays resume a delayed goal resumes (similar to call) next next alternative clause else next alternative in inline disjunction (;) spyterm setup data spy point modify spied data was modified Frame is a data structure which represents the whole call stack at the time the port is generated. It is a tf-structure with the following components: tf with [ invoc:Invoc, % invocation number (integer >= 0) goal:Goal, % the goal term itself (term) depth:Depth, % nesting level (integer >= 0) chp:Chp, % a timestamp for the choicepoint at call time parent:Parent, % the parent in the call stack (another tf-frame, or 0) proc:Proc, % an identifier for the goal predicate (black box) path:Path, % the full pathname of the source file where goal % occurs (empty atom, '' if no info available) from:From, % Offset (in bytes) in source file to start of functor % for the goal to:To, % Offset (in bytes) in source file to end of functor % for the goal module:Module % the context module (atom) ] There is an auxiliary predicate to extract further hidden information from the tf data structure: get_tf_prop(+TfStructure, +What, -Info) What Info spy on/off skip on/off module the definition module of the goal predicate (atom) Note that there are two modules involved: The module field in the tf-structure is the context module, ie. the module from which the predicate was called. The module returned by get_tf_prop/3 is the definition module, ie. the module where the predicate is defined. Simple example -------------- This is the simplest possible tracer interface. It just writes all trace lines to the standard output: % file mini_tracer.ecl :- module(mini_tracer). :- import sepia_kernel. :- set_event_handler(252, print_trace/2). print_trace(_, trace_line with [ port:Port, frame:(tf with [goal:Goal]) ]) :- writeln(Port:Goal). Usage: ECLiPSe Constraint Logic Programming System [kernel] Version 5.3 #46, Tue Dec 25 00:32 2001 [eclipse 1]: use_module(mini_tracer). mini_tracer.ecl compiled traceable 180 bytes in 0.04 seconds Yes (0.04s cpu) [eclipse 2]: trace(true). call : true exit : true Yes (0.00s cpu) Continuing ---------- Execution of the debugged program continues when the handler for event 252 returns (succeeds). The handler can also fail or abort, which is equivalent to inserting a fail or abort into the debugged program. The low-level tracer has a prefilter which can be used to filter out uninteresting trace lines more efficiently (when a trace line does not satisfy the filter condition, the event 252 is not raised for this trace line). The prefilter conditions are: Invocation number: Min..Max port is only traced if its invocation number is between Min and Max Nesting depth: Min..Max port is only traced if its nesting depth is between Min and Max Port mask: list of port names (e.g. call, exit, ...) port is only traced if it is one of the specified ports Preds: boolean port is only traced if the port predicate has a spypoint This prefilter can be configured using the predicate configure_prefilter(+Invoc, +Depth, +Ports, +Preds, +Module) Invoc a numerical range specification, e.g. Min..Max where Min and Max are integers Min-Max same as Min..Max I same as I..I =(I) same as I..I =<(I) same as 0..I >=(I) same as I..maxint <(I) same as 0..I-1 >(I) same as I+1..maxint _ same as 0..maxint Depth a numerical range specification (like Invoc) Ports a port set specification, e.g. _ any port PortList all ports in the list (list of atoms which are valid port names) Port same as [Port] ~Ports all ports except the ones specified Preds 'all' any predicate 'spied' only spied predicates Module currently ignored configure_prefilter/5 can be called from within the handler for event 252 to change the filter conditions before continuing, or within the handler for the initialisation event 250 to configure the filter initially. Limitations ------------ At 'fail' and 'leave' ports, the arguments of the called goals are not available. They are replaced by '...' as in [eclipse 2]: 8 is 3+4. (1) 1 CALL 8 is 3 + 4 %> creep (1) 1 FAIL ... is ... %> creep Peer Interface for Debugger GUI (lib(tracer_tcl)) ------------------------------------------------- Date: July 2003 Version: 5.6 onwards Updated: July 2008, version 6.0 The Eclipse-side code is in lib(tracer_tcl), the Tcl code in eclipse_tools.tcl. The same Eclipse-side code is used for Saros (5.10). The protocol uses the following facilities: - a from_eclipse queue 'debug_traceline' - the new multitask-facilities for stopping and continuing execution - RPCs from the GUI side Sequence of events roughly: Trace starts, and event 250 is raised. tracer_tcl:trace_start_handler_tcl/0 is called, initialises Eclipse side, and sends [] in exdr format to GUI via debug_traceline. GUI does initialisation for start tracing, and returns control to Eclipse. Eclipse hits trace port, and event 252 is raised. tracer_tcl:trace_line_handler_tcl/2 is called. Trace line information is written to the debug_traceline queue. This is a list [Depth, Style, Line, Invoc, Port, Prio, Path, From, To] in exdr format. From and To are set to -1 to indicate that source display should not be updated. peer_do_multitask(tracer) is called, which blocks. peer_do_multitask(tracer) returns. Get tracer command from global variable 'tracer_command'.(see `handling of tracer commands) Continue execution. The GUI side is completely event/handler based. In principle: Initialisation: Create queues, in particular 'debug_traceline'. Set up multitask handlers. Queue handler for 'debug_traceline': Start of tracing session, indicated by [] sent from ECLiPSe side. This ensures that any source file is refreshed when the new trace. The trace line information is read from the debug_traceline-queue, and displayed in the GUI. Multitask start handler: call peer_multitask_confirm/0 activate tracer buttons, etc Multitask interaction handler: check if any tracer buttons were pressed, and if yes, do appropriate RPCs to configure the tracer and set the global variable 'tracer_command'. Multitask end handler: deactivate tracer buttons More detailed comments from Kish about the TCL implementation: Queue hander for 'debug_traceline': tkecl:handle_trace_line Multitask start handler: tkecl:multi_start_handler >> call peer_multitask_confirm/0 >> activate tracer buttons, etc The handler now sets a return code to indicate interest if the phase is `tracer' type, which then cause peer_multitask_confirm/0 to be called. Multitask interaction handler: tkecl:multi_interact_handler >> check if any tracer buttons were pressed, and if yes, do appropriate RPCs >> to configure the tracer and set the global variable 'tracer_command'. This is not quite correct. The tracer buttons etc. have their own handlers, which sets the global variable 'tracer_command' to the appropriate value. The interaction handler checks to see if tracer_command was set (after going into the Tk event loop to allow the buttons to be pressed etc.), and if so, do the appropriate actions/RPCs depending on the value of the tracer_command. The tracer buttons (if they still exist) are disabled. The 'tracer_state' global variable is set to 'disabled' [with 5.7, this must be set in all cases, even when the tracer window is no longer there; as the handler uses it to determine the appropriate return code from the handler]. Multitask end handler: tkecl:multi_end_handler >> deactivate tracer buttons No, the buttons are already deactivated by the interaction handler. The only action performed here is for when the tools are used remotely: the entire tools GUI is "frozen" so that when ECLiPSe has control, you can't use the GUI. Handling of tracer commands in tracer_tcl ----------------------------------------- Author: Kish Shen Date: Aug 2005 Version: 5.8 (most of it applies to earlier versions) After other RPCs, the command is passed from the GUI to ECLiPSe by the RPC: tracer_tcl:set_tracer_command(+Cmd) the debug phase is then terminated and ECLiPSe execution resumed. Cmd understood by ECLiPSe side are: "a" (abort) "filter" (tracer filter) filter condition is set by the RPCs: tracer_tcl:prepare_filter(++Count) Count: non-negative integer giving number of times filter conditions should be met before stopping sepia_kernel:configure_prefilter/4 The GUI should allow the user to set all the arguments of this predicate. See filter.txt for more details "s" (skip) "n" (no debug) "N" (no debug, and turn off debugger) "c" (creep) "i" (jump to invocation N) N set by RPC sepia_kernel:configure_prefilter(N,_,_,_,_) "j" (jump to depth range Min..Max) Min,Max set by RPC sepia_kernel:configure_prefilter(_,Min..Max,_,_,_) Note: "up" command should be handled by setting Min..Max to 0.. f(N) (fail to invocation N) "z" (zap to non-current port) Note: for zapping to a sepcified port, Cmd should be "" rather than "z", with an RPC sepia_kernel:configure_prefilter(_,_,Port,_,dontcare) to set up the port to zap to.