Blob Blame History Raw
/* gdb_interface.c - core analysis suite
 *
 * Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc.
 * Copyright (C) 2002-2015,2018-2019 David Anderson
 * Copyright (C) 2002-2015,2018-2019 Red Hat, Inc. All rights reserved.
 *
 * This program 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.
 *
 * This program 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.
 */

#include "defs.h"

static void exit_after_gdb_info(void);
static int is_restricted_command(char *, ulong);
static void strip_redirection(char *);
int get_frame_offset(ulong);

int *gdb_output_format;
unsigned int *gdb_print_max;
int *gdb_prettyprint_structs;
int *gdb_prettyprint_arrays;
int *gdb_repeat_count_threshold;
int *gdb_stop_print_at_null;
unsigned int *gdb_output_radix;

static ulong gdb_user_print_option_address(char *);

/*
 *  Called from main() this routine sets up the call-back hook such that
 *  gdb's main() routine -- renamed gdb_main() -- will call back to
 *  our main_loop() after gdb initializes.
 */
void
gdb_main_loop(int argc, char **argv)
{
	argc = 1;

	if (pc->flags & SILENT) {
		if (pc->flags & READNOW)
			argv[argc++] = "--readnow";
		argv[argc++] = "--quiet";
		argv[argc++] = pc->namelist_debug ? 
			pc->namelist_debug : 
			(pc->debuginfo_file && (st->flags & CRC_MATCHES) ?
			pc->debuginfo_file : pc->namelist);
	} else {
		if (pc->flags & READNOW)
			argv[argc++] = "--readnow";
		argv[argc++] = pc->namelist_debug ? 
			pc->namelist_debug : 
			(pc->debuginfo_file && (st->flags & CRC_MATCHES) ?
			pc->debuginfo_file : pc->namelist);
	}

	if (CRASHDEBUG(1)) {
		int i;
		fprintf(fp, "gdb ");
		for (i = 1; i < argc; i++)
			fprintf(fp, "%s ", argv[i]);
		fprintf(fp, "\n");
	}

        optind = 0;
#if defined(GDB_5_3) || defined(GDB_6_0) || defined(GDB_6_1)
        command_loop_hook = main_loop;
#else
	deprecated_command_loop_hook = main_loop;
#endif
        gdb_main_entry(argc, argv);
}

/*
 *  Update any hooks that gdb has set.
 */
void
update_gdb_hooks(void)
{
#if defined(GDB_6_0) || defined(GDB_6_1)
	command_loop_hook = pc->flags & VERSION_QUERY ?
        	exit_after_gdb_info : main_loop;
	target_new_objfile_hook = NULL;
#endif
#if defined(GDB_7_0) || defined(GDB_7_3_1) || defined(GDB_7_6)
	deprecated_command_loop_hook = pc->flags & VERSION_QUERY ?
		exit_after_gdb_info : main_loop;
#endif
}

void
gdb_readnow_warning(void)
{
	if ((THIS_GCC_VERSION >= GCC(3,4,0)) && (THIS_GCC_VERSION < GCC(4,0,0))
	    && !(pc->flags & READNOW)) {
		fprintf(stderr, 
 "WARNING: Because this kernel was compiled with gcc version %d.%d.%d, certain\n" 
 "         commands or command options may fail unless crash is invoked with\n"
 "         the  \"--readnow\" command line option.\n\n",
			kt->gcc_version[0],
			kt->gcc_version[1],
			kt->gcc_version[2]);
	}
}

/*
 *  Used only by the -v command line option, get gdb to initialize itself
 *  with no arguments, print its version and GPL paragraph, and then call
 *  back to exit_after_gdb_info().
 */
void
display_gdb_banner(void)
{
	optind = 0;
#if defined(GDB_5_3) || defined(GDB_6_0) || defined(GDB_6_1)
        command_loop_hook = exit_after_gdb_info;
#else
        deprecated_command_loop_hook = exit_after_gdb_info;
#endif
	args[0] = "gdb";
	args[1] = "-version";
	gdb_main_entry(2, args);
}

static void
exit_after_gdb_info(void)
{
        fprintf(fp, "\n");
        clean_exit(0);
}

/* 
 *  Stash a copy of the gdb version locally.  This can be called before
 *  gdb gets initialized, so bypass gdb_interface().
 */
void
get_gdb_version(void)
{
        struct gnu_request request;

	if (!pc->gdb_version) {
        	request.command = GNU_VERSION;
		gdb_command_funnel(&request);    /* bypass gdb_interface() */
		pc->gdb_version = request.buf;
	}
}

void
gdb_session_init(void)
{
	struct gnu_request *req;
	int debug_data_pulled_in;

        if (!have_partial_symbols() && !have_full_symbols())
		no_debugging_data(FATAL);

	/*
	 *  Restore the SIGINT and SIGPIPE handlers, which got temporarily
	 *  re-assigned by gdb.  The SIGINT call also initializes GDB's
         *  SIGINT sigaction.
	 */
	SIGACTION(SIGINT, restart, &pc->sigaction, &pc->gdb_sigaction);
	SIGACTION(SIGPIPE, SIG_IGN, &pc->sigaction, NULL);

	if (!(pc->flags & DROP_CORE)) 
		SIGACTION(SIGSEGV, restart, &pc->sigaction, NULL);

	/*
	 *  Set up pointers to gdb variables.
	 */
#if defined(GDB_5_3) || defined(GDB_6_0) || defined(GDB_6_1)
	gdb_output_format = &output_format;
	gdb_print_max = &print_max;
	gdb_prettyprint_structs = &prettyprint_structs;
	gdb_prettyprint_arrays = &prettyprint_arrays;
	gdb_repeat_count_threshold = &repeat_count_threshold;
	gdb_stop_print_at_null = &stop_print_at_null;
	gdb_output_radix = &output_radix;
#else
	gdb_output_format = (int *) 
		gdb_user_print_option_address("output_format");
	gdb_print_max = (unsigned int *)
		gdb_user_print_option_address("print_max");
	gdb_prettyprint_structs = (int *)
		gdb_user_print_option_address("prettyprint_structs");
	gdb_prettyprint_arrays = (int *)
		gdb_user_print_option_address("prettyprint_arrays");
	gdb_repeat_count_threshold = (int *)
		gdb_user_print_option_address("repeat_count_threshold");
	gdb_stop_print_at_null = (int *)
		gdb_user_print_option_address("stop_print_at_null");
	gdb_output_radix = (unsigned int *)
		gdb_user_print_option_address("output_radix");
#endif
	/*
         *  If the output radix is set via the --hex or --dec command line
	 *  option, then pc->output_radix will be non-zero; otherwise use 
	 *  the gdb default.  
	 */
	if (pc->output_radix) {  
		*gdb_output_radix = pc->output_radix;
		*gdb_output_format = (*gdb_output_radix == 10) ? 0 : 'x';
	}

	switch (*gdb_output_radix)
	{
	case 10:
	case 16:
		pc->output_radix = *gdb_output_radix;
		break;
	default:
		pc->output_radix = *gdb_output_radix = 10;
		*gdb_output_format = 0;
	}
		
	*gdb_prettyprint_structs = 1;
	*gdb_repeat_count_threshold = 0x7fffffff;
	*gdb_print_max = 256;
#ifdef GDB_5_3
	gdb_disassemble_from_exec = 0;
#endif

	pc->flags |= GDB_INIT;   /* set here so gdb_interface will work */

        req = (struct gnu_request *)GETBUF(sizeof(struct gnu_request));
        req->buf = GETBUF(BUFSIZE);

	/*
	 *  Make sure the namelist has symbolic data.  Later versions of
	 *  gcc may require that debug data be pulled in by printing a 
	 *  static kernel data structure.
  	 */
	debug_data_pulled_in = FALSE;
retry:
	BZERO(req->buf, BUFSIZE);
        req->command = GNU_GET_DATATYPE;
	req->name = XEN_HYPER_MODE() ? "page_info" : "task_struct";
        req->flags = GNU_RETURN_ON_ERROR;
        gdb_interface(req);

        if (req->flags & GNU_COMMAND_FAILED) {
		if (XEN_HYPER_MODE())
			no_debugging_data(WARNING);  /* just bail out */

		if (!debug_data_pulled_in) {
			if (CRASHDEBUG(1))
				error(INFO, 
           "gdb_session_init: pulling in debug data by accessing init_mm.mmap %s\n",
					symbol_exists("sysfs_mount") ?
					"and syfs_mount" : "");
			debug_data_pulled_in = TRUE;
			req->command = GNU_PASS_THROUGH;
			req->flags = GNU_RETURN_ON_ERROR|GNU_NO_READMEM;
			req->name = NULL;
			if (symbol_exists("sysfs_mount"))
				sprintf(req->buf, "print sysfs_mount, init_mm.mmap");
			else
				sprintf(req->buf, "print init_mm.mmap");
			gdb_interface(req);
        		if (!(req->flags & GNU_COMMAND_FAILED)) 
				goto retry;
		}
		no_debugging_data(WARNING);
	}

	if (pc->flags & KERNEL_DEBUG_QUERY) {
		fprintf(fp, "\n%s: %s: contains debugging data\n\n",
			pc->program_name, pc->namelist);
		if (REMOTE())
			remote_exit();
		clean_exit(0);
	}

	/*
	 *  Set up any pre-ordained gdb settings here that can't be
	 *  accessed directly.
	 */

	req->command = GNU_PASS_THROUGH;
	req->name = NULL, req->flags = 0;
	sprintf(req->buf, "set height 0");
	gdb_interface(req);

	req->command = GNU_PASS_THROUGH;
	req->name = NULL, req->flags = 0;
	sprintf(req->buf, "set width 0");
	gdb_interface(req);

       /*
        *  Patch gdb's symbol values with the correct values from either
        *  the System.map or non-debug vmlinux, whichever is in effect.
        */
	if ((pc->flags & SYSMAP) || (kt->flags & (RELOC_SET|RELOC_FORCE)) || 
	    (pc->namelist_debug && !pc->debuginfo_file)) {
		req->command = GNU_PATCH_SYMBOL_VALUES;
        	req->flags = GNU_RETURN_ON_ERROR;
		gdb_interface(req);
        	if (req->flags & GNU_COMMAND_FAILED)
			error(FATAL, "patching of gdb symbol values failed\n");
	} else if (!(pc->flags & SILENT))
		fprintf(fp, "\n");


	FREEBUF(req->buf);
	FREEBUF(req);
}

/*
 *  Quickest way to gdb -- just pass a command string to pass through.
 */
int
gdb_pass_through(char *cmd, FILE *fptr, ulong flags)
{
        struct gnu_request *req;
	int retval;

	if (CRASHDEBUG(1))
  		console("gdb_pass_through: [%s]\n", cmd); 

        req = (struct gnu_request *)GETBUF(sizeof(struct gnu_request));
        req->buf = cmd;
	if (fptr)
		req->fp = fptr;
        req->command = GNU_PASS_THROUGH;
	req->flags = flags;

        gdb_interface(req);

	if ((req->flags & (GNU_RETURN_ON_ERROR|GNU_COMMAND_FAILED)) ==
	    (GNU_RETURN_ON_ERROR|GNU_COMMAND_FAILED))
		retval = FALSE;
	else
		retval = TRUE;

        FREEBUF(req);

	return retval;
}


/*
 *  General purpose routine for passing commands to gdb.  All gdb commands
 *  come through here, where they are passed to gdb_command_funnel().
 */
void 
gdb_interface(struct gnu_request *req)
{
	if (!(pc->flags & GDB_INIT)) 
		error(FATAL, "gdb_interface: gdb not initialized?\n"); 

	if (output_closed()) 
		restart(0);

	if (!req->fp) {
		req->fp = ((pc->flags & RUNTIME) || (pc->flags2 & ALLOW_FP)) ? 
			fp : CRASHDEBUG(1) ? fp : pc->nullfp;
	}

	pc->cur_req = req;
	pc->cur_gdb_cmd = req->command;

	if (req->flags & GNU_RETURN_ON_ERROR) {
		error_hook = gdb_error_hook;
        	if (setjmp(pc->gdb_interface_env)) {
			pc->last_gdb_cmd = pc->cur_gdb_cmd;
			pc->cur_gdb_cmd = 0;
			pc->cur_req = NULL;
			req->flags |= GNU_COMMAND_FAILED;
			pc->flags &= ~IN_GDB;
			return;
		}
	} else
		error_hook = NULL;

	if (CRASHDEBUG(2))
		dump_gnu_request(req, IN_GDB);

        if (!(pc->flags & DROP_CORE)) 
		SIGACTION(SIGSEGV, restart, &pc->sigaction, NULL);
        else 
		SIGACTION(SIGSEGV, SIG_DFL, &pc->sigaction, NULL);

	if (interruptible()) { 
		SIGACTION(SIGINT, pc->gdb_sigaction.sa_handler, 
			&pc->gdb_sigaction, NULL);
	} else {
		SIGACTION(SIGINT, SIG_IGN, &pc->sigaction, NULL);
		SIGACTION(SIGPIPE, SIG_IGN, &pc->sigaction, NULL);
	} 

	pc->flags |= IN_GDB;
	gdb_command_funnel(req);
	pc->flags &= ~IN_GDB;

	SIGACTION(SIGINT, restart, &pc->sigaction, NULL);
	SIGACTION(SIGSEGV, SIG_DFL, &pc->sigaction, NULL);

	if (CRASHDEBUG(2))
		dump_gnu_request(req, !IN_GDB);

	error_hook = NULL;
        pc->last_gdb_cmd = pc->cur_gdb_cmd;
        pc->cur_gdb_cmd = 0;
	pc->cur_req = NULL;
}

/*
 *  help -g output
 */
void
dump_gdb_data(void)
{
        fprintf(fp, "    prettyprint_arrays: %d\n", *gdb_prettyprint_arrays);
        fprintf(fp, "   prettyprint_structs: %d\n", *gdb_prettyprint_structs);
        fprintf(fp, "repeat_count_threshold: %x\n", *gdb_repeat_count_threshold);
	fprintf(fp, "    stop_print_at_null: %d\n", *gdb_stop_print_at_null);
	fprintf(fp, "             print_max: %d\n", *gdb_print_max);
        fprintf(fp, "          output_radix: %d\n", *gdb_output_radix);
        fprintf(fp, "         output_format: ");
        switch (*gdb_output_format)
        {
        case 'x':
                fprintf(fp, "hex\n"); break;
        case 'o':
                fprintf(fp, "octal\n"); break;
        case 0:
                fprintf(fp, "decimal\n"); break;
        }
}

void
dump_gnu_request(struct gnu_request *req, int in_gdb)
{
	int others;
	char buf[BUFSIZE];

	if (pc->flags & KERNEL_DEBUG_QUERY)
		return;

	console("%scommand: %d (%s)\n", in_gdb ? "GDB IN: " : "GDB OUT: ", 
		req->command, gdb_command_string(req->command, buf, TRUE));
        console("buf: %lx ", req->buf);
        if (req->buf && ascii_string(req->buf))
                console(" \"%s\"", req->buf);
        console("\n");
        console("fp: %lx ", req->fp);

	if (req->fp == pc->nullfp)
		console("(pc->nullfp) ");
	if (req->fp == pc->stdpipe)
		console("(pc->stdpipe) ");
	if (req->fp == pc->pipe)
		console("(pc->pipe) ");
	if (req->fp == pc->ofile)
		console("(pc->ofile) ");
	if (req->fp == pc->ifile)
		console("(pc->ifile) ");
	if (req->fp == pc->ifile_pipe)
		console("(pc->ifile_pipe) ");
	if (req->fp == pc->ifile_ofile)
		console("(pc->ifile_ofile) ");
	if (req->fp == pc->tmpfile)
		console("(pc->tmpfile) ");
	if (req->fp == pc->saved_fp)
		console("(pc->saved_fp) ");
	if (req->fp == pc->tmp_fp)
		console("(pc->tmp_fp) ");

	console("flags: %lx  (", req->flags);
	others = 0;
	if (req->flags & GNU_PRINT_LINE_NUMBERS)
		console("%sGNU_PRINT_LINE_NUMBERS", others++ ? "|" : "");
	if (req->flags & GNU_FUNCTION_ONLY)
                console("%sGNU_FUNCTION_ONLY", others++ ? "|" : "");
        if (req->flags & GNU_PRINT_ENUMERATORS)
                console("%sGNU_PRINT_ENUMERATORS", others++ ? "|" : "");
        if (req->flags & GNU_RETURN_ON_ERROR)
                console("%sGNU_RETURN_ON_ERROR", others++ ? "|" : "");
        if (req->flags & GNU_FROM_TTY_OFF)
                console("%sGNU_FROM_TTY_OFF", others++ ? "|" : "");
        if (req->flags & GNU_NO_READMEM)
                console("%sGNU_NO_READMEM", others++ ? "|" : "");
        if (req->flags & GNU_VAR_LENGTH_TYPECODE)
                console("%sGNU_VAR_LENGTH_TYPECODE", others++ ? "|" : "");
	console(")\n");

        console("addr: %lx ", req->addr);
        console("addr2: %lx ", req->addr2);
        console("count: %ld\n", req->count);

	if ((ulong)req->name > (ulong)PATCH_KERNEL_SYMBOLS_STOP) 
		console("name: \"%s\" ", req->name);
	else
		console("name: %lx ", (ulong)req->name);
	console("length: %ld ", req->length);
        console("typecode: %d\n", req->typecode);
#if defined(GDB_5_3) || defined(GDB_6_0) || defined(GDB_6_1) || defined(GDB_7_0)
	console("typename: %s\n", req->typename);
#else
	console("type_name: %s\n", req->type_name);
#endif
	console("target_typename: %s\n", req->target_typename);
	console("target_length: %ld ", req->target_length);
	console("target_typecode: %d ", req->target_typecode);
	console("is_typedef: %d ", req->is_typedef);
	console("member: \"%s\" ", req->member);
	console("member_offset: %ld\n", req->member_offset);
	console("member_length: %ld\n", req->member_length);
        console("member_typecode: %d\n", req->member_typecode);
	console("member_main_type_name: %s\n", req->member_main_type_name);
	console("member_main_type_tag_name: %s\n", req->member_main_type_tag_name);
	console("member_target_type_name: %s\n", req->member_target_type_name);
	console("member_target_type_tag_name: %s\n", req->member_target_type_tag_name);
	console("value: %lx ", req->value);
	console("tagname: \"%s\" ", req->tagname);
	console("pc: %lx  ", req->pc);
	if (is_kernel_text(req->pc))
		console("(%s)", value_to_symstr(req->pc, buf, 0));
	console("\n");
	console("sp: %lx ", req->sp);
	console("ra: %lx ", req->ra);
        console("frame: %ld ", req->frame);
	console("prevsp: %lx\n", req->prevsp);
	console("prevpc: %lx ", req->prevpc);
	console("lastsp: %lx ", req->lastsp);
        console("task: %lx ", req->task);
	console("debug: %lx\n", req->debug);
	console("\n");
}

char *
gdb_command_string(int cmd, char *buf, int live)
{
        switch (cmd)
        {
        case GNU_PASS_THROUGH:
                sprintf(buf, "GNU_PASS_THROUGH");
                break;
        case GNU_DATATYPE_INIT:
                sprintf(buf, "GNU_DATATYPE_INIT");
                break;
        case GNU_DISASSEMBLE:
                sprintf(buf, "GNU_DISASSEMBLE");
                break;
        case GNU_GET_LINE_NUMBER:
                sprintf(buf, "GNU_GET_LINE_NUMBER");
                break;
        case GNU_GET_DATATYPE:
		if (live)
                	sprintf(buf, "GNU_GET_DATATYPE[%s]", 
				pc->cur_req->name ? pc->cur_req->name : "?");
		else
			sprintf(buf, "GNU_GET_DATATYPE");
                break;
        case GNU_STACK_TRACE:
                sprintf(buf, "GNU_STACK_TRACE");
                break;
	case GNU_ALPHA_FRAME_OFFSET:
		sprintf(buf, "GNU_ALPHA_FRAME_OFFSET");
		break;
	case GNU_COMMAND_EXISTS:
                sprintf(buf, "GNU_COMMAND_EXISTS");
                break;
	case GNU_FUNCTION_NUMARGS:
                sprintf(buf, "GNU_FUNCTION_NUMARGS");
                break;
	case GNU_RESOLVE_TEXT_ADDR:
                sprintf(buf, "GNU_RESOLVE_TEXT_ADDR");
                break;
	case GNU_DEBUG_COMMAND:
                sprintf(buf, "GNU_DEBUG_COMMAND");
                break;
	case GNU_ADD_SYMBOL_FILE:
                sprintf(buf, "GNU_ADD_SYMBOL_FILE");
                break;
	case GNU_DELETE_SYMBOL_FILE:
                sprintf(buf, "GNU_DELETE_SYMBOL_FILE");
                break;
	case GNU_VERSION:
                sprintf(buf, "GNU_VERSION");
                break;
       case GNU_GET_SYMBOL_TYPE:
                sprintf(buf, "GNU_GET_SYMBOL_TYPE");
                break;
        case GNU_PATCH_SYMBOL_VALUES:
                sprintf(buf, "GNU_PATCH_SYMBOL_VALUES");
                break;
        case GNU_USER_PRINT_OPTION:
                sprintf(buf, "GNU_USER_PRINT_OPTION");
		break;
        case GNU_SET_CRASH_BLOCK:
                sprintf(buf, "GNU_SET_CRASH_BLOCK");
		break;
	case GNU_GET_FUNCTION_RANGE:
                sprintf(buf, "GNU_GET_FUNCTION_RANGE");
                break;
	case 0:
		buf[0] = NULLCHAR;
		break;
        default:
                sprintf(buf, "(?)\n");
                break;
        }

	return buf;
}

/*
 *  Restore known gdb state.
 */
void
restore_gdb_sanity(void)
{
        if (!(pc->flags & GDB_INIT))
                return;

        if (pc->output_radix) {
                *gdb_output_radix = pc->output_radix;   
                *gdb_output_format = (*gdb_output_radix == 10) ? 0 : 'x';
        }

        *gdb_prettyprint_structs = 1;   /* these may piss somebody off... */
	*gdb_repeat_count_threshold = 0x7fffffff;

	error_hook = NULL;

	if (st->flags & ADD_SYMBOL_FILE) {
		error(INFO, 
		    "%s\n     gdb add-symbol-file command failed\n", 
			st->current->mod_namelist);
		delete_load_module(st->current->mod_base);
                st->flags &= ~ADD_SYMBOL_FILE;
	}

	if (pc->cur_gdb_cmd) {
		pc->last_gdb_cmd = pc->cur_gdb_cmd;
		pc->cur_gdb_cmd = 0;
	}
}

/*
 *  Check whether string in args[0] is a valid gdb command.
 */
int
is_gdb_command(int merge_orig_args, ulong flags)
{
        int retval;
        struct gnu_request *req;

        if (!args[0])
                return FALSE;

	if (STREQ(args[0], "Q")) {
		args[0] = "q";
		return TRUE;
	}

	if (is_restricted_command(args[0], flags))
		return FALSE;

        req = (struct gnu_request *)GETBUF(sizeof(struct gnu_request));
	req->buf = GETBUF(strlen(args[0])+1);
        req->command = GNU_COMMAND_EXISTS;
        req->name = args[0];
        req->flags = GNU_RETURN_ON_ERROR; 
	req->fp = pc->nullfp;

        gdb_interface(req);

	if (req->flags & GNU_COMMAND_FAILED) 
		retval = FALSE;
	else
        	retval = req->value;

	FREEBUF(req->buf);
        FREEBUF(req);

	if (retval && merge_orig_args) {
		int i;
		for (i = argcnt; i; i--)
			args[i] = args[i-1];
		args[0] = "gdb";
		argcnt++;
	}

        return retval;
}

/*
 *  Check whether a command is on the gdb-prohibited list.
 */
static char *prohibited_list[] = {
	"run", "r", "break", "b", "tbreak", "hbreak", "thbreak", "rbreak",
	"watch", "rwatch", "awatch", "attach", "continue", "c", "fg", "detach", 
	"finish", "handle", "interrupt", "jump", "kill", "next", "nexti", 
	"signal", "step", "s", "stepi", "target", "thread", "until", "delete", 
	"clear", "disable", "enable", "condition", "ignore", "frame", 
	"select-frame", "f", "up", "down", "catch", "tcatch", "return",
	"file", "exec-file", "core-file", "symbol-file", "load", "si", "ni", 
	"shell", 
	NULL  /* must be last */
};

static char *restricted_list[] = {
	"define", "document", "while", "if",
	NULL  /* must be last */
};

#define RESTRICTED_GDB_COMMAND \
        "restricted gdb command: %s\n%s\"%s\" may only be used in a .gdbinit file or in a command file.\n%sThe .gdbinit file is read automatically during %s initialization.\n%sOther user-defined command files may be read interactively during\n%s%s runtime by using the gdb \"source\" command.\n"

static int
is_restricted_command(char *cmd, ulong flags)
{
	int i;
	char *newline;

	for (i = 0; prohibited_list[i]; i++) {
		if (STREQ(prohibited_list[i], cmd)) {
			if (flags == RETURN_ON_ERROR)
				return TRUE;
			pc->curcmd = pc->program_name;
                	error(FATAL, "prohibited gdb command: %s\n", cmd);
		}
	}

	for (i = 0; restricted_list[i]; i++) {
		if (STREQ(restricted_list[i], cmd)) {
			if (flags == RETURN_ON_ERROR)
				return TRUE;
			newline = space(strlen(pc->program_name)+2);
			pc->curcmd = pc->program_name;
			error(FATAL, RESTRICTED_GDB_COMMAND, 
				cmd, newline, cmd,
				newline, pc->program_name,
				newline, newline, pc->program_name);
		}
	}

	if (kt->relocate && 
	    STRNEQ("disassemble", cmd) && STRNEQ(cmd, "disas"))
               	error(FATAL, 
		    "the gdb \"disassemble\" command is prohibited because the kernel text\n"
		    "%swas relocated%s; use the crash \"dis\" command instead.\n",
			space(strlen(pc->curcmd)+2), kt->flags2 & KASLR ? " by KASLR" : "");
	
	return FALSE;
}

/*
 *  Remove pipe/redirection stuff from the end of the command line.
 */ 
static void
strip_redirection(char *buf)
{
	char *p1, *p2;

	p1 = strstr_rightmost(buf, args[argcnt-1]);
	p2 = p1 + strlen(args[argcnt-1]);
	console("strip_redirection: [%s]\n", p2);

	if ((p1 = strpbrk(p2, "|!>")))
		*p1 = NULLCHAR;

	strip_ending_whitespace(buf);
}

/*
 *  Command for passing strings directly to gdb.
 */
void
cmd_gdb(void)
{
	char buf[BUFSIZE];
        char **argv;

	argv = STREQ(args[0], "gdb") ? &args[1] : &args[0];

        if (*argv == NULL)
                cmd_usage(pc->curcmd, SYNOPSIS);

        if (STREQ(*argv, "set") && argv[1]) {
                /*
                 *  Intercept set commands in case something has to be done
		 *  here or elsewhere.
                 */ 
                if (STREQ(argv[1], "gdb")) {
                        cmd_set();
                        return;
                }
                if (STREQ(argv[1], "output-radix") && argv[2])
                        pc->output_radix = stol(argv[2], FAULT_ON_ERROR, NULL);
        }

	/*
	 *  If the command is not restricted, pass it on.
	 */
	if (!is_restricted_command(*argv, FAULT_ON_ERROR)) {
		if (STREQ(pc->command_line, "gdb")) {
			strcpy(buf, first_space(pc->orig_line));
			strip_beginning_whitespace(buf);
		} else
			strcpy(buf, pc->orig_line);

		if (pc->redirect & (REDIRECT_TO_FILE|REDIRECT_TO_PIPE))
			strip_redirection(buf);

		if (!gdb_pass_through(buf, NULL, GNU_RETURN_ON_ERROR))
			error(INFO, "gdb request failed: %s\n", buf);
	}
}

/*
 *  The gdb target_xfer_memory() has a hook installed to re-route
 *  all memory accesses back here; reads of 1 or 4 bytes come primarily
 *  from text disassembly requests, and are diverted to the text cache.
 */
int 
gdb_readmem_callback(ulong addr, void *buf, int len, int write)
{ 
	char locbuf[SIZEOF_32BIT], *p1;
	uint32_t *p2;
	int memtype;
	ulong readflags;

	if (write)
		return FALSE;

	if (!(pc->cur_req)) {
		return(readmem(addr, KVADDR, buf, len, 
			"gdb_readmem_callback", RETURN_ON_ERROR));
	}

	if (pc->cur_req->flags & GNU_NO_READMEM)
		return TRUE;

	readflags = pc->curcmd_flags & PARTIAL_READ_OK ?
		RETURN_ON_ERROR|RETURN_PARTIAL : RETURN_ON_ERROR;

	if (STREQ(pc->curcmd, "bpf") && pc->curcmd_private &&
	    (addr > (ulong)pc->curcmd_private))
		readflags |= QUIET;

	if (pc->curcmd_flags & MEMTYPE_UVADDR)
		memtype = UVADDR;
	else if (pc->curcmd_flags & MEMTYPE_FILEADDR)
		memtype = FILEADDR;
	else if (!IS_KVADDR(addr)) {
		if (STREQ(pc->curcmd, "gdb") && 
		    STRNEQ(pc->cur_req->buf, "x/")) {
			memtype = UVADDR;
		} else {
			if (CRASHDEBUG(1))
			        console("gdb_readmem_callback: %lx %d FAILED\n",
					addr, len);
			return FALSE;
		}
	} else
		memtype = KVADDR;

	if (CRASHDEBUG(1))
		console("gdb_readmem_callback[%d]: %lx %d\n", 
			memtype, addr, len);

	if (memtype == FILEADDR)
		return(readmem(pc->curcmd_private, memtype, buf, len,
			"gdb_readmem_callback", readflags));
	
	switch (len)
	{
	case SIZEOF_8BIT:
		if (STREQ(pc->curcmd, "bt")) {
			if (readmem(addr, memtype, buf, SIZEOF_8BIT,
		    	    "gdb_readmem_callback", readflags)) 
				return TRUE;
		}

		p1 = (char *)buf;
		if ((memtype == KVADDR) && 
		    text_value_cache_byte(addr, (unsigned char *)p1)) 
			return TRUE;

		if (!readmem(addr, memtype, locbuf, SIZEOF_32BIT,
		    "gdb_readmem_callback", readflags)) 
			return FALSE;

		*p1 = locbuf[0];
		if (memtype == KVADDR) {
			p2 = (uint32_t *)locbuf;
			text_value_cache(addr, *p2, 0);
		}
		return TRUE;

	case SIZEOF_32BIT:
		if (STREQ(pc->curcmd, "bt")) {
			if (readmem(addr, memtype, buf, SIZEOF_32BIT,
		    	    "gdb_readmem_callback", readflags)) 
				return TRUE;
		}

		if ((memtype == KVADDR) && text_value_cache(addr, 0, buf)) 
			return TRUE;

		if (!readmem(addr, memtype, buf, SIZEOF_32BIT, 
		    "gdb_readmem callback", readflags))
			return FALSE;

		if (memtype == KVADDR)
			text_value_cache(addr, 
				(uint32_t)*((uint32_t *)buf), NULL);
		return TRUE;
	}

	return(readmem(addr, memtype, buf, len, 
		"gdb_readmem_callback", readflags));
}

/*
 *  Machine-specific line-number pc section range verifier.
 */
int
gdb_line_number_callback(ulong pc, ulong low, ulong high)
{
	if (machdep->verify_line_number)
		return machdep->verify_line_number(pc, low, high);

	return TRUE;
}

/*
 *  Prevent gdb from trying to translate and print pointers
 *  that are not kernel virtual addresses.  
 */
int
gdb_print_callback(ulong addr)
{
	if (!addr)
		return FALSE;
	else
		return IS_KVADDR(addr);
}

/*
 *  Used by gdb_interface() to catch gdb-related errors, if desired.
 */
void
gdb_error_hook(void)
{
	char buf1[BUFSIZE];
	char buf2[BUFSIZE];
	int buffers;

	if (CRASHDEBUG(2)) {
		sprintf(buf2, "\n");

		if (CRASHDEBUG(5) && (buffers = get_embedded()))
			sprintf(buf2, "(%d buffer%s in use)\n",
				buffers, buffers > 1 ? "s" : "");

		fprintf(stderr, "%s: returned via gdb_error_hook %s",
			gdb_command_string(pc->cur_gdb_cmd, buf1, TRUE), buf2);

		console("%s: returned via gdb_error_hook %s",
			gdb_command_string(pc->cur_gdb_cmd, buf1, TRUE), buf2);
	}

#ifdef GDB_7_6
	do_cleanups(all_cleanups()); 
#else
	do_cleanups(NULL); 
#endif

	longjmp(pc->gdb_interface_env, 1);
}


/*
 *  gdb callback to access debug mode. 
 */
int
gdb_CRASHDEBUG(ulong dval)
{
	if (CRASHDEBUG(dval)) 
		return TRUE;

	return (pc->cur_req && (pc->cur_req->debug >= dval));
}

static ulong 
gdb_user_print_option_address(char *name)
{
        struct gnu_request request;

	request.command = GNU_USER_PRINT_OPTION;
	request.name = name;
	gdb_command_funnel(&request);    
	return request.addr;
}

/*
 *  Try to set a crash scope block based upon the vaddr.   
 */
int
gdb_set_crash_scope(ulong vaddr, char *arg)
{
        struct gnu_request request, *req = &request;
	char name[BUFSIZE];
	struct load_module *lm;

	if (!is_kernel_text(vaddr)) {
		error(INFO, "invalid text address: %s\n", arg);
		return FALSE;
	}

	if (module_symbol(vaddr, NULL, &lm, name, 0)) {
		if (!(lm->mod_flags & MOD_LOAD_SYMS)) {
			error(INFO, "attempting to find/load \"%s\" module debuginfo\n", 
				lm->mod_name);
			if (!load_module_symbols_helper(lm->mod_name)) {
				error(INFO, "cannot find/load \"%s\" module debuginfo\n", 
					lm->mod_name);
				return FALSE;
			}
		}
	} else if (kt->flags2 & KASLR)
		vaddr -= (kt->relocate * -1);

	req->command = GNU_SET_CRASH_BLOCK;
	req->addr = vaddr;
	req->flags = 0;
	req->addr2 = 0;
	gdb_command_funnel(req);    

	if (CRASHDEBUG(1))
		fprintf(fp, 
		    "gdb_set_crash_scope: %s  addr: %lx  block: %lx\n",
			req->flags & GNU_COMMAND_FAILED ? "FAILED" : "OK",  
			req->addr, req->addr2);

	if (req->flags & GNU_COMMAND_FAILED) {
		error(INFO, 
			"gdb cannot find text block for address: %s\n", arg);
		return FALSE;
	}

	return TRUE;
}

#ifndef ALPHA
/*
 *  Stub routine needed for resolution by non-alpha, modified gdb code.
 */
int
get_frame_offset(ulong pc)
{
	return (error(FATAL, 
	    "get_frame_offset: invalid request for non-alpha systems!\n"));
}
#endif /* !ALPHA */