| | 1 | /* $Header$ */ |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 1999, 2002 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * Please see the accompanying license file, LICENSE.TXT, for information |
| | 7 | * on using and copying this software. |
| | 8 | */ |
| | 9 | /* |
| | 10 | Name |
| | 11 | vmdbg.h - T3 VM debugging API |
| | 12 | Function |
| | 13 | Provides an interface to debugging operations in the VM |
| | 14 | Notes |
| | 15 | |
| | 16 | Modified |
| | 17 | 11/23/99 MJRoberts - Creation |
| | 18 | */ |
| | 19 | |
| | 20 | #ifndef VMDBG_H |
| | 21 | #define VMDBG_H |
| | 22 | |
| | 23 | #include "vmglob.h" |
| | 24 | #include "vmfunc.h" |
| | 25 | #include "vmhash.h" |
| | 26 | #include "vmobj.h" |
| | 27 | #include "tcprstyp.h" |
| | 28 | |
| | 29 | |
| | 30 | /* ------------------------------------------------------------------------ */ |
| | 31 | /* |
| | 32 | * Internal breakpoint tracking record |
| | 33 | */ |
| | 34 | struct CVmDebugBp |
| | 35 | { |
| | 36 | /* create */ |
| | 37 | CVmDebugBp(); |
| | 38 | |
| | 39 | /* delete */ |
| | 40 | ~CVmDebugBp(); |
| | 41 | |
| | 42 | /* notify of VM termination */ |
| | 43 | void do_terminate(VMG0_); |
| | 44 | |
| | 45 | /* get/set the in-use flag */ |
| | 46 | int is_in_use() const { return in_use_; } |
| | 47 | void set_in_use(int f) { in_use_ = f; } |
| | 48 | |
| | 49 | /* get/set the disabled flag */ |
| | 50 | int is_disabled() const { return disabled_; } |
| | 51 | void set_disabled(int f) { disabled_ = f; } |
| | 52 | |
| | 53 | /* |
| | 54 | * set the breakpoint's information - returns zero on success, |
| | 55 | * non-zero if an error occurs (such as compiling the condition |
| | 56 | * expression) |
| | 57 | */ |
| | 58 | int set_info(VMG_ ulong code_addr, const char *cond, int change, |
| | 59 | int disabled, char *errbuf, size_t errbuflen); |
| | 60 | |
| | 61 | /* get my code address */ |
| | 62 | ulong get_code_addr() const { return code_addr_; } |
| | 63 | |
| | 64 | /* |
| | 65 | * Set the condition text - returns zero on success, non-zero if an |
| | 66 | * error occurs compiling the expression. If 'change' is true, then we |
| | 67 | * break when the condition changes; otherwise, we break when the |
| | 68 | * condition becomes true. |
| | 69 | */ |
| | 70 | int set_condition(VMG_ const char *cond, int change, |
| | 71 | char *errbuf, size_t errbuflen); |
| | 72 | |
| | 73 | /* |
| | 74 | * delete the breakpoint - remove the breakpoint from the code and |
| | 75 | * mark it as unused |
| | 76 | */ |
| | 77 | void do_delete(VMG0_); |
| | 78 | |
| | 79 | /* |
| | 80 | * Set or remove the BP instruction at my code address. If 'always' |
| | 81 | * is set, we'll update the instruction regardless of whether the |
| | 82 | * debugger has control or not; normally, we'll only update the |
| | 83 | * instruction when the debugger doesn't have control, since we |
| | 84 | * don't leave breakpoint instructions in the code at other times. |
| | 85 | */ |
| | 86 | void set_bp_instr(VMG_ int set, int always); |
| | 87 | |
| | 88 | /* check to see if I have a condition */ |
| | 89 | int has_condition() const { return has_cond_ && compiled_cond_ != 0; } |
| | 90 | |
| | 91 | /* |
| | 92 | * Is our condition a stop-on-change condition? This returns true if |
| | 93 | * so, false if this we instead have a stop-when-true condition. Note |
| | 94 | * that this information is not meaningful unless has_condition() |
| | 95 | * returns true. |
| | 96 | */ |
| | 97 | int stop_on_change() const { return stop_on_change_; } |
| | 98 | |
| | 99 | /* |
| | 100 | * evaluate the condition - return true if the condition evaluates |
| | 101 | * to true (non-zero integer, true, or any object, string, or list |
| | 102 | * value), false if not |
| | 103 | */ |
| | 104 | int eval_cond(VMG0_); |
| | 105 | |
| | 106 | /* |
| | 107 | * determine if this is a global breakpoint - a global breakpoint is |
| | 108 | * one that is not associated with a code location (indicated by a |
| | 109 | * code address of zero) |
| | 110 | */ |
| | 111 | int is_global() const { return code_addr_ == 0; } |
| | 112 | |
| | 113 | private: |
| | 114 | /* code address of breakpoint */ |
| | 115 | ulong code_addr_; |
| | 116 | |
| | 117 | /* condition expression */ |
| | 118 | char *cond_; |
| | 119 | |
| | 120 | /* length of buffer allocated for cond, if any */ |
| | 121 | size_t cond_buf_len_; |
| | 122 | |
| | 123 | /* code object with compiled expression */ |
| | 124 | class CVmPoolDynObj *compiled_cond_; |
| | 125 | |
| | 126 | /* |
| | 127 | * The previous value of the condition. If this is a stop-on-change |
| | 128 | * condition, we'll stop as soon as the current value of the expression |
| | 129 | * differs from this saved value. If we have a condition, but we |
| | 130 | * haven't yet computed the "old" value, this will have the 'empty' |
| | 131 | * type. |
| | 132 | */ |
| | 133 | vm_globalvar_t *prv_val; |
| | 134 | |
| | 135 | /* |
| | 136 | * original instruction byte at this breakpoint (we replace the |
| | 137 | * instruction byte with the BP instruction, but we must remember |
| | 138 | * the original for when we clear or disable the breakpoint) |
| | 139 | */ |
| | 140 | char orig_instr_; |
| | 141 | |
| | 142 | /* flag: breakpoint is in use */ |
| | 143 | uint in_use_ : 1; |
| | 144 | |
| | 145 | /* flag: breakpoint has a conditional expression */ |
| | 146 | uint has_cond_ : 1; |
| | 147 | |
| | 148 | /* flag: our condition is stop-on-change, not stop-when-true */ |
| | 149 | uint stop_on_change_ : 1; |
| | 150 | |
| | 151 | /* flag: breakpoint is disabled */ |
| | 152 | uint disabled_ : 1; |
| | 153 | |
| | 154 | }; |
| | 155 | |
| | 156 | /* maximum number of breakpoints */ |
| | 157 | const size_t VMDBG_BP_MAX = 100; |
| | 158 | |
| | 159 | /* ------------------------------------------------------------------------ */ |
| | 160 | /* |
| | 161 | * Structure for saving debugger step modes. This is used to save and |
| | 162 | * restore the step modes before doing a recursive evaluation. |
| | 163 | */ |
| | 164 | struct vmdbg_step_save_t |
| | 165 | { |
| | 166 | int old_step; |
| | 167 | int old_step_in; |
| | 168 | int old_step_out; |
| | 169 | }; |
| | 170 | |
| | 171 | |
| | 172 | /* ------------------------------------------------------------------------ */ |
| | 173 | /* |
| | 174 | * Debugger API object |
| | 175 | */ |
| | 176 | class CVmDebug |
| | 177 | { |
| | 178 | friend class CVmRun; |
| | 179 | friend struct CVmDebugBp; |
| | 180 | |
| | 181 | public: |
| | 182 | CVmDebug(VMG0_); |
| | 183 | ~CVmDebug(); |
| | 184 | |
| | 185 | /* |
| | 186 | * Initialize. The VM will call this after setting up all of the VM |
| | 187 | * globals, but before loading the image file. |
| | 188 | */ |
| | 189 | void init(VMG_ const char *image_filename); |
| | 190 | |
| | 191 | /* |
| | 192 | * Initialization phase 2 - this is called just after we finish |
| | 193 | * loading the image file. The debugger can perform any final |
| | 194 | * set-up that can't be handled until after the image is loaded. |
| | 195 | */ |
| | 196 | void init_after_load(VMG0_); |
| | 197 | |
| | 198 | /* |
| | 199 | * Terminate. The VM will call this when shutting down, before |
| | 200 | * deleting any of the globals. |
| | 201 | */ |
| | 202 | void terminate(VMG0_); |
| | 203 | |
| | 204 | /* |
| | 205 | * Determine if the loaded program was compiled for debugging. |
| | 206 | * We'll check to see if the image loader found a GSYM (global |
| | 207 | * symbol table) block in the file. |
| | 208 | */ |
| | 209 | int image_has_debug_info(VMG0_) const; |
| | 210 | |
| | 211 | /* determine if the debugger has control */ |
| | 212 | int is_in_debugger() const { return in_debugger_ != 0; } |
| | 213 | |
| | 214 | |
| | 215 | /* -------------------------------------------------------------------- */ |
| | 216 | /* |
| | 217 | * Add an entry to a reverse-lookup hash table. The image file |
| | 218 | * loader must call this function for each object, function, and |
| | 219 | * property symbol it loads from the debug records in an image file. |
| | 220 | */ |
| | 221 | void add_rev_sym(const char *sym, size_t sym_len, |
| | 222 | tc_symtype_t sym_type, ulong sym_val); |
| | 223 | |
| | 224 | /* |
| | 225 | * Look up a symbol given the type and identifier. Returns a |
| | 226 | * pointer to a null-terminated string giving the name of the |
| | 227 | * symbol, or null if the given identifier doesn't have an |
| | 228 | * associated symbol. |
| | 229 | */ |
| | 230 | const char *objid_to_sym(vm_obj_id_t objid) const |
| | 231 | { return find_rev_sym(obj_rev_table_, (ulong)objid); } |
| | 232 | |
| | 233 | const char *propid_to_sym(vm_prop_id_t propid) const |
| | 234 | { return find_rev_sym(prop_rev_table_, (ulong)propid); } |
| | 235 | |
| | 236 | const char *funcaddr_to_sym(pool_ofs_t func_addr) const |
| | 237 | { return find_rev_sym(func_rev_table_, (ulong)func_addr); } |
| | 238 | |
| | 239 | const char *enum_to_sym(ulong enum_id) const |
| | 240 | { return find_rev_sym(enum_rev_table_, enum_id); } |
| | 241 | |
| | 242 | /* |
| | 243 | * Given a symbol name, get the final modifying object. If the symbol |
| | 244 | * is a synthesized modified base object, we'll find the actual symbol |
| | 245 | * that was used in the source code with 'modify.' |
| | 246 | */ |
| | 247 | const char *get_modifying_sym(const char *base_sym) const; |
| | 248 | |
| | 249 | |
| | 250 | /* -------------------------------------------------------------------- */ |
| | 251 | /* |
| | 252 | * Method header list |
| | 253 | */ |
| | 254 | |
| | 255 | /* allocate the method header list */ |
| | 256 | void alloc_method_header_list(ulong cnt); |
| | 257 | |
| | 258 | /* |
| | 259 | * Given a code pool address, find the method header containing the |
| | 260 | * address. This searches the method header list for the nearest |
| | 261 | * method header whose address is less than the given address. |
| | 262 | */ |
| | 263 | pool_ofs_t find_method_header(pool_ofs_t ofs); |
| | 264 | |
| | 265 | /* get the number of method headers */ |
| | 266 | ulong get_method_header_cnt() const { return method_hdr_cnt_; } |
| | 267 | |
| | 268 | /* set the given method header list entry */ |
| | 269 | void set_method_header(ulong idx, ulong addr) |
| | 270 | { |
| | 271 | /* store the given entry */ |
| | 272 | method_hdr_[idx] = addr; |
| | 273 | } |
| | 274 | |
| | 275 | /* get the given method header entry */ |
| | 276 | ulong get_method_header(ulong idx) const { return method_hdr_[idx]; } |
| | 277 | |
| | 278 | |
| | 279 | /* -------------------------------------------------------------------- */ |
| | 280 | /* |
| | 281 | * Get information on the source location at a given stack level. |
| | 282 | * If successful, fills in the filename pointer and line number and |
| | 283 | * returns zero. If no source information is available at the stack |
| | 284 | * level, returns non-zero to indicate failure. |
| | 285 | * |
| | 286 | * Level 0 is the current stack level, 1 is the enclosing frame, and |
| | 287 | * so on. |
| | 288 | */ |
| | 289 | int get_source_info(VMG_ const char **fname, unsigned long *linenum, |
| | 290 | int level) const; |
| | 291 | |
| | 292 | |
| | 293 | /* |
| | 294 | * Enumerate local variables at the given stack level - 0 is the |
| | 295 | * current stack level, 1 is the enclosing frame, and so on. |
| | 296 | */ |
| | 297 | void enum_locals(VMG_ void (*cbfunc)(void *, const char *, size_t), |
| | 298 | void *cbctx, int level); |
| | 299 | |
| | 300 | /* |
| | 301 | * Build a stack trace listing, invoking the callback for each level |
| | 302 | * of the stack trace. If 'source_info' is true, we'll include the |
| | 303 | * source file name and line number for each point in the trace. |
| | 304 | */ |
| | 305 | void build_stack_listing(VMG_ |
| | 306 | void (*cbfunc)(void *ctx, |
| | 307 | const char *str, int strl), |
| | 308 | void *cbctx, int source_info); |
| | 309 | |
| | 310 | |
| | 311 | /* -------------------------------------------------------------------- */ |
| | 312 | /* |
| | 313 | * Breakpoints |
| | 314 | */ |
| | 315 | |
| | 316 | /* |
| | 317 | * Toggle a breakpoint at the given code location. Returns zero on |
| | 318 | * success, non-zero on failure. If successful, fills in *bpnum |
| | 319 | * with the breakpoint ID, and fills in *did_set with true if we set |
| | 320 | * a breakpoint, false if we deleted an existing breakpoint at the |
| | 321 | * location. cond is null if the breakpoint is unconditional, or a |
| | 322 | * pointer to a character string giving the source code for an |
| | 323 | * expression that must evaluate to true when the breakpoint is |
| | 324 | * encountered for the breakpoint to suspend execution. |
| | 325 | */ |
| | 326 | int toggle_breakpoint(VMG_ ulong code_addr, |
| | 327 | const char *cond, int change, |
| | 328 | int *bpnum, int *did_set, |
| | 329 | char *errbuf, size_t errbuflen); |
| | 330 | |
| | 331 | /* toggle the disabled status of a breakpoint */ |
| | 332 | void toggle_breakpoint_disable(VMG_ int bpnum); |
| | 333 | |
| | 334 | /* set a breakpoint's disabled status */ |
| | 335 | void set_breakpoint_disable(VMG_ int bpnum, int disable); |
| | 336 | |
| | 337 | /* determine if a breakpoint is disabled - returns true if so */ |
| | 338 | int is_breakpoint_disabled(VMG_ int bpnum); |
| | 339 | |
| | 340 | /* set a breakpoint's condition expression - returns 0 on success */ |
| | 341 | int set_breakpoint_condition(VMG_ int bpnum, const char *cond, int change, |
| | 342 | char *errbuf, size_t errbuflen); |
| | 343 | |
| | 344 | /* delete a breakpoint given the breakpoint ID */ |
| | 345 | void delete_breakpoint(VMG_ int bpnum); |
| | 346 | |
| | 347 | /* -------------------------------------------------------------------- */ |
| | 348 | /* |
| | 349 | * Set execution mode. These calls do not immediately resume |
| | 350 | * execution, but merely set the mode that will be in effect when |
| | 351 | * execution resumes. These calls can only be made while the |
| | 352 | * program is stopped. |
| | 353 | */ |
| | 354 | |
| | 355 | /* |
| | 356 | * Set single-step mode to STEP IN. In this mode, we will stop as |
| | 357 | * soon as we reach a new statement. |
| | 358 | */ |
| | 359 | void set_step_in() |
| | 360 | { |
| | 361 | /* set single-step and step-in modes */ |
| | 362 | single_step_ = TRUE; |
| | 363 | step_in_ = TRUE; |
| | 364 | step_out_ = FALSE; |
| | 365 | } |
| | 366 | |
| | 367 | /* |
| | 368 | * Set DEBUG TRACE mode. This activates single-step mode from within a |
| | 369 | * VM intrinsic. |
| | 370 | */ |
| | 371 | void set_debug_trace() |
| | 372 | { |
| | 373 | /* |
| | 374 | * set single-step mode so that we stop as soon as we're back in |
| | 375 | * byte code |
| | 376 | */ |
| | 377 | set_step_in(); |
| | 378 | |
| | 379 | /* |
| | 380 | * clear any current statement location, so that we stop |
| | 381 | * immediately as soon as we get back to byte code, even if our |
| | 382 | * last debugger entry was in the same line of code |
| | 383 | */ |
| | 384 | cur_stm_start_ = cur_stm_end_ = 0; |
| | 385 | } |
| | 386 | |
| | 387 | /* |
| | 388 | * Set single-step mode for a 'break' key (Ctrl-C, Ctrl+Break, etc., |
| | 389 | * according to local conventions). This asynchronously interrupts the |
| | 390 | * interpreter and breaks into the debugger while the program is |
| | 391 | * running, which is useful to break out of infinite loops or very |
| | 392 | * lengthy processing. |
| | 393 | */ |
| | 394 | void set_break_stop() |
| | 395 | { |
| | 396 | /* set single-step-in mode */ |
| | 397 | set_step_in(); |
| | 398 | |
| | 399 | /* |
| | 400 | * Forget our last execution location, so that we'll stop again |
| | 401 | * even if we haven't moved from our last break location. Since |
| | 402 | * we're explicitly breaking execution, we want to stop whether |
| | 403 | * we've gone anywhere or not. |
| | 404 | */ |
| | 405 | cur_stm_start_ = cur_stm_end_ = 0; |
| | 406 | } |
| | 407 | |
| | 408 | /* |
| | 409 | * Set single-step mode to STEP OVER. In this mode, we will stop as |
| | 410 | * soon as we reach a new statement at the same stack level as the |
| | 411 | * current statement or at an enclosing stack level. |
| | 412 | */ |
| | 413 | void set_step_over(VMG0_); |
| | 414 | |
| | 415 | /* |
| | 416 | * Set single-step mode to STEP OUT. In this mode, we will stop as |
| | 417 | * soon as we reach a new statement at a stack level enclosing the |
| | 418 | * current statement's stack level. |
| | 419 | */ |
| | 420 | void set_step_out(VMG0_); |
| | 421 | |
| | 422 | /* |
| | 423 | * Set single-step mode to GO. In this mode, we won't stop until we |
| | 424 | * reach a breakpoint. |
| | 425 | */ |
| | 426 | void set_go() |
| | 427 | { |
| | 428 | /* clear single-step mode */ |
| | 429 | single_step_ = FALSE; |
| | 430 | step_in_ = FALSE; |
| | 431 | step_out_ = FALSE; |
| | 432 | } |
| | 433 | |
| | 434 | /* |
| | 435 | * Save the step mode in preparation for a recursive execution (of a |
| | 436 | * debugger expression), and set the mode for the recursive |
| | 437 | * execution. Turns off stepping modes. |
| | 438 | */ |
| | 439 | void prepare_for_eval(vmdbg_step_save_t *info) |
| | 440 | { |
| | 441 | /* save the original execution mode */ |
| | 442 | info->old_step = single_step_; |
| | 443 | info->old_step_in = step_in_; |
| | 444 | info->old_step_out = step_out_; |
| | 445 | |
| | 446 | /* set execution mode to RUN */ |
| | 447 | single_step_ = FALSE; |
| | 448 | step_in_ = FALSE; |
| | 449 | step_out_ = FALSE; |
| | 450 | } |
| | 451 | |
| | 452 | /* restore original execution modes after recursive execution */ |
| | 453 | void restore_from_eval(vmdbg_step_save_t *info) |
| | 454 | { |
| | 455 | single_step_ = info->old_step; |
| | 456 | step_in_ = info->old_step_in; |
| | 457 | step_out_ = info->old_step_out; |
| | 458 | } |
| | 459 | |
| | 460 | |
| | 461 | /* -------------------------------------------------------------------- */ |
| | 462 | /* |
| | 463 | * Set the execution pointer to a new location. The new code |
| | 464 | * address is given as an absolute code pool address. The new |
| | 465 | * location must be within the current method. Updates |
| | 466 | * *exec_ofs_ptr with the method offset of the new location. |
| | 467 | */ |
| | 468 | int set_exec_ofs(unsigned int *exec_ofs_ptr, unsigned long code_addr) |
| | 469 | { |
| | 470 | /* set the method offset pointer */ |
| | 471 | *exec_ofs_ptr = (unsigned int)(code_addr - entry_ofs_); |
| | 472 | |
| | 473 | /* success */ |
| | 474 | return 0; |
| | 475 | } |
| | 476 | |
| | 477 | /* |
| | 478 | * Determine if a code location is within the current active method. |
| | 479 | * Returns true if so, false if not. |
| | 480 | */ |
| | 481 | int is_in_current_method(VMG_ unsigned long code_addr); |
| | 482 | |
| | 483 | /* -------------------------------------------------------------------- */ |
| | 484 | /* |
| | 485 | * Evaluate an expression. Returns zero on success, non-zero on |
| | 486 | * failure. 'level' is the stack level at which to evaluate the |
| | 487 | * expression - 0 is the currently active method, 1 is its caller, |
| | 488 | * and so on. |
| | 489 | */ |
| | 490 | int eval_expr(VMG_ char *buf, size_t buflen, const char *expr, |
| | 491 | int level, int *is_lval, int *is_openable, |
| | 492 | void (*aggcb)(void *, const char *, int, const char *), |
| | 493 | void *aggctx, int speculative); |
| | 494 | |
| | 495 | /* |
| | 496 | * Compile an expression. Fills in *code_obj with a handle to the |
| | 497 | * code pool object containing the compiled byte-code. If dst_buf |
| | 498 | * is non-null, we will format a message describing any error that |
| | 499 | * occurs into the buffer. |
| | 500 | * |
| | 501 | * We'll compile the expression using the given local symbol table |
| | 502 | * and the given active stack level. Note that the local symbol |
| | 503 | * table specified need not be the local symbol table for the given |
| | 504 | * active stack level, since we're only compiling, not evaluating, |
| | 505 | * the expression; for example, when compiling a condition |
| | 506 | * expression for a breakpoint, we'll normally compile the |
| | 507 | * expression in the context of the breakpoint's source location, |
| | 508 | * which might not even be in the active stack when the condition is |
| | 509 | * compiled. |
| | 510 | */ |
| | 511 | int compile_expr(VMG_ const char *expr, |
| | 512 | int level, class CVmDbgSymtab *local_symtab, |
| | 513 | int self_valid, int speculative, int *is_lval, |
| | 514 | class CVmPoolDynObj **code_obj, |
| | 515 | char *dst_buf, size_t dst_buf_len); |
| | 516 | |
| | 517 | /* -------------------------------------------------------------------- */ |
| | 518 | /* |
| | 519 | * Get/set the UI context. This information is for use by the UI |
| | 520 | * code. We don't use the UI context for anything; we just maintain |
| | 521 | * it so that the UI can keep track of what's going on between |
| | 522 | * calls. |
| | 523 | */ |
| | 524 | void *get_ui_ctx() const { return ui_ctx_; } |
| | 525 | void set_ui_ctx(void *ctx) { ui_ctx_ = ctx; } |
| | 526 | |
| | 527 | protected: |
| | 528 | /* -------------------------------------------------------------------- */ |
| | 529 | /* |
| | 530 | * CVmRun API - the byte-code execution loop uses these routines |
| | 531 | */ |
| | 532 | |
| | 533 | /* |
| | 534 | * Step into the debugger - the byte-code execution loop calls this |
| | 535 | * just before executing each instruction when the interpreter is in |
| | 536 | * single-step mode, or when a breakpoint is encountered. We'll |
| | 537 | * activate the debugger user interface if necessary. This returns |
| | 538 | * in order to resume execution. bp is true if we encountered a |
| | 539 | * breakpoint, false if we're single-stepping. |
| | 540 | */ |
| | 541 | void step(VMG_ const uchar **pc_ptr, pool_ofs_t, int bp, |
| | 542 | int error_code); |
| | 543 | |
| | 544 | /* |
| | 545 | * synchronize the internal execution point - acts like a step() |
| | 546 | * without actually stopping |
| | 547 | */ |
| | 548 | void sync_exec_pos(VMG_ const uchar *pc_ptr, |
| | 549 | pool_ofs_t method_start_ofs); |
| | 550 | |
| | 551 | /* |
| | 552 | * Get single-step mode - returns true if we're stopping at each |
| | 553 | * instruction, false if we're running until we hit a breakpoint. |
| | 554 | * The byte-code execution loop should call step() on each |
| | 555 | * instruction if this returns true. |
| | 556 | * |
| | 557 | * We must also single-step through code if there are any active |
| | 558 | * global breakpoints, even when the user is not interactively |
| | 559 | * single-stepping. |
| | 560 | */ |
| | 561 | int is_single_step() const |
| | 562 | { return single_step_ || global_bp_cnt_ != 0; } |
| | 563 | |
| | 564 | |
| | 565 | /* -------------------------------------------------------------------- */ |
| | 566 | /* |
| | 567 | * Internal operations |
| | 568 | */ |
| | 569 | |
| | 570 | /* |
| | 571 | * Get information on the execution location at the given stack |
| | 572 | * level |
| | 573 | */ |
| | 574 | int get_stack_level_info(VMG_ int level, class CVmFuncPtr *func_ptr, |
| | 575 | class CVmDbgLinePtr *line_ptr, |
| | 576 | ulong *stm_start, ulong *stm_end) const; |
| | 577 | |
| | 578 | /* |
| | 579 | * look up a symbol in one of our reverse mapping tables |
| | 580 | */ |
| | 581 | const char *find_rev_sym(const class CVmHashTable *hashtab, |
| | 582 | ulong val) const; |
| | 583 | |
| | 584 | /* |
| | 585 | * Format a value into a buffer |
| | 586 | */ |
| | 587 | void format_val(VMG_ char *buf, size_t buflen, |
| | 588 | const struct vm_val_t *val); |
| | 589 | |
| | 590 | /* find a breakpoint given a code address */ |
| | 591 | CVmDebugBp *find_bp(ulong code_addr); |
| | 592 | |
| | 593 | /* allocate a new breakpoint record */ |
| | 594 | CVmDebugBp *alloc_bp(); |
| | 595 | |
| | 596 | /* |
| | 597 | * suspend/resume breakpoints - this doesn't affect the permanent |
| | 598 | * status of any breakpoint, but simply allows for removal of BP |
| | 599 | * instructions from the code while the debugger has control |
| | 600 | */ |
| | 601 | void suspend_all_bps(VMG0_); |
| | 602 | void restore_all_bps(VMG0_); |
| | 603 | |
| | 604 | private: |
| | 605 | /* flag: debugger has control */ |
| | 606 | int in_debugger_ : 1; |
| | 607 | |
| | 608 | /* |
| | 609 | * single-step mode - if this is true, we're stepping through code; |
| | 610 | * otherwise, we're running until we hit a breakpoint |
| | 611 | */ |
| | 612 | int single_step_ : 1; |
| | 613 | |
| | 614 | /* |
| | 615 | * step-in mode - if this is true, and single_step_ is true, we'll |
| | 616 | * break as soon as we reach a new statement anywhere; otherwise, |
| | 617 | * we'll stop only if we reach a new statement at the same stack |
| | 618 | * level as the current statement (in other words, we're stepping |
| | 619 | * over subroutine calls made by the current statement) |
| | 620 | */ |
| | 621 | int step_in_ : 1 ; |
| | 622 | |
| | 623 | /* |
| | 624 | * Step-out mode - if this is true, and single_step_ is true, we'll |
| | 625 | * break as soon as we reach a new statement outside the current |
| | 626 | * call level. (This mode flag is actually only important for |
| | 627 | * native code interaction, because we actually know to stop on a |
| | 628 | * step-out using step-over with an enclosing stack level.) |
| | 629 | */ |
| | 630 | int step_out_ : 1; |
| | 631 | |
| | 632 | /* |
| | 633 | * step-over-breakpoint mode - if this is true, we're stepping over |
| | 634 | * a breakpoint |
| | 635 | */ |
| | 636 | int step_over_bp_ : 1; |
| | 637 | |
| | 638 | /* |
| | 639 | * Original step flags - these store the step flags that will be in |
| | 640 | * effect after we finish a step_over_bp operation |
| | 641 | */ |
| | 642 | int orig_single_step_ : 1; |
| | 643 | int orig_step_in_ : 1; |
| | 644 | int orig_step_out_ : 1; |
| | 645 | |
| | 646 | /* |
| | 647 | * flag: we've been initialized during program load; when this flag is |
| | 648 | * set, we'll have to make corresponding uninitializations when the |
| | 649 | * program terminates |
| | 650 | */ |
| | 651 | int program_inited_ : 1; |
| | 652 | |
| | 653 | /* breakpoint being stepped over */ |
| | 654 | CVmDebugBp *step_over_bp_bp_; |
| | 655 | |
| | 656 | /* |
| | 657 | * Step-resume stack frame level. When step_in_ is false, we won't |
| | 658 | * resume stepping code until the frame pointer is at this level or |
| | 659 | * an enclosing level. |
| | 660 | */ |
| | 661 | size_t step_frame_depth_; |
| | 662 | |
| | 663 | /* |
| | 664 | * Method header for the current function. We set this each time we |
| | 665 | * enter the debugger via step(). |
| | 666 | */ |
| | 667 | CVmFuncPtr func_ptr_; |
| | 668 | |
| | 669 | /* |
| | 670 | * Debug records pointer for the current function. We set this each |
| | 671 | * time we enter the debugger via step(). Note that we must keep |
| | 672 | * track of whether the debug pointer is valid, because some |
| | 673 | * functions might not have debug tables at all. |
| | 674 | */ |
| | 675 | CVmDbgTablePtr dbg_ptr_; |
| | 676 | int dbg_ptr_valid_ : 1; |
| | 677 | |
| | 678 | /* function header pointer for current function */ |
| | 679 | pool_ofs_t entry_ofs_; |
| | 680 | |
| | 681 | /* current program counter */ |
| | 682 | pool_ofs_t pc_; |
| | 683 | |
| | 684 | /* debugger line records for current statement */ |
| | 685 | CVmDbgLinePtr cur_stm_line_; |
| | 686 | |
| | 687 | /* |
| | 688 | * Current statement boundaries. We set this each time we enter the |
| | 689 | * debugger via step(). We usually step through code until we reach |
| | 690 | * the beginning of a new statement; we use these boundaries to |
| | 691 | * determine when we leave the current statement. As long as the |
| | 692 | * current byte-code offset is within these boundaries, (inclusive), |
| | 693 | * we know we're within the same statement. |
| | 694 | */ |
| | 695 | ulong cur_stm_start_; |
| | 696 | ulong cur_stm_end_; |
| | 697 | |
| | 698 | /* |
| | 699 | * User interface context. We maintain this location for use by the |
| | 700 | * UI code to store its context information. |
| | 701 | */ |
| | 702 | void *ui_ctx_; |
| | 703 | |
| | 704 | /* |
| | 705 | * Reverse-mapping hash tables for various symbol types. These hash |
| | 706 | * tables allow us to find the symbol for a given object ID, |
| | 707 | * property ID, or function address. |
| | 708 | */ |
| | 709 | class CVmHashTable *obj_rev_table_; |
| | 710 | class CVmHashTable *prop_rev_table_; |
| | 711 | class CVmHashTable *func_rev_table_; |
| | 712 | class CVmHashTable *enum_rev_table_; |
| | 713 | |
| | 714 | /* breakpoints */ |
| | 715 | CVmDebugBp bp_[VMDBG_BP_MAX]; |
| | 716 | |
| | 717 | /* |
| | 718 | * Number of global breakpoints in effect (when this is non-zero, we |
| | 719 | * must trace through code even in 'go' mode, so we can evaluate the |
| | 720 | * global breakpoints repeatedly and thereby catch when the first |
| | 721 | * one hits). This only counts enabled global breakpoints - if a |
| | 722 | * global breakpoint is disabled it must be removed from this count, |
| | 723 | * since we won't need to consider it when checking for hits. |
| | 724 | */ |
| | 725 | int global_bp_cnt_; |
| | 726 | |
| | 727 | /* host interface (for the compiler's use) */ |
| | 728 | class CTcHostIfcDebug *hostifc_; |
| | 729 | |
| | 730 | /* |
| | 731 | * method header list - this is a list of the method headers in the |
| | 732 | * program, sorted by address |
| | 733 | */ |
| | 734 | ulong *method_hdr_; |
| | 735 | ulong method_hdr_cnt_; |
| | 736 | }; |
| | 737 | |
| | 738 | /* ------------------------------------------------------------------------ */ |
| | 739 | /* |
| | 740 | * Debugger programmatic interface to the user interface. This is to be |
| | 741 | * implemented by each UI subsystem. |
| | 742 | */ |
| | 743 | class CVmDebugUI |
| | 744 | { |
| | 745 | public: |
| | 746 | /* |
| | 747 | * Initialize. We'll call this once before entering any other |
| | 748 | * functions, so that the UI code can set up its private |
| | 749 | * information. The UI can create a private context and store a |
| | 750 | * pointer via G_debugger->set_ui_ctx(). |
| | 751 | */ |
| | 752 | static void init(VMG_ const char *image_filename); |
| | 753 | |
| | 754 | /* |
| | 755 | * Initialization, phase 2 - this is called just after we've |
| | 756 | * finished loading the image file. |
| | 757 | */ |
| | 758 | static void init_after_load(VMG0_); |
| | 759 | |
| | 760 | /* |
| | 761 | * Terminate. We'll call this before terminating, to allow the UI |
| | 762 | * code to release any resources it allocated. |
| | 763 | */ |
| | 764 | static void terminate(VMG0_); |
| | 765 | |
| | 766 | /* |
| | 767 | * Invoke the UI main command loop entrypoint. The engine calls |
| | 768 | * this whenever we hit a breakpoint or reach a single-step point. |
| | 769 | * This routine should not return until it is ready to let the |
| | 770 | * program continue execution. |
| | 771 | */ |
| | 772 | static void cmd_loop(VMG_ int bp_number, |
| | 773 | int error_code, unsigned int *exec_ofs); |
| | 774 | }; |
| | 775 | |
| | 776 | |
| | 777 | /* ------------------------------------------------------------------------ */ |
| | 778 | /* |
| | 779 | * Hash table entry for reverse-mapping hash tables. These tables allow |
| | 780 | * the debugger to look up a symbol name given the symbol's value: an |
| | 781 | * object ID, a property ID, or a function address. We map each of |
| | 782 | * these types of values to a ulong value, which we use as the hash key. |
| | 783 | */ |
| | 784 | class CVmHashEntryDbgRev: public CVmHashEntry |
| | 785 | { |
| | 786 | public: |
| | 787 | /* |
| | 788 | * Construct the entry. sym_val is the symbol's value (an object |
| | 789 | * ID, a property ID, or a function address) coerced to a ulong. |
| | 790 | */ |
| | 791 | CVmHashEntryDbgRev(ulong sym_val, const char *sym, size_t len); |
| | 792 | ~CVmHashEntryDbgRev(); |
| | 793 | |
| | 794 | /* check for a match */ |
| | 795 | virtual int matches(const char *str, size_t len) const; |
| | 796 | |
| | 797 | /* get the symbol name for this entry */ |
| | 798 | const char *get_sym() const { return sym_; } |
| | 799 | size_t get_sym_len() const { return sym_len_; } |
| | 800 | |
| | 801 | protected: |
| | 802 | char *sym_; |
| | 803 | size_t sym_len_; |
| | 804 | }; |
| | 805 | |
| | 806 | /* |
| | 807 | * Hash function for reverse mapping tables. This hash function doesn't |
| | 808 | * make any assumptions about the range of character values, since we're |
| | 809 | * using sequences of raw binary bytes for hash keys. |
| | 810 | */ |
| | 811 | class CVmHashFuncDbgRev: public CVmHashFunc |
| | 812 | { |
| | 813 | public: |
| | 814 | unsigned int compute_hash(const char *str, size_t len) const; |
| | 815 | }; |
| | 816 | |
| | 817 | |
| | 818 | /* ------------------------------------------------------------------------ */ |
| | 819 | /* |
| | 820 | * Condition compilation macros. Some files can be compiled for |
| | 821 | * stand-alone use or use within a debugger application; when compiled |
| | 822 | * stand-alone, certain debugger-related operations are removed, which |
| | 823 | * can improve performance over the debugger-enabled version. |
| | 824 | */ |
| | 825 | #ifdef VM_DEBUGGER |
| | 826 | /* |
| | 827 | * DEBUGGER-ENABLED VERSION |
| | 828 | */ |
| | 829 | |
| | 830 | /* include debugger-only code */ |
| | 831 | #define VM_IF_DEBUGGER(x) x |
| | 832 | |
| | 833 | /* do NOT include non-debugger-only code */ |
| | 834 | #define VM_IF_NOT_DEBUGGER(x) |
| | 835 | |
| | 836 | #else /* VM_DEBUGGER */ |
| | 837 | /* |
| | 838 | * STAND-ALONE (NON-DEBUGGER) VERSION |
| | 839 | */ |
| | 840 | |
| | 841 | /* do NOT include debugger-only code in a stand-alone version */ |
| | 842 | #define VM_IF_DEBUGGER(x) |
| | 843 | |
| | 844 | /* include non-debugger-only code */ |
| | 845 | #define VM_IF_NOT_DEBUGGER(x) x |
| | 846 | |
| | 847 | #endif /* VM_DEBUGGER */ |
| | 848 | |
| | 849 | #endif /* VMDBG_H */ |
| | 850 | |