mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-09-28 01:48:04 +03:00
781 lines
19 KiB
C
781 lines
19 KiB
C
/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define VM_PRIVATE
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <setjmp.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
|
|
#include "system4/dasm.h"
|
|
#include "system4/hashtable.h"
|
|
#include "system4/string.h"
|
|
#include "system4/utfsjis.h"
|
|
|
|
#include "vm.h"
|
|
#include "vm/heap.h"
|
|
#include "vm/page.h"
|
|
|
|
#include "debugger.h"
|
|
#include "little_endian.h"
|
|
#include "xsystem4.h"
|
|
|
|
bool dbg_enabled = true;
|
|
bool dbg_start_in_debugger = false;
|
|
unsigned dbg_current_frame = 0;
|
|
static jmp_buf dbg_continuation;
|
|
|
|
enum {
|
|
DBG_CONTINUE = 1,
|
|
DBG_QUIT = 2,
|
|
};
|
|
|
|
void dbg_continue(void)
|
|
{
|
|
longjmp(dbg_continuation, DBG_CONTINUE);
|
|
}
|
|
|
|
void dbg_quit(void)
|
|
{
|
|
longjmp(dbg_continuation, DBG_QUIT);
|
|
}
|
|
|
|
void dbg_start(void(*fun)(void*), void *data)
|
|
{
|
|
dbg_current_frame = 0;
|
|
switch (setjmp(dbg_continuation)) {
|
|
case 0:
|
|
break;
|
|
case DBG_CONTINUE:
|
|
return;
|
|
case DBG_QUIT:
|
|
vm_exit(0);
|
|
default:
|
|
ERROR("setjmp returned unexpected value");
|
|
}
|
|
fun(data);
|
|
}
|
|
|
|
static void _dbg_repl(void *_)
|
|
{
|
|
dbg_cmd_repl();
|
|
}
|
|
|
|
void dbg_repl(void)
|
|
{
|
|
if (!dbg_enabled)
|
|
return;
|
|
dbg_start(_dbg_repl, NULL);
|
|
}
|
|
|
|
void dbg_init(void)
|
|
{
|
|
dbg_cmd_init();
|
|
#ifdef HAVE_SCHEME
|
|
dbg_scm_init();
|
|
#endif
|
|
}
|
|
|
|
void dbg_fini(void)
|
|
{
|
|
#ifdef HAVE_SCHEME
|
|
dbg_scm_fini();
|
|
#endif
|
|
}
|
|
|
|
static struct hash_table *bp_table = NULL;
|
|
|
|
static void add_breakpoint(uint32_t addr, struct breakpoint *bp)
|
|
{
|
|
if (!bp_table)
|
|
bp_table = ht_create(64);
|
|
|
|
struct ht_slot *slot = ht_put_int(bp_table, addr, NULL);
|
|
if (slot->value) {
|
|
WARNING("Overwriting breakpoint at %0x%08x", addr);
|
|
free(slot->value);
|
|
}
|
|
slot->value = bp;
|
|
}
|
|
|
|
static void delete_breakpoint(uint32_t addr, struct breakpoint *bp)
|
|
{
|
|
// restore opcode
|
|
LittleEndian_putW(ain->code, addr, bp->restore_op);
|
|
|
|
// remove from hash table
|
|
struct ht_slot *slot = ht_put_int(bp_table, addr, NULL);
|
|
assert(slot->value == bp);
|
|
slot->value = NULL;
|
|
|
|
free(bp->message);
|
|
free(bp);
|
|
}
|
|
|
|
static struct breakpoint *get_breakpoint(uint32_t addr)
|
|
{
|
|
if (!bp_table)
|
|
return NULL;
|
|
return ht_get_int(bp_table, addr, NULL);
|
|
}
|
|
|
|
bool dbg_set_function_breakpoint(const char *_name, void(*cb)(struct breakpoint*), void *data)
|
|
{
|
|
char *name = utf2sjis(_name, 0);
|
|
int fno = ain_get_function(ain, name);
|
|
free(name);
|
|
|
|
if (fno < 0) {
|
|
DBG_ERROR("No function with name '%s'", display_sjis0(_name));
|
|
return false;
|
|
}
|
|
|
|
struct ain_function *f = &ain->functions[fno];
|
|
struct breakpoint *bp = xcalloc(1, sizeof(struct breakpoint));
|
|
bp->restore_op = LittleEndian_getW(ain->code, f->address);
|
|
bp->cb = cb;
|
|
bp->data = data;
|
|
bp->message = xmalloc(512);
|
|
snprintf(bp->message, 511, "Hit breakpoint at function '%s' (0x%08x)",
|
|
display_utf0(_name), f->address);
|
|
LittleEndian_putW(ain->code, f->address, BREAKPOINT | bp->restore_op);
|
|
add_breakpoint(f->address, bp);
|
|
|
|
printf("Set breakpoint at function '%s' (0x%08x)\n", display_utf0(_name), f->address);
|
|
return true;
|
|
}
|
|
|
|
bool dbg_set_address_breakpoint(uint32_t address, void(*cb)(struct breakpoint*), void *data)
|
|
{
|
|
if (address & 1 || address >= ain->code_size - 2) {
|
|
DBG_ERROR("Invalid address: 0x%08x", address);
|
|
return false;
|
|
}
|
|
|
|
// XXX: this is not very robust; some illegal addresses will slip through
|
|
// if the data looks like a valid opcode
|
|
enum opcode op = LittleEndian_getW(ain->code, address);
|
|
if (op < 0 || op >= NR_OPCODES) {
|
|
DBG_ERROR("Invalid address: 0x%08x", address);
|
|
return false;
|
|
}
|
|
|
|
struct breakpoint *bp = xcalloc(1, sizeof(struct breakpoint));
|
|
bp->restore_op = op;
|
|
bp->cb = cb;
|
|
bp->data = data;
|
|
bp->message = xmalloc(512);
|
|
snprintf(bp->message, 511, "Hit breakpoint at 0x%08x", address);
|
|
LittleEndian_putW(ain->code, address, BREAKPOINT | bp->restore_op);
|
|
add_breakpoint(address, bp);
|
|
|
|
printf("Set breakpoint at 0x%08x\n", address);
|
|
return true;
|
|
}
|
|
|
|
static void dbg_step_breakpoint_cb(struct breakpoint *bp)
|
|
{
|
|
// XXX: mutually recursive functions could trigger breakpoint early.
|
|
// use size of call stack to check for this case
|
|
if ((intptr_t)bp->data != call_stack_ptr)
|
|
return;
|
|
delete_breakpoint(instr_ptr, bp);
|
|
dbg_cmd_repl();
|
|
}
|
|
|
|
static void dbg_set_step_breakpoint(int32_t address, int call_index)
|
|
{
|
|
// if a breakpoint is already set on the next address, leave it
|
|
enum opcode op = LittleEndian_getW(ain->code, address);
|
|
if ((op & OPTYPE_MASK) == BREAKPOINT)
|
|
return;
|
|
|
|
struct breakpoint *bp = xcalloc(1, sizeof(struct breakpoint));
|
|
bp->restore_op = op & ~OPTYPE_MASK;
|
|
assert(bp->restore_op >= 0 && bp->restore_op < NR_OPCODES);
|
|
bp->cb = dbg_step_breakpoint_cb;
|
|
bp->data = (void*)(intptr_t)call_index;
|
|
bp->message = NULL;
|
|
LittleEndian_putW(ain->code, address, BREAKPOINT | bp->restore_op);
|
|
add_breakpoint(address, bp);
|
|
}
|
|
|
|
static int32_t get_function_address(int fno)
|
|
{
|
|
assert(fno > 0 && fno < ain->nr_functions);
|
|
return ain->functions[fno].address;
|
|
}
|
|
|
|
/* Determine the next address at the current instruction. */
|
|
static int32_t dbg_next_address(bool into, int *call_index)
|
|
{
|
|
*call_index = call_stack_ptr;
|
|
enum opcode current = LittleEndian_getW(ain->code, instr_ptr) & ~OPTYPE_MASK;
|
|
assert(current >= 0 && current < NR_OPCODES);
|
|
switch (current) {
|
|
case JUMP:
|
|
return get_argument(0);
|
|
case IFZ:
|
|
if (stack_peek(0).i == 0)
|
|
return get_argument(0);
|
|
break;
|
|
case IFNZ:
|
|
if (stack_peek(0).i)
|
|
return get_argument(0);
|
|
break;
|
|
case RETURN: {
|
|
*call_index = call_stack_ptr-1;
|
|
return call_stack[call_stack_ptr-1].return_address;
|
|
}
|
|
case _MSG:
|
|
if (!into)
|
|
break;
|
|
// TODO: step into message function
|
|
return -1;
|
|
case SWITCH:
|
|
return get_switch_address(get_argument(0), stack_peek(0).i);
|
|
case STRSWITCH:
|
|
return get_strswitch_address(get_argument(0), heap_get_string(stack_peek(0).i));
|
|
case SJUMP: {
|
|
if (!into)
|
|
break;
|
|
// XXX: can't determine RETURN address of scenario call (VM_RETURN)
|
|
return -1;
|
|
/*
|
|
int fno = heap[stack_peek(0).i].page->index;
|
|
return ain->functions[fno].address;
|
|
*/
|
|
}
|
|
case CALLFUNC:
|
|
case CALLMETHOD:
|
|
case THISCALLMETHOD_NOPARAM:
|
|
if (!into)
|
|
break;
|
|
*call_index = call_stack_ptr + 1;
|
|
return get_function_address(get_argument(0));
|
|
case CALLFUNC2:
|
|
if (!into)
|
|
break;
|
|
*call_index = call_stack_ptr + 1;
|
|
return get_function_address(stack_peek(1).i);
|
|
case SH_IF_LOC_LT_IMM:
|
|
if (local_get(get_argument(0)).i < get_argument(1))
|
|
return get_argument(2);
|
|
break;
|
|
case SH_IF_LOC_GE_IMM:
|
|
if (local_get(get_argument(0)).i >= get_argument(1))
|
|
return get_argument(2);
|
|
break;
|
|
case SH_IF_STRUCTREF_NE_LOCALREF:
|
|
if (member_get(get_argument(0)).i != local_get(get_argument(1)).i)
|
|
return get_argument(2);
|
|
break;
|
|
case SH_IF_STRUCTREF_GT_IMM:
|
|
if (member_get(get_argument(0)).i > get_argument(1))
|
|
return get_argument(2);
|
|
break;
|
|
case SH_STRUCTREF2_CALLMETHOD_NO_PARAM:
|
|
if (!into)
|
|
break;
|
|
*call_index = call_stack_ptr + 1;
|
|
return get_function_address(get_argument(2));
|
|
case SH_IF_STRUCTREF_Z:
|
|
if (!member_get(get_argument(0)).i)
|
|
return get_argument(1);
|
|
break;
|
|
case SH_IF_STRUCT_A_NOT_EMPTY: {
|
|
struct page *array = heap_get_page(member_get(get_argument(0)).i);
|
|
if (array && array->nr_vars)
|
|
return get_argument(1);
|
|
break;
|
|
}
|
|
case SH_IF_LOC_GT_IMM:
|
|
if (local_get(get_argument(0)).i > get_argument(1))
|
|
return get_argument(2);
|
|
break;
|
|
case SH_IF_STRUCTREF_NE_IMM:
|
|
if (member_get(get_argument(0)).i != get_argument(1))
|
|
return get_argument(2);
|
|
break;
|
|
case SH_IF_STRUCTREF_EQ_IMM:
|
|
if (member_get(get_argument(0)).i == get_argument(1))
|
|
return get_argument(2);
|
|
break;
|
|
case SH_IF_SREF_NE_STR0: {
|
|
struct string *a = heap_get_string(stack_peek_var()->i);
|
|
struct string *b = ain->strings[get_argument(0)];
|
|
if (strcmp(a->text, b->text))
|
|
return get_argument(1);
|
|
break;
|
|
}
|
|
case DG_CALL: {
|
|
if (!into)
|
|
return get_argument(1);
|
|
// XXX: can't determine RETURN address of delegate call (VM_RETURN)
|
|
return -1;
|
|
}
|
|
default:
|
|
// XXX: catch any unhandled control-flow instructions
|
|
if (instructions[current].ip_inc == 0)
|
|
return -1;
|
|
break;
|
|
}
|
|
return instr_ptr + instruction_width(current);
|
|
}
|
|
|
|
bool dbg_set_step_over_breakpoint(void)
|
|
{
|
|
int call_index;
|
|
int32_t address = dbg_next_address(false, &call_index);
|
|
if (address < 0)
|
|
return false;
|
|
dbg_set_step_breakpoint(address, call_index);
|
|
return true;
|
|
}
|
|
|
|
bool dbg_set_step_into_breakpoint(void)
|
|
{
|
|
int call_index;
|
|
int32_t address = dbg_next_address(true, &call_index);
|
|
if (address < 0)
|
|
return false;
|
|
dbg_set_step_breakpoint(address, call_index);
|
|
return true;
|
|
}
|
|
|
|
static void _dbg_handle_breakpoint(void *data)
|
|
{
|
|
struct breakpoint *bp = data;
|
|
if (bp->cb) {
|
|
bp->cb(bp);
|
|
} else {
|
|
printf("%s\n", bp->message);
|
|
dbg_cmd_repl();
|
|
}
|
|
}
|
|
|
|
void dbg_handle_breakpoint(void)
|
|
{
|
|
struct breakpoint *bp = get_breakpoint(instr_ptr);
|
|
if (!bp) {
|
|
WARNING("Unregistered breakpoint");
|
|
return;
|
|
}
|
|
dbg_start(_dbg_handle_breakpoint, bp);
|
|
}
|
|
|
|
void dbg_print_frame(unsigned no)
|
|
{
|
|
if (no >= call_stack_ptr) {
|
|
DBG_ERROR("Invalid frame number: %d", no);
|
|
return;
|
|
}
|
|
unsigned cs_no = call_stack_ptr - (1 + no);
|
|
struct ain_function *f = &ain->functions[call_stack[cs_no].fno];
|
|
uint32_t addr = no ? call_stack[cs_no+1].call_address : instr_ptr;
|
|
printf("%c #%d 0x%08x in %s\n", no == dbg_current_frame ? '*' : ' ',
|
|
no, addr, display_sjis0(f->name));
|
|
}
|
|
|
|
void dbg_print_stack_trace(void)
|
|
{
|
|
for (int i = 0; i < call_stack_ptr; i++) {
|
|
dbg_print_frame(i);
|
|
}
|
|
}
|
|
|
|
struct ain_variable *dbg_get_member(const char *name, union vm_value *val_out)
|
|
{
|
|
struct page *page = get_struct_page(dbg_current_frame);
|
|
if (!page)
|
|
return NULL;
|
|
assert(page->type == STRUCT_PAGE);
|
|
assert(page->index >= 0 && page->index < ain->nr_structures);
|
|
struct ain_struct *s = &ain->structures[page->index];
|
|
assert(page->nr_vars == s->nr_members);
|
|
for (int i = 0; i < s->nr_members; i++) {
|
|
if (!strcmp(s->members[i].name, name)) {
|
|
*val_out = page->values[i];
|
|
return &s->members[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct ain_variable *dbg_get_local(const char *name, union vm_value *val_out)
|
|
{
|
|
struct page *page = get_local_page(dbg_current_frame);
|
|
if (!page)
|
|
return NULL;
|
|
assert(page->type == LOCAL_PAGE);
|
|
assert(page->index >= 0 && page->index < ain->nr_functions);
|
|
struct ain_function *f = &ain->functions[page->index];
|
|
for (int i = 0; i < f->nr_vars; i++) {
|
|
if (!strcmp(f->vars[i].name, name)) {
|
|
*val_out = page->values[i];
|
|
return &f->vars[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct ain_variable *dbg_get_global(const char *name, union vm_value *val_out)
|
|
{
|
|
for (int i = 0; i < ain->nr_globals; i++) {
|
|
if (!strcmp(ain->globals[i].name, name)) {
|
|
*val_out = global_get(i);
|
|
return &ain->globals[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
struct ain_variable *dbg_get_variable(const char *name, union vm_value *val_out)
|
|
{
|
|
struct ain_variable *var;
|
|
if (!strncmp(name, "this.", 5) && (var = dbg_get_member(name+5, val_out)))
|
|
return var;
|
|
if ((var = dbg_get_local(name, val_out)))
|
|
return var;
|
|
if ((var = dbg_get_global(name, val_out)))
|
|
return var;
|
|
return NULL;
|
|
}
|
|
|
|
struct string *dbg_value_to_string(struct ain_type *type, union vm_value value, int recursive)
|
|
{
|
|
switch (type->data) {
|
|
case AIN_INT:
|
|
case AIN_LONG_INT:
|
|
return integer_to_string(value.i);
|
|
case AIN_FLOAT:
|
|
return float_to_string(value.f, 6);
|
|
case AIN_BOOL:
|
|
return cstr_to_string(value.i ? "true" : "false");
|
|
case AIN_STRING: {
|
|
struct string *out = cstr_to_string("\"");
|
|
string_append(&out, heap_get_string(value.i));
|
|
string_push_back(&out, '"');
|
|
return out;
|
|
}
|
|
case AIN_STRUCT:
|
|
case AIN_REF_STRUCT: {
|
|
if (value.i < 0)
|
|
return cstr_to_string("NULL");
|
|
struct page *page = heap_get_page(value.i);
|
|
if (page->nr_vars == 0) {
|
|
return cstr_to_string("{}");
|
|
}
|
|
if (!recursive) {
|
|
return cstr_to_string("{ <...> }");
|
|
}
|
|
struct string *out = cstr_to_string("{ ");
|
|
for (int i = 0; i < page->nr_vars; i++) {
|
|
struct ain_variable *m = &ain->structures[type->struc].members[i];
|
|
if (i) {
|
|
string_append_cstr(&out, "; ", 2);
|
|
}
|
|
string_append_cstr(&out, m->name, strlen(m->name));
|
|
string_append_cstr(&out, " = ", 3);
|
|
struct string *tmp = dbg_value_to_string(&m->type, page->values[i], recursive-1);
|
|
string_append(&out, tmp);
|
|
free_string(tmp);
|
|
}
|
|
string_append_cstr(&out, " }", 2);
|
|
return out;
|
|
}
|
|
case AIN_ARRAY_TYPE:
|
|
case AIN_REF_ARRAY_TYPE: {
|
|
if (value.i < 0)
|
|
return cstr_to_string("[]");
|
|
struct page *page = heap_get_page(value.i);
|
|
if (!page || page->nr_vars == 0) {
|
|
return cstr_to_string("[]");
|
|
}
|
|
// get member type
|
|
struct ain_type t;
|
|
t.data = variable_type(page, 0, &t.struc, &t.rank);
|
|
t.array_type = NULL;
|
|
if (!recursive) {
|
|
switch (t.data) {
|
|
case AIN_STRUCT:
|
|
case AIN_REF_STRUCT:
|
|
case AIN_ARRAY_TYPE:
|
|
case AIN_REF_ARRAY_TYPE:
|
|
return cstr_to_string("[ <...> ]");
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
struct string *out = cstr_to_string("[ ");
|
|
for (int i = 0; i < page->nr_vars; i++) {
|
|
if (i) {
|
|
string_append_cstr(&out, "; ", 2);
|
|
}
|
|
struct string *tmp = dbg_value_to_string(&t, page->values[i], recursive-1);
|
|
string_append(&out, tmp);
|
|
free_string(tmp);
|
|
}
|
|
string_append_cstr(&out, " ]", 2);
|
|
return out;
|
|
}
|
|
default:
|
|
return cstr_to_string("<unsupported-data-type>");
|
|
}
|
|
return string_ref(&EMPTY_STRING);
|
|
}
|
|
|
|
// the maximum number of instructions preceeding the instruction pointer to be displayed
|
|
#define DASM_REWIND 16
|
|
// the number of instructions following the instruction pointer to be displayed
|
|
#define DASM_FWD 8
|
|
|
|
// Rewind to DASM_REWIND instructions before the current instruction pointer.
|
|
static bool dbg_init_dasm(struct dasm *dasm)
|
|
{
|
|
unsigned addr_i = 0;
|
|
size_t addr[DASM_REWIND] = {0};
|
|
int fno = call_stack[call_stack_ptr-1].fno;
|
|
|
|
dasm_init(dasm, ain);
|
|
dasm_jump(dasm, ain->functions[fno].address - 6);
|
|
|
|
for (; dasm_addr(dasm) < instr_ptr && !dasm_eof(dasm); dasm_next(dasm)) {
|
|
addr[addr_i++ % DASM_REWIND] = dasm_addr(dasm);
|
|
}
|
|
|
|
if (dasm_addr(dasm) != instr_ptr) {
|
|
goto error;
|
|
}
|
|
|
|
addr_i = (addr_i+1) % DASM_REWIND;
|
|
if (addr[addr_i]) {
|
|
dasm_jump(dasm, addr[addr_i]);
|
|
} else if (addr[0]) {
|
|
dasm_jump(dasm, addr[0]);
|
|
}
|
|
|
|
return true;
|
|
error:
|
|
DBG_ERROR("Couldn't locate instruction pointer from current function");
|
|
return false;
|
|
}
|
|
|
|
static void dbg_print_string(const char *str)
|
|
{
|
|
// TODO: escape
|
|
printf("\"%s\"", display_sjis0(str));
|
|
}
|
|
|
|
static void dbg_print_identifier(const char *str)
|
|
{
|
|
if (strchr(str, ' '))
|
|
dbg_print_string(str);
|
|
else
|
|
printf("%s", display_sjis0(str));
|
|
}
|
|
|
|
static void dbg_print_function_name(struct ain_function *fun)
|
|
{
|
|
int i = ain_get_function_index(ain, fun);
|
|
char *name = fun->name;
|
|
char buf[512];
|
|
if (i > 0) {
|
|
snprintf(buf, 512, "%s#%d", fun->name, i);
|
|
name = buf;
|
|
}
|
|
dbg_print_identifier(name);
|
|
}
|
|
|
|
static void dbg_print_local(struct dasm *dasm, int32_t n)
|
|
{
|
|
int fno = dasm_function(dasm);
|
|
if (fno < 0 || fno >= ain->nr_functions) {
|
|
printf("%d", n);
|
|
return;
|
|
}
|
|
|
|
struct ain_function *f = &ain->functions[fno];
|
|
if (n < 0 || n >= f->nr_vars) {
|
|
printf("<invalid local variable number: %d>", n);
|
|
return;
|
|
}
|
|
|
|
int dup_no = 0;
|
|
for (int i = 0; i < f->nr_vars; i++) {
|
|
if (i == n)
|
|
break;
|
|
if (!strcmp(f->vars[i].name, f->vars[n].name))
|
|
dup_no++;
|
|
}
|
|
|
|
char *name;
|
|
char buf[512];
|
|
if (dup_no) {
|
|
snprintf(buf, 512, "%s#%d", f->vars[n].name, dup_no);
|
|
name = buf;
|
|
} else {
|
|
name = f->vars[n].name;
|
|
}
|
|
|
|
dbg_print_identifier(name);
|
|
}
|
|
|
|
static void dbg_print_arg(struct dasm *dasm, int n)
|
|
{
|
|
static int hll = 0;
|
|
int32_t value = dasm_arg(dasm, n);
|
|
switch (dasm_arg_type(dasm, n)) {
|
|
case T_INT:
|
|
case T_SWITCH:
|
|
printf("%d", value);
|
|
break;
|
|
case T_FLOAT: {
|
|
union { int32_t i; float f; } cast = { .i = value };
|
|
printf("%f", cast.f);
|
|
break;
|
|
}
|
|
case T_ADDR:
|
|
printf("0x%08x", value);
|
|
break;
|
|
case T_FUNC:
|
|
if (value < 0 || value >= ain->nr_functions)
|
|
printf("<invalid function number: %d>", value);
|
|
else
|
|
dbg_print_function_name(&ain->functions[value]);
|
|
break;
|
|
case T_DLG:
|
|
if (value < 0 || value >= ain->nr_delegates)
|
|
printf("<invalid delegate number: %d>", value);
|
|
else
|
|
dbg_print_identifier(ain->delegates[value].name);
|
|
break;
|
|
case T_STRING:
|
|
if (value < 0 || value >= ain->nr_strings)
|
|
printf("<invalid string number: %d>", value);
|
|
else
|
|
dbg_print_string(ain->strings[value]->text);
|
|
break;
|
|
case T_MSG:
|
|
if (value < 0 || value >= ain->nr_messages)
|
|
printf("<invalid message number: %d>", value);
|
|
else
|
|
printf("%d ; %s", value, display_sjis0(ain->messages[value]->text));
|
|
break;
|
|
case T_LOCAL:
|
|
dbg_print_local(dasm, value);
|
|
break;
|
|
case T_GLOBAL:
|
|
if (value < 0 || value >= ain->nr_globals)
|
|
printf("<invalid global number: %d>", value);
|
|
else
|
|
dbg_print_identifier(ain->globals[value].name);
|
|
break;
|
|
case T_STRUCT:
|
|
if (value < 0 || value >= ain->nr_structures)
|
|
printf("<invalid struct number: %d>", value);
|
|
else
|
|
dbg_print_identifier(ain->structures[value].name);
|
|
break;
|
|
case T_SYSCALL:
|
|
if (value < 0 || value >= NR_SYSCALLS || !syscalls[value].name)
|
|
printf("<invalid syscall number: %d>", value);
|
|
else
|
|
printf("%s", syscalls[value].name);
|
|
break;
|
|
case T_HLL:
|
|
if (value < 0 || value >= ain->nr_libraries) {
|
|
printf("<invalid library number: %d>", value);
|
|
} else {
|
|
dbg_print_identifier(ain->libraries[value].name);
|
|
hll = value;
|
|
}
|
|
break;
|
|
case T_HLLFUNC:
|
|
if (hll < 0 || hll >= ain->nr_libraries)
|
|
printf("%d", value);
|
|
else if (value < 0 || value >= ain->libraries[hll].nr_functions)
|
|
printf("<invalid library function number: %d>", value);
|
|
else
|
|
dbg_print_identifier(ain->libraries[hll].functions[value].name);
|
|
break;
|
|
case T_FILE:
|
|
if (!ain->nr_filenames)
|
|
printf("%d", value);
|
|
else if (value < 0 || value >= ain->nr_filenames)
|
|
printf("<invalid file number: %d>", value);
|
|
else
|
|
dbg_print_identifier(ain->filenames[value]);
|
|
break;
|
|
default:
|
|
printf("<invalid arg type: %d>", dasm_arg_type(dasm, n));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void dbg_print_instruction(struct dasm *dasm)
|
|
{
|
|
char c = dasm_addr(dasm) == instr_ptr ? '*' : ' ';
|
|
printf("%c 0x%08x: %s", c, (unsigned)dasm_addr(dasm), dasm_instruction(dasm)->name);
|
|
for (int i = 0; i < dasm_nr_args(dasm); i++) {
|
|
putchar(' ');
|
|
dbg_print_arg(dasm, i);
|
|
}
|
|
putchar('\n');
|
|
}
|
|
|
|
void dbg_print_dasm(void)
|
|
{
|
|
struct dasm dasm;
|
|
if (!dbg_init_dasm(&dasm))
|
|
return;
|
|
|
|
for (; dasm_addr(&dasm) < instr_ptr && !dasm_eof(&dasm); dasm_next(&dasm)) {
|
|
dbg_print_instruction(&dasm);
|
|
}
|
|
|
|
for (int i = 0; i < DASM_FWD && !dasm_eof(&dasm); dasm_next(&dasm), i++) {
|
|
dbg_print_instruction(&dasm);
|
|
}
|
|
}
|
|
|
|
#define STACK_MAX 16
|
|
|
|
void dbg_print_stack(void)
|
|
{
|
|
int i = 0;
|
|
if (stack_ptr > STACK_MAX)
|
|
i = stack_ptr - STACK_MAX;
|
|
|
|
for (; i < stack_ptr; i++) {
|
|
printf(" [%d]: 0x%08x\n", i, stack[i].i);
|
|
}
|
|
}
|
|
|
|
void dbg_print_vm_state(void)
|
|
{
|
|
puts(" Disassembly");
|
|
puts(" -----------");
|
|
dbg_print_dasm();
|
|
puts("");
|
|
puts(" Stack");
|
|
puts(" -----");
|
|
dbg_print_stack();
|
|
}
|