Files
haiku-beta6/src/system/kernel/debug/debug_commands.cpp
T
Ingo Weinhold 46ec230162 When in the kernel debugger we do now record the relevant information
(fault address, pc, read/write) when a page fault occurs, and print them
in case this caused the termination of a debugger command.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@26949 a95241bf-73f2-0310-859d-f6bbb57e9c96
2008-08-12 17:09:42 +00:00

530 lines
12 KiB
C++

/*
* Copyright 2008, Ingo Weinhold, [email protected]
* Copyright 2002-2008, Axel Dörfler, [email protected]
* Distributed under the terms of the MIT License.
*
* Copyright 2001, Travis Geiselbrecht. All rights reserved.
* Distributed under the terms of the NewOS License.
*/
#include "debug_commands.h"
#include <setjmp.h>
#include <stdio.h>
#include <string.h>
#include <KernelExport.h>
#include <debug.h>
#include <lock.h>
#include <thread.h>
#include <util/AutoLock.h>
#include "debug_output_filter.h"
#include "debug_variables.h"
#define INVOKE_COMMAND_FAULT 1
#define INVOKE_COMMAND_ERROR 2
static const int32 kMaxInvokeCommandDepth = 5;
static const int32 kOutputBufferSize = 1024;
static spinlock sSpinlock = B_SPINLOCK_INITIALIZER;
static struct debugger_command *sCommands;
static jmp_buf sInvokeCommandEnv[kMaxInvokeCommandDepth];
static int32 sInvokeCommandLevel = 0;
static bool sInvokeCommandDirectly = false;
static bool sInCommand = false;
static char sOutputBuffers[MAX_DEBUGGER_COMMAND_PIPE_LENGTH][kOutputBufferSize];
static debugger_command_pipe* sCurrentPipe;
static int32 sCurrentPipeSegment;
static int invoke_pipe_segment(debugger_command_pipe* pipe, int32 index,
char* argument);
class PipeDebugOutputFilter : public DebugOutputFilter {
public:
PipeDebugOutputFilter()
{
}
PipeDebugOutputFilter(debugger_command_pipe* pipe, int32 segment,
char* buffer, size_t bufferSize)
:
fPipe(pipe),
fSegment(segment),
fBuffer(buffer),
fBufferCapacity(bufferSize - 1),
fBufferSize(0)
{
}
virtual void PrintString(const char* string)
{
if (fPipe->broken)
return;
size_t size = strlen(string);
while (const char* newLine = strchr(string, '\n')) {
size_t length = newLine - string;
_Append(string, length);
// invoke command
fBuffer[fBufferSize] = '\0';
invoke_pipe_segment(fPipe, fSegment + 1, fBuffer);
fBufferSize = 0;
string = newLine + 1;
size -= length + 1;
}
_Append(string, size);
if (fBufferSize == fBufferCapacity) {
// buffer is full, but contains no newline -- execute anyway
invoke_pipe_segment(fPipe, fSegment + 1, fBuffer);
fBufferSize = 0;
}
}
virtual void Print(const char* format, va_list args)
{
if (fPipe->broken)
return;
// print directly into the buffer
fBufferSize += vsnprintf(fBuffer + fBufferSize,
fBufferCapacity - fBufferSize, format, args);
// execute every complete line
fBuffer[fBufferSize] = '\0';
char* line = fBuffer;
while (char* newLine = strchr(line, '\n')) {
// invoke command
*newLine = '\0';
invoke_pipe_segment(fPipe, fSegment + 1, line);
line = newLine + 1;
}
size_t left = fBuffer + fBufferSize - line;
if (left == fBufferCapacity) {
// buffer is full, but contains no newline -- execute anyway
invoke_pipe_segment(fPipe, fSegment + 1, fBuffer);
left = 0;
}
if (left > 0)
memmove(fBuffer, line, left);
fBufferSize = left;
}
private:
void _Append(const char* string, size_t length)
{
size_t toAppend = min_c(length, fBufferCapacity - fBufferSize);
memcpy(fBuffer + fBufferSize, string, toAppend);
fBufferSize += length;
}
private:
debugger_command_pipe* fPipe;
int32 fSegment;
char* fBuffer;
size_t fBufferCapacity;
size_t fBufferSize;
};
static PipeDebugOutputFilter sPipeOutputFilters[
MAX_DEBUGGER_COMMAND_PIPE_LENGTH - 1];
static int
invoke_pipe_segment(debugger_command_pipe* pipe, int32 index, char* argument)
{
// set debug output
DebugOutputFilter* oldFilter = set_debug_output_filter(
index == pipe->segment_count - 1
? &gDefaultDebugOutputFilter
: (DebugOutputFilter*)&sPipeOutputFilters[index]);
// set last command argument
debugger_command_pipe_segment& segment = pipe->segments[index];
if (index > 0)
segment.argv[segment.argc - 1] = argument;
// invoke command
int32 oldIndex = sCurrentPipeSegment;
sCurrentPipeSegment = index;
int result = invoke_debugger_command(segment.command, segment.argc,
segment.argv);
segment.invocations++;
sCurrentPipeSegment = oldIndex;
// reset debug output
set_debug_output_filter(oldFilter);
if (result == B_KDEBUG_ERROR) {
pipe->broken = true;
// Abort the previous pipe segment execution. The complete pipe is
// aborted iteratively this way.
if (index > 0)
abort_debugger_command();
}
return result;
}
debugger_command*
next_debugger_command(debugger_command* command, const char* prefix,
int prefixLen)
{
if (command == NULL)
command = sCommands;
else
command = command->next;
while (command != NULL && !strncmp(prefix, command->name, prefixLen) == 0)
command = command->next;
return command;
}
debugger_command *
find_debugger_command(const char *name, bool partialMatch, bool& ambiguous)
{
debugger_command *command;
ambiguous = false;
// search command by full name
for (command = sCommands; command != NULL; command = command->next) {
if (strcmp(name, command->name) == 0)
return command;
}
// if it couldn't be found, search for a partial match
if (partialMatch) {
int length = strlen(name);
command = next_debugger_command(NULL, name, length);
if (command != NULL) {
if (next_debugger_command(command, name, length) == NULL)
return command;
ambiguous = true;
}
}
return NULL;
}
/*! Returns wether or not a debugger command is currently being invoked.
*/
bool
in_command_invocation(void)
{
return sInCommand;
}
/*! This function is a safe gate through which debugger commands are invoked.
It sets a fault handler before invoking the command, so that an invalid
memory access will not result in another KDL session on top of this one
(and "cont" not to work anymore). We use setjmp() + longjmp() to "unwind"
the stack after catching a fault.
*/
int
invoke_debugger_command(struct debugger_command *command, int argc, char** argv)
{
// intercept invocations with "--help" and directly print the usage text
// If we know the command's usage text, intercept "--help" invocations
// and print it directly.
if (argc == 2 && argv[1] != NULL && strcmp(argv[1], "--help") == 0
&& command->usage != NULL) {
kprintf_unfiltered("usage: %s ", command->name);
kputs_unfiltered(command->usage);
return 0;
}
struct thread* thread = thread_get_current_thread();
addr_t oldFaultHandler = thread->fault_handler;
// replace argv[0] with the actual command name
argv[0] = (char *)command->name;
// Invoking the command directly might be useful when debugging debugger
// commands.
if (sInvokeCommandDirectly)
return command->func(argc, argv);
sInCommand = true;
switch (setjmp(sInvokeCommandEnv[sInvokeCommandLevel++])) {
case 0:
int result;
thread->fault_handler = (addr_t)&&error;
// Fake goto to trick the compiler not to optimize the code at the
// label away.
if (!thread)
goto error;
result = command->func(argc, argv);
thread->fault_handler = oldFaultHandler;
sInvokeCommandLevel--;
sInCommand = false;
return result;
error:
// jump to INVOKE_COMMAND_FAULT case, cleaning up the stack
longjmp(sInvokeCommandEnv[--sInvokeCommandLevel],
INVOKE_COMMAND_FAULT);
case INVOKE_COMMAND_FAULT:
{
debug_page_fault_info* info = debug_get_page_fault_info();
if ((info->flags & DEBUG_PAGE_FAULT_NO_INFO) == 0) {
kprintf_unfiltered("\n[*** %s FAULT at %#lx, pc: %#lx ***]\n",
(info->flags & DEBUG_PAGE_FAULT_NO_INFO) != 0
? "WRITE" : "READ",
info->fault_address, info->pc);
} else {
kprintf_unfiltered("\n[*** READ/WRITE FAULT (?), "
"pc: %#lx ***]\n", info->pc);
}
break;
}
case INVOKE_COMMAND_ERROR:
// command aborted (no page fault)
break;
}
thread->fault_handler = oldFaultHandler;
sInCommand = false;
return B_KDEBUG_ERROR;
}
/*! Aborts the currently executed debugger command (in fact the complete pipe),
unless direct command invocation has been set. If successful, the function
won't return.
*/
void
abort_debugger_command()
{
if (!sInvokeCommandDirectly && sInvokeCommandLevel > 0) {
longjmp(sInvokeCommandEnv[--sInvokeCommandLevel],
INVOKE_COMMAND_ERROR);
}
}
int
invoke_debugger_command_pipe(debugger_command_pipe* pipe)
{
debugger_command_pipe* oldPipe = sCurrentPipe;
sCurrentPipe = pipe;
// prepare outputs
// TODO: If a pipe is invoked in a pipe, outputs will clash.
int32 segments = pipe->segment_count;
for (int32 i = 0; i < segments - 1; i++) {
new(&sPipeOutputFilters[i]) PipeDebugOutputFilter(pipe, i,
sOutputBuffers[i], kOutputBufferSize);
}
int result = invoke_pipe_segment(pipe, 0, NULL);
// perform final rerun for all commands that want it
for (int32 i = 1; result != B_KDEBUG_ERROR && i < segments; i++) {
debugger_command_pipe_segment& segment = pipe->segments[i];
if ((segment.command->flags & B_KDEBUG_PIPE_FINAL_RERUN) != 0)
result = invoke_pipe_segment(pipe, i, NULL);
}
sCurrentPipe = oldPipe;
return result;
}
debugger_command_pipe*
get_current_debugger_command_pipe()
{
return sCurrentPipe;
}
debugger_command_pipe_segment*
get_current_debugger_command_pipe_segment()
{
return sCurrentPipe != NULL
? &sCurrentPipe->segments[sCurrentPipeSegment] : NULL;
}
debugger_command*
get_debugger_commands()
{
return sCommands;
}
void
sort_debugger_commands()
{
// bubble sort the commands
debugger_command* stopCommand = NULL;
while (stopCommand != sCommands) {
debugger_command** command = &sCommands;
while (true) {
debugger_command* nextCommand = (*command)->next;
if (nextCommand == stopCommand) {
stopCommand = *command;
break;
}
if (strcmp((*command)->name, nextCommand->name) > 0) {
(*command)->next = nextCommand->next;
nextCommand->next = *command;
*command = nextCommand;
}
command = &(*command)->next;
}
}
}
status_t
add_debugger_command_etc(const char* name, debugger_command_hook func,
const char* description, const char* usage, uint32 flags)
{
struct debugger_command *cmd;
cmd = (struct debugger_command*)malloc(sizeof(struct debugger_command));
if (cmd == NULL)
return B_NO_MEMORY;
cmd->func = func;
cmd->name = name;
cmd->description = description;
cmd->usage = usage;
cmd->flags = flags;
InterruptsSpinLocker _(sSpinlock);
cmd->next = sCommands;
sCommands = cmd;
return B_OK;
}
status_t
add_debugger_command_alias(const char* newName, const char* oldName,
const char* description)
{
// get the old command
bool ambiguous;
debugger_command* command = find_debugger_command(oldName, false,
ambiguous);
if (command == NULL)
return B_NAME_NOT_FOUND;
// register new command
return add_debugger_command_etc(newName, command->func,
description != NULL ? description : command->description,
command->usage, command->flags);
}
bool
print_debugger_command_usage(const char* commandName)
{
// get the command
bool ambiguous;
debugger_command* command = find_debugger_command(commandName, true,
ambiguous);
if (command == NULL)
return false;
// directly print the usage text, if we know it, otherwise invoke the
// command with "--help"
if (command->usage != NULL) {
kprintf_unfiltered("usage: %s ", command->name);
kputs_unfiltered(command->usage);
} else {
char* args[3] = { NULL, "--help", NULL };
invoke_debugger_command(command, 2, args);
}
return true;
}
// #pragma mark - public API
int
add_debugger_command(char *name, int (*func)(int, char **), char *desc)
{
return add_debugger_command_etc(name, func, desc, NULL, 0);
}
int
remove_debugger_command(char * name, int (*func)(int, char **))
{
struct debugger_command *cmd = sCommands;
struct debugger_command *prev = NULL;
cpu_status state;
state = disable_interrupts();
acquire_spinlock(&sSpinlock);
while (cmd) {
if (!strcmp(cmd->name, name) && cmd->func == func)
break;
prev = cmd;
cmd = cmd->next;
}
if (cmd) {
if (cmd == sCommands)
sCommands = cmd->next;
else
prev->next = cmd->next;
}
release_spinlock(&sSpinlock);
restore_interrupts(state);
if (cmd) {
free(cmd);
return B_NO_ERROR;
}
return B_NAME_NOT_FOUND;
}