Truncated Windows/x86 stacks when using FPO. Add stack scanning to recover

instruction and frame pointers with better reliability.  r=bryner

http://groups.google.com/group/google-breakpad-dev/browse_thread/thread/e74af03fb0629aa0


git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@146 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
mmentovai 2007-04-20 18:36:42 +00:00
parent 1d4ce0a1d8
commit b63740b329
2 changed files with 119 additions and 6 deletions

View file

@ -103,6 +103,10 @@ class Stackwalker {
// get information from the stack. // get information from the stack.
MemoryRegion *memory_; MemoryRegion *memory_;
// A list of modules, for populating each StackFrame's module information.
// This field is optional and may be NULL.
const CodeModules *modules_;
private: private:
// Obtains the context frame, the innermost called procedure in a stack // Obtains the context frame, the innermost called procedure in a stack
// trace. Returns NULL on failure. GetContextFrame allocates a new // trace. Returns NULL on failure. GetContextFrame allocates a new
@ -122,10 +126,6 @@ class Stackwalker {
const CallStack *stack, const CallStack *stack,
const vector< linked_ptr<StackFrameInfo> > &stack_frame_info) = 0; const vector< linked_ptr<StackFrameInfo> > &stack_frame_info) = 0;
// A list of modules, for populating each StackFrame's module information.
// This field is optional and may be NULL.
const CodeModules *modules_;
// The optional SymbolSupplier for resolving source line info. // The optional SymbolSupplier for resolving source line info.
SymbolSupplier *supplier_; SymbolSupplier *supplier_;

View file

@ -38,6 +38,7 @@
#include "processor/stackwalker_x86.h" #include "processor/stackwalker_x86.h"
#include "google_breakpad/processor/call_stack.h" #include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/code_modules.h"
#include "google_breakpad/processor/memory_region.h" #include "google_breakpad/processor/memory_region.h"
#include "google_breakpad/processor/stack_frame_cpu.h" #include "google_breakpad/processor/stack_frame_cpu.h"
#include "processor/linked_ptr.h" #include "processor/linked_ptr.h"
@ -163,15 +164,23 @@ StackFrame* StackwalkerX86::GetCallerFrame(
// postfix notation and will be passed to PostfixEvaluator::Evaluate. // postfix notation and will be passed to PostfixEvaluator::Evaluate.
// Given the dictionary and the program string, it is possible to compute // Given the dictionary and the program string, it is possible to compute
// the return address and the values of other registers in the calling // the return address and the values of other registers in the calling
// function. // function. When encountering a nontraditional frame (one which takes
// advantage of FPO), the stack may need to be scanned for these values.
// For traditional frames, simple deterministic dereferencing suffices
// without any need for scanning. The results of program string evaluation
// will be used to determine whether to scan for better values.
string program_string; string program_string;
bool traditional_frame = true;
bool recover_ebp = true;
if (last_frame_info && last_frame_info->valid == StackFrameInfo::VALID_ALL) { if (last_frame_info && last_frame_info->valid == StackFrameInfo::VALID_ALL) {
// FPO data available. // FPO data available.
traditional_frame = false;
if (!last_frame_info->program_string.empty()) { if (!last_frame_info->program_string.empty()) {
// The FPO data has its own program string, which will tell us how to // The FPO data has its own program string, which will tell us how to
// get to the caller frame, and may even fill in the values of // get to the caller frame, and may even fill in the values of
// nonvolatile registers and provide pointers to local variables and // nonvolatile registers and provide pointers to local variables and
// parameters. // parameters. In some cases, particularly with program strings that use
// .raSearchStart, the stack may need to be scanned afterward.
program_string = last_frame_info->program_string; program_string = last_frame_info->program_string;
} else if (last_frame_info->allocates_base_pointer) { } else if (last_frame_info->allocates_base_pointer) {
// The function corresponding to the last frame doesn't use the frame // The function corresponding to the last frame doesn't use the frame
@ -197,6 +206,15 @@ StackFrame* StackwalkerX86::GetCallerFrame(
// the caller is at a known location in the saved-register area of // the caller is at a known location in the saved-register area of
// the stack frame. // the stack frame.
// //
// For this type of frame, MSVC 14 (from Visual Studio 8/2005) in
// link-time code generation mode (/LTCG and /GL) can generate erroneous
// debugging data. The reported size of saved registers can be 0,
// which is clearly an error because these frames must, at the very
// least, save %ebp. For this reason, in addition to those given above
// about the use of .raSearchStart, the stack may need to be scanned
// for a better return address and a better frame pointer after the
// program string is evaluated.
//
// %eip_new = *(%esp_old + callee_params + saved_regs + locals) // %eip_new = *(%esp_old + callee_params + saved_regs + locals)
// %ebp_new = *(%esp_old + callee_params + saved_regs - 8) // %ebp_new = *(%esp_old + callee_params + saved_regs - 8)
// %esp_new = %esp_old + callee_params + saved_regs + locals + 4 // %esp_new = %esp_old + callee_params + saved_regs + locals + 4
@ -217,12 +235,18 @@ StackFrame* StackwalkerX86::GetCallerFrame(
// is the value that it had in the caller, so it can be carried // is the value that it had in the caller, so it can be carried
// straight through without bringing its validity into question. // straight through without bringing its validity into question.
// //
// Because of the use of .raSearchStart, the stack will possibly be
// examined to locate a better return address after program string
// evaluation. The stack will not be examined to locate a saved
// %ebp value, because these frames do not save (or use) %ebp.
//
// %eip_new = *(%esp_old + callee_params + saved_regs + locals) // %eip_new = *(%esp_old + callee_params + saved_regs + locals)
// %esp_new = %esp_old + callee_params + saved_regs + locals + 4 // %esp_new = %esp_old + callee_params + saved_regs + locals + 4
// %ebp_new = %ebp_old // %ebp_new = %ebp_old
program_string = "$eip .raSearchStart ^ = " program_string = "$eip .raSearchStart ^ = "
"$esp .raSearchStart 4 + = " "$esp .raSearchStart 4 + = "
"$ebp $ebp ="; "$ebp $ebp =";
recover_ebp = false;
} }
} else { } else {
// No FPO information is available for the last frame. Assume that the // No FPO information is available for the last frame. Assume that the
@ -244,6 +268,10 @@ StackFrame* StackwalkerX86::GetCallerFrame(
// CALL itself, and 4 bytes for the callee's PUSH of the caller's frame // CALL itself, and 4 bytes for the callee's PUSH of the caller's frame
// pointer. // pointer.
// //
// Instruction and frame pointer recovery for these traditional frames is
// entirely deterministic, and the stack will not be scanned after
// recovering these values.
//
// %eip_new = *(%ebp_old + 4) // %eip_new = *(%ebp_old + 4)
// %esp_new = %ebp_old + 8 // %esp_new = %ebp_old + 8
// %ebp_new = *(%ebp_old) // %ebp_new = *(%ebp_old)
@ -264,6 +292,91 @@ StackFrame* StackwalkerX86::GetCallerFrame(
return NULL; return NULL;
} }
// If this stack frame did not use %ebp in a traditional way, locating the
// return address isn't entirely deterministic. In that case, the stack
// can be scanned to locate the return address.
//
// Even in nontraditional frames, if program string evaluation resulted in
// both %eip and %ebp values of 0, trust that the end of the stack has been
// reached and don't scan for anything else.
if (!traditional_frame &&
(dictionary["$eip"] != 0 || dictionary["$ebp"] != 0)) {
int offset = 0;
// This scan can only be done if a CodeModules object is available, to
// check that candidate return addresses are in fact inside a module.
//
// TODO(mmentovai): This ignores dynamically-generated code. One possible
// solution is to check the minidump's memory map to see if the candidate
// %eip value comes from a mapped executable page, although this would
// require dumps that contain MINIDUMP_MEMORY_INFO, which the Breakpad
// client doesn't currently write (it would need to call MiniDumpWriteDump
// with the MiniDumpWithFullMemoryInfo type bit set). Even given this
// ability, older OSes (pre-XP SP2) and CPUs (pre-P4) don't enforce
// an independent execute privilege on memory pages.
u_int32_t eip = dictionary["$eip"];
if (modules_ && !modules_->GetModuleForAddress(eip)) {
const int kRASearchWords = 15;
// The instruction pointer at .raSearchStart was invalid, so start
// looking one 32-bit word above that location.
u_int32_t location_start = dictionary[".raSearchStart"] + 4;
for (u_int32_t location = location_start;
location <= location_start + kRASearchWords * 4;
location += 4) {
if (!memory_->GetMemoryAtAddress(location, &eip))
break;
if (modules_->GetModuleForAddress(eip)) {
// This is a better return address that what program string
// evaluation found. Use it, and set %esp to the location above the
// one where the return address was found.
//
// TODO(mmentovai): The return-address check can be made even
// stronger in modules for which debugging data is available. In
// that case, it's possible to check that the candidate return
// address is inside a known function.
dictionary["$eip"] = eip;
dictionary["$esp"] = location + 4;
offset = location - location_start;
break;
}
}
}
// When trying to recover the previous value of the frame pointer (%ebp),
// start looking at the lowest possible address in the saved-register
// area, and look at the entire saved register area, increased by the
// size of |offset| to account for additional data that may be on the
// stack. The scan is performed from the highest possible address to
// the lowest, because we expect that the function's prolog would have
// saved %ebp early.
u_int32_t ebp = dictionary["$ebp"];
u_int32_t value; // throwaway variable to check pointer validity
if (recover_ebp && !memory_->GetMemoryAtAddress(ebp, &value)) {
int fp_search_bytes = last_frame_info->saved_register_size + offset;
u_int32_t location_end = last_frame->context.esp +
last_frame_callee_parameter_size;
for (u_int32_t location = location_end + fp_search_bytes;
location >= location_end;
location -= 4) {
if (!memory_->GetMemoryAtAddress(location, &ebp))
break;
if (memory_->GetMemoryAtAddress(ebp, &value)) {
// The candidate value is a pointer to the same memory region
// (the stack). Prefer it as a recovered %ebp result.
dictionary["$ebp"] = ebp;
break;
}
}
}
}
// Treat an instruction address of 0 as end-of-stack. Treat incorrect stack // Treat an instruction address of 0 as end-of-stack. Treat incorrect stack
// direction as end-of-stack to enforce progress and avoid infinite loops. // direction as end-of-stack to enforce progress and avoid infinite loops.
if (dictionary["$eip"] == 0 || if (dictionary["$eip"] == 0 ||