mirror of https://github.com/crash-utility/crash
1086 lines
28 KiB
C
1086 lines
28 KiB
C
/* 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"
|
|
|
|
#ifndef GDB_10_2
|
|
static void exit_after_gdb_info(void);
|
|
#endif
|
|
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;
|
|
unsigned char *gdb_prettyprint_structs;
|
|
unsigned char *gdb_prettyprint_arrays;
|
|
unsigned int *gdb_repeat_count_threshold;
|
|
unsigned char *gdb_stop_print_at_null;
|
|
unsigned int *gdb_output_radix;
|
|
static void gdb_error_debug(void);
|
|
|
|
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;
|
|
#ifndef GDB_10_2
|
|
#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
|
|
#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;
|
|
#ifndef GDB_10_2
|
|
#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
|
|
#endif
|
|
args[0] = "gdb";
|
|
args[1] = "-version";
|
|
gdb_main_entry(2, args);
|
|
}
|
|
|
|
#ifndef GDB_10_2
|
|
static void
|
|
exit_after_gdb_info(void)
|
|
{
|
|
fprintf(fp, "\n");
|
|
clean_exit(0);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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 = (unsigned char *)
|
|
gdb_user_print_option_address("prettyprint_structs");
|
|
gdb_prettyprint_arrays = (unsigned char *)
|
|
gdb_user_print_option_address("prettyprint_arrays");
|
|
gdb_repeat_count_threshold = (unsigned int *)
|
|
gdb_user_print_option_address("repeat_count_threshold");
|
|
gdb_stop_print_at_null = (unsigned char *)
|
|
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);
|
|
|
|
#ifdef GDB_10_2
|
|
req->command = GNU_PASS_THROUGH;
|
|
req->name = NULL, req->flags = 0;
|
|
sprintf(req->buf, "set max-value-size unlimited");
|
|
gdb_interface(req);
|
|
|
|
req->command = GNU_PASS_THROUGH;
|
|
req->name = NULL, req->flags = 0;
|
|
sprintf(req->buf, "set max-completions unlimited");
|
|
gdb_interface(req);
|
|
#endif
|
|
|
|
#if 0
|
|
/*
|
|
* 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))
|
|
#else
|
|
if (!(pc->flags & SILENT))
|
|
#endif
|
|
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 (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 (req->flags & GNU_COMMAND_FAILED)
|
|
gdb_error_debug();
|
|
|
|
if (CRASHDEBUG(2))
|
|
dump_gnu_request(req, !IN_GDB);
|
|
|
|
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;
|
|
|
|
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", "until", "delete",
|
|
"clear", "disable", "enable", "condition", "ignore", "catch",
|
|
"tcatch", "return", "file", "exec-file", "core-file", "symbol-file",
|
|
"load", "si", "ni", "shell", "sy",
|
|
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, 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;
|
|
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 (!readmem(addr, memtype, locbuf, SIZEOF_32BIT,
|
|
"gdb_readmem_callback", readflags))
|
|
return FALSE;
|
|
|
|
*p1 = locbuf[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 (!readmem(addr, memtype, buf, SIZEOF_32BIT,
|
|
"gdb_readmem callback", readflags))
|
|
return FALSE;
|
|
|
|
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);
|
|
}
|
|
|
|
char *
|
|
gdb_lookup_module_symbol(ulong addr, ulong *offset)
|
|
{
|
|
struct syment *sp;
|
|
|
|
if ((sp = value_search_module(addr, offset))) {
|
|
return sp->name;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
int
|
|
is_kvaddr(ulong addr)
|
|
{
|
|
return IS_KVADDR(addr);
|
|
}
|
|
|
|
/*
|
|
* Used by gdb_interface() to catch gdb-related errors, if desired.
|
|
*/
|
|
static void
|
|
gdb_error_debug(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);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 (vaddr) {
|
|
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;
|
|
req->fp = pc->nullfp;
|
|
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 */
|
|
|
|
unsigned long crash_get_kaslr_offset(void);
|
|
unsigned long crash_get_kaslr_offset(void)
|
|
{
|
|
return kt->relocate * -1;
|
|
}
|
|
|
|
/* Callbacks for crash_target */
|
|
int crash_get_current_task_reg (int regno, const char *regname,
|
|
int regsize, void *value);
|
|
int crash_get_current_task_reg (int regno, const char *regname,
|
|
int regsize, void *value)
|
|
{
|
|
if (!machdep->get_current_task_reg)
|
|
return FALSE;
|
|
return machdep->get_current_task_reg(regno, regname, regsize, value);
|
|
}
|
|
|