Debugger - Stage 2 (artf511247)
authorBotond Baranyi <botond.baranyi@ericsson.com>
Mon, 2 May 2016 10:48:52 +0000 (12:48 +0200)
committerBotond Baranyi <botond.baranyi@ericsson.com>
Mon, 2 May 2016 12:05:06 +0000 (14:05 +0200)
Change-Id: I0be2f63e69743d2661ad1d4359c55d9e758b9e26
Signed-off-by: Botond Baranyi <botond.baranyi@ericsson.com>
16 files changed:
compiler2/ttcn3/AST_ttcn3.cc
compiler2/ttcn3/AST_ttcn3.hh
compiler2/ttcn3/Statement.cc
core/Communication.cc
core/Communication.hh
core/DebugCommands.hh [new file with mode: 0644]
core/Debugger.cc
core/Debugger.hh
core/Makefile
core/Message_types.hh
core/Runtime.cc
core/Textbuf.cc
core/Textbuf.hh
mctr2/cli/Cli.cc
mctr2/mctr/MainController.cc
mctr2/mctr/MainController.h

index b720bcb0be34372452c1edb1714f1d375d5eb1d7..85568e2b974cae248384114f4fa2a41d63ffb1a7 100644 (file)
@@ -617,10 +617,10 @@ namespace Ttcn {
     return true;
   }
   
-  void Reference::refd_param_usage_found()
+  void Reference::ref_usage_found()
   {
     Common::Assignment *ass = get_refd_assignment();
-    if (!ass) FATAL_ERROR("Reference::refd_param_usage_found()");
+    if (!ass) FATAL_ERROR("Reference::ref_usage_found()");
     switch (ass->get_asstype()) {
     case Common::Assignment::A_PAR_VAL_OUT:
     case Common::Assignment::A_PAR_TEMPL_OUT:
@@ -632,9 +632,18 @@ namespace Ttcn {
     case Common::Assignment::A_PAR_PORT:
     case Common::Assignment::A_PAR_TIMER: {
       FormalPar *fpar = dynamic_cast<FormalPar*>(ass);
-      if (!fpar) FATAL_ERROR("Reference::refd_param_usage_found()");
+      if (fpar == NULL) {
+        FATAL_ERROR("Reference::ref_usage_found()");
+      }
       fpar->set_usage_found();
       break; }
+    case Common::Assignment::A_EXT_CONST: {
+      Def_ExtConst* def = dynamic_cast<Def_ExtConst*>(ass);
+      if (def == NULL) {
+        FATAL_ERROR("Reference::ref_usage_found()");
+      }
+      def->set_usage_found();
+      break; }
     default:
       break;
     }
@@ -642,7 +651,7 @@ namespace Ttcn {
 
   void Reference::generate_code(expression_struct_t *expr)
   {
-    refd_param_usage_found();
+    ref_usage_found();
     Common::Assignment *ass = get_refd_assignment();
     if (!ass) FATAL_ERROR("Reference::generate_code()");
     if (parlist) {
@@ -670,7 +679,7 @@ namespace Ttcn {
       return;
     }
     
-    refd_param_usage_found();
+    ref_usage_found();
     Common::Assignment *ass = get_refd_assignment();
     if (!ass) FATAL_ERROR("Reference::generate_code_const_ref()");
 
@@ -731,7 +740,7 @@ namespace Ttcn {
   void Reference::generate_code_portref(expression_struct_t *expr,
     Scope *p_scope)
   {
-    refd_param_usage_found();
+    ref_usage_found();
     Common::Assignment *ass = get_refd_assignment();
     if (!ass) FATAL_ERROR("Reference::generate_code_portref()");
     expr->expr = mputstr(expr->expr,
@@ -743,7 +752,7 @@ namespace Ttcn {
   void Reference::generate_code_ispresentbound(expression_struct_t *expr,
     bool is_template, const bool isbound)
   {
-    refd_param_usage_found();
+    ref_usage_found();
     Common::Assignment *ass = get_refd_assignment();
     const string& ass_id = ass->get_genname_from_scope(my_scope);
     const char *ass_id_str = ass_id.c_str();
@@ -1674,10 +1683,6 @@ namespace Ttcn {
   {
     target->header.includes = mputstr(target->header.includes,
       "#include <TTCN3.hh>\n");
-    /*if (debugger_active) {
-      target->header.includes = mputstr(target->header.includes,
-        "#include \"init_debug.inc\"\n");
-    }*/
     for (size_t i = 0; i < impmods_v.size(); i++) {
       ImpMod *im = impmods_v[i];
       Common::Module *m = im->get_mod();
@@ -2819,12 +2824,14 @@ namespace Ttcn {
   
   void Module::generate_debugger_init(output_struct* output)
   {
+    static boolean first = TRUE;
     // create the initializer function
-    output->source.global_vars = mputstr(output->source.global_vars,
-      "\n/* Initializing TTCN-3 debugger */\n"
+    output->source.global_vars = mputprintf(output->source.global_vars,
+      "\n/* Initializing the TTCN-3 debugger */\n"
       "void init_ttcn3_debugger()\n"
       "{\n"
-      /*"  debugger_manual_init();\n"*/);
+      "%s", first ? "  ttcn3_debugger.activate();\n" : "");
+    first = FALSE;
     
     // initialize global scope and variables (including imported variables)
     char* str_glob = generate_debugger_global_vars(NULL, this);
@@ -2883,11 +2890,19 @@ namespace Ttcn {
         }
         // else fall through
       case Common::Assignment::A_CONST:
-      //case Common::Assignment::A_EXT_CONST: TODO: handle unused ext_const
       case Common::Assignment::A_MODULEPAR:
       case Common::Assignment::A_MODULEPAR_TEMP:
         str = generate_code_debugger_add_var(str, ass, current_mod, "global");
         break;
+      case Common::Assignment::A_EXT_CONST: {
+        Def_ExtConst* def = dynamic_cast<Def_ExtConst*>(ass);
+        if (def == NULL) {
+          FATAL_ERROR("Module::generate_debugger_global_vars");
+        }
+        if (def->is_used()) {
+          str = generate_code_debugger_add_var(str, ass, current_mod, "global");
+        }
+        break; }
       default:
         break;
       }
@@ -3559,6 +3574,7 @@ namespace Ttcn {
     if (!p_type) FATAL_ERROR("Ttcn::Def_ExtConst::Def_ExtConst()");
     type = p_type;
     type->set_ownertype(Type::OT_CONST_DEF, this);
+    usage_found = false;
   }
 
   Def_ExtConst::~Def_ExtConst()
@@ -9512,7 +9528,7 @@ namespace Ttcn {
           // check if the reference is a parameter, mark it as used if it is
           Reference* ref = dynamic_cast<Reference*>(val->get_reference());
           if (ref != NULL) {
-            ref->refd_param_usage_found();
+            ref->ref_usage_found();
           }
         }
       } else {
@@ -9544,7 +9560,7 @@ namespace Ttcn {
           Reference* ref = dynamic_cast<Reference*>(temp->get_DerivedRef() != NULL ?
             temp->get_DerivedRef() : temp->get_Template()->get_reference());
           if (ref != NULL) {
-            ref->refd_param_usage_found();
+            ref->ref_usage_found();
           }
         }
       } else {
index e694c26b295bfad8639470b4ee4c754149c85ce6..894ec970f50cddd82bed02553568e739f9d7b8d7 100644 (file)
@@ -345,8 +345,9 @@ namespace Ttcn {
      * and the referred objects are bound or not.*/
     void generate_code_ispresentbound(expression_struct_t *expr,
       bool is_template, const bool isbound);
-    /** If the referenced object is a formal parameter, it is marked as used. */
-    void refd_param_usage_found();
+    /** Lets the referenced assignment object know, that the reference is used
+      * at least once (only relevant for formal parameters and external constants). */
+    void ref_usage_found();
   private:
     /** Detects whether the first identifier in subrefs is a module id */
     void detect_modid();
@@ -965,6 +966,7 @@ namespace Ttcn {
   class Def_ExtConst : public Definition {
   private:
     Type *type;
+    bool usage_found;
 
     /// Copy constructor disabled
     Def_ExtConst(const Def_ExtConst& p);
@@ -981,6 +983,10 @@ namespace Ttcn {
     virtual void generate_code(output_struct *target, bool clean_up = false);
     virtual void generate_code(CodeGenHelper& cgh);
     virtual void dump_internal(unsigned level) const;
+    /** Indicates that the parameter is used at least once. */
+    void set_usage_found() { usage_found = true; }
+    /** Returns true if the external constant is used at least once. */
+    bool is_used() const { return usage_found; }
   };
 
   /**
index 8610a6520a67100da9b358c58a4f3d60af36e4e9..2dc15a37f864a885c53e41671080fb0253151463 100644 (file)
@@ -7704,7 +7704,7 @@ error:
   char *Assignment::generate_code(char *str)
   {
     // check if the LHS reference is a parameter, mark it as used if it is
-    ref->refd_param_usage_found();
+    ref->ref_usage_found();
     FieldOrArrayRefs *t_subrefs = ref->get_subrefs();
     const bool rhs_copied = self_ref;
     switch (asstype) {
index 8600812b8062873fcf947a72e5b53c7c9252bea8..db8fb8d35a8da6d9c9636e965a65467647354226 100644 (file)
@@ -52,6 +52,8 @@
 #include "../common/version.h"
 
 #include "Event_Handler.hh"
+#include "Debugger.hh"
+#include "DebugCommands.hh"
 
 class MC_Connection : public Fd_And_Timeout_Event_Handler {
   virtual void Handle_Fd_Event(int fd,
@@ -591,6 +593,9 @@ void TTCN_Communication::process_all_messages_hc()
     case MSG_EXIT_HC:
       process_exit_hc();
       break;
+    case MSG_DEBUG_COMMAND:
+      process_debug_command();
+      break;
     default:
       process_unsupported_message(msg_type, msg_end);
     }
@@ -676,6 +681,9 @@ void TTCN_Communication::process_all_messages_tc()
     case MSG_UNMAP_ACK:
       process_unmap_ack();
       break;
+    case MSG_DEBUG_COMMAND:
+      process_debug_command();
+      break;
     default:
       if (TTCN_Runtime::is_mtc()) {
         // messages: MC -> MTC
@@ -715,6 +723,56 @@ void TTCN_Communication::process_all_messages_tc()
   }
 }
 
+void TTCN_Communication::process_debug_messages()
+{
+  // receives and processes messages from the MC, while test execution is halted
+  // by the debugger
+  char *buf_ptr;
+  int buf_len;
+  Text_Buf storage_buf;
+  while (ttcn3_debugger.is_halted()) {
+    incoming_buf.get_end(buf_ptr, buf_len);
+
+    int recv_len = recv(mc_fd, buf_ptr, buf_len, 0);
+
+    if (recv_len > 0) {
+      incoming_buf.increase_length(recv_len);
+
+      while (incoming_buf.is_message() && ttcn3_debugger.is_halted()) {
+        int msg_len = incoming_buf.pull_int().get_val();
+        int msg_end = incoming_buf.get_pos() + msg_len;
+        int msg_type = incoming_buf.pull_int().get_val();
+        // process only debug commands and 'stop' messages, store the rest
+        switch (msg_type) {
+        case MSG_DEBUG_COMMAND:
+          process_debug_command();
+          break;
+        case MSG_STOP:
+          process_stop();
+          break;
+        default: {
+          // store all other messages in a different buffer
+          int data_len = msg_end - incoming_buf.get_pos();
+          char* msg_data = new char[data_len];
+          incoming_buf.pull_raw(data_len, msg_data);
+          incoming_buf.cut_message();
+          storage_buf.push_int(msg_type);
+          storage_buf.push_raw(data_len, msg_data);
+          delete [] msg_data;
+          storage_buf.calculate_length();
+          break; }
+        }
+      }
+    }
+  }
+  // append the stored messages to the beginning of the main buffer and
+  // process them
+  if (storage_buf.is_message()) {
+    incoming_buf.push_raw_front(storage_buf.get_len(), storage_buf.get_data());
+    process_all_messages_tc();
+  }
+}
+
 void TTCN_Communication::send_version()
 {
   Text_Buf text_buf;
@@ -1095,6 +1153,26 @@ void TTCN_Communication::send_killed(verdicttype final_verdict,
   send_message(text_buf);
 }
 
+void TTCN_Communication::send_debug_return_value(int return_type, const char* message)
+{
+  Text_Buf text_buf;
+  text_buf.push_int(MSG_DEBUG_RETURN_VALUE);
+  text_buf.push_int(return_type);
+  timeval tv;
+  gettimeofday(&tv, NULL);
+  text_buf.push_int(tv.tv_sec);
+  text_buf.push_int(tv.tv_usec);
+  text_buf.push_string(message);
+  send_message(text_buf);
+}
+
+void TTCN_Communication::send_debug_halt_req()
+{
+  Text_Buf text_buf;
+  text_buf.push_int(MSG_DEBUG_HALT_REQ);
+  send_message(text_buf);
+}
+
 boolean TTCN_Communication::send_log(time_t timestamp_sec, long timestamp_usec,
   unsigned int event_severity, size_t message_text_len,
   const char *message_text)
@@ -1845,6 +1923,27 @@ void TTCN_Communication::process_unsupported_message(int msg_type, int msg_end)
   incoming_buf.cut_message();
 }
 
+void TTCN_Communication::process_debug_command()
+{
+  int command = incoming_buf.pull_int().get_val();
+  int argument_count = incoming_buf.pull_int().get_val();
+  char** arguments = NULL;
+  if (argument_count > 0) {
+    arguments = new char*[argument_count];
+    for (int i = 0; i < argument_count; ++i) {
+      arguments[i] = incoming_buf.pull_string();
+    }
+  }
+  incoming_buf.cut_message();
+  ttcn3_debugger.execute_command(command, argument_count, arguments);
+  if (argument_count > 0) {
+    for (int i = 0; i < argument_count; ++i) {
+      delete [] arguments[i];
+    }
+    delete [] arguments;
+  }
+}
+
 /* * * * Temporary squatting place because it includes version.h * * * */
 
 const struct runtime_version current_runtime_version = {
index 334ddc41382ea4a180709522b98d44656a083250..2d51295e1966d1420fd7f13e8bcb718e559d7291 100644 (file)
@@ -68,6 +68,7 @@ public:
 
   static void process_all_messages_hc();
   static void process_all_messages_tc();
+  static void process_debug_messages();
 
   static void send_version();
   static void send_configure_ack();
@@ -143,7 +144,9 @@ public:
   static void send_stopped_killed(verdicttype final_verdict,
     const char* reason = "");
   static void send_killed(verdicttype final_verdict, const char* reason = "");
-
+  
+  static void send_debug_return_value(int return_type, const char* message);
+  static void send_debug_halt_req();
 
   /** @brief Send a log message to the MC.
 
@@ -214,6 +217,8 @@ private:
 
   static void process_error();
   static void process_unsupported_message(int msg_type, int msg_end);
+  
+  static void process_debug_command();
   /** @} */
 };
 
diff --git a/core/DebugCommands.hh b/core/DebugCommands.hh
new file mode 100644 (file)
index 0000000..e5d569c
--- /dev/null
@@ -0,0 +1,86 @@
+/******************************************************************************
+ * Copyright (c) 2000-2016 Ericsson Telecom AB
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *   
+ *   Baranyi, Botond â€“ initial implementation
+ *
+ ******************************************************************************/
+
+#ifndef DEBUGCOMMANDS_HH
+#define DEBUGCOMMANDS_HH
+
+/** list of commands coming from the user interface to the debugger (parameters listed in comments) */
+
+// settings
+#define D_SWITCH                  1 // 1, "on" or "off"
+#define D_ADD_BREAKPOINT          2 // 2, module name and line number
+#define D_REMOVE_BREAKPOINT       3 // 2, module name and line number
+#define D_SET_ERROR_BEHAVIOR      4 // 1, "yes" or "no"
+#define D_SET_FAIL_BEHAVIOR       5 // 1, "yes" or "no"
+#define D_SET_OUTPUT              6 // 1-2, "console", or "file" or "both" + file name
+// printing and overwriting data
+#define D_SET_COMPONENT           7 // 1, "mtc" or component reference
+#define D_PRINT_CALL_STACK        8 // 0
+#define D_SET_STACK_LEVEL         9 // 1, stack level
+#define D_LIST_VARIABLES         10 // 1-2, "local", "global", "comp" or "all", + optional filter (pattern)
+#define D_PRINT_VARIABLE         11 // 1+, list of variable names
+#define D_OVERWRITE_VARIABLE     12 // 2, variable name, new value (in module parameter syntax)
+#define D_PRINT_SNAPSHOTS        13 // 0
+#define D_SET_SNAPSHOT_BEHAVIOR  14 // TBD
+// stepping
+#define D_STEP_OVER              15 // 0
+#define D_STEP_INTO              16 // 0
+#define D_STEP_OUT               17 // 0
+#define D_RUN_TO_CURSOR          18 // 2, module name and line number
+// the halted state
+#define D_HALT                   19 // 0
+#define D_CONTINUE               20 // 0
+#define D_EXIT                   21 // 1, "test" or "all"
+// batch files
+#define D_BATCH                  22 // 1, batch file name
+#define D_SET_HALTING_BATCH_FILE 23 // 1-2, "no", or "yes" + batch file name
+// initialization
+#define D_SETUP                  24 // 5+, arguments for D_SWITCH, D_SET_OUTPUT, D_ERROR_BEHAVIOR, D_FAIL_BEHAVIOR + any number of D_ADD_BREAKPOINT arguments
+
+#define D_ERROR                   0 // any
+
+/** names of commands in the user interface */
+
+#define D_SWITCH_TEXT "debug"
+#define D_ADD_BREAKPOINT_TEXT "daddbp"
+#define D_REMOVE_BREAKPOINT_TEXT "drembp"
+#define D_SET_ERROR_BEHAVIOR_TEXT "derrcfg"
+#define D_SET_FAIL_BEHAVIOR_TEXT "dfailcfg"
+#define D_SET_OUTPUT_TEXT "doutput"
+#define D_SET_COMPONENT_TEXT "dcomp"
+#define D_PRINT_CALL_STACK_TEXT "dprintstack"
+#define D_SET_STACK_LEVEL_TEXT "dstacklevel"
+#define D_LIST_VARIABLES_TEXT "dlistvar"
+#define D_PRINT_VARIABLE_TEXT "dprintvar"
+#define D_OVERWRITE_VARIABLE_TEXT "dsetvar"
+#define D_PRINT_SNAPSHOTS_TEXT "dprintss"
+#define D_SET_SNAPSHOT_BEHAVIOR_TEXT "dsscfg"
+#define D_STEP_OVER_TEXT "dstepover"
+#define D_STEP_INTO_TEXT "dstepinto"
+#define D_STEP_OUT_TEXT "dstepout"
+#define D_RUN_TO_CURSOR_TEXT "drunto"
+#define D_HALT_TEXT "dhalt"
+#define D_CONTINUE_TEXT "dcont"
+#define D_EXIT_TEXT "dexit"
+#define D_BATCH_TEXT "dbatch"
+#define D_SET_HALTING_BATCH_FILE_TEXT "dbatchcfg"
+
+/** debugger return value types */
+
+#define DRET_NOTIFICATION   0
+#define DRET_SETTING_CHANGE 1
+#define DRET_DATA           2
+#define DRET_EXIT_ALL       3
+
+#endif /* DEBUGCOMMANDS_HH */
+
index 7e86c0c4ee36f94fe27f96c36a6dda33e59ca100..a5331cab477ff40bf6f13f508a70a23dc718a19e 100644 (file)
  ******************************************************************************/
 
 #include "Debugger.hh"
+#include "DebugCommands.hh"
+#include "Communication.hh"
+#include "../common/pattern.hh"
+#include <unistd.h>
+#include <pwd.h>
 
 //////////////////////////////////////////////////////
 ////////////////// TTCN3_Debugger ////////////////////
 
 TTCN3_Debugger ttcn3_debugger;
 
-void TTCN3_Debugger::switch_off()
+void TTCN3_Debugger::switch_state(const char* p_state_str)
 {
-  if (!active) {
-    print("The debugger is already switched off.\n");
-  }
-  else {
-    print("Debugger switched off.\n");
+  if (!strcmp(p_state_str, "on")) {
+    if (active) {
+      print(DRET_NOTIFICATION, "The debugger is already switched on.");
+    }
+    else {
+      active = true;
+      print(DRET_SETTING_CHANGE, "Debugger switched on.");
+    }
   }
-  active = false;
-}
-
-void TTCN3_Debugger::switch_on()
-{
-  if (active) {
-    print("The debugger is already switched on.\n");
+  else if(!strcmp(p_state_str, "off")) {
+    if (!active) {
+      print(DRET_NOTIFICATION, "The debugger is already switched off.");
+    }
+    else {
+      active = false;
+      print(DRET_SETTING_CHANGE, "Debugger switched off.");
+    }
   }
   else {
-    print("Debugger switched on.\n");
+    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'yes' or 'no'.");
   }
-  active = true;
 }
 
 void TTCN3_Debugger::add_breakpoint(const char* p_module, int p_line /*const char* batch_file*/)
@@ -48,10 +56,12 @@ void TTCN3_Debugger::add_breakpoint(const char* p_module, int p_line /*const cha
     bp.module = mcopystr(p_module);
     bp.line = p_line;
     breakpoints.push_back(bp);
-    print("Breakpoint added in module '%s' at line %d.\n", p_module, p_line);
+    print(DRET_SETTING_CHANGE, "Breakpoint added in module '%s' at line %d.",
+      p_module, p_line);
   }
   else {
-    print("Breakpoint already set in module '%s' at line %d.\n", p_module, p_line);
+    print(DRET_NOTIFICATION, "Breakpoint already set in module '%s' at line %d.",
+      p_module, p_line);
   }
 }
 
@@ -61,10 +71,12 @@ void TTCN3_Debugger::remove_breakpoint(const char* p_module, int p_line)
   if (pos != breakpoints.size()) {
     Free(breakpoints[pos].module);
     breakpoints.erase_at(pos);
-    print("Breakpoint removed in module '%s' from line %d.\n", p_module, p_line);
+    print(DRET_SETTING_CHANGE, "Breakpoint removed in module '%s' from line %d.",
+      p_module, p_line);
   }
   else {
-    print("No breakpoint found in module '%s' at line %d.\n", p_module, p_line);
+    print(DRET_NOTIFICATION, "No breakpoint found in module '%s' at line %d.",
+      p_module, p_line);
   }
 }
 
@@ -79,7 +91,7 @@ void TTCN3_Debugger::set_special_breakpoint(special_breakpoint_t p_type, const c
   }
   // else if "batch"
   else {
-    print("Argument 1 is invalid.\n");
+    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'yes' or 'no'.");
     return;
   }
   const char* sbp_type_str;
@@ -99,63 +111,151 @@ void TTCN3_Debugger::set_special_breakpoint(special_breakpoint_t p_type, const c
     // should never happen
     return;
   }
-  print("%s verdict behavior %sset to %s.\n", sbp_type_str,
+  print(DRET_SETTING_CHANGE, "%s verdict behavior %sset to %s.", sbp_type_str,
     state_changed ? "" : "was already ",
-    new_state ? "halt the program" : "do nothing");
+    new_state ? "halt test execution" : "do nothing");
 }
 
 void TTCN3_Debugger::print_call_stack()
 {
   for (size_t i = call_stack.size(); i != 0; --i) {
-    print("%d.\t", (int)call_stack.size() - (int)i + 1);
+    add_to_result("%d.\t", (int)call_stack.size() - (int)i + 1);
     call_stack[i - 1]->print_function();
+    if (i != 1) {
+      add_to_result("\n");
+    }
   }
 }
 
 void TTCN3_Debugger::set_stack_level(int new_level)
 {
-  if (new_level < 0 || (size_t)new_level > call_stack.size()) {
-    print("Invalid new stack level.\n");
+  if (!halted) {
+    print(DRET_NOTIFICATION, "Stack level can only be set if test execution is halted.");
+  }
+  else if (new_level <= 0 || (size_t)new_level > call_stack.size()) {
+    print(DRET_NOTIFICATION, "Invalid new stack level. Expected 1 - %d.",
+      (int)call_stack.size());
   }
   else {
-    stack_level = new_level;
+    stack_level = (int)call_stack.size() - new_level;
+    call_stack[stack_level]->print_function();
+    print(DRET_NOTIFICATION, "Stack level set to:\n%d.\t%s", new_level, command_result);
   }
 }
 
-void TTCN3_Debugger::print_variable(const TTCN3_Debugger::variable_t* p_var) const
+void TTCN3_Debugger::print_variable(const TTCN3_Debugger::variable_t* p_var)
 {
-  print("%s := %s\n", p_var->name, (const char*)p_var->print_function(*p_var));
+  add_to_result("[%s] %s := %s", p_var->type_name, p_var->name,
+    (const char*)p_var->print_function(*p_var));
 }
 
 void TTCN3_Debugger::set_output(const char* p_output_type, const char* p_file_name)
 {
-  FILE* new_fp; 
-  if (!strcmp(p_output_type, "stdout")) {
-    new_fp = stdout;
-  }
-  else if (!strcmp(p_output_type, "stderr")) {
-    new_fp = stderr;
+  FILE* new_fp = NULL;
+  bool file, console;
+  bool same_file = false;
+  char* final_file_name = NULL;
+  // check the command's parameters before actually changing anything
+  if (!strcmp(p_output_type, "console")) {
+    file = false;
+    console = true;
   }
   else if (!strcmp(p_output_type, "file")) {
+    file = true;
+    console = false;
+  }
+  else if (!strcmp(p_output_type, "both")) {
+    file = true;
+    console = true;
+  }
+  else {
+    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'console', 'file' or 'both'.");
+    return;
+  }
+  if (file) {
     if (p_file_name == NULL) {
-      print("Missing output file name.\n");
+      print(DRET_NOTIFICATION, "Argument 2 (output file name) is missing.");
       return;
     }
-    new_fp = fopen(p_file_name, "w");
-    if (new_fp == NULL) {
-      print("Failed to open file '%s' for writing.\n");
-      return;
+    if (output_file_name != NULL && !strcmp(p_file_name, output_file_name)) {
+      // don't reopen it if it's the same file as before
+      same_file = true;
+    }
+    else if (!TTCN_Runtime::is_hc()) {
+        // don't open any files on HCs, just store settings for future PTCs
+      final_file_name = finalize_file_name(p_file_name);
+      new_fp = fopen(final_file_name, "w");
+      if (new_fp == NULL) {
+        print(DRET_NOTIFICATION, "Failed to open file '%s' for writing.", final_file_name);
+        return;
+      }
     }
   }
+  // print the change notification to the old output
+  char* file_str = file ? mprintf("file '%s'", TTCN_Runtime::is_hc() ? p_file_name
+    : final_file_name) : NULL;
+  Free(final_file_name);
+  print(DRET_SETTING_CHANGE, "Debugger set to print its output to %s%s%s.",
+    console ? "the console" : "", (console && file) ? " and to " : "",
+    file ? file_str : "");
+  if (file) {
+    Free(file_str);
+  }
+  if (!same_file && !TTCN_Runtime::is_hc()) {
+    if (output_file != NULL) {
+      fclose(output_file);
+    }
+    output_file = new_fp;
+  }
+  send_to_console = console;
+  Free(output_file_name);
+  if (file) {
+    output_file_name = mcopystr(p_file_name);
+  }
+}
+
+void TTCN3_Debugger::halt()
+{
+  if (!halted) {
+    halted = true;
+    stack_level = call_stack.size() - 1;
+    print(DRET_NOTIFICATION, "Test execution halted.");
+    TTCN_Communication::process_debug_messages();
+  }
   else {
-    print("Argument 1 is invalid.\n");
-    return;
+    print(DRET_NOTIFICATION, "Test execution is already halted.");
+  }
+}
+
+void TTCN3_Debugger::resume()
+{
+  if (halted) {
+    halted = false;
+    stack_level = -1;
+    print(DRET_NOTIFICATION, "Test execution resumed.");
+  }
+  else {
+    print(DRET_NOTIFICATION, "Test execution is not halted.");
   }
-  // don't close the previous file, if the command's parameters are invalid
-  if (output != stdout && output != stderr) {
-    fclose(output);
+}
+
+void TTCN3_Debugger::exit_(const char* p_what)
+{
+  bool exit_all;
+  if (!strcmp(p_what, "test")) {
+    exit_all = false;
+  }
+  else if (!strcmp(p_what, "all")) {
+    exit_all = true;
+  }
+  else {
+    print(DRET_NOTIFICATION, "Argument 1 is invalid. Expected 'test' or 'all'.");
+    return;
   }
-  output = new_fp;
+  halted = false;
+  print((exit_all && TTCN_Runtime::is_mtc()) ? DRET_EXIT_ALL : DRET_NOTIFICATION,
+    "Exiting %s.", exit_all ? "test execution" : "current test");
+  TTCN_Runtime::stop_execution();
 }
 
 size_t TTCN3_Debugger::find_breakpoint(const char* p_module, int p_line) const
@@ -178,22 +278,107 @@ TTCN3_Debugger::variable_t* TTCN3_Debugger::find_variable(const void* p_value) c
   return NULL;
 }
 
+char* TTCN3_Debugger::finalize_file_name(const char* p_file_name_skeleton)
+{
+  if (p_file_name_skeleton == NULL) {
+    return NULL;
+  }
+  size_t len = strlen(p_file_name_skeleton);
+  size_t next_idx = 0;
+  char* ret_val = NULL;
+  for (size_t i = 0; i < len - 1; ++i) {
+    if (p_file_name_skeleton[i] == '%') {
+      ret_val = mputstrn(ret_val, p_file_name_skeleton + next_idx, i - next_idx);
+      switch (p_file_name_skeleton[i + 1]) {
+      case 'e': // %e -> executable name
+        ret_val = mputstr(ret_val, TTCN_Logger::get_executable_name());
+        break;
+      case 'h': // %h -> host name
+        ret_val = mputstr(ret_val, TTCN_Runtime::get_host_name());
+        break;
+      case 'p': // %p -> process ID
+        ret_val = mputprintf(ret_val, "%ld", (long)getpid());
+        break;
+      case 'l': { // %l -> login name
+        setpwent();
+        struct passwd *p = getpwuid(getuid());
+        if (NULL != p) {
+          ret_val = mputstr(ret_val, p->pw_name);
+        }
+        endpwent();
+        break; }
+      case 'r': // %r -> component reference
+        if (TTCN_Runtime::is_single()) {
+          ret_val = mputstr(ret_val, "single");
+        }
+        else if (TTCN_Runtime::is_mtc()) {
+          ret_val = mputstr(ret_val, "mtc");
+        }
+        else if (TTCN_Runtime::is_ptc()) {
+          ret_val = mputprintf(ret_val, "%d", (component)self);
+        }
+        break;
+      case 'n': // %n -> component name
+        if (TTCN_Runtime::is_mtc()) {
+          ret_val = mputstr(ret_val, "MTC");
+        }
+        else if (TTCN_Runtime::is_ptc()) {
+          ret_val = mputstr(ret_val, TTCN_Runtime::get_component_name());
+        }
+        break;
+      case '%': // %% -> single %
+        ret_val = mputc(ret_val, '%');
+        break;
+      default: // unknown sequence -> leave it as it is 
+        ret_val = mputstrn(ret_val, p_file_name_skeleton + i, 2);
+        break;
+      }
+      next_idx = i + 2;
+      ++i;
+    }
+  }
+  if (next_idx < len) {
+    ret_val = mputstr(ret_val, p_file_name_skeleton + next_idx);
+  }
+  return ret_val;
+}
+
+void TTCN3_Debugger::print(int return_type, const char* fmt, ...) const
+{
+  va_list parameters;
+  va_start(parameters, fmt);
+  char* str = mprintf_va_list(fmt, parameters);
+  va_end(parameters);
+  TTCN_Communication::send_debug_return_value(return_type, send_to_console ? str : NULL);
+  if (output_file != NULL) {
+    fprintf(output_file, "%s\n", str);
+    fflush(output_file);
+  }
+  Free(str);
+}
+
 TTCN3_Debugger::TTCN3_Debugger()
 {
+  enabled = false;
   active = false;
-  output = stderr;
+  halted = false;
+  output_file = NULL;
+  output_file_name = NULL;
+  send_to_console = true;
   snapshots = NULL;
   last_breakpoint_entry.module = NULL;
   last_breakpoint_entry.line = 0;
   stack_level = -1;
   fail_behavior = false;
   error_behavior = false;
+  command_result = NULL;
 }
 
 TTCN3_Debugger::~TTCN3_Debugger()
 {
-  if (output != stdout && output != stderr) {
-    fclose(output);
+  if (output_file != NULL) {
+    fclose(output_file);
+    Free(output_file_name);
   }
   for (size_t i = 0; i < breakpoints.size(); ++i) {
     Free(breakpoints[i].module);
@@ -208,6 +393,7 @@ TTCN3_Debugger::~TTCN3_Debugger()
     delete variables[i];
   }
   Free(snapshots);
+  Free(command_result);
 }
 
 TTCN3_Debug_Scope* TTCN3_Debugger::add_global_scope(const char* p_module)
@@ -230,7 +416,7 @@ TTCN3_Debug_Scope* TTCN3_Debugger::add_component_scope(const char* p_component)
 
 void TTCN3_Debugger::set_return_value(const CHARSTRING& p_value)
 {
-  if (active) {
+  if (active && !call_stack.empty()) {
     call_stack[call_stack.size() - 1]->set_return_value(p_value);
   }
 }
@@ -245,51 +431,28 @@ void TTCN3_Debugger::breakpoint_entry(int p_line /*bool p_stepping_helper*/)
     switch (p_line) {
     case SBP_FAIL_VERDICT:
       trigger = fail_behavior;
-      trigger_type = "Fail verdict";
+      trigger_type = "Automatic breakpoint (fail verdict)";
       actual_line = last_breakpoint_entry.line;
       break;
     case SBP_ERROR_VERDICT:
       trigger = error_behavior;
-      trigger_type = "Error verdict";
+      trigger_type = "Automatic breakpoint (error verdict)";
       actual_line = last_breakpoint_entry.line;
       break;
-    default:
-      // code lines
+    default: // code lines
+      // make sure it's not the same breakpoint entry as last time
       trigger = (last_breakpoint_entry.line == 0 || p_line != last_breakpoint_entry.line ||
         module_name != last_breakpoint_entry.module) &&
         find_breakpoint(module_name, p_line) != breakpoints.size();
-      trigger_type = "Breakpoint";
+      trigger_type = "User breakpoint";
       actual_line = p_line;
       break;
     }
-    // make sure it's not the same breakpoint entry as last time
     if (trigger) {
-      stack_level = call_stack.size() - 1;
-      print("%s reached in module '%s' at line %d.\n", trigger_type,
-        module_name, actual_line);
-      ///////////////////////////////////////////////////////////////////////////////////
-      /*print("##################################################\n");
-      print("Call stack:\n");
-      charstring_list params = NULL_VALUE;
-      execute_command(D_PRINT_CALL_STACK, params);
-      print("##################################################\n");
-      print("Variables: ");
-      params[0] = "global";
-      execute_command(D_LIST_VARIABLES, params);
-      params.set_size(0);
-      size_t idx = 0;
-      const TTCN3_Debug_Scope* glob_scope = get_global_scope(module_name);
-      for (size_t i = 0; i < variables.size(); ++i) {
-        if (glob_scope->find_variable(variables[i]->name) != NULL) {
-          params[idx++] = variables[i]->name;
-        }
-      }
-      execute_command(D_PRINT_VARIABLE, params);
-      print("##################################################\n");
-      print("Function call snapshots:\n");
-      params.set_size(0);
-      execute_command(D_PRINT_SNAPSHOTS, params);*/
-      ///////////////////////////////////////////////////////////////////////////////////
+      print(DRET_NOTIFICATION, "%s reached in module '%s' at line %d.",
+        trigger_type, module_name, actual_line);
+      TTCN_Communication::send_debug_halt_req();
+      halt();
     }
     last_breakpoint_entry.module = (char*)module_name;
     last_breakpoint_entry.line = p_line;
@@ -404,13 +567,12 @@ CHARSTRING TTCN3_Debugger::print_base_var(const TTCN3_Debugger::variable_t& p_va
   return TTCN_Logger::end_event_log2str();
 }
 
-void TTCN3_Debugger::print(const char* fmt, ...) const
+void TTCN3_Debugger::add_to_result(const char* fmt, ...)
 {
   va_list parameters;
   va_start(parameters, fmt);
-  vfprintf(output, fmt, parameters);
+  command_result = mputprintf_va_list(command_result, fmt, parameters);
   va_end(parameters);
-  fflush(output);
 }
 
 void TTCN3_Debugger::add_function(TTCN3_Debug_Function* p_function)
@@ -446,7 +608,6 @@ const TTCN3_Debugger::variable_t* TTCN3_Debugger::add_variable(const void* p_val
                                                                const char* p_type,
                                                                CHARSTRING (*p_print_function)(const TTCN3_Debugger::variable_t&))
 {
-   
   if (call_stack.empty()) {
     // no call stack yet, so this is a global or component variable
     variable_t* var = find_variable(p_value);
@@ -496,36 +657,39 @@ const TTCN3_Debug_Scope* TTCN3_Debugger::get_component_scope(const char* p_compo
 
 void TTCN3_Debugger::add_snapshot(const char* p_snapshot)
 {
+  if (snapshots != NULL) {
+    snapshots = mputc(snapshots, '\n');
+  }
   snapshots = mputstr(snapshots, p_snapshot);
 }
 
 #define CHECK_NOF_ARGUMENTS(exp_num) \
-  if (exp_num != p_arguments.size_of()) { \
-    print("Invalid number of arguments. Expected %d, got %d.\n", \
-      (int)exp_num, (int)p_arguments.size_of()); \
+  if (exp_num != p_argument_count) { \
+    print(DRET_NOTIFICATION, "Invalid number of arguments. Expected %d, got %d.", \
+      (int)exp_num, (int)p_argument_count); \
     return; \
   }
 
 #define CHECK_NOF_ARGUMENTS_RANGE(min, max) \
-  if ((int)min > p_arguments.size_of() || (int)max < p_arguments.size_of()) { \
-    print("Invalid number of arguments. Expected at least %d and at most %d, got %d.\n", \
-      (int)min, (int)max, p_arguments.size_of()); \
+  if ((int)min > p_argument_count || (int)max < p_argument_count) { \
+    print(DRET_NOTIFICATION, "Invalid number of arguments. Expected at least %d " \
+      "and at most %d, got %d.", (int)min, (int)max, p_argument_count); \
     return; \
   }
 
 #define CHECK_NOF_ARGUMENTS_MIN(min) \
-  if ((int)min > p_arguments.size_of()) { \
-    print("Invalid number of arguments. Expected at least %d, got %d.\n", \
-      (int)min, p_arguments.size_of()); \
+  if ((int)min > p_argument_count) { \
+    print(DRET_NOTIFICATION, "Invalid number of arguments. Expected at least %d, got %d.", \
+      (int)min, p_argument_count); \
     return; \
   }
 
 #define CHECK_INT_ARGUMENT(arg_idx) \
   { \
-    const char* str = (const char*)p_arguments[arg_idx]; \
-    for (int i = 0; i < p_arguments[arg_idx].lengthof(); ++i) { \
-      if (str[i] < '0' || str[i] > '9') { \
-        print("Argument %d is not an integer.\n", (int)(arg_idx + 1)); \
+    size_t len = strlen(p_arguments[arg_idx]); \
+    for (size_t i = 0; i < len; ++i) { \
+      if (p_arguments[arg_idx][i] < '0' || p_arguments[arg_idx][i] > '9') { \
+        print(DRET_NOTIFICATION, "Argument %d is not an integer.", (int)(arg_idx + 1)); \
         return; \
       } \
     } \
@@ -533,31 +697,30 @@ void TTCN3_Debugger::add_snapshot(const char* p_snapshot)
 
 #define CHECK_CALL_STACK \
   if (call_stack.empty()) { \
-    print("This command can only be executed when the program is running.\n"); \
+    print(DRET_NOTIFICATION, "This command can only be used during test execution."); \
     return; \
   }
 
-void TTCN3_Debugger::execute_command(TTCN3_Debugger::debug_command_t p_command,
-                                     const charstring_list& p_arguments)
+#define STACK_LEVEL (stack_level >= 0) ? (size_t)stack_level : (call_stack.size() - 1)
+
+void TTCN3_Debugger::execute_command(int p_command, int p_argument_count,
+                                     char** p_arguments)
 {
-  if (!active && p_command != D_SWITCH_ON && p_command != D_SWITCH_OFF) {
-    print("Cannot run debug commands while the debugger is switched off.\n");
+  if (!enabled) {
     return;
   }
-  for (int i = 0; i < p_arguments.size_of(); ++i) {
-    if (!p_arguments[i].is_bound()) {
-      print("Argument %d is unbound.\n", i + 1);
+  Free(command_result);
+  command_result = NULL;
+  for (int i = 0; i < p_argument_count; ++i) {
+    if (p_arguments[i] == NULL) {
+      print(DRET_NOTIFICATION, "Argument %d is a null pointer.", i + 1);
       return;
     }
   }
   switch (p_command) {
-  case D_SWITCH_OFF:
-    CHECK_NOF_ARGUMENTS(0)
-    switch_off();
-    break;
-  case D_SWITCH_ON:
-    CHECK_NOF_ARGUMENTS(0)
-    switch_on();
+  case D_SWITCH:
+    CHECK_NOF_ARGUMENTS(1)
+    switch_state(p_arguments[0]);
     break;
   case D_ADD_BREAKPOINT:
     CHECK_NOF_ARGUMENTS(2)
@@ -577,12 +740,14 @@ void TTCN3_Debugger::execute_command(TTCN3_Debugger::debug_command_t p_command,
     CHECK_NOF_ARGUMENTS(1)
     set_special_breakpoint(SBP_FAIL_VERDICT, p_arguments[0]);
     break;
-  // ...
   case D_SET_OUTPUT:
     CHECK_NOF_ARGUMENTS_RANGE(1, 2)
-    set_output(p_arguments[0], (p_arguments.size_of() == 2) ? (const char*)p_arguments[1] : NULL);
+    set_output(p_arguments[0], (p_argument_count == 2) ? p_arguments[1] : NULL);
+    break;
+  case D_SET_COMPONENT:
+    print(DRET_NOTIFICATION, "Command " D_SET_COMPONENT_TEXT " should have been "
+      "sent to the Main Controller.");
     break;
-  // ...
   case D_PRINT_CALL_STACK:
     CHECK_CALL_STACK
     CHECK_NOF_ARGUMENTS(0)
@@ -593,35 +758,89 @@ void TTCN3_Debugger::execute_command(TTCN3_Debugger::debug_command_t p_command,
     CHECK_NOF_ARGUMENTS(1)
     CHECK_INT_ARGUMENT(0)
     set_stack_level(str2int(p_arguments[0]));
-    break;
+    return; // don't print the command result in this case
   case D_LIST_VARIABLES:
     CHECK_CALL_STACK
     CHECK_NOF_ARGUMENTS_RANGE(1, 2)
-    call_stack[stack_level]->list_variables(p_arguments[0],
-      (p_arguments.size_of() == 2) ? (const char*)p_arguments[1] : NULL);
+    call_stack[STACK_LEVEL]->list_variables(p_arguments[0],
+      (p_argument_count == 2) ? p_arguments[1] : NULL);
     break;
   case D_PRINT_VARIABLE:
     CHECK_CALL_STACK
     CHECK_NOF_ARGUMENTS_MIN(1)
-    for (int i = 0; i < p_arguments.size_of(); ++i) {
-      const variable_t* var = call_stack[stack_level]->find_variable(p_arguments[i]);
+    for (int i = 0; i < p_argument_count; ++i) {
+      const variable_t* var = call_stack[STACK_LEVEL]->find_variable(p_arguments[i]);
       if (var != NULL) {
         print_variable(var);
       }
       else {
-        print("Variable '%s' not found.\n", (const char*)p_arguments[i]);
+        add_to_result("Variable '%s' not found.", p_arguments[i]);
+      }
+      if (i != p_argument_count - 1) {
+        add_to_result("\n");
       }
     }
     break;
   // ...
   case D_PRINT_SNAPSHOTS:
     CHECK_NOF_ARGUMENTS(0)
-    print("%s", snapshots);
+    add_to_result("%s", snapshots);
     break;
   // ...
-  default:
-    print("Command not implemented.\n");
+  case D_HALT:
+    if (TTCN_Runtime::is_mtc()) {
+      CHECK_CALL_STACK
+    }
+    CHECK_NOF_ARGUMENTS(0)
+    halt();
+    break;
+  case D_CONTINUE:
+    CHECK_NOF_ARGUMENTS(0)
+    resume();
+    break;
+  case D_EXIT:
+    CHECK_NOF_ARGUMENTS(1)
+    if (TTCN_Runtime::is_mtc()) {
+      CHECK_CALL_STACK
+    }
+    exit_(p_arguments[0]);
+    break;
+  case D_SETUP:
+    CHECK_NOF_ARGUMENTS_MIN(5)
+    if (strlen(p_arguments[0]) > 0) {
+      switch_state(p_arguments[0]);
+    }
+    if (strlen(p_arguments[1]) > 0) {
+      set_output(p_arguments[1], p_arguments[2]);
+    }
+    if (strlen(p_arguments[3]) > 0) {
+      set_special_breakpoint(SBP_ERROR_VERDICT, p_arguments[3]);
+    }
+    if (strlen(p_arguments[4]) > 0) {
+      set_special_breakpoint(SBP_FAIL_VERDICT, p_arguments[4]);
+    }
+    for (int i = 5; i < p_argument_count; i += 2) {
+      add_breakpoint(p_arguments[i], str2int(p_arguments[i + 1]));
+    }
     break;
+  default:
+    print(DRET_NOTIFICATION, "Command not implemented.");
+    return;
+  }
+  if (command_result != NULL) {
+    print(DRET_DATA, command_result);
+  }
+}
+
+void TTCN3_Debugger::open_output_file()
+{
+  if (output_file == NULL && output_file_name != NULL) {
+    char* final_file_name = finalize_file_name(output_file_name);
+    output_file = fopen(final_file_name, "w");
+    if (output_file == NULL) {
+      print(DRET_NOTIFICATION, "Failed to open file '%s' for writing.", final_file_name);
+    }
+    Free(final_file_name);
   }
 }
 
@@ -663,12 +882,14 @@ const TTCN3_Debugger::variable_t* TTCN3_Debug_Scope::find_variable(const char* p
   return NULL;
 }
 
-void TTCN3_Debug_Scope::list_variables(const char* p_filter, bool& p_first) const
+void TTCN3_Debug_Scope::list_variables(regex_t* p_posix_regexp, bool& p_first) const
 {
   for (size_t i = 0; i < variables.size(); ++i) {
-    // the filter is currently ignored
-    ttcn3_debugger.print("%s%s", p_first ? "" : " ", variables[i]->name);
-    p_first = false;
+    if (p_posix_regexp == NULL ||
+        regexec(p_posix_regexp, variables[i]->name, 0, NULL, 0) == 0) {
+      ttcn3_debugger.add_to_result("%s%s", p_first ? "" : " ", variables[i]->name);
+      p_first = false;
+    }
   }
 }
 
@@ -719,7 +940,6 @@ TTCN3_Debug_Function::~TTCN3_Debug_Function()
     if (return_value.is_bound()) {
       snapshot = mputprintf(snapshot, " returned %s", (const char*)return_value);
     }
-    snapshot = mputc(snapshot, '\n');
     ttcn3_debugger.add_snapshot(snapshot);
     Free(snapshot);
   }
@@ -773,7 +993,7 @@ void TTCN3_Debug_Function::initial_snapshot() const
         }
       }
     }
-    snapshot = mputstr(snapshot, ")\n");
+    snapshot = mputstr(snapshot, ")");
     ttcn3_debugger.add_snapshot(snapshot);
     Free(snapshot);
   }
@@ -786,7 +1006,7 @@ void TTCN3_Debug_Function::add_scope(TTCN3_Debug_Scope* p_scope)
 
 void TTCN3_Debug_Function::remove_scope(TTCN3_Debug_Scope* p_scope)
 {
-  if (scopes[scopes.size() - 1] == p_scope) {
+  if (!scopes.empty() && scopes[scopes.size() - 1] == p_scope) {
     scopes.erase_at(scopes.size() - 1);
   }
 }
@@ -821,18 +1041,18 @@ const TTCN3_Debugger::variable_t* TTCN3_Debug_Function::find_variable(const char
 
 void TTCN3_Debug_Function::print_function() const
 {
-  ttcn3_debugger.print("[%s]\t%s(", function_type, function_name);
+  ttcn3_debugger.add_to_result("[%s]\t%s(", function_type, function_name);
   if (parameter_names->size_of() > 0) {
     for (int i = 0; i < parameter_names->size_of(); ++i) {
       if (i > 0) {
-        ttcn3_debugger.print(", ");
+        ttcn3_debugger.add_to_result(", ");
       }
       const TTCN3_Debugger::variable_t* parameter = find_variable((*parameter_names)[i]);
-      ttcn3_debugger.print("[%s] %s := %s", (const char*)(*parameter_types)[i],
+      ttcn3_debugger.add_to_result("[%s] %s := %s", (const char*)(*parameter_types)[i],
         (const char*)(*parameter_names)[i], (const char*)parameter->print_function(*parameter));
     }
   }
-  ttcn3_debugger.print(")\n");
+  ttcn3_debugger.add_to_result(")");
 }
 
 void TTCN3_Debug_Function::list_variables(const char* p_scope, const char* p_filter) const
@@ -847,32 +1067,61 @@ void TTCN3_Debug_Function::list_variables(const char* p_scope, const char* p_fil
   else if (!strcmp(p_scope, "global")) {
     list_global = true;
   }
-  else if (!strcmp(p_scope, "comp") || !strcmp(p_scope, "component")) {
+  else if (!strcmp(p_scope, "comp")) {
     list_comp = true;
   }
-  else {
-    if (strcmp(p_scope, "all")) {
-      ttcn3_debugger.print("Invalid scope. Listing variables in all scopes.\n");
-    }
+  else if (!strcmp(p_scope, "all")) {
     list_local = true;
     list_global = true;
     list_comp = true;
   }
+  else {
+    ttcn3_debugger.print(DRET_NOTIFICATION, "Argument 1 is invalid. "
+      "Expected 'local', 'global', 'comp' or 'all'.");
+    return;
+  }
+  regex_t* posix_regexp = NULL;
+  if (p_filter != NULL) {
+    char* posix_str = TTCN_pattern_to_regexp(p_filter);
+    if (posix_str == NULL) {
+      ttcn3_debugger.print(DRET_NOTIFICATION, "Argument 2 is invalid. "
+        "Expected a valid TTCN-3 character pattern.");
+      return;
+    }
+    posix_regexp = new regex_t;
+    int ret_val = regcomp(posix_regexp, posix_str, REG_EXTENDED | REG_NOSUB);
+    Free(posix_str);
+    if (ret_val != 0) {
+      char msg[512];
+      regerror(ret_val, posix_regexp, msg, sizeof(msg));
+      regfree(posix_regexp);
+      delete posix_regexp;
+      ttcn3_debugger.print(DRET_NOTIFICATION, "Compilation of POSIX regular "
+        "expression failed.");
+      return;
+    }
+  }
   if (list_local) {
     for (size_t i = 0; i < variables.size(); ++i) {
-      ttcn3_debugger.print("%s%s", first ? "" : " ", variables[i]->name);
-      first = false;
+      if (posix_regexp == NULL ||
+          regexec(posix_regexp, variables[i]->name, 0, NULL, 0) == 0) {
+        ttcn3_debugger.add_to_result("%s%s", first ? "" : " ", variables[i]->name);
+        first = false;
+      }
     }
   }
   if (list_global && global_scope != NULL && global_scope->has_variables()) {
-    global_scope->list_variables(p_filter, first);
+    global_scope->list_variables(posix_regexp, first);
   }
   if (list_comp && component_scope != NULL && component_scope->has_variables()) {
-    component_scope->list_variables(p_filter, first);
+    component_scope->list_variables(posix_regexp, first);
   }
   if (first) {
-    ttcn3_debugger.print("No variables found.");
+    ttcn3_debugger.print(DRET_NOTIFICATION, "No variables found.");
+  }
+  if (posix_regexp != NULL) {
+    regfree(posix_regexp);
+    delete posix_regexp;
   }
-  ttcn3_debugger.print("\n");
 }
 
index 42271c68fa72dfeb0b17c69505a9622d116c71a3..370c0344919e7f6b2c1c3c01adf980463427fd35 100644 (file)
@@ -22,6 +22,7 @@
 #else
 #include "RT1/PreGenRecordOf.hh"
 #endif
+#include <regex.h>
 
 /** alias for record of charstring */
 typedef PreGenRecordOf::PREGEN__RECORD__OF__CHARSTRING charstring_list;
@@ -58,7 +59,7 @@ public:
     * component scopes */
   struct named_scope_t {
     /** scope name (module name for global scopes, or component type name for
-      * component scopes), not owned*/
+      * component scopes), not owned */
     const char* name;
     /** scope pointer, owned */
     TTCN3_Debug_Scope* scope;
@@ -73,38 +74,6 @@ public:
     // const char* batch_file;
   };
   
-  /** list of commands coming from the user interface (parameters listed in comments) */
-  enum debug_command_t {
-    // on/off switch
-    D_SWITCH_OFF, // 0
-    D_SWITCH_ON, // 0
-    // breakpoints
-    D_ADD_BREAKPOINT, // 2, module name and line number
-    D_REMOVE_BREAKPOINT, // 2, module name and line number
-    D_SET_ERROR_BEHAVIOR, // 1, "yes" or "no"
-    D_SET_FAIL_BEHAVIOR, // 1, "yes" or "no"
-    // printing and overwriting data
-    D_SET_OUTPUT, // 1-2, "stdout", "stderr" or "file" + file name
-    D_SET_PROCESS, // 1, 'mtc' or component reference
-    D_PRINT_CALL_STACK, // 0
-    D_SET_STACK_LEVEL, // 1, stack level
-    D_LIST_VARIABLES, // 1-2, "local", "global", "comp", "component" or "all", + optional filter (pattern)
-    D_PRINT_VARIABLE, // 1+, list of variable names
-    D_OVERWRITE_VARIABLE, // 2, variable name, new value (in module parameter syntax)
-    D_PRINT_SNAPSHOTS, // 0
-    D_SET_SNAPSHOT_BEHAVIOR, // TBD
-    // stepping
-    D_STEP_OVER, // 0
-    D_STEP_INTO, // 0
-    D_STEP_OUT, // 0
-    D_RUN_TO_CURSOR, // 2, module name and line number
-    // ending the halted state
-    D_CONTINUE, // 0
-    D_EXIT, // 0
-    // batch files
-    D_SET_HALTING_BATCH_FILE // TBD
-  };
-  
   /** special breakpoint types, passed to breakpoint_entry() as the line parameter,
     * so these need to be 0 or negative, to never conflict with any line number */
   enum special_breakpoint_t {
@@ -116,11 +85,27 @@ public:
   
 private:
   
-  /** the debugger's on/off switch */
+  /** indicates whether the debugger has been activated, meaning that the debugger's
+    * command line option (-n) was used during the build (switched automatically
+    * by generated code) */
+  bool enabled;
+  
+  /** the debugger's on/off switch (switched by the user) */
   bool active;
   
-  /** the debugger's output file handler */
-  FILE* output;
+  /** true if test execution has been halted (by a breakpoint or by the user) */
+  bool halted;
+  
+  /** the debugger's output file handler (NULL if the debugger's output is only
+    * sent to the console) */
+  FILE* output_file;
+  
+  /** name of the debugger's output file (NULL if the debugger's output is only
+    * sent to the console) */
+  char* output_file_name;
+  
+  /** indicates whether the debugger's output should be sent to the console */
+  bool send_to_console;
   
   /** list of all global and component variables, elements are owned */
   Vector<variable_t*> variables;
@@ -144,7 +129,7 @@ private:
   /** stores the last line hit by breakpoint_entry() */
   breakpoint_t last_breakpoint_entry;
   
-  /** current stack level (reset whenever a breakpoint is reached) */
+  /** current stack level (reset when test execution is halted or resumed) */
   int stack_level;
   
   /** behavior triggered by setting the local verdict to FAIL
@@ -155,17 +140,16 @@ private:
     * (a breakpoint is activated if set to true) */
   bool error_behavior;
   
+  /** result of the last executed or currently executing command */
+  char* command_result;
+  
   //////////////////////////////////////////////////////
   ///////////////// internal functions /////////////////
   //////////////////////////////////////////////////////
   
-  /** switches the debugger off
-    * handles the D_SWITCH_OFF command */
-  void switch_off();
-  
-  /** switches the debugger on 
-    * handles the D_SWITCH_ON command */
-  void switch_on();
+  /** switches the debugger on or off
+    * handles the D_SWITCH command */
+  void switch_state(const char* p_state_str);
   
   /** adds a new breakpoint at the specified module and line 
     * handles the D_ADD_BREAKPOINT command */
@@ -185,26 +169,42 @@ private:
     * handles the D_PRINT_CALL_STACK command */
   void print_call_stack();
   
-  /** sets the current stack level to the specified level
+  /** sets the current stack level to the specified value
     * handles the D_SET_STACK_LEVEL command */
   void set_stack_level(int new_level);
   
   /** prints the specified variable
     * handles (one parameter of) the D_PRINT_VARIABLE command */
-  void print_variable(const variable_t* p_var) const;
+  void print_variable(const variable_t* p_var);
   
-  /** sets the debugger's output to a different stream 
+  /** sets the debugger's output to the console and/or a text file
     * handles the D_SET_OUTPUT command
-    * @param p_output_type "stdout", "stderr" or "file"
-    * @param p_file_name output file name, if the output is a file, or NULL */
+    * @param p_output_type "console", "file" or "both"
+    * @param p_file_name output file name or NULL */
   void set_output(const char* p_output_type, const char* p_file_name);
   
+  /** halts test execution, processing only debug commands
+    * handles the D_HALT command */
+  void halt();
+  
+  /** resumes the halted test execution
+    * handles the D_CONTINUE command */
+  void resume();
+  
+  /** exits the current test or the execution of all tests
+    * handles the D_EXIT command */
+  void exit_(const char* p_what);
+  
   /** returns the index of the specified breakpoint, if found,
     * otherwise returns breakpoints.size() */
   size_t find_breakpoint(const char* p_module, int p_line) const;
   
   /** returns the specified variable, if found, otherwise returns NULL */
   TTCN3_Debugger::variable_t* find_variable(const void* p_value) const;
+  
+  /** handles metacharacters in the specified file name skeleton
+    * @return final file name (must be freed by caller) */
+  static char* finalize_file_name(const char* p_file_name_skeleton);
 
 public:
   /** constructor - called once per process (at the beginning) */
@@ -217,10 +217,15 @@ public:
   ////// methods called from TITAN generated code //////
   //////////////////////////////////////////////////////
   
-  /** creates, stores and returns a new global scope for the specified module */
+  /** activates the debugger */
+  void activate() { enabled = true; }
+  
+  /** creates, stores and returns a new global scope for the specified module
+    * (this scope contains all global variables visible in the module) */
   TTCN3_Debug_Scope* add_global_scope(const char* p_module);
   
-  /** creates, stores and returns a new global scope for the specified module */
+  /** creates, stores and returns a new component scope for the specified component
+    * type (this scope contains all variables declared in the component type) */
   TTCN3_Debug_Scope* add_component_scope(const char* p_component);
   
   /** stores the string representation of the current function's return value
@@ -288,11 +293,21 @@ public:
   ////// methods called by other debugger classes //////
   //////////////////////////////////////////////////////
   
+  /** returns true if the debugger is activated (through the compiler switch) */
+  bool is_activated() const { return enabled; }
+  
   /** returns true if the debugger is switched on */
   bool is_on() const { return active; }
   
-  /** prints formatted string to the debugger's output stream */
-  void print(const char* fmt, ...) const;
+  /** returns true if test execution has been halted by the debugger */
+  bool is_halted() const { return halted; }
+  
+  /** prints the formatted string to the console and/or output file
+    * (used for printing notifications or error messages) */
+  void print(int return_type, const char* fmt, ...) const;
+  
+  /** adds the formatted string to the currently executed command's result string */
+  void add_to_result(const char* fmt, ...);
   
   /** adds the specified function object pointer to the call stack
     * (only if the debugger is switched on) */
@@ -315,7 +330,7 @@ public:
     * if the call stack is empty, an entry for a global or component variable is
     * created and stored in the main debugger object (if it doesn't already exist);
     * if the call stack is not empty (and if the debugger is switched on), the 
-    * variable entry for a local variable is created and stored by the current function*/
+    * variable entry for a local variable is created and stored by the current function */
   const variable_t* add_variable(const void* p_value, const char* p_name, const char* p_type,
     CHARSTRING (*p_print_function)(const variable_t&));
   
@@ -333,7 +348,11 @@ public:
   void add_snapshot(const char* p_snapshot);
   
   /** executes a command received from the user interface */
-  void execute_command(debug_command_t p_command, const charstring_list& p_arguments);
+  void execute_command(int p_command, int p_argument_count, char** p_arguments);
+  
+  /** opens the debugger's output file for writing (if one has been set, but not
+    * opened, in the HC process) */
+  void open_output_file();
 };
 
 /** the main debugger object */
@@ -390,10 +409,10 @@ public:
   /** returns the specified variable, if found, otherwise returns NULL */
   const TTCN3_Debugger::variable_t* find_variable(const char* p_name) const;
   
-  /** prints the names of variables in this scope that match the specified pattern
-    * @param p_filter the mentioned pattern
+  /** prints the names of variables in this scope that match the specified
+    * @param p_posix_regexp the pattern converted into a POSIX regex structure
     * @param p_first true if no variables have been printed yet */
-  void list_variables(const char* p_filter, bool& p_first) const;
+  void list_variables(regex_t* p_posix_regexp, bool& p_first) const;
 };
 
 
@@ -472,7 +491,7 @@ public:
   /** stores the string representation of the value returned by the function */
   void set_return_value(const CHARSTRING& p_value);
   
-  /** saves the function's initial snapshot (including the values on 'in' and
+  /** saves the function's initial snapshot (including the values of 'in' and
     * 'inout' parameters) in the main debugger object
     * (only if the debugger is switched on) */
   void initial_snapshot() const;
index d4fcd8446269a6787a27991ddff8313ad41983c6..a01209d85d9a7901a84cd8033546efa06866a45b 100644 (file)
@@ -186,7 +186,7 @@ Port.hh Event_Handler.hh Struct_of.hh Array.hh Optional.hh Textbuf.hh Encdec.hh
 Module_list.hh Parameters.h Addfunc.hh RAW.hh BER.hh TEXT.hh ASN_Null.hh \
 ASN_Any.hh ASN_External.hh ASN_EmbeddedPDV.hh ASN_CharacterString.hh XER.hh \
 XmlReader.hh cversion.h TitanLoggerControl.ttcn TitanLoggerApi.xsd Vector.hh \
-JSON.hh Profiler.hh RefdIndex.hh ProfilerTools.hh Debugger.hh
+JSON.hh Profiler.hh RefdIndex.hh ProfilerTools.hh Debugger.hh DebugCommands.hh
 # Copied during "make install"
 
 ifdef REGEX_DIR
index d23739270c072e256bcb32232274d4796a64d5b4..b47fa1526e0271892ee745121342f107f3a4240d 100644 (file)
 #define MSG_MAPPED             18
 #define MSG_UNMAP_REQ          19
 #define MSG_UNMAPPED           20
+#define MSG_DEBUG_HALT_REQ     101
 
 /* Messages from MTC to MC (up) */
 
 #define MSG_STOPPED_KILLED     22
 #define MSG_KILLED             23
 
+/* Messages from MC to HC or TC (down) */
+
+#define MSG_DEBUG_COMMAND      100
+
+/* Messages from HC or TC to MC (up) */
+
+#define MSG_DEBUG_RETURN_VALUE 100
+
 #endif
index 27ca3b3b28c021d65d08dec2ca960062654a0d1e..625004e51026862447a3f43257fb10dc91c14aa8 100644 (file)
@@ -502,6 +502,9 @@ int TTCN_Runtime::ptc_main()
       ret_val = EXIT_FAILURE;
     }
     if (ret_val == EXIT_SUCCESS) {
+      if (ttcn3_debugger.is_activated()) {
+        ttcn3_debugger.open_output_file();
+      }
       try {
         do {
           TTCN_Snapshot::take_new(TRUE);
index 53a3cd94589350a9a0220635c6899ad77d17f2bc..b7d9e37139e3540a3070a22674adad7912f907ce 100644 (file)
@@ -291,6 +291,18 @@ void Text_Buf::push_raw(int len, const void *data)
   buf_len += len;
 }
 
+void Text_Buf::push_raw_front(int len, const void* data)
+{
+  if (len < 0) TTCN_error("Text encoder: Encoding raw data with negative "
+                          "length (%d).", len);
+  Reallocate(buf_len + len);
+  for (int i = buf_len - 1; i >= 0; --i) {
+    ((char*)data_ptr)[buf_begin + len + i] = ((char*)data_ptr)[buf_begin + i];
+  }
+  memcpy((char*)data_ptr + buf_begin, data, len);
+  buf_len += len;
+}
+
 /** Extract a fixed number of bytes from the buffer.
  *
  * @param len number of bytes to read
index 29d27c8abc090ca5ddd8196b23c2189bdc46fd7e..05f3901d1ecf9d66143519d730ea228f427117ad 100644 (file)
@@ -56,6 +56,7 @@ public:
   double pull_double();
 
   void push_raw(int len, const void *data);
+  void push_raw_front(int len, const void *data);
   void pull_raw(int len, void *data);
 
   void push_string(const char *string_ptr);
index be4de36e009cc4753ab851eba1c1c87d3bc93826..a217c39d0f6756c892137a0c2f6680c3e666a618 100644 (file)
@@ -40,6 +40,7 @@
 #include "../../common/version_internal.h"
 #include "../../common/memory.h"
 #include "../../common/config_preproc.h"
+#include "../../core/DebugCommands.hh"
 
 #define PROMPT "MC2> "
 #define CMTC_TEXT "cmtc"
@@ -102,6 +103,72 @@ static const Command command_list[] = {
   { NULL, NULL, NULL, NULL }
 };
 
+struct DebugCommand {
+  const char *name;
+  int commandID;
+  const char *synopsis;
+  const char *description;
+};
+
+static const DebugCommand debug_command_list[] = {
+  { D_SWITCH_TEXT, D_SWITCH, D_SWITCH_TEXT " on|off",
+    "Switch the debugger on or off." },
+  { D_ADD_BREAKPOINT_TEXT, D_ADD_BREAKPOINT,
+    D_ADD_BREAKPOINT_TEXT " <module> <line>",
+    "Add breakpoint at specified location." },
+  { D_REMOVE_BREAKPOINT_TEXT, D_REMOVE_BREAKPOINT,
+    D_REMOVE_BREAKPOINT_TEXT " <module> <line>",
+    "Remove breakpoint from specified location." },
+  { D_SET_ERROR_BEHAVIOR_TEXT, D_SET_ERROR_BEHAVIOR,
+    D_SET_ERROR_BEHAVIOR_TEXT " yes|no",
+    "Set whether to halt test execution when component verdict is set to 'error'." },
+  { D_SET_FAIL_BEHAVIOR_TEXT, D_SET_FAIL_BEHAVIOR,
+    D_SET_FAIL_BEHAVIOR_TEXT " yes|no",
+    "Set whether to halt test execution when component verdict is set to 'fail'." },
+  { D_SET_OUTPUT_TEXT, D_SET_OUTPUT,
+    D_SET_OUTPUT_TEXT " console|file|both [file_name]",
+    "Set the output of the debugger." },
+  { D_SET_COMPONENT_TEXT, D_SET_COMPONENT,
+    D_SET_COMPONENT_TEXT " mtc|<component_reference>",
+    "Set the test component to print debug information from." },
+  { D_PRINT_CALL_STACK_TEXT, D_PRINT_CALL_STACK, D_PRINT_CALL_STACK_TEXT,
+    "Print call stack." },
+  { D_SET_STACK_LEVEL_TEXT, D_SET_STACK_LEVEL, D_SET_STACK_LEVEL_TEXT " <level>",
+    "Set the stack level to print debug information from." },
+  { D_LIST_VARIABLES_TEXT, D_LIST_VARIABLES,
+    D_LIST_VARIABLES_TEXT " local|global|comp|all [pattern]",
+    "List variable names." },
+  { D_PRINT_VARIABLE_TEXT, D_PRINT_VARIABLE,
+    D_PRINT_VARIABLE_TEXT " <variable_name>[{ <variable_name>}]",
+    "Print current value of one or more variables." },
+  { D_OVERWRITE_VARIABLE_TEXT, D_OVERWRITE_VARIABLE,
+    D_OVERWRITE_VARIABLE_TEXT " <variable_name> <value>",
+    "Overwrite the current value of a variable." },
+  { D_PRINT_SNAPSHOTS_TEXT, D_PRINT_SNAPSHOTS, D_PRINT_SNAPSHOTS_TEXT,
+    "Print snapshots of function calls until this point." },
+  // D_SET_SNAPSHOT_BEHAVIOR_TEXT
+  { D_STEP_OVER_TEXT, D_STEP_OVER, D_STEP_OVER_TEXT,
+    "Resume test execution until the next line of code (in this function or the "
+    "caller function)." },
+  { D_STEP_INTO_TEXT, D_STEP_INTO, D_STEP_INTO_TEXT,
+    "Resume test execution until the next line of code (on any stack level)." },
+  { D_STEP_OUT_TEXT, D_STEP_OUT, D_STEP_OUT_TEXT,
+    "Resume test execution until the next line of code in the caller function." },
+  { D_RUN_TO_CURSOR_TEXT, D_RUN_TO_CURSOR, D_RUN_TO_CURSOR_TEXT " <module> <line>",
+    "Resume test execution until the specified location." },
+  { D_HALT_TEXT, D_HALT, D_HALT_TEXT, "Halt test execution." },
+  { D_CONTINUE_TEXT, D_CONTINUE, D_CONTINUE_TEXT, "Resume halted test execution." },
+  { D_EXIT_TEXT, D_EXIT, D_EXIT_TEXT " test|all",
+    "Exit the current test or the execution of all tests." },
+  { D_BATCH_TEXT, D_BATCH, D_BATCH_TEXT " <batch_file_name>",
+    "Run commands from batch file." },
+  { D_SET_HALTING_BATCH_FILE_TEXT, D_SET_HALTING_BATCH_FILE,
+    D_SET_HALTING_BATCH_FILE_TEXT " yes|no [batch_file_name]",
+    "Set whether a batch file should be executed automatically when test execution "
+    "is halted by the debugger." },
+  { NULL, D_ERROR, NULL, NULL }
+};
+
 Cli::Cli()
 {
   loggingEnabled = TRUE;
@@ -395,7 +462,7 @@ int Cli::batchMode()
 void Cli::processCommand(char *line_read)
 {
   for (const Command *command = command_list; command->name != NULL;
-    command++) {
+       command++) {
     size_t command_name_len = strlen(command->name);
     if (!strncmp(line_read, command->name, command_name_len)) {
       memset(line_read, ' ', command_name_len);
@@ -404,6 +471,16 @@ void Cli::processCommand(char *line_read)
       return;
     }
   }
+  for (const DebugCommand* command = debug_command_list; command->name != NULL;
+       command++) {
+    size_t command_name_len = strlen(command->name);
+    if (!strncmp(line_read, command->name, command_name_len)) {
+      memset(line_read, ' ', command_name_len);
+      stripLWS(line_read);
+      MainController::debug_command(command->commandID, line_read);
+      return;
+    }
+  }
   puts("Unknown command, try again...");
 }
 
@@ -656,6 +733,10 @@ void Cli::helpCallback(const char *arguments)
       command->name != NULL; command++) {
       printf(" %s", command->name);
     }
+    for (const DebugCommand *command = debug_command_list;
+         command->name != NULL; command++) {
+      printf(" %s", command->name);
+    }
     putchar('\n');
   } else {
     for (const Command *command = command_list;
@@ -668,6 +749,16 @@ void Cli::helpCallback(const char *arguments)
         return;
       }
     }
+    for (const DebugCommand *command = debug_command_list;
+         command->name != NULL; command++) {
+      if (!strncmp(arguments, command->name,
+        strlen(command->name))) {
+        printf("%s usage: %s\n%s\n", command->name,
+          command->synopsis,
+          command->description);
+        return;
+      }
+    }
     printf("No help for %s.\n", arguments);
   }
 }
@@ -719,6 +810,7 @@ void Cli::exitCallback(const char *arguments)
 char *Cli::completeCommand(const char *prefix, int state)
 {
   static int command_index;
+  static int debug_command_index;
   static size_t prefix_len;
   const char *command_name;
 
@@ -727,6 +819,7 @@ char *Cli::completeCommand(const char *prefix, int state)
 
   if(state == 0) {
     command_index = 0;
+    debug_command_index = 0;
     prefix_len = strlen(prefix);
   }
 
@@ -737,6 +830,14 @@ char *Cli::completeCommand(const char *prefix, int state)
       return strdup(command_name);
     }
   }
+  
+  while ((command_name = debug_command_list[debug_command_index].name)) {
+    ++debug_command_index;
+    if (strncmp(prefix, command_name, prefix_len) == 0) {
+      // Must allocate buffer for returned string (readline frees it)
+      return strdup(command_name);
+    }
+  }
   // No match found
   return NULL;
 }
index 6442b6abecf017c42326e1b04450e8893f5e781c..f4258e16fbfb8cd3fe59a65bd5d02d1144c80c22 100644 (file)
@@ -40,6 +40,7 @@
 #include "../../core/Error.hh"
 #include "../../core/Textbuf.hh"
 #include "../../core/Logger.hh"
+#include "DebugCommands.hh"
 
 #include <stdio.h>
 #include <string.h>
@@ -87,6 +88,9 @@ int MainController::server_fd;
 int MainController::server_fd_unix = -1;
 boolean MainController::server_fd_disabled;
 
+debugger_settings_struct MainController::debugger_settings;
+debug_command_struct MainController::last_debug_command;
+
 void MainController::disable_server_fd()
 {
   if (!server_fd_disabled) {
@@ -683,6 +687,7 @@ void MainController::configure_host(host_struct *host, boolean should_notify)
         host->hostname);
     }
     send_configure(host, config_str);
+    send_debug_setup(host);
   }
 }
 
@@ -843,6 +848,7 @@ int MainController::n_components, MainController::n_active_ptcs,
     MainController::max_ptcs;
 component_struct **MainController::components;
 component_struct *MainController::mtc, *MainController::system;
+const component_struct* MainController::debugger_active_tc;
 component MainController::next_comp_ref, MainController::tc_first_comp_ref;
 
 boolean MainController::any_component_done_requested,
@@ -2726,6 +2732,9 @@ void MainController::handle_hc_data(host_struct *hc, boolean recv_from_socket)
         case MSG_HC_READY:
           process_hc_ready(hc);
           break;
+        case MSG_DEBUG_RETURN_VALUE:
+          process_debug_return_value(*hc->text_buf, hc->log_source, false);
+          break;
         default:
           error("Invalid message type (%d) was received on HC "
             "connection from %s [%s].", message_type,
@@ -2864,6 +2873,12 @@ void MainController::handle_tc_data(component_struct *tc,
         case MSG_UNMAPPED:
           process_unmapped(tc);
           break;
+        case MSG_DEBUG_RETURN_VALUE:
+          process_debug_return_value(*tc->text_buf, tc->log_source, tc == mtc);
+          break;
+        case MSG_DEBUG_HALT_REQ:
+          process_debug_halt_req(tc);
+          break;
         default:
           if (tc == mtc) {
             // these messages can be received only from the MTC
@@ -3074,6 +3089,26 @@ void MainController::clean_up()
   hosts = NULL;
   Free(config_str);
   config_str = NULL;
+  
+  Free(debugger_settings.on_switch);
+  debugger_settings.on_switch = NULL;
+  Free(debugger_settings.output_type);
+  debugger_settings.output_type = NULL;
+  Free(debugger_settings.output_file);
+  debugger_settings.output_file = NULL;
+  Free(debugger_settings.error_behavior);
+  debugger_settings.error_behavior = NULL;
+  Free(debugger_settings.fail_behavior);
+  debugger_settings.fail_behavior = NULL;
+  for (int i = 0; i < debugger_settings.nof_breakpoints; ++i) {
+    Free(debugger_settings.breakpoints[i].module);
+    Free(debugger_settings.breakpoints[i].line);
+  }
+  debugger_settings.nof_breakpoints = 0;
+  Free(debugger_settings.breakpoints);
+  debugger_settings.breakpoints = NULL;
+  Free(last_debug_command.arguments);
+  last_debug_command.arguments = NULL;
 
   while (timer_head != NULL) cancel_timer(timer_head);
 
@@ -3328,6 +3363,68 @@ void MainController::send_unmap_ack(component_struct *tc)
   send_message(tc->tc_fd, text_buf);
 }
 
+static void get_next_argument_loc(const char* arguments, size_t len, size_t& start, size_t& end)
+{
+  while (start < len && isspace(arguments[start])) {
+    ++start;
+  }
+  end = start;
+  while (end < len && !isspace(arguments[end])) {
+    ++end;
+  }
+}
+
+void MainController::send_debug_command(int fd, int commandID, const char* arguments)
+{
+  Text_Buf text_buf;
+  text_buf.push_int(MSG_DEBUG_COMMAND);
+  text_buf.push_int(commandID);
+  
+  size_t arg_len = strlen(arguments);
+  int arg_count = 0;
+  for (size_t i = 0; i < arg_len; ++i) {
+    if (isspace(arguments[i]) && (i == 0 || !isspace(arguments[i - 1]))) {
+      ++arg_count;
+    }
+  }
+  if (arg_len > 0) {
+    ++arg_count;
+  }
+  text_buf.push_int(arg_count);
+  
+  if (arg_count > 0) {
+    size_t start = 0;
+    size_t end = 0;
+    while (start < arg_len) {
+      get_next_argument_loc(arguments, arg_len, start, end);
+      // don't use push_string, as that requires a null-terminated string
+      text_buf.push_int(end - start);
+      text_buf.push_raw(end - start, arguments + start);
+      start = end;
+    }
+  }
+  
+  send_message(fd, text_buf);
+}
+
+void MainController::send_debug_setup(host_struct *hc)
+{
+  Text_Buf text_buf;
+  text_buf.push_int(MSG_DEBUG_COMMAND);
+  text_buf.push_int(D_SETUP);
+  text_buf.push_int(5 + 2 * debugger_settings.nof_breakpoints);
+  text_buf.push_string(debugger_settings.on_switch);
+  text_buf.push_string(debugger_settings.output_file);
+  text_buf.push_string(debugger_settings.output_type);
+  text_buf.push_string(debugger_settings.error_behavior);
+  text_buf.push_string(debugger_settings.fail_behavior);
+  for (int i = 0; i < debugger_settings.nof_breakpoints; ++i) {
+    text_buf.push_string(debugger_settings.breakpoints[i].module);
+    text_buf.push_string(debugger_settings.breakpoints[i].line);
+  }
+  send_message(hc->hc_fd, text_buf);
+}
+
 void MainController::send_cancel_done_mtc(component component_reference,
   boolean cancel_any)
 {
@@ -5318,6 +5415,123 @@ void MainController::process_unmapped(component_struct *tc)
   status_change();
 }
 
+void MainController::process_debug_return_value(Text_Buf& text_buf, char* log_source, bool from_mtc)
+{
+  int return_type = text_buf.pull_int().get_val();
+  timeval tv;
+  tv.tv_sec = text_buf.pull_int().get_val();
+  tv.tv_usec = text_buf.pull_int().get_val();
+  char* message = text_buf.pull_string();
+  if (return_type == DRET_DATA) {
+    char* result = mprintf("\n%s", message);
+    notify(&tv, log_source, TTCN_Logger::DEBUG_UNQUALIFIED, result);
+    Free(result);
+  }
+  else {
+    if (from_mtc) {
+      if (return_type == DRET_SETTING_CHANGE) {
+        switch (last_debug_command.command) {
+        case D_SWITCH:
+          Free(debugger_settings.on_switch);
+          debugger_settings.on_switch = mcopystr(last_debug_command.arguments);
+          break;
+        case D_SET_OUTPUT: {
+          Free(debugger_settings.output_type);
+          Free(debugger_settings.output_file);
+          debugger_settings.output_file = NULL;
+          size_t args_len = mstrlen(last_debug_command.arguments);
+          size_t start = 0;
+          size_t end = 0;
+          get_next_argument_loc(last_debug_command.arguments, args_len, start, end);
+          debugger_settings.output_type = mcopystrn(last_debug_command.arguments + start, end - start);
+          if (end < args_len) {
+            start = end;
+            get_next_argument_loc(last_debug_command.arguments, args_len, start, end);
+            debugger_settings.output_file = mcopystrn(last_debug_command.arguments + start, end - start);
+          }
+          break; }
+        case D_SET_ERROR_BEHAVIOR:
+          Free(debugger_settings.error_behavior);
+          debugger_settings.error_behavior = mcopystr(last_debug_command.arguments);
+          break;
+        case D_SET_FAIL_BEHAVIOR:
+          Free(debugger_settings.fail_behavior);
+          debugger_settings.fail_behavior = mcopystr(last_debug_command.arguments);
+          break;
+        case D_ADD_BREAKPOINT: {
+          debugger_settings.breakpoints = (debugger_settings_struct::breakpoint_struct*)
+            Realloc(debugger_settings.breakpoints, (debugger_settings.nof_breakpoints + 1) *
+            sizeof(debugger_settings_struct::breakpoint_struct));
+          size_t args_len = mstrlen(last_debug_command.arguments);
+          size_t start = 0;
+          size_t end = 0;
+          get_next_argument_loc(last_debug_command.arguments, args_len, start, end);
+          debugger_settings.breakpoints[debugger_settings.nof_breakpoints].module =
+            mcopystrn(last_debug_command.arguments + start, end - start);
+          start = end;
+          get_next_argument_loc(last_debug_command.arguments, args_len, start, end);
+          debugger_settings.breakpoints[debugger_settings.nof_breakpoints].line =
+            mcopystrn(last_debug_command.arguments + start, end - start);
+          ++debugger_settings.nof_breakpoints;
+          break; }
+        case D_REMOVE_BREAKPOINT: {
+          size_t args_len = mstrlen(last_debug_command.arguments);
+          size_t start = 0;
+          size_t end = 0;
+          get_next_argument_loc(last_debug_command.arguments, args_len, start, end);
+          char* module = mcopystrn(last_debug_command.arguments + start, end - start);
+          start = end;
+          get_next_argument_loc(last_debug_command.arguments, args_len, start, end);
+          char* line = mcopystrn(last_debug_command.arguments + start, end - start);
+          for (int i = 0; i < debugger_settings.nof_breakpoints; ++i) {
+            if (!strcmp(debugger_settings.breakpoints[i].module, module) &&
+                !strcmp(debugger_settings.breakpoints[i].line, line)) {
+              Free(debugger_settings.breakpoints[i].module);
+              Free(debugger_settings.breakpoints[i].line);
+              for (int j = i; j < debugger_settings.nof_breakpoints - 1; ++j) {
+                debugger_settings.breakpoints[j] = debugger_settings.breakpoints[j + 1];
+              }
+              debugger_settings.breakpoints = (debugger_settings_struct::breakpoint_struct*)
+                Realloc(debugger_settings.breakpoints, (debugger_settings.nof_breakpoints - 1) *
+                sizeof(debugger_settings_struct::breakpoint_struct));
+              --debugger_settings.nof_breakpoints;
+              break;
+            }
+          }
+          Free(module);
+          Free(line);
+          break; }
+        default:
+          break;
+        }
+      }
+      else if (return_type == DRET_EXIT_ALL) {
+        stop_requested = TRUE;
+      }
+    }
+    notify(&tv, log_source, TTCN_Logger::DEBUG_UNQUALIFIED, message);
+  }
+  delete [] message;
+}
+
+void MainController::process_debug_halt_req(component_struct* tc)
+{
+  //lock();
+  // don't send the halt command back to the requesting component
+  if (tc != mtc) {
+    send_debug_command(mtc->tc_fd, D_HALT, "");
+  }
+  for (component i = tc_first_comp_ref; i < n_components; ++i) {
+    component_struct* comp = components[i];
+    if (tc != comp && comp->tc_state != PTC_STALE && comp->tc_state != TC_EXITED) {
+      send_debug_command(comp->tc_fd, D_HALT, "");
+    }
+  }
+  debugger_active_tc = tc;
+  //status_change();
+  //unlock();
+}
+
 void MainController::process_testcase_started()
 {
   if (mc_state != MC_EXECUTING_CONTROL) {
@@ -5520,6 +5734,16 @@ void MainController::initialize(UserInterface& par_ui, int par_max_ptcs)
   n_hosts = 0;
   hosts = NULL;
   config_str = NULL;
+  
+  debugger_settings.on_switch = NULL;
+  debugger_settings.output_type = NULL;
+  debugger_settings.output_file = NULL;
+  debugger_settings.error_behavior = NULL;
+  debugger_settings.fail_behavior = NULL;
+  debugger_settings.nof_breakpoints = 0;
+  debugger_settings.breakpoints = NULL;
+  last_debug_command.command = D_ERROR;
+  last_debug_command.arguments = NULL;
 
   version_known = FALSE;
   n_modules = 0;
@@ -5530,6 +5754,7 @@ void MainController::initialize(UserInterface& par_ui, int par_max_ptcs)
   components = NULL;
   mtc = NULL;
   system = NULL;
+  debugger_active_tc = NULL;
   next_comp_ref = FIRST_PTC_COMPREF;
 
   stop_after_tc = FALSE;
@@ -6132,6 +6357,99 @@ void MainController::stop_execution()
   unlock();
 }
 
+void MainController::debug_command(int commandID, char* arguments)
+{
+  lock();
+  if (mtc != NULL) {
+    switch (commandID) {
+    case D_SET_COMPONENT: // handled by the MC
+      if (!strcmp(arguments, "mtc")) {
+        notify("Debugger %sset to print data from the MTC.",
+          debugger_active_tc == mtc ? "was already " : "");
+        debugger_active_tc = mtc;
+      }
+      else {
+        size_t len = strlen(arguments);
+        for (size_t i = 0; i < len; ++i) {
+          if (arguments[i] < '0' || arguments[i] > '9') {
+            notify("Argument 1 is invalid. Expected 'mtc' or integer value "
+              "(component reference).");
+            unlock();
+            return;
+          }
+        }
+        const component_struct* tc = lookup_component(strtol(arguments, NULL, 10));
+        if (tc == NULL || tc->tc_state == PTC_STALE || tc->tc_state == TC_EXITED) {
+          notify("Invalid component reference %s.", arguments);
+        }
+        else {
+          notify("Debugger %sset to print data from PTC %s%s%d%s.",
+            debugger_active_tc == tc ? "was already " : "",
+            tc->comp_name != NULL ? tc->comp_name : "",
+            tc->comp_name != NULL ? "(" : "", tc->comp_ref,
+            tc->comp_name != NULL ? ")" : "");
+          debugger_active_tc = tc;
+        }
+      }
+      break;
+    case D_PRINT_CALL_STACK:
+    case D_SET_STACK_LEVEL:
+    case D_LIST_VARIABLES:
+    case D_PRINT_VARIABLE:
+    case D_OVERWRITE_VARIABLE:
+    case D_PRINT_SNAPSHOTS:
+    case D_STEP_OVER:
+    case D_STEP_INTO:
+    case D_STEP_OUT:
+    case D_RUN_TO_CURSOR:
+      // it's a data printing or stepping command, needs to be sent to the
+      // active component
+      if (debugger_active_tc == NULL) {
+        // set the MTC as active if test execution hasn't halted and no
+        // D_SET_COMPONENT command has been issued
+        debugger_active_tc = mtc;
+      }
+      send_debug_command(debugger_active_tc->tc_fd, commandID, arguments);
+      break;
+    case D_SWITCH:
+    case D_SET_OUTPUT:
+    case D_SET_ERROR_BEHAVIOR:
+    case D_SET_FAIL_BEHAVIOR:
+    case D_ADD_BREAKPOINT:
+    case D_REMOVE_BREAKPOINT:
+      // it's a global setting, needs to be sent to all HCs and TCs
+      for (int i = 0; i < n_hosts; i++) {
+        send_debug_command(hosts[i]->hc_fd, commandID, arguments);
+      }
+      // store this command, the next MSG_DEBUG_RETURN_VALUE message might need it
+      last_debug_command.command = commandID;
+      Free(last_debug_command.arguments);
+      last_debug_command.arguments = mcopystr(arguments);
+      // no break, send it to TCs, too
+    case D_HALT:
+    case D_CONTINUE:
+    case D_EXIT:
+      // it's a global setting or a command related to the halted state,
+      // needs to be sent to all TCs
+      send_debug_command(mtc->tc_fd, commandID, arguments);
+      for (component i = FIRST_PTC_COMPREF; i < n_components; ++i) {
+        component_struct *comp = components[i];
+        if (comp != NULL && comp->tc_state != PTC_STALE && comp->tc_state != TC_EXITED) {
+          send_debug_command(comp->tc_fd, commandID, arguments);
+        }
+      }
+      break;
+    default:
+      break;
+    }
+  }
+  else {
+    notify("Cannot execute debug commands before the MTC is created.");
+  }
+  status_change();
+  unlock();
+}
+
 mc_state_enum MainController::get_state()
 {
   lock();
index 1bb841e97c0691952e87ff878d65bf6ffdb502b9..4993bffadcbcf62b0b3854dd594d2002164b29b8 100644 (file)
@@ -240,6 +240,26 @@ struct module_version_info {
 /** Possible reasons for waking up the MC thread from the main thread. */
 enum wakeup_reason_t { REASON_NOTHING, REASON_SHUTDOWN, REASON_MTC_KILL_TIMER };
 
+/** Structure for storing the settings needed to initialize the debugger of a
+  * newly connected HC */
+struct debugger_settings_struct {
+  char* on_switch;
+  char* output_type;
+  char* output_file;
+  char* error_behavior;
+  char* fail_behavior;
+  int nof_breakpoints;
+  struct breakpoint_struct {
+    char* module;
+    char* line;
+  }* breakpoints;
+};
+
+struct debug_command_struct {
+  int command;
+  char* arguments;
+};
+
 /** The MainController class. The collection of all functions and data
  *   structures */
 class MainController {
@@ -310,6 +330,8 @@ class MainController {
   static int n_hosts;
   static host_struct **hosts;
   static char *config_str;
+  static debugger_settings_struct debugger_settings;
+  static debug_command_struct last_debug_command;
   static host_struct *add_new_host(unknown_connection *conn);
   static void close_hc_connection(host_struct *hc);
   static boolean is_hc_in_state(hc_state_enum checked_state);
@@ -328,6 +350,7 @@ class MainController {
   static int n_components, n_active_ptcs, max_ptcs;
   static component_struct **components;
   static component_struct *mtc, *system;
+  static const component_struct* debugger_active_tc;
   static component next_comp_ref, tc_first_comp_ref;
   static boolean any_component_done_requested, any_component_done_sent,
   all_component_done_requested, any_component_killed_requested,
@@ -494,6 +517,8 @@ private:
   static void send_unmap(component_struct *tc,
     const char *local_port, const char *system_port);
   static void send_unmap_ack(component_struct *tc);
+  static void send_debug_command(int fd, int commandID, const char* arguments);
+  static void send_debug_setup(host_struct *hc);
 
   /* Messages to MTC */
   static void send_cancel_done_mtc(component component_reference,
@@ -563,6 +588,8 @@ private:
   static void process_mapped(component_struct *tc);
   static void process_unmap_req(component_struct *tc);
   static void process_unmapped(component_struct *tc);
+  static void process_debug_return_value(Text_Buf& text_buf, char* log_source, bool from_mtc);
+  static void process_debug_halt_req(component_struct *tc);
 
   /* Incoming messages from MTC */
   static void process_testcase_started();
@@ -600,6 +627,8 @@ public:
   static void stop_after_testcase(boolean new_state);
   static void continue_testcase();
   static void stop_execution();
+  
+  static void debug_command(int commandID, char* arguments);
 
   static mc_state_enum get_state();
   static boolean get_stop_after_testcase();
This page took 0.058089 seconds and 5 git commands to generate.