mirror of
https://github.com/crash-utility/crash
synced 2024-12-14 05:04:32 +00:00
1028 lines
27 KiB
C
1028 lines
27 KiB
C
|
/* gdb_interface.c - core analysis suite
|
||
|
*
|
||
|
* Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc.
|
||
|
* Copyright (C) 2002-2013 David Anderson
|
||
|
* Copyright (C) 2002-2013 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("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 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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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, &pc->orig_line[3]);
|
||
|
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;
|
||
|
|
||
|
if (write)
|
||
|
return FALSE;
|
||
|
|
||
|
if (pc->cur_req->flags & GNU_NO_READMEM)
|
||
|
return TRUE;
|
||
|
|
||
|
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", RETURN_ON_ERROR));
|
||
|
|
||
|
switch (len)
|
||
|
{
|
||
|
case SIZEOF_8BIT:
|
||
|
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", RETURN_ON_ERROR))
|
||
|
return FALSE;
|
||
|
|
||
|
*p1 = locbuf[0];
|
||
|
if (memtype == KVADDR) {
|
||
|
p2 = (uint32_t *)locbuf;
|
||
|
text_value_cache(addr, *p2, 0);
|
||
|
}
|
||
|
return TRUE;
|
||
|
|
||
|
case SIZEOF_32BIT:
|
||
|
if ((memtype == KVADDR) && text_value_cache(addr, 0, buf))
|
||
|
return TRUE;
|
||
|
|
||
|
if (!readmem(addr, memtype, buf, SIZEOF_32BIT,
|
||
|
"gdb_readmem callback", RETURN_ON_ERROR))
|
||
|
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", RETURN_ON_ERROR));
|
||
|
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 */
|
||
|
|
||
|
|