From: Botond Baranyi Date: Mon, 2 May 2016 10:48:52 +0000 (+0200) Subject: Debugger - Stage 2 (artf511247) X-Git-Url: http://git.efficios.com/?a=commitdiff_plain;h=016a1a934440d2c464b86d0c64b7cdfc78568bb8;p=deliverable%2Ftitan.core.git Debugger - Stage 2 (artf511247) Change-Id: I0be2f63e69743d2661ad1d4359c55d9e758b9e26 Signed-off-by: Botond Baranyi --- diff --git a/compiler2/ttcn3/AST_ttcn3.cc b/compiler2/ttcn3/AST_ttcn3.cc index b720bcb..85568e2 100644 --- a/compiler2/ttcn3/AST_ttcn3.cc +++ b/compiler2/ttcn3/AST_ttcn3.cc @@ -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(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(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 \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(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(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(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 { diff --git a/compiler2/ttcn3/AST_ttcn3.hh b/compiler2/ttcn3/AST_ttcn3.hh index e694c26..894ec97 100644 --- a/compiler2/ttcn3/AST_ttcn3.hh +++ b/compiler2/ttcn3/AST_ttcn3.hh @@ -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; } }; /** diff --git a/compiler2/ttcn3/Statement.cc b/compiler2/ttcn3/Statement.cc index 8610a65..2dc15a3 100644 --- a/compiler2/ttcn3/Statement.cc +++ b/compiler2/ttcn3/Statement.cc @@ -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) { diff --git a/core/Communication.cc b/core/Communication.cc index 8600812..db8fb8d 100644 --- a/core/Communication.cc +++ b/core/Communication.cc @@ -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 = { diff --git a/core/Communication.hh b/core/Communication.hh index 334ddc4..2d51295 100644 --- a/core/Communication.hh +++ b/core/Communication.hh @@ -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 index 0000000..e5d569c --- /dev/null +++ b/core/DebugCommands.hh @@ -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 */ + diff --git a/core/Debugger.cc b/core/Debugger.cc index 7e86c0c..a5331ca 100644 --- a/core/Debugger.cc +++ b/core/Debugger.cc @@ -12,6 +12,11 @@ ******************************************************************************/ #include "Debugger.hh" +#include "DebugCommands.hh" +#include "Communication.hh" +#include "../common/pattern.hh" +#include +#include ////////////////////////////////////////////////////// ////////////////// TTCN3_Debugger //////////////////// @@ -19,26 +24,29 @@ 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"); } diff --git a/core/Debugger.hh b/core/Debugger.hh index 42271c6..370c034 100644 --- a/core/Debugger.hh +++ b/core/Debugger.hh @@ -22,6 +22,7 @@ #else #include "RT1/PreGenRecordOf.hh" #endif +#include /** 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 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; diff --git a/core/Makefile b/core/Makefile index d4fcd84..a01209d 100644 --- a/core/Makefile +++ b/core/Makefile @@ -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 diff --git a/core/Message_types.hh b/core/Message_types.hh index d237392..b47fa15 100644 --- a/core/Message_types.hh +++ b/core/Message_types.hh @@ -103,6 +103,7 @@ #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) */ @@ -116,4 +117,12 @@ #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 diff --git a/core/Runtime.cc b/core/Runtime.cc index 27ca3b3..625004e 100644 --- a/core/Runtime.cc +++ b/core/Runtime.cc @@ -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); diff --git a/core/Textbuf.cc b/core/Textbuf.cc index 53a3cd9..b7d9e37 100644 --- a/core/Textbuf.cc +++ b/core/Textbuf.cc @@ -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 diff --git a/core/Textbuf.hh b/core/Textbuf.hh index 29d27c8..05f3901 100644 --- a/core/Textbuf.hh +++ b/core/Textbuf.hh @@ -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); diff --git a/mctr2/cli/Cli.cc b/mctr2/cli/Cli.cc index be4de36..a217c39 100644 --- a/mctr2/cli/Cli.cc +++ b/mctr2/cli/Cli.cc @@ -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 " ", + "Add breakpoint at specified location." }, + { D_REMOVE_BREAKPOINT_TEXT, D_REMOVE_BREAKPOINT, + D_REMOVE_BREAKPOINT_TEXT " ", + "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|", + "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 " ", + "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 " [{ }]", + "Print current value of one or more variables." }, + { D_OVERWRITE_VARIABLE_TEXT, D_OVERWRITE_VARIABLE, + D_OVERWRITE_VARIABLE_TEXT " ", + "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 " ", + "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 " ", + "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; } diff --git a/mctr2/mctr/MainController.cc b/mctr2/mctr/MainController.cc index 6442b6a..f4258e1 100644 --- a/mctr2/mctr/MainController.cc +++ b/mctr2/mctr/MainController.cc @@ -40,6 +40,7 @@ #include "../../core/Error.hh" #include "../../core/Textbuf.hh" #include "../../core/Logger.hh" +#include "DebugCommands.hh" #include #include @@ -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(); diff --git a/mctr2/mctr/MainController.h b/mctr2/mctr/MainController.h index 1bb841e..4993bff 100644 --- a/mctr2/mctr/MainController.h +++ b/mctr2/mctr/MainController.h @@ -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();