* Implemented parse_expression(). The back-end is an expression parser
that is a little more powerful than BeOS'. It features:
- Persistent and temporary uint64 variables. The former kind is set
only by the user. The latter (those prefixed "_") can be set
automatically by commands, thus e.g. making it easier to access
members of a dumped structure. They are unset when the next command
is invoked. The special temporary variable "_" is defined as a
command's return value.
- Expressions can contain nested command invocations using brackets
("[ ... ]").
- Command lines are parsed by the expression parser, too. They can
contain command invocations (in brackets) and expressions (in
parentheses).
* Added debugger commands:
- expr: Evaluates the given expression and prints the result.
- unset: Undefines a variable.
- vars: Prints the values of all defined variables.
* Moved debugger command code into its own source file.
git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@23546 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -59,7 +59,18 @@ extern void debug_stop_screen_debug_output(void);
|
||||
extern void dprintf_no_syslog(const char *format, ...)
|
||||
__attribute__ ((format (__printf__, 1, 2)));
|
||||
|
||||
extern void _user_debug_output(const char *userString);
|
||||
extern bool is_debug_variable_defined(const char* variableName);
|
||||
extern bool set_debug_variable(const char* variableName, uint64 value);
|
||||
extern uint64 get_debug_variable(const char* variableName,
|
||||
uint64 defaultValue);
|
||||
extern bool remove_debug_variable(const char* variableName);
|
||||
extern void remove_all_temporary_debug_variables();
|
||||
|
||||
extern bool evaluate_debug_expression(const char* expression,
|
||||
uint64* result, bool silent);
|
||||
extern int evaluate_debug_command(const char* command);
|
||||
|
||||
extern void _user_debug_output(const char *userString);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ UsePrivateHeaders [ FDirName kernel debug ] syslog_daemon ;
|
||||
KernelMergeObject kernel_debug.o :
|
||||
blue_screen.cpp
|
||||
debug.cpp
|
||||
debug_commands.cpp
|
||||
debug_parser.cpp
|
||||
debug_variables.cpp
|
||||
frame_buffer_console.cpp
|
||||
gdb.c
|
||||
tracing.cpp
|
||||
|
||||
@@ -28,26 +28,20 @@
|
||||
#include <syslog_daemon.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <syslog.h>
|
||||
|
||||
#include "debug_commands.h"
|
||||
#include "debug_variables.h"
|
||||
|
||||
|
||||
static const char* const kKDLPrompt = "kdebug> ";
|
||||
|
||||
extern "C" int kgets(char *buffer, int length);
|
||||
static int invoke_command(struct debugger_command *command, int argc,
|
||||
char** argv);
|
||||
|
||||
typedef struct debugger_command {
|
||||
struct debugger_command *next;
|
||||
int (*func)(int, char **);
|
||||
const char *name;
|
||||
const char *description;
|
||||
} debugger_command;
|
||||
|
||||
int dbg_register_file[B_MAX_CPU_COUNT][14];
|
||||
/* XXXmpetit -- must be made generic */
|
||||
@@ -66,11 +60,6 @@ static struct syslog_message *sSyslogMessage;
|
||||
static struct ring_buffer *sSyslogBuffer;
|
||||
static bool sSyslogDropped = false;
|
||||
|
||||
static struct debugger_command *sCommands;
|
||||
|
||||
static jmp_buf sInvokeCommandEnv;
|
||||
static bool sInvokeCommandDirectly = false;
|
||||
|
||||
static const char* sCurrentKernelDebuggerMessage;
|
||||
|
||||
#define SYSLOG_BUFFER_SIZE 65536
|
||||
@@ -97,52 +86,6 @@ static char *sArguments[MAX_ARGS] = { NULL, };
|
||||
#define distance(a, b) ((a) < (b) ? (b) - (a) : (a) - (b))
|
||||
|
||||
|
||||
static debugger_command*
|
||||
next_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;
|
||||
}
|
||||
|
||||
|
||||
static debugger_command *
|
||||
find_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_command(NULL, name, length);
|
||||
if (command != NULL) {
|
||||
if (next_command(command, name, length) == NULL)
|
||||
return command;
|
||||
|
||||
ambiguous = true;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
kputchar(char c)
|
||||
{
|
||||
@@ -256,14 +199,14 @@ public:
|
||||
tmpChar = *firstSpace;
|
||||
*firstSpace = '\0';
|
||||
bool ambiguous;
|
||||
debugger_command* command = find_command(buffer, true, ambiguous);
|
||||
debugger_command* command = find_debugger_command(buffer, true, ambiguous);
|
||||
*firstSpace = tmpChar;
|
||||
|
||||
if (command != NULL) {
|
||||
kputchar('\n');
|
||||
|
||||
char* args[3] = { NULL, "--help", NULL };
|
||||
invoke_command(command, 2, args);
|
||||
invoke_debugger_command(command, 2, args);
|
||||
} else {
|
||||
if (ambiguous)
|
||||
kprintf("\nambiguous command\n");
|
||||
@@ -281,7 +224,7 @@ public:
|
||||
debugger_command* command = NULL;
|
||||
int32 longestCommonPrefix = 0;
|
||||
const char* previousCommandName = NULL;
|
||||
while ((command = next_command(command, buffer, position))
|
||||
while ((command = next_debugger_command(command, buffer, position))
|
||||
!= NULL) {
|
||||
count++;
|
||||
int32 nameLength = strlen(command->name);
|
||||
@@ -312,7 +255,7 @@ public:
|
||||
reprintLine = true;
|
||||
} else if (count == 1) {
|
||||
// exactly one completion
|
||||
command = next_command(NULL, buffer, position);
|
||||
command = next_debugger_command(NULL, buffer, position);
|
||||
|
||||
// check for sufficient space in the buffer
|
||||
int32 neededSpace = longestName - position + 1;
|
||||
@@ -345,7 +288,7 @@ public:
|
||||
int columns = 80 / (longestName + 2);
|
||||
debugger_command* command = NULL;
|
||||
int column = 0;
|
||||
while ((command = next_command(command, buffer, position))
|
||||
while ((command = next_debugger_command(command, buffer, position))
|
||||
!= NULL) {
|
||||
// spacing
|
||||
if (column > 0 && column % columns == 0)
|
||||
@@ -548,101 +491,6 @@ kgets(char *buffer, int length)
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
parse_line(const char *buffer, char **argv, int *_argc, int32 maxArgs)
|
||||
{
|
||||
char *string = sParseLine;
|
||||
int32 index = 0;
|
||||
|
||||
strcpy(string, buffer);
|
||||
|
||||
for (; index < maxArgs && string[0]; index++) {
|
||||
char quoted;
|
||||
char c;
|
||||
|
||||
// skip white space
|
||||
while ((c = string[0]) != '\0' && isspace(c)) {
|
||||
string++;
|
||||
}
|
||||
if (!c)
|
||||
break;
|
||||
|
||||
if (c == '\'' || c == '"') {
|
||||
argv[index] = ++string;
|
||||
quoted = c;
|
||||
} else {
|
||||
argv[index] = string;
|
||||
quoted = 0;
|
||||
}
|
||||
|
||||
// find end of string
|
||||
|
||||
while (string[0]
|
||||
&& ((quoted && string[0] != quoted)
|
||||
|| (!quoted && !isspace(string[0])))) {
|
||||
if (string[0] == '\\') {
|
||||
// filter out backslashes
|
||||
strcpy(string, string + 1);
|
||||
string++;
|
||||
}
|
||||
string++;
|
||||
}
|
||||
|
||||
if (string[0]) {
|
||||
// terminate string
|
||||
string[0] = '\0';
|
||||
string++;
|
||||
}
|
||||
}
|
||||
|
||||
return *_argc = index;
|
||||
}
|
||||
|
||||
|
||||
/*! 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.
|
||||
*/
|
||||
static int
|
||||
invoke_command(struct debugger_command *command, int argc, char** argv)
|
||||
{
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
result = command->func(argc, argv);
|
||||
thread->fault_handler = oldFaultHandler;
|
||||
return result;
|
||||
|
||||
error:
|
||||
longjmp(sInvokeCommandEnv, 1);
|
||||
// jump into the else branch
|
||||
} else {
|
||||
kprintf("\n[*** READ/WRITE FAULT ***]\n");
|
||||
}
|
||||
|
||||
thread->fault_handler = oldFaultHandler;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
kernel_debugger_loop(void)
|
||||
{
|
||||
@@ -652,44 +500,43 @@ kernel_debugger_loop(void)
|
||||
kprintf("Welcome to Kernel Debugging Land...\n");
|
||||
kprintf("Running on CPU %ld\n", sDebuggerOnCPU);
|
||||
|
||||
for (;;) {
|
||||
struct debugger_command *cmd = NULL;
|
||||
int argc;
|
||||
int32 continuableLine = -1;
|
||||
// Index of the previous command line, if the command returned
|
||||
// B_KDEBUG_CONT, i.e. asked to be repeatable, -1 otherwise.
|
||||
|
||||
for (;;) {
|
||||
CommandLineEditingHelper editingHelper;
|
||||
kprintf(kKDLPrompt);
|
||||
read_line(sLineBuffer[sCurrentLine], LINE_BUFFER_SIZE, &editingHelper);
|
||||
parse_line(sLineBuffer[sCurrentLine], sArguments, &argc, MAX_ARGS);
|
||||
char* line = sLineBuffer[sCurrentLine];
|
||||
read_line(line, LINE_BUFFER_SIZE, &editingHelper);
|
||||
|
||||
// We support calling last executed command again if
|
||||
// B_KDEDUG_CONT was returned last time, so cmd != NULL
|
||||
if (argc <= 0 && cmd == NULL)
|
||||
continue;
|
||||
// check, if the line is empty or whitespace only
|
||||
bool whiteSpaceOnly = true;
|
||||
for (int i = 0 ; line[i] != '\0'; i++) {
|
||||
if (!isspace(line[i])) {
|
||||
whiteSpaceOnly = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (whiteSpaceOnly) {
|
||||
if (continuableLine < 0)
|
||||
continue;
|
||||
|
||||
// the previous command can be repeated
|
||||
sCurrentLine = continuableLine;
|
||||
line = sLineBuffer[sCurrentLine];
|
||||
}
|
||||
|
||||
sDebuggerOnCPU = smp_get_current_cpu();
|
||||
|
||||
bool ambiguous;
|
||||
if (argc > 0)
|
||||
cmd = find_command(sArguments[0], true, ambiguous);
|
||||
int rc = evaluate_debug_command(line);
|
||||
|
||||
if (cmd == NULL) {
|
||||
if (ambiguous) {
|
||||
kprintf("Ambiguous command. Use tab completion to get a list "
|
||||
"of matching commands. Enter \"help\" to get a list of "
|
||||
"all supported commands.\n");
|
||||
} else {
|
||||
kprintf("Unknown command. Enter \"help\" to get a list of all "
|
||||
"supported commands.\n");
|
||||
}
|
||||
} else {
|
||||
int rc = invoke_command(cmd, argc, sArguments);
|
||||
if (rc == B_KDEBUG_QUIT)
|
||||
break; // okay, exit now.
|
||||
|
||||
if (rc == B_KDEBUG_QUIT)
|
||||
break; // okay, exit now.
|
||||
|
||||
if (rc != B_KDEBUG_CONT)
|
||||
cmd = NULL; // forget last command executed...
|
||||
}
|
||||
// If the command is continuable, remember the current line index.
|
||||
continuableLine = (rc == B_KDEBUG_CONT ? sCurrentLine : -1);
|
||||
|
||||
if (++sCurrentLine >= HISTORY_SIZE)
|
||||
sCurrentLine = 0;
|
||||
@@ -725,7 +572,7 @@ cmd_help(int argc, char **argv)
|
||||
bool ambiguous;
|
||||
|
||||
if (argc > 1) {
|
||||
specified = find_command(argv[1], false, ambiguous);
|
||||
specified = find_debugger_command(argv[1], false, ambiguous);
|
||||
if (specified == NULL) {
|
||||
start = argv[1];
|
||||
startLength = strlen(start);
|
||||
@@ -740,7 +587,8 @@ cmd_help(int argc, char **argv)
|
||||
else
|
||||
kprintf("debugger commands:\n");
|
||||
|
||||
for (command = sCommands; command != NULL; command = command->next) {
|
||||
for (command = get_debugger_commands(); command != NULL;
|
||||
command = command->next) {
|
||||
if (specified && command->func != specified->func)
|
||||
continue;
|
||||
if (start != NULL && strncmp(start, command->name, startLength))
|
||||
@@ -771,6 +619,25 @@ cmd_dump_kdl_message(int argc, char **argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
cmd_expr(int argc, char **argv)
|
||||
{
|
||||
static const char* usage = "usage: expr <expression>\n"
|
||||
"Evaluates the given expression and prints the result.\n";
|
||||
if (argc != 2 || strcmp(argv[1], "--help") == 0) {
|
||||
kprintf(usage);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64 result;
|
||||
if (evaluate_debug_expression(argv[1], &result, false)) {
|
||||
kprintf("%llu (0x%llx)\n", result, result);
|
||||
set_debug_variable("_", result);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static status_t
|
||||
syslog_sender(void *data)
|
||||
@@ -1017,7 +884,10 @@ debug_init_post_vm(kernel_args *args)
|
||||
add_debugger_command("continue", &cmd_continue, "Leave kernel debugger");
|
||||
add_debugger_command("message", &cmd_dump_kdl_message,
|
||||
"Reprint the message printed when entering KDL");
|
||||
add_debugger_command("expr", &cmd_expr,
|
||||
"Evaluates the given expression and prints the result");
|
||||
|
||||
debug_variables_init();
|
||||
frame_buffer_console_init(args);
|
||||
arch_debug_console_init_settings(args);
|
||||
tracing_init();
|
||||
@@ -1089,75 +959,11 @@ debug_init_post_modules(struct kernel_args *args)
|
||||
// #pragma mark - public API
|
||||
|
||||
|
||||
int
|
||||
add_debugger_command(char *name, int (*func)(int, char **), char *desc)
|
||||
{
|
||||
cpu_status state;
|
||||
struct debugger_command *cmd;
|
||||
|
||||
cmd = (struct debugger_command *)malloc(sizeof(struct debugger_command));
|
||||
if (cmd == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
cmd->func = func;
|
||||
cmd->name = name;
|
||||
cmd->description = desc;
|
||||
|
||||
state = disable_interrupts();
|
||||
acquire_spinlock(&sSpinlock);
|
||||
|
||||
cmd->next = sCommands;
|
||||
sCommands = cmd;
|
||||
|
||||
release_spinlock(&sSpinlock);
|
||||
restore_interrupts(state);
|
||||
|
||||
return B_NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
uint32
|
||||
parse_expression(const char *expression)
|
||||
{
|
||||
// TODO: Implement expression parser (cf. BeBook).
|
||||
return strtoul(expression, NULL, 0);
|
||||
uint64 result;
|
||||
return (evaluate_debug_expression(expression, &result, true) ? result : 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1207,27 +1013,8 @@ kernel_debugger(const char *message)
|
||||
|
||||
sCurrentKernelDebuggerMessage = message;
|
||||
|
||||
// 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) {
|
||||
debugger_command* tmpCommand = nextCommand->next;
|
||||
(*command)->next = nextCommand->next;
|
||||
nextCommand->next = *command;
|
||||
*command = nextCommand;
|
||||
}
|
||||
|
||||
command = &(*command)->next;
|
||||
}
|
||||
}
|
||||
// sort the commands
|
||||
sort_debugger_commands();
|
||||
|
||||
kernel_debugger_loop();
|
||||
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de
|
||||
* Copyright 2002-2007, Axel Dörfler, axeld@pinc-software.de
|
||||
* 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 <string.h>
|
||||
|
||||
#include <KernelExport.h>
|
||||
|
||||
#include <debug.h>
|
||||
#include <lock.h>
|
||||
#include <thread.h>
|
||||
|
||||
#include "debug_variables.h"
|
||||
|
||||
|
||||
static spinlock sSpinlock = 0;
|
||||
|
||||
static struct debugger_command *sCommands;
|
||||
|
||||
static jmp_buf sInvokeCommandEnv;
|
||||
static bool sInvokeCommandDirectly = false;
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/*! 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)
|
||||
{
|
||||
// remove the temporary variables of the previously executed command, if
|
||||
// this command sets a temporary variable
|
||||
mark_temporary_debug_variables_obsolete();
|
||||
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
result = command->func(argc, argv);
|
||||
thread->fault_handler = oldFaultHandler;
|
||||
return result;
|
||||
|
||||
error:
|
||||
longjmp(sInvokeCommandEnv, 1);
|
||||
// jump into the else branch
|
||||
} else {
|
||||
kprintf("\n[*** READ/WRITE FAULT ***]\n");
|
||||
}
|
||||
|
||||
thread->fault_handler = oldFaultHandler;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
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) {
|
||||
debugger_command* tmpCommand = nextCommand->next;
|
||||
(*command)->next = nextCommand->next;
|
||||
nextCommand->next = *command;
|
||||
*command = nextCommand;
|
||||
}
|
||||
|
||||
command = &(*command)->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - public API
|
||||
|
||||
|
||||
int
|
||||
add_debugger_command(char *name, int (*func)(int, char **), char *desc)
|
||||
{
|
||||
cpu_status state;
|
||||
struct debugger_command *cmd;
|
||||
|
||||
cmd = (struct debugger_command *)malloc(sizeof(struct debugger_command));
|
||||
if (cmd == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
cmd->func = func;
|
||||
cmd->name = name;
|
||||
cmd->description = desc;
|
||||
|
||||
state = disable_interrupts();
|
||||
acquire_spinlock(&sSpinlock);
|
||||
|
||||
cmd->next = sCommands;
|
||||
sCommands = cmd;
|
||||
|
||||
release_spinlock(&sSpinlock);
|
||||
restore_interrupts(state);
|
||||
|
||||
return B_NO_ERROR;
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef _KERNEL_DEBUG_COMMANDS_H
|
||||
#define _KERNEL_DEBUG_COMMANDS_H
|
||||
|
||||
|
||||
#include <SupportDefs.h>
|
||||
|
||||
|
||||
struct debugger_command {
|
||||
struct debugger_command *next;
|
||||
int (*func)(int, char **);
|
||||
const char *name;
|
||||
const char *description;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
debugger_command* next_debugger_command(debugger_command* command,
|
||||
const char* prefix, int prefixLen);
|
||||
debugger_command* find_debugger_command(const char* name, bool partialMatch,
|
||||
bool& ambiguous);
|
||||
int invoke_debugger_command(struct debugger_command *command, int argc,
|
||||
char** argv);
|
||||
|
||||
debugger_command* get_debugger_commands();
|
||||
void sort_debugger_commands();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
|
||||
#endif // _KERNEL_DEBUG_COMMANDS_H
|
||||
@@ -0,0 +1,929 @@
|
||||
/*
|
||||
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de
|
||||
* Copyright 2006, Stephan Aßmus, superstippi@gmx.de
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
#include <debug.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <KernelExport.h>
|
||||
|
||||
#include "debug_commands.h"
|
||||
#include "debug_variables.h"
|
||||
|
||||
|
||||
/*
|
||||
Grammar:
|
||||
|
||||
commandLine := command | ( "(" expression ")" )
|
||||
expression := term | assignment
|
||||
assignment := variable ( "=" | "+=" | "-=" | "*=" | "/=" | "%=" )
|
||||
expression
|
||||
term := sum
|
||||
sum := product ( ( "+" | "-" ) product )*
|
||||
product := unary ( ( "*" | "/" | "%" ) unary )*
|
||||
unary := [ "-" ] atom
|
||||
atom := variable | ( "(" expression ")" ) | ( "[" command "]" )
|
||||
variable := identifier
|
||||
identifier := ( "_" | "a" - "z" | "A" - "Z" )
|
||||
( "_" | "a" - "z" | "A" - "Z" | "0" - "9" )*
|
||||
command := identifier argument*
|
||||
argument := ( "(" expression ")" ) | ( "[" commandLine "]" )
|
||||
| unquotedString | quotedString
|
||||
*/
|
||||
|
||||
|
||||
static const int kMaxTokenLength = 128;
|
||||
static const int kJumpBufferCount = 10;
|
||||
|
||||
static const int kMaxArgumentCount = 16;
|
||||
static const size_t kTemporaryStorageSize = 10240;
|
||||
|
||||
static jmp_buf sJumpBuffers[kJumpBufferCount];
|
||||
static int sNextJumpBufferIndex = 0;
|
||||
|
||||
static char sExceptionMessage[128];
|
||||
static int sExceptionPosition;
|
||||
|
||||
static char sTempBuffer[128];
|
||||
// for composing debug output etc.
|
||||
|
||||
// temporary storage for command argument vectors and the arguments itself
|
||||
static uint8 sTemporaryStorage[kTemporaryStorageSize];
|
||||
static size_t sTemporaryStorageUsed = 0;
|
||||
|
||||
enum {
|
||||
TOKEN_ASSIGN_FLAG = 0x100,
|
||||
TOKEN_FLAGS = TOKEN_ASSIGN_FLAG,
|
||||
|
||||
TOKEN_IDENTIFIER = 'a',
|
||||
|
||||
TOKEN_CONSTANT = '0',
|
||||
|
||||
TOKEN_PLUS = '+',
|
||||
TOKEN_MINUS = '-',
|
||||
|
||||
TOKEN_STAR = '*',
|
||||
TOKEN_SLASH = '/',
|
||||
TOKEN_MODULO = '%',
|
||||
|
||||
TOKEN_ASSIGN = '=' | TOKEN_ASSIGN_FLAG,
|
||||
TOKEN_PLUS_ASSIGN = TOKEN_PLUS | TOKEN_ASSIGN_FLAG,
|
||||
TOKEN_MINUS_ASSIGN = TOKEN_MINUS | TOKEN_ASSIGN_FLAG,
|
||||
TOKEN_STAR_ASSIGN = TOKEN_STAR | TOKEN_ASSIGN_FLAG,
|
||||
TOKEN_SLASH_ASSIGN = TOKEN_SLASH | TOKEN_ASSIGN_FLAG,
|
||||
TOKEN_MODULO_ASSIGN = TOKEN_MODULO | TOKEN_ASSIGN_FLAG,
|
||||
|
||||
TOKEN_OPENING_PARENTHESIS = '(',
|
||||
TOKEN_CLOSING_PARENTHESIS = ')',
|
||||
TOKEN_OPENING_BRACKET = '[',
|
||||
TOKEN_CLOSING_BRACKET = ']',
|
||||
|
||||
TOKEN_STRING = '"',
|
||||
TOKEN_UNKNOWN = '?',
|
||||
TOKEN_NONE = ' ',
|
||||
TOKEN_END_OF_LINE = '\n',
|
||||
};
|
||||
|
||||
struct Token {
|
||||
char string[kMaxTokenLength];
|
||||
uint64 value;
|
||||
int32 type;
|
||||
int32 position;
|
||||
|
||||
void SetTo(const char* string, int32 length, int32 position, int32 type)
|
||||
{
|
||||
length = min_c((size_t)length, (sizeof(this->string) - 1));
|
||||
strlcpy(this->string, string, length + 1);
|
||||
this->type = type;
|
||||
this->value = 0;
|
||||
this->position = position;
|
||||
}
|
||||
|
||||
void Unset()
|
||||
{
|
||||
string[0] = '\0';
|
||||
value = 0;
|
||||
type = TOKEN_NONE;
|
||||
position = 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// #pragma mark - exceptions
|
||||
|
||||
|
||||
static void
|
||||
parse_exception(const char* message, int32 position)
|
||||
{
|
||||
if (sNextJumpBufferIndex == 0) {
|
||||
kprintf("parse_exception(): No jump buffer!\n");
|
||||
kprintf("exception: \"%s\", position: %lu\n", message, position);
|
||||
return;
|
||||
}
|
||||
|
||||
strlcpy(sExceptionMessage, message, sizeof(sExceptionMessage));
|
||||
sExceptionPosition = position;
|
||||
|
||||
longjmp(sJumpBuffers[sNextJumpBufferIndex - 1], 1);
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - temporary storage
|
||||
|
||||
|
||||
static void*
|
||||
allocate_temp_storage(size_t size)
|
||||
{
|
||||
// 8 byte align
|
||||
size = (size + 7) & ~7;
|
||||
|
||||
if (sTemporaryStorageUsed + size > kTemporaryStorageSize) {
|
||||
parse_exception("out of temporary storage for command execution", -1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void* buffer = sTemporaryStorage + sTemporaryStorageUsed;
|
||||
sTemporaryStorageUsed += size;
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
free_temp_storage(void* _buffer)
|
||||
{
|
||||
uint8* buffer = (uint8*)_buffer;
|
||||
|
||||
if (buffer == NULL)
|
||||
return;
|
||||
|
||||
// must be freed in the reverse allocation order
|
||||
if (buffer < sTemporaryStorage
|
||||
|| buffer > sTemporaryStorage + sTemporaryStorageUsed) {
|
||||
panic("Invalid pointer passed to free_temp_storage(): %p, temp "
|
||||
"storage base: %p", buffer, sTemporaryStorage);
|
||||
return;
|
||||
}
|
||||
|
||||
sTemporaryStorageUsed = buffer - sTemporaryStorage;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - Tokenizer
|
||||
|
||||
|
||||
class Tokenizer {
|
||||
public:
|
||||
Tokenizer(const char* string)
|
||||
: fCommandMode(false)
|
||||
{
|
||||
SetTo(string);
|
||||
}
|
||||
|
||||
void SetTo(const char* string)
|
||||
{
|
||||
fString = fCurrentChar = string;
|
||||
fCurrentToken.Unset();
|
||||
fReuseToken = false;
|
||||
}
|
||||
|
||||
void SetPosition(int32 position)
|
||||
{
|
||||
fCurrentChar = fString + position;
|
||||
fCurrentToken.Unset();
|
||||
fReuseToken = false;
|
||||
}
|
||||
|
||||
void SetCommandMode(bool commandMode)
|
||||
{
|
||||
fCommandMode = commandMode;
|
||||
}
|
||||
|
||||
const Token& NextToken()
|
||||
{
|
||||
if (fCurrentToken.type == TOKEN_END_OF_LINE)
|
||||
return fCurrentToken;
|
||||
|
||||
if (fReuseToken) {
|
||||
fReuseToken = false;
|
||||
return fCurrentToken;
|
||||
}
|
||||
|
||||
while (*fCurrentChar != 0 && isspace(*fCurrentChar))
|
||||
fCurrentChar++;
|
||||
|
||||
if (*fCurrentChar == 0) {
|
||||
fCurrentToken.SetTo("", 0, _CurrentPos(), TOKEN_END_OF_LINE);
|
||||
return fCurrentToken;
|
||||
}
|
||||
|
||||
return (fCommandMode ? _NextTokenCommand() : _NextTokenExpression());
|
||||
}
|
||||
|
||||
void RewindToken()
|
||||
{
|
||||
fReuseToken = true;
|
||||
}
|
||||
|
||||
private:
|
||||
const Token& _NextTokenExpression()
|
||||
{
|
||||
if (isdigit(*fCurrentChar)) {
|
||||
// number
|
||||
const char* begin = fCurrentChar++;
|
||||
|
||||
if (*fCurrentChar == 'x') {
|
||||
// hex number
|
||||
fCurrentChar++;
|
||||
while (*fCurrentChar != 0
|
||||
&& (isdigit(*fCurrentChar)
|
||||
|| strchr("abcdeABCDE", *fCurrentChar))) {
|
||||
fCurrentChar++;
|
||||
}
|
||||
|
||||
if (fCurrentChar - begin == 2)
|
||||
parse_exception("invalid hex number", begin - fString);
|
||||
|
||||
} else {
|
||||
// decimal number
|
||||
while (*fCurrentChar != 0 && isdigit(*fCurrentChar))
|
||||
fCurrentChar++;
|
||||
}
|
||||
|
||||
int32 length = fCurrentChar - begin;
|
||||
fCurrentToken.SetTo(begin, length, _CurrentPos() - length,
|
||||
TOKEN_CONSTANT);
|
||||
fCurrentToken.value = strtoull(fCurrentToken.string, NULL, 0);
|
||||
|
||||
} else if (isalpha(*fCurrentChar) || *fCurrentChar == '_') {
|
||||
// identifier
|
||||
const char* begin = fCurrentChar;
|
||||
while (*fCurrentChar != 0
|
||||
&& (isalpha(*fCurrentChar) || *fCurrentChar == '_'
|
||||
|| isdigit(*fCurrentChar))) {
|
||||
fCurrentChar++;
|
||||
}
|
||||
|
||||
int32 length = fCurrentChar - begin;
|
||||
fCurrentToken.SetTo(begin, length, _CurrentPos() - length,
|
||||
TOKEN_IDENTIFIER);
|
||||
|
||||
} else {
|
||||
const char* begin = fCurrentChar;
|
||||
char c = *fCurrentChar;
|
||||
fCurrentChar++;
|
||||
int32 flags = 0;
|
||||
|
||||
switch (c) {
|
||||
case '=':
|
||||
fCurrentChar--;
|
||||
case '+':
|
||||
case '-':
|
||||
case '*':
|
||||
case '/':
|
||||
case '%':
|
||||
if (*fCurrentChar == '=') {
|
||||
fCurrentChar++;
|
||||
flags = TOKEN_ASSIGN_FLAG;
|
||||
}
|
||||
|
||||
case '(':
|
||||
case ')':
|
||||
case '[':
|
||||
case ']':
|
||||
{
|
||||
int32 length = fCurrentChar - begin;
|
||||
fCurrentToken.SetTo(begin, length, _CurrentPos() - length,
|
||||
c | flags);
|
||||
break;
|
||||
}
|
||||
|
||||
case '"':
|
||||
{
|
||||
_QuotedString();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
_UnquotedString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fCurrentToken;
|
||||
}
|
||||
|
||||
const Token& _NextTokenCommand()
|
||||
{
|
||||
switch (*fCurrentChar) {
|
||||
case '(':
|
||||
case ')':
|
||||
case '[':
|
||||
case ']':
|
||||
fCurrentToken.SetTo(fCurrentChar, 1, _CurrentPos(),
|
||||
*fCurrentChar);
|
||||
fCurrentChar++;
|
||||
return fCurrentToken;
|
||||
case '"':
|
||||
return _QuotedString();
|
||||
|
||||
default:
|
||||
return _UnquotedString();
|
||||
}
|
||||
}
|
||||
|
||||
const Token& _QuotedString()
|
||||
{
|
||||
const char* begin = fCurrentChar++;
|
||||
int32 length = 0;
|
||||
|
||||
while (*fCurrentChar != '\0' && *fCurrentChar != '"') {
|
||||
char c = *fCurrentChar;
|
||||
fCurrentChar++;
|
||||
|
||||
if (c == '\\') {
|
||||
// an escaped char
|
||||
c = *fCurrentChar;
|
||||
fCurrentChar++;
|
||||
|
||||
if (c == '\0')
|
||||
break;
|
||||
}
|
||||
|
||||
if ((size_t)length
|
||||
>= sizeof(fCurrentToken.string) - 1) {
|
||||
parse_exception("quoted string too long", begin - fString);
|
||||
}
|
||||
|
||||
fCurrentToken.string[length++] = c;
|
||||
}
|
||||
|
||||
if (*fCurrentChar == '\0') {
|
||||
parse_exception("unexpected end of line while "
|
||||
"parsing quoted string", begin - fString);
|
||||
}
|
||||
|
||||
fCurrentChar++;
|
||||
|
||||
fCurrentToken.string[length] = '\0';
|
||||
fCurrentToken.value = 0;
|
||||
fCurrentToken.type = TOKEN_STRING;
|
||||
fCurrentToken.position = begin - fString;
|
||||
|
||||
return fCurrentToken;
|
||||
}
|
||||
|
||||
const Token& _UnquotedString()
|
||||
{
|
||||
const char* begin = fCurrentChar;
|
||||
|
||||
while (*fCurrentChar != 0 && !_IsUnquotedDelimitingChar(*fCurrentChar))
|
||||
fCurrentChar++;
|
||||
|
||||
int32 length = fCurrentChar - begin;
|
||||
fCurrentToken.SetTo(begin, length, _CurrentPos() - length,
|
||||
TOKEN_UNKNOWN);
|
||||
|
||||
return fCurrentToken;
|
||||
}
|
||||
|
||||
bool _IsUnquotedDelimitingChar(char c)
|
||||
{
|
||||
if (isspace(c))
|
||||
return true;
|
||||
|
||||
switch (c) {
|
||||
case '(':
|
||||
case ')':
|
||||
case '[':
|
||||
case ']':
|
||||
case '"':
|
||||
return true;
|
||||
|
||||
case '=':
|
||||
case '+':
|
||||
case '-':
|
||||
case '*':
|
||||
case '/':
|
||||
case '%':
|
||||
return !fCommandMode;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int32 _CurrentPos() const
|
||||
{
|
||||
return fCurrentChar - fString;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* fString;
|
||||
const char* fCurrentChar;
|
||||
Token fCurrentToken;
|
||||
bool fReuseToken;
|
||||
bool fCommandMode;
|
||||
};
|
||||
|
||||
|
||||
// #pragma mark - ExpressionParser
|
||||
|
||||
|
||||
class ExpressionParser {
|
||||
public:
|
||||
ExpressionParser();
|
||||
~ExpressionParser();
|
||||
|
||||
uint64 EvaluateExpression(
|
||||
const char* expressionString);
|
||||
uint64 EvaluateCommand(
|
||||
const char* expressionString,
|
||||
int& returnCode);
|
||||
|
||||
private:
|
||||
uint64 _ParseExpression();
|
||||
uint64 _ParseCommand(int& returnCode);
|
||||
bool _ParseArgument(int& argc, char** argv);
|
||||
void _AddArgument(int& argc, char** argv,
|
||||
const char* argument);
|
||||
uint64 _ParseSum();
|
||||
uint64 _ParseProduct();
|
||||
uint64 _ParsePower();
|
||||
uint64 _ParseUnary();
|
||||
uint64 _ParseAtom();
|
||||
|
||||
const Token& _EatToken(int32 type);
|
||||
|
||||
Tokenizer fTokenizer;
|
||||
};
|
||||
|
||||
|
||||
ExpressionParser::ExpressionParser()
|
||||
: fTokenizer("")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
ExpressionParser::~ExpressionParser()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
uint64
|
||||
ExpressionParser::EvaluateExpression(const char* expressionString)
|
||||
{
|
||||
fTokenizer.SetTo(expressionString);
|
||||
|
||||
uint64 value = _ParseExpression();
|
||||
const Token& token = fTokenizer.NextToken();
|
||||
if (token.type != TOKEN_END_OF_LINE)
|
||||
parse_exception("parse error", token.position);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
uint64
|
||||
ExpressionParser::EvaluateCommand(const char* expressionString,
|
||||
int& returnCode)
|
||||
{
|
||||
fTokenizer.SetTo(expressionString);
|
||||
|
||||
// Allowed is not only a command, but also an assignment. Either starts with
|
||||
// an identifier.
|
||||
_EatToken(TOKEN_IDENTIFIER);
|
||||
|
||||
uint64 value;
|
||||
const Token& token = fTokenizer.NextToken();
|
||||
if (token.type & TOKEN_ASSIGN_FLAG) {
|
||||
// an assignment
|
||||
fTokenizer.SetTo(expressionString);
|
||||
value = _ParseExpression();
|
||||
returnCode = 0;
|
||||
} else {
|
||||
// no assignment, so let's assume it's a command
|
||||
fTokenizer.SetTo(expressionString);
|
||||
fTokenizer.SetCommandMode(true);
|
||||
value = _ParseCommand(returnCode);
|
||||
}
|
||||
|
||||
if (token.type != TOKEN_END_OF_LINE)
|
||||
parse_exception("parse error", token.position);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
uint64
|
||||
ExpressionParser::_ParseExpression()
|
||||
{
|
||||
const Token& token = fTokenizer.NextToken();
|
||||
int32 position = token.position;
|
||||
if (token.type == TOKEN_IDENTIFIER) {
|
||||
char variable[MAX_DEBUG_VARIABLE_NAME_LEN];
|
||||
strlcpy(variable, token.string, sizeof(variable));
|
||||
|
||||
int32 assignmentType = fTokenizer.NextToken().type;
|
||||
if (assignmentType & TOKEN_ASSIGN_FLAG) {
|
||||
// an assignment
|
||||
uint64 rhs = _ParseExpression();
|
||||
|
||||
// handle the standard assignment separately -- the other kinds
|
||||
// need the variable to be defined
|
||||
if (assignmentType == TOKEN_ASSIGN) {
|
||||
if (!set_debug_variable(variable, rhs)) {
|
||||
snprintf(sTempBuffer, sizeof(sTempBuffer),
|
||||
"failed to set value for variable \"%s\"",
|
||||
variable);
|
||||
parse_exception(sTempBuffer, position);
|
||||
}
|
||||
|
||||
return rhs;
|
||||
}
|
||||
|
||||
// variable must be defined
|
||||
if (!is_debug_variable_defined(variable)) {
|
||||
snprintf(sTempBuffer, sizeof(sTempBuffer),
|
||||
"variable \"%s\" not defined in modifying assignment",
|
||||
variable);
|
||||
parse_exception(sTempBuffer, position);
|
||||
}
|
||||
|
||||
uint64 variableValue = get_debug_variable(variable, 0);
|
||||
|
||||
// check for division by zero for the respective assignment types
|
||||
if ((assignmentType == TOKEN_SLASH_ASSIGN
|
||||
|| assignmentType == TOKEN_MODULO_ASSIGN)
|
||||
&& rhs == 0) {
|
||||
parse_exception("division by zero", position);
|
||||
}
|
||||
|
||||
// compute the new variable value
|
||||
switch (assignmentType) {
|
||||
case TOKEN_PLUS_ASSIGN:
|
||||
variableValue += rhs;
|
||||
break;
|
||||
case TOKEN_MINUS_ASSIGN:
|
||||
variableValue -= rhs;
|
||||
break;
|
||||
case TOKEN_STAR_ASSIGN:
|
||||
variableValue *= rhs;
|
||||
break;
|
||||
case TOKEN_SLASH_ASSIGN:
|
||||
variableValue /= rhs;
|
||||
break;
|
||||
case TOKEN_MODULO_ASSIGN:
|
||||
variableValue %= rhs;
|
||||
break;
|
||||
default:
|
||||
fTokenizer.SetPosition(position);
|
||||
return _ParseSum();
|
||||
}
|
||||
|
||||
set_debug_variable(variable, variableValue);
|
||||
return variableValue;
|
||||
}
|
||||
}
|
||||
|
||||
// no assignment -- reset to the identifier position and parse a sum
|
||||
fTokenizer.SetPosition(position);
|
||||
return _ParseSum();
|
||||
}
|
||||
|
||||
|
||||
uint64
|
||||
ExpressionParser::_ParseCommand(int& returnCode)
|
||||
{
|
||||
fTokenizer.SetCommandMode(false);
|
||||
const Token& token = _EatToken(TOKEN_IDENTIFIER);
|
||||
fTokenizer.SetCommandMode(true);
|
||||
|
||||
bool ambiguous;
|
||||
debugger_command* command = find_debugger_command(token.string, true,
|
||||
ambiguous);
|
||||
|
||||
if (command == NULL) {
|
||||
if (ambiguous) {
|
||||
snprintf(sTempBuffer, sizeof(sTempBuffer),
|
||||
"Ambiguous command \"%s\". Use tab "
|
||||
"completion to get a list of matching commands. Enter \"help\" "
|
||||
"to get a list of all supported commands.\n", token.string);
|
||||
} else {
|
||||
snprintf(sTempBuffer, sizeof(sTempBuffer),
|
||||
"Unknown command \"%s\". Enter \"help\" to get a list of "
|
||||
"all supported commands.\n", token.string);
|
||||
}
|
||||
|
||||
parse_exception(sTempBuffer, -1);
|
||||
}
|
||||
|
||||
// allocate temporary buffer for the argument vector
|
||||
char** argv = (char**)allocate_temp_storage(
|
||||
kMaxArgumentCount * sizeof(char*));
|
||||
int argc = 0;
|
||||
argv[argc++] = (char*)command->name;
|
||||
|
||||
// get the arguments
|
||||
while (fTokenizer.NextToken().type != TOKEN_END_OF_LINE) {
|
||||
fTokenizer.RewindToken();
|
||||
if (!_ParseArgument(argc, argv))
|
||||
break;
|
||||
}
|
||||
|
||||
// invoke the command
|
||||
returnCode = invoke_debugger_command(command, argc, argv);
|
||||
|
||||
free_temp_storage(argv);
|
||||
|
||||
return get_debug_variable("_", 0);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
ExpressionParser::_ParseArgument(int& argc, char** argv)
|
||||
{
|
||||
const Token& token = fTokenizer.NextToken();
|
||||
switch (token.type) {
|
||||
case TOKEN_OPENING_PARENTHESIS:
|
||||
{
|
||||
// this starts an expression
|
||||
fTokenizer.SetCommandMode(false);
|
||||
uint64 value = _ParseExpression();
|
||||
fTokenizer.SetCommandMode(true);
|
||||
_EatToken(TOKEN_CLOSING_PARENTHESIS);
|
||||
|
||||
snprintf(sTempBuffer, sizeof(sTempBuffer), "%llu", value);
|
||||
_AddArgument(argc, argv, sTempBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
case TOKEN_OPENING_BRACKET:
|
||||
{
|
||||
// this starts a sub command
|
||||
int returnValue;
|
||||
uint64 value = _ParseCommand(returnValue);
|
||||
_EatToken(TOKEN_CLOSING_BRACKET);
|
||||
|
||||
snprintf(sTempBuffer, sizeof(sTempBuffer), "%llu", value);
|
||||
_AddArgument(argc, argv, sTempBuffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
case TOKEN_STRING:
|
||||
case TOKEN_UNKNOWN:
|
||||
_AddArgument(argc, argv, token.string);
|
||||
return true;
|
||||
|
||||
case TOKEN_CLOSING_PARENTHESIS:
|
||||
case TOKEN_CLOSING_BRACKET:
|
||||
// those don't belong to us
|
||||
fTokenizer.RewindToken();
|
||||
return false;
|
||||
|
||||
default:
|
||||
{
|
||||
snprintf(sTempBuffer, sizeof(sTempBuffer), "unexpected token "
|
||||
"\"%s\"", token.string);
|
||||
parse_exception(sTempBuffer, token.position);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ExpressionParser::_AddArgument(int& argc, char** argv, const char* argument)
|
||||
{
|
||||
if (argc == kMaxArgumentCount)
|
||||
parse_exception("too many arguments for command", 0);
|
||||
|
||||
size_t length = strlen(argument) + 1;
|
||||
char* buffer = (char*)allocate_temp_storage(length);
|
||||
memcpy(buffer, argument, length);
|
||||
|
||||
argv[argc++] = buffer;
|
||||
}
|
||||
|
||||
|
||||
uint64
|
||||
ExpressionParser::_ParseSum()
|
||||
{
|
||||
uint64 value = _ParseProduct();
|
||||
|
||||
while (true) {
|
||||
const Token& token = fTokenizer.NextToken();
|
||||
switch (token.type) {
|
||||
case TOKEN_PLUS:
|
||||
value = value + _ParseProduct();
|
||||
break;
|
||||
case TOKEN_MINUS:
|
||||
value = value - _ParseProduct();
|
||||
break;
|
||||
|
||||
default:
|
||||
fTokenizer.RewindToken();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint64
|
||||
ExpressionParser::_ParseProduct()
|
||||
{
|
||||
uint64 value = _ParseUnary();
|
||||
|
||||
while (true) {
|
||||
Token token = fTokenizer.NextToken();
|
||||
switch (token.type) {
|
||||
case TOKEN_STAR:
|
||||
value = value * _ParseUnary();
|
||||
break;
|
||||
case TOKEN_SLASH: {
|
||||
uint64 rhs = _ParseUnary();
|
||||
if (rhs == 0)
|
||||
parse_exception("division by zero", token.position);
|
||||
value = value / rhs;
|
||||
break;
|
||||
}
|
||||
case TOKEN_MODULO: {
|
||||
uint64 rhs = _ParseUnary();
|
||||
if (rhs == 0)
|
||||
parse_exception("modulo by zero", token.position);
|
||||
value = value % rhs;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
fTokenizer.RewindToken();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint64
|
||||
ExpressionParser::_ParseUnary()
|
||||
{
|
||||
const Token& token = fTokenizer.NextToken();
|
||||
if (token.type == TOKEN_END_OF_LINE)
|
||||
parse_exception("unexpected end of expression", token.position);
|
||||
|
||||
switch (token.type) {
|
||||
case TOKEN_MINUS:
|
||||
return -_ParseUnary();
|
||||
|
||||
default:
|
||||
fTokenizer.RewindToken();
|
||||
return _ParseAtom();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
uint64
|
||||
ExpressionParser::_ParseAtom()
|
||||
{
|
||||
const Token& token = fTokenizer.NextToken();
|
||||
if (token.type == TOKEN_END_OF_LINE)
|
||||
parse_exception("unexpected end of expression", token.position);
|
||||
|
||||
if (token.type == TOKEN_CONSTANT)
|
||||
return token.value;
|
||||
|
||||
if (token.type == TOKEN_IDENTIFIER) {
|
||||
if (!is_debug_variable_defined(token.string)) {
|
||||
snprintf(sTempBuffer, sizeof(sTempBuffer),
|
||||
"variable '%s' undefined", token.string);
|
||||
parse_exception(sTempBuffer, token.position);
|
||||
}
|
||||
|
||||
return get_debug_variable(token.string, 0);
|
||||
}
|
||||
|
||||
if (token.type == TOKEN_OPENING_PARENTHESIS) {
|
||||
uint64 value = _ParseExpression();
|
||||
|
||||
_EatToken(TOKEN_CLOSING_PARENTHESIS);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// it can only be a "[ command ]" expression now
|
||||
fTokenizer.RewindToken();
|
||||
|
||||
_EatToken(TOKEN_OPENING_BRACKET);
|
||||
|
||||
fTokenizer.SetCommandMode(true);
|
||||
int returnValue;
|
||||
uint64 value = _ParseCommand(returnValue);
|
||||
fTokenizer.SetCommandMode(false);
|
||||
|
||||
_EatToken(TOKEN_CLOSING_BRACKET);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
|
||||
const Token&
|
||||
ExpressionParser::_EatToken(int32 type)
|
||||
{
|
||||
const Token& token = fTokenizer.NextToken();
|
||||
if (token.type != type) {
|
||||
snprintf(sTempBuffer, sizeof(sTempBuffer), "expected token type '%c', "
|
||||
"got token '%s'", char(type & ~TOKEN_FLAGS), token.string);
|
||||
parse_exception(sTempBuffer, token.position);
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
bool
|
||||
evaluate_debug_expression(const char* expression, uint64* _result, bool silent)
|
||||
{
|
||||
if (sNextJumpBufferIndex >= kJumpBufferCount) {
|
||||
kprintf("evaluate_debug_expression(): Out of jump buffers for "
|
||||
"exception handling\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool success;
|
||||
uint64 result;
|
||||
void* temporaryStorageMark = allocate_temp_storage(0);
|
||||
// get a temporary storage mark, so we can cleanup everything that
|
||||
// is allocated during the evaluation
|
||||
|
||||
if (setjmp(sJumpBuffers[sNextJumpBufferIndex++]) == 0) {
|
||||
result = ExpressionParser().EvaluateExpression(expression);
|
||||
success = true;
|
||||
} else {
|
||||
result = 0;
|
||||
success = false;
|
||||
if (!silent) {
|
||||
if (sExceptionPosition >= 0) {
|
||||
kprintf("%s, at position: %d, in expression: %s\n",
|
||||
sExceptionMessage, sExceptionPosition, expression);
|
||||
} else
|
||||
kprintf("%s", sExceptionMessage);
|
||||
}
|
||||
}
|
||||
|
||||
sNextJumpBufferIndex--;
|
||||
|
||||
// cleanup temp allocations
|
||||
free_temp_storage(temporaryStorageMark);
|
||||
|
||||
if (success && _result != NULL)
|
||||
*_result = result;
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
evaluate_debug_command(const char* commandLine)
|
||||
{
|
||||
if (sNextJumpBufferIndex >= kJumpBufferCount) {
|
||||
kprintf("evaluate_debug_command(): Out of jump buffers for "
|
||||
"exception handling\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int returnCode = 0;
|
||||
void* temporaryStorageMark = allocate_temp_storage(0);
|
||||
// get a temporary storage mark, so we can cleanup everything that
|
||||
// is allocated during the evaluation
|
||||
|
||||
if (setjmp(sJumpBuffers[sNextJumpBufferIndex++]) == 0) {
|
||||
ExpressionParser().EvaluateCommand(commandLine, returnCode);
|
||||
} else {
|
||||
if (sExceptionPosition >= 0) {
|
||||
kprintf("%s, at position: %d, in command line: %s\n",
|
||||
sExceptionMessage, sExceptionPosition, commandLine);
|
||||
} else
|
||||
kprintf("%s", sExceptionMessage);
|
||||
}
|
||||
|
||||
sNextJumpBufferIndex--;
|
||||
|
||||
// cleanup temp allocations
|
||||
free_temp_storage(temporaryStorageMark);
|
||||
|
||||
return returnCode;
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
#include "debug_variables.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <KernelExport.h>
|
||||
|
||||
#include <debug.h>
|
||||
|
||||
|
||||
static const int kVariableCount = 64;
|
||||
static const int kTemporaryVariableCount = 32;
|
||||
static const char kTemporaryVariablePrefix = '_';
|
||||
static const char* const kCommandReturnValueVariable = "_";
|
||||
|
||||
struct Variable {
|
||||
char name[MAX_DEBUG_VARIABLE_NAME_LEN];
|
||||
uint64 value;
|
||||
|
||||
inline bool IsUsed() const
|
||||
{
|
||||
return name[0] != '\0';
|
||||
}
|
||||
|
||||
void Init(const char* variableName)
|
||||
{
|
||||
strlcpy(name, variableName, sizeof(name));
|
||||
}
|
||||
|
||||
void Uninit()
|
||||
{
|
||||
name[0] = '\0';
|
||||
}
|
||||
|
||||
inline bool HasName(const char* variableName) const
|
||||
{
|
||||
return strncmp(name, variableName, sizeof(name)) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
static Variable sVariables[kVariableCount];
|
||||
static Variable sTemporaryVariables[kTemporaryVariableCount];
|
||||
|
||||
static bool sTemporaryVariablesObsolete = false;
|
||||
|
||||
|
||||
static inline bool
|
||||
is_temporary_variable(const char* variableName)
|
||||
{
|
||||
return variableName[0] == kTemporaryVariablePrefix;
|
||||
}
|
||||
|
||||
|
||||
static Variable*
|
||||
get_variable(const char* variableName, bool create)
|
||||
{
|
||||
Variable* variables;
|
||||
int variableCount;
|
||||
|
||||
// get the variable domain (persistent or temporary)
|
||||
if (is_temporary_variable(variableName)) {
|
||||
variables = sTemporaryVariables;
|
||||
variableCount = kTemporaryVariableCount;
|
||||
} else {
|
||||
variables = sVariables;
|
||||
variableCount = kVariableCount;
|
||||
}
|
||||
|
||||
Variable* freeSlot = NULL;
|
||||
|
||||
for (int i = 0; i < variableCount; i++) {
|
||||
Variable* variable = variables + i;
|
||||
|
||||
if (!variable->IsUsed()) {
|
||||
if (freeSlot == NULL)
|
||||
freeSlot = variable;
|
||||
} else if (variable->HasName(variableName))
|
||||
return variable;
|
||||
}
|
||||
|
||||
if (create && freeSlot != NULL) {
|
||||
freeSlot->Init(variableName);
|
||||
return freeSlot;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - debugger commands
|
||||
|
||||
|
||||
static int
|
||||
cmd_unset_variable(int argc, char **argv)
|
||||
{
|
||||
static const char* usage = "usage: unset <variable>\n"
|
||||
"Unsets the given variable, if it exists.\n";
|
||||
if (argc != 2 || strcmp(argv[1], "--help") == 0) {
|
||||
kprintf(usage);
|
||||
return 0;
|
||||
}
|
||||
|
||||
remove_debug_variable(argv[2]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
cmd_variables(int argc, char **argv)
|
||||
{
|
||||
static const char* usage = "usage: vars\n"
|
||||
"Unsets the given variable, if it exists.\n";
|
||||
if (argc != 1) {
|
||||
kprintf(usage);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// persistent variables
|
||||
for (int i = 0; i < kVariableCount; i++) {
|
||||
Variable& variable = sVariables[i];
|
||||
if (variable.IsUsed()) {
|
||||
kprintf("%16s: %llu (0x%llx)\n", variable.name, variable.value,
|
||||
variable.value);
|
||||
}
|
||||
}
|
||||
|
||||
// temporary variables
|
||||
for (int i = 0; i < kTemporaryVariableCount; i++) {
|
||||
Variable& variable = sTemporaryVariables[i];
|
||||
if (variable.IsUsed()) {
|
||||
kprintf("%16s: %llu (0x%llx)\n", variable.name, variable.value,
|
||||
variable.value);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - kernel public functions
|
||||
|
||||
|
||||
bool
|
||||
is_debug_variable_defined(const char* variableName)
|
||||
{
|
||||
return get_variable(variableName, false) != NULL;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
set_debug_variable(const char* variableName, uint64 value)
|
||||
{
|
||||
if (sTemporaryVariablesObsolete && is_temporary_variable(variableName)
|
||||
&& strcmp(variableName, kCommandReturnValueVariable) != 0) {
|
||||
remove_all_temporary_debug_variables();
|
||||
}
|
||||
|
||||
|
||||
if (Variable* variable = get_variable(variableName, true)) {
|
||||
variable->value = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
uint64
|
||||
get_debug_variable(const char* variableName, uint64 defaultValue)
|
||||
{
|
||||
if (Variable* variable = get_variable(variableName, false))
|
||||
return variable->value;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
remove_debug_variable(const char* variableName)
|
||||
{
|
||||
// We don't allow explicit removal of inidividual temporary variables.
|
||||
// This speeds up removing them all.
|
||||
if (is_temporary_variable(variableName))
|
||||
return false;
|
||||
|
||||
if (Variable* variable = get_variable(variableName, false)) {
|
||||
variable->Uninit();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
remove_all_temporary_debug_variables()
|
||||
{
|
||||
for (int i = 0; i < kTemporaryVariableCount; i++) {
|
||||
Variable& variable = sTemporaryVariables[i];
|
||||
if (!variable.IsUsed())
|
||||
return;
|
||||
|
||||
variable.Uninit();
|
||||
}
|
||||
|
||||
// always keep the return value variable defined
|
||||
set_debug_variable(kCommandReturnValueVariable, 0);
|
||||
|
||||
sTemporaryVariablesObsolete = false;
|
||||
}
|
||||
|
||||
|
||||
/*! Schedules all temporary variables for removal.
|
||||
They will be removed, the next time set_debug_variable() is invoked
|
||||
for a temporary variable other than the command return value variable.
|
||||
*/
|
||||
void
|
||||
mark_temporary_debug_variables_obsolete()
|
||||
{
|
||||
sTemporaryVariablesObsolete = true;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
debug_variables_init()
|
||||
{
|
||||
// always keep the return value variable defined
|
||||
set_debug_variable(kCommandReturnValueVariable, 0);
|
||||
|
||||
add_debugger_command("unset", &cmd_unset_variable,
|
||||
"Unsets the given variable");
|
||||
add_debugger_command("vars", &cmd_variables,
|
||||
"Lists all defined variables with their values");
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef _KERNEL_DEBUG_VARIABLES_H
|
||||
#define _KERNEL_DEBUG_VARIABLES_H
|
||||
|
||||
#include <SupportDefs.h>
|
||||
|
||||
|
||||
#define MAX_DEBUG_VARIABLE_NAME_LEN 24
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void mark_temporary_debug_variables_obsolete();
|
||||
|
||||
void debug_variables_init();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
|
||||
#endif // _KERNEL_DEBUG_VARIABLES_H
|
||||
Reference in New Issue
Block a user