Commit | Line | Data |
---|---|---|
f08ff9ca BB |
1 | /****************************************************************************** |
2 | * Copyright (c) 2000-2016 Ericsson Telecom AB | |
3 | * All rights reserved. This program and the accompanying materials | |
4 | * are made available under the terms of the Eclipse Public License v1.0 | |
5 | * which accompanies this distribution, and is available at | |
6 | * http://www.eclipse.org/legal/epl-v10.html | |
7 | * | |
8 | * Contributors: | |
9 | * | |
10 | * Baranyi, Botond – initial implementation | |
11 | * | |
12 | ******************************************************************************/ | |
13 | ||
14 | #include "DebuggerUI.hh" | |
15 | #include "DebugCommands.hh" | |
16 | #include "Debugger.hh" | |
f08ff9ca BB |
17 | #include <stdio.h> |
18 | #include <ctype.h> | |
19 | ||
5ea46818 BB |
20 | #ifdef ADVANCED_DEBUGGER_UI |
21 | #include "../mctr2/editline/libedit/src/editline/readline.h" | |
22 | // use a different file, than the MCTR CLI, since not all commands are the same | |
23 | #define TTCN3_HISTORY_FILENAME ".ttcn3_history_single" | |
24 | #endif | |
25 | ||
f08ff9ca BB |
26 | #define PROMPT_TEXT "DEBUG> " |
27 | #define BATCH_TEXT "batch" | |
f57971fe | 28 | #define HELP_TEXT "help" |
f08ff9ca | 29 | |
f08ff9ca BB |
30 | const TTCN_Debugger_UI::command_t TTCN_Debugger_UI::debug_command_list[] = { |
31 | { D_SWITCH_TEXT, D_SWITCH, D_SWITCH_TEXT " on|off", | |
32 | "Switch the debugger on or off." }, | |
33 | { D_SET_BREAKPOINT_TEXT, D_SET_BREAKPOINT, | |
34 | D_SET_BREAKPOINT_TEXT " <module> <line> [<batch_file>]", | |
35 | "Add a breakpoint at the specified location, or change the batch file of " | |
f57971fe | 36 | "an existing breakpoint." }, |
f08ff9ca BB |
37 | { D_REMOVE_BREAKPOINT_TEXT, D_REMOVE_BREAKPOINT, |
38 | D_REMOVE_BREAKPOINT_TEXT " all|<module> [all|<line>]", "Remove a breakpoint, " | |
39 | "or all breakpoints from a module, or all breakpoints from all modules." }, | |
40 | { D_SET_AUTOMATIC_BREAKPOINT_TEXT, D_SET_AUTOMATIC_BREAKPOINT, | |
41 | D_SET_AUTOMATIC_BREAKPOINT_TEXT " error|fail on|off [<batch_file>]", | |
42 | "Switch an automatic breakpoint (truggered by an event) on or off, and/or " | |
43 | "change its batch file." }, | |
44 | { D_SET_OUTPUT_TEXT, D_SET_OUTPUT, | |
45 | D_SET_OUTPUT_TEXT " console|file|both [file_name]", | |
46 | "Set the output of the debugger." }, | |
47 | { D_SET_GLOBAL_BATCH_FILE_TEXT, D_SET_GLOBAL_BATCH_FILE, | |
48 | D_SET_GLOBAL_BATCH_FILE_TEXT " on|off [batch_file_name]", | |
49 | "Set whether a batch file should be executed automatically when test execution " | |
50 | "is halted (breakpoint-specific batch files override this setting)." }, | |
1cb5b229 BB |
51 | { D_PRINT_SETTINGS_TEXT, D_PRINT_SETTINGS, D_PRINT_SETTINGS_TEXT, |
52 | "Prints the debugger's settings." }, | |
f08ff9ca BB |
53 | { D_PRINT_CALL_STACK_TEXT, D_PRINT_CALL_STACK, D_PRINT_CALL_STACK_TEXT, |
54 | "Print call stack." }, | |
55 | { D_SET_STACK_LEVEL_TEXT, D_SET_STACK_LEVEL, D_SET_STACK_LEVEL_TEXT " <level>", | |
56 | "Set the stack level to print debug information from." }, | |
57 | { D_LIST_VARIABLES_TEXT, D_LIST_VARIABLES, | |
58 | D_LIST_VARIABLES_TEXT " local|global|comp|all [pattern]", | |
59 | "List variable names." }, | |
60 | { D_PRINT_VARIABLE_TEXT, D_PRINT_VARIABLE, | |
61 | D_PRINT_VARIABLE_TEXT " <variable_name>|$ [{ <variable_name>|$}]", | |
62 | "Print current value of one or more variables ('$' is substituted with the " | |
63 | "result of the last " D_LIST_VARIABLES_TEXT " command)." }, | |
64 | { D_OVERWRITE_VARIABLE_TEXT, D_OVERWRITE_VARIABLE, | |
65 | D_OVERWRITE_VARIABLE_TEXT " <variable_name> <value>", | |
66 | "Overwrite the current value of a variable." }, | |
67 | { D_PRINT_SNAPSHOTS_TEXT, D_PRINT_SNAPSHOTS, D_PRINT_SNAPSHOTS_TEXT, | |
68 | "Print snapshots of function calls until this point." }, | |
69 | // D_SET_SNAPSHOT_BEHAVIOR_TEXT | |
70 | { D_STEP_OVER_TEXT, D_STEP_OVER, D_STEP_OVER_TEXT, | |
71 | "Resume test execution until the next line of code (in this function or the " | |
72 | "caller function)." }, | |
73 | { D_STEP_INTO_TEXT, D_STEP_INTO, D_STEP_INTO_TEXT, | |
74 | "Resume test execution until the next line of code (on any stack level)." }, | |
75 | { D_STEP_OUT_TEXT, D_STEP_OUT, D_STEP_OUT_TEXT, | |
76 | "Resume test execution until the next line of code in the caller function." }, | |
77 | { D_RUN_TO_CURSOR_TEXT, D_RUN_TO_CURSOR, D_RUN_TO_CURSOR_TEXT " <module> <line>", | |
78 | "Resume test execution until the specified location." }, | |
79 | { D_HALT_TEXT, D_HALT, D_HALT_TEXT, "Halt test execution." }, | |
80 | { D_CONTINUE_TEXT, D_CONTINUE, D_CONTINUE_TEXT, "Resume halted test execution." }, | |
81 | { D_EXIT_TEXT, D_EXIT, D_EXIT_TEXT " test|all", | |
82 | "Exit the current test or the execution of all tests." }, | |
83 | { NULL, D_ERROR, NULL, NULL } | |
84 | }; | |
85 | ||
5ea46818 | 86 | #ifdef ADVANCED_DEBUGGER_UI |
f08ff9ca | 87 | char* TTCN_Debugger_UI::ttcn3_history_filename = NULL; |
5ea46818 | 88 | #endif |
f08ff9ca BB |
89 | |
90 | /** local function for extracting the command name and its arguments from an | |
91 | * input line | |
92 | * @param arguments [in] input line | |
93 | * @param len [in] length of the input line | |
94 | * @param start [in] indicates the position to start searching from | |
95 | * @param start [out] the next argument's start position (set to len if no further | |
96 | * arguments were found) | |
97 | * @param end [out] the position of the first character after the next argument */ | |
98 | static void get_next_argument_loc(const char* arguments, size_t len, size_t& start, size_t& end) | |
99 | { | |
100 | while (start < len && isspace(arguments[start])) { | |
101 | ++start; | |
102 | } | |
103 | end = start; | |
104 | while (end < len && !isspace(arguments[end])) { | |
105 | ++end; | |
106 | } | |
107 | } | |
108 | ||
109 | void TTCN_Debugger_UI::process_command(const char* p_line_read) | |
110 | { | |
111 | // locate the command text | |
112 | size_t len = strlen(p_line_read); | |
113 | size_t start = 0; | |
114 | size_t end = 0; | |
115 | get_next_argument_loc(p_line_read, len, start, end); | |
116 | if (start == len) { | |
117 | // empty command | |
118 | return; | |
119 | } | |
5ea46818 | 120 | #ifdef ADVANCED_DEBUGGER_UI |
f08ff9ca | 121 | add_history(p_line_read + start); |
5ea46818 | 122 | #endif |
f08ff9ca BB |
123 | for (const command_t *command = debug_command_list; command->name != NULL; |
124 | ++command) { | |
125 | if (!strncmp(p_line_read + start, command->name, end - start)) { | |
126 | // count the arguments | |
127 | int argument_count = 0; | |
128 | size_t start_tmp = start; | |
129 | size_t end_tmp = end; | |
130 | while (start_tmp < len) { | |
131 | start_tmp = end_tmp; | |
132 | get_next_argument_loc(p_line_read, len, start_tmp, end_tmp); | |
133 | if (start_tmp < len) { | |
134 | ++argument_count; | |
135 | } | |
136 | } | |
137 | // extract the arguments into a string array | |
138 | char** arguments; | |
139 | if (argument_count > 0) { | |
140 | arguments = new char*[argument_count]; | |
141 | for (int i = 0; i < argument_count; ++i) { | |
142 | start = end; | |
143 | get_next_argument_loc(p_line_read, len, start, end); | |
144 | arguments[i] = mcopystrn(p_line_read + start, end - start); | |
145 | } | |
146 | } | |
147 | else { | |
148 | arguments = NULL; | |
149 | } | |
150 | ttcn3_debugger.execute_command(command->commandID, argument_count, arguments); | |
151 | if (argument_count > 0) { | |
152 | for (int i = 0; i < argument_count; ++i) { | |
153 | Free(arguments[i]); | |
154 | } | |
155 | delete [] arguments; | |
156 | } | |
157 | return; | |
158 | } | |
159 | } | |
160 | if (!strncmp(p_line_read + start, BATCH_TEXT, end - start)) { | |
161 | start = end; | |
162 | get_next_argument_loc(p_line_read, len, start, end); // just to skip to the argument | |
163 | // the entire argument list is treated as one file name (even if it contains spaces) | |
164 | execute_batch_file(p_line_read + start); | |
165 | return; | |
166 | } | |
f57971fe BB |
167 | if (!strncmp(p_line_read + start, HELP_TEXT, end - start)) { |
168 | start = end; | |
169 | get_next_argument_loc(p_line_read, len, start, end); // just to skip to the argument | |
170 | help(p_line_read + start); | |
171 | return; | |
172 | } | |
f08ff9ca BB |
173 | puts("Unknown command, try again..."); |
174 | } | |
175 | ||
f57971fe BB |
176 | void TTCN_Debugger_UI::help(const char* p_argument) |
177 | { | |
178 | if (*p_argument == 0) { | |
179 | puts("Help is available for the following commands:"); | |
180 | printf(BATCH_TEXT); | |
181 | for (const command_t *command = debug_command_list; | |
182 | command->name != NULL; command++) { | |
183 | printf(" %s", command->name); | |
184 | } | |
185 | putchar('\n'); | |
186 | } | |
187 | else { | |
188 | for (const command_t *command = debug_command_list; | |
189 | command->name != NULL; command++) { | |
190 | if (!strncmp(p_argument, command->name, strlen(command->name))) { | |
191 | printf("%s usage: %s\n%s\n", command->name, command->synopsis, | |
192 | command->description); | |
193 | return; | |
194 | } | |
195 | } | |
196 | if (!strcmp(p_argument, BATCH_TEXT)) { | |
197 | puts(BATCH_TEXT " usage: " BATCH_TEXT "\nRun commands from batch file."); | |
198 | } | |
199 | else { | |
200 | printf("No help for %s.\n", p_argument); | |
201 | } | |
202 | } | |
203 | } | |
204 | ||
f08ff9ca BB |
205 | void TTCN_Debugger_UI::init() |
206 | { | |
5ea46818 | 207 | #ifdef ADVANCED_DEBUGGER_UI |
f08ff9ca BB |
208 | // initialize history library |
209 | using_history(); | |
210 | // calculate history file name | |
211 | const char *home_directory = getenv("HOME"); | |
212 | if (home_directory == NULL) { | |
213 | home_directory = "."; | |
214 | } | |
215 | ttcn3_history_filename = mprintf("%s/%s", home_directory, TTCN3_HISTORY_FILENAME); | |
216 | // read history from file, don't bother if it does not exist | |
217 | read_history(ttcn3_history_filename); | |
218 | // set our own command completion function | |
219 | rl_completion_entry_function = (Function*)complete_command; | |
5ea46818 | 220 | #endif |
f08ff9ca BB |
221 | } |
222 | ||
223 | void TTCN_Debugger_UI::clean_up() | |
224 | { | |
5ea46818 | 225 | #ifdef ADVANCED_DEBUGGER_UI |
f08ff9ca BB |
226 | if (write_history(ttcn3_history_filename)) { |
227 | puts("Could not save debugger command history."); | |
228 | } | |
229 | Free(ttcn3_history_filename); | |
5ea46818 | 230 | #endif |
f08ff9ca BB |
231 | } |
232 | ||
233 | void TTCN_Debugger_UI::read_loop() | |
234 | { | |
235 | while (ttcn3_debugger.is_halted()) { | |
5ea46818 | 236 | #ifdef ADVANCED_DEBUGGER_UI |
f08ff9ca BB |
237 | // print the prompt and read a line using the readline(), which |
238 | // automatically handles command completion and command history | |
239 | char* line = readline(PROMPT_TEXT); | |
240 | if (line != NULL) { | |
5ea46818 BB |
241 | #else |
242 | // simplified debugger UI: use a simple fgets | |
243 | printf(PROMPT_TEXT); | |
244 | char line[1024]; | |
245 | char* res = fgets(line, sizeof(line), stdin); | |
246 | if (res != NULL) { | |
247 | #endif | |
f08ff9ca | 248 | process_command(line); |
5ea46818 | 249 | #ifdef ADVANCED_DEBUGGER_UI |
f08ff9ca | 250 | free(line); |
5ea46818 | 251 | #endif |
f08ff9ca BB |
252 | } |
253 | else { | |
254 | // EOF was received -> exit all | |
255 | puts("exit all"); | |
256 | char** args = new char*[1]; | |
257 | args[0] = (char*)"all"; | |
258 | ttcn3_debugger.execute_command(D_EXIT, 1, args); | |
259 | delete [] args; | |
260 | } | |
261 | } | |
262 | } | |
263 | ||
264 | void TTCN_Debugger_UI::execute_batch_file(const char* p_file_name) | |
265 | { | |
266 | FILE* fp = fopen(p_file_name, "r"); | |
267 | if (fp == NULL) { | |
268 | printf("Failed to open file '%s' for reading.\n", p_file_name); | |
269 | return; | |
270 | } | |
271 | else { | |
272 | printf("Executing batch file '%s'.\n", p_file_name); | |
273 | } | |
274 | char line[1024]; | |
275 | while (fgets(line, sizeof(line), fp) != NULL) { | |
276 | size_t len = strlen(line); | |
277 | if (line[len - 1] == '\n') { | |
278 | line[len - 1] = '\0'; | |
279 | --len; | |
280 | } | |
281 | if (len != 0) { | |
282 | printf("%s\n", line); | |
283 | process_command(line); | |
284 | } | |
285 | } | |
286 | if (!feof(fp)) { | |
287 | printf("Error occurred while reading batch file '%s' (error code: %d).\n", | |
288 | p_file_name, ferror(fp)); | |
289 | } | |
290 | fclose(fp); | |
291 | } | |
292 | ||
293 | void TTCN_Debugger_UI::print(const char* p_str) | |
294 | { | |
295 | puts(p_str); | |
296 | } | |
297 | ||
5ea46818 | 298 | #ifdef ADVANCED_DEBUGGER_UI |
f08ff9ca BB |
299 | char* TTCN_Debugger_UI::complete_command(const char* p_prefix, int p_state) |
300 | { | |
301 | static int command_index; | |
302 | static size_t prefix_len; | |
303 | const char *command_name; | |
304 | ||
305 | if (p_state == 0) { | |
306 | command_index = 0; | |
307 | prefix_len = strlen(p_prefix); | |
308 | } | |
309 | ||
310 | while ((command_name = debug_command_list[command_index].name)) { | |
311 | ++command_index; | |
312 | if (strncmp(p_prefix, command_name, prefix_len) == 0) { | |
313 | // must allocate buffer for returned string (readline() frees it) | |
314 | return strdup(command_name); | |
315 | } | |
316 | } | |
317 | // no match found | |
318 | return NULL; | |
5ea46818 BB |
319 | } |
320 | #endif |