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:
parent
1d4ce0a1d8
commit
b63740b329
2 changed files with 119 additions and 6 deletions
|
@ -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_;
|
||||||
|
|
||||||
|
|
|
@ -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 ||
|
||||||
|
|
Loading…
Reference in a new issue