diff options
Diffstat (limited to 'debug.c')
-rw-r--r-- | debug.c | 5732 |
1 files changed, 5732 insertions, 0 deletions
diff --git a/debug.c b/debug.c new file mode 100644 index 00000000..4b70f2e2 --- /dev/null +++ b/debug.c @@ -0,0 +1,5732 @@ +/* + * debug.c - gawk debugger + */ + +/* + * Copyright (C) 2004, 2010 the Free Software Foundation, Inc. + * + * This file is part of GAWK, the GNU implementation of the + * AWK Programming Language. + * + * GAWK is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * GAWK is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "awk.h" +#include "cmd.h" + +#ifndef O_RDONLY +#include <fcntl.h> /* open() */ +#endif + +extern int exiting; +extern SRCFILE *srcfiles; +extern INSTRUCTION *rule_list; +extern INSTRUCTION *code_block; +extern NODE **fcall_list; +extern long fcall_count; +extern FILE *output_fp; +extern IOBUF *curfile; +extern const char *command_file; +extern int r_interpret(INSTRUCTION *); +extern int zzparse(void); +#define read_command() (void) zzparse() + +extern int free_instruction(INSTRUCTION *, int *); +extern void destroy_symbol(char *name); +extern const char *redir2str(int redirtype); + +static char *linebuf = NULL; /* used to print a single line of source */ +static size_t linebuf_len; + +FILE *out_fp; +char *dPrompt; +char *commands_Prompt = "> "; /* breakpoint or watchpoint commands list */ +char *eval_Prompt = "@> "; /* awk statement(s) */ + +int input_from_tty = FALSE; +int input_fd; + +static SRCFILE *cur_srcfile; +static long cur_frame = 0; +static INSTRUCTION *cur_pc; +int cur_rule = 0; + +static int prog_running = FALSE; + +struct condition { + INSTRUCTION *code; + CONTEXT *ctxt; + char *expr; +}; + +struct commands_item { + struct commands_item *next; + struct commands_item *prev; + int cmd; + char *cmd_string; + CMDARG *arg; +}; + +/* breakpoint structure */ +typedef struct break_point { + struct break_point *next; + struct break_point *prev; + int number; + + long ignore_count; + long hit_count; + char *src; + INSTRUCTION *bpi; /* Op_breakpoint */ + + struct commands_item commands; /* list of commands to run */ + int silent; + + struct condition cndn; + + short flags; +#define BP_ENABLE 1 +#define BP_ENABLE_ONCE 2 /* enable once */ +#define BP_TEMP 4 +#define BP_IGNORE 8 + +} BREAKPOINT; + +static BREAKPOINT breakpoints = { &breakpoints, &breakpoints, 0 }; + +/* do_save -- save command */ +static int sess_history_base = 0; + +/* 'list' command */ +static int last_printed_line = 0; +static int last_print_count; /* # of lines printed */ + +/* watch or display item */ +struct list_item { + struct list_item *next; + struct list_item *prev; + int number; /* item number */ + + NODE *symbol; /* variable or function param */ + NODE **subs; /* subscripts */ + int num_subs; /* subscript(dimension) count */ + char *sname; /* symbol or param name */ + + long fcall_count; + + struct commands_item commands; + int silent; + struct condition cndn; + + /* This is for the value of the watched item */ + union { + NODE *n; + long l; + } value[2]; +#define cur_value value[0].n +#define cur_size value[0].l +#define old_value value[1].n +#define old_size value[1].l + + int flags; +#define PARAM 1 +#define SUBSCRIPT 2 +#define FIELD_NUM 4 +#define OLD_IS_ARRAY 8 /* old item is array */ +#define CUR_IS_ARRAY 16 /* current item is array */ +}; + +#define IS_PARAM(d) (((d)->flags & PARAM) != 0) +#define IS_SUBSCRIPT(d) (((d)->flags & SUBSCRIPT) != 0) +#define IS_FIELD(d) (((d)->flags & FIELD_NUM) != 0) +#define WATCHING_ARRAY(d) (((d)->flags & CUR_IS_ARRAY) != 0) + +static struct list_item display_list = { &display_list, &display_list, 0 }; +static struct list_item watch_list = { &watch_list, &watch_list, 0 }; + + +/* Structure to maintain data for processing debugger commands */ + +static struct { + long fcall_count; /* 'finish', 'until', 'next', 'step', 'nexti' commands */ + int sourceline; /* source line number last + * time we stopped execution, + * used by next, until and step commands + */ + char *source; /* next, until and step */ + + INSTRUCTION *pc; /* 'until' and 'return' commands */ + int repeat_count; /* 'step', 'next', 'stepi', 'nexti' commands */ + int print_frame; /* print frame info, 'finish' and 'until' */ + int print_ret; /* print returned value, 'finish' */ + int break_point; /* non-zero (breakpoint number) if stopped at break point */ + int watch_point; /* non-zero (watchpoint number) if stopped at watch point */ + + int (*check_func)(INSTRUCTION **); /* function to decide when to suspend + * awk interpreter and return control + * to debugger command interpreter. + */ + + enum argtype command; /* command type */ +} stop; + + +/* restart related stuff */ +extern char **d_argv; /* copy of argv array */ +static int need_restart = FALSE; +enum { BREAK=1, WATCH, DISPLAY, HISTORY, OPTION }; +static const char *const env_variable[] = { +"", +"DGAWK_BREAK", +"DGAWK_WATCH", +"DGAWK_DISPLAY", +"DGAWK_HISTORY", +"DGAWK_OPTION", +}; +static void serialize(int ); +static void unserialize(int ); +static const char *commands_string = NULL; +static int commands_string_len = 0; +static char line_sep; +#define FSEP (char)'\037' +#define RSEP (char)'\036' +#define CSEP (char)'\035' + + +/* debugger option */ +struct dbg_option { + const char *name; + int *num_val; + char **str_val; + void (*assign)(const char *); + const char *help_txt; +}; + +#define DEFAULT_HISTFILE "./.dgawk_history" +#define DEFAULT_OPTFILE "./.dgawkrc" +#define DEFAULT_PROMPT "dgawk> " +#define DEFAULT_LISTSIZE 15 +#define DEFAULT_HISTSIZE 100 + +static void set_gawk_output(const char *file); +static void set_prompt(const char *value); +static void set_listsize(const char *value); +static void set_trace(const char *value); +static void set_save_history(const char *value); +static void set_save_options(const char *value); +static void set_history_size(const char *value); +static const char *history_file = DEFAULT_HISTFILE; +static const char *options_file = DEFAULT_OPTFILE; + +/* keep all option variables in one place */ + +static char *output_file = "/dev/stdout"; /* gawk output redirection */ +char *dgawk_Prompt = NULL; /* initialized in interpret */ +static int list_size = DEFAULT_LISTSIZE; /* # of lines that 'list' prints */ +static int do_trace = FALSE; +static int do_save_history = TRUE; +static int do_save_options = TRUE; +static int history_size = DEFAULT_HISTSIZE; /* max # of lines in history file */ + +static const struct dbg_option option_list[] = { +{"history_size", &history_size, NULL, &set_history_size, + gettext_noop("set or show the number of lines to keep in history file.") }, +{"listsize", &list_size, NULL, &set_listsize, + gettext_noop("set or show the list command window size.") }, +{"outfile", NULL, &output_file, &set_gawk_output, + gettext_noop("set or show gawk output file.") }, +{"prompt", NULL, &dgawk_Prompt, &set_prompt, + gettext_noop("set or show debugger prompt."), }, +{"save_history", &do_save_history, NULL, &set_save_history, + gettext_noop("(un)set or show saving of command history (value=on|off).") }, +{"save_options", &do_save_options, NULL, &set_save_options, + gettext_noop("(un)set or show saving of options (value=on|off).") }, +{"trace", &do_trace, NULL, &set_trace, + gettext_noop("(un)set or show instruction tracing (value=on|off).") }, +{0, NULL, NULL, NULL, 0}, +}; + +static void save_options(const char *file); + + +/* pager */ +jmp_buf pager_quit_tag; +static int screen_width = INT_MAX; /* no of columns */ +static int screen_height = INT_MAX; /* no of rows */ +static int pager_lines_printed = 0; /* no of lines printed so far */ + +static void restart(int run) ATTRIBUTE_NORETURN; +static void close_all(void); +static int open_readfd(const char *file); +static int find_lines(SRCFILE *s); +static SRCFILE *source_find(char *src); +static int print_lines(char *src, int start_line, int nlines); +static void print_symbol(NODE *r, int isparam); +static NODE *find_frame(long num); +static NODE *find_param(const char *name, long num, char **pname); +static NODE *find_symbol(const char *name, char **pname); +static NODE *find_array(const char *name); +static void print_field(long field_num); +static int print_function(INSTRUCTION *pc, void *); +static void print_frame(NODE *func, char *src, int srcline); +static void print_numbered_frame(long num); +static void print_cur_frame_and_sourceline(void); +static INSTRUCTION *find_rule(char *src, long lineno); +static INSTRUCTION *mk_breakpoint(char *src, int srcline); +static int execute_commands(struct commands_item *commands); +static void delete_commands_item(struct commands_item *c); +static NODE *execute_code(volatile INSTRUCTION *code); +static int pre_execute_code(INSTRUCTION **pi, int inloop); +static int parse_condition(int type, int num, char *expr); +static BREAKPOINT *add_breakpoint(INSTRUCTION *, INSTRUCTION *, char *, int); +static BREAKPOINT *set_breakpoint_next(INSTRUCTION *rp, INSTRUCTION *ip); +static BREAKPOINT *set_breakpoint_at(INSTRUCTION *, int, int); +static int set_breakpoint(CMDARG *arg, int temporary); +static void delete_breakpoint(BREAKPOINT *b); +static BREAKPOINT *find_breakpoint(long num); +static void display(struct list_item *d); +static struct list_item *find_item(struct list_item *list, long num); +static struct list_item *add_item(struct list_item *list, int type, NODE *symbol, char *pname); +static void delete_item(struct list_item *d); +static int breakpoint_triggered(BREAKPOINT *b); +static int watchpoint_triggered(struct list_item *w); + +static void print_instruction(INSTRUCTION *pc, Func_print print_func, FILE *fp, int in_dump); +static void next_command(); +static char *g_readline(const char *prompt); +static int prompt_yes_no(const char *, char , int , FILE *); + +static struct pf_data { + Func_print print_func; + int defn; + FILE *fp; +} pf_data; + +char * (*read_a_line)(const char *) = 0; /* reads a line of input */ + +struct command_source +{ + int fd; + int is_tty; + char * (*read_func)(const char *); + int (*close_func)(int); + int eof_status; /* see push_cmd_src */ + int cmd; /* D_source or 0 */ + char *str; /* sourced file */ + struct command_source *next; +}; + +static struct command_source *cmd_src = NULL; + +#define get_param_count(f) (f)->lnode->param_cnt +#define get_params(f) (f)->parmlist + + +#define CHECK_PROG_RUNNING() \ + do { \ + if (! prog_running) { \ + d_error(_("program not running.")); \ + return FALSE; \ + } \ + } while (FALSE) + +#define r_format_arg static NODE * format_arg +#define fmt_msg d_error +#include "awkprintf.h" +#undef fmt_msg +#undef r_format_arg + +/* g_readline -- read a line of text; the interface is like 'readline' but + * without any command-line editing; used when not compiled with + * readline support and/or input is not from terminal (prompt set to NULL). + */ + +static char * +g_readline(const char *prompt) +{ + char *line; + size_t line_size = 100; + static char buf[2]; + char *p, *end; + int n; + + if (input_from_tty && prompt && *prompt) + fprintf(out_fp, "%s", prompt); + + emalloc(line, char *, line_size + 1, "g_readline"); + p = line; + end = line + line_size; + while ((n = read(input_fd, buf, 1)) > 0) { + if (buf[0] == '\n') { + if (p > line && p[-1] == '\r') + p--; + break; + } + if (p == end) { + erealloc(line, char *, 2 * line_size + 1, "g_readline"); + p = line + line_size; + line_size *= 2; + end = line + line_size; + } + *p++ = buf[0]; + } + if (n == -1 || (n == 0 && p == line)) { + efree(line); + return NULL; + } + *p = '\0'; + return line; +} + + +/* d_error --- print an error message */ + +void +d_error(const char *mesg, ...) +{ + va_list args; + va_start(args, mesg); + fprintf(out_fp, _("error: ")); + vfprintf(out_fp, mesg, args); + fprintf(out_fp, "\n"); + va_end(args); +} + +/* find_lines --- find the positions of the lines in the source file. */ + +static int +find_lines(SRCFILE *s) +{ + char *buf, *p, *end; + int n; + int ofs = 0; + int *pos; + int pos_size; + int maxlen = 0; + int numlines = 0; + char lastchar = '\0'; + + emalloc(buf, char *, s->bufsize, "find_lines"); + pos_size = s->srclines; + emalloc(s->line_offset, int *, (pos_size + 2) * sizeof(int), "find_lines"); + pos = s->line_offset; + pos[0] = 0; + + while ((n = read(s->fd, buf, s->bufsize)) > 0) { + end = buf + n; + lastchar = buf[n - 1]; + p = buf; + while (p < end) { + if (*p++ == '\n') { + if (++numlines > pos_size) { + erealloc(s->line_offset, int *, (2 * pos_size + 2) * sizeof(int), "find_lines"); + pos = s->line_offset + pos_size; + pos_size *= 2; + } + *++pos = ofs + (p - buf); + if ((pos[0] - pos[-1]) > maxlen) + maxlen = pos[0] - pos[-1]; /* length including NEWLINE */ + } + } + ofs += n; + } + efree(buf); + + if (n == -1) { + d_error(_("can't read source file `%s' (%s)"), + s->src, strerror(errno)); + return -1; + } + if (ofs <= 0) { + fprintf(out_fp, _("source file `%s' is empty.\n"), s->src); + return -1; + } + + if (lastchar != '\n') { + /* fake a NEWLINE at end */ + *++pos = ofs + 1; + numlines++; + if ((pos[0] - pos[-1]) > maxlen) + maxlen = pos[0] - pos[-1]; + } + s->maxlen = maxlen; + s->srclines = numlines; + return 0; +} + +/* source_find --- return the SRCFILE struct for the source 'src' */ + +static SRCFILE * +source_find(char *src) +{ + SRCFILE *s; + struct stat sbuf; + char *path; + int errno_val = 0; + + if (src == NULL || *src == '\0') { + d_error(_("no current source file.")); + return NULL; + } + + if (cur_srcfile->src == src) /* strcmp(cur_srcfile->src, src) == 0 */ + return cur_srcfile; + + for (s = srcfiles->next; s != srcfiles; s = s->next) { + if ((s->stype == SRC_FILE || s->stype == SRC_INC) + && strcmp(s->src, src) == 0) + return s; + } + + path = find_source(src, &sbuf, &errno_val); + if (path != NULL) { + efree(path); + for (s = srcfiles->next; s != srcfiles; s = s->next) { + if ((s->stype == SRC_FILE || s->stype == SRC_INC) + && files_are_same(& s->sbuf, & sbuf)) + return s; + } + } + + d_error(_("No source file named `%s' (%s)"), src, strerror(errno_val)); + return NULL; +} + +/* print_lines --- print source lines, and update 'cur_srcfile' */ + +static int +print_lines(char *src, int start_line, int nlines) +{ + SRCFILE *s; + int *pos; + int i; + struct stat sbuf; + + s = source_find(src); + if (s == NULL) + return -1; + if (s->fd <= INVALID_HANDLE && (s->fd = srcopen(s)) <= INVALID_HANDLE) { + d_error(_("can't open source file `%s' for reading (%s)"), + src, strerror(errno)); + return -1; + } + + if (fstat(s->fd, &sbuf) == 0 && s->mtime < sbuf.st_mtime) { + fprintf(out_fp, _("WARNING: source file `%s' modified since program compilation.\n"), + src); + efree(s->line_offset); + s->line_offset = NULL; + s->mtime = sbuf.st_mtime; + + /* reopen source file */ + close(s->fd); + s->fd = INVALID_HANDLE; + if ((s->fd = srcopen(s)) <= INVALID_HANDLE) { + d_error(_("can't open source file `%s' for reading (%s)"), + src, strerror(errno)); + return -1; + } + } + + if (s->line_offset == NULL && find_lines(s) != 0) + return -1; + if (start_line < 1 || start_line > s->srclines) { + d_error(_("line number %d out of range; `%s' has %d lines"), + start_line, src, s->srclines); + return -1; + } + + assert(nlines > 0); + if ((start_line + nlines - 1) > s->srclines) + nlines = s->srclines - start_line + 1; + + pos = s->line_offset; + if (lseek(s->fd, (off_t) pos[start_line - 1], SEEK_SET) < 0) { + d_error("%s: %s", src, strerror(errno)); + return -1; + } + + if (linebuf == NULL) { + emalloc(linebuf, char *, s->maxlen + 20, "print_lines"); /* 19 for line # */ + linebuf_len = s->maxlen; + } else if (linebuf_len < s->maxlen) { + erealloc(linebuf, char *, s->maxlen + 20, "print_lines"); + linebuf_len = s->maxlen; + } + + for (i = start_line; i < start_line + nlines; i++) { + int supposed_len, len; + char *p; + + sprintf(linebuf, "%-8d", i); + + /* mark the line about to be executed with =>; nlines > 1 + * condition makes sure that we are in list command + */ + if (nlines > 1) { + BREAKPOINT *b; + int has_bpt = FALSE; + for (b = breakpoints.prev; b != &breakpoints; b = b->prev) { + if (src == b->src && i == b->bpi->source_line) { + has_bpt = TRUE; + break; + } + } + if (prog_running && src == source && i == sourceline) { + if (has_bpt) + sprintf(linebuf, "%-4d:b=>", i); + else + sprintf(linebuf, "%-4d =>", i); + } else if (has_bpt) + sprintf(linebuf, "%-4d:b ", i); + } + + p = linebuf + strlen(linebuf); + supposed_len = pos[i] - pos[i - 1]; + len = read(s->fd, p, supposed_len); + switch (len) { + case -1: + d_error(_("can't read source file `%s' (%s)"), + src, strerror(errno)); + return -1; + + case 0: + d_error(_("unexpected eof while reading file `%s', line %d"), + src, i); + return -1; + + default: + if (i == s->srclines && p[len - 1] != '\n') + p[len++] = '\n'; +#if 0 + if (len != supposed_len || p[len - 1] != '\n') { + d_error(_("source file `%s' modified since start of program execution"), + src); + return -1; + } +#endif + len += (p - linebuf); + if (fwrite(linebuf, sizeof(char), len, out_fp) != len) + return -1; + } + } + + if (cur_srcfile != s) { + if (cur_srcfile->fd != INVALID_HANDLE) { + close(cur_srcfile->fd); + cur_srcfile->fd = INVALID_HANDLE; + } + cur_srcfile = s; + } + return (i - 1); /* no of lines printed */ +} + +/* do_list --- list command */ + +int +do_list(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + long line_first, line_last; + long count = list_size; + INSTRUCTION *rp; + char *src = cur_srcfile->src; + + line_first = last_printed_line + 1; /* default or no arg */ + if (arg == NULL) /* list or list + */ + goto list; + + switch (arg->type) { + case D_int: /* list n or list - */ + if (arg->a_int < 0) { /* list - */ + line_first = last_printed_line - last_print_count - list_size + 1; + if (line_first < 1) { + if (last_printed_line != last_print_count) + line_first = 1; + else + return FALSE; + } + } else { +line: + line_first = arg->a_int - list_size / 2; + if (line_first < 1) + line_first = 1; + } + break; + + case D_range: /* list m-n */ +range: + line_first = arg->a_int; + arg = arg->next; + assert(arg != NULL); + assert(arg->type == D_int); + count = arg->a_int - line_first + 1; + break; + + case D_string: + src = arg->a_string; + if (arg->next != NULL) { + arg = arg->next; + if (arg->type == D_int) /* list file:n */ + goto line; + else if (arg->type == D_range) /* list file:m-n */ + goto range; + else if (arg->type == D_func) /* list file:function */ + goto func; + else + line_first = 1; + } else + line_first = 1; + break; + + case D_func: /* list function */ +func: + rp = arg->a_node->code_ptr; + src = rp->source_file; + line_first = rp->source_line - list_size / 2; + if (line_first < 1) + line_first = 1; + break; + + default: + break; + } + +list: + line_last = print_lines(src, line_first, count); + if (line_last != -1) { + last_printed_line = line_last; + last_print_count = line_last - line_first + 1; + } + return FALSE; +} + +/* do_info --- info command */ + +int +do_info(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + if (arg == NULL || arg->type != D_argument) + return FALSE; + + switch (arg->a_argument) { + case A_SOURCE: + fprintf(out_fp, _("Current source file: %s\n"), cur_srcfile->src); + fprintf(out_fp, _("Number of lines: %d\n"), cur_srcfile->srclines); + break; + + case A_SOURCES: + { + SRCFILE *s; + for (s = srcfiles->next; s != srcfiles; s = s->next) { + fprintf(out_fp, _("Source file (lines): %s (%d)\n"), + (s->stype == SRC_FILE || s->stype == SRC_INC) ? s->src + : "cmd. line", + s->srclines); + } + } + break; + + case A_BREAK: + initialize_pager(out_fp); + if (setjmp(pager_quit_tag) == 0) { + BREAKPOINT *b; + struct commands_item *c; + + gprintf(out_fp, _("Number Disp Enabled Location\n\n")); + for (b = breakpoints.prev; b != &breakpoints; b = b->prev) { + char *disp = "keep"; + if (b->flags & BP_ENABLE_ONCE) + disp = "dis"; + else if(b->flags & BP_TEMP) + disp = "del"; + gprintf(out_fp, "%-6d %-4.4s %-7.7s file %s, line #%d\n", + b->number, disp, (b->flags & BP_ENABLE) ? "yes" : "no", + b->src, b->bpi->source_line); + if (b->hit_count > 0) + gprintf(out_fp, _("\tno of hits = %ld\n"), b->hit_count); + if (b->flags & BP_IGNORE) + gprintf(out_fp, _("\tignore next %ld hit(s)\n"), b->ignore_count); + if (b->cndn.code != NULL) + gprintf(out_fp, _("\tstop condition: %s\n"), b->cndn.expr); + if (b->commands.next != &b->commands) + gprintf(out_fp, _("\tcommands:\n")); + for (c = b->commands.next; c != &b->commands; c = c->next) { + gprintf(out_fp, "\t%s\n", c->cmd_string); + if (c->cmd == D_eval) { + char *start, *end; + CMDARG *a = c->arg; + start = strchr(a->a_string, '{'); + end = strrchr(a->a_string, '}'); + if (start == NULL || end == NULL) + continue; + start++; + *end = '\0'; + gprintf(out_fp, "%s", start); /* FIXME: translate ? */ + *end = '}'; + } + } + } + } + break; + + case A_FRAME: + CHECK_PROG_RUNNING(); + fprintf(out_fp, _("Current frame: ")); + print_numbered_frame(cur_frame); + if (cur_frame < fcall_count) { + fprintf(out_fp, _("Called by frame: ")); + print_numbered_frame(cur_frame + 1); + } + if (cur_frame > 0) { + fprintf(out_fp, _("Caller of frame: ")); + print_numbered_frame(cur_frame - 1); + } + break; + + case A_ARGS: + case A_LOCALS: + { + NODE *f, *func; + INSTRUCTION *pc; + int arg_count, pcount; + int i, from, to; + char **pnames; + + CHECK_PROG_RUNNING(); + f = find_frame(cur_frame); + func = f->func_node; + if (func == NULL) { + /* print ARGV ? */ + fprintf(out_fp, _("None in main().\n")); + return FALSE; + } + + pcount = get_param_count(func); /* # of defined params */ + pnames = get_params(func); /* param names */ + + pc = (INSTRUCTION *) f->reti; /* Op_func_call instruction */ + arg_count = (pc + 1)->expr_count; /* # of arguments supplied */ + + if (arg_count > pcount) /* extra args */ + arg_count = pcount; + if (arg->a_argument == A_ARGS) { + from = 0; + to = arg_count - 1; + } else { + from = arg_count; + to = pcount - 1; + } + + for (i = from; i <= to; i++) { + NODE *r; + r = f->stack[i]; + if (r->type == Node_array_ref) + r = r->orig_array; + fprintf(out_fp, "%s = ", pnames[i]); + print_symbol(r, TRUE); + } + if (to < from) + fprintf(out_fp, "%s", + arg->a_argument == A_ARGS ? + _("No arguments.\n") : + _("No locals.\n")); + } + break; + + case A_VARIABLES: + initialize_pager(out_fp); + if (setjmp(pager_quit_tag) == 0) { + gprintf(out_fp, _("All defined variables:\n\n")); + print_vars(gprintf, out_fp); + } + break; + + case A_FUNCTIONS: + initialize_pager(out_fp); + if (setjmp(pager_quit_tag) == 0) { + gprintf(out_fp, _("All defined functions:\n\n")); + pf_data.print_func = gprintf; + pf_data.fp = out_fp; + pf_data.defn = TRUE; + (void) foreach_func((int (*)(INSTRUCTION *, void *)) print_function, + TRUE, /* sort */ + &pf_data /* data */ + ); + } + break; + + case A_DISPLAY: + case A_WATCH: + initialize_pager(out_fp); + if (setjmp(pager_quit_tag) == 0) { + struct list_item *d, *list; + + if (arg->a_argument == A_DISPLAY) { + list = &display_list; + gprintf(out_fp, _("Auto-display variables:\n\n")); + } else { + list = &watch_list; + gprintf(out_fp, _("Watch variables:\n\n")); + } + for (d = list->prev; d != list; d = d->prev) { + int i; + struct commands_item *c; + NODE *symbol = d->symbol; + + if (IS_SUBSCRIPT(d)) { + gprintf(out_fp, "%d:\t%s", d->number, d->sname); + for (i = 0; i < d->num_subs; i++) { + NODE *sub; + sub = d->subs[i]; + gprintf(out_fp, "[\"%s\"]", sub->stptr); + } + gprintf(out_fp, "\n"); + } else if (IS_FIELD(d)) + gprintf(out_fp, "%d:\t$%ld\n", d->number, (long) symbol->numbr); + else + gprintf(out_fp, "%d:\t%s\n", d->number, d->sname); + if (d->cndn.code != NULL) + gprintf(out_fp, _("\tstop condition: %s\n"), d->cndn.expr); + if (d->commands.next != &d->commands) + gprintf(out_fp, _("\tcommands:\n")); + for (c = d->commands.next; c != &d->commands; c = c->next) { + gprintf(out_fp, "\t%s\n", c->cmd_string); + if (c->cmd == D_eval) { + char *start, *end; + CMDARG *a = c->arg; + start = strchr(a->a_string, '{'); + end = strrchr(a->a_string, '}'); + if (start == NULL || end == NULL) + continue; + start++; + *end = '\0'; + gprintf(out_fp, "%s", start); /* FIXME: translate ? */ + *end = '}'; + } + } + + } + } + break; + + default: + break; + } + + return FALSE; +} + +/* print_symbol --- print a symbol table entry */ + +static void +print_symbol(NODE *r, int isparam) +{ + switch (r->type) { + case Node_var_new: + fprintf(out_fp, "untyped variable\n"); + break; + case Node_var: + if (! isparam && r->var_update) + r->var_update(); + valinfo(r->var_value, fprintf, out_fp); + break; + case Node_var_array: + fprintf(out_fp, "array, %ld elements\n", r->table_size); + break; + case Node_func: + fprintf(out_fp, "`function'\n"); + break; + default: + break; + } +} + +/* find_frame --- find frame given a frame number */ + +static NODE * +find_frame(long num) +{ + assert(num >= 0); + if (num == 0) + return frame_ptr; + + assert(prog_running == TRUE); + assert(num <= fcall_count); + assert(fcall_list[num] != NULL); + return fcall_list[num]; +} + +/* find_param --- find a function parameter in a given frame number */ + +static NODE * +find_param(const char *name, long num, char **pname) +{ + NODE *r = NULL; + NODE *f; + + if (pname) + *pname = NULL; + + if (num < 0 || num > fcall_count || name == NULL) + return NULL; + f = find_frame(num); + if (f->func_node != NULL) { /* in function */ + NODE *func; + char **pnames; + int i, pcount; + + func = f->func_node; + pnames = get_params(func); + pcount = get_param_count(func); + + for (i = 0; i < pcount; i++) { + if (STREQ(name, pnames[i])) { + r = f->stack[i]; + if (r->type == Node_array_ref) + r = r->orig_array; + if (pname) + *pname = pnames[i]; + break; + } + } + } + return r; +} + +/* find_symbol --- find a symbol in current context */ + +static +NODE *find_symbol(const char *name, char **pname) +{ + NODE *r = NULL; + + if (pname) + *pname = NULL; + if (prog_running) + r = find_param(name, cur_frame, pname); + if (r == NULL) + r = lookup(name); + if (r == NULL) + fprintf(out_fp, _("no symbol `%s' in current context\n"), name); + return r; +} + +/* find_array -- find an array in current context */ + +static NODE * +find_array(const char *name) +{ + NODE *r; + r = find_symbol(name, NULL); + if (r != NULL && r->type != Node_var_array) { + fprintf(out_fp, _("`%s' is not an array\n"), name); + return NULL; + } + return r; +} + +/* print_field --- print the value of $n */ + +static void +print_field(long field_num) +{ + NODE **lhs; + lhs = get_field(field_num, NULL); + if (*lhs == Null_field || *lhs == Nnull_string) + fprintf(out_fp, _("$%ld = uninitialized field\n"), field_num); + else { + fprintf(out_fp, "$%ld = ", field_num); + valinfo(*lhs, fprintf, out_fp); + } +} + +extern int comp_func(const void *p1, const void *p2); + +/* print_array --- print the contents of an array */ + +static void +print_array(NODE *arr, char *arr_name, Func_print print_func) +{ + NODE *bucket; + NODE **list = NULL; + int i, j; + size_t num_elems = 0; + + if (arr->var_array == NULL || arr->table_size == 0) { + print_func(out_fp, _("array `%s' is empty\n"), arr_name); + return; + } + + /* sort indices */ + + /* allocate space for array */ + num_elems = arr->table_size; + emalloc(list, NODE **, num_elems * sizeof(NODE *), "print_array"); + + /* populate it */ + for (i = j = 0; i < arr->array_size; i++) { + bucket = arr->var_array[i]; + if (bucket == NULL) + continue; + for (; bucket != NULL; bucket = bucket->ahnext) + list[j++] = bucket; + } + qsort(list, num_elems, sizeof(NODE *), comp_func); /* shazzam! */ + + for (i = 0; i < num_elems; i++) { + bucket = list[i]; + if (bucket->ahvalue->type == Node_var_array) { + arr = bucket->ahvalue; + print_array(arr, arr->vname, print_func); + } else { + print_func(out_fp, "%s[\"%s\"] = ", arr_name, bucket->ahname_str); + valinfo(bucket->ahvalue, print_func, out_fp); + } + } + efree(list); +} + +/* print_subscript --- print an array element */ + +static void +print_subscript(NODE *arr, char *arr_name, CMDARG *a, int count) +{ + NODE *r, *subs; + + subs = a->a_node; + r = in_array(arr, subs); + if (r == NULL) + fprintf(out_fp, _("[\"%s\"] not in array `%s'\n"), subs->stptr, arr_name); + else if (r->type == Node_var_array) { + if (count > 1) + print_subscript(r, r->vname, a->next, count - 1); + else { + /* print # of elements in array */ + fprintf(out_fp, "%s = ", r->vname); + print_symbol(r, FALSE); + } + } else { + fprintf(out_fp, "%s[\"%s\"] = ", arr_name, subs->stptr); + valinfo(r, fprintf, out_fp); + } +} + +/* do_print_var --- print command */ + +int +do_print_var(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + NODE *r; + CMDARG *a; + char *name, *pname; + + for (a = arg; a != NULL; a = a->next) { + switch (a->type) { + case D_variable: + name = a->a_string; + if ((r = find_symbol(name, &pname)) != NULL) { + fprintf(out_fp, "%s = ", name); + print_symbol(r, (pname != NULL)); + } + break; + + case D_subscript: + assert(a->a_count > 0); + name = a->a_string; + r = find_array(name); + if (r != NULL) + print_subscript(r, name, a->next, a->a_count); + break; + + case D_array: + name = a->a_string; + if ((r = find_array(name)) != NULL) { + int count = a->a_count; + for (; count > 0; count--) { + NODE *value, *subs; + a = a->next; + subs = a->a_node; + value = in_array(r, subs); + if (value == NULL) { + fprintf(out_fp, _("[\"%s\"] not in array `%s'\n"), + subs->stptr, name); + break; + } else if (value->type != Node_var_array) { + fprintf(out_fp, _("`%s[\"%s\"]' is not an array\n"), + name, subs->stptr); + break; + } else { + r = value; + name = r->vname; + } + } + if (count == 0) { + initialize_pager(out_fp); + if (setjmp(pager_quit_tag) == 0) + print_array(r, name, gprintf); + } + } + break; + + case D_field: + print_field(a->a_node->numbr); + break; + + default: + /* notably D_node, subscript for invalid array name; skip */ + break; + } + } + return FALSE; +} + +/* do_set_var --- set command */ + +int +do_set_var(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + NODE *r, *val; + NODE **lhs; + char *name, *pname; + + switch (arg->type) { + case D_variable: + name = arg->a_string; + arg = arg->next; + val = arg->a_node; + + if ((r = find_symbol(name, &pname)) == NULL) + break; + + switch (r->type) { + case Node_var_new: + r->type = Node_var; + r->var_value = Nnull_string; + /* fall through */ + case Node_var: + lhs = &r->var_value; + unref(*lhs); + *lhs = dupnode(val); + if (pname == NULL && r->var_assign != NULL) + r->var_assign(); + fprintf(out_fp, "%s = ", name); + print_symbol(r, (pname != NULL)); + break; + + default: + d_error(_("`%s' is not a scalar variable"), name); + break; + } + break; + + case D_subscript: + { + NODE *subs, *value; + int count = arg->a_count; + + assert(count > 0); + name = arg->a_string; + r = find_array(name); + if (r == NULL) + break; + for (; count > 0; count--) { + arg = arg->next; + subs = arg->a_node; + value = in_array(r, subs); + + if (count == 1) { + if (value != NULL && value->type == Node_var_array) + d_error(_("attempt to use array `%s[\"%s\"]' in a scalar context"), + name, subs->stptr); + else { + arg = arg->next; + val = arg->a_node; + lhs = assoc_lookup(r, subs, FALSE); + unref(*lhs); + *lhs = dupnode(val); + fprintf(out_fp, "%s[\"%s\"] = ", name, subs->stptr); + valinfo(*lhs, fprintf, out_fp); + } + } else { + if (value == NULL) { + const char *aname = make_aname(r, subs); + NODE *array; + getnode(array); + array->type = Node_var_array; + array->var_array = NULL; + array->vname = estrdup(aname, strlen(aname)); + *assoc_lookup(r, subs, FALSE) = array; + r = array; + } else if (value->type != Node_var_array) { + d_error(_("attempt to use scalar `%s[\"%s\"]' as array"), + name, subs->stptr); + break; + } else { + r = value; + name = r->vname; + } + } + } + } + break; + + case D_field: + { + long field_num; + Func_ptr assign = NULL; + + field_num = (long) arg->a_node->numbr; + assert(field_num >= 0); + arg = arg->next; + val = arg->a_node; + lhs = get_field(field_num, &assign); + unref(*lhs); + *lhs = dupnode(val); + if (assign) + assign(); + print_field(field_num); + } + break; + + default: + break; + } + return FALSE; +} + +/* find_item --- find an item in the watch/display list */ + +static struct list_item * +find_item(struct list_item *list, long num) +{ + struct list_item *d; + + if (num <= 0) + return NULL; + for (d = list->next; d != list; d = d->next) { + if (d->number == num) + return d; + } + return NULL; +} + +/* delete_item --- delete an item from the watch/display list */ + +static void +delete_item(struct list_item *d) +{ + struct commands_item *c; + int i; + + if (IS_SUBSCRIPT(d)) { + for (i = 0; i < d->num_subs; i++) + unref(d->subs[i]); + efree(d->subs); + } else if (IS_FIELD(d)) + unref(d->symbol); + + if ((d->flags & CUR_IS_ARRAY) == 0) + unref(d->cur_value); + if ((d->flags & OLD_IS_ARRAY) == 0) + unref(d->old_value); + + /* delete commands */ + for (c = d->commands.next; c != &d->commands; c = c->next) { + c = c->prev; + delete_commands_item(c->next); + } + + free_context(d->cndn.ctxt, FALSE); + if (d->cndn.expr != NULL) + efree(d->cndn.expr); + + d->next->prev = d->prev; + d->prev->next = d->next; + efree(d); +} + +/* add_item --- craete a watch/display item and add it to the list */ + +static struct list_item * +add_item(struct list_item *list, int type, NODE *symbol, char *pname) +{ + struct list_item *d; + + emalloc(d, struct list_item *, sizeof(struct list_item), "add_item"); + memset(d, 0, sizeof(struct list_item)); + d->commands.next = d->commands.prev = &d->commands; + + d->number = ++list->number; + d->sname = symbol->vname; + if (pname != NULL) { /* function param */ + d->sname = pname; + d->flags |= PARAM; + d->fcall_count = fcall_count - cur_frame; + } + + if (type == D_field) { /* field number */ + d->symbol = symbol; + d->flags |= FIELD_NUM; + } else if (type == D_subscript) { /* subscript */ + d->symbol = symbol; + d->flags |= SUBSCRIPT; + } else /* array or variable */ + d->symbol = symbol; + + /* add to list */ + d->next = list->next; + d->prev = list; + list->next = d; + d->next->prev = d; + return d; +} + +/* do_add_item --- add an item to the watch/display list */ + +static struct list_item * +do_add_item(struct list_item *list, CMDARG *arg) +{ + NODE *symbol = NULL; + char *name, *pname = NULL; + struct list_item *item = NULL; + + switch (arg->type) { + case D_subscript: + case D_variable: + name = arg->a_string; + if ((symbol = find_symbol(name, &pname)) == NULL) + return NULL; + if (symbol->type == Node_func) { + d_error(_("`%s' is a function"), name); + return NULL; + } + if (arg->type == D_subscript && symbol->type != Node_var_array) { + d_error(_("`%s' is not an array\n"), name); + return NULL; + } + + item = add_item(list, arg->type, symbol, pname); + if (item != NULL && arg->type == D_subscript) { + NODE **subs; + int count = arg->a_count; + int i; + + assert(count > 0); + emalloc(subs, NODE **, count * sizeof(NODE *), "do_add_item"); + for (i = 0; i < count; i++) { + arg = arg->next; + subs[i] = dupnode(arg->a_node); + (void) force_string(subs[i]); + } + item->subs = subs; + item->num_subs = count; + } + break; + + case D_field: + symbol = dupnode(arg->a_node); + item = add_item(list, D_field, symbol, NULL); + break; + + default: + break; + } + + /* watch condition if any */ + if (list == &watch_list) { + arg = arg->next; + if (item != NULL && arg != NULL) { + if (parse_condition(D_watch, item->number, arg->a_string) == 0) + arg->a_string = NULL; /* don't let free_cmdarg free it */ + else + fprintf(out_fp, _("watchpoint %d is unconditional\n"), item->number); + } + } + return item; +} + +/* do_delete_item --- delete a watch/display item from list. */ + +static void +do_delete_item(struct list_item *list, CMDARG *arg) +{ + if (arg == NULL) { + while (list->next != list) + delete_item(list->next); + } + + for (; arg != NULL; arg = arg->next) { + struct list_item *d; + if (arg->type == D_range) { + long i, j; + + i = arg->a_int; + arg = arg->next; + j = arg->a_int; + if (j > list->number) + j = list->number; + for (; i <= j; i++) { + if ((d = find_item(list, i)) != NULL) + delete_item(d); + } + } else { + if ((d = find_item(list, arg->a_int)) == NULL) { + /* split into two for easier message translation */ + if (list == &display_list) + d_error(_("No display item numbered %ld"), + arg->a_int); + else + d_error(_("No watch item numbered %ld"), + arg->a_int); + } else + delete_item(d); + } + } +} + +/* display --- print an item from the auto-display list */ + +static void +display(struct list_item *d) +{ + NODE *symbol; + + symbol = d->symbol; + if (IS_PARAM(d) && (d->fcall_count != (fcall_count - cur_frame))) + return; + + if (IS_SUBSCRIPT(d)) { + NODE *sub, *r; + int i = 0, count = d->num_subs; + for (i = 0; i < count; i++) { + sub = d->subs[i]; + r = in_array(symbol, sub); + if (r == NULL) { + fprintf(out_fp, _("%d: [\"%s\"] not in array `%s'\n"), + d->number, sub->stptr, d->sname); + break; + } + if (r->type == Node_var_array) { + symbol = r; + if (i == count - 1) /* it's a sub-array */ + goto print_sym; /* print # of elements in sub-array */ + } else { + if (i != count - 1) + return; /* FIXME msg and delete item ? */ + fprintf(out_fp, "%d: %s[\"%s\"] = ", d->number, + d->sname, sub->stptr); + valinfo(r, fprintf, out_fp); + } + } + } else if (IS_FIELD(d)) { + NODE *r = d->symbol; + fprintf(out_fp, "%d: ", d->number); + print_field(r->numbr); + } else { +print_sym: + fprintf(out_fp, "%d: %s = ", d->number, d->sname); + print_symbol(symbol, IS_PARAM(d)); + } +} + + +/* do_display --- display command */ + +int +do_display(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + struct list_item *d; + + if (arg == NULL) { + /* display all items */ + for (d = display_list.prev; d != &display_list; d = d->prev) + display(d); + return FALSE; + } + + if ((d = do_add_item(&display_list, arg)) != NULL) + display(d); + + return FALSE; +} + +/* do_undisplay --- undisplay command */ + +int +do_undisplay(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + do_delete_item(&display_list, arg); + return FALSE; +} + +/* condition_triggered --- test if a condition expression is true */ + +static int +condition_triggered(struct condition *cndn) +{ + NODE *r; + int di; + + assert(cndn != NULL); + if (cndn->code == NULL) + return TRUE; + + set_context(cndn->ctxt); + r = execute_code((volatile INSTRUCTION *) cndn->code); + set_context(NULL); /* switch to prev context */ + if (r == NULL) /* fatal error */ + return FALSE; /* not triggered */ + + force_number(r); + di = (r->numbr != 0.0); + DEREF(r); + return di; +} + + + +static int +find_subscript(struct list_item *item, NODE **ptr) +{ + NODE *symbol = item->symbol; + NODE *sub, *r; + int i = 0, count = item->num_subs; + + r = *ptr = NULL; + for (i = 0; i < count; i++) { + sub = item->subs[i]; + r = in_array(symbol, sub); + if (r == NULL) + return 0; + if (r->type == Node_var_array) + symbol = r; + else if (i < count - 1) + return -1; + } + *ptr = r; + return 0; +} + +/* cmp_val --- compare values of watched item, returns TRUE if different; */ + +static int +cmp_val(struct list_item *w, NODE *old, NODE *new) +{ + /* + * case old new result + * ------------------------------ + * 1: NULL ARRAY TRUE + * 2: NULL SCALAR TRUE + * 3: NULL NULL FALSE + * 4: SCALAR SCALAR cmp_node + * 5: SCALAR ARRAY TRUE + * 6: SCALAR NULL TRUE + * 7: ARRAY SCALAR TRUE + * 8: ARRAY ARRAY compare size + * 9: ARRAY NULL TRUE + */ + + if (WATCHING_ARRAY(w)) { + long size = 0; + if (! new) /* 9 */ + return TRUE; + if (new->type == Node_val) /* 7 */ + return TRUE; + /* new->type == Node_var_array */ /* 8 */ + if (new->var_array != NULL) + size = new->table_size; + if (w->cur_size == size) + return FALSE; + return TRUE; + } + + if (! old && ! new) /* 3 */ + return FALSE; + if ((! old && new) /* 1, 2 */ + || (old && ! new)) /* 6 */ + return TRUE; + + if (new->type == Node_var_array) /* 5 */ + return TRUE; + return cmp_nodes(old, new); /* 4 */ +} + +/* watchpoint_triggered --- check if we should stop at this watchpoint; + * update old and current values accordingly. + */ + +static int +watchpoint_triggered(struct list_item *w) +{ + NODE *symbol; + NODE *t1, *t2; + + symbol = w->symbol; + if (IS_PARAM(w) && (w->fcall_count != (fcall_count - cur_frame))) + return 0; /* parameter with same name in a different function */ + if (! condition_triggered(&w->cndn)) + return 0; + + t1 = w->cur_value; + t2 = (NODE *) 0; + if (IS_SUBSCRIPT(w)) + (void) find_subscript(w, &t2); + else if (IS_FIELD(w)) { + long field_num; + field_num = (long) w->symbol->numbr; + t2 = *get_field(field_num, NULL); + } else { + switch (symbol->type) { + case Node_var: + t2 = symbol->var_value; + break; + case Node_var_array: + t2 = symbol; + break; + case Node_var_new: + break; + default: + cant_happen(); + } + } + + if (! cmp_val(w, t1, t2)) + return 0; + + /* update old and current values */ + + if ((w->flags & OLD_IS_ARRAY) == 0) + unref(w->old_value); + w->flags &= ~OLD_IS_ARRAY; + if (WATCHING_ARRAY(w)) { /* 7, 8, 9 */ + w->old_size = w->cur_size; + w->flags |= OLD_IS_ARRAY; + if (! t2) { + w->flags &= ~CUR_IS_ARRAY; + w->cur_value = 0; + } else if (t2->type == Node_val) { + w->flags &= ~CUR_IS_ARRAY; + w->cur_value = dupnode(t2); + } else + w->cur_size = (t2->var_array != NULL) ? t2->table_size : 0; + } else if (! t1) { /* 1, 2 */ + w->old_value = 0; + /* new != NULL */ + if (t2->type == Node_val) + w->cur_value = dupnode(t2); + else { + w->flags |= CUR_IS_ARRAY; + w->cur_size = (t2->var_array != NULL) ? t2->table_size : 0; + } + } else /* if (t1->type == Node_val) */ { /* 4, 5, 6 */ + w->old_value = w->cur_value; + if (! t2) + w->cur_value = 0; + else if (t2->type == Node_var_array) { + w->flags |= CUR_IS_ARRAY; + w->cur_size = (t2->var_array != NULL) ? t2->table_size : 0; + } else + w->cur_value = dupnode(t2); + } + + return w->number; +} + +/* initialize_watch_item --- initialize current value of a watched item */ + +static int +initialize_watch_item(struct list_item *w) +{ + NODE *t, *r; + NODE *symbol = w->symbol; + + if (IS_SUBSCRIPT(w)) { + if (find_subscript(w, &r) == -1) { + d_error(_("attempt to use scalar value as array")); + return -1; + } + + if (r == NULL) + w->cur_value = (NODE *) 0; + else if (r->type == Node_var_array) { /* it's a sub-array */ + w->flags |= CUR_IS_ARRAY; + w->cur_size = (r->var_array != NULL) ? r->table_size : 0; + } else + w->cur_value = dupnode(r); + } else if (IS_FIELD(w)) { + long field_num; + t = w->symbol; + field_num = (long) t->numbr; + r = *get_field(field_num, NULL); + w->cur_value = dupnode(r); + } else { + if (symbol->type == Node_var_new) + w->cur_value = (NODE *) 0; + else if (symbol->type == Node_var) { + r = symbol->var_value; + w->cur_value = dupnode(r); + } else if (symbol->type == Node_var_array) { + w->flags |= CUR_IS_ARRAY; + w->cur_size = (symbol->var_array != NULL) ? symbol->table_size : 0; + } /* else + can't happen */ + } + return 0; +} + +/* do_watch --- watch command */ + +int +do_watch(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + struct list_item *w; + NODE *symbol, *sub; + int i; + + w = do_add_item(&watch_list, arg); + if (w == NULL) + return FALSE; + + if (initialize_watch_item(w) == -1) { + delete_item(w); + return FALSE; + } + + fprintf(out_fp, "Watchpoint %d: ", w->number); + symbol = w->symbol; + +/* FIXME: common code also in print_watch_item */ + if (IS_SUBSCRIPT(w)) { + fprintf(out_fp, "%s", w->sname); + for (i = 0; i < w->num_subs; i++) { + sub = w->subs[i]; + fprintf(out_fp, "[\"%s\"]", sub->stptr); + } + fprintf(out_fp, "\n"); + } else if (IS_FIELD(w)) + fprintf(out_fp, "$%ld\n", (long) symbol->numbr); + else + fprintf(out_fp, "%s\n", w->sname); + + return FALSE; +} + +/* do_unwatch --- unwatch command */ + +int +do_unwatch(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + do_delete_item(&watch_list, arg); + return FALSE; +} + +/* callback from pop_frame in eval.c */ + +void +frame_popped() +{ + struct list_item *item; + + /* delete all out of scope watchpoints */ + for (item = watch_list.next; item != &watch_list; item = item->next) { + if (IS_PARAM(item) && (item->fcall_count > fcall_count)) { + fprintf(out_fp, + _("Watchpoint %d deleted because parameter is out of scope.\n"), + item->number); + item = item->prev; + delete_item(item->next); + } + } + + /* delete all out of scope display items */ + for (item = display_list.next; item != &display_list; item = item->next) { + if (IS_PARAM(item) && (item->fcall_count > fcall_count)) { + fprintf(out_fp, + _("Display %d deleted because parameter is out of scope.\n"), + item->number); + item = item->prev; + delete_item(item->next); + } + } +} + +/* print_function --- print function name, parameters, and optionally + * file and line number. + */ + +static int +print_function(INSTRUCTION *pc, void *x) +{ + NODE *func; + int i, pcount; + char **pnames; + struct pf_data *data = (struct pf_data *) x; + int defn = data->defn; + Func_print print_func = data->print_func; + FILE *fp = data->fp; + + func = pc->func_body; + pcount = get_param_count(func); + pnames = get_params(func); + + print_func(fp, "%s(", func->lnode->param); + for (i = 0; i < pcount; i++) { + print_func(fp, "%s", pnames[i]); + if (i < pcount - 1) + print_func(fp, ", "); + } + print_func(fp, ")"); + if (defn) + print_func(fp, _(" in file `%s', line %d\n"), + pc->source_file, pc->source_line); + return 0; +} + +/* print_frame --- print function name, parameters, + * source and line number of where it is + * executing. + */ + +static void +print_frame(NODE *func, char *src, int srcline) +{ + if (func == NULL) + fprintf(out_fp, "main()"); + else { + pf_data.print_func = fprintf; + pf_data.fp = out_fp; + pf_data.defn = FALSE; + (void) print_function(func->code_ptr, &pf_data); + } + fprintf(out_fp, _(" at `%s':%d"), src, srcline); +} + +/* print_numbered_frame --- print a frame given its number */ + +static void +print_numbered_frame(long num) +{ + NODE *f; + + assert(prog_running == TRUE); + f = find_frame(num); + if (num == 0) { + fprintf(out_fp, "#%ld\t ", num); + print_frame(f->func_node, source, sourceline); + } else { + fprintf(out_fp, _("#%ld\tin "), num); + print_frame(f->func_node, f->vname, + ((INSTRUCTION *) find_frame(num - 1)->reti)->source_line); + } + fprintf(out_fp, "\n"); +} + +/* do_backtrace --- backtrace command */ + +int +do_backtrace(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + long cur = 0; + long last = fcall_count; + + CHECK_PROG_RUNNING(); + if (arg != NULL && arg->type == D_int) { + long count = arg->a_int; + + /* frame_ptr (frame #0), fcall_list[1, 2, ... fcall_count] => total count */ + if (count >= 0) { + /* toward outermost frame #fcall_count */ + last = count - 1; + if (last > fcall_count) + last = fcall_count; + } else { + /* toward innermost frame #0 */ + cur = 1 + fcall_count + count; + if (cur < 0) + cur = 0; + } + } + + for (; cur <= last; cur++) { + print_numbered_frame(cur); + } + if (cur <= fcall_count) + fprintf(out_fp, _("More stack frames follow ...\n")); + return FALSE; +} + +/* print_cur_frame_and_sourceline --- print current frame, and + * current source line. + */ + +static void +print_cur_frame_and_sourceline() +{ + NODE *f; + int srcline; + char *src; + + assert(prog_running == TRUE); + f = find_frame(cur_frame); + if (cur_frame == 0) { + src = source; + srcline = sourceline; + } else { + f = find_frame(cur_frame); + src = f->vname; + srcline = ((INSTRUCTION *) find_frame(cur_frame - 1)->reti)->source_line; + } + + fprintf(out_fp, (cur_frame > 0 ? _("#%ld\tin ") : "#%ld\t "), cur_frame); + print_frame(f->func_node, src, srcline); + fprintf(out_fp, "\n"); + print_lines(src, srcline, 1); + last_printed_line = srcline - list_size / 2; + if (last_printed_line < 0) + last_printed_line = 0; +} + +/* do_frame --- frame command */ + +int +do_frame(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + CHECK_PROG_RUNNING(); + if (arg && arg->type == D_int) { + if (arg->a_int < 0 || arg->a_int > fcall_count) { + d_error(_("invalid frame number")); + return FALSE; + } + cur_frame = arg->a_int; + } + print_cur_frame_and_sourceline(); + return FALSE; +} + +/* do_up --- up command */ + +int +do_up(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + CHECK_PROG_RUNNING(); + if (arg != NULL && arg->type == D_int) + cur_frame += arg->a_int; + else + cur_frame++; + if (cur_frame < 0) + cur_frame = 0; + else if (cur_frame > fcall_count) + cur_frame = fcall_count; + print_cur_frame_and_sourceline(); + return FALSE; +} + +/* do_down --- down command */ + +int +do_down(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + CHECK_PROG_RUNNING(); + if (arg != NULL && arg->type == D_int) + cur_frame -= arg->a_int; + else + cur_frame--; + if (cur_frame < 0) + cur_frame = 0; + else if (cur_frame > fcall_count) + cur_frame = fcall_count; + print_cur_frame_and_sourceline(); + return FALSE; +} + +/* find_rule --- find a rule or function in file 'src' containing + * source line 'lineno' + */ + +static INSTRUCTION * +find_rule(char *src, long lineno) +{ + INSTRUCTION *rp; + + assert(lineno > 0); + for (rp = rule_list->nexti; rp != NULL; rp = rp->nexti) { + if ((rp - 1)->source_file == src + && lineno >= (rp + 1)->first_line + && lineno <= (rp + 1)->last_line) + return (rp - 1); + } + return NULL; +} + +/* mk_breakpoint --- create a breakpoint instruction and the corresponding + * breakpoint structure. + */ + +static INSTRUCTION * +mk_breakpoint(char *src, int srcline) +{ + INSTRUCTION *bp; + BREAKPOINT *b; + + bp = bcalloc(Op_breakpoint, 1, srcline); + emalloc(b, BREAKPOINT *, sizeof(BREAKPOINT), "mk_breakpoint"); + memset(&b->cndn, 0, sizeof(struct condition)); + b->commands.next = b->commands.prev = &b->commands; + b->silent = FALSE; + + + b->number = ++watch_list.number; /* breakpoints and watchpoints use same counter */ + b->ignore_count = 0; + b->hit_count = 0; + b->flags = BP_ENABLE; + b->src = src; + bp->break_pt = b; + b->bpi = bp; + + /* prepend to list */ + b->next = breakpoints.next; + b->prev = &breakpoints; + breakpoints.next = b; + b->next->prev = b; + return bp; +} + +/* delete_breakpoint --- delete a breakpoint structure and + * disable the breakpoint instruction. + */ + +static void +delete_breakpoint(BREAKPOINT *b) +{ + INSTRUCTION *pc = b->bpi; + struct commands_item *c; + + /* N.B.: easiest thing to do is to turn Op_breakpoint into a no-op; + * deleteing the instruction is not that simple, + * since could have reference to it somewhere else (e.g. cur_pc). + */ + + pc->opcode = Op_no_op; + pc->source_line = 0; + pc->break_pt = NULL; + + /* delete commands */ + for (c = b->commands.next; c != &b->commands; c = c->next) { + c = c->prev; + delete_commands_item(c->next); + } + + free_context(b->cndn.ctxt, FALSE); + if (b->cndn.expr != NULL) + efree(b->cndn.expr); + + /* remove from list */ + b->next->prev = b->prev; + b->prev->next = b->next; + efree(b); +} + +/* find_breakpoint --- find the breakpoint structure from a breakpoint number */ + +static BREAKPOINT * +find_breakpoint(long num) +{ + BREAKPOINT *b; + + if (num <= 0) + return NULL; + + for (b = breakpoints.next; b != &breakpoints; b = b->next) { + if (b->number == num) + return b; + } + return NULL; +} + +/* add_breakpoint --- add a breakpoint instruction between PREVP and IP */ + +static BREAKPOINT * +add_breakpoint(INSTRUCTION *prevp, INSTRUCTION *ip, char *src, int silent) +{ + BREAKPOINT *b; + INSTRUCTION *bp; + int lineno = ip->source_line; + + /* add new breakpoint instruction at the end of + * already set breakpoints at this line number. + */ + + while (ip->opcode == Op_breakpoint && ip->source_line == lineno) { + if (! silent) { + b = ip->break_pt; + /* + * This is more verbose that it might otherwise be, + * in order to provide easily translatable strings. + */ + if (b->flags & BP_ENABLE) { + if (b->flags & BP_IGNORE) + fprintf(out_fp, + _("Note: breakpoint %d (enabled, ignore next %ld hits), also set at %s:%d"), + b->number, + b->ignore_count, + b->src, + lineno); + else + fprintf(out_fp, + _("Note: breakpoint %d (enabled), also set at %s:%d"), + b->number, + b->src, + lineno); + } else { + if (b->flags & BP_IGNORE) + fprintf(out_fp, + _("Note: breakpoint %d (disabled, ignore next %ld hits), also set at %s:%d"), + b->number, + b->ignore_count, + b->src, + lineno); + else + fprintf(out_fp, + _("Note: breakpoint %d (disabled), also set at %s:%d"), + b->number, + b->src, + lineno); + } + } + prevp = ip; + ip = ip->nexti; + } + + assert(ip->source_line == lineno); + + bp = mk_breakpoint(src, lineno); + prevp->nexti = bp; + bp->nexti = ip; + b = bp->break_pt; + if (! silent) + fprintf(out_fp, _("Breakpoint %d set at file `%s', line %d\n"), + b->number, src, lineno); + return b; +} + +/* set_breakpoint_at --- set a breakpoint at given line number*/ + +static BREAKPOINT * +set_breakpoint_at(INSTRUCTION *rp, int lineno, int silent) +{ + INSTRUCTION *ip, *prevp; + + for (prevp = rp, ip = rp->nexti; ip; prevp = ip, ip = ip->nexti) { + if (ip->source_line >= lineno) + return add_breakpoint(prevp, ip, rp->source_file, silent); + if (ip == (rp + 1)->lasti) + break; + } + return NULL; +} + +/* set_breakpoint_next --- set a breakpoint at the next instruction */ + +static BREAKPOINT * +set_breakpoint_next(INSTRUCTION *rp, INSTRUCTION *ip) +{ + INSTRUCTION *prevp; + + if (ip == (rp + 1)->lasti) + return NULL; + prevp = ip; + if (ip->opcode != Op_breakpoint) + ip = ip->nexti; + for (; ip; prevp = ip, ip = ip->nexti) { + if (ip->source_line > 0) + return add_breakpoint(prevp, ip, rp->source_file, FALSE); + if (ip == (rp + 1)->lasti) + break; + } + return NULL; +} + +/* set_breakpoint --- set a breakpoint */ + +static int +set_breakpoint(CMDARG *arg, int temporary) +{ + int lineno; + BREAKPOINT *b = NULL; + INSTRUCTION *rp, *ip; + NODE *func; + SRCFILE *s = cur_srcfile; + char *src = cur_srcfile->src; + + if (arg == NULL) { +/* +* (From GDB Documentation): +* +* When called without any arguments, break sets a breakpoint at the next instruction +* to be executed in the selected stack frame (see section Examining the Stack). +* In any selected frame but the innermost, this makes your program stop as soon +* as control returns to that frame. This is similar to the effect of a finish command +* in the frame inside the selected frame--except that finish does not leave an +* active breakpoint. If you use break without an argument in the innermost frame, +* GDB stops the next time it reaches the current location; this may be useful +* inside loops. +* GDB normally ignores breakpoints when it resumes execution, until at least +* one instruction has been executed. If it did not do this, +* you would be unable to proceed past a breakpoint without first disabling the +* breakpoint. This rule applies whether or not the breakpoint already existed +* when your program stopped. +*/ + CHECK_PROG_RUNNING(); + if (cur_frame == 0) { + src = source; + ip = cur_pc; + } else { + NODE *f; + f = find_frame(cur_frame); + src = f->vname; + ip = (INSTRUCTION *) find_frame(cur_frame - 1)->reti; /* Op_func_call */ + } + rp = find_rule(src, ip->source_line); + assert(rp != NULL); + if ((b = set_breakpoint_next(rp, ip)) == NULL) + fprintf(out_fp, _("Can't set breakpoint in file `%s'\n"), src); + else { + if (cur_frame == 0) { /* stop next time */ + b->flags |= BP_IGNORE; + b->ignore_count = 1; + } + if (temporary) + b->flags |= BP_TEMP; + } + return FALSE; + } + + /* arg != NULL */ + + switch (arg->type) { + case D_string: /* break filename:lineno|function */ + s = source_find(arg->a_string); + arg = arg->next; + if (s == NULL || arg == NULL + || (arg->type != D_int && arg->type != D_func)) + return FALSE; + src = s->src; + if (arg->type == D_func) /* break filename:function */ + goto func; + else + /* fall through */ + case D_int: /* break lineno */ + lineno = (int) arg->a_int; + if (lineno <= 0 || lineno > s->srclines) + d_error(_("line number %d in file `%s' out of range"), lineno, src); + else { + rp = find_rule(src, lineno); + if (rp == NULL) + fprintf(out_fp, _("Can't find rule!!!\n")); + if (rp == NULL || (b = set_breakpoint_at(rp, lineno, FALSE)) == NULL) + fprintf(out_fp, _("Can't set breakpoint at `%s':%d\n"), + src, lineno); + if (b != NULL && temporary) + b->flags |= BP_TEMP; + } + break; + + case D_func: /* break function */ +func: + func = arg->a_node; + rp = func->code_ptr; + if ((b = set_breakpoint_at(rp, rp->source_line, FALSE)) == NULL) + fprintf(out_fp, _("Can't set breakpoint in function `%s'\n"), + func->lnode->param); + else if (temporary) + b->flags |= BP_TEMP; + lineno = b->bpi->source_line; + break; + + default: + return FALSE; + } + /* condition if any */ + arg = arg->next; + if (b != NULL && arg != NULL) { + if (parse_condition(D_break, b->number, arg->a_string) == 0) + arg->a_string = NULL; /* don't let free_cmdarg free it */ + else + fprintf(out_fp, _("breakpoint %d set at file `%s', line %d is unconditional\n"), + b->number, src, lineno); + } + return FALSE; +} + + +/* breakpoint_triggered --- check if we should stop at this breakpoint */ + +static int +breakpoint_triggered(BREAKPOINT *b) +{ + if ((b->flags & BP_ENABLE) == 0) + return 0; + if ((b->flags & BP_IGNORE) != 0) { + if (--b->ignore_count <= 0) + b->flags &= ~BP_IGNORE; + return 0; + } + + if (! condition_triggered(&b->cndn)) + return 0; + + b->hit_count++; + if (b->flags & BP_ENABLE_ONCE) { + b->flags &= ~BP_ENABLE_ONCE; + b->flags &= ~BP_ENABLE; + } + return b->number; +} + +/* do_breakpoint --- break command */ + +int +do_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + return set_breakpoint(arg, FALSE); +} + +/* do_tmp_breakpoint --- tbreak command */ + +int +do_tmp_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + return set_breakpoint(arg, TRUE); +} + +/* do_clear --- clear command */ + +int +do_clear(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + int lineno; + BREAKPOINT *b; + INSTRUCTION *rp, *ip; + NODE *func; + SRCFILE *s = cur_srcfile; + char *src = cur_srcfile->src; + int bp_found = FALSE; + + if (arg == NULL) { /* clear */ + CHECK_PROG_RUNNING(); + if (cur_frame == 0) { + lineno = sourceline; + src = source; + } else { + NODE *f; + f = find_frame(cur_frame); + src = f->vname; + lineno = ((INSTRUCTION *) find_frame(cur_frame - 1)->reti)->source_line; + } + goto delete_bp; + } + + switch (arg->type) { + case D_string: /* clear filename:lineno|function */ + s = source_find(arg->a_string); + arg = arg->next; + if (s == NULL || arg == NULL || + (arg->type != D_int && arg->type != D_func)) + return FALSE; + src = s->src; + if (arg->type == D_func) + goto func; + /* else + fall through */ + case D_int: /* clear lineno */ + lineno = (int) arg->a_int; + if (lineno <= 0 || lineno > s->srclines) { + d_error(_("line number %d in file `%s' out of range"), lineno, src); + return FALSE; + } + break; + + case D_func: /* clear function */ +func: + func = arg->a_node; + rp = func->code_ptr; + for (ip = rp->nexti; ip; ip = ip->nexti) { + if (ip->source_line <= 0) + continue; + if (ip->opcode != Op_breakpoint) + break; + b = ip->break_pt; + if (++bp_found == 1) + fprintf(out_fp, _("Deleted breakpoint %d"), b->number); + else + fprintf(out_fp, ", %d", b->number); + delete_breakpoint(b); + } + if (! bp_found) + fprintf(out_fp, _("No breakpoint(s) at entry to function `%s'\n"), + func->lnode->param); + else + fprintf(out_fp, "\n"); + /* fall through */ + default: + return FALSE; + } + +delete_bp: + rp = find_rule(src, lineno); + if (rp != NULL) { + for (ip = rp->nexti; ip; ip = ip->nexti) { + if (ip->opcode == Op_breakpoint && ip->source_line == lineno) { + b = ip->break_pt; + if (++bp_found == 1) + fprintf(out_fp, _("Deleted breakpoint %d"), b->number); + else + fprintf(out_fp, ", %d", b->number); + delete_breakpoint(b); + } + if (ip == (rp + 1)->lasti) + break; + } + } + + if (! bp_found) + fprintf(out_fp, _("No breakpoint at file `%s', line #%d\n"), + src, (int) lineno); + else + fprintf(out_fp, "\n"); + return FALSE; +} + +/* enable_breakpoint --- enable a breakpoint and set its disposition */ + +static inline void +enable_breakpoint(BREAKPOINT *b, short disp) +{ + b->flags &= ~(BP_ENABLE_ONCE|BP_TEMP); + b->flags |= BP_ENABLE; + if (disp) + b->flags |= disp; +} + +/* do_enable_breakpoint --- enable command */ + +int +do_enable_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + BREAKPOINT *b; + short disp = 0; + + if (arg != NULL && arg->type == D_argument) { + if (arg->a_argument == A_DEL) /* del */ + disp = BP_TEMP; + else /* once */ + disp = BP_ENABLE_ONCE; + arg = arg->next; + } + + if (arg == NULL) { /* enable [once|del] */ + for (b = breakpoints.next; b != &breakpoints; b = b->next) + enable_breakpoint(b, disp); + } + + for (; arg != NULL; arg = arg->next) { + if (arg->type == D_range) { + long i, j; + + i = arg->a_int; + arg = arg->next; + j = arg->a_int; + if (j > breakpoints.number) + j = breakpoints.number; + for (; i <= j; i++) { + if ((b = find_breakpoint(i)) != NULL) + enable_breakpoint(b, disp); + } + } else { + assert(arg->type == D_int); + if ((b = find_breakpoint(arg->a_int)) == NULL) + d_error(_("invalid breakpoint number")); + else + enable_breakpoint(b, disp); + } + } + return FALSE; +} + +/* do_delete_breakpoint --- delete command */ + +int +do_delete_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + if (arg == NULL) { + int delete_all = TRUE; + delete_all = prompt_yes_no( + _("Delete all breakpoints? (y or n) "), + _("y")[0], TRUE, out_fp); + + if (delete_all) { + while (breakpoints.next != &breakpoints) + delete_breakpoint(breakpoints.next); + } + } + + for (; arg != NULL; arg = arg->next) { + BREAKPOINT *b; + if (arg->type == D_range) { + long i, j; + + i = arg->a_int; + arg = arg->next; + j = arg->a_int; + if (j > breakpoints.number) + j = breakpoints.number; + for (; i <= j; i++) { + if ((b = find_breakpoint(i)) != NULL) + delete_breakpoint(b); + } + } else { + if ((b = find_breakpoint(arg->a_int)) == NULL) + d_error(_("invalid breakpoint number")); + else + delete_breakpoint(b); + } + } + return FALSE; +} + +/* do_ignore_breakpoint --- ignore command */ + +int +do_ignore_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + BREAKPOINT *b; + + if (arg == NULL || arg->type != D_int + || arg->next == NULL || arg->next->type != D_int) + return FALSE; + + if ((b = find_breakpoint(arg->a_int)) == NULL) + d_error(_("invalid breakpoint number")); + else { + b->ignore_count = arg->next->a_int; + if (b->ignore_count > 0) { + b->flags |= BP_IGNORE; + fprintf(out_fp, _("Will ignore next %ld crossing(s) of breakpoint %d.\n"), + b->ignore_count, b->number); + } else { + b->flags &= ~BP_IGNORE; + fprintf(out_fp, _("Will stop next time breakpoint %d is reached.\n"), + b->number); + } + } + return FALSE; +} + +/* do_disable_breakpoint --- disable command */ + +int +do_disable_breakpoint(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + BREAKPOINT *b; + + if (arg == NULL) { + /* disable all */ + for (b = breakpoints.next; b != &breakpoints; b = b->next) + b->flags &= ~BP_ENABLE; + } + + for (; arg != NULL; arg = arg->next) { + if (arg->type == D_range) { + long i, j; + + i = arg->a_int; + arg = arg->next; + j = arg->a_int; + if (j > breakpoints.number) + j = breakpoints.number; + for (; i <= j; i++) + if ((b = find_breakpoint(i)) != NULL) + b->flags &= ~BP_ENABLE; + } else { + if ((b = find_breakpoint(arg->a_int)) == NULL) + d_error(_("invalid breakpoint number")); + else + b->flags &= ~BP_ENABLE; + } + } + return FALSE; +} + +#ifdef HAVE_LIBREADLINE + +/* get_parmlist --- list of function params in current context */ + +char ** +get_parmlist() +{ + NODE *func; + + if (! prog_running) + return NULL; + func = find_frame(cur_frame)->func_node; + if (func == NULL) /* in main */ + return NULL; + return func->parmlist; +} + +/* initialize_readline --- initialize readline */ + +static void +initialize_readline() +{ + /* tell readline which stream to use for output, + * default input stream is stdin. + */ + rl_outstream = out_fp; + + /* allow conditional parsing of the ~/.inputrc file. */ + rl_readline_name = "gawk"; + + /* our completion function. */ + rl_attempted_completion_function = command_completion; + + read_a_line = readline; +} +#else +#define initialize_readline() /* nothing */ +#endif + + +/* interpret --- debugger entry point */ + +int +interpret(INSTRUCTION *pc) +{ + char *run; + + input_fd = fileno(stdin); + out_fp = stdout; + if (isatty(input_fd)) + input_from_tty = TRUE; + if (input_fd == 0 && input_from_tty) + initialize_readline(); + + if (! read_a_line) + read_a_line = g_readline; + + push_cmd_src(input_fd, input_from_tty, read_a_line, 0, 0, EXIT_FATAL); + + setbuf(out_fp, (char *) NULL); + for (cur_srcfile = srcfiles->prev; cur_srcfile != srcfiles; + cur_srcfile = cur_srcfile->prev) { + if (cur_srcfile->stype == SRC_FILE + || cur_srcfile->stype == SRC_INC) + break; + } + + if (cur_srcfile == srcfiles) { + fprintf(out_fp, _("Can only debug programs provided with the `-f' option.\n")); + exit(EXIT_FAILURE); + } + + dgawk_Prompt = estrdup(DEFAULT_PROMPT, strlen(DEFAULT_PROMPT)); + dPrompt = dgawk_Prompt; + + memset(&stop, 0, sizeof(stop)); + stop.command = D_illegal; + + if ((run = getenv("DGAWK_RESTART")) != NULL) { + /* We are restarting; restore state (breakpoints, history etc.) + * passed as environment variables and optionally execute the run command. + */ + unserialize(BREAK); + unserialize(WATCH); + unserialize(DISPLAY); + unserialize(HISTORY); + unserialize(OPTION); + unsetenv("DGAWK_RESTART"); + fprintf(out_fp, "Restarting ...\n"); + if (run[0] == 'T') + (void) do_run(NULL, 0); + + } else if (command_file != NULL) { + /* run commands from a file (--command=file or -R file) */ + int fd; + fd = open_readfd(command_file); + if (fd == INVALID_HANDLE) { + fprintf(stderr, _("can't open source file `%s' for reading (%s)"), + command_file, strerror(errno)); + exit(EXIT_FAILURE); + } + push_cmd_src(fd, FALSE, g_readline, close, 0, EXIT_FAILURE); + cmd_src->str = estrdup(command_file, strlen(command_file)); + + } else { + int fd; + +#ifdef HAVE_LIBREADLINE + (void) read_history(history_file); + sess_history_base = history_length; +#endif + + /* read saved options */ + fd = open_readfd(options_file); + if (fd > INVALID_HANDLE) + push_cmd_src(fd, FALSE, g_readline, close, 0, EXIT_SUCCESS); + } + + /* start the command interpreter */ + read_command(); /* yyparse */ + return EXIT_SUCCESS; +} + + +/* N.B.: ignore breakpoints and watchpoints for return command */ + +/* check_watchpoint --- check if any watchpoint triggered */ + +static int +check_watchpoint() +{ + struct list_item *w; + + if (stop.command == D_return) + return FALSE; + for (w = watch_list.prev; w != &watch_list; w = w->prev) { + int wnum = watchpoint_triggered(w); + if (wnum > 0) { + stop.watch_point = wnum; + stop.print_frame = TRUE; + return TRUE; + } + } + return FALSE; +} + +/* check_breakpoint --- check if breakpoint triggered */ + +static int +check_breakpoint(INSTRUCTION **pi) +{ + INSTRUCTION *pc; + + pc = *pi; + if (stop.command == D_return) + return FALSE; + if (pc->opcode == Op_breakpoint) { + int bnum; + *pi = pc->nexti; /* skip past the breakpoint instruction; + * interpreter doesn't process Op_breakpoint. + */ + bnum = breakpoint_triggered(pc->break_pt); + if (bnum > 0) { + stop.break_point = bnum; + stop.print_frame = TRUE; + return TRUE; + } + } + return FALSE; +} + +/* restart --- restart the debugger */ + +static void +restart(int run) +{ + /* save state in the environment after serialization */ + serialize(BREAK); + serialize(WATCH); + serialize(DISPLAY); + serialize(HISTORY); + serialize(OPTION); + + /* tell the new process to restore state from the environment */ + setenv("DGAWK_RESTART", (run ? "TRUE" : "FALSE"), 1); + + /* close all open files */ + close_all(); + + /* start a new process replacing the current process */ + execvp(d_argv[0], d_argv); + + /* execvp failed !!! */ + fprintf(out_fp, _("Failed to restart debugger")); + exit(EXIT_FAILURE); +} + +/* do_run --- run command */ + +int +do_run(CMDARG *arg ATTRIBUTE_UNUSED, int cmd ATTRIBUTE_UNUSED) +{ + if (prog_running) { + if (! input_from_tty) + need_restart = TRUE; /* handled later */ + else { + need_restart = prompt_yes_no( + _("Program already running. Restart from beginning (y/n)? "), + _("y")[0], FALSE, out_fp); + + if (! need_restart) { + fprintf(out_fp, _("Program not restarted\n")); + return FALSE; + } + } + } + + if (need_restart) { + /* avoid endless cycles of restarting */ + if (command_file != NULL) { + /* input_from_tty = FALSE */ + fprintf(stderr, _("error: cannot restart, operation not allowed\n")); + exit(EXIT_FAILURE); + } + + if (cmd_src->cmd == D_source) { + /* input_from_tty = FALSE */ + fprintf(out_fp, _("error (%s): cannot restart, ignoring rest of the commands\n"), cmd_src->str); + pop_cmd_src(); + return FALSE; + } + + restart(TRUE); /* does not return */ + } + + fprintf(out_fp, _("Starting program: \n")); + + prog_running = TRUE; + fatal_tag_valid = TRUE; + if (setjmp(fatal_tag) == 0) + (void) r_interpret(code_block); + + fatal_tag_valid = FALSE; + prog_running = FALSE; + fprintf(out_fp, _("Program exited %s with exit value: %d\n"), + (! exiting && exit_val != EXIT_SUCCESS) ? "abnormally" + : "normally", + exit_val); + need_restart = TRUE; + return FALSE; +} + +/* do_quit --- quit command */ + +int +do_quit(CMDARG *arg ATTRIBUTE_UNUSED, int cmd ATTRIBUTE_UNUSED) +{ + int terminate = TRUE; + if (prog_running) + terminate = prompt_yes_no( + _("The program is running. Exit anyway (y/n)? "), + _("y")[0], TRUE, out_fp); + if (terminate) { + close_all(); + do_trace = FALSE; /* don't save 'trace on' */ + +#ifdef HAVE_LIBREADLINE + if (do_save_history && input_from_tty) { + int ret; + ret = write_history(history_file); + if (ret == 0 && history_length > history_size) + (void) history_truncate_file(history_file, history_size); + } +#endif + if (do_save_options && input_from_tty) + save_options(options_file); + + exit(exit_val); + } + return FALSE; +} + +/* do_continue --- continue command */ + +int +do_continue(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + BREAKPOINT *b; + + CHECK_PROG_RUNNING(); + if (! arg || arg->type != D_int) + return TRUE; + + /* arg is breakpoint ignore count if stopped at a breakpoint */ + if (! stop.break_point) { + fprintf(out_fp, _("Not stopped at any breakpoint; argument ignored.\n")); + return TRUE; + } + b = find_breakpoint(stop.break_point); + if (b == NULL) { + d_error(_("invalid breakpoint number %d."), stop.break_point); + return FALSE; + } + b->flags |= BP_IGNORE; + b->ignore_count = arg->a_int; + fprintf(out_fp, _("Will ignore next %ld crossings of breakpoint %d.\n"), + b->ignore_count, stop.break_point); + return TRUE; +} + +/* next_step --- common code for next and step commands */ + +static int +next_step(CMDARG *arg, int cmd) +{ + CHECK_PROG_RUNNING(); + if (arg != NULL && arg->type == D_int) + stop.repeat_count = arg->a_int; + else + stop.repeat_count = 1; + stop.command = cmd; + return TRUE; +} + +/* check_step --- process step command, return TRUE if stopping */ + +static int +check_step(INSTRUCTION **pi) +{ + if (fcall_count != stop.fcall_count) { + stop.fcall_count = fcall_count; + stop.sourceline = sourceline; + stop.source = source; + stop.print_frame = TRUE; + return (--stop.repeat_count == 0); + } + + if (source != stop.source) { + stop.source = source; + stop.sourceline = sourceline; + return (--stop.repeat_count == 0); + } + + if (sourceline != stop.sourceline) { + stop.sourceline = sourceline; + return (--stop.repeat_count == 0); + } + return FALSE; +} + +/* do_step -- process step command, return TRUE if stopping */ + +int +do_step(CMDARG *arg, int cmd) +{ + int ret; + ret = next_step(arg, cmd); + if (ret) { + stop.fcall_count = fcall_count; + stop.source = source; + stop.sourceline = sourceline; + stop.check_func = check_step; + } + return ret; +} + +/* do_stepi -- process stepi command, return TRUE if stopping */ + +static int +check_stepi(INSTRUCTION **pi) +{ + return (--stop.repeat_count == 0); +} + +/* do_stepi -- stepi command */ + +int +do_stepi(CMDARG *arg, int cmd) +{ + int ret; + ret = next_step(arg, cmd); + if (ret) + stop.check_func = check_stepi; + return ret; +} + + +/* check_next -- process next command returning TRUE if stopping */ + +static int +check_next(INSTRUCTION **pi) +{ + /* make sure not to step inside function calls */ + + if (fcall_count < stop.fcall_count) { + stop.fcall_count = fcall_count; + stop.sourceline = sourceline; + stop.source = source; + stop.print_frame = TRUE; + return (--stop.repeat_count == 0); + } + + if (fcall_count == stop.fcall_count) { + if (source != stop.source) { + stop.source = source; + stop.sourceline = sourceline; + return (--stop.repeat_count == 0); + } + if (sourceline != stop.sourceline) { + stop.sourceline = sourceline; + return (--stop.repeat_count == 0); + } + } + +#if 0 + /* redundant ? */ + if (fcall_count > stop.fcall_count) { + stop.source = source; + stop.sourceline = sourceline; + } +#endif + + return FALSE; +} + +/* do_next -- next command */ + +int +do_next(CMDARG *arg, int cmd) +{ + int ret; + + ret = next_step(arg, cmd); + if (ret) { + stop.source = source; + stop.sourceline = sourceline; + stop.fcall_count = fcall_count; + stop.check_func = check_next; + } + return ret; +} + +/* check_nexti --- process nexti command, returns TRUE if stopping */ + +static int +check_nexti(INSTRUCTION **pi) +{ + + /* make sure not to step inside function calls */ + + if (fcall_count < stop.fcall_count) { + stop.print_frame = TRUE; + stop.fcall_count = fcall_count; + } + return (fcall_count == stop.fcall_count + && --stop.repeat_count == 0); +} + +/* do_nexti -- nexti command */ + +int +do_nexti(CMDARG *arg, int cmd) +{ + int ret; + + ret = next_step(arg, cmd); + if (ret) { + stop.fcall_count = fcall_count; + stop.check_func = check_nexti; + } + return ret; +} + +/* check_finish --- process finish command, returns TRUE if stopping */ + +static int +check_finish(INSTRUCTION **pi) +{ + if (fcall_count == stop.fcall_count) { + stop.print_frame = TRUE; + return TRUE; + } + return FALSE; +} + +/* do_finish --- finish command */ + +int +do_finish(CMDARG *arg ATTRIBUTE_UNUSED, int cmd) +{ + CHECK_PROG_RUNNING(); + if (cur_frame == fcall_count) { + fprintf(out_fp, + _("'finish' not meaningful in the outermost frame main()\n")); + return FALSE; + } + stop.fcall_count = fcall_count - cur_frame - 1; + assert(stop.fcall_count >= 0); + fprintf(out_fp, _("Run till return from ")); + print_numbered_frame(cur_frame); + stop.check_func = check_finish; + stop.command = cmd; + stop.print_ret = TRUE; + return TRUE; +} + +/* check_return --- process return, returns TRUE if stopping */ + +static int +check_return(INSTRUCTION **pi) +{ + assert(fcall_count >= stop.fcall_count); + + if (fcall_count == stop.fcall_count) { + stop.print_frame = TRUE; + return TRUE; + } + + if (fcall_count > stop.fcall_count) { /* innermost frame just returned */ + /* force this one to return too */ + NODE *func; + + func = find_frame(cur_frame)->func_node; + assert(func != NULL); + *pi = (func->code_ptr + 1)->lasti; + /* assert((*pi)->opcode == Op_K_return); */ + } + + return FALSE; +} + +/* do_return --- return command */ + +int +do_return(CMDARG *arg, int cmd) +{ + NODE *func; + + CHECK_PROG_RUNNING(); + func = find_frame(cur_frame)->func_node; + if (func == NULL) { + fprintf(out_fp, _("'return' not meaningful in the outermost frame main()\n")); + return FALSE; + } + + stop.fcall_count = fcall_count - cur_frame - 1; + assert(stop.fcall_count >= 0); + stop.pc = (func->code_ptr + 1)->lasti; + assert(stop.pc->opcode == Op_K_return); + stop.command = cmd; + + stop.check_func = check_return; + + if (arg != NULL && arg->type == D_node) { /* optional return value */ + NODE *n; + n = dupnode(arg->a_node); + PUSH(n); + } else + PUSH(Nnull_string); + + return TRUE; +} + +/* check_until --- process until, returns TRUE if stopping */ + +int +check_until(INSTRUCTION **pi) +{ + if (fcall_count < stop.fcall_count) { /* current stack frame returned */ + stop.print_frame = TRUE; + return TRUE; + } else if (fcall_count == stop.fcall_count) { + if (stop.pc && *pi == stop.pc) /* until location */ + return TRUE; + if (stop.sourceline > 0 /* until */ + && source == stop.source + && sourceline > stop.sourceline) + return TRUE; + } + return FALSE; +} + +/* do_until --- until command */ + +int +do_until(CMDARG *arg, int cmd) +{ + SRCFILE *s = cur_srcfile; + char *src = cur_srcfile->src; + int lineno; + INSTRUCTION *rp, *ip; + NODE *func; + + CHECK_PROG_RUNNING(); + stop.pc = NULL; + stop.sourceline = 0; + + if (arg == NULL) { /* until without argument */ + + /* GDB doc.: continue running until a source line past the current line, + * in the current stack frame, is reached. Is used to avoid single + * stepping through a loop more than once. ... + * This means that when you reach the end of a loop after single + * stepping though it, until makes your program continue execution + * until it exits the loop. In contrast, a next command at the end + * of a loop simply steps back to the beginning of the loop, which + * forces you to step through the next iteration. + */ + + stop.source = source; + stop.sourceline = sourceline; + stop.fcall_count = fcall_count - cur_frame; + stop.check_func = check_until; + stop.command = cmd; + return TRUE; + } + + /* GDB: until location - continue running program until + * either the specified location is reached, or the + * current stack frame returns. + */ + + switch (arg->type) { + case D_string: /* until filename : lineno|function */ + s = source_find(arg->a_string); + arg = arg->next; + if (s == NULL || arg == NULL + || (arg->type != D_int && arg->type != D_func)) + return FALSE; + src = s->src; + if (arg->type == D_func) + goto func; + /* else + fall through */ + case D_int: /* until lineno */ + lineno = arg->a_int; + if (lineno <= 0 || lineno > s->srclines) { + d_error(_("line number %d in file `%s' out of range"), + lineno, src); + return FALSE; + } + break; + + case D_func: /* until function */ +func: + func = arg->a_node; + rp = func->code_ptr; + for (ip = rp->nexti; ip; ip = ip->nexti) { + if (ip->opcode != Op_breakpoint && ip->source_line > 0) { + stop.pc = ip; + stop.fcall_count = fcall_count - cur_frame; + stop.check_func = check_until; + stop.command = cmd; + return TRUE; + } + } + fprintf(out_fp, _("Can't find specified location in function `%s'\n"), + func->lnode->param); + /* fall through */ + default: + return FALSE; + } + + if ((rp = find_rule(src, lineno)) == NULL) { + d_error(_("invalid source line %d in file `%s'"), lineno, src); + return FALSE; + } + + for (ip = rp->nexti; ip; ip = ip->nexti) { + if (ip->opcode != Op_breakpoint && ip->source_line >= lineno) { + stop.pc = ip; + stop.fcall_count = fcall_count - cur_frame; + stop.check_func = check_until; + stop.command = cmd; + return TRUE; + } + if (ip == (rp + 1)->lasti) + break; + } + fprintf(out_fp, _("Can't find specified location %d in file `%s'\n"), + lineno, src); + return FALSE; +} + +/* print_watch_item --- print watched item name, old and current values */ + +static void +print_watch_item(struct list_item *w) +{ + NODE *symbol, *sub; + int i; + + symbol = w->symbol; + if (IS_SUBSCRIPT(w)) { + fprintf(out_fp, "%s", w->sname); + for (i = 0; i < w->num_subs; i++) { + sub = w->subs[i]; + fprintf(out_fp, "[\"%s\"]", sub->stptr); + } + fprintf(out_fp, "\n"); + } else if (IS_FIELD(w)) + fprintf(out_fp, "$%ld\n", (long) symbol->numbr); + else + fprintf(out_fp, "%s\n", w->sname); + + +#define print_value(X, S, V) \ +if (X) \ + fprintf(out_fp, "array, %ld elements\n", w->S); \ +else if (! w->V) \ + fprintf(out_fp, IS_SUBSCRIPT(w) ? \ + _("element not in array\n") : _("untyped variable\n")); \ +else \ + valinfo(w->V, fprintf, out_fp); + + fprintf(out_fp, " Old value: "); + print_value((w->flags & OLD_IS_ARRAY), old_size, old_value); + fprintf(out_fp, " New value: "); + print_value((w->flags & CUR_IS_ARRAY), cur_size, cur_value); + +#undef print_value +} + +/* next_command --- (optionally) print stoppage location and reason; + * also fetch next debug command from the user. + */ + +static void +next_command() +{ + static int last_rule = 0; + struct list_item *d = NULL, *w = NULL; + BREAKPOINT *b = NULL; + SRCFILE *s; + + if (source == NULL) { + stop.command = D_illegal; + stop.check_func = NULL; + return; + } + + if (stop.break_point) { + b = find_breakpoint(stop.break_point); + assert(b != NULL); + if (b->silent) + goto no_output; + } else if (stop.watch_point) { + w = find_item(&watch_list, stop.watch_point); + if (w->silent) + goto no_output; + } + + if (cur_rule != last_rule) { + fprintf(out_fp, _("Stopping in %s ...\n"), ruletab[cur_rule]); + last_rule = cur_rule; + } + + if (b != NULL) + fprintf(out_fp, "Breakpoint %d, ", b->number); + else if (w != NULL) { + fprintf(out_fp, "Watchpoint %d: ", w->number); + print_watch_item(w); + } + + /* frame info */ + if (stop.print_frame) { + print_frame(frame_ptr->func_node, source, sourceline); + fprintf(out_fp, "\n"); + stop.print_frame = FALSE; + } + + (void) print_lines(source, sourceline, 1); + + /* automatic display of variables */ + for (d = display_list.prev; d != &display_list; d = d->prev) + display(d); + +no_output: + /* update last_printed_line, so that output of 'list' is + * centered around current sourceline + */ + + last_printed_line = sourceline - list_size / 2; + if (last_printed_line < 0) + last_printed_line = 0; + + /* update current source file */ + s = source_find(source); + if (cur_srcfile != s) { + if (cur_srcfile->fd != INVALID_HANDLE) { + close(cur_srcfile->fd); + cur_srcfile->fd = INVALID_HANDLE; + } + cur_srcfile = s; + } + + stop.command = D_illegal; + stop.check_func = NULL; + + if (b != NULL) { + int ret; + ret = execute_commands(&b->commands); + if ((b->flags & BP_TEMP) != 0) + delete_breakpoint(b); + if (ret) /* resume execution */ + return; + } else if (w != NULL && execute_commands(&w->commands)) + return; + + read_command(); /* zzparse */ +} + +/* post_execute --- post_hook in the interpreter */ + +void +post_execute(INSTRUCTION *pc, int inloop) +{ + if (get_context()->level > 0) + return; + + switch (pc->opcode) { + case Op_K_break: + case Op_K_continue: + if (inloop) + break; + /* else + fall through */ + case Op_K_next: + case Op_K_nextfile: + case Op_K_exit: + if (stop.command == D_finish) { + /* cancel finish command */ + stop.print_ret = FALSE; + stop.print_frame = FALSE; + stop.command = D_illegal; + stop.check_func = NULL; + fprintf(out_fp, _("'finish' not meaningful with non-local jump '%s'\n"), + op2str(pc->opcode)); + } else if (stop.command == D_until) { + /* cancel until command */ + stop.print_frame = FALSE; + stop.command = D_illegal; + stop.check_func = NULL; + fprintf(out_fp, _("'until' not meaningful with non-local jump '%s'\n"), + op2str(pc->opcode)); + } + break; + + case Op_K_return: + if (stop.command == D_finish + && fcall_count == stop.fcall_count + && stop.print_ret + ) { + NODE *r; + + assert(stack_empty() == FALSE); + /* print the returned value before it disappears. */ + r = TOP(); + fprintf(out_fp, "Returned value = "); + valinfo(r, fprintf, out_fp); + stop.print_ret = FALSE; + } + break; + + case Op_newfile: + case Op_get_record: + return; + + default: + break; + } +} + +/* pre_execute --- pre_hook, called by the interpreter before execution; + * checks if execution needs to be suspended and control + * transferred to the debugger. + */ + +int +pre_execute(INSTRUCTION **pi, int inloop) +{ + static int cant_stop = FALSE; + NODE *m; + + if (get_context()->level > 0) + return pre_execute_code(pi, inloop); + + cur_pc = *pi; + stop.break_point = 0; + stop.watch_point = 0; + cur_frame = 0; + + if (do_trace + && cur_pc->opcode != Op_breakpoint + && stop.command != D_return + ) + print_instruction(cur_pc, fprintf, out_fp, FALSE); + +/* N.B.: For Op_field_spec_lhs must execute instructions upto Op_field_assign + * as a group before stopping. Otherwise, watch/print of field variables + * yield surprising results. Ditto for Op_push_lhs for special variables + * (upto Op_var_assign, the set_FOO routine). + */ + + switch (cur_pc->opcode) { + case Op_field_spec_lhs: + cant_stop = TRUE; + break; + + case Op_field_assign: + cant_stop = FALSE; + return TRUE; /* may stop at next instruction */ + + case Op_push_lhs: + m = cur_pc->memory; + if (m->type == Node_var && m->var_assign) + cant_stop = TRUE; + break; + + case Op_arrayfor_incr: /* can have special var as array variable !!! */ + m = cur_pc->array_var; + if (m->type == Node_var && m->var_assign) + cant_stop = TRUE; + break; + + case Op_pop_loop: /* need to unset cant_stop for Op_arrayfor_incr */ + case Op_var_assign: + cant_stop = FALSE; + return TRUE; /* may stop at next instruction */ + + case Op_rule: + cur_rule = cur_pc->in_rule; + return TRUE; + + case Op_func: + case Op_ext_func: + case Op_var_update: + return TRUE; + + case Op_breakpoint: + break; /* processed later in check_breakpoint() */ + + default: + if (cur_pc->source_line <= 0) + return TRUE; + break; + } + + if (cant_stop) + return TRUE; + + assert(sourceline > 0); + + if (check_breakpoint(pi) + || check_watchpoint() + || (stop.check_func && stop.check_func(pi))) { + next_command(); /* return to debugger interface */ + if (stop.command == D_return) + *pi = stop.pc; /* jump to this instruction */ + } + + /* if cur_pc == *pi, interpreter executes cur_pc; + * Otherwise, jumps to instruction *pi. + */ + return (cur_pc == *pi); +} + +/* print_memory --- print a scalar value */ + +static void +print_memory(NODE *m, char **fparms, Func_print print_func, FILE *fp) +{ + switch (m->type) { + case Node_val: + if (m == Nnull_string) + print_func(fp, "Nnull_string"); + else if ((m->flags & NUMBER) != 0) + print_func(fp, "%g", m->numbr); + else if ((m->flags & STRING) != 0) + pp_string_fp(print_func, fp, m->stptr, m->stlen, '"', FALSE); + else if ((m->flags & NUMCUR) != 0) + print_func(fp, "%g", m->numbr); + else if ((m->flags & STRCUR) != 0) + pp_string_fp(print_func, fp, m->stptr, m->stlen, '"', FALSE); + else + print_func(fp, "-?-"); + print_func(fp, " [%s]", flags2str(m->flags)); + break; + + case Node_regex: + pp_string_fp(print_func, fp, m->re_exp->stptr, m->re_exp->stlen, '/', FALSE); + break; + + case Node_dynregex: + break; + + case Node_param_list: + assert(fparms != NULL); + print_func(fp, "%s", fparms[m->param_cnt]); + break; + + case Node_var: + case Node_var_new: + case Node_var_array: + print_func(fp, "%s", m->vname); + break; + + default: + print_func(fp, "?"); /* can't happen */ + } +} + +/* print_instruction --- print a bytecode */ + +static void +print_instruction(INSTRUCTION *pc, Func_print print_func, FILE *fp, int in_dump) +{ + static char **fparms = NULL; + int pcount = 0; + NODE *func = NULL; + static int noffset = 0; + + if (noffset == 0) { + static char buf[50]; + /* offset for 2nd to last lines in a multi-line output */ + noffset = sprintf(buf, "[ :%p] %-20.20s: ", pc, opcode2str(pc->opcode)); + } + + if (pc->opcode == Op_func) { + func = pc->func_body; + fparms = get_params(func); + pcount = get_param_count(func); + if (in_dump) { + int j; + print_func(fp, "\n\t# Function: %s (", func->lnode->param); + for (j = 0; j < pcount; j++) { + print_func(fp, "%s", fparms[j]); + if (j < pcount - 1) + print_func(fp, ", "); + } + print_func(fp, ")\n\n"); + } + } else if (pc->opcode == Op_ext_func) { + func = pc->func_body; + fparms = get_params(func); + pcount = get_param_count(func); + if (in_dump) + print_func(fp, "\n\t# Extension function: %s (... %d params ...)\n\n", + func->lnode->param, pcount); + } else if (pc->opcode == Op_rule) { + if (in_dump) + print_func(fp, "\n\t# %s\n\n", ruletab[pc->in_rule]); + } + + if (pc->opcode == Op_newfile) + print_func(fp, "\n"); + + if (pc->source_line <= 0) + print_func(fp, "[ :%p] %-20.20s: ", pc, opcode2str(pc->opcode)); + else + print_func(fp, "[%6d:%p] %-20.20s: ", + pc->source_line, pc, opcode2str(pc->opcode)); + + if (prog_running && ! in_dump) { + /* find params in the current frame */ + func = find_frame(0)->func_node; + if (func != NULL) + fparms = get_params(func); + /* else + fparms = NULL; */ + } + + + switch (pc->opcode) { + case Op_var_update: + print_func(fp, "[update_%s]\n", pc->memory->vname); + break; + + case Op_var_assign: + print_func(fp, "[set_%s]\n", pc->memory->vname); + break; + + case Op_field_spec_lhs: + print_func(fp, "[target_assign = %p] [do_reference = %s]\n", + pc->target_assign, pc->do_reference ? "TRUE" : "FALSE"); + break; + + case Op_ext_func: + print_func(fp, "[param_cnt = %d]\n", pcount); + break; + + case Op_func: + print_func(fp, "[param_cnt = %d] [source_file = %s]\n", pcount, + pc->source_file ? pc->source_file : "cmd. line"); + break; + + case Op_K_getline_redir: + print_func(fp, "[into_var = %s] [redir_type = \"%s\"]\n", + pc->into_var ? "TRUE" : "FALSE", + redir2str(pc->redir_type)); + break; + + case Op_K_getline: + print_func(fp, "[into_var = %s]\n", pc->into_var ? "TRUE" : "FALSE"); + print_func(fp, "%*s[target_beginfile = %p] [target_endfile = %p]\n", + noffset, "", + (pc + 1)->target_beginfile, (pc + 1)->target_endfile); + break; + + case Op_K_print_rec: + print_func(fp, "[redir_type = \"%s\"]\n", redir2str(pc->redir_type)); + break; + + case Op_K_print: + case Op_K_printf: + print_func(fp, "[expr_count = %ld] [redir_type = \"%s\"]\n", + pc->expr_count, redir2str(pc->redir_type)); + break; + + case Op_indirect_func_call: + case Op_func_call: + print_func(fp, "[func_name = %s] [arg_count = %ld]\n", + pc->func_name, (pc + 1)->expr_count); + break; + + case Op_push_loop: + if (pc->target_continue != NULL) + print_func(fp, "[target_continue = %p] [target_break = %p]\n", + pc->target_continue, pc->target_break); + else + print_func(fp, "[target_break = %p]\n", pc->target_break); + break; + + case Op_K_nextfile: + case Op_newfile: + print_func(fp, "[target_jmp = %p] [target_endfile = %p]\n", + pc->target_jmp, pc->target_endfile); + break; + + case Op_jmp: + case Op_jmp_false: + case Op_jmp_true: + case Op_and: + case Op_or: + case Op_K_next: + case Op_arrayfor_init: + case Op_K_break: + case Op_K_continue: + case Op_K_exit: + print_func(fp, "[target_jmp = %p]\n", pc->target_jmp); + break; + + case Op_arrayfor_incr: + print_func(fp, "[array_var = %s] [target_jmp = %p]\n", + pc->array_var->type == Node_param_list ? + fparms[pc->array_var->param_cnt] : pc->array_var->vname, + pc->target_jmp); + break; + + case Op_line_range: + print_func(fp, "[triggered = %ld] [target_jmp = %p]\n", + pc->triggered, pc->target_jmp); + break; + + case Op_cond_pair: + print_func(fp, "[line_range = %p] [target_jmp = %p]\n", + pc->line_range, pc->target_jmp); + break; + + case Op_builtin: + { + const char *fname = getfname(pc->builtin); + if (fname == NULL) + print_func(fp, "(extension func) [arg_count = %ld]\n", pc->expr_count); + else + print_func(fp, "%s [arg_count = %ld]\n", fname, pc->expr_count); + } + break; + + case Op_subscript: + case Op_sub_array: + print_func(fp, "[sub_count = %ld]\n", pc->sub_count); + break; + + case Op_store_sub: + print_memory(pc->memory, fparms, print_func, fp); + print_func(fp, " [sub_count = %ld]\n", pc->expr_count); + break; + + case Op_subscript_lhs: + print_func(fp, "[sub_count = %ld] [do_reference = %s]\n", + pc->sub_count, + pc->do_reference ? "TRUE" : "FALSE"); + break; + + case Op_K_delete: + case Op_in_array: + print_func(fp, "[expr_count = %ld]\n", pc->expr_count); + break; + case Op_concat: + /* NB: concat_flag CSVAR only used in grammar, don't display it */ + print_func(fp, "[expr_count = %ld] [concat_flag = %s]\n", + pc->expr_count, + (pc->concat_flag & CSUBSEP) ? "CSUBSEP" : "0"); + break; + + case Op_rule: + print_func(fp, "[in_rule = %s] [source_file = %s]\n", + ruletab[pc->in_rule], + pc->source_file ? pc->source_file : "cmd. line"); + break; + + case Op_lint: + { + static const char *const linttypetab[] = { + "LINT_illegal", + "LINT_assign_in_cond", + "LINT_no_effect" + }; + print_func(fp, "[lint_type = %s]\n", linttypetab[pc->lint_type]); + } + break; + + case Op_exec_count: + print_func(fp, "[exec_count = %ld]\n", pc->exec_count); + break; + + case Op_K_switch: + { + INSTRUCTION *curr; + print_func(fp, "[case_val = %p ] [switch_dflt = %p]\n", + pc->case_val, pc->switch_dflt); + + for (curr = pc->case_val; curr != NULL; curr = curr->nexti) { + print_func(fp, "[%6d:%p] %-20.20s: ", + curr->source_line, curr, opcode2str(curr->opcode)); + if (curr->opcode == Op_K_case) { + print_func(fp, "case: "); + print_memory(curr->memory, fparms, print_func, fp); + } + print_func(fp, " [target_stmt = %p]\n", curr->target_stmt); + } + } + break; + + case Op_store_var: + case Op_push_lhs: + print_memory(pc->memory, fparms, print_func, fp); + print_func(fp, " [do_reference = %s]\n", + pc->do_reference ? "TRUE" : "FALSE"); + break; + + case Op_push_i: + case Op_push: + case Op_push_param: + case Op_push_array: + case Op_push_re: + case Op_match_rec: + case Op_match: + case Op_nomatch: + case Op_plus_i: + case Op_minus_i: + case Op_times_i: + case Op_exp_i: + case Op_quotient_i: + case Op_mod_i: + case Op_assign_concat: + print_memory(pc->memory, fparms, print_func, fp); + /* fall through */ + default: + print_func(fp, "\n"); + break; + } +} + +/* do_trace_instruction --- trace command */ + +int +do_trace_instruction(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + if (arg != NULL && arg->type == D_argument + && arg->a_argument == A_TRACE_ON) + do_trace = TRUE; + else + do_trace = FALSE; + return FALSE; +} + +/* print_code --- print a list of instructions */ + +static int +print_code(INSTRUCTION *pc, void *x) +{ + struct pf_data *data = (struct pf_data *) x; + for (; pc != NULL; pc = pc->nexti) + print_instruction(pc, data->print_func, data->fp, data->defn /* in_dump */); + return 0; +} + +/* do_dump_instructions --- dump command */ + +int +do_dump_instructions(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + FILE *fp; + + if (arg != NULL && arg->type == D_string) { + /* dump to a file */ + if ((fp = fopen(arg->a_string, "w")) == NULL) { + d_error(_("could not open `%s' for writing (%s)"), + arg->a_string, strerror(errno)); + return FALSE; + } + pf_data.print_func = fprintf; + pf_data.fp = fp; + pf_data.defn = TRUE; /* in_dump = TRUE */ + (void) print_code(code_block, &pf_data); + (void) foreach_func((int (*)(INSTRUCTION *, void *)) print_code, + TRUE, /* sort */ + &pf_data /* data */ + ); + fclose(fp); + return FALSE; + } + + initialize_pager(out_fp); + if (setjmp(pager_quit_tag) == 0) { + pf_data.print_func = gprintf; + pf_data.fp = out_fp; + pf_data.defn = TRUE; /* in_dump = TRUE */ + (void) print_code(code_block, &pf_data); + (void) foreach_func((int (*)(INSTRUCTION *, void *)) print_code, + TRUE, /* sort */ + &pf_data /* data */ + ); + } + return FALSE; +} + +/* do_save --- save command */ + +int +do_save(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ +#ifdef HAVE_LIBREADLINE + FILE *fp; + HIST_ENTRY **hist_list; + int i; + + if ((fp = fopen(arg->a_string, "w")) == NULL) { + d_error(_("could not open `%s' for writing (%s)"), + arg->a_string, strerror(errno)); + return FALSE; + } + + hist_list = history_list(); + if (hist_list && history_length > sess_history_base) { + for (i = sess_history_base; hist_list[i] != NULL; i++) { + char *line; + line = hist_list[i]->line; + + /* exclude save commands; + * N.B.: this test may fail if there is another + * command with the same first 2 letters. + */ + + if (strlen(line) > 1 + && STREQN(line, "sa", 2)) + continue; + + fprintf(fp, "%s\n", line); + } + } + fclose(fp); +#endif + return FALSE; +} + +/* do_option --- option command */ + +int +do_option(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + const struct dbg_option *opt; + char *name, *value; + + if (arg == NULL) { /* display all available options and corresponding values */ + for (opt = option_list; opt->name; opt++) { + if (opt->str_val != NULL) + fprintf(out_fp, "%s = \"%s\"\n", opt->name, *(opt->str_val)); + else + fprintf(out_fp, "%s = %d\n", opt->name, *(opt->num_val)); + } + return FALSE; + } + + name = arg->a_string; + arg = arg->next; + value = arg ? arg->a_string : NULL; + + for (opt = option_list; opt->name; opt++) { /* linear search */ + if (STREQ(name, opt->name)) + break; + } + if (! opt->name) + return FALSE; + + if (value == NULL) { /* display current setting */ + if (opt->str_val != NULL) + fprintf(out_fp, "%s = \"%s\"\n", opt->name, *(opt->str_val)); + else + fprintf(out_fp, "%s = %d\n", opt->name, *(opt->num_val)); + } else + (*(opt->assign))(value); + return FALSE; +} + + +#ifdef HAVE_LIBREADLINE + +/* initialize_pager --- initialize our idea of the terminal size */ + +void +initialize_pager(FILE *fp) +{ + if (! isatty(fileno(fp)) || ! input_from_tty || input_fd != 0) { + screen_width = INT_MAX; + screen_height = INT_MAX; + } else { + /* get the terminal size from readline. */ + + rl_reset_terminal(NULL); /* N.B.: NULL argument means + * "use TERM env variable for terminal name". + */ + rl_get_screen_size(&screen_height, &screen_width); + if (screen_height <= 1) + screen_height = INT_MAX; + if (screen_width <= 1) + screen_width = INT_MAX; + } + pager_lines_printed = 0; +} +#endif + +static void +prompt_continue(FILE *fp) +{ + int quit_pager = FALSE; + + if (isatty(fileno(fp)) && input_fd == 0) + quit_pager = prompt_yes_no( + _("\t------[Enter] to continue or q [Enter] to quit------"), + _("q")[0], FALSE, fp); + if (quit_pager) + longjmp(pager_quit_tag, 1); + pager_lines_printed = 0; +} + +/* gprintf --- like fprintf but allows paging */ + +int +gprintf(FILE *fp, const char *format, ...) +{ + va_list args; + static char *buf = NULL; + static size_t buflen = 0; + static int bl = 0; + char *p, *q; + int nchar; + +#define GPRINTF_BUFSIZ 512 + if (buf == NULL) { + buflen = GPRINTF_BUFSIZ; + emalloc(buf, char *, (buflen + 2) * sizeof(char), "gprintf"); + } else if (buflen - bl < GPRINTF_BUFSIZ/2) { + buflen += GPRINTF_BUFSIZ; + erealloc(buf, char *, (buflen + 2) * sizeof(char), "gprintf"); + } +#undef GPRINTF_BUFSIZ + + while (TRUE) { + va_start(args, format); + nchar = vsnprintf(buf + bl, buflen - bl, format, args); + va_end(args); + if (nchar == 0) + return 0; + if (nchar > 0 && nchar < buflen - bl) { + bl += nchar; + if (buf[bl-1] != '\n') /* buffer output until see a newline at end */ + return nchar; + break; + } + + /* enlarge buffer, and try again */ + buflen *= 2; + erealloc(buf, char *, (buflen + 2) * sizeof(char), "gprintf"); + } + + bl = 0; + for (p = buf; (q = strchr(p, '\n')) != NULL; p = q + 1) { + int sz = (int) (q - p); + + while (sz > 0) { + int cnt; + cnt = sz > screen_width ? screen_width : sz; + + /* do not print partial line before scrolling */ + if (cnt < sz && (pager_lines_printed == (screen_height - 2))) + prompt_continue(fp); + + if (fwrite(p, sizeof(char), cnt, fp) != cnt) + return -1; + if (cnt == sz) + break; + else { + if (++pager_lines_printed == (screen_height - 1)) + prompt_continue(fp); + sz -= screen_width; + assert(sz > 0); + p += cnt; + } + } + + fprintf(fp, "\n"); + if (++pager_lines_printed == (screen_height - 1)) + prompt_continue(fp); + p++; + } + return nchar; +} + + +static int +serialize_subscript(char *buf, int buflen, struct list_item *item) +{ + int bl = 0, nchar, i; + NODE *sub; + + nchar = snprintf(buf, buflen, "%d%c%d%c%s%c%d%c", + item->number, FSEP, D_subscript, FSEP, item->sname, FSEP, + item->num_subs, FSEP); + if (nchar <= 0) + return 0; + else if (nchar >= buflen) /* need larger buffer */ + return nchar; + bl += nchar; + for (i = 0; i < item->num_subs; i++) { + sub = item->subs[i]; + nchar = snprintf(buf + bl, buflen - bl, "%lu%c%s%c", + (unsigned long) sub->stlen, FSEP, sub->stptr, FSEP); + if (nchar <= 0) + return 0; + bl += nchar; + if (bl >= buflen) /* need larger buffer */ + return bl; + } + return bl; +} + + + +/* serialize --- convert a list structure to a byte stream and + * save in environment. + */ + +static void +serialize(int type) +{ +#ifndef HAVE_LIBREADLINE +#define HIST_ENTRY void +#define history_list() NULL +#endif + + static char *buf = NULL; + static int buflen = 0; + int bl; + BREAKPOINT *b = NULL; + struct list_item *wd = NULL; + HIST_ENTRY **hist_list = NULL; + int hist_index = 0; + HIST_ENTRY *h = NULL; + struct dbg_option *opt = NULL; + struct commands_item *commands = NULL, *c; + int cnum = 0; + struct condition *cndn = NULL; + void *ptr, *end_ptr; + + switch (type) { + case BREAK: + end_ptr = (void *) &breakpoints; + ptr = (void *) breakpoints.prev; + break; + case WATCH: + end_ptr = (void *) &watch_list; + ptr = (void *) watch_list.prev; + break; + case DISPLAY: + end_ptr = (void *) &display_list; + ptr = (void *) display_list.prev; + break; + case HISTORY: + hist_list = history_list(); + if (hist_list == NULL) /* empty history list */ + return; + end_ptr = NULL; + ptr = (void *) hist_list[0]; + break; + case OPTION: + { + int n; + n = sizeof(option_list)/sizeof(option_list[0]); + end_ptr = (void *) &option_list[n - 1]; + ptr = (void *) option_list; + } + break; + + default: + return; + } + + if (type != HISTORY && ptr == end_ptr) /* empty list */ + return; + +#define SERIALIZE_BUFSIZ 512 + + if (buf == NULL) { /* first time */ + buflen = SERIALIZE_BUFSIZ; + emalloc(buf, char *, buflen + 2, "serialize"); + } + bl = 0; + + while (ptr != end_ptr) { + int nchar = 0; + if (buflen - bl < SERIALIZE_BUFSIZ/2) { +enlarge_buffer: + buflen *= 2; + erealloc(buf, char *, buflen + 2, "serialize"); + } + +#undef SERIALIZE_BUFSIZ + + /* field seperator is FSEP ('\037'), and the record separator is RSEP ('\036') */ + + switch (type) { + case BREAK: + b = (BREAKPOINT *) ptr; + + /* src source_line flags ignore_count hit_count number; + * commands and condition processed later in the end switch + */ + + nchar = snprintf(buf + bl, buflen - bl, + "%s%c%d%c%d%c%d%c%d%c%d%c", + b->src, FSEP, b->bpi->source_line, FSEP, b->flags, FSEP, + (int) b->ignore_count, FSEP, + (int) b->hit_count, FSEP, b->number, FSEP); + cnum = b->number; + commands = &b->commands; + cndn = &b->cndn; + break; + case DISPLAY: + case WATCH: + wd = (struct list_item *) ptr; + + /* subscript -- number type sname num_subs subs(stlen + stptr) [commands [condition]] + * variable -- number type sname [commands [condition]] + * field -- number type symbol(numbr) [commands [condition]] + */ + + if (IS_PARAM(wd)) /* exclude parameters */ + nchar = 0; + else if (IS_SUBSCRIPT(wd)) + nchar = serialize_subscript(buf + bl, buflen - bl, wd); + else if (IS_FIELD(wd)) + nchar = snprintf(buf + bl, buflen - bl, "%d%c%d%c%d%c", + wd->number, FSEP, D_field, FSEP, (int) wd->symbol->numbr, FSEP); + else + nchar = snprintf(buf + bl, buflen - bl, "%d%c%d%c%s%c", + wd->number, FSEP, D_variable, FSEP, wd->sname, FSEP); + cnum = wd->number; + commands = &wd->commands; + cndn = &wd->cndn; + break; + case HISTORY: +#ifdef HAVE_LIBREADLINE + h = (HIST_ENTRY *) ptr; + nchar = strlen(h->line); + if (nchar >= buflen - bl) + goto enlarge_buffer; + strcpy(buf + bl, h->line); +#endif + break; + case OPTION: + opt = (struct dbg_option *) ptr; + if (opt->num_val != NULL) + nchar = snprintf(buf + bl, buflen - bl, + "%s%c%d%c", opt->name, FSEP, *(opt->num_val), FSEP); + else + nchar = snprintf(buf + bl, buflen - bl, + "%s%c%s%c", opt->name, FSEP, *(opt->str_val), FSEP); + break; + default: + break; + } + + if (nchar == 0) /* skip empty history lines etc.*/ + ; + else if (nchar > 0 && nchar < buflen - bl) { + bl += nchar; + buf[bl] = RSEP; /* record */ + buf[++bl] = '\0'; + } else + goto enlarge_buffer; + + switch (type) { + case BREAK: + case WATCH: + /* recreate the `commands' command strings including the `commands' + * and `end' commands; command seperator is '\034'. + * re-parsed in unserialize to recover the commands list. + * Alternatively, one could encode(serialize) each command and it's arguments. + */ + + bl--; /* undo RSEP from above */ + + /* compute required room in buffer */ + nchar = 0; + for (c = commands->next; c != commands; c = c->next) { + nchar += (strlen(c->cmd_string) + 1); + if (c->cmd == D_eval) { + CMDARG *a = c->arg; + nchar += (strlen(a->a_string) + 1); /* awk statements */ + nchar += (strlen("end") + 1); + } + } + + if (nchar > 0) { /* non-empty commands list */ + nchar += (strlen("commands ") + 20 + strlen("end") + 2); /* 20 for cnum (an int) */ + if (nchar > buflen - bl) { + buflen = bl + nchar; + erealloc(buf, char *, buflen + 3, "serialize"); + } + nchar = sprintf(buf + bl, "commands %d", cnum); + bl += nchar; + buf[bl++] = CSEP; + for (c = commands->next; c != commands; c = c->next) { + nchar = strlen(c->cmd_string); + memcpy(buf + bl, c->cmd_string, nchar); + bl += nchar; + buf[bl++] = CSEP; + + if (c->cmd == D_eval) { + CMDARG *a = c->arg; + nchar = strlen(a->a_string); /* statements */ + memcpy(buf + bl, a->a_string, nchar); + bl += nchar; + buf[bl++] = CSEP; + nchar = strlen("end"); /* end of 'eval' */ + memcpy(buf + bl, "end", nchar); + bl += nchar; + buf[bl++] = CSEP; + } + } + nchar = strlen("end"); /* end of 'commands' */ + memcpy(buf + bl, "end", nchar); + bl += nchar; + } + buf[bl++] = FSEP; /* field */ + buf[bl++] = RSEP; /* record */ + buf[bl] = '\0'; + + /* condition expression */ + if (cndn->expr) { + bl--; /* undo RSEP from above */ + nchar = strlen(cndn->expr); + if (nchar > buflen - bl) { + buflen = bl + nchar; + erealloc(buf, char *, buflen + 3, "serialize"); + } + memcpy(buf + bl, cndn->expr, nchar); + bl += nchar; + buf[bl++] = FSEP; /* field */ + buf[bl++] = RSEP; /* record */ + buf[bl] = '\0'; + } + + ptr = (type == BREAK) ? (void *) b->prev : (void *) wd->prev; + break; + case DISPLAY: + ptr = (void *) wd->prev; + break; + case HISTORY: + ptr = (void *) hist_list[++hist_index]; + break; + case OPTION: + ptr = (void *) (++opt); + break; + default: + break; + } + } + + if (bl > 0) /* non-empty list */ + setenv(env_variable[type], buf, 1); +} + + +static void +unserialize_commands(char *str, int str_len) +{ + if (str_len <= 0 || str == NULL) + return; + commands_string = str; + commands_string_len = str_len; + push_cmd_src(INVALID_HANDLE, FALSE, read_commands_string, 0, 0, EXIT_FATAL); + line_sep = CSEP; + read_command(); /* forced to return in do_commands */ + pop_cmd_src(); +} + + +/* unserialize_list_item --- create a list_item structure from unserialized data */ + +static struct list_item * +unserialize_list_item(struct list_item *list, char **pstr, int *pstr_len, int field_cnt) +{ + int num, type, i; + struct list_item *l; + NODE *symbol = NULL; + int sub_cnt, cnt; + NODE **subs = NULL; + + /* subscript -- number type sname num_subs subs [commands [condition]] + * variable -- number type sname [commands [condition]] + * field -- number type symbol(numbr) commands [commands [condition]] + */ + + num = strtol(pstr[0], NULL, 0); + type = strtol(pstr[1], NULL, 0); + + if (type == D_field) { + int field_num; + field_num = strtol(pstr[2], NULL, 0); + symbol = make_number((AWKNUM) field_num); + cnt = 3; + } else { + char *name; + name = estrdup(pstr[2], pstr_len[2]); + symbol = find_symbol(name, NULL); + efree(name); + if (symbol == NULL) + return NULL; + cnt = 3; + if (type == D_subscript) { + int sub_len; + sub_cnt = strtol(pstr[3], NULL, 0); + emalloc(subs, NODE **, sub_cnt * sizeof(NODE *), "unserialize_list_item"); + cnt++; + for (i = 0; i < sub_cnt; i++) { + sub_len = strtol(pstr[cnt], NULL, 0); + subs[i] = make_string(pstr[cnt + 1], sub_len); + cnt += 2; + } + } + } + + l = add_item(list, type, symbol, NULL); + if (type == D_subscript) { + l->num_subs = sub_cnt; + l->subs = subs; + } + l->number = num; /* keep same item number across executions */ + + if (list == &watch_list) { + initialize_watch_item(l); + /* unserialize watchpoint `commands' */ + unserialize_commands(pstr[cnt], pstr_len[cnt]); + cnt++; + if (field_cnt > cnt) { + char *expr; + expr = estrdup(pstr[cnt], pstr_len[cnt]); + if (parse_condition(D_watch, l->number, expr) != 0) + efree(expr); + } + if (num > list->number) /* update list number counter */ + list->number = num; + } else + list->number = num; + + return l; +} + +/* unserialize_breakpoint --- create a breakpoint structure from unserialized data */ + +static BREAKPOINT * +unserialize_breakpoint(char **pstr, int *pstr_len, int field_cnt) +{ + char *src; + int lineno; + BREAKPOINT *b = NULL; + INSTRUCTION *rp; + SRCFILE *s; + + /* src source_line flags ignore_count hit_count number commands [condition] */ + + src = estrdup(pstr[0], pstr_len[0]); + s = source_find(src); + efree(src); + if (s == NULL) + return NULL; + src = s->src; + lineno = strtol(pstr[1], NULL, 0); + if (lineno <= 0 || lineno > s->srclines) + return NULL; + rp = find_rule(src, lineno); + if (rp == NULL + || (b = set_breakpoint_at(rp, lineno, TRUE)) == NULL + ) + return NULL; + + b->flags = strtol(pstr[2], NULL, 0); + b->ignore_count = strtol(pstr[3], NULL, 0); + b->hit_count = strtol(pstr[4], NULL, 0); + b->number = strtol(pstr[5], NULL, 0); /* same number as previous run */ + + if (field_cnt > 6) /* unserialize breakpoint `commands' */ + unserialize_commands(pstr[6], pstr_len[6]); + + if (field_cnt > 7) { /* condition expression */ + char *expr; + expr = estrdup(pstr[7], pstr_len[7]); + if (parse_condition(D_break, b->number, expr) != 0) + efree(expr); + } + + if (b->number > watch_list.number) /* watch and break has same number counter */ + watch_list.number = b->number; /* update counter */ + return b; +} + +/* unserialize_option --- set a debugger option from unserialized data. */ + +static struct dbg_option * +unserialize_option(char **pstr, int *pstr_len, int field_cnt ATTRIBUTE_UNUSED) +{ + const struct dbg_option *opt; + + for (opt = option_list; opt->name; opt++) { + if (STREQN(pstr[0], opt->name, pstr_len[0])) { + char *value; + value = estrdup(pstr[1], pstr_len[1]); + (*(opt->assign))(value); + efree(value); + return ((struct dbg_option *) opt); + } + } + return NULL; +} + +/* unserialize -- reconstruct list from serialized data stored in + * environment variable. + */ + +static void +unserialize(int type) +{ + char *val; + char *p, *q, *r, *s; +#define MAX_FIELD 30 + static char *pstr[MAX_FIELD]; + static int pstr_len[MAX_FIELD]; + + val = getenv(env_variable[type]); + if (val == NULL) + return; + + for (p = val; (q = strchr(p, RSEP)) != NULL; p = q + 1) { + int field_cnt = 0; + if (type == HISTORY) { + *q = '\0'; + add_history(p); + *q = RSEP; + continue; + } + + r = p; + while ((s = strchr(r, FSEP)) != NULL && s < q) { + pstr[field_cnt] = r; + pstr_len[field_cnt] = (int) (s - r); + r = s + 1; + field_cnt++; + if (field_cnt == MAX_FIELD) +#ifdef GAWKDEBUG + fatal("Increase MAX_FIELD and recompile.\n"); +#else + return; +#endif + } + + switch (type) { + case BREAK: + (void) unserialize_breakpoint(pstr, pstr_len, field_cnt); + break; + case DISPLAY: + (void) unserialize_list_item(&display_list, pstr, pstr_len, field_cnt); + break; + case WATCH: + (void) unserialize_list_item(&watch_list, pstr, pstr_len, field_cnt); + break; + case OPTION: + (void) unserialize_option(pstr, pstr_len, field_cnt); + break; + case HISTORY: + /* processed at the beginning of for loop */ + break; + default: + break; + } + } + +#ifdef HAVE_LIBREADLINE + if (type == HISTORY) + sess_history_base = history_length; +#endif + + unsetenv(env_variable[type]); +#undef MAX_FIELD +} + +static int +prompt_yes_no(const char *mesg, char res_true, int res_default, FILE *fp) +{ + char *in_str; + int ret = res_default; /* default */ + + if (input_from_tty) { + fprintf(fp, "%s", _(mesg)); + in_str = read_a_line(NULL); + if (in_str == NULL) /* EOF */ + exit(EXIT_FAILURE); + ret = (*in_str == res_true); + efree(in_str); + } + return ret; +} + +/* has_break_or_watch_point --- check if given breakpoint or watchpoint + * number exists. When flag any is TRUE, + * check if any breakpoint/watchpoint + * has been set (ignores num). Returns + * type (breakpoint or watchpoint) or 0. + */ + +int +has_break_or_watch_point(int *pnum, int any) +{ + BREAKPOINT *b = NULL; + struct list_item *w = NULL; + + if (any) { + if (breakpoints.next != &breakpoints) + b = breakpoints.next; + if (watch_list.next != &watch_list) + w = watch_list.next; + + if (! b && ! w) + return 0; + if (b && ! w) { + *pnum = b->number; + return D_break; + } + if (w && ! b) { + *pnum = w->number; + return D_watch; + } + if (w->number > b->number) { + *pnum = w->number; + return D_watch; + } + *pnum = b->number; + return D_break; + } + + /* N.B: breakpoints and watchpoints get numbers from a single + * counter/sequencer watch_list.number. + */ + + for (b = breakpoints.next; b != &breakpoints; b = b->next) { + if (b->number == *pnum) + return D_break; + } + for (w = watch_list.next; w != &watch_list; w = w->next) { + if (w->number == *pnum) + return D_watch; + } + + return 0; +} + +/* delete_commands_item --- delete item(command) from `commands' list. */ + +static void +delete_commands_item(struct commands_item *c) +{ + efree(c->cmd_string); + free_cmdarg(c->arg); + c->next->prev = c->prev; + c->prev->next = c->next; + efree(c); +} + +/* do_commands --- commands command */ + +int +do_commands(CMDARG *arg, int cmd) +{ + static BREAKPOINT *b; + static struct list_item *w; + static struct commands_item *commands; + struct commands_item *c; + + if (cmd == D_commands) { + int num, type; + if (arg == NULL) + type = has_break_or_watch_point(&num, TRUE); + else { + num = arg->a_int; + type = has_break_or_watch_point(&num, FALSE); + } + b = NULL; + w = NULL; + if (type == D_break) + b = find_breakpoint(num); + else if (type == D_watch) + w = find_item(&watch_list, num); + assert((b != NULL) || (w != NULL)); + commands = (b != NULL) ? &b->commands : &w->commands; + + /* delete current commands */ + for (c = commands->next; c != commands; c = c->next) { + c = c->prev; + delete_commands_item(c->next); + } + return FALSE; + + } else if (cmd == D_end) { + commands = NULL; + if (read_a_line == read_commands_string) /* unserializig commands */ + return TRUE; /* done unserializing, terminate zzparse() */ + return FALSE; + + } else if (cmd == D_silent) { + if (b != NULL) + b->silent = TRUE; + else if (w != NULL) + w->silent = TRUE; + /* we also append silent command to the list for use + * in `info break(watch)', and to simplify + * serialization/unserialization of commands. + */ + } + + assert(commands != NULL); + + emalloc(c, struct commands_item *, sizeof(struct commands_item), "do_commands"); + c->next = NULL; + c->cmd = cmd; + + /* N.B.: first arg is the command string, see command.y */ + c->cmd_string = arg->a_string; + c->arg = arg->next; /* actual arguments to the command */ + efree(arg); + + /* append to the list */ + c->prev = commands->prev; + c->next = commands; + commands->prev = c; + c->prev->next = c; + return FALSE; +} + +/* execute_commands --- execute breakpoint/watchpoint commands, the first + * command that resumes execution terminates + * commands processing. + */ + +static int +execute_commands(struct commands_item *commands) +{ + struct commands_item *c; + Func_cmd cmd_ptr; + int ret = FALSE; + + for (c = commands->next; c != commands; c = c->next) { + if (c->cmd == D_silent) + continue; + cmd_ptr = get_command(c->cmd); /* command handler */ + ret = (*cmd_ptr)(c->arg, c->cmd); + if (ret) /* resume execution (continue, next etc.) */ + break; + } + return ret; +} + +/* do_print_f --- printf command */ + +int +do_print_f(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + int count = 0; + int i; + CMDARG *a; + NODE **tmp; + NODE *r; + char *name; + + /* count maximum required size for tmp */ + for (a = arg; a != NULL ; a = a->next) + count++; + emalloc(tmp, NODE **, count * sizeof(NODE *), "do_print_f"); + + for (i = 0, a = arg; a != NULL ; i++, a = a->next) { + switch (a->type) { + case D_variable: + name = a->a_string; + r = find_symbol(name, NULL); + if (r == NULL) + goto done; + if (r->type == Node_var_new) + tmp[i] = Nnull_string; + else if (r->type != Node_var) { + d_error(_("`%s' is not a scalar variable"), name); + goto done; + } else + tmp[i] = r->var_value; + break; + case D_field: + { + long field_num; + r = a->a_node; + field_num = (long) r->numbr; + tmp[i] = *get_field(field_num, NULL); + } + break; + case D_subscript: + { + int cnt = a->a_count; + name = a->a_string; + r = find_array(name); + if (r == NULL) + goto done; + + for (; cnt > 0; cnt--) { + NODE *value, *subs; + a = a->next; + subs = a->a_node; + value = in_array(r, subs); + if (cnt == 1) { + if (value == NULL) + tmp[i] = Nnull_string; /* FIXME: goto done ? */ + else if (value->type == Node_var_array) { + d_error(_("attempt to use array `%s[\"%s\"]' in a scalar context"), + name, subs->stptr); + goto done; + } else + tmp[i] = value; + } else { + if (value == NULL) { + d_error(_("[\"%s\"] not in array `%s'"), + subs->stptr, name); + goto done; + } else if (value->type != Node_var_array) { + d_error(_("attempt to use scalar `%s[\"%s\"]' as array"), + name, subs->stptr); + goto done; + } else { + r = value; + name = r->vname; + } + } + } + } + break; + case D_node: + tmp[i] = a->a_node; + break; + default: + break; + } + } + + force_string(tmp[0]); + r = format_arg(tmp[0]->stptr, tmp[0]->stlen, tmp, i); + if (r != NULL) { + (void) fwrite(r->stptr, sizeof(char), r->stlen, out_fp); + unref(r); + } +done: + efree(tmp); + return FALSE; +} + +/* do_source --- source command */ + +int +do_source(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + int fd; + char *file = arg->a_string; + + fd = open_readfd(file); + if (fd <= INVALID_HANDLE) { + d_error(_("can't open source file `%s' for reading (%s)"), + file, strerror(errno)); + return FALSE; + } + + push_cmd_src(fd, FALSE, g_readline, close, D_source, EXIT_SUCCESS); + cmd_src->str = estrdup(file, strlen(file)); + return FALSE; +} + +/* open_readfd --- open a file for reading */ + +static int +open_readfd(const char *file) +{ + int fd; + + fd = open(file, O_RDONLY); + if (fd <= INVALID_HANDLE) + return INVALID_HANDLE; + else if (os_isdir(fd)) { + (void) close(fd); + errno = EISDIR; + return INVALID_HANDLE; + } + return fd; +} + +/* find_option --- check if option name is valid */ + +int +find_option(char *name) +{ + const char *p; + int idx; + + for (idx = 0; (p = option_list[idx].name); idx++) { + if (STREQ(p, name)) + return idx; + } + return -1; +} + +/* option_help --- display help text for debugger options */ + +void +option_help() +{ + const struct dbg_option *opt; + + for (opt = option_list; opt->name; opt++) + fprintf(out_fp, "\t%-15.15s - %s\n", opt->name, _(opt->help_txt)); +} + +#ifdef HAVE_LIBREADLINE + +/* option_generator --- generator function for option name completion */ + +char * +option_generator(const char *text, int state) +{ + static size_t textlen; + static int idx; + const char *name; + + if (! state) { /* first time */ + textlen = strlen(text); + idx = 0; + } + + while ((name = option_list[idx++].name)) { + if (strncmp(name, text, textlen) == 0) + return estrdup(name, strlen(name)); + } + return NULL; +} + +#endif + +/* set_gawk_output --- redirect gawk (normal) output */ + +static void +set_gawk_output(const char *file) +{ + int fd = INVALID_HANDLE; + FILE *fp = NULL; + + if (output_fp != stdout) { + if (output_fp != stderr) { + fclose(output_fp); + efree(output_file); + } + output_fp = stdout; + output_is_tty = isatty(fileno(stdout)); + output_file = "/dev/stdout"; + } + + if (file == NULL || file[0] == '\0') + return; + + errno = 0; + if ((fd = os_devopen(file, O_WRONLY)) != INVALID_HANDLE) { + fp = fdopen(fd, "w"); + if (fp == NULL) + close(fd); + + } else if (STREQN(file, "/dev/", 5)) { + char *cp = (char *) file + 5; + + if (STREQ(cp, "stdout")) + return; + if (STREQ(cp, "stderr")) { + output_fp = stderr; + output_file = "/dev/stderr"; + output_is_tty = isatty(fileno(stderr)); + return; + } + + if (STREQN(cp, "fd/", 3)) { + cp += 3; + fd = (int) strtoul(cp, NULL, 10); + if (errno == 0 && fd > INVALID_HANDLE) { + fp = fdopen(fd, "w"); + if (fp == NULL) + fd = INVALID_HANDLE; + } else + fd = INVALID_HANDLE; + } else { + /* /dev/ttyN, /dev/pts/N, /dev/null etc. */ + fd = open(file, O_WRONLY); + } + + if (fd > INVALID_HANDLE && fp == NULL) { + fp = fdopen(fd, "w"); + if (fp == NULL) + close(fd); + } + + } else { + /* regular file */ + fp = fopen(file, "w"); + } + + if (fp != NULL) { + output_fp = fp; + output_file = estrdup(file, strlen(file)); + setbuf(fp, (char *) NULL); + output_is_tty = isatty(fileno(fp)); + } else { + d_error(_("could not open `%s' for writing (%s)"), + file, + errno != 0 ? strerror(errno) : _("reason unknown")); + fprintf(out_fp, _("sending output to stdout\n")); + } +} + +/* set_prompt --- set debugger prompt */ + +static void +set_prompt(const char *value) +{ + efree(dgawk_Prompt); + dgawk_Prompt = estrdup(value, strlen(value)); + dPrompt = dgawk_Prompt; +} + +/* set_option_flag --- convert option string to flag value */ + +static int +set_option_flag(const char *value) +{ + long n; + if (STREQ(value, "on")) + return TRUE; + if (STREQ(value, "off")) + return FALSE; + errno = 0; + n = strtol(value, NULL, 0); + return (errno == 0 && n != 0); +} + +/* set_option_num --- set integer option value from string */ + +static void +set_option_num(int *pnum, const char *value) +{ + long n; + errno = 0; + n = strtol(value, NULL, 0); + if (errno == 0 && n > 0) + *pnum = n; + else + d_error(_("invalid number")); +} + +/* set_listsize --- set list output window size */ + +static void +set_listsize(const char *value) +{ + set_option_num(&list_size, value); +} + +/* set_trace --- set instruction tracing on or off */ + +static void +set_trace(const char *value) +{ + do_trace = set_option_flag(value); +} + +/* set_save_history --- save history on exit */ + +static void +set_save_history(const char *value) +{ + do_save_history = set_option_flag(value); +} + +/* set_save_options --- save options on exit */ + +static void +set_save_options(const char *value) +{ + do_save_options = set_option_flag(value); +} + +/* set_history_size --- maximum entries in history file */ + +static void +set_history_size(const char *value) +{ + set_option_num(&history_size, value); +} + + +/* read_commands_string --- one of the many ways zzlex fetches a line to parse; + * this one is used to parse `commands' string during + * unserialization. + */ + +char * +read_commands_string(const char *prompt ATTRIBUTE_UNUSED) +{ + char *p, *end, *line; + + if (commands_string == NULL) + return NULL; + + p = (char *) commands_string; + end = (char *) commands_string + commands_string_len; + for (; p < end; p++) { + if (*p == line_sep) { + line = estrdup(commands_string, p - commands_string); + commands_string = p + 1; + commands_string_len = end - commands_string; + return line; + } + } + + line = estrdup(commands_string, commands_string_len); + commands_string = NULL; + commands_string_len = 0; + return line; +} + +/* save_options --- save current options to file */ + +static void +save_options(const char *file) +{ + FILE *fp; + const struct dbg_option *opt; + + fp = fopen(file, "w"); + if (fp == NULL) + return; + + for (opt = option_list; opt->name; opt++) { + if (opt->str_val != NULL) + fprintf(fp, "option %s = \"%s\"\n", opt->name, *(opt->str_val)); + else + fprintf(fp, "option %s = %d\n", opt->name, *(opt->num_val)); + } + fclose(fp); + chmod(file, 0600); +} + +/* close_all --- close all open files */ + +static void +close_all() +{ + int stdio_problem; + struct command_source *cs; + + (void) nextfile(&curfile, TRUE); /* close input data file */ + (void) close_io(&stdio_problem); + if (cur_srcfile->fd != INVALID_HANDLE) { + close(cur_srcfile->fd); + cur_srcfile->fd = INVALID_HANDLE; + } + for (cs = cmd_src; cs != NULL; cs = cs->next) { + if (cs->close_func && cs->fd != INVALID_HANDLE) { + cs->close_func(cs->fd); + cs->fd = INVALID_HANDLE; + } + } + + set_gawk_output(NULL); /* closes output_fp if not stdout */ +} + +/* install_params --- install function parameters into the symbol table */ + +static void +install_params(NODE *func) +{ + NODE *np; + + if (func == NULL) + return; + /* function parameters of type Node_param_list */ + np = func->lnode; + for (np = np->rnode; np != NULL; np = np->rnode) + install_symbol(np->param, np); +} + +/* remove_params --- remove function parameters out of the symbol table */ + +static void +remove_params(NODE *func) +{ + NODE *np; + + if (func == NULL) + return; + np = func->lnode; + for (np = np->rnode; np != NULL; np = np->rnode) + remove_symbol(np->param); +} + +/* pre_execute_code --- pre_hook for execute_code, called by pre_execute */ + +static int +pre_execute_code(INSTRUCTION **pi, int inloop) +{ + INSTRUCTION *ei = *pi; + + switch (ei->opcode) { + case Op_K_break: + case Op_K_continue: + if (inloop) + break; + /* else + fall through */ + case Op_K_exit: + case Op_K_next: + case Op_K_nextfile: + case Op_K_getline: /* getline without redirection */ + d_error(_("`%s' not allowed in current context;" + " statement ignored"), + op2str(ei->opcode)); + *pi = ei->nexti; + break; + case Op_K_return: + if (ei->nexti != NULL) { /* not an implicit return */ + NODE *r; + d_error(_("`return' not allowed in current context;" + " statement ignored")); + /* throw away return value already pushed onto stack */ + r = POP_SCALAR(); + DEREF(r); + *pi = ei->nexti; + } + break; + default: + break; + } + return (ei == *pi); +} + +extern void unwind_stack(STACK_ITEM *sp_bottom); + +static NODE * +execute_code(volatile INSTRUCTION *code) +{ + volatile NODE *r = NULL; + volatile jmp_buf fatal_tag_stack; + STACK_ITEM *ctxt_stack_bottom; + + /* We use one global stack for all contexts. + * Remember stack bottom for current context; in case of + * a fatal error, unwind stack until stack_ptr is below that 'bottom'. + */ + ctxt_stack_bottom = stack_ptr + 1; + + PUSH_BINDING(fatal_tag_stack); + if (setjmp(fatal_tag) == 0) { + (void) r_interpret((INSTRUCTION *) code); + assert(stack_ptr == ctxt_stack_bottom); + r = POP_SCALAR(); + } else /* fatal error */ + unwind_stack(ctxt_stack_bottom); + + POP_BINDING(fatal_tag_stack); + + if (exit_val != EXIT_SUCCESS) { /* must be EXIT_FATAL? */ + exit_val = EXIT_SUCCESS; + return NULL; + } + return (NODE *) r; +} + +/* do_eval --- eval command */ + +int +do_eval(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + NODE *r, *ret_val; + NODE *f = NULL; + NODE *this_frame = NULL, *this_func = NULL; + NODE **sp; + INSTRUCTION *eval, *code = NULL; + CONTEXT *ctxt; + char **save_parmlist = NULL; + int ecount = 0, pcount = 0; + int ret; + + if (prog_running) { + this_frame = find_frame(0); + this_func = this_frame->func_node; + } + + install_params(this_func); /* expose current function parameters to eval */ + ctxt = new_context(); + ctxt->install_func = append_symbol; /* keep track of newly installed globals */ + set_context(ctxt); + (void) add_srcfile(SRC_CMDLINE, arg->a_string, srcfiles, NULL, NULL); + ret = parse_program(&code); + remove_params(this_func); + if (ret != 0) { + set_context(NULL); /* switch to prev context (main) */ + free_context(ctxt, FALSE /* keep_globals */); + return FALSE; + } + + f = lookup("@eval"); + assert(f != NULL); + if (this_func == NULL) { /* in main */ + /* do a function call */ + eval = bcalloc(Op_func_call, 2, 0); + eval->source_file = cur_srcfile->src; + eval->func_body = f; + eval->func_name = NULL; /* not needed, func_body already assigned */ + (eval + 1)->expr_count = 0; + eval->nexti = bcalloc(Op_stop, 1, 0); + + } else { + /* execute as a part of the current function */ + int i; + char **varnames; + INSTRUCTION *t; + NODE *np; + + eval = f->code_ptr; /* Op_func */ + eval->source_file = cur_srcfile->src; + /* turn implicit Op_K_return into Op_stop */ + t = (eval + 1)->lasti; /* Op_K_return */ + t->opcode = Op_stop; + + /* add or append eval locals to the current frame stack */ + ecount = f->lnode->param_cnt; /* eval local count */ + pcount = this_func->lnode->param_cnt; + save_parmlist = this_func->parmlist; + + if (ecount > 0) { + if (pcount == 0) + emalloc(this_frame->stack, NODE **, ecount * sizeof(NODE *), "do_eval"); + else + erealloc(this_frame->stack, NODE **, (pcount + ecount) * sizeof(NODE *), "do_eval"); + + emalloc(varnames, char **, (pcount + ecount + 1) * sizeof(char *), "do_eval"); + if (pcount > 0) + memcpy(varnames, save_parmlist, pcount * sizeof(char *)); + for (np = f->lnode->rnode, i = 0; np != NULL; np = np->rnode, i++) { + varnames[pcount + i] = np->param; + np->param_cnt += pcount; /* appending eval locals: fixup param_cnt */ + } + varnames[pcount + ecount] = NULL; + sp = this_frame->stack + pcount; + for (i = 0; i < ecount; i++) { + getnode(r); + memset(r, 0, sizeof(NODE)); + *sp++ = r; + /* local variable */ + r->type = Node_var_new; + r->vname = varnames[pcount + i]; + } + + this_func->parmlist = varnames; + this_func->lnode->param_cnt += ecount; + } + } + +#if 0 + pf_data.print_func = fprintf; + pf_data.fp = out_fp; + pf_data.defn = FALSE; /* in_dump = FALSE */ + (void) print_code(f->code_ptr, &pf_data); +#endif + + ret_val = execute_code((volatile INSTRUCTION *) eval); + + if (ret_val != NULL) + DEREF(ret_val); /* throw away return value */ + /* else + fatal error */ + + if (this_func != NULL && ecount > 0) { + int i; + + /* undo frame manipulation from above */ + + /* free eval locals */ + sp = this_frame->stack + pcount; + for (i = ecount; i > 0; i--) { + r = *sp; + if (r->type == Node_var) /* eval local variable */ + DEREF(r->var_value); + else if (r->type == Node_var_array) /* eval local array */ + assoc_clear(r); + freenode(r); + *sp++ = (NODE *) 0; + } + if (pcount == 0) { + efree(this_frame->stack); + this_frame->stack = NULL; + } /* else + restore_frame() will free it */ + + efree(this_func->parmlist); + this_func->parmlist = save_parmlist; + this_func->lnode->param_cnt -= ecount; + } + + /* always destroy symbol "@eval", however destroy all newly installed + * globals only if fatal error in r_interpret (r == NULL). + */ + + set_context(NULL); /* switch to prev context (main) */ + free_context(ctxt, (ret_val != NULL)); /* free all instructions and optionally symbols */ + if (ret_val != NULL) + destroy_symbol("@eval"); /* destroy "@eval" */ + return FALSE; +} + +/* +GDB Documentation: + ... When you use condition, GDB checks expression +immediately for syntactic correctness, and to determine whether symbols +in it have referents in the context of your breakpoint. If expression +uses symbols not referenced in the context of the breakpoint, GDB prints +an error message: + + No symbol "foo" in current context. +*/ + +static int invalid_symbol = 0; + +void +check_symbol(char *name) +{ + invalid_symbol++; + d_error(_("No symbol `%s' in current context"), name); + /* install anyway, but keep track of it */ + append_symbol(name); +} + +/* parse_condition --- compile a condition expression */ + +static int +parse_condition(int type, int num, char *expr) +{ + INSTRUCTION *code = NULL; + CONTEXT *ctxt = NULL; + int ret; + BREAKPOINT *b; + struct list_item *w; + NODE *this_func = NULL; + INSTRUCTION *it, *stop, *rule; + struct condition *cndn = NULL; + + if (type == D_break && (b = find_breakpoint(num)) != NULL) { + INSTRUCTION *rp; + cndn = &b->cndn; + rp = find_rule(b->src, b->bpi->source_line); + if (rp != NULL && rp->opcode == Op_func) + this_func = rp->func_body; + } else if (type == D_watch && (w = find_item(&watch_list, num)) != NULL) { + cndn = &w->cndn; + this_func = find_frame(cur_frame)->func_node; + } + + if (cndn == NULL) + return -1; + if (expr == NULL) + goto out; /* delete condition */ + + install_params(this_func); + ctxt = new_context(); + invalid_symbol = 0; + ctxt->install_func = check_symbol; + set_context(ctxt); + (void) add_srcfile(SRC_CMDLINE, expr, srcfiles, NULL, NULL); + ret = parse_program(&code); + remove_params(this_func); + set_context(NULL); + + if (ret != 0 || invalid_symbol) { + free_context(ctxt, FALSE /* keep_globals */); + return -1; + } + + /* condition expression is parsed as awk pattern without + * any action. The code is then modified to end up with + * a `1.0' on stack when the expression is true, `0.0' otherwise. + */ + + assert(code != NULL); + rule = ctxt->rule_list.nexti; + stop = bcalloc(Op_stop, 1, 0); + + it = rule->firsti; /* Op_K_print_rec */ + assert(it->opcode == Op_K_print_rec); + it->opcode = Op_push_i; + it->memory = mk_number((AWKNUM) 1.0, PERM|NUMBER|NUMCUR); + it->nexti = bcalloc(Op_jmp, 1, 0); + it->nexti->target_jmp = stop; + it->nexti->nexti = rule->lasti; + + it = rule->lasti; /* Op_no_op, target for Op_jmp_false */ + assert(it->opcode == Op_no_op); + it->opcode = Op_push_i; + it->memory = mk_number((AWKNUM) 0.0, PERM|NUMBER|NUMCUR); + it->nexti = stop; + +out: + if (cndn->expr != NULL) + efree(cndn->expr); + free_context(cndn->ctxt, FALSE); + cndn->code = code; + cndn->expr = expr; + cndn->ctxt = ctxt; + + return 0; +} + +/* do_condition --- condition command */ + +int +do_condition(CMDARG *arg, int cmd ATTRIBUTE_UNUSED) +{ + int type, num; + char *expr = NULL; + + num = arg->a_int; + type = has_break_or_watch_point(&num, FALSE); + if (! type) + return FALSE; + arg = arg->next; /* condition expression */ + if (arg != NULL) + expr = arg->a_string; + if (parse_condition(type, num, expr) == 0 && arg != NULL) + arg->a_string = NULL; /* don't let free_cmdarg free it */ + return FALSE; +} + +/* in_cmd_src --- check if filename already in cmd_src */ + +int +in_cmd_src(const char *filename) +{ + struct command_source *cs; + for (cs = cmd_src; cs != NULL; cs = cs->next) { + if (cs->str != NULL && STREQ(cs->str, filename)) + return TRUE; + } + return FALSE; +} + +int +get_eof_status() +{ + if (cmd_src == NULL) + return EXIT_FATAL; + return cmd_src->eof_status; +} + +void +push_cmd_src( + int fd, + int istty, + char * (*readfunc)(const char *), + int (*closefunc)(int), + int ctype, + int eofstatus) +{ + struct command_source *cs; + emalloc(cs, struct command_source *, sizeof(struct command_source), "push_cmd_src"); + cs->fd = fd; + cs->is_tty = istty; + cs->read_func = readfunc; + cs->close_func = closefunc; + cs->cmd = ctype; + + /* eof_status = EXIT_FATAL - exit with status EXIT_FATAL on EOF or error. + * = EXIT_FAILURE - exit status EXIT_FAILURE on error. + * = EXIT_SUCCESS - don't exit on EOF or error. + */ + cs->eof_status = eofstatus; + cs->str = NULL; + cs->next = cmd_src; + cmd_src = cs; + + input_fd = fd; + input_from_tty = istty; + read_a_line = readfunc; +} + +int +pop_cmd_src() +{ + struct command_source *cs; + + if (cmd_src->next == NULL) + return -1; + + cs = cmd_src; + cmd_src = cs->next; + if (cs->close_func && cs->fd != INVALID_HANDLE) + cs->close_func(cs->fd); + if (cs->str != NULL) + efree(cs->str); + efree(cs); + + input_fd = cmd_src->fd; + input_from_tty = cmd_src->is_tty; + read_a_line = cmd_src->read_func; + return 0; +} |