* Introduced pipes in the kernel debugger. The syntax is similar to

pipes in the shell, though the semantics is a little different: The
  second command is invoked whenever the first command has written a
  complete line. The line is passed as last argument to the second
  command. The new command flag B_KDEBUG_PIPE_FINAL_RERUN causes the
  second command to be invoked again (with NULL argument) after the
  first command is done.
* Added kprintf_unfiltered() and kputs_unfiltered() which bypass the
  pipe mechanism and directly print to the bluescreen/serial output.
* Moved most commands from debug.cpp to the new
  debug_builtin_commands.cpp.
* B_KDEBUG_DONT_PARSE_ARGUMENTS commands don't get an argument anymore,
  if it would consist of white space only.
* Added new debugger command return value B_KDEBUG_ERROR, which
  indicates that executing the command failed. This return code will
  abort a complete pipe.
* Since debugger commands can nest (i.e. one command can invoke another
  one) the setjmp()/longjmp() mechanism to restore the stack after a
  page fault in a command needs more than one jump buffer.
* Added abort_debugger_command(), which longjmp()s out of the currently
  executed command. This will also abort the current pipe.
* When pagination is enabled pressing "a" will abort the running command
  (as opposed to "q" which only disables the blue screen output, but
  lets the command continue).
* Added debugger commands:
  - "grep" which can be used to filter output by pattern. Removed the
    "filter" command and the underlying mechanism that did that before.
  - "head" which prints only the first lines of output of another
    command.
  - "wc" counts lines, words, and characters of another command's
    output.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25744 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2008-06-01 02:25:00 +00:00
parent 5f3c81d6a6
commit 73aa393d73
10 changed files with 836 additions and 215 deletions
+246 -22
View File
@@ -10,6 +10,7 @@
#include "debug_commands.h"
#include <setjmp.h>
#include <stdio.h>
#include <string.h>
#include <KernelExport.h>
@@ -19,16 +20,172 @@
#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 = 0;
static struct debugger_command *sCommands;
static jmp_buf sInvokeCommandEnv;
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 : &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*
@@ -98,9 +255,10 @@ 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 && strcmp(argv[1], "--help") == 0 && command->usage != NULL) {
kprintf("usage: %s ", command->name);
kputs(command->usage);
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;
}
@@ -117,30 +275,96 @@ invoke_debugger_command(struct debugger_command *command, int argc, char** argv)
sInCommand = true;
if (setjmp(sInvokeCommandEnv) == 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;
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);
result = command->func(argc, argv);
thread->fault_handler = oldFaultHandler;
sInCommand = false;
return result;
thread->fault_handler = oldFaultHandler;
sInvokeCommandLevel--;
sInCommand = false;
return result;
error:
longjmp(sInvokeCommandEnv, 1);
// jump into the else branch
} else {
kprintf("\n[*** READ/WRITE FAULT ***]\n");
// jump to INVOKE_COMMAND_FAULT case, cleaning up the stack
longjmp(sInvokeCommandEnv[--sInvokeCommandLevel],
INVOKE_COMMAND_FAULT);
case INVOKE_COMMAND_FAULT:
kprintf_unfiltered("\n[*** READ/WRITE FAULT ***]\n");
break;
case INVOKE_COMMAND_ERROR:
// command aborted (no page fault)
break;
}
thread->fault_handler = oldFaultHandler;
sInCommand = false;
return 0;
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;
}
@@ -234,8 +458,8 @@ print_debugger_command_usage(const char* commandName)
// directly print the usage text, if we know it, otherwise invoke the
// command with "--help"
if (command->usage != NULL) {
kprintf("usage: %s ", command->name);
kputs(command->usage);
kprintf_unfiltered("usage: %s ", command->name);
kputs_unfiltered(command->usage);
} else {
char* args[3] = { NULL, "--help", NULL };
invoke_debugger_command(command, 2, args);