cfad47cfa3/tads3/vmdbg.h
Commiter: Nikos Chantziaras
Author: Nikos Chantziaras
Revision: cfad47cfa3
File Size: 27.9 KB
(June 01, 2009 20:54 UTC) Almost 3 years ago
Initial commit.
Showing without highlighting since it looks like a big file and may slow your browser - show with highlighting
Show/hide line numbers/* $Header$ */
/*
* Copyright (c) 1999, 2002 Michael J. Roberts. All Rights Reserved.
*
* Please see the accompanying license file, LICENSE.TXT, for information
* on using and copying this software.
*/
/*
Name
vmdbg.h - T3 VM debugging API
Function
Provides an interface to debugging operations in the VM
Notes
Modified
11/23/99 MJRoberts - Creation
*/
#ifndef VMDBG_H
#define VMDBG_H
#include "vmglob.h"
#include "vmfunc.h"
#include "vmhash.h"
#include "vmobj.h"
#include "tcprstyp.h"
/* ------------------------------------------------------------------------ */
/*
* Internal breakpoint tracking record
*/
struct CVmDebugBp
{
/* create */
CVmDebugBp();
/* delete */
~CVmDebugBp();
/* notify of VM termination */
void do_terminate(VMG0_);
/* get/set the in-use flag */
int is_in_use() const { return in_use_; }
void set_in_use(int f) { in_use_ = f; }
/* get/set the disabled flag */
int is_disabled() const { return disabled_; }
void set_disabled(int f) { disabled_ = f; }
/*
* set the breakpoint's information - returns zero on success,
* non-zero if an error occurs (such as compiling the condition
* expression)
*/
int set_info(VMG_ ulong code_addr, const char *cond, int change,
int disabled, char *errbuf, size_t errbuflen);
/* get my code address */
ulong get_code_addr() const { return code_addr_; }
/*
* Set the condition text - returns zero on success, non-zero if an
* error occurs compiling the expression. If 'change' is true, then we
* break when the condition changes; otherwise, we break when the
* condition becomes true.
*/
int set_condition(VMG_ const char *cond, int change,
char *errbuf, size_t errbuflen);
/*
* delete the breakpoint - remove the breakpoint from the code and
* mark it as unused
*/
void do_delete(VMG0_);
/*
* Set or remove the BP instruction at my code address. If 'always'
* is set, we'll update the instruction regardless of whether the
* debugger has control or not; normally, we'll only update the
* instruction when the debugger doesn't have control, since we
* don't leave breakpoint instructions in the code at other times.
*/
void set_bp_instr(VMG_ int set, int always);
/* check to see if I have a condition */
int has_condition() const { return has_cond_ && compiled_cond_ != 0; }
/*
* Is our condition a stop-on-change condition? This returns true if
* so, false if this we instead have a stop-when-true condition. Note
* that this information is not meaningful unless has_condition()
* returns true.
*/
int stop_on_change() const { return stop_on_change_; }
/*
* evaluate the condition - return true if the condition evaluates
* to true (non-zero integer, true, or any object, string, or list
* value), false if not
*/
int eval_cond(VMG0_);
/*
* determine if this is a global breakpoint - a global breakpoint is
* one that is not associated with a code location (indicated by a
* code address of zero)
*/
int is_global() const { return code_addr_ == 0; }
private:
/* code address of breakpoint */
ulong code_addr_;
/* condition expression */
char *cond_;
/* length of buffer allocated for cond, if any */
size_t cond_buf_len_;
/* code object with compiled expression */
class CVmPoolDynObj *compiled_cond_;
/*
* The previous value of the condition. If this is a stop-on-change
* condition, we'll stop as soon as the current value of the expression
* differs from this saved value. If we have a condition, but we
* haven't yet computed the "old" value, this will have the 'empty'
* type.
*/
vm_globalvar_t *prv_val;
/*
* original instruction byte at this breakpoint (we replace the
* instruction byte with the BP instruction, but we must remember
* the original for when we clear or disable the breakpoint)
*/
char orig_instr_;
/* flag: breakpoint is in use */
uint in_use_ : 1;
/* flag: breakpoint has a conditional expression */
uint has_cond_ : 1;
/* flag: our condition is stop-on-change, not stop-when-true */
uint stop_on_change_ : 1;
/* flag: breakpoint is disabled */
uint disabled_ : 1;
};
/* maximum number of breakpoints */
const size_t VMDBG_BP_MAX = 100;
/* ------------------------------------------------------------------------ */
/*
* Structure for saving debugger step modes. This is used to save and
* restore the step modes before doing a recursive evaluation.
*/
struct vmdbg_step_save_t
{
int old_step;
int old_step_in;
int old_step_out;
};
/* ------------------------------------------------------------------------ */
/*
* Debugger API object
*/
class CVmDebug
{
friend class CVmRun;
friend struct CVmDebugBp;
public:
CVmDebug(VMG0_);
~CVmDebug();
/*
* Initialize. The VM will call this after setting up all of the VM
* globals, but before loading the image file.
*/
void init(VMG_ const char *image_filename);
/*
* Initialization phase 2 - this is called just after we finish
* loading the image file. The debugger can perform any final
* set-up that can't be handled until after the image is loaded.
*/
void init_after_load(VMG0_);
/*
* Terminate. The VM will call this when shutting down, before
* deleting any of the globals.
*/
void terminate(VMG0_);
/*
* Determine if the loaded program was compiled for debugging.
* We'll check to see if the image loader found a GSYM (global
* symbol table) block in the file.
*/
int image_has_debug_info(VMG0_) const;
/* determine if the debugger has control */
int is_in_debugger() const { return in_debugger_ != 0; }
/* -------------------------------------------------------------------- */
/*
* Add an entry to a reverse-lookup hash table. The image file
* loader must call this function for each object, function, and
* property symbol it loads from the debug records in an image file.
*/
void add_rev_sym(const char *sym, size_t sym_len,
tc_symtype_t sym_type, ulong sym_val);
/*
* Look up a symbol given the type and identifier. Returns a
* pointer to a null-terminated string giving the name of the
* symbol, or null if the given identifier doesn't have an
* associated symbol.
*/
const char *objid_to_sym(vm_obj_id_t objid) const
{ return find_rev_sym(obj_rev_table_, (ulong)objid); }
const char *propid_to_sym(vm_prop_id_t propid) const
{ return find_rev_sym(prop_rev_table_, (ulong)propid); }
const char *funcaddr_to_sym(pool_ofs_t func_addr) const
{ return find_rev_sym(func_rev_table_, (ulong)func_addr); }
const char *enum_to_sym(ulong enum_id) const
{ return find_rev_sym(enum_rev_table_, enum_id); }
/*
* Given a symbol name, get the final modifying object. If the symbol
* is a synthesized modified base object, we'll find the actual symbol
* that was used in the source code with 'modify.'
*/
const char *get_modifying_sym(const char *base_sym) const;
/* -------------------------------------------------------------------- */
/*
* Method header list
*/
/* allocate the method header list */
void alloc_method_header_list(ulong cnt);
/*
* Given a code pool address, find the method header containing the
* address. This searches the method header list for the nearest
* method header whose address is less than the given address.
*/
pool_ofs_t find_method_header(pool_ofs_t ofs);
/* get the number of method headers */
ulong get_method_header_cnt() const { return method_hdr_cnt_; }
/* set the given method header list entry */
void set_method_header(ulong idx, ulong addr)
{
/* store the given entry */
method_hdr_[idx] = addr;
}
/* get the given method header entry */
ulong get_method_header(ulong idx) const { return method_hdr_[idx]; }
/* -------------------------------------------------------------------- */
/*
* Get information on the source location at a given stack level.
* If successful, fills in the filename pointer and line number and
* returns zero. If no source information is available at the stack
* level, returns non-zero to indicate failure.
*
* Level 0 is the current stack level, 1 is the enclosing frame, and
* so on.
*/
int get_source_info(VMG_ const char **fname, unsigned long *linenum,
int level) const;
/*
* Enumerate local variables at the given stack level - 0 is the
* current stack level, 1 is the enclosing frame, and so on.
*/
void enum_locals(VMG_ void (*cbfunc)(void *, const char *, size_t),
void *cbctx, int level);
/*
* Build a stack trace listing, invoking the callback for each level
* of the stack trace. If 'source_info' is true, we'll include the
* source file name and line number for each point in the trace.
*/
void build_stack_listing(VMG_
void (*cbfunc)(void *ctx,
const char *str, int strl),
void *cbctx, int source_info);
/* -------------------------------------------------------------------- */
/*
* Breakpoints
*/
/*
* Toggle a breakpoint at the given code location. Returns zero on
* success, non-zero on failure. If successful, fills in *bpnum
* with the breakpoint ID, and fills in *did_set with true if we set
* a breakpoint, false if we deleted an existing breakpoint at the
* location. cond is null if the breakpoint is unconditional, or a
* pointer to a character string giving the source code for an
* expression that must evaluate to true when the breakpoint is
* encountered for the breakpoint to suspend execution.
*/
int toggle_breakpoint(VMG_ ulong code_addr,
const char *cond, int change,
int *bpnum, int *did_set,
char *errbuf, size_t errbuflen);
/* toggle the disabled status of a breakpoint */
void toggle_breakpoint_disable(VMG_ int bpnum);
/* set a breakpoint's disabled status */
void set_breakpoint_disable(VMG_ int bpnum, int disable);
/* determine if a breakpoint is disabled - returns true if so */
int is_breakpoint_disabled(VMG_ int bpnum);
/* set a breakpoint's condition expression - returns 0 on success */
int set_breakpoint_condition(VMG_ int bpnum, const char *cond, int change,
char *errbuf, size_t errbuflen);
/* delete a breakpoint given the breakpoint ID */
void delete_breakpoint(VMG_ int bpnum);
/* -------------------------------------------------------------------- */
/*
* Set execution mode. These calls do not immediately resume
* execution, but merely set the mode that will be in effect when
* execution resumes. These calls can only be made while the
* program is stopped.
*/
/*
* Set single-step mode to STEP IN. In this mode, we will stop as
* soon as we reach a new statement.
*/
void set_step_in()
{
/* set single-step and step-in modes */
single_step_ = TRUE;
step_in_ = TRUE;
step_out_ = FALSE;
}
/*
* Set DEBUG TRACE mode. This activates single-step mode from within a
* VM intrinsic.
*/
void set_debug_trace()
{
/*
* set single-step mode so that we stop as soon as we're back in
* byte code
*/
set_step_in();
/*
* clear any current statement location, so that we stop
* immediately as soon as we get back to byte code, even if our
* last debugger entry was in the same line of code
*/
cur_stm_start_ = cur_stm_end_ = 0;
}
/*
* Set single-step mode for a 'break' key (Ctrl-C, Ctrl+Break, etc.,
* according to local conventions). This asynchronously interrupts the
* interpreter and breaks into the debugger while the program is
* running, which is useful to break out of infinite loops or very
* lengthy processing.
*/
void set_break_stop()
{
/* set single-step-in mode */
set_step_in();
/*
* Forget our last execution location, so that we'll stop again
* even if we haven't moved from our last break location. Since
* we're explicitly breaking execution, we want to stop whether
* we've gone anywhere or not.
*/
cur_stm_start_ = cur_stm_end_ = 0;
}
/*
* Set single-step mode to STEP OVER. In this mode, we will stop as
* soon as we reach a new statement at the same stack level as the
* current statement or at an enclosing stack level.
*/
void set_step_over(VMG0_);
/*
* Set single-step mode to STEP OUT. In this mode, we will stop as
* soon as we reach a new statement at a stack level enclosing the
* current statement's stack level.
*/
void set_step_out(VMG0_);
/*
* Set single-step mode to GO. In this mode, we won't stop until we
* reach a breakpoint.
*/
void set_go()
{
/* clear single-step mode */
single_step_ = FALSE;
step_in_ = FALSE;
step_out_ = FALSE;
}
/*
* Save the step mode in preparation for a recursive execution (of a
* debugger expression), and set the mode for the recursive
* execution. Turns off stepping modes.
*/
void prepare_for_eval(vmdbg_step_save_t *info)
{
/* save the original execution mode */
info->old_step = single_step_;
info->old_step_in = step_in_;
info->old_step_out = step_out_;
/* set execution mode to RUN */
single_step_ = FALSE;
step_in_ = FALSE;
step_out_ = FALSE;
}
/* restore original execution modes after recursive execution */
void restore_from_eval(vmdbg_step_save_t *info)
{
single_step_ = info->old_step;
step_in_ = info->old_step_in;
step_out_ = info->old_step_out;
}
/* -------------------------------------------------------------------- */
/*
* Set the execution pointer to a new location. The new code
* address is given as an absolute code pool address. The new
* location must be within the current method. Updates
* *exec_ofs_ptr with the method offset of the new location.
*/
int set_exec_ofs(unsigned int *exec_ofs_ptr, unsigned long code_addr)
{
/* set the method offset pointer */
*exec_ofs_ptr = (unsigned int)(code_addr - entry_ofs_);
/* success */
return 0;
}
/*
* Determine if a code location is within the current active method.
* Returns true if so, false if not.
*/
int is_in_current_method(VMG_ unsigned long code_addr);
/* -------------------------------------------------------------------- */
/*
* Evaluate an expression. Returns zero on success, non-zero on
* failure. 'level' is the stack level at which to evaluate the
* expression - 0 is the currently active method, 1 is its caller,
* and so on.
*/
int eval_expr(VMG_ char *buf, size_t buflen, const char *expr,
int level, int *is_lval, int *is_openable,
void (*aggcb)(void *, const char *, int, const char *),
void *aggctx, int speculative);
/*
* Compile an expression. Fills in *code_obj with a handle to the
* code pool object containing the compiled byte-code. If dst_buf
* is non-null, we will format a message describing any error that
* occurs into the buffer.
*
* We'll compile the expression using the given local symbol table
* and the given active stack level. Note that the local symbol
* table specified need not be the local symbol table for the given
* active stack level, since we're only compiling, not evaluating,
* the expression; for example, when compiling a condition
* expression for a breakpoint, we'll normally compile the
* expression in the context of the breakpoint's source location,
* which might not even be in the active stack when the condition is
* compiled.
*/
int compile_expr(VMG_ const char *expr,
int level, class CVmDbgSymtab *local_symtab,
int self_valid, int speculative, int *is_lval,
class CVmPoolDynObj **code_obj,
char *dst_buf, size_t dst_buf_len);
/* -------------------------------------------------------------------- */
/*
* Get/set the UI context. This information is for use by the UI
* code. We don't use the UI context for anything; we just maintain
* it so that the UI can keep track of what's going on between
* calls.
*/
void *get_ui_ctx() const { return ui_ctx_; }
void set_ui_ctx(void *ctx) { ui_ctx_ = ctx; }
protected:
/* -------------------------------------------------------------------- */
/*
* CVmRun API - the byte-code execution loop uses these routines
*/
/*
* Step into the debugger - the byte-code execution loop calls this
* just before executing each instruction when the interpreter is in
* single-step mode, or when a breakpoint is encountered. We'll
* activate the debugger user interface if necessary. This returns
* in order to resume execution. bp is true if we encountered a
* breakpoint, false if we're single-stepping.
*/
void step(VMG_ const uchar **pc_ptr, pool_ofs_t, int bp,
int error_code);
/*
* synchronize the internal execution point - acts like a step()
* without actually stopping
*/
void sync_exec_pos(VMG_ const uchar *pc_ptr,
pool_ofs_t method_start_ofs);
/*
* Get single-step mode - returns true if we're stopping at each
* instruction, false if we're running until we hit a breakpoint.
* The byte-code execution loop should call step() on each
* instruction if this returns true.
*
* We must also single-step through code if there are any active
* global breakpoints, even when the user is not interactively
* single-stepping.
*/
int is_single_step() const
{ return single_step_ || global_bp_cnt_ != 0; }
/* -------------------------------------------------------------------- */
/*
* Internal operations
*/
/*
* Get information on the execution location at the given stack
* level
*/
int get_stack_level_info(VMG_ int level, class CVmFuncPtr *func_ptr,
class CVmDbgLinePtr *line_ptr,
ulong *stm_start, ulong *stm_end) const;
/*
* look up a symbol in one of our reverse mapping tables
*/
const char *find_rev_sym(const class CVmHashTable *hashtab,
ulong val) const;
/*
* Format a value into a buffer
*/
void format_val(VMG_ char *buf, size_t buflen,
const struct vm_val_t *val);
/* find a breakpoint given a code address */
CVmDebugBp *find_bp(ulong code_addr);
/* allocate a new breakpoint record */
CVmDebugBp *alloc_bp();
/*
* suspend/resume breakpoints - this doesn't affect the permanent
* status of any breakpoint, but simply allows for removal of BP
* instructions from the code while the debugger has control
*/
void suspend_all_bps(VMG0_);
void restore_all_bps(VMG0_);
private:
/* flag: debugger has control */
int in_debugger_ : 1;
/*
* single-step mode - if this is true, we're stepping through code;
* otherwise, we're running until we hit a breakpoint
*/
int single_step_ : 1;
/*
* step-in mode - if this is true, and single_step_ is true, we'll
* break as soon as we reach a new statement anywhere; otherwise,
* we'll stop only if we reach a new statement at the same stack
* level as the current statement (in other words, we're stepping
* over subroutine calls made by the current statement)
*/
int step_in_ : 1 ;
/*
* Step-out mode - if this is true, and single_step_ is true, we'll
* break as soon as we reach a new statement outside the current
* call level. (This mode flag is actually only important for
* native code interaction, because we actually know to stop on a
* step-out using step-over with an enclosing stack level.)
*/
int step_out_ : 1;
/*
* step-over-breakpoint mode - if this is true, we're stepping over
* a breakpoint
*/
int step_over_bp_ : 1;
/*
* Original step flags - these store the step flags that will be in
* effect after we finish a step_over_bp operation
*/
int orig_single_step_ : 1;
int orig_step_in_ : 1;
int orig_step_out_ : 1;
/*
* flag: we've been initialized during program load; when this flag is
* set, we'll have to make corresponding uninitializations when the
* program terminates
*/
int program_inited_ : 1;
/* breakpoint being stepped over */
CVmDebugBp *step_over_bp_bp_;
/*
* Step-resume stack frame level. When step_in_ is false, we won't
* resume stepping code until the frame pointer is at this level or
* an enclosing level.
*/
size_t step_frame_depth_;
/*
* Method header for the current function. We set this each time we
* enter the debugger via step().
*/
CVmFuncPtr func_ptr_;
/*
* Debug records pointer for the current function. We set this each
* time we enter the debugger via step(). Note that we must keep
* track of whether the debug pointer is valid, because some
* functions might not have debug tables at all.
*/
CVmDbgTablePtr dbg_ptr_;
int dbg_ptr_valid_ : 1;
/* function header pointer for current function */
pool_ofs_t entry_ofs_;
/* current program counter */
pool_ofs_t pc_;
/* debugger line records for current statement */
CVmDbgLinePtr cur_stm_line_;
/*
* Current statement boundaries. We set this each time we enter the
* debugger via step(). We usually step through code until we reach
* the beginning of a new statement; we use these boundaries to
* determine when we leave the current statement. As long as the
* current byte-code offset is within these boundaries, (inclusive),
* we know we're within the same statement.
*/
ulong cur_stm_start_;
ulong cur_stm_end_;
/*
* User interface context. We maintain this location for use by the
* UI code to store its context information.
*/
void *ui_ctx_;
/*
* Reverse-mapping hash tables for various symbol types. These hash
* tables allow us to find the symbol for a given object ID,
* property ID, or function address.
*/
class CVmHashTable *obj_rev_table_;
class CVmHashTable *prop_rev_table_;
class CVmHashTable *func_rev_table_;
class CVmHashTable *enum_rev_table_;
/* breakpoints */
CVmDebugBp bp_[VMDBG_BP_MAX];
/*
* Number of global breakpoints in effect (when this is non-zero, we
* must trace through code even in 'go' mode, so we can evaluate the
* global breakpoints repeatedly and thereby catch when the first
* one hits). This only counts enabled global breakpoints - if a
* global breakpoint is disabled it must be removed from this count,
* since we won't need to consider it when checking for hits.
*/
int global_bp_cnt_;
/* host interface (for the compiler's use) */
class CTcHostIfcDebug *hostifc_;
/*
* method header list - this is a list of the method headers in the
* program, sorted by address
*/
ulong *method_hdr_;
ulong method_hdr_cnt_;
};
/* ------------------------------------------------------------------------ */
/*
* Debugger programmatic interface to the user interface. This is to be
* implemented by each UI subsystem.
*/
class CVmDebugUI
{
public:
/*
* Initialize. We'll call this once before entering any other
* functions, so that the UI code can set up its private
* information. The UI can create a private context and store a
* pointer via G_debugger->set_ui_ctx().
*/
static void init(VMG_ const char *image_filename);
/*
* Initialization, phase 2 - this is called just after we've
* finished loading the image file.
*/
static void init_after_load(VMG0_);
/*
* Terminate. We'll call this before terminating, to allow the UI
* code to release any resources it allocated.
*/
static void terminate(VMG0_);
/*
* Invoke the UI main command loop entrypoint. The engine calls
* this whenever we hit a breakpoint or reach a single-step point.
* This routine should not return until it is ready to let the
* program continue execution.
*/
static void cmd_loop(VMG_ int bp_number,
int error_code, unsigned int *exec_ofs);
};
/* ------------------------------------------------------------------------ */
/*
* Hash table entry for reverse-mapping hash tables. These tables allow
* the debugger to look up a symbol name given the symbol's value: an
* object ID, a property ID, or a function address. We map each of
* these types of values to a ulong value, which we use as the hash key.
*/
class CVmHashEntryDbgRev: public CVmHashEntry
{
public:
/*
* Construct the entry. sym_val is the symbol's value (an object
* ID, a property ID, or a function address) coerced to a ulong.
*/
CVmHashEntryDbgRev(ulong sym_val, const char *sym, size_t len);
~CVmHashEntryDbgRev();
/* check for a match */
virtual int matches(const char *str, size_t len) const;
/* get the symbol name for this entry */
const char *get_sym() const { return sym_; }
size_t get_sym_len() const { return sym_len_; }
protected:
char *sym_;
size_t sym_len_;
};
/*
* Hash function for reverse mapping tables. This hash function doesn't
* make any assumptions about the range of character values, since we're
* using sequences of raw binary bytes for hash keys.
*/
class CVmHashFuncDbgRev: public CVmHashFunc
{
public:
unsigned int compute_hash(const char *str, size_t len) const;
};
/* ------------------------------------------------------------------------ */
/*
* Condition compilation macros. Some files can be compiled for
* stand-alone use or use within a debugger application; when compiled
* stand-alone, certain debugger-related operations are removed, which
* can improve performance over the debugger-enabled version.
*/
#ifdef VM_DEBUGGER
/*
* DEBUGGER-ENABLED VERSION
*/
/* include debugger-only code */
#define VM_IF_DEBUGGER(x) x
/* do NOT include non-debugger-only code */
#define VM_IF_NOT_DEBUGGER(x)
#else /* VM_DEBUGGER */
/*
* STAND-ALONE (NON-DEBUGGER) VERSION
*/
/* do NOT include debugger-only code in a stand-alone version */
#define VM_IF_DEBUGGER(x)
/* include non-debugger-only code */
#define VM_IF_NOT_DEBUGGER(x) x
#endif /* VM_DEBUGGER */
#endif /* VMDBG_H */
|