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:
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;
}
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) {
return;
}
- refd_param_usage_found();
+ ref_usage_found();
Common::Assignment *ass = get_refd_assignment();
if (!ass) FATAL_ERROR("Reference::generate_code_const_ref()");
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,
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();
{
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();
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);
}
// 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;
}
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()
// 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 {
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 {
* 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();
class Def_ExtConst : public Definition {
private:
Type *type;
+ bool usage_found;
/// Copy constructor disabled
Def_ExtConst(const Def_ExtConst& p);
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; }
};
/**
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) {
#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,
case MSG_EXIT_HC:
process_exit_hc();
break;
+ case MSG_DEBUG_COMMAND:
+ process_debug_command();
+ break;
default:
process_unsupported_message(msg_type, msg_end);
}
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
}
}
+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;
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)
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 = {
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();
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.
static void process_error();
static void process_unsupported_message(int msg_type, int msg_end);
+
+ static void process_debug_command();
/** @} */
};
--- /dev/null
+/******************************************************************************
+ * 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 */
+
******************************************************************************/
#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*/)
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);
}
}
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);
}
}
}
// 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;
// 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
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);
delete variables[i];
}
Free(snapshots);
+ Free(command_result);
}
TTCN3_Debug_Scope* TTCN3_Debugger::add_global_scope(const char* p_module)
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);
}
}
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;
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)
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);
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; \
} \
} \
#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)
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)
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);
}
}
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;
+ }
}
}
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);
}
}
}
}
- snapshot = mputstr(snapshot, ")\n");
+ snapshot = mputstr(snapshot, ")");
ttcn3_debugger.add_snapshot(snapshot);
Free(snapshot);
}
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);
}
}
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
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");
}
#else
#include "RT1/PreGenRecordOf.hh"
#endif
+#include <regex.h>
/** alias for record of charstring */
typedef PreGenRecordOf::PREGEN__RECORD__OF__CHARSTRING charstring_list;
* 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;
// 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 {
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;
/** 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
* (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 */
* 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) */
////// 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
////// 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) */
* 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&));
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 */
/** 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;
};
/** 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;
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
#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
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);
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
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);
#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"
{ 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;
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);
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...");
}
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;
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);
}
}
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;
if(state == 0) {
command_index = 0;
+ debug_command_index = 0;
prefix_len = strlen(prefix);
}
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;
}
#include "../../core/Error.hh"
#include "../../core/Textbuf.hh"
#include "../../core/Logger.hh"
+#include "DebugCommands.hh"
#include <stdio.h>
#include <string.h>
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) {
host->hostname);
}
send_configure(host, config_str);
+ send_debug_setup(host);
}
}
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,
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,
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
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);
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)
{
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) {
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;
components = NULL;
mtc = NULL;
system = NULL;
+ debugger_active_tc = NULL;
next_comp_ref = FIRST_PTC_COMPREF;
stop_after_tc = FALSE;
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();
/** 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 {
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);
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,
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,
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();
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();