/[gxemul]/upstream/0.3.8/src/debugger.c
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Contents of /upstream/0.3.8/src/debugger.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 23 - (show annotations)
Mon Oct 8 16:19:43 2007 UTC (16 years, 6 months ago) by dpavlin
File MIME type: text/plain
File size: 51672 byte(s)
0.3.8
1 /*
2 * Copyright (C) 2003-2006 Anders Gavare. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * 3. The name of the author may not be used to endorse or promote products
13 * derived from this software without specific prior written permission.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 *
28 * $Id: debugger.c,v 1.132 2006/02/04 11:10:58 debug Exp $
29 *
30 * Single-step debugger.
31 *
32 *
33 * TODO:
34 *
35 * This entire module is very much non-reentrant. :-/
36 *
37 * Add more functionality that already exists elsewhere in the emulator.
38 *
39 * Call stack display?
40 *
41 * More generic expression evaluator (for example + - * / between multiple
42 * terms), including _TAB COMPLETION_ of symbols and register names!
43 *
44 * Nicer looking output of register dumps, floating point registers,
45 * etc. Warn about weird/invalid register contents.
46 *
47 * Ctrl-C doesn't enter the debugger on some OSes (HP-UX?)...
48 *
49 * Many other TODOs.
50 */
51
52 #include <ctype.h>
53 #include <signal.h>
54 #include <stdio.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <unistd.h>
58
59 #include "bintrans.h"
60 #include "console.h"
61 #include "cpu.h"
62 #include "device.h"
63 #include "debugger.h"
64 #include "diskimage.h"
65 #include "emul.h"
66 #include "machine.h"
67 #include "memory.h"
68 #include "misc.h"
69 #include "net.h"
70 #include "x11.h"
71
72
73 extern int extra_argc;
74 extern char **extra_argv;
75
76 extern int quiet_mode;
77
78
79 /*
80 * Global debugger variables:
81 */
82
83 volatile int single_step = 0;
84 int force_debugger_at_exit = 0;
85 int show_opcode_statistics = 0;
86
87 volatile int single_step_breakpoint = 0;
88 int debugger_n_steps_left_before_interaction = 0;
89
90 int old_instruction_trace = 0;
91 int old_quiet_mode = 0;
92 int old_show_trace_tree = 0;
93
94
95 /*
96 * Private (global) debugger variables:
97 */
98
99 static volatile int ctrl_c;
100
101 static int debugger_n_emuls;
102 static struct emul **debugger_emuls;
103 static struct emul *debugger_emul;
104 static struct machine *debugger_machine;
105
106 static int exit_debugger;
107
108 #define MAX_CMD_BUFLEN 72
109 #define N_PREVIOUS_CMDS 150
110 static char *last_cmd[N_PREVIOUS_CMDS];
111 static int last_cmd_index;
112
113 static char repeat_cmd[MAX_CMD_BUFLEN];
114
115 #define MAGIC_UNTOUCHED 0x98ca76c2ffcc0011ULL
116
117 static uint64_t last_dump_addr = MAGIC_UNTOUCHED;
118 static uint64_t last_unasm_addr = MAGIC_UNTOUCHED;
119
120
121 /*
122 * debugger_readchar():
123 *
124 * TODO: This uses up 100% CPU, maybe that isn't too good. The usleep() call
125 * might make it a tiny bit nicer on other running processes, but it
126 * is still very ugly.
127 */
128 char debugger_readchar(void)
129 {
130 int ch;
131 while ((ch = console_readchar(MAIN_CONSOLE)) < 0) {
132 x11_check_event(debugger_emuls, debugger_n_emuls);
133 usleep(1);
134 }
135 return ch;
136 }
137
138
139 /*
140 * debugger_activate():
141 *
142 * This is a signal handler for CTRL-C. It shouldn't be called directly,
143 * but setup code in emul.c sets the CTRL-C signal handler to use this
144 * function.
145 */
146 void debugger_activate(int x)
147 {
148 ctrl_c = 1;
149
150 if (single_step) {
151 /* Already in the debugger. Do nothing. */
152 int i;
153 for (i=0; i<MAX_CMD_BUFLEN; i++)
154 console_makeavail(MAIN_CONSOLE, '\b');
155 console_makeavail(MAIN_CONSOLE, ' ');
156 console_makeavail(MAIN_CONSOLE, '\n');
157 printf("^C");
158 fflush(stdout);
159 } else {
160 /* Enter the single step debugger. */
161 single_step = 1;
162
163 /* Discard any chars in the input queue: */
164 while (console_charavail(MAIN_CONSOLE))
165 console_readchar(MAIN_CONSOLE);
166 }
167
168 /* Clear the repeat-command buffer: */
169 repeat_cmd[0] = '\0';
170
171 /* Reactivate the signal handler: */
172 signal(SIGINT, debugger_activate);
173 }
174
175
176 /*
177 * debugger_parse_name():
178 *
179 * This function reads a string, and tries to match it to a register name,
180 * a symbol, or treat it as a decimal numeric value.
181 *
182 * Some examples:
183 *
184 * "0x7fff1234" ==> numeric value (hex, in this case)
185 * "pc", "r5", "hi", "t4" ==> register (CPU dependent)
186 * "memcpy+64" ==> symbol (plus offset)
187 *
188 * Register names can be preceeded by "x:" where x is the CPU number. (CPU
189 * 0 is assumed by default.)
190 *
191 * To force detection of different types, a character can be added in front of
192 * the name: "$" for numeric values, "%" for registers, and "@" for symbols.
193 *
194 * Return value is:
195 *
196 * NAME_PARSE_NOMATCH no match
197 * NAME_PARSE_MULTIPLE multiple matches
198 *
199 * or one of these (and then *valuep is read or written, depending on
200 * the writeflag):
201 *
202 * NAME_PARSE_REGISTER a register
203 * NAME_PARSE_NUMBER a hex number
204 * NAME_PARSE_SYMBOL a symbol
205 */
206 #define NAME_PARSE_NOMATCH 0
207 #define NAME_PARSE_MULTIPLE 1
208 #define NAME_PARSE_REGISTER 2
209 #define NAME_PARSE_NUMBER 3
210 #define NAME_PARSE_SYMBOL 4
211 static int debugger_parse_name(struct machine *m, char *name, int writeflag,
212 uint64_t *valuep)
213 {
214 int match_register = 0, match_symbol = 0, match_numeric = 0;
215 int skip_register, skip_numeric, skip_symbol;
216
217 if (m == NULL || name == NULL) {
218 fprintf(stderr, "debugger_parse_name(): NULL ptr\n");
219 exit(1);
220 }
221
222 /* Warn about non-signextended values: */
223 if (writeflag) {
224 if (m->cpus[0]->is_32bit) {
225 /* Automagically sign-extend. TODO: Is this good? */
226 if (((*valuep) >> 32) == 0 && (*valuep) & 0x80000000ULL)
227 (*valuep) |= 0xffffffff00000000ULL;
228 } else {
229 if (((*valuep) >> 32) == 0 && (*valuep) & 0x80000000ULL)
230 printf("WARNING: The value is not sign-extende"
231 "d. Is this what you intended?\n");
232 }
233 }
234
235 skip_register = name[0] == '$' || name[0] == '@';
236 skip_numeric = name[0] == '%' || name[0] == '@';
237 skip_symbol = name[0] == '$' || name[0] == '%';
238
239 /* Check for a register match: */
240 if (!skip_register && strlen(name) >= 1)
241 cpu_register_match(m, name, writeflag, valuep,
242 &match_register);
243
244 /* Check for a number match: */
245 if (!skip_numeric && isdigit((int)name[0])) {
246 uint64_t x;
247 x = strtoull(name, NULL, 0);
248 if (writeflag) {
249 printf("You cannot assign like that.\n");
250 } else
251 *valuep = x;
252 match_numeric = 1;
253 }
254
255 /* Check for a symbol match: */
256 if (!skip_symbol) {
257 int res;
258 char *p, *sn;
259 uint64_t newaddr, ofs = 0;
260
261 sn = malloc(strlen(name) + 1);
262 if (sn == NULL) {
263 fprintf(stderr, "out of memory in debugger\n");
264 exit(1);
265 }
266 strlcpy(sn, name, strlen(name)+1);
267
268 /* Is there a '+' in there? Then treat that as an offset: */
269 p = strchr(sn, '+');
270 if (p != NULL) {
271 *p = '\0';
272 ofs = strtoull(p+1, NULL, 0);
273 }
274
275 res = get_symbol_addr(&m->symbol_context, sn, &newaddr);
276 if (res) {
277 if (writeflag) {
278 printf("You cannot assign like that.\n");
279 } else
280 *valuep = newaddr + ofs;
281 match_symbol = 1;
282 }
283
284 free(sn);
285 }
286
287 if (match_register + match_symbol + match_numeric > 1)
288 return NAME_PARSE_MULTIPLE;
289
290 if (match_register)
291 return NAME_PARSE_REGISTER;
292 if (match_numeric)
293 return NAME_PARSE_NUMBER;
294 if (match_symbol)
295 return NAME_PARSE_SYMBOL;
296
297 return NAME_PARSE_NOMATCH;
298 }
299
300
301 /*
302 * show_breakpoint():
303 */
304 static void show_breakpoint(struct machine *m, int i)
305 {
306 printf("%3i: 0x", i);
307 if (m->cpus[0]->is_32bit)
308 printf("%08x", (int)m->breakpoint_addr[i]);
309 else
310 printf("%016llx", (long long)m->breakpoint_addr[i]);
311 if (m->breakpoint_string[i] != NULL)
312 printf(" (%s)", m->breakpoint_string[i]);
313 if (m->breakpoint_flags[i])
314 printf(": flags=0x%x", m->breakpoint_flags[i]);
315 printf("\n");
316 }
317
318
319 /****************************************************************************/
320
321
322 /*
323 * debugger_cmd_breakpoint():
324 *
325 * TODO: automagic "expansion" for the subcommand names (s => show).
326 */
327 static void debugger_cmd_breakpoint(struct machine *m, char *cmd_line)
328 {
329 int i, res;
330
331 while (cmd_line[0] != '\0' && cmd_line[0] == ' ')
332 cmd_line ++;
333
334 if (cmd_line[0] == '\0') {
335 printf("syntax: breakpoint subcmd [args...]\n");
336 printf("Available subcmds (and args) are:\n");
337 printf(" add addr add a breakpoint for address addr\n");
338 printf(" delete x delete breakpoint nr x\n");
339 printf(" show show current breakpoints\n");
340 return;
341 }
342
343 if (strcmp(cmd_line, "show") == 0) {
344 if (m->n_breakpoints == 0)
345 printf("No breakpoints set.\n");
346 for (i=0; i<m->n_breakpoints; i++)
347 show_breakpoint(m, i);
348 return;
349 }
350
351 if (strncmp(cmd_line, "delete ", 7) == 0) {
352 int x = atoi(cmd_line + 7);
353
354 if (m->n_breakpoints == 0) {
355 printf("No breakpoints set.\n");
356 return;
357 }
358 if (x < 0 || x >= m->n_breakpoints) {
359 printf("Invalid breakpoint nr %i. Use 'breakpoint "
360 "show' to see the current breakpoints.\n", x);
361 return;
362 }
363
364 free(m->breakpoint_string[x]);
365
366 for (i=x; i<m->n_breakpoints-1; i++) {
367 m->breakpoint_addr[i] = m->breakpoint_addr[i+1];
368 m->breakpoint_string[i] = m->breakpoint_string[i+1];
369 m->breakpoint_flags[i] = m->breakpoint_flags[i+1];
370 }
371 m->n_breakpoints --;
372
373 /* Clear translations: */
374 for (i=0; i<m->ncpus; i++)
375 if (m->cpus[i]->translation_cache != NULL)
376 cpu_create_or_reset_tc(m->cpus[i]);
377 return;
378 }
379
380 if (strncmp(cmd_line, "add ", 4) == 0) {
381 uint64_t tmp;
382 size_t breakpoint_buf_len;
383
384 if (m->n_breakpoints >= MAX_BREAKPOINTS) {
385 printf("Too many breakpoints. (You need to recompile"
386 " gxemul to increase this. Max = %i.)\n",
387 MAX_BREAKPOINTS);
388 return;
389 }
390
391 i = m->n_breakpoints;
392
393 res = debugger_parse_name(m, cmd_line + 4, 0, &tmp);
394 if (!res) {
395 printf("Couldn't parse '%s'\n", cmd_line + 4);
396 return;
397 }
398
399 breakpoint_buf_len = strlen(cmd_line+4) + 1;
400 m->breakpoint_string[i] = malloc(breakpoint_buf_len);
401 if (m->breakpoint_string[i] == NULL) {
402 printf("out of memory in debugger_cmd_breakpoint()\n");
403 exit(1);
404 }
405 strlcpy(m->breakpoint_string[i], cmd_line+4,
406 breakpoint_buf_len);
407 m->breakpoint_addr[i] = tmp;
408 m->breakpoint_flags[i] = 0;
409
410 m->n_breakpoints ++;
411 show_breakpoint(m, i);
412
413 /* Clear translations: */
414 for (i=0; i<m->ncpus; i++)
415 if (m->cpus[i]->translation_cache != NULL)
416 cpu_create_or_reset_tc(m->cpus[i]);
417 return;
418 }
419
420 printf("Unknown breakpoint subcommand.\n");
421 }
422
423
424 /*
425 * debugger_cmd_bintrans():
426 */
427 static void debugger_cmd_bintrans(struct machine *m, char *cmd_line)
428 {
429 int i;
430
431 if (*cmd_line == '\0')
432 goto printstate;
433
434 if (!m->bintrans_enabled_from_start) {
435 printf("You must have enabled bintrans from the start of the "
436 "simulation.\nIt is not possible to turn on afterwards.\n");
437 return;
438 }
439
440 while (*cmd_line == ' ')
441 cmd_line++;
442
443 /* Note: len 3 and 4, to include the NUL char. */
444 if (strncasecmp(cmd_line, "on", 3) == 0) {
445 m->bintrans_enable = 1;
446 for (i=0; i<m->ncpus; i++)
447 bintrans_restart(m->cpus[i]);
448 } else if (strncasecmp(cmd_line, "off", 4) == 0)
449 m->bintrans_enable = 0;
450 else
451 printf("syntax: bintrans [on|off]\n");
452
453 printstate:
454 printf("bintrans is now %s%s\n",
455 m->bintrans_enable? "ENABLED" : "disabled",
456 m->old_bintrans_enable? " (using the OLD bintrans system)" : "");
457 }
458
459
460 /*
461 * debugger_cmd_continue():
462 */
463 static void debugger_cmd_continue(struct machine *m, char *cmd_line)
464 {
465 if (*cmd_line) {
466 printf("syntax: continue\n");
467 return;
468 }
469
470 exit_debugger = 1;
471 }
472
473
474 /*
475 * debugger_cmd_device():
476 */
477 static void debugger_cmd_device(struct machine *m, char *cmd_line)
478 {
479 int i;
480 struct memory *mem;
481 struct cpu *c;
482
483 if (cmd_line[0] == '\0')
484 goto return_help;
485
486 if (m->cpus == NULL) {
487 printf("No cpus (?)\n");
488 return;
489 }
490 c = m->cpus[m->bootstrap_cpu];
491 if (c == NULL) {
492 printf("m->cpus[m->bootstrap_cpu] = NULL\n");
493 return;
494 }
495 mem = m->cpus[m->bootstrap_cpu]->mem;
496
497 if (m->cpus == NULL) {
498 printf("No cpus (?)\n");
499 return;
500 }
501 c = m->cpus[m->bootstrap_cpu];
502 if (c == NULL) {
503 printf("m->cpus[m->bootstrap_cpu] = NULL\n");
504 return;
505 }
506 mem = m->cpus[m->bootstrap_cpu]->mem;
507
508 if (strcmp(cmd_line, "all") == 0) {
509 device_dumplist();
510 } else if (strncmp(cmd_line, "add ", 4) == 0) {
511 device_add(m, cmd_line+4);
512 } else if (strcmp(cmd_line, "consoles") == 0) {
513 console_debug_dump(m);
514 } else if (strncmp(cmd_line, "remove ", 7) == 0) {
515 i = atoi(cmd_line + 7);
516 if (i==0 && cmd_line[7]!='0') {
517 printf("Weird device number. Use 'device list'.\n");
518 } else
519 memory_device_remove(m->memory, i);
520 } else if (strcmp(cmd_line, "list") == 0) {
521 if (mem->n_mmapped_devices == 0)
522 printf("No memory-mapped devices in this machine.\n");
523
524 for (i=0; i<mem->n_mmapped_devices; i++) {
525 printf("%2i: %25s @ 0x%011llx, len = 0x%llx",
526 i, mem->dev_name[i],
527 (long long)mem->dev_baseaddr[i],
528 (long long)mem->dev_length[i]);
529 if (mem->dev_flags[i]) {
530 printf(" (");
531 if (mem->dev_flags[i] & DM_DYNTRANS_OK)
532 printf("DYNTRANS R");
533 if (mem->dev_flags[i] & DM_DYNTRANS_WRITE_OK)
534 printf("+W");
535 printf(")");
536 }
537 printf("\n");
538 }
539 } else
540 goto return_help;
541
542 return;
543
544 return_help:
545 printf("syntax: devices cmd [...]\n");
546 printf("Available cmds are:\n");
547 printf(" add name_and_params add a device to the current "
548 "machine\n");
549 printf(" all list all registered devices\n");
550 printf(" consoles list all slave consoles\n");
551 printf(" list list memory-mapped devices in the"
552 " current machine\n");
553 printf(" remove x remove device nr x from the "
554 "current machine\n");
555 }
556
557
558 /*
559 * debugger_cmd_dump():
560 *
561 * Dump emulated memory in hex and ASCII.
562 *
563 * syntax: dump [addr [endaddr]]
564 */
565 static void debugger_cmd_dump(struct machine *m, char *cmd_line)
566 {
567 uint64_t addr, addr_start, addr_end;
568 struct cpu *c;
569 struct memory *mem;
570 char *p = NULL;
571 int x, r;
572
573 if (cmd_line[0] != '\0') {
574 uint64_t tmp;
575 char *tmps = strdup(cmd_line);
576
577 /* addr: */
578 p = strchr(tmps, ' ');
579 if (p != NULL)
580 *p = '\0';
581 r = debugger_parse_name(m, tmps, 0, &tmp);
582 free(tmps);
583
584 if (r == NAME_PARSE_NOMATCH || r == NAME_PARSE_MULTIPLE) {
585 printf("Unparsable address: %s\n", cmd_line);
586 return;
587 } else {
588 last_dump_addr = tmp;
589 }
590
591 p = strchr(cmd_line, ' ');
592 }
593
594 addr_start = last_dump_addr;
595
596 if (addr_start == MAGIC_UNTOUCHED) {
597 uint64_t tmp;
598 int match_register = 0;
599 cpu_register_match(m, "pc", 0, &tmp, &match_register);
600 if (match_register) {
601 addr_start = tmp;
602 } else {
603 printf("No starting address.\n");
604 return;
605 }
606 }
607
608 addr_end = addr_start + 16 * 16;
609
610 /* endaddr: */
611 if (p != NULL) {
612 while (*p == ' ' && *p)
613 p++;
614 r = debugger_parse_name(m, p, 0, &addr_end);
615 if (r == NAME_PARSE_NOMATCH || r == NAME_PARSE_MULTIPLE) {
616 printf("Unparsable address: %s\n", cmd_line);
617 return;
618 }
619 }
620
621 if (m->cpus == NULL) {
622 printf("No cpus (?)\n");
623 return;
624 }
625 c = m->cpus[m->bootstrap_cpu];
626 if (c == NULL) {
627 printf("m->cpus[m->bootstrap_cpu] = NULL\n");
628 return;
629 }
630 mem = m->cpus[m->bootstrap_cpu]->mem;
631
632 addr = addr_start & ~0xf;
633
634 ctrl_c = 0;
635
636 while (addr < addr_end) {
637 unsigned char buf[16];
638 memset(buf, 0, sizeof(buf));
639 r = c->memory_rw(c, mem, addr, &buf[0], sizeof(buf),
640 MEM_READ, CACHE_NONE | NO_EXCEPTIONS);
641
642 if (c->is_32bit)
643 printf("0x%08x ", (int)addr);
644 else
645 printf("0x%016llx ", (long long)addr);
646
647 if (r == MEMORY_ACCESS_FAILED)
648 printf("(memory access failed)\n");
649 else {
650 for (x=0; x<16; x++) {
651 if (addr + x >= addr_start &&
652 addr + x < addr_end)
653 printf("%02x%s", buf[x],
654 (x&3)==3? " " : "");
655 else
656 printf(" %s", (x&3)==3? " " : "");
657 }
658 printf(" ");
659 for (x=0; x<16; x++) {
660 if (addr + x >= addr_start &&
661 addr + x < addr_end)
662 printf("%c", (buf[x]>=' ' &&
663 buf[x]<127)? buf[x] : '.');
664 else
665 printf(" ");
666 }
667 printf("\n");
668 }
669
670 if (ctrl_c)
671 return;
672
673 addr += sizeof(buf);
674 }
675
676 last_dump_addr = addr_end;
677
678 strlcpy(repeat_cmd, "dump", MAX_CMD_BUFLEN);
679 }
680
681
682 /*
683 * debugger_cmd_emuls():
684 *
685 * Dump info about all current emuls.
686 */
687 static void debugger_cmd_emuls(struct machine *m, char *cmd_line)
688 {
689 int i, iadd = DEBUG_INDENTATION;
690
691 if (*cmd_line) {
692 printf("syntax: emuls\n");
693 return;
694 }
695
696 for (i=0; i<debugger_n_emuls; i++) {
697 struct emul *e = debugger_emuls[i];
698
699 if (e == NULL)
700 continue;
701
702 debug("emulation %i: \"%s\"\n", i,
703 e->name == NULL? "(no name)" : e->name);
704 debug_indentation(iadd);
705
706 emul_dumpinfo(e);
707
708 debug_indentation(-iadd);
709 }
710 }
711
712
713 /*
714 * debugger_cmd_focus():
715 *
716 * Changes focus to specific machine (in a specific emulation).
717 */
718 static void debugger_cmd_focus(struct machine *m, char *cmd_line)
719 {
720 int x = -1, y = -1;
721 char *p;
722
723 if (!cmd_line[0]) {
724 printf("syntax: focus x[,y]\n");
725 printf("where x and y are integers as reported by the"
726 " 'emuls' command\n");
727 goto print_current_focus_and_return;
728 }
729
730 x = atoi(cmd_line);
731 p = strchr(cmd_line, ',');
732 if (p == cmd_line) {
733 printf("No machine number specified?\n");
734 printf("syntax: focus x[,y]\n");
735 return;
736 }
737
738 if (p != NULL)
739 y = atoi(p + 1);
740
741 if (y != -1) {
742 /* Change emul: */
743 if (y < 0 || y >= debugger_n_emuls) {
744 printf("Invalid emul number: %i\n", y);
745 return;
746 }
747
748 debugger_emul = debugger_emuls[y];
749
750 /* This is just in case the machine change below fails... */
751 debugger_machine = debugger_emul->machines[0];
752 }
753
754 /* Change machine: */
755 if (x < 0 || x >= debugger_emul->n_machines) {
756 printf("Invalid machine number: %i\n", x);
757 return;
758 }
759
760 debugger_machine = debugger_emul->machines[x];
761
762 print_current_focus_and_return:
763 printf("current emul: \"%s\"\n", debugger_emul->name == NULL?
764 "(no name)" : debugger_emul->name);
765 printf("current machine: \"%s\"\n", debugger_machine->name == NULL?
766 "(no name)" : debugger_machine->name);
767 }
768
769
770 /* This is defined below. */
771 static void debugger_cmd_help(struct machine *m, char *cmd_line);
772
773
774 /*
775 * debugger_cmd_itrace():
776 */
777 static void debugger_cmd_itrace(struct machine *m, char *cmd_line)
778 {
779 if (*cmd_line) {
780 printf("syntax: itrace\n");
781 return;
782 }
783
784 old_instruction_trace = 1 - old_instruction_trace;
785 printf("instruction_trace = %s\n", old_instruction_trace? "ON":"OFF");
786 /* TODO: how to preserve quiet_mode? */
787 old_quiet_mode = 0;
788 printf("quiet_mode = %s\n", old_quiet_mode? "ON" : "OFF");
789 }
790
791
792 /*
793 * debugger_cmd_lookup():
794 */
795 static void debugger_cmd_lookup(struct machine *m, char *cmd_line)
796 {
797 uint64_t addr;
798 int res;
799 char *symbol;
800 uint64_t offset;
801
802 if (cmd_line[0] == '\0') {
803 printf("syntax: lookup name|addr\n");
804 return;
805
806 }
807
808 /* Addresses never need to be given in decimal form anyway,
809 so assuming hex here will be ok. */
810 addr = strtoull(cmd_line, NULL, 16);
811
812 if (addr == 0) {
813 uint64_t newaddr;
814 res = get_symbol_addr(&m->symbol_context,
815 cmd_line, &newaddr);
816 if (!res) {
817 printf("lookup for '%s' failed\n", cmd_line);
818 return;
819 }
820 printf("%s = 0x", cmd_line);
821 if (m->cpus[0]->is_32bit)
822 printf("%08x\n", (int)newaddr);
823 else
824 printf("%016llx\n", (long long)newaddr);
825 return;
826 }
827
828 symbol = get_symbol_name(&m->symbol_context, addr, &offset);
829
830 if (symbol != NULL) {
831 if (m->cpus[0]->is_32bit)
832 printf("0x%08x", (int)addr);
833 else
834 printf("0x%016llx", (long long)addr);
835 printf(" = %s\n", symbol);
836 } else
837 printf("lookup for '%s' failed\n", cmd_line);
838 }
839
840
841 /*
842 * debugger_cmd_machine():
843 *
844 * Dump info about the currently focused machine.
845 */
846 static void debugger_cmd_machine(struct machine *m, char *cmd_line)
847 {
848 int iadd = DEBUG_INDENTATION;
849
850 if (*cmd_line) {
851 printf("syntax: machine\n");
852 return;
853 }
854
855 debug("machine \"%s\":\n", m->name);
856 debug_indentation(iadd);
857 machine_dumpinfo(m);
858 debug_indentation(-iadd);
859 }
860
861
862 /*
863 * debugger_cmd_ninstrs():
864 */
865 static void debugger_cmd_ninstrs(struct machine *m, char *cmd_line)
866 {
867 int toggle = 1;
868 int previous_mode = m->show_nr_of_instructions;
869
870 if (cmd_line[0] != '\0') {
871 while (cmd_line[0] != '\0' && cmd_line[0] == ' ')
872 cmd_line ++;
873 switch (cmd_line[0]) {
874 case '0':
875 toggle = 0;
876 m->show_nr_of_instructions = 0;
877 break;
878 case '1':
879 toggle = 0;
880 m->show_nr_of_instructions = 1;
881 break;
882 case 'o':
883 case 'O':
884 toggle = 0;
885 switch (cmd_line[1]) {
886 case 'n':
887 case 'N':
888 m->show_nr_of_instructions = 1;
889 break;
890 default:
891 m->show_nr_of_instructions = 0;
892 }
893 break;
894 default:
895 printf("syntax: trace [on|off]\n");
896 return;
897 }
898 }
899
900 if (toggle)
901 m->show_nr_of_instructions = !m->show_nr_of_instructions;
902
903 printf("show_nr_of_instructions = %s",
904 m->show_nr_of_instructions? "ON" : "OFF");
905 if (m->show_nr_of_instructions != previous_mode)
906 printf(" (was: %s)", previous_mode? "ON" : "OFF");
907 printf("\n");
908 }
909
910
911 /*
912 * debugger_cmd_opcodestats():
913 */
914 static void debugger_cmd_opcodestats(struct machine *m, char *cmd_line)
915 {
916 if (*cmd_line) {
917 printf("syntax: opcodestats\n");
918 return;
919 }
920
921 if (!show_opcode_statistics) {
922 printf("You need to start the emulator "
923 "with -s, if you want to gather statistics.\n");
924 } else
925 cpu_show_full_statistics(m);
926 }
927
928
929 /*
930 * debugger_cmd_pause():
931 */
932 static void debugger_cmd_pause(struct machine *m, char *cmd_line)
933 {
934 int cpuid = -1;
935
936 if (cmd_line[0] != '\0')
937 cpuid = atoi(cmd_line);
938 else {
939 printf("syntax: pause cpuid\n");
940 return;
941 }
942
943 if (cpuid < 0 || cpuid >= m->ncpus) {
944 printf("cpu%i doesn't exist.\n", cpuid);
945 return;
946 }
947
948 m->cpus[cpuid]->running ^= 1;
949
950 printf("cpu%i (%s) in machine \"%s\" is now %s\n", cpuid,
951 m->cpus[cpuid]->name, m->name,
952 m->cpus[cpuid]->running? "RUNNING" : "STOPPED");
953 }
954
955
956 /*
957 * debugger_cmd_print():
958 */
959 static void debugger_cmd_print(struct machine *m, char *cmd_line)
960 {
961 int res;
962 uint64_t tmp;
963
964 while (cmd_line[0] != '\0' && cmd_line[0] == ' ')
965 cmd_line ++;
966
967 if (cmd_line[0] == '\0') {
968 printf("syntax: print expr\n");
969 return;
970 }
971
972 res = debugger_parse_name(m, cmd_line, 0, &tmp);
973 switch (res) {
974 case NAME_PARSE_NOMATCH:
975 printf("No match.\n");
976 break;
977 case NAME_PARSE_MULTIPLE:
978 printf("Multiple matches. Try prefixing with %%, $, or @.\n");
979 break;
980 case NAME_PARSE_REGISTER:
981 printf("%s = 0x%llx\n", cmd_line, (long long)tmp);
982 break;
983 case NAME_PARSE_SYMBOL:
984 if (m->cpus[0]->is_32bit)
985 printf("%s = 0x%08x\n", cmd_line, (int)tmp);
986 else
987 printf("%s = 0x%016llx\n", cmd_line, (long long)tmp);
988 break;
989 case NAME_PARSE_NUMBER:
990 printf("0x%llx\n", (long long)tmp);
991 break;
992 }
993 }
994
995
996 /*
997 * debugger_cmd_put():
998 */
999 static void debugger_cmd_put(struct machine *m, char *cmd_line)
1000 {
1001 static char put_type = ' '; /* Remembered across multiple calls. */
1002 char copy[200];
1003 int res, syntax_ok = 0;
1004 char *p, *p2, *q = NULL;
1005 uint64_t addr, data;
1006 unsigned char a_byte;
1007
1008 strncpy(copy, cmd_line, sizeof(copy));
1009 copy[sizeof(copy)-1] = '\0';
1010
1011 /* syntax: put [b|h|w|d|q] addr, data */
1012
1013 p = strchr(copy, ',');
1014 if (p != NULL) {
1015 *p++ = '\0';
1016 while (*p == ' ' && *p)
1017 p++;
1018 while (strlen(copy) >= 1 &&
1019 copy[strlen(copy) - 1] == ' ')
1020 copy[strlen(copy) - 1] = '\0';
1021
1022 /* printf("L = '%s', R = '%s'\n", copy, p); */
1023
1024 q = copy;
1025 p2 = strchr(q, ' ');
1026
1027 if (p2 != NULL) {
1028 *p2 = '\0';
1029 if (strlen(q) != 1) {
1030 printf("Invalid type '%s'\n", q);
1031 return;
1032 }
1033 put_type = *q;
1034 q = p2 + 1;
1035 }
1036
1037 /* printf("type '%c', L '%s', R '%s'\n", put_type, q, p); */
1038 syntax_ok = 1;
1039 }
1040
1041 if (!syntax_ok) {
1042 printf("syntax: put [b|h|w|d|q] addr, data\n");
1043 printf(" b byte (8 bits)\n");
1044 printf(" h half-word (16 bits)\n");
1045 printf(" w word (32 bits)\n");
1046 printf(" d doubleword (64 bits)\n");
1047 printf(" q quad-word (128 bits)\n");
1048 return;
1049 }
1050
1051 if (put_type == ' ') {
1052 printf("No type specified.\n");
1053 return;
1054 }
1055
1056 /* here: q is the address, p is the data. */
1057 res = debugger_parse_name(m, q, 0, &addr);
1058 switch (res) {
1059 case NAME_PARSE_NOMATCH:
1060 printf("Couldn't parse the address.\n");
1061 return;
1062 case NAME_PARSE_MULTIPLE:
1063 printf("Multiple matches for the address."
1064 " Try prefixing with %%, $, or @.\n");
1065 return;
1066 case NAME_PARSE_REGISTER:
1067 case NAME_PARSE_SYMBOL:
1068 case NAME_PARSE_NUMBER:
1069 break;
1070 default:
1071 printf("INTERNAL ERROR in debugger.c.\n");
1072 return;
1073 }
1074
1075 res = debugger_parse_name(m, p, 0, &data);
1076 switch (res) {
1077 case NAME_PARSE_NOMATCH:
1078 printf("Couldn't parse the data.\n");
1079 return;
1080 case NAME_PARSE_MULTIPLE:
1081 printf("Multiple matches for the data value."
1082 " Try prefixing with %%, $, or @.\n");
1083 return;
1084 case NAME_PARSE_REGISTER:
1085 case NAME_PARSE_SYMBOL:
1086 case NAME_PARSE_NUMBER:
1087 break;
1088 default:
1089 printf("INTERNAL ERROR in debugger.c.\n");
1090 return;
1091 }
1092
1093 /* TODO: haha, maybe this should be refactored */
1094
1095 switch (put_type) {
1096 case 'b':
1097 a_byte = data;
1098 if (m->cpus[0]->is_32bit)
1099 printf("0x%08x", (int)addr);
1100 else
1101 printf("0x%016llx", (long long)addr);
1102 printf(": %02x", a_byte);
1103 if (data > 255)
1104 printf(" (NOTE: truncating %0llx)", (long long)data);
1105 res = m->cpus[0]->memory_rw(m->cpus[0], m->cpus[0]->mem, addr,
1106 &a_byte, 1, MEM_WRITE, CACHE_NONE | NO_EXCEPTIONS);
1107 if (!res)
1108 printf(" FAILED!\n");
1109 printf("\n");
1110 return;
1111 case 'h':
1112 if ((addr & 1) != 0)
1113 printf("WARNING: address isn't aligned\n");
1114 if (m->cpus[0]->is_32bit)
1115 printf("0x%08x", (int)addr);
1116 else
1117 printf("0x%016llx", (long long)addr);
1118 printf(": %04x", (int)data);
1119 if (data > 0xffff)
1120 printf(" (NOTE: truncating %0llx)", (long long)data);
1121 res = store_16bit_word(m->cpus[0], addr, data);
1122 if (!res)
1123 printf(" FAILED!\n");
1124 printf("\n");
1125 return;
1126 case 'w':
1127 if ((addr & 3) != 0)
1128 printf("WARNING: address isn't aligned\n");
1129 if (m->cpus[0]->is_32bit)
1130 printf("0x%08x", (int)addr);
1131 else
1132 printf("0x%016llx", (long long)addr);
1133 printf(": %08x", (int)data);
1134 if (data > 0xffffffff && (data >> 32) != 0
1135 && (data >> 32) != 0xffffffff)
1136 printf(" (NOTE: truncating %0llx)", (long long)data);
1137 res = store_32bit_word(m->cpus[0], addr, data);
1138 if (!res)
1139 printf(" FAILED!\n");
1140 printf("\n");
1141 return;
1142 case 'd':
1143 if ((addr & 7) != 0)
1144 printf("WARNING: address isn't aligned\n");
1145 if (m->cpus[0]->is_32bit)
1146 printf("0x%08x", (int)addr);
1147 else
1148 printf("0x%016llx", (long long)addr);
1149 printf(": %016llx", (long long)data);
1150 res = store_64bit_word(m->cpus[0], addr, data);
1151 if (!res)
1152 printf(" FAILED!\n");
1153 printf("\n");
1154 return;
1155 case 'q':
1156 printf("quad-words: TODO\n");
1157 /* TODO */
1158 return;
1159 default:
1160 printf("Unimplemented type '%c'\n", put_type);
1161 return;
1162 }
1163 }
1164
1165
1166 /*
1167 * debugger_cmd_quiet():
1168 */
1169 static void debugger_cmd_quiet(struct machine *m, char *cmd_line)
1170 {
1171 int toggle = 1;
1172 int previous_mode = old_quiet_mode;
1173
1174 if (cmd_line[0] != '\0') {
1175 while (cmd_line[0] != '\0' && cmd_line[0] == ' ')
1176 cmd_line ++;
1177 switch (cmd_line[0]) {
1178 case '0':
1179 toggle = 0;
1180 old_quiet_mode = 0;
1181 break;
1182 case '1':
1183 toggle = 0;
1184 old_quiet_mode = 1;
1185 break;
1186 case 'o':
1187 case 'O':
1188 toggle = 0;
1189 switch (cmd_line[1]) {
1190 case 'n':
1191 case 'N':
1192 old_quiet_mode = 1;
1193 break;
1194 default:
1195 old_quiet_mode = 0;
1196 }
1197 break;
1198 default:
1199 printf("syntax: quiet [on|off]\n");
1200 return;
1201 }
1202 }
1203
1204 if (toggle)
1205 old_quiet_mode = 1 - old_quiet_mode;
1206
1207 printf("quiet_mode = %s", old_quiet_mode? "ON" : "OFF");
1208 if (old_quiet_mode != previous_mode)
1209 printf(" (was: %s)", previous_mode? "ON" : "OFF");
1210 printf("\n");
1211 }
1212
1213
1214 /*
1215 * debugger_cmd_quit():
1216 */
1217 static void debugger_cmd_quit(struct machine *m, char *cmd_line)
1218 {
1219 int i, j, k;
1220 struct emul *e;
1221
1222 if (*cmd_line) {
1223 printf("syntax: quit\n");
1224 return;
1225 }
1226
1227 for (i=0; i<debugger_n_emuls; i++) {
1228 single_step = 0;
1229
1230 e = debugger_emuls[i];
1231 force_debugger_at_exit = 0;
1232
1233 for (j=0; j<e->n_machines; j++) {
1234 struct machine *m = e->machines[j];
1235
1236 for (k=0; k<m->ncpus; k++)
1237 m->cpus[k]->running = 0;
1238
1239 m->exit_without_entering_debugger = 1;
1240 }
1241 }
1242
1243 exit_debugger = 1;
1244 }
1245
1246
1247 /*
1248 * debugger_cmd_reg():
1249 */
1250 static void debugger_cmd_reg(struct machine *m, char *cmd_line)
1251 {
1252 int i, cpuid = -1, coprocnr = -1;
1253 int gprs, coprocs;
1254 char *p;
1255
1256 /* [cpuid][,c] */
1257 if (cmd_line[0] != '\0') {
1258 if (cmd_line[0] != ',') {
1259 cpuid = strtoull(cmd_line, NULL, 0);
1260 if (cpuid < 0 || cpuid >= m->ncpus) {
1261 printf("cpu%i doesn't exist.\n", cpuid);
1262 return;
1263 }
1264 }
1265 p = strchr(cmd_line, ',');
1266 if (p != NULL) {
1267 coprocnr = atoi(p + 1);
1268 if (coprocnr < 0 || coprocnr >= 4) {
1269 printf("Invalid coprocessor number.\n");
1270 return;
1271 }
1272 }
1273 }
1274
1275 gprs = (coprocnr == -1)? 1 : 0;
1276 coprocs = (coprocnr == -1)? 0x0 : (1 << coprocnr);
1277
1278 for (i=0; i<m->ncpus; i++)
1279 if (cpuid == -1 || i == cpuid)
1280 cpu_register_dump(m, m->cpus[i], gprs, coprocs);
1281 }
1282
1283
1284 /*
1285 * debugger_cmd_step():
1286 */
1287 static void debugger_cmd_step(struct machine *m, char *cmd_line)
1288 {
1289 int n = 1;
1290
1291 if (cmd_line[0] != '\0') {
1292 n = strtoull(cmd_line, NULL, 0);
1293 if (n < 1) {
1294 printf("invalid nr of steps\n");
1295 return;
1296 }
1297 }
1298
1299 debugger_n_steps_left_before_interaction = n - 1;
1300
1301 /* Special hack, see debugger() for more info. */
1302 exit_debugger = -1;
1303
1304 strlcpy(repeat_cmd, "step", MAX_CMD_BUFLEN);
1305 }
1306
1307
1308 /*
1309 * debugger_cmd_tlbdump():
1310 *
1311 * Dump each CPU's TLB contents.
1312 */
1313 static void debugger_cmd_tlbdump(struct machine *m, char *cmd_line)
1314 {
1315 int x = -1;
1316 int rawflag = 0;
1317
1318 if (cmd_line[0] != '\0') {
1319 char *p;
1320 if (cmd_line[0] != ',') {
1321 x = strtoull(cmd_line, NULL, 0);
1322 if (x < 0 || x >= m->ncpus) {
1323 printf("cpu%i doesn't exist.\n", x);
1324 return;
1325 }
1326 }
1327 p = strchr(cmd_line, ',');
1328 if (p != NULL) {
1329 switch (p[1]) {
1330 case 'r':
1331 case 'R':
1332 rawflag = 1;
1333 break;
1334 default:
1335 printf("Unknown tlbdump flag.\n");
1336 printf("syntax: tlbdump [cpuid][,r]\n");
1337 return;
1338 }
1339 }
1340 }
1341
1342 cpu_tlbdump(m, x, rawflag);
1343 }
1344
1345
1346 /*
1347 * debugger_cmd_trace():
1348 */
1349 static void debugger_cmd_trace(struct machine *m, char *cmd_line)
1350 {
1351 int toggle = 1;
1352 int previous_mode = old_show_trace_tree;
1353
1354 if (cmd_line[0] != '\0') {
1355 while (cmd_line[0] != '\0' && cmd_line[0] == ' ')
1356 cmd_line ++;
1357 switch (cmd_line[0]) {
1358 case '0':
1359 toggle = 0;
1360 old_show_trace_tree = 0;
1361 break;
1362 case '1':
1363 toggle = 0;
1364 old_show_trace_tree = 1;
1365 break;
1366 case 'o':
1367 case 'O':
1368 toggle = 0;
1369 switch (cmd_line[1]) {
1370 case 'n':
1371 case 'N':
1372 old_show_trace_tree = 1;
1373 break;
1374 default:
1375 old_show_trace_tree = 0;
1376 }
1377 break;
1378 default:
1379 printf("syntax: trace [on|off]\n");
1380 return;
1381 }
1382 }
1383
1384 if (toggle)
1385 old_show_trace_tree = 1 - old_show_trace_tree;
1386
1387 printf("show_trace_tree = %s", old_show_trace_tree? "ON" : "OFF");
1388 if (old_show_trace_tree != previous_mode)
1389 printf(" (was: %s)", previous_mode? "ON" : "OFF");
1390 printf("\n");
1391
1392 if (m->bintrans_enable && old_show_trace_tree)
1393 printf("NOTE: the trace tree functionality doesn't "
1394 "work very well with bintrans!\n");
1395 }
1396
1397
1398 /*
1399 * debugger_cmd_unassemble():
1400 *
1401 * Dump emulated memory as instructions.
1402 *
1403 * syntax: unassemble [addr [endaddr]]
1404 */
1405 static void debugger_cmd_unassemble(struct machine *m, char *cmd_line)
1406 {
1407 uint64_t addr, addr_start, addr_end;
1408 struct cpu *c;
1409 struct memory *mem;
1410 char *p = NULL;
1411 int r, lines_left = -1;
1412
1413 if (cmd_line[0] != '\0') {
1414 uint64_t tmp;
1415 char *tmps = strdup(cmd_line);
1416
1417 /* addr: */
1418 p = strchr(tmps, ' ');
1419 if (p != NULL)
1420 *p = '\0';
1421 r = debugger_parse_name(m, tmps, 0, &tmp);
1422 free(tmps);
1423
1424 if (r == NAME_PARSE_NOMATCH || r == NAME_PARSE_MULTIPLE) {
1425 printf("Unparsable address: %s\n", cmd_line);
1426 return;
1427 } else {
1428 last_unasm_addr = tmp;
1429 }
1430
1431 p = strchr(cmd_line, ' ');
1432 }
1433
1434 addr_start = last_unasm_addr;
1435
1436 if (addr_start == MAGIC_UNTOUCHED) {
1437 uint64_t tmp;
1438 int match_register = 0;
1439 cpu_register_match(m, "pc", 0, &tmp, &match_register);
1440 if (match_register) {
1441 addr_start = tmp;
1442 } else {
1443 printf("No starting address.\n");
1444 return;
1445 }
1446 }
1447
1448 addr_end = addr_start + 1000;
1449
1450 /* endaddr: */
1451 if (p != NULL) {
1452 while (*p == ' ' && *p)
1453 p++;
1454 r = debugger_parse_name(m, p, 0, &addr_end);
1455 if (r == NAME_PARSE_NOMATCH || r == NAME_PARSE_MULTIPLE) {
1456 printf("Unparsable address: %s\n", cmd_line);
1457 return;
1458 }
1459 } else
1460 lines_left = 20;
1461
1462 if (m->cpus == NULL) {
1463 printf("No cpus (?)\n");
1464 return;
1465 }
1466 c = m->cpus[m->bootstrap_cpu];
1467 if (c == NULL) {
1468 printf("m->cpus[m->bootstrap_cpu] = NULL\n");
1469 return;
1470 }
1471 mem = m->cpus[m->bootstrap_cpu]->mem;
1472
1473 addr = addr_start;
1474
1475 ctrl_c = 0;
1476
1477 while (addr < addr_end) {
1478 unsigned int i, len;
1479 unsigned char buf[17]; /* TODO: How long can an
1480 instruction be, on weird archs? */
1481 memset(buf, 0, sizeof(buf));
1482
1483 for (i=0; i<sizeof(buf); i++)
1484 c->memory_rw(c, mem, addr+i, buf+i, 1, MEM_READ,
1485 CACHE_NONE | NO_EXCEPTIONS);
1486
1487 len = cpu_disassemble_instr(m, c, buf, 0, addr, 0);
1488
1489 if (ctrl_c)
1490 return;
1491 if (len == 0)
1492 break;
1493
1494 addr += len;
1495
1496 if (lines_left != -1) {
1497 lines_left --;
1498 if (lines_left == 0)
1499 break;
1500 }
1501 }
1502
1503 last_unasm_addr = addr;
1504
1505 strlcpy(repeat_cmd, "unassemble", MAX_CMD_BUFLEN);
1506 }
1507
1508
1509 /*
1510 * debugger_cmd_version():
1511 */
1512 static void debugger_cmd_version(struct machine *m, char *cmd_line)
1513 {
1514 if (*cmd_line) {
1515 printf("syntax: version\n");
1516 return;
1517 }
1518
1519 #ifdef VERSION
1520 printf("%s, %s\n", VERSION, COMPILE_DATE);
1521 #else
1522 printf("(no version), %s\n", COMPILE_DATE);
1523 #endif
1524 }
1525
1526
1527 struct cmd {
1528 char *name;
1529 char *args;
1530 int tmp_flag;
1531 void (*f)(struct machine *, char *cmd_line);
1532 char *description;
1533 };
1534
1535 static struct cmd cmds[] = {
1536 { "breakpoint", "...", 0, debugger_cmd_breakpoint,
1537 "manipulate breakpoints" },
1538
1539 { "bintrans", "[on|off]", 0, debugger_cmd_bintrans,
1540 "toggle bintrans on or off" },
1541
1542 /* NOTE: Try to keep 'c' down to only one command. Having 'continue'
1543 available as a one-letter command is very convenient. */
1544
1545 { "continue", "", 0, debugger_cmd_continue,
1546 "continue execution" },
1547
1548 { "device", "...", 0, debugger_cmd_device,
1549 "show info about (or manipulate) devices" },
1550
1551 { "dump", "[addr [endaddr]]", 0, debugger_cmd_dump,
1552 "dump memory contents in hex and ASCII" },
1553
1554 { "emuls", "", 0, debugger_cmd_emuls,
1555 "print a summary of all current emuls" },
1556
1557 { "focus", "x[,y]", 0, debugger_cmd_focus,
1558 "changes focus to machine x (in emul y)" },
1559
1560 { "help", "", 0, debugger_cmd_help,
1561 "print this help message" },
1562
1563 { "itrace", "", 0, debugger_cmd_itrace,
1564 "toggle instruction_trace on or off" },
1565
1566 { "lookup", "name|addr", 0, debugger_cmd_lookup,
1567 "lookup a symbol by name or address" },
1568
1569 { "machine", "", 0, debugger_cmd_machine,
1570 "print a summary of the current machine" },
1571
1572 { "ninstrs", "[on|off]", 0, debugger_cmd_ninstrs,
1573 "toggle (set or unset) show_nr_of_instructions" },
1574
1575 { "opcodestats", "", 0, debugger_cmd_opcodestats,
1576 "show opcode statistics" },
1577
1578 { "pause", "cpuid", 0, debugger_cmd_pause,
1579 "pause (or unpause) a CPU" },
1580
1581 { "print", "expr", 0, debugger_cmd_print,
1582 "evaluate an expression without side-effects" },
1583
1584 { "put", "[b|h|w|d|q] addr, data", 0, debugger_cmd_put,
1585 "modify emulated memory contents" },
1586
1587 { "quiet", "[on|off]", 0, debugger_cmd_quiet,
1588 "toggle quiet_mode on or off" },
1589
1590 { "quit", "", 0, debugger_cmd_quit,
1591 "quit the emulator" },
1592
1593 { "reg", "[cpuid][,c]", 0, debugger_cmd_reg,
1594 "show GPRs (or coprocessor c's registers)" },
1595
1596 /* NOTE: Try to keep 's' down to only one command. Having 'step'
1597 available as a one-letter command is very convenient. */
1598
1599 { "step", "[n]", 0, debugger_cmd_step,
1600 "single-step one (or n) instruction(s)" },
1601
1602 { "tlbdump", "[cpuid][,r]", 0, debugger_cmd_tlbdump,
1603 "dump TLB contents (add ',r' for raw data)" },
1604
1605 { "trace", "[on|off]", 0, debugger_cmd_trace,
1606 "toggle show_trace_tree on or off" },
1607
1608 { "unassemble", "[addr [endaddr]]", 0, debugger_cmd_unassemble,
1609 "dump memory contents as instructions" },
1610
1611 { "version", "", 0, debugger_cmd_version,
1612 "print version information" },
1613
1614 /* Note: NULL handler. */
1615 { "x = expr", "", 0, NULL, "generic assignment" },
1616
1617 { NULL, NULL, 0, NULL, NULL }
1618 };
1619
1620
1621 /*
1622 * debugger_cmd_help():
1623 *
1624 * Print a list of available commands.
1625 *
1626 * NOTE: This is placed after the cmds[] array, because it needs to
1627 * access it.
1628 *
1629 * TODO: Command completion (ie just type "help s" for "help step").
1630 */
1631 static void debugger_cmd_help(struct machine *m, char *cmd_line)
1632 {
1633 int only_one = 0, only_one_match = 0;
1634 char *nlines_env = getenv("LINES");
1635 int nlines = atoi(nlines_env != NULL? nlines_env : "999999"), curlines;
1636 size_t i, j, max_name_len = 0;
1637
1638 if (cmd_line[0] != '\0') {
1639 only_one = 1;
1640 }
1641
1642 i = 0;
1643 while (cmds[i].name != NULL) {
1644 size_t a = strlen(cmds[i].name);
1645 if (cmds[i].args != NULL)
1646 a += 1 + strlen(cmds[i].args);
1647 if (a > max_name_len)
1648 max_name_len = a;
1649 i++;
1650 }
1651
1652 curlines = 0;
1653 if (!only_one) {
1654 printf("Available commands:\n");
1655 curlines++;
1656 }
1657
1658 i = 0;
1659 while (cmds[i].name != NULL) {
1660 char buf[100];
1661 snprintf(buf, sizeof(buf), "%s", cmds[i].name);
1662
1663 if (only_one) {
1664 if (strcmp(cmds[i].name, cmd_line) != 0) {
1665 i++;
1666 continue;
1667 }
1668 only_one_match = 1;
1669 }
1670
1671 if (cmds[i].args != NULL)
1672 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
1673 " %s", cmds[i].args);
1674
1675 printf(" ");
1676 for (j=0; j<max_name_len; j++)
1677 if (j < strlen(buf))
1678 printf("%c", buf[j]);
1679 else
1680 printf(" ");
1681
1682 printf(" %s\n", cmds[i].description);
1683 i++;
1684
1685 curlines ++;
1686 if (curlines >= nlines - 1) {
1687 char ch;
1688 printf("-- more --"); fflush(stdout);
1689 ch = debugger_readchar();
1690 printf("\n");
1691 if (ch == 'q' || ch == 'Q')
1692 return;
1693 curlines = 0;
1694 }
1695 }
1696
1697 if (only_one) {
1698 if (!only_one_match)
1699 printf("%s: no such command\n", cmd_line);
1700 return;
1701 }
1702
1703 /* TODO: generalize/refactor */
1704 curlines += 8;
1705 if (curlines > nlines - 1) {
1706 char ch;
1707 printf("-- more --"); fflush(stdout);
1708 ch = debugger_readchar();
1709 printf("\n");
1710 if (ch == 'q' || ch == 'Q')
1711 return;
1712 curlines = 0;
1713 }
1714
1715 printf("\nIn generic assignments, x must be a register, and expr can be"
1716 " a register, a\nnumeric value, or a symbol name (+ an optional "
1717 "numeric offset). In case there\nare multiple matches (i.e. a "
1718 "symbol that has the same name as a register), you\nmay add a "
1719 "prefix character as a hint: '%%' for registers, '@' for symbols,"
1720 " and\n'$' for numeric values. Use 0x for hexadecimal values.\n");
1721 }
1722
1723
1724 /****************************************************************************/
1725
1726
1727 /*
1728 * debugger_assignment():
1729 *
1730 * cmd contains something like "pc=0x80001000", or "r31=memcpy+0x40".
1731 */
1732 void debugger_assignment(struct machine *m, char *cmd)
1733 {
1734 char *left, *right;
1735 int res_left, res_right;
1736 uint64_t tmp;
1737 uint64_t old_pc = m->cpus[0]->pc; /* TODO: multiple cpus? */
1738
1739 left = malloc(MAX_CMD_BUFLEN);
1740 if (left == NULL) {
1741 fprintf(stderr, "out of memory in debugger_assignment()\n");
1742 exit(1);
1743 }
1744 strlcpy(left, cmd, MAX_CMD_BUFLEN);
1745 right = strchr(left, '=');
1746 if (right == NULL) {
1747 fprintf(stderr, "internal error in the debugger\n");
1748 exit(1);
1749 }
1750 *right = '\0';
1751
1752 /* Remove trailing spaces in left: */
1753 while (strlen(left) >= 1 && left[strlen(left)-1] == ' ')
1754 left[strlen(left)-1] = '\0';
1755
1756 /* Remove leading spaces in right: */
1757 right++;
1758 while (*right == ' ' && *right != '\0')
1759 right++;
1760
1761 /* printf("left = '%s'\nright = '%s'\n", left, right); */
1762
1763 res_right = debugger_parse_name(m, right, 0, &tmp);
1764 switch (res_right) {
1765 case NAME_PARSE_NOMATCH:
1766 printf("No match for the right-hand side of the assignment.\n");
1767 break;
1768 case NAME_PARSE_MULTIPLE:
1769 printf("Multiple matches for the right-hand side of the "
1770 "assignment.\n");
1771 break;
1772 default:
1773 res_left = debugger_parse_name(m, left, 1, &tmp);
1774 switch (res_left) {
1775 case NAME_PARSE_NOMATCH:
1776 printf("No match for the left-hand side of the "
1777 "assignment.\n");
1778 break;
1779 case NAME_PARSE_MULTIPLE:
1780 printf("Multiple matches for the left-hand side "
1781 "of the assignment.\n");
1782 break;
1783 default:
1784 debugger_cmd_print(m, left);
1785 }
1786 }
1787
1788 /*
1789 * If the PC has changed, then release any breakpoint we were
1790 * currently stopped at.
1791 *
1792 * TODO: multiple cpus?
1793 */
1794 if (old_pc != m->cpus[0]->pc)
1795 single_step_breakpoint = 0;
1796
1797 free(left);
1798 }
1799
1800
1801 /*
1802 * debugger_execute_cmd():
1803 */
1804 void debugger_execute_cmd(char *cmd, int cmd_len)
1805 {
1806 int i, n, i_match, matchlen;
1807
1808 /*
1809 * Is there a '=' on the command line? Then try to do an
1810 * assignment. (Only if there is just one word, followed
1811 * by the '=' sign. This makes it possible to use commands
1812 * such as "device add name addr=xyz".)
1813 */
1814 if (strchr(cmd, '=') != NULL) {
1815 /* Count the nr of words: */
1816 int nw = 0, inword = 0;
1817 char *p = cmd;
1818 while (*p) {
1819 if (*p == '=')
1820 break;
1821 if (*p != ' ') {
1822 if (!inword)
1823 nw ++;
1824 inword = 1;
1825 } else
1826 inword = 0;
1827 p++;
1828 }
1829
1830 if (nw == 1) {
1831 debugger_assignment(debugger_machine, cmd);
1832 return;
1833 }
1834 }
1835
1836 i = 0;
1837 while (cmds[i].name != NULL)
1838 cmds[i++].tmp_flag = 0;
1839
1840 /* How many chars in cmd to match against: */
1841 matchlen = 0;
1842 while (isalpha((int)cmd[matchlen]))
1843 matchlen ++;
1844
1845 /* Check for a command name match: */
1846 n = i = i_match = 0;
1847 while (cmds[i].name != NULL) {
1848 if (strncasecmp(cmds[i].name, cmd, matchlen) == 0
1849 && cmds[i].f != NULL) {
1850 cmds[i].tmp_flag = 1;
1851 i_match = i;
1852 n++;
1853 }
1854 i++;
1855 }
1856
1857 /* No match? */
1858 if (n == 0) {
1859 printf("Unknown command '%s'. Type 'help' for help.\n", cmd);
1860 return;
1861 }
1862
1863 /* More than one match? */
1864 if (n > 1) {
1865 printf("Ambiguous command '%s': ", cmd);
1866 i = 0;
1867 while (cmds[i].name != NULL) {
1868 if (cmds[i].tmp_flag)
1869 printf(" %s", cmds[i].name);
1870 i++;
1871 }
1872 printf("\n");
1873 return;
1874 }
1875
1876 /* Exactly one match: */
1877 if (cmds[i_match].f != NULL) {
1878 char *p = cmd + matchlen;
1879 /* Remove leading whitespace from the args... */
1880 while (*p != '\0' && *p == ' ')
1881 p++;
1882
1883 /* ... and run the command: */
1884 cmds[i_match].f(debugger_machine, p);
1885 } else
1886 printf("FATAL ERROR: internal error in debugger.c:"
1887 " no handler for this command?\n");
1888 }
1889
1890
1891 /*
1892 * debugger_readline():
1893 *
1894 * Read a line from the terminal.
1895 */
1896 static char *debugger_readline(void)
1897 {
1898 int ch, i, j, n, i_match, reallen, cmd_len, cursor_pos;
1899 int read_from_index = last_cmd_index;
1900 char *cmd = last_cmd[last_cmd_index];
1901
1902 cmd_len = 0; cmd[0] = '\0';
1903 printf("GXemul> ");
1904 fflush(stdout);
1905
1906 ch = '\0';
1907 cmd_len = 0;
1908 cursor_pos = 0;
1909
1910 while (ch != '\n') {
1911 ch = debugger_readchar();
1912
1913 if ((ch == '\b' || ch == 127) && cursor_pos > 0) {
1914 /* Backspace. */
1915 cursor_pos --;
1916 cmd_len --;
1917 memmove(cmd + cursor_pos, cmd + cursor_pos + 1,
1918 cmd_len);
1919 cmd[cmd_len] = '\0';
1920 printf("\b");
1921 for (i=cursor_pos; i<cmd_len; i++)
1922 printf("%c", cmd[i]);
1923 printf(" \b");
1924 for (i=cursor_pos; i<cmd_len; i++)
1925 printf("\b");
1926 } else if (ch == 4 && cmd_len > 0 && cursor_pos < cmd_len) {
1927 /* CTRL-D: Delete. */
1928 cmd_len --;
1929 memmove(cmd + cursor_pos, cmd + cursor_pos + 1,
1930 cmd_len);
1931 cmd[cmd_len] = '\0';
1932 for (i=cursor_pos; i<cmd_len; i++)
1933 printf("%c", cmd[i]);
1934 printf(" \b");
1935 for (i=cursor_pos; i<cmd_len; i++)
1936 printf("\b");
1937 } else if (ch == 1) {
1938 /* CTRL-A: Start of line. */
1939 while (cursor_pos > 0) {
1940 cursor_pos --;
1941 printf("\b");
1942 }
1943 } else if (ch == 2) {
1944 /* CTRL-B: Backwards one character. */
1945 if (cursor_pos > 0) {
1946 printf("\b");
1947 cursor_pos --;
1948 }
1949 } else if (ch == 5) {
1950 /* CTRL-E: End of line. */
1951 while (cursor_pos < cmd_len) {
1952 printf("%c", cmd[cursor_pos]);
1953 cursor_pos ++;
1954 }
1955 } else if (ch == 6) {
1956 /* CTRL-F: Forward one character. */
1957 if (cursor_pos < cmd_len) {
1958 printf("%c",
1959 cmd[cursor_pos]);
1960 cursor_pos ++;
1961 }
1962 } else if (ch == 11) {
1963 /* CTRL-K: Kill to end of line. */
1964 for (i=0; i<MAX_CMD_BUFLEN; i++)
1965 console_makeavail(MAIN_CONSOLE, 4); /* :-) */
1966 } else if (ch == 14 || ch == 16) {
1967 /* CTRL-P: Previous line in the command history,
1968 CTRL-N: next line */
1969 do {
1970 if (ch == 14 &&
1971 read_from_index == last_cmd_index)
1972 break;
1973 if (ch == 16)
1974 i = read_from_index - 1;
1975 else
1976 i = read_from_index + 1;
1977
1978 if (i < 0)
1979 i = N_PREVIOUS_CMDS - 1;
1980 if (i >= N_PREVIOUS_CMDS)
1981 i = 0;
1982
1983 /* Special case: pressing 'down'
1984 to reach last_cmd_index: */
1985 if (i == last_cmd_index) {
1986 read_from_index = i;
1987 for (i=cursor_pos; i<cmd_len;
1988 i++)
1989 printf(" ");
1990 for (i=cmd_len-1; i>=0; i--)
1991 printf("\b \b");
1992 cmd[0] = '\0';
1993 cmd_len = cursor_pos = 0;
1994 } else if (last_cmd[i][0] != '\0') {
1995 /* Copy from old line: */
1996 read_from_index = i;
1997 for (i=cursor_pos; i<cmd_len;
1998 i++)
1999 printf(" ");
2000 for (i=cmd_len-1; i>=0; i--)
2001 printf("\b \b");
2002 strlcpy(cmd,
2003 last_cmd[read_from_index],
2004 MAX_CMD_BUFLEN);
2005 cmd_len = strlen(cmd);
2006 printf("%s", cmd);
2007 cursor_pos = cmd_len;
2008 }
2009 } while (0);
2010 } else if (ch >= ' ' && cmd_len < MAX_CMD_BUFLEN-1) {
2011 /* Visible character: */
2012 memmove(cmd + cursor_pos + 1, cmd + cursor_pos,
2013 cmd_len - cursor_pos);
2014 cmd[cursor_pos] = ch;
2015 cmd_len ++;
2016 cursor_pos ++;
2017 cmd[cmd_len] = '\0';
2018 printf("%c", ch);
2019 for (i=cursor_pos; i<cmd_len; i++)
2020 printf("%c", cmd[i]);
2021 for (i=cursor_pos; i<cmd_len; i++)
2022 printf("\b");
2023 } else if (ch == '\r' || ch == '\n') {
2024 ch = '\n';
2025 printf("\n");
2026 } else if (ch == '\t') {
2027 /* Super-simple tab-completion: */
2028 i = 0;
2029 while (cmds[i].name != NULL)
2030 cmds[i++].tmp_flag = 0;
2031
2032 /* Check for a (partial) command match: */
2033 n = i = i_match = 0;
2034 while (cmds[i].name != NULL) {
2035 if (strncasecmp(cmds[i].name, cmd,
2036 cmd_len) == 0) {
2037 cmds[i].tmp_flag = 1;
2038 i_match = i;
2039 n++;
2040 }
2041 i++;
2042 }
2043
2044 switch (n) {
2045 case 0: /* Beep. */
2046 printf("\a");
2047 break;
2048 case 1: /* Add the rest of the command: */
2049 reallen = strlen(cmds[i_match].name);
2050 for (i=cmd_len; i<reallen; i++)
2051 console_makeavail(MAIN_CONSOLE,
2052 cmds[i_match].name[i]);
2053 /* ... and a space, if the command takes
2054 any arguments: */
2055 if (cmds[i_match].args != NULL &&
2056 cmds[i_match].args[0] != '\0')
2057 console_makeavail(MAIN_CONSOLE, ' ');
2058 break;
2059 default:
2060 /* Show all possible commands: */
2061 printf("\a\n"); /* Beep. :-) */
2062 i = 0; /* i = cmds index */
2063 j = 0; /* j = # of cmds printed */
2064 while (cmds[i].name != NULL) {
2065 if (cmds[i].tmp_flag) {
2066 size_t q;
2067 if (j == 0)
2068 printf(" ");
2069 printf("%s",
2070 cmds[i].name);
2071 j++;
2072 if (j != 6)
2073 for (q=0; q<13-strlen(
2074 cmds[i].name); q++)
2075 printf(" ");
2076 if (j == 6) {
2077 printf("\n");
2078 j = 0;
2079 }
2080 }
2081 i++;
2082 }
2083 if (j != 0)
2084 printf("\n");
2085 printf("GXemul> ");
2086 for (i=0; i<cmd_len; i++)
2087 printf("%c", cmd[i]);
2088 }
2089 } else if (ch == 27) {
2090 /* Escape codes: (cursor keys etc) */
2091 while ((ch = console_readchar(MAIN_CONSOLE)) < 0)
2092 usleep(1);
2093 if (ch == '[' || ch == 'O') {
2094 while ((ch = console_readchar(MAIN_CONSOLE))
2095 < 0)
2096 usleep(1);
2097 switch (ch) {
2098 case '2': /* 2~ = ins */
2099 case '5': /* 5~ = pgup */
2100 case '6': /* 6~ = pgdn */
2101 /* TODO: Ugly hack, but might work. */
2102 while ((ch = console_readchar(
2103 MAIN_CONSOLE)) < 0)
2104 usleep(1);
2105 /* Do nothing for these keys. */
2106 break;
2107 case '3': /* 3~ = delete */
2108 /* TODO: Ugly hack, but might work. */
2109 while ((ch = console_readchar(
2110 MAIN_CONSOLE)) < 0)
2111 usleep(1);
2112 console_makeavail(MAIN_CONSOLE, '\b');
2113 break;
2114 case 'A': /* Up. */
2115 /* Up cursor ==> CTRL-P */
2116 console_makeavail(MAIN_CONSOLE, 16);
2117 break;
2118 case 'B': /* Down. */
2119 /* Down cursor ==> CTRL-N */
2120 console_makeavail(MAIN_CONSOLE, 14);
2121 break;
2122 case 'C':
2123 /* Right cursor ==> CTRL-F */
2124 console_makeavail(MAIN_CONSOLE, 6);
2125 break;
2126 case 'D': /* Left */
2127 /* Left cursor ==> CTRL-B */
2128 console_makeavail(MAIN_CONSOLE, 2);
2129 break;
2130 case 'F':
2131 /* End ==> CTRL-E */
2132 console_makeavail(MAIN_CONSOLE, 5);
2133 break;
2134 case 'H':
2135 /* Home ==> CTRL-A */
2136 console_makeavail(MAIN_CONSOLE, 1);
2137 break;
2138 }
2139 }
2140 }
2141
2142 fflush(stdout);
2143 }
2144
2145 return cmd;
2146 }
2147
2148
2149 /*
2150 * debugger():
2151 *
2152 * This is a loop, which reads a command from the terminal, and executes it.
2153 */
2154 void debugger(void)
2155 {
2156 int i, cmd_len;
2157 char *cmd;
2158
2159 if (debugger_n_steps_left_before_interaction > 0) {
2160 debugger_n_steps_left_before_interaction --;
2161 return;
2162 }
2163
2164 /*
2165 * Clear all dyntrans translations, because otherwise things would
2166 * become to complex to keep in sync.
2167 */
2168 /* TODO: In all machines */
2169 for (i=0; i<debugger_machine->ncpus; i++)
2170 if (debugger_machine->cpus[i]->translation_cache != NULL)
2171 cpu_create_or_reset_tc(debugger_machine->cpus[i]);
2172
2173 exit_debugger = 0;
2174
2175 while (!exit_debugger) {
2176 /* Read a line from the terminal: */
2177 cmd = debugger_readline();
2178 cmd_len = strlen(cmd);
2179
2180 /* Remove spaces: */
2181 while (cmd_len > 0 && cmd[0]==' ')
2182 memmove(cmd, cmd+1, cmd_len --);
2183 while (cmd_len > 0 && cmd[cmd_len-1] == ' ')
2184 cmd[(cmd_len--)-1] = '\0';
2185
2186 /* No command? Then try reading another line. */
2187 if (cmd_len == 0) {
2188 /* Special case for repeated commands: */
2189 if (repeat_cmd[0] != '\0')
2190 strlcpy(cmd, repeat_cmd, MAX_CMD_BUFLEN);
2191 else
2192 continue;
2193 } else {
2194 last_cmd_index ++;
2195 if (last_cmd_index >= N_PREVIOUS_CMDS)
2196 last_cmd_index = 0;
2197
2198 repeat_cmd[0] = '\0';
2199 }
2200
2201 debugger_execute_cmd(cmd, cmd_len);
2202
2203 /* Special hack for the "step" command: */
2204 if (exit_debugger == -1)
2205 return;
2206 }
2207
2208 gettimeofday(&debugger_machine->starttime, NULL);
2209 debugger_machine->ncycles_since_gettimeofday = 0;
2210
2211 single_step = 0;
2212 debugger_machine->instruction_trace = old_instruction_trace;
2213 debugger_machine->show_trace_tree = old_show_trace_tree;
2214 quiet_mode = old_quiet_mode;
2215 }
2216
2217
2218 /*
2219 * debugger_reset():
2220 *
2221 * This function should be called before calling debugger(), when it is
2222 * absolutely necessary that debugger() is interactive. Otherwise, it might
2223 * return without doing anything, such as when single-stepping multiple
2224 * instructions at a time.
2225 */
2226 void debugger_reset(void)
2227 {
2228 debugger_n_steps_left_before_interaction = 0;
2229 }
2230
2231
2232 /*
2233 * debugger_init():
2234 *
2235 * Must be called before any other debugger function is used.
2236 */
2237 void debugger_init(struct emul **emuls, int n_emuls)
2238 {
2239 int i;
2240
2241 debugger_n_emuls = n_emuls;
2242 debugger_emuls = emuls;
2243
2244 if (n_emuls < 1) {
2245 fprintf(stderr, "\nERROR: No emuls (?)\n");
2246 exit(1);
2247 }
2248
2249 debugger_emul = emuls[0];
2250 if (emuls[0]->n_machines < 1) {
2251 fprintf(stderr, "\nERROR: No machines in emuls[0], "
2252 "cannot handle this situation yet.\n\n");
2253 exit(1);
2254 }
2255
2256 debugger_machine = emuls[0]->machines[0];
2257
2258 for (i=0; i<N_PREVIOUS_CMDS; i++) {
2259 last_cmd[i] = malloc(MAX_CMD_BUFLEN);
2260 if (last_cmd[i] == NULL) {
2261 fprintf(stderr, "debugger_init(): out of memory\n");
2262 exit(1);
2263 }
2264 last_cmd[i][0] = '\0';
2265 }
2266
2267 last_cmd_index = 0;
2268 repeat_cmd[0] = '\0';
2269 }
2270

  ViewVC Help
Powered by ViewVC 1.1.26