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 $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 variable *last-stop-reason* = #f; ///// // 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 () slot stop-reason-process :: , required-init-keyword: process:; slot stop-reason-thread :: , required-init-keyword: thread:; end class; ///// // An internal stop reason that is language-independant. define abstract sealed class () end class; ///// // An internal stop-reason that is NOT defined by the access path. // The subtree under this class is defined by a client of the access // path who wants to model stop-reasons with respect to the language // they are trying to analyse. 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; define class () end class; define abstract class () end class; define class () end class; define class () slot stop-reason-process-exit-code :: , required-init-keyword: exit-code:; end class; define abstract class () end class; define class () end class; define class () slot stop-reason-thread-exit-code :: , required-init-keyword: exit-code:; end class; define abstract class () slot stop-reason-library :: , required-init-keyword: library:; end class; define class () end class; define class () end class; define class () slot stop-reason-exit-code :: , required-init-keyword: exit-code:; end class; define class () slot stop-reason-debug-string :: , required-init-keyword: debug-string:; end class; define abstract class () 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 abstract class () 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 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; ///// WAIT-FOR-STOP-REASON define method wait-for-stop-reason (ap :: , #key timeout = #f, profile-interval = #f) 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 method wait-for-stop-reason-with-timeout (conn :: , timeout :: , #key profile-at = #f) => (_ :: ) let code = $timed-out; if (size(conn.process.stop-reason-queue) > 0) *last-stop-reason* := pop-last(conn.process.stop-reason-queue); code := *last-stop-reason*.stop-reason-code; else machine-cycle(conn.process); if (size(conn.process.stop-reason-queue) > 0) *last-stop-reason* := pop-last(conn.process.stop-reason-queue); code := *last-stop-reason*.stop-reason-code; end if end if; if ((code == $timed-out) & (profile-at)) code := $profiler; end if; code; end method; ///// WAIT-FOR-STOP-REASON-NO-TIMEOUT // Called if no timeout keyword is supplied. define method wait-for-stop-reason-no-timeout (conn :: , #key profile-at = #f) => (_ :: ) if (size(conn.process.stop-reason-queue) > 0) *last-stop-reason* := pop-last(conn.process.stop-reason-queue); *last-stop-reason*.stop-reason-code; else machine-cycle(conn.process); if (size(conn.process.stop-reason-queue) > 0) *last-stop-reason* := pop-last(conn.process.stop-reason-queue); *last-stop-reason*.stop-reason-code; elseif (profile-at) $profiler; else wait-for-stop-reason-no-timeout(conn); end if; end if; end method; ///// GET-DEBUG-EVENT-PROCESS-EXIT-CODE // Given that the last received stop reason was an // , this returns the exit code. define method get-debug-event-process-exit-code (conn :: ) => (_ :: ) 0; end method; ///// GET-DEBUG-EVENT-THREAD-EXIT-CODE // Given that the last received stop reason was an // , this returns the exit code. define method get-debug-event-thread-exit-code (conn :: ) => (_ :: ) 0; 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 method get-debug-event-string-information (conn :: ) => (addr :: , sz :: , unicode? :: ) values(0, 0, #f); 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 method get-debug-event-library (conn :: ) => (_ :: ) 0; 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 method get-debug-event-thread (conn :: ) => (_ :: ) *last-stop-reason*.signalling-thread; end method; ///// GET-DEBUG-EVENT-PROCESS // This function is currently pointless, since there is only one // . define method get-debug-event-process (conn :: ) => (_ :: ) let nub-process = 0; let process = make (, 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 method get-exception-address (conn :: ) => (_ :: ) *last-stop-reason*.address; 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 :: ) let source-process = get-debug-event-process (ap.connection); let source-thread = #f; let source-library = #f; let stop-reason = #f; unless ((event-type == $timed-out) | (event-type == $profiler)) source-thread := find-or-make-thread(ap, get-debug-event-thread(ap.connection)); end unless; select (event-type) $timed-out => stop-reason := #f; $create-process => source-library := *the-only-library*; stop-reason := make (, process: source-process, thread: source-thread); $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)); $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 := *the-only-library*; stop-reason := make (, process: source-process, thread: source-thread, library: source-library); $unload-dll => source-library := *the-only-library*; 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 = "Whoopsee"; stop-reason := make (, process: source-process, thread: source-thread, debug-string: str); $access-violation => stop-reason := make (, process: source-process, thread: source-thread); $array-bounds-exception => stop-reason := make (, process: source-process, thread: source-thread); $illegal-instruction-exception => stop-reason := make (, process: source-process, thread: source-thread); $privileged-instruction-exception => stop-reason := make (, process: source-process, thread: source-thread); $denormal-exception => stop-reason := make (, process: source-process, thread: source-thread); $float-divide-by-zero-exception => stop-reason := make (, process: source-process, thread: source-thread); $inexact-result-exception => stop-reason := make (, process: source-process, thread: source-thread); $invalid-float-operation-exception => stop-reason := make (, process: source-process, thread: source-thread); $float-overflow-exception => stop-reason := make (, process: source-process, thread: source-thread); $float-underflow-exception => stop-reason := make (, process: source-process, thread: source-thread); $float-stack-check-exception => stop-reason := make (, process: source-process, thread: source-thread); $integer-divide-by-zero-exception => stop-reason := make (, process: source-process, thread: source-thread); $noncontinuable-exception => stop-reason := make (, process: source-process, thread: source-thread); $breakpoint-exception => stop-reason := make (, process: source-process, thread: source-thread, address: get-exception-address (ap.connection)); $hard-coded-breakpoint-exception => stop-reason := make (, process: source-process, thread: source-thread); $single-step-exception => stop-reason := make (, process: source-process, thread: source-thread, address: get-exception-address (ap.connection)); $profiler => stop-reason := make(); otherwise => stop-reason := make (, process: source-process, thread: source-thread, address: get-exception-address (ap.connection)); end select; stop-reason; 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 :: ) => (_ :: ) 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 method connection-can-receive-first-chance (conn :: , code :: ) => (_ :: ) let answer // = nub-can-receive-first-chance(conn.process, code); = 1; 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 :: ) => (_ :: ) 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 ("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 ("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 :: ) => (_ :: ) 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 :: ) => (_ :: ) 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 method connection-set-first-chance (conn :: , code :: ) => () // nub-set-first-chance(conn.process, code); end method; define method receiving-first-chance?-setter (set == #f, ap :: , etype :: ) => (_ :: ) 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 method connection-unset-first-chance (conn :: , code :: ) => () // nub-unset-first-chance(conn.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 :: ) => (_ :: ) // connection-thread-stopped-at-first-chance?(app.connection, thread); #f; end method; /* define method connection-thread-stopped-at-first-chance? (conn :: , thread :: ) => (_ :: ) let (code, fchance) = nub-thread-stop-information(conn.process, thread.nub-descriptor); if (fchance == 1) #t else #f end if end method; */