module: access-path-implementation synopsis: Functions for handling stack traces 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 define constant $nub-dynamic-chain-terminator = 0; ///// define abstract class () slot nub-descriptor :: , required-init-keyword: descriptor:; slot pointer :: , init-keyword: pointer:; slot return-address :: , init-keyword: return-address:; slot next-instruction :: , init-keyword: next-instruction:; slot next :: false-or(), init-value: #f; slot previous :: false-or(), init-value: #f; slot lexicals :: , init-value: #[]; end class; ///// define class () end class; ///// define class () end class; ///// define abstract class () slot C-name :: , required-init-keyword: name:; slot lexical-variable-address :: , required-init-keyword: address:; // slot lexical-variable-type :: ; // DUMMY ONLY HACK slot argument? :: , init-value: #f, init-keyword: argument?:; end class; ///// GENERIC FUNCTIONS define generic initialize-stack-trace (ap :: , thread :: ) => (_ :: ); define generic next-frame (ap :: , frame :: ) => (_ ::false-or()); define generic previous-frame (ap :: , frame :: ) => (_ :: false-or()); define generic frame-pointer (ap :: , frame :: ) => (_ :: ); define generic frame-return-address (ap :: , frame :: ) => (_ :: ); define generic frame-instruction-address (ap :: , frame :: ) => (_ :: ); define generic lexical-variable-name (v :: ) => (_ :: ); define generic do-frame-arguments (function :: , ap :: , frame :: ) => (); define generic do-frame-lexicals (function :: , ap :: , frame :: ) => (); ///// INITIALIZE-STACK-TRACE define method initialize-stack-trace (ap :: , thread :: ) => (_ :: ) // Pull out the topmost stack frame for this thread. if (thread.stack) thread.stack else let (closest, previous, nxt) = nearest-symbols(ap, thread.nub-descriptor.eip); if ((closest) & (closest.remote-symbol-address == thread.nub-descriptor.eip)) thread.stack := get-unbuilt-function-frame(ap.connection, thread); else thread.stack := get-top-function-frame (ap.connection, thread); end if; thread.stack end if; end method; ///// GET-TOP-FUNCTION-FRAME // Looks at the frame-pointer in the frozen runtime (for the given // thread) and constructs a object for the topmost // stack frame. define method get-top-function-frame (conn :: , thread :: ) => (_ :: ) // Pull all information about the topmost stack frame out of the // debugger nub. let nub-top-frame :: = thread.nub-descriptor.ebp; let top-frame-pointer :: = nub-top-frame; let top-frame-return :: = conn.process.memory.contents[nub-top-frame + 1]; // As this is the top frame, the next instruction to be executed in the // frame is known to be the next instruction executed by this thread. let top-frame-next-instr :: = thread.nub-descriptor.eip; // Create the corresponding instance of and // return it. let top-frame = make (, descriptor: nub-top-frame, pointer: top-frame-pointer, return-address: top-frame-return, next-instruction: top-frame-next-instr); top-frame; end method; ///// GET-UNBUILT-FUNCTION-FRAME // Looks at the frame-pointer in the frozen runtime (for the given // thread) and constructs a proxy for the yet-to-be-built // stack frame. define method get-unbuilt-function-frame (conn :: , thread :: ) => (_ :: ) // Pull all information about the topmost stack frame out of the // debugger nub. let nub-top-frame :: = thread.nub-descriptor.ebp; let top-frame-pointer :: = nub-top-frame; // For an unbuilt frame, the return address is known to be at the // top of stack (since a CALL will have just been executed). let top-frame-return :: = conn.process.memory.contents[thread.nub-descriptor.esp]; // As this is the top frame, the next instruction to be executed in the // frame is known to be the next instruction executed by this thread. let top-frame-next-instr :: = thread.nub-descriptor.eip; // Create the corresponding instance of and // return it. let top-frame = make (, descriptor: nub-top-frame, pointer: top-frame-pointer, return-address: top-frame-return, next-instruction: top-frame-next-instr); top-frame; end method; ///// GET-PREVIOUS-FUNCTION-FRAME // Given a function frame, examines the state of the stack and returns // the object for the previous stack frame, if one // exists. define method get-previous-function-frame (conn :: , frame :: ) => (_ :: false-or()) let nub-frame :: = conn.process.memory.contents[frame.pointer]; let frame-pointer :: = nub-frame; // If the frame pointer is a termination of the dynamic chain, then // return #f rather than an instance of . if ((frame-pointer == 0) | (frame-pointer == $NOP)) #f; else // We still have a valid frame, so get its return address from the // nub. let frame-return = conn.process.memory.contents[frame-pointer + 1]; // The next instruction address is not pulled from the nub, since // it is equal to the return address of the last frame that was // pulled. let older-frame = make (, descriptor: nub-frame, pointer: frame-pointer, return-address: frame-return, next-instruction: frame.return-address); // Link the frames. older-frame.next := frame; frame.previous := older-frame; older-frame.previous := #f; older-frame; end if end method; define method get-previous-function-frame (conn :: , frame :: ) => (_ :: false-or()) let nub-frame :: = frame.nub-descriptor; let frame-pointer :: = frame.pointer; let frame-return = conn.process.memory.contents[frame-pointer + 1]; // The next instruction address is not pulled from the nub, since // it is equal to the return address of the last frame that was // pulled. let older-frame = make (, descriptor: nub-frame, pointer: frame-pointer, return-address: frame-return, next-instruction: frame.return-address); // Link the frames. older-frame.next := frame; frame.previous := older-frame; older-frame.previous := #f; older-frame; end method; ///// NEXT-FRAME define method next-frame (ap :: , frame :: ) => (_ :: false-or()) frame.next end method; ///// PREVIOUS-FRAME define method previous-frame (ap :: , frame :: ) => (_ :: false-or()) if (frame.previous) frame.previous else let f = get-previous-function-frame (ap.connection, frame); f; end if; end method; ///// NUMBER-OF-ACTIVE-THREADS // Counts the number of threads in an access path. define method number-of-active-threads (ap :: ) => (i :: ) size(ap.threads); end method; ///// NUMBER-OF-FRAMES-ON-STACK // Counts the stack frames for a thread. define method number-of-frames-on-stack (ap :: , thread :: ) => (i :: ) let i = 0; let current-frame = initialize-stack-trace(ap, thread); while (current-frame) i := i + 1; current-frame := previous-frame(ap, current-frame); end while; i; end method; ///// FRAME-POINTER define method frame-pointer (ap :: , frame :: ) => (_ :: ) frame.pointer end method; define method frame-pointer (ap :: , frame :: ) => (_ :: ) 0 end method; ///// FRAME-RETURN-ADDRESS define method frame-return-address (ap :: , frame :: ) => (_ :: ) frame.return-address end method; ///// FRAME-INSTRUCTION-ADDRESS define method frame-instruction-address (ap :: , frame :: ) => (_ :: ) frame.next-instruction end method; ///// DO-FRAME-ARGUMENTS define method do-frame-arguments (function :: , ap :: , frame :: ) => () let frame-ptr :: = frame-pointer (ap, frame); // Since we are lazily pulling debug info, we may not yet have // examined the lexicals for this frame. Read them now if // necessary. if (frame.lexicals = #[]) read-frame-lexicals (ap.connection, frame) end if; // Apply the function only to frame arguments - these being lexicals // with positive frame offsets. for (lexvar in frame.lexicals) let addr :: = lexical-variable-address (lexvar); if (lexvar.argument?) function (lexvar) end if end for; end method; ///// DO-FRAME-LEXICALS define method do-frame-lexicals (function :: , ap :: , frame :: ) => () // Again, now is the time to pull lexical variable information if // we need it. if (frame.lexicals = #[]) read-frame-lexicals (ap.connection, frame) end if; // Now we apply "function" to all lexicals. for (lexvar in frame.lexicals) function (lexvar) end for; end method; ///// LIVE-LEXICALS-COUNT // Pretty poor but valid implementation define method live-lexicals-count (ap :: , frame :: ) => (count :: ) let i = 0; do-frame-lexicals(method (x :: ) i := i + 1; end method, ap, frame); i; end method; ///// LEXICAL-VARIABLE-NAME // This is a virtual slot accessor. The name is stored as a C-string. // This function converts the name to a dylan . define method lexical-variable-name (v :: ) => (_ :: ) v.C-name; end method; ///// FIND-LEXICAL-VARIABLE define method find-lexical-variable (ap :: , frame :: , name :: ) => (_ :: false-or()) // Maybe this function should be implemented in terms of do-frame-lexicals // (in the same way as find-symbol), except that I'll run into the // closure bug again... let target = #f; // Once again, get the lexicals vector if necessary. if (frame.lexicals = #[]) read-frame-lexicals (ap.connection, frame) end if; // Point target to a matching lexical if it is found. (There can only // be at most one matching lexical variable, otherwise the program // being debugged is completely knackered anyway!) for (lexvar in frame.lexicals) if (lexvar.lexical-variable-name = name) target := lexvar end if end for; // Just return whatever we managed to find. target; end method; ///// READ-FRAME-LEXICALS define method read-frame-lexicals (conn :: , frame :: ) => () let func = map-eip-to-function(conn.process, frame.next-instruction); let args = func.arguments; let lexs = func.lexicals; let list = make(, size: size(args) + size(lexs)); let i = 0; for (arg in args) list[i] := make(, name: arg, address: frame.pointer - (i + 1), argument?: #t); i := i + 1; end for; for (lex in lexs) list[i] := make(, name: lex, address: frame.pointer - (i + 1), argument?: #f); i := i + 1; end for; frame.lexicals := list; end method; define method older-stack-frame? (ap :: , this-one :: , than-this-one :: ) => (answer :: ) this-one > than-this-one end method;