module: access-path-implementation synopsis: Implementation of stop reasons author: Paul Howard Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND // Stop reason integer codes...(in no particular order) define constant $timed-out = 0; define constant $timed-out-handled = 32; define constant $timed-out-unhandled = 33; define constant $access-violation = 1; define constant $array-bounds-exception = 2; define constant $illegal-instruction-exception = 3; define constant $privileged-instruction-exception = 4; define constant $denormal-exception = 5; define constant $float-divide-by-zero-exception = 6; define constant $inexact-result-exception = 7; define constant $invalid-float-operation-exception = 8; define constant $float-overflow-exception = 9; define constant $float-underflow-exception = 10; define constant $float-stack-check-exception = 11; define constant $integer-divide-by-zero-exception = 12; define constant $noncontinuable-exception = 13; define constant $breakpoint-exception = 14; define constant $hard-coded-breakpoint-exception = 15; define constant $single-step-exception = 16; define constant $create-process = 17; define constant $exit-process = 18; define constant $create-thread = 19; define constant $exit-thread = 20; define constant $load-dll = 21; define constant $unload-dll = 22; // define constant $RIP = 23; define constant $output-debug-string = 24; define constant $profiler = 25; define constant $profiler-unhandled = 34; define constant $unclassified = 26; define constant $integer-overflow-exception = 27; define constant $stack-overflow-exception = 28; define constant $source-step-over = 29; define constant $source-step-out = 30; define constant $source-step-into = 31; define constant $step-operation-step-out = 7; define constant $step-operation-step-over = 8; define constant $step-operation-step-into = 9; ///// // The most general reason why an application stops internally. This is // the root of a fairly large hierarchy. define abstract sealed class () end class; ///// // Stop-reasons defined by the access path, representing debug events // within the application. define abstract sealed class () constant slot stop-reason-process :: , required-init-keyword: process:; constant slot stop-reason-thread :: , required-init-keyword: thread:; end class; ///// // Stop reasons defined by the access path, representing system-level // debug events generated from within the application. define abstract sealed class () end class; ///// // Stop reasons NOT defined by the access path. These represent // stop-reasons generated by the application, but not at system level. define abstract open class () end class; // The application can now timeout on an incoming debug event // (e.g. page faults), for which the debugger needs to pass back // an unhandled exception; this stop-reason models those special // circumstances define abstract open class () end class; ///// // Stop reasons NOT defined by the access path. Left as an open class // for clients to expand upon. define abstract open class () end class; ///// // The stop reason generated by the profiler engine when its time // to gather statistical data from the stack. define class () end class; // Models profiler timeouts on incoming unhandled debug events define class (, ) end class; define class () end class; // Models timeouts on incoming handled & unhandled debug events define class () end class; define class (, ) end class; define abstract class () end class; define class () constant slot stop-reason-executable-component :: , required-init-keyword: executable:; end class; define class () constant slot stop-reason-process-exit-code :: , required-init-keyword: exit-code:; end class; define abstract class () end class; define class () end class; define class () constant slot stop-reason-thread-exit-code :: , required-init-keyword: exit-code:; end class; define abstract class () constant slot stop-reason-library :: , required-init-keyword: library:; end class; define class () end class; define class () end class; define class () constant slot stop-reason-exit-code :: , required-init-keyword: exit-code:; end class; define class () constant slot stop-reason-debug-string :: , required-init-keyword: debug-string:; end class; define abstract class () constant slot stop-reason-debug-point-address :: , required-init-keyword: address:; end class; define class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define abstract class () constant slot stop-reason-exception-address :: , required-init-keyword: exception-address:; constant slot stop-reason-exception-first-chance? :: , init-value: #f, init-keyword: first-chance?:; end class; define abstract class () end class; define class () end class; define class () end class; define abstract class () end class; define constant $access-violation-undecidable = 0; define constant $access-violation-on-read = 1; define constant $access-violation-on-write = 2; define constant $access-violation-on-execute = 3; define class () constant slot stop-reason-access-violation-address :: , required-init-keyword: violation-address:; constant slot stop-reason-access-violation-operation :: , required-init-keyword: violation-operation:; end class; define class () end class; define abstract class () end class; define class () end class; define class () end class; define abstract class () end class; define abstract class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define abstract class () end class; define class () end class; define class () end class; define class () end class; define class () end class; define class () end class; ///// WAIT-FOR-STOP-REASON define method wait-for-stop-reason (ap :: , #key timeout = #f, profile-interval = #f) => (maybe-sr :: false-or()) let sr = #f; if (timeout) sr := wait-for-stop-reason-with-timeout (ap.connection, timeout, profile-at: profile-interval) else sr := wait-for-stop-reason-no-timeout (ap.connection, profile-at: profile-interval) end if; if (sr ~= $timed-out) set-application-state (ap, #"stopped"); construct-stop-reason (ap, sr); else #f end if; end method; ///// WAIT-FOR-STOP-REASON-WITH-TIMEOUT // Called if the timeout keyword is supplied. define open generic wait-for-stop-reason-with-timeout (conn :: , timeout :: , #key) => (code :: ); define method wait-for-stop-reason-with-timeout (conn :: , timeout :: , #key profile-at = #f) => (code :: ) let code :: = if (profile-at) nub-profile-wait-for-stop-reason-with-timeout (conn.connection-process, timeout, profile-at); else nub-wait-for-stop-reason-with-timeout (conn.connection-process, timeout); end if; code; end method; ///// WAIT-FOR-STOP-REASON-NO-TIMEOUT // Called if no timeout keyword is supplied. define open generic wait-for-stop-reason-no-timeout (conn :: , #key) => (code :: ); define method wait-for-stop-reason-no-timeout (conn :: , #key profile-at = #f) => (code :: ) let code :: = if (profile-at) nub-profile-wait-for-stop-reason-no-timeout (conn.connection-process, profile-at); else nub-wait-for-stop-reason-no-timeout (conn.connection-process); end if; code; end method; ///// GET-DEBUG-EVENT-PROCESS-EXIT-CODE // Given that the last received stop reason was an // , this returns the exit code. define open generic get-debug-event-process-exit-code (conn :: ) => (code :: ); define method get-debug-event-process-exit-code (conn :: ) => (code :: ) let raw-code = nub-stop-reason-process-exit-code (conn.connection-process); if (instance?(raw-code, )) raw-code else 0 end if; end method; ///// GET-DEBUG-EVENT-THREAD-EXIT-CODE // Given that the last received stop reason was an // , this returns the exit code. define open generic get-debug-event-thread-exit-code (conn :: ) => (code :: ); define method get-debug-event-thread-exit-code (conn :: ) => (code :: ) let raw-code = nub-stop-reason-thread-exit-code (conn.connection-process); if (instance?(raw-code, )) raw-code else 0 end if end method; ///// GET-DEBUG-EVENT-STRING-INFORMATION // Given that the last received stop reason was an // , grab the address // and length of the string from the debugger nub. define open generic get-debug-event-string-information (conn :: ) => (addr :: , sz :: , unicode? :: ); define method get-debug-event-string-information (conn :: ) => (addr :: , sz :: , unicode? :: ) let string-addr = nub-stop-reason-debug-string-address(conn.connection-process); let string-len = nub-stop-reason-debug-string-length(conn.connection-process); let unicode-answer = nub-stop-reason-debug-string-is-unicode(conn.connection-process); unless(instance?(string-len, )) string-len := 0 end unless; values (string-addr, string-len, unicode-answer == 1); end method; ///// GET-DEBUG-EVENT-LIBRARY // Given that the last received stop reason was one that has to // do with a remote-library, this returns a handle on the affected // library. define open generic get-debug-event-library (conn :: ) => (lib :: ); define method get-debug-event-library (conn :: ) => (lib :: ) nub-stop-reason-library (conn.connection-process); end method; ///// GET-DEBUG-EVENT-THREAD // All stop reasons are associated with the thread that generated // them. This function returns a handle on that thread. define open generic get-debug-event-thread (conn :: )=> (thr :: ); define method get-debug-event-thread (conn :: )=> (thr :: ) nub-stop-reason-thread (conn.connection-process); end method; ///// GET-DEBUG-EVENT-PROCESS // This function is currently pointless, since there is only one // . define open generic get-debug-event-process (conn :: ) => (proc :: ); define method get-debug-event-process (conn :: ) => (proc :: ) let nub-process = nub-stop-reason-process (conn.connection-process); let process = make (, nub-descriptor: nub-process); process; end method; ///// GET-EXCEPTION-ADDRESS // Returns the address at which the last exception was encountered. // We only currently make use of this for finding out where a // breakpoint was hit. We could perhaps make more use of it... define open generic get-exception-address (conn :: ) => (ptr :: ); define method get-exception-address (conn :: ) => (ptr :: ) nub-stop-reason-exception-address (conn.connection-process); end method; ///// EXCEPTION-IS-FIRST-CHANCE? // Decides whether an exception is a first-chance exception define open generic exception-is-first-chance? (conn :: ) => (answer :: ); define method exception-is-first-chance? (conn :: ) => (answer :: ) let int-answer = nub-exception-first-chance (conn.connection-process); int-answer == 1 end method; ///// GET-EXCEPTION-VIOLATION-ADDRESS // Returns the address that the application was trying to access when // an access violation occurred. define open generic get-exception-violation-address (conn :: ) => (ptr :: ); define method get-exception-violation-address (conn :: ) => (ptr :: ) nub-stop-reason-violation-address (conn.connection-process); end method; ///// GET-EXCEPTION-VIOLATION-OP // Returns a code for the operation that the application was trying // to perform when an access violation occured. define open generic get-exception-violation-op (conn :: ) => (op :: ); define method get-exception-violation-op (conn :: ) => (op :: ) nub-stop-reason-violation-op (conn.connection-process); end method; ///// FIRST-DEBUGGER-INVOCATION? // Is this the first time that the application has signalled a hard-coded // breakpoint? // If so, we will assume this breakpoint to be "special", and that it // is a signal that the application, and all of its shared libraries, // have loaded and performed their system-level initializations. On // Windows, this is true for free. Debugger nubs on other platforms, // however, might need to force the generation of this stop reason. define open generic first-debugger-invocation? (conn :: ) => (well? :: ); define method first-debugger-invocation? (conn :: ) => (well? :: ) let int-answer = nub-first-hard-coded-breakpoint(conn.connection-process); int-answer == 1 end method; ///// CONSTRUCT-STOP-REASON // Given a low-level stop-reason code, this constructs a high-level // stop reason event object. (Returns #f if the code indicates a // timeout). define method construct-stop-reason (ap :: , event-type :: , #key process, thread) => (maybe-sr :: false-or()) let source-process :: = process | get-debug-event-process (ap.connection); let source-thread = if (thread) thread else unless (profiler-event?(event-type)) find-or-make-thread (ap, get-debug-event-thread (ap.connection)) end unless; end if; let source-library = #f; let stop-reason = #f; if (source-thread) select (event-type) $exit-thread, $exit-process, $create-thread, $create-process => #f; otherwise => update-thread-stack-size-on-connection(ap.connection, source-thread); end select; end if; select (event-type) $timed-out => stop-reason := #f; $timed-out-handled => stop-reason := make(); $timed-out-unhandled => stop-reason := make(); $create-process => source-library := find-or-make-library (ap, get-debug-event-library (ap.connection)); stop-reason := make (, process: source-process, thread: source-thread, executable: source-library); register-access-path(ap); $create-thread => stop-reason := make (, process: source-process, thread: source-thread); $exit-process => stop-reason := make (, process: source-process, thread: source-thread, exit-code: get-debug-event-process-exit-code (ap.connection)); deregister-access-path(ap); $exit-thread => stop-reason := make (, process: source-process, thread: source-thread, exit-code: get-debug-event-thread-exit-code (ap.connection)); ap.threads := remove! (ap.threads, source-thread); $load-dll => source-library := find-or-make-library (ap, get-debug-event-library (ap.connection)); stop-reason := make (, process: source-process, thread: source-thread, library: source-library); $unload-dll => source-library := find-or-make-library (ap, get-debug-event-library (ap.connection)); stop-reason := make (, process: source-process, thread: source-thread, library: source-library); ap.libraries := remove! (ap.libraries, source-library); $output-debug-string => let (string-address, string-length, string-unicode?) = get-debug-event-string-information(ap.connection); let str = ""; if (~string-unicode?) block () str := read-byte-string (ap, string-address, string-length); exception () str := "" end block; end if; stop-reason := make (, process: source-process, thread: source-thread, debug-string: str); $access-violation => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), violation-address: get-exception-violation-address(ap.connection), violation-operation: get-exception-violation-op(ap.connection), process: source-process, thread: source-thread); $array-bounds-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $illegal-instruction-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $privileged-instruction-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $denormal-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $float-divide-by-zero-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $inexact-result-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $invalid-float-operation-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $float-overflow-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $float-underflow-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $float-stack-check-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $integer-divide-by-zero-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $noncontinuable-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $integer-overflow-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $stack-overflow-exception => stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); $breakpoint-exception => stop-reason := make (, process: source-process, thread: source-thread, address: get-exception-address (ap.connection)); $source-step-out => stop-reason := make (, process: source-process, thread: source-thread, address: get-exception-address (ap.connection)); $source-step-into => stop-reason := make (, process: source-process, thread: source-thread, address: get-exception-address (ap.connection)); $source-step-over => stop-reason := make (, process: source-process, thread: source-thread, address: get-exception-address (ap.connection)); $hard-coded-breakpoint-exception => if (first-debugger-invocation?(ap.connection)) stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); else stop-reason := make (, exception-address: get-exception-address(ap.connection), first-chance?: exception-is-first-chance?(ap.connection), process: source-process, thread: source-thread); end if; $single-step-exception => stop-reason := make (, process: source-process, thread: source-thread, address: get-exception-address (ap.connection)); $profiler => stop-reason := make (); $profiler-unhandled => stop-reason := make (); otherwise => stop-reason := make (, process: source-process, thread: source-thread, first-chance?: exception-is-first-chance?(ap.connection), exception-address: get-exception-address (ap.connection)); end select; stop-reason; end method; define method profiler-event? (event :: ) => (profiling? :: ) select (event) $profiler, $profiler-unhandled => #t; otherwise => #f; end; end method; define constant $exception-subset = vector (, , , , , , , , , , , , , , , ); ///// RECEIVABLE-FIRST-CHANCE-EXCEPTIONS // Returns the set of exception classes that the debugger is capable of // receiving and processing at first-chance. define method receivable-first-chance-exceptions (ap :: ) => (seq :: ) if (ap.cached-receivable-first-chance-exceptions?) ap.cached-exception-set else let exceptions = make (, size: 0); for (exception-class in $exception-subset) let (name, code) = exception-information (exception-class); if (connection-can-receive-first-chance (ap.connection, code)) exceptions := add!(exceptions, exception-class); end if end for; ap.cached-receivable-first-chance-exceptions? := #t; ap.cached-exception-set := exceptions; exceptions; end if; end method; ///// CONNECTION-CAN-RECEIVE-FIRST-CHANCE // Calls the nub to determine whether an exception is first-chance // receivable. define open generic connection-can-receive-first-chance (conn :: , code :: ) => (yes-or-no :: ); define method connection-can-receive-first-chance (conn :: , code :: ) => (yes-or-no :: ) let answer = nub-can-receive-first-chance(conn.connection-process, code); if (answer == 1) #t else #f end if end method; ///// RECEIVING-FIRST-CHANCE? // Is the given exception class currently being first-chance processed // at the access-path level? define method receiving-first-chance? (ap :: , etype :: ) => (yes-or-no :: ) if (member?(etype, receivable-first-chance-exceptions(ap))) if (member?(etype, ap.first-chance-exception-set)) #t else #f end if else #f end if end method; ///// EXCEPTION-INFORMATION // Returns the integer code for the stop-reason class, along with its name. define method exception-information (exception-class == ) => (name :: , code :: ) values ("Access Violation", $access-violation) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Unclassified Exception", $unclassified) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Array Bounds Exceeded", $array-bounds-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Illegal Instruction", $illegal-instruction-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Privileged Instruction", $privileged-instruction-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Integer Overflow", $integer-overflow-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Stack Overflow", $stack-overflow-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Float Denormal", $denormal-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Float Division By Zero", $float-divide-by-zero-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Inexact Float Result", $inexact-result-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Invalid Float Operation", $invalid-float-operation-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Floating Point Overflow", $float-overflow-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Floating Point Underflow", $float-underflow-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Floating Point Stack Error", $float-stack-check-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Integer Division By Zero", $integer-divide-by-zero-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Noncontinuable Exception", $noncontinuable-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Breakpoint", $breakpoint-exception) end method; define method exception-information (exception-class == ) => (name :: , code :: ) values ("Hard Coded Breakpoint", $hard-coded-breakpoint-exception) end method; ///// EXCEPTION-NAME // Returns printable text for the exception class. define method exception-name (ap :: , ex :: ) => (name :: ) let (name, code) = exception-information(ex); name; end method; ///// RECEIVING-FIRST-CHANCE?-SETTER // Filters (or unfilters) first-chance occurrences of the given exception // type. define method receiving-first-chance?-setter (set == #t, ap :: , etype :: ) => (b :: ) if (member?(etype, receivable-first-chance-exceptions(ap))) unless (member?(etype, ap.first-chance-exception-set)) let (name, code) = exception-information(etype); ap.first-chance-exception-set := add!(ap.first-chance-exception-set, etype); connection-set-first-chance(ap.connection, code); end unless; end if; #t; end method; define open generic connection-set-first-chance (conn :: , code :: ) => (); define method connection-set-first-chance (conn :: , code :: ) => () nub-set-first-chance(conn.connection-process, code); end method; define method receiving-first-chance?-setter (set == #f, ap :: , etype :: ) => (b :: ) if (member?(etype, receivable-first-chance-exceptions(ap))) if (member?(etype, ap.first-chance-exception-set)) let (name, code) = exception-information(etype); ap.first-chance-exception-set := remove!(ap.first-chance-exception-set, etype); connection-unset-first-chance(ap.connection, code); end if; end if; #f; end method; define open generic connection-unset-first-chance (conn :: , code :: ) => (); define method connection-unset-first-chance (conn :: , code :: ) => () nub-unset-first-chance(conn.connection-process, code); end method; ///// FIRST-CHANCE-EXCEPTION? // Is a thread stopped at a first-chance exception or not? define method first-chance-exception? (app :: , thread :: ) => (b :: ) connection-thread-stopped-at-first-chance?(app.connection, thread); end method; define open generic connection-thread-stopped-at-first-chance? (conn :: , thread :: ) => (b :: ); define method connection-thread-stopped-at-first-chance? (conn :: , thread :: ) => (b :: ) let (code, fchance) = nub-thread-stop-information(conn.connection-process, thread.nub-descriptor); if (fchance == 1) #t else #f end if end method;