* 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:
Ingo Weinhold
2008-01-16 00:01:20 +00:00
parent 5d24ef2e15
commit 3b4fa1664e
8 changed files with 1537 additions and 279 deletions
+12 -1
View File
@@ -59,7 +59,18 @@ extern void debug_stop_screen_debug_output(void);
extern void dprintf_no_syslog(const char *format, ...) extern void dprintf_no_syslog(const char *format, ...)
__attribute__ ((format (__printf__, 1, 2))); __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 #ifdef __cplusplus
} }
+3
View File
@@ -5,6 +5,9 @@ UsePrivateHeaders [ FDirName kernel debug ] syslog_daemon ;
KernelMergeObject kernel_debug.o : KernelMergeObject kernel_debug.o :
blue_screen.cpp blue_screen.cpp
debug.cpp debug.cpp
debug_commands.cpp
debug_parser.cpp
debug_variables.cpp
frame_buffer_console.cpp frame_buffer_console.cpp
gdb.c gdb.c
tracing.cpp tracing.cpp
+65 -278
View File
@@ -28,26 +28,20 @@
#include <syslog_daemon.h> #include <syslog_daemon.h>
#include <ctype.h> #include <ctype.h>
#include <setjmp.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <syslog.h> #include <syslog.h>
#include "debug_commands.h"
#include "debug_variables.h"
static const char* const kKDLPrompt = "kdebug> "; static const char* const kKDLPrompt = "kdebug> ";
extern "C" int kgets(char *buffer, int length); 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]; int dbg_register_file[B_MAX_CPU_COUNT][14];
/* XXXmpetit -- must be made generic */ /* XXXmpetit -- must be made generic */
@@ -66,11 +60,6 @@ static struct syslog_message *sSyslogMessage;
static struct ring_buffer *sSyslogBuffer; static struct ring_buffer *sSyslogBuffer;
static bool sSyslogDropped = false; static bool sSyslogDropped = false;
static struct debugger_command *sCommands;
static jmp_buf sInvokeCommandEnv;
static bool sInvokeCommandDirectly = false;
static const char* sCurrentKernelDebuggerMessage; static const char* sCurrentKernelDebuggerMessage;
#define SYSLOG_BUFFER_SIZE 65536 #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)) #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 static void
kputchar(char c) kputchar(char c)
{ {
@@ -256,14 +199,14 @@ public:
tmpChar = *firstSpace; tmpChar = *firstSpace;
*firstSpace = '\0'; *firstSpace = '\0';
bool ambiguous; bool ambiguous;
debugger_command* command = find_command(buffer, true, ambiguous); debugger_command* command = find_debugger_command(buffer, true, ambiguous);
*firstSpace = tmpChar; *firstSpace = tmpChar;
if (command != NULL) { if (command != NULL) {
kputchar('\n'); kputchar('\n');
char* args[3] = { NULL, "--help", NULL }; char* args[3] = { NULL, "--help", NULL };
invoke_command(command, 2, args); invoke_debugger_command(command, 2, args);
} else { } else {
if (ambiguous) if (ambiguous)
kprintf("\nambiguous command\n"); kprintf("\nambiguous command\n");
@@ -281,7 +224,7 @@ public:
debugger_command* command = NULL; debugger_command* command = NULL;
int32 longestCommonPrefix = 0; int32 longestCommonPrefix = 0;
const char* previousCommandName = NULL; const char* previousCommandName = NULL;
while ((command = next_command(command, buffer, position)) while ((command = next_debugger_command(command, buffer, position))
!= NULL) { != NULL) {
count++; count++;
int32 nameLength = strlen(command->name); int32 nameLength = strlen(command->name);
@@ -312,7 +255,7 @@ public:
reprintLine = true; reprintLine = true;
} else if (count == 1) { } else if (count == 1) {
// exactly one completion // exactly one completion
command = next_command(NULL, buffer, position); command = next_debugger_command(NULL, buffer, position);
// check for sufficient space in the buffer // check for sufficient space in the buffer
int32 neededSpace = longestName - position + 1; int32 neededSpace = longestName - position + 1;
@@ -345,7 +288,7 @@ public:
int columns = 80 / (longestName + 2); int columns = 80 / (longestName + 2);
debugger_command* command = NULL; debugger_command* command = NULL;
int column = 0; int column = 0;
while ((command = next_command(command, buffer, position)) while ((command = next_debugger_command(command, buffer, position))
!= NULL) { != NULL) {
// spacing // spacing
if (column > 0 && column % columns == 0) 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 static void
kernel_debugger_loop(void) kernel_debugger_loop(void)
{ {
@@ -652,44 +500,43 @@ kernel_debugger_loop(void)
kprintf("Welcome to Kernel Debugging Land...\n"); kprintf("Welcome to Kernel Debugging Land...\n");
kprintf("Running on CPU %ld\n", sDebuggerOnCPU); kprintf("Running on CPU %ld\n", sDebuggerOnCPU);
for (;;) { int32 continuableLine = -1;
struct debugger_command *cmd = NULL; // Index of the previous command line, if the command returned
int argc; // B_KDEBUG_CONT, i.e. asked to be repeatable, -1 otherwise.
for (;;) {
CommandLineEditingHelper editingHelper; CommandLineEditingHelper editingHelper;
kprintf(kKDLPrompt); kprintf(kKDLPrompt);
read_line(sLineBuffer[sCurrentLine], LINE_BUFFER_SIZE, &editingHelper); char* line = sLineBuffer[sCurrentLine];
parse_line(sLineBuffer[sCurrentLine], sArguments, &argc, MAX_ARGS); read_line(line, LINE_BUFFER_SIZE, &editingHelper);
// We support calling last executed command again if // check, if the line is empty or whitespace only
// B_KDEDUG_CONT was returned last time, so cmd != NULL bool whiteSpaceOnly = true;
if (argc <= 0 && cmd == NULL) for (int i = 0 ; line[i] != '\0'; i++) {
continue; 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(); sDebuggerOnCPU = smp_get_current_cpu();
bool ambiguous; int rc = evaluate_debug_command(line);
if (argc > 0)
cmd = find_command(sArguments[0], true, ambiguous);
if (cmd == NULL) { if (rc == B_KDEBUG_QUIT)
if (ambiguous) { break; // okay, exit now.
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) // If the command is continuable, remember the current line index.
break; // okay, exit now. continuableLine = (rc == B_KDEBUG_CONT ? sCurrentLine : -1);
if (rc != B_KDEBUG_CONT)
cmd = NULL; // forget last command executed...
}
if (++sCurrentLine >= HISTORY_SIZE) if (++sCurrentLine >= HISTORY_SIZE)
sCurrentLine = 0; sCurrentLine = 0;
@@ -725,7 +572,7 @@ cmd_help(int argc, char **argv)
bool ambiguous; bool ambiguous;
if (argc > 1) { if (argc > 1) {
specified = find_command(argv[1], false, ambiguous); specified = find_debugger_command(argv[1], false, ambiguous);
if (specified == NULL) { if (specified == NULL) {
start = argv[1]; start = argv[1];
startLength = strlen(start); startLength = strlen(start);
@@ -740,7 +587,8 @@ cmd_help(int argc, char **argv)
else else
kprintf("debugger commands:\n"); 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) if (specified && command->func != specified->func)
continue; continue;
if (start != NULL && strncmp(start, command->name, startLength)) if (start != NULL && strncmp(start, command->name, startLength))
@@ -771,6 +619,25 @@ cmd_dump_kdl_message(int argc, char **argv)
return 0; 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 static status_t
syslog_sender(void *data) 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("continue", &cmd_continue, "Leave kernel debugger");
add_debugger_command("message", &cmd_dump_kdl_message, add_debugger_command("message", &cmd_dump_kdl_message,
"Reprint the message printed when entering KDL"); "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); frame_buffer_console_init(args);
arch_debug_console_init_settings(args); arch_debug_console_init_settings(args);
tracing_init(); tracing_init();
@@ -1089,75 +959,11 @@ debug_init_post_modules(struct kernel_args *args)
// #pragma mark - public API // #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 uint32
parse_expression(const char *expression) parse_expression(const char *expression)
{ {
// TODO: Implement expression parser (cf. BeBook). uint64 result;
return strtoul(expression, NULL, 0); return (evaluate_debug_expression(expression, &result, true) ? result : 0);
} }
@@ -1207,27 +1013,8 @@ kernel_debugger(const char *message)
sCurrentKernelDebuggerMessage = message; sCurrentKernelDebuggerMessage = message;
// bubble sort the commands // sort the commands
debugger_command* stopCommand = NULL; sort_debugger_commands();
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;
}
}
kernel_debugger_loop(); kernel_debugger_loop();
+225
View File
@@ -0,0 +1,225 @@
/*
* Copyright 2008, Ingo Weinhold, [email protected]
* Copyright 2002-2007, 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 <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;
}
+38
View File
@@ -0,0 +1,38 @@
/*
* Copyright 2008, Ingo Weinhold, [email protected]
* 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
+929
View File
@@ -0,0 +1,929 @@
/*
* Copyright 2008, Ingo Weinhold, [email protected]
* Copyright 2006, Stephan Aßmus, [email protected]
* 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;
}
+239
View File
@@ -0,0 +1,239 @@
/*
* Copyright 2008, Ingo Weinhold, [email protected]
* 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");
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright 2008, Ingo Weinhold, [email protected]
* 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