Improvements to the help command and general debugger usability.

This commit is contained in:
Lior Halphon 2016-08-13 22:52:41 +03:00
parent e79ddee705
commit f9236d12bf
2 changed files with 137 additions and 93 deletions

View File

@ -49,30 +49,30 @@
<window title="Debug Console" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="21F-Ah-yHX" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" resizable="YES" utility="YES" HUD="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="272" y="172" width="320" height="240"/>
<rect key="contentRect" x="272" y="172" width="600" height="400"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<value key="minSize" type="size" width="320" height="240"/>
<value key="minSize" type="size" width="600" height="400"/>
<view key="contentView" id="dCP-E5-7Fi">
<rect key="frame" x="0.0" y="0.0" width="320" height="240"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="400"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView borderType="none" horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" id="oTo-zx-o6N">
<rect key="frame" x="0.0" y="25" width="320" height="216"/>
<rect key="frame" x="0.0" y="25" width="600" height="376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="EQe-Ad-L7S">
<rect key="frame" x="0.0" y="0.0" width="320" height="216"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView editable="NO" drawsBackground="NO" importsGraphics="NO" richText="NO" baseWritingDirection="leftToRight" findStyle="bar" verticallyResizable="YES" allowsNonContiguousLayout="YES" id="doS-dM-hnl">
<rect key="frame" x="0.0" y="0.0" width="320" height="216"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="376"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="textColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="0.14901960784313725" green="0.14901960784313725" blue="0.14901960784313725" alpha="1" colorSpace="calibratedRGB"/>
<size key="minSize" width="320" height="216"/>
<size key="maxSize" width="463" height="10000000"/>
<size key="minSize" width="600" height="376"/>
<size key="maxSize" width="600" height="10000000"/>
<color key="insertionPointColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="320" height="216"/>
<size key="maxSize" width="463" height="10000000"/>
<size key="minSize" width="600" height="376"/>
<size key="maxSize" width="600" height="10000000"/>
<allowedInputSourceLocales>
<string>NSAllRomanInputSourcesLocaleIdentifier</string>
</allowedInputSourceLocales>
@ -85,12 +85,12 @@
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="NO" id="cwi-6E-rbh">
<rect key="frame" x="304" y="0.0" width="16" height="216"/>
<rect key="frame" x="584" y="0.0" width="16" height="376"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField focusRingType="none" verticalHuggingPriority="750" mirrorLayoutDirectionWhenInternationalizing="never" id="l22-S8-uji">
<rect key="frame" x="0.0" y="0.0" width="320" height="24"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="24"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" state="on" focusRingType="none" id="p3j-nS-44f">
<font key="font" metaFont="fixedUser" size="11"/>
@ -105,7 +105,7 @@
</connections>
</textField>
<box verticalHuggingPriority="750" title="Box" boxType="separator" titlePosition="noTitle" id="960-dL-7ZY">
<rect key="frame" x="0.0" y="23" width="320" height="5"/>
<rect key="frame" x="0.0" y="23" width="600" height="5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxY="YES"/>
<color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>

View File

@ -588,13 +588,15 @@ value_t debugger_evaluate(GB_gameboy_t *gb, const char *string,
return VALUE_16(literal);
}
typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments);
struct debugger_command_s;
typedef bool debugger_command_imp_t(GB_gameboy_t *gb, char *arguments, const struct debugger_command_s *command);
typedef struct {
typedef struct debugger_command_s {
const char *command;
uint8_t min_length;
debugger_command_imp_t *implementation;
const char *help_string; // Null if should not appear in help
const char *arguments_format; // For usage message
} debugger_command_t;
static const char *lstrip(const char *str)
@ -611,12 +613,22 @@ GB_log(gb, "Program is running. \n"); \
return false; \
}
static bool cont(GB_gameboy_t *gb, char *arguments)
static void print_usage(GB_gameboy_t *gb, const debugger_command_t *command)
{
if (command->arguments_format) {
GB_log(gb, "Usage: %s %s\n", command->command, command->arguments_format);
}
else {
GB_log(gb, "Usage: %s\n", command->command);
}
}
static bool cont(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
STOPPED_ONLY
if (strlen(lstrip(arguments))) {
GB_log(gb, "Usage: continue\n");
print_usage(gb, command);
return true;
}
@ -624,12 +636,12 @@ static bool cont(GB_gameboy_t *gb, char *arguments)
return false;
}
static bool next(GB_gameboy_t *gb, char *arguments)
static bool next(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
STOPPED_ONLY
if (strlen(lstrip(arguments))) {
GB_log(gb, "Usage: next\n");
print_usage(gb, command);
return true;
}
@ -639,24 +651,24 @@ static bool next(GB_gameboy_t *gb, char *arguments)
return false;
}
static bool step(GB_gameboy_t *gb, char *arguments)
static bool step(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
STOPPED_ONLY
if (strlen(lstrip(arguments))) {
GB_log(gb, "Usage: step\n");
print_usage(gb, command);
return true;
}
return false;
}
static bool finish(GB_gameboy_t *gb, char *arguments)
static bool finish(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
STOPPED_ONLY
if (strlen(lstrip(arguments))) {
GB_log(gb, "Usage: finish\n");
print_usage(gb, command);
return true;
}
@ -666,12 +678,12 @@ static bool finish(GB_gameboy_t *gb, char *arguments)
return false;
}
static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments)
static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
STOPPED_ONLY
if (strlen(lstrip(arguments))) {
GB_log(gb, "Usage: sld\n");
print_usage(gb, command);
return true;
}
@ -681,10 +693,10 @@ static bool stack_leak_detection(GB_gameboy_t *gb, char *arguments)
return false;
}
static bool registers(GB_gameboy_t *gb, char *arguments)
static bool registers(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
if (strlen(lstrip(arguments))) {
GB_log(gb, "Usage: registers\n");
print_usage(gb, command);
return true;
}
@ -723,10 +735,10 @@ static uint16_t find_breakpoint(GB_gameboy_t *gb, value_t addr)
return (uint16_t) min;
}
static bool breakpoint(GB_gameboy_t *gb, char *arguments)
static bool breakpoint(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
GB_log(gb, "Usage: breakpoint <expression>[ if <condition expression>]\n");
print_usage(gb, command);
return true;
}
@ -788,21 +800,17 @@ static bool breakpoint(GB_gameboy_t *gb, char *arguments)
return true;
}
static bool delete(GB_gameboy_t *gb, char *arguments)
static bool delete(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
GB_log(gb, "Delete all breakpoints? ");
char *answer = gb->input_callback(gb);
if (answer[0] == 'Y' || answer[0] == 'y') {
for (unsigned i = gb->n_breakpoints; i--;) {
if (gb->breakpoints[i].condition) {
free(gb->breakpoints[i].condition);
}
for (unsigned i = gb->n_breakpoints; i--;) {
if (gb->breakpoints[i].condition) {
free(gb->breakpoints[i].condition);
}
free(gb->breakpoints);
gb->breakpoints = NULL;
gb->n_breakpoints = 0;
}
free(gb->breakpoints);
gb->breakpoints = NULL;
gb->n_breakpoints = 0;
return true;
}
@ -852,11 +860,11 @@ static uint16_t find_watchpoint(GB_gameboy_t *gb, value_t addr)
return (uint16_t) min;
}
static bool watch(GB_gameboy_t *gb, char *arguments)
static bool watch(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
print_usage:
GB_log(gb, "Usage: watch (r|w|rw) <expression>[ if <condition expression>]\n");
print_usage(gb, command);
return true;
}
@ -944,21 +952,17 @@ print_usage:
return true;
}
static bool unwatch(GB_gameboy_t *gb, char *arguments)
static bool unwatch(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
GB_log(gb, "Delete all watchpoints? ");
char *answer = gb->input_callback(gb);
if (answer[0] == 'Y' || answer[0] == 'y') {
for (unsigned i = gb->n_watchpoints; i--;) {
if (gb->watchpoints[i].condition) {
free(gb->watchpoints[i].condition);
}
for (unsigned i = gb->n_watchpoints; i--;) {
if (gb->watchpoints[i].condition) {
free(gb->watchpoints[i].condition);
}
free(gb->watchpoints);
gb->watchpoints = NULL;
gb->n_watchpoints = 0;
}
free(gb->watchpoints);
gb->watchpoints = NULL;
gb->n_watchpoints = 0;
return true;
}
@ -986,10 +990,10 @@ static bool unwatch(GB_gameboy_t *gb, char *arguments)
return true;
}
static bool list(GB_gameboy_t *gb, char *arguments)
static bool list(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
if (strlen(lstrip(arguments))) {
GB_log(gb, "Usage: list\n");
print_usage(gb, command);
return true;
}
@ -1069,10 +1073,10 @@ static bool should_break(GB_gameboy_t *gb, uint16_t addr)
return _should_break(gb, full_addr);
}
static bool print(GB_gameboy_t *gb, char *arguments)
static bool print(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
GB_log(gb, "Usage: print <expression>\n");
print_usage(gb, command);
return true;
}
@ -1084,10 +1088,10 @@ static bool print(GB_gameboy_t *gb, char *arguments)
return true;
}
static bool examine(GB_gameboy_t *gb, char *arguments)
static bool examine(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
if (strlen(lstrip(arguments)) == 0) {
GB_log(gb, "Usage: examine <expression>\n");
print_usage(gb, command);
return true;
}
@ -1118,17 +1122,17 @@ static bool examine(GB_gameboy_t *gb, char *arguments)
return true;
}
static bool mbc(GB_gameboy_t *gb, char *arguments)
static bool mbc(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
if (strlen(lstrip(arguments))) {
GB_log(gb, "Usage: mbc\n");
print_usage(gb, command);
return true;
}
const GB_cartridge_t *cartridge = gb->cartridge_type;
if (cartridge->has_ram) {
GB_log(gb, "Cartrdige includes%s RAM: %x\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size);
GB_log(gb, "Cartrdige includes%s RAM: $%x bytes\n", cartridge->has_battery? " battery-backed": "", gb->mbc_ram_size);
}
else {
GB_log(gb, "No cartridge RAM\n");
@ -1166,10 +1170,10 @@ static bool mbc(GB_gameboy_t *gb, char *arguments)
return true;
}
static bool backtrace(GB_gameboy_t *gb, char *arguments)
static bool backtrace(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command)
{
if (strlen(lstrip(arguments))) {
GB_log(gb, "Usage: backtrace\n");
print_usage(gb, command);
return true;
}
@ -1181,51 +1185,50 @@ static bool backtrace(GB_gameboy_t *gb, char *arguments)
return true;
}
static bool help(GB_gameboy_t *gb, char *arguments);
static bool help(GB_gameboy_t *gb, char *arguments, const debugger_command_t *command);
#define HELP_NEWLINE "\n "
/* Commands without implementations are aliases of the previous non-alias commands */
static const debugger_command_t commands[] = {
{"continue", 1, cont, "Continue running until next stop"},
{"next", 1, next, "Run the next instruction, skipping over function calls"},
{"step", 1, step, "Run the next instruction, stepping into function calls"},
{"finish", 1, finish, "Run until the current function returns"},
{"backtrace", 2, backtrace, "Display the current call stack"},
{"bt", 2, backtrace, NULL},
{"sld", 3, stack_leak_detection, "Run until the current function returns, or a stack leak is detected (Experimental)"},
{"bt", 2, }, /* Alias */
{"sld", 3, stack_leak_detection, "Like finish, but stops if a stack leak is detected. (Experimental)"},
{"registers", 1, registers, "Print values of processor registers and other important registers"},
{"cartridge", 2, mbc, "Displays information about the MBC and cartridge"},
{"mbc", 3, mbc, NULL},
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression. Can also modify the condition of existing breakpoints."},
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints"},
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression. Can also modify the condition and type of existing watchpoints."},
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints"},
{"mbc", 3, }, /* Alias */
{"breakpoint", 1, breakpoint, "Add a new breakpoint at the specified address/expression." HELP_NEWLINE
"Can also modify the condition of existing breakpoints.",
"<expression>[ if <condition expression>]"},
{"delete", 2, delete, "Delete a breakpoint by its address, or all breakpoints", "[<expression>]"},
{"watch", 1, watch, "Add a new watchpoint at the specified address/expression." HELP_NEWLINE
"Can also modify the condition and type of existing watchpoints.",
" (r|w|rw) <expression>[ if <condition expression>]"},
{"unwatch", 3, unwatch, "Delete a watchpoint by its address, or all watchpoints", "[<expression>]"},
{"list", 1, list, "List all set breakpoints and watchpoints"},
{"print", 1, print, "Evaluate and print an expression"},
{"eval", 2, print, NULL},
{"examine", 2, examine, "Examine values at address"},
{"x", 1, examine, NULL},
{"print", 1, print, "Evaluate and print an expression", "<expression>"},
{"eval", 2, }, /* Alias */
{"examine", 2, examine, "Examine values at address", "<expression>"},
{"x", 1, }, /* Alias */
{"help", 1, help, "List available commands"},
{"help", 1, help, "List available commands or show help for the specified command", "[<command>]"},
{NULL,}, /* Null terminator */
};
static bool help(GB_gameboy_t *gb, char *arguments)
{
/* Todo: command specific help */
const debugger_command_t *command = commands;
for (size_t i = sizeof(commands) / sizeof(*command); i--; command++) {
if (command->help_string) {
GB_attributed_log(gb, GB_LOG_BOLD, "%s", command->command);
GB_log(gb, ": %s\n", command->help_string);
}
}
return true;
}
static const debugger_command_t *find_command(const char *string)
{
const debugger_command_t *command = commands;
size_t length = strlen(string);
for (size_t i = sizeof(commands) / sizeof(*command); i--; command++) {
for (const debugger_command_t *command = commands; command->command; command++) {
if (command->min_length > length) continue;
if (memcmp(command->command, string, length) == 0) { /* Is a substring? */
/* Aliases */
while (!command->implementation) {
command--;
}
return command;
}
}
@ -1233,6 +1236,47 @@ static const debugger_command_t *find_command(const char *string)
return NULL;
}
static void print_command_shortcut(GB_gameboy_t *gb, const debugger_command_t *command)
{
GB_attributed_log(gb, GB_LOG_BOLD | GB_LOG_UNDERLINE, "%.*s", command->min_length, command->command);
GB_attributed_log(gb, GB_LOG_BOLD , "%s", command->command + command->min_length);
}
static void print_command_description(GB_gameboy_t *gb, const debugger_command_t *command)
{
print_command_shortcut(gb, command);
GB_log(gb, ": ");
GB_log(gb, (const char *)&" %s\n" + strlen(command->command), command->help_string);
}
static bool help(GB_gameboy_t *gb, char *arguments, const debugger_command_t *ignored)
{
const debugger_command_t *command = find_command(arguments);
if (command) {
print_command_description(gb, command);
GB_log(gb, "\n");
print_usage(gb, command);
command++;
if (command->command && !command->implementation) { /* Command has aliases*/
GB_log(gb, "\nAliases: ");
do {
print_command_shortcut(gb, command);
GB_log(gb, " ");
command++;
} while (command->command && !command->implementation);
GB_log(gb, "\n");
}
return true;
}
for (const debugger_command_t *command = commands; command->command; command++) {
if (command->help_string) {
print_command_description(gb, command);
}
}
return true;
}
void GB_debugger_call_hook(GB_gameboy_t *gb, uint16_t call_addr)
{
/* Called just after the CPU calls a function/enters an interrupt/etc... */
@ -1409,7 +1453,7 @@ bool GB_debugger_do_command(GB_gameboy_t *gb, char *input)
const debugger_command_t *command = find_command(command_string);
if (command) {
return command->implementation(gb, arguments);
return command->implementation(gb, arguments, command);
}
else {
GB_log(gb, "%s: no such command.\n", command_string);