aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/processor/stackwalker_x86.cc45
-rw-r--r--src/processor/stackwalker_x86_unittest.cc213
2 files changed, 245 insertions, 13 deletions
diff --git a/src/processor/stackwalker_x86.cc b/src/processor/stackwalker_x86.cc
index 0d8b3edc..29d98e4b 100644
--- a/src/processor/stackwalker_x86.cc
+++ b/src/processor/stackwalker_x86.cc
@@ -217,14 +217,27 @@ StackFrameX86* StackwalkerX86::GetCallerByWindowsFrameInfo(
dictionary[".cbLocals"] = last_frame_info->local_size;
uint32_t raSearchStart = last_frame->context.esp +
- last_frame_callee_parameter_size +
- last_frame_info->local_size +
- last_frame_info->saved_register_size;
+ last_frame_callee_parameter_size +
+ last_frame_info->local_size +
+ last_frame_info->saved_register_size;
uint32_t raSearchStartOld = raSearchStart;
uint32_t found = 0; // dummy value
// Scan up to three words above the calculated search value, in case
// the stack was aligned to a quadword boundary.
+ //
+ // TODO(ivan.penkov): Consider cleaning up the scan for return address that
+ // follows. The purpose of this scan is to adjust the .raSearchStart
+ // calculation (which is based on register %esp) in the cases where register
+ // %esp may have been aligned (up to a quadword). There are two problems
+ // with this approach:
+ // 1) In practice, 64 byte boundary alignment is seen which clearly can not
+ // be handled by a three word scan.
+ // 2) A search for a return address is "guesswork" by definition because
+ // the results will be different depending on what is left on the stack
+ // from previous executions.
+ // So, basically, the results from this scan should be ignored if other means
+ // for calculation of the value of .raSearchStart are available.
if (ScanForReturnAddress(raSearchStart, &raSearchStart, &found, 3) &&
last_frame->trust == StackFrame::FRAME_TRUST_CONTEXT &&
last_frame->windows_frame_info != NULL &&
@@ -241,11 +254,6 @@ StackFrameX86* StackwalkerX86::GetCallerByWindowsFrameInfo(
ScanForReturnAddress(raSearchStart, &raSearchStart, &found, 3);
}
- // The difference between raSearch and raSearchStart is unknown,
- // but making them the same seems to work well in practice.
- dictionary[".raSearchStart"] = raSearchStart;
- dictionary[".raSearch"] = raSearchStart;
-
dictionary[".cbParams"] = last_frame_info->parameter_size;
// Decide what type of program string to use. The program string is in
@@ -330,6 +338,27 @@ StackFrameX86* StackwalkerX86::GetCallerByWindowsFrameInfo(
recover_ebp = false;
}
+ // Check for alignment operators in the program string. If alignment
+ // operators are found, then current %ebp must be valid and it is the only
+ // reliable data point that can be used for getting to the previous frame.
+ // E.g. the .raSearchStart calculation (above) is based on %esp and since
+ // %esp was aligned in the current frame (which is a lossy operation) the
+ // calculated value of .raSearchStart cannot be correct and should not be
+ // used. Instead .raSearchStart must be calculated based on %ebp.
+ // The code that follows assumes that .raSearchStart is supposed to point
+ // at the saved return address (ebp + 4).
+ // For some more details on this topic, take a look at the following thread:
+ // https://groups.google.com/forum/#!topic/google-breakpad-dev/ZP1FA9B1JjM
+ if ((StackFrameX86::CONTEXT_VALID_EBP & last_frame->context_validity) != 0 &&
+ program_string.find('@') != string::npos) {
+ raSearchStart = last_frame->context.ebp + 4;
+ }
+
+ // The difference between raSearch and raSearchStart is unknown,
+ // but making them the same seems to work well in practice.
+ dictionary[".raSearchStart"] = raSearchStart;
+ dictionary[".raSearch"] = raSearchStart;
+
// Now crank it out, making sure that the program string set at least the
// two required variables.
PostfixEvaluator<uint32_t> evaluator =
diff --git a/src/processor/stackwalker_x86_unittest.cc b/src/processor/stackwalker_x86_unittest.cc
index 690f5f31..008b496b 100644
--- a/src/processor/stackwalker_x86_unittest.cc
+++ b/src/processor/stackwalker_x86_unittest.cc
@@ -565,15 +565,18 @@ TEST_F(GetCallerFrame, WindowsFrameDataAligned) {
" $ebp $T1 4 - ^ ="
" $eip $T1 ^ ="
" $esp $T1 4 + =");
+ Label frame0_esp, frame0_ebp;
Label frame1_esp, frame1_ebp;
stack_section.start() = 0x80000000;
stack_section
// frame 0
+ .Mark(&frame0_esp)
.D32(0x0ffa0ffa) // unused saved register
.D32(0xdeaddead) // locals
.D32(0xbeefbeef)
.D32(0) // 8-byte alignment
- .D32(frame1_ebp)
+ .Mark(&frame0_ebp)
+ .D32(frame1_ebp) // saved %ebp
.D32(0x5000129d) // return address
// frame 1
.Mark(&frame1_esp)
@@ -584,8 +587,8 @@ TEST_F(GetCallerFrame, WindowsFrameDataAligned) {
RegionFromSection();
raw_context.eip = 0x4000aa85;
- raw_context.esp = stack_section.start().Value();
- raw_context.ebp = 0xf052c1de; // should not be needed to walk frame
+ raw_context.esp = frame0_esp.Value();
+ raw_context.ebp = frame0_ebp.Value();
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerX86 walker(&system_info, &raw_context, &stack_region, &modules,
@@ -606,8 +609,8 @@ TEST_F(GetCallerFrame, WindowsFrameDataAligned) {
ASSERT_EQ(StackFrameX86::CONTEXT_VALID_ALL, frame0->context_validity);
EXPECT_EQ(0x4000aa85U, frame0->instruction);
EXPECT_EQ(0x4000aa85U, frame0->context.eip);
- EXPECT_EQ(stack_section.start().Value(), frame0->context.esp);
- EXPECT_EQ(0xf052c1deU, frame0->context.ebp);
+ EXPECT_EQ(frame0_esp.Value(), frame0->context.esp);
+ EXPECT_EQ(frame0_ebp.Value(), frame0->context.ebp);
EXPECT_TRUE(frame0->windows_frame_info != NULL);
}
@@ -1462,6 +1465,206 @@ TEST_F(GetCallerFrame, ReturnAddressIsNotInKnownModule) {
}
}
+// Test the .raSearchStart/.raSearch calculation when alignment operators are
+// used in the program string. The current %ebp must be valid and it is the
+// only reliable data point that can be used for that calculation.
+TEST_F(GetCallerFrame, HandleAlignmentInProgramString) {
+ MockCodeModule chrome_dll(0x59630000, 0x19e3000, "chrome.dll", "version1");
+ SetModuleSymbols(&chrome_dll, // chrome.dll
+ "FUNC 56422 50c 8 base::MessageLoop::RunTask"
+ "(base::PendingTask const &)\n"
+ "56422 e 458 4589\n"
+ "STACK WIN 4 56422 50c 11 0 8 c ac 0 1 $T1 .raSearch = $T0 "
+ "$T1 4 - 8 @ = $ebp $T1 4 - ^ = $eip $T1 ^ = $esp $T1 4 + = "
+ "$20 $T0 176 - ^ = $23 $T0 180 - ^ = $24 $T0 184 - ^ =\n"
+ "FUNC 55d34 34a 0 base::MessageLoop::DoWork()\n"
+ "55d34 11 596 4589\n"
+ "STACK WIN 4 55d34 34a 19 0 0 c 134 0 1 $T1 .raSearch = "
+ "$T0 $T1 4 - 8 @ = $ebp $T1 4 - ^ = $eip $T1 ^ = $esp "
+ "$T1 4 + = $20 $T0 312 - ^ = $23 $T0 316 - ^ = $24 $T0 "
+ "320 - ^ =\n"
+ "FUNC 55c39 fb 0 base::MessagePumpForIO::DoRunLoop()\n"
+ "55c39 d 518 19962\n"
+ "STACK WIN 4 55c39 fb d 0 0 c 34 0 1 $T1 .raSearch = $T0 "
+ "$T1 4 - 64 @ = $ebp $T1 4 - ^ = $eip $T1 ^ = $esp $T1 4 + "
+ "= $20 $T0 56 - ^ = $23 $T0 60 - ^ = $24 $T0 64 - ^ =\n"
+ "FUNC 55bf0 49 4 base::MessagePumpWin::Run(base::"
+ "MessagePump::Delegate *)\n"
+ "55bf0 49 48 4724\n"
+ "STACK WIN 4 55bf0 49 c 0 4 0 10 0 1 $T0 $ebp = $eip $T0 4 "
+ "+ ^ = $ebp $T0 ^ = $esp $T0 8 + =\n"
+ "FUNC 165d de 4 malloc\n"
+ "165d 6 119 54\n"
+ "STACK WIN 4 165d de d 0 4 8 0 0 1 $T1 .raSearch = $T0 "
+ "$T1 4 - 8 @ = $ebp $T1 4 - ^ = $eip $T1 ^ = $esp $T1 4 "
+ "+ = $23 $T0 4 - ^ = $24 $T0 8 - ^ =\n"
+ "FUNC 55ac9 79 0 base::MessageLoop::RunInternal()\n"
+ "55ac9 d 427 4589\n"
+ "STACK WIN 4 55ac9 79 d 0 0 8 10 0 1 $T1 .raSearch = $T0 "
+ "$T1 4 - 8 @ = $ebp $T1 4 - ^ = $eip $T1 ^ = $esp $T1 4 + = "
+ "$23 $T0 20 - ^ = $24 $T0 24 - ^ =\n");
+
+ // Create some modules with some stock debugging information.
+ MockCodeModules local_modules;
+ local_modules.Add(&chrome_dll);
+
+ Label frame0_esp;
+ Label frame0_ebp;
+ Label frame1_esp;
+ Label frame1_ebp;
+ Label frame2_esp;
+ Label frame2_ebp;
+ Label frame3_esp;
+ Label frame3_ebp;
+
+ stack_section.start() = 0x046bfc80;
+ stack_section
+ .D32(0)
+ .Mark(&frame0_esp)
+ .D32(0x01e235a0)
+ .D32(0x00000000)
+ .D32(0x01e9f580)
+ .D32(0x01e9f580)
+ .D32(0x00000020)
+ .D32(0x00000000)
+ .D32(0x00463674)
+ .D32(0x00000020)
+ .D32(0x00000000)
+ .D32(0x046bfcd8)
+ .D32(0x046bfcd8)
+ .D32(0x0001204b)
+ .D32(0x00000000)
+ .D32(0xfdddb523)
+ .D32(0x00000000)
+ .D32(0x00000007)
+ .D32(0x00000040)
+ .D32(0x00000000)
+ .D32(0x59631693) // chrome_59630000!malloc+0x36
+ .D32(0x01e9f580)
+ .D32(0x01e9f580)
+ .D32(0x046bfcf8)
+ .D32(0x77da6704) // ntdll!NtSetIoCompletion+0xc
+ .D32(0x046bfd4c)
+ .D32(0x59685bec) // chrome_59630000!base::MessageLoop::StartHistogrammer..
+ .D32(0x01e235a0)
+
+ .Mark(&frame0_ebp)
+ .D32(frame1_ebp) // Child EBP .D32(0x046bfd0c)
+ .D32(0x59685c2e) // Return address in
+ // chrome_59630000!base::MessagePumpWin::Run+0x3e
+ .Mark(&frame1_esp)
+ .D32(0x01e75a90)
+ .D32(0x046bfd4c)
+ .D32(0x01e75a90)
+ .D32(0x00000000)
+ .D32(0x00000300)
+ .D32(0x00000001)
+
+ .Mark(&frame1_ebp)
+ .D32(frame2_ebp) // Child EBP .D32(0x046bfd30)
+ .D32(0x59685b3c) // Return address in
+ // chrome_59630000!base::MessageLoop::RunInternal+0x73
+ .Mark(&frame2_esp)
+ .D32(0x01e75a90)
+ .D32(0x00000000)
+ .D32(0x046bfd4c)
+ .D32(0x59658123) // chrome_59630000!std::deque..
+ .D32(0x046bfda0)
+ .D32(0x01e79d70)
+ .D32(0x046bfda0)
+
+ .Mark(&frame2_ebp) // .D32(0x046bfd40)
+ .D32(0) // saved %ebp (stack end)
+ .D32(0); // saved %eip (stack end)
+
+ RegionFromSection();
+ raw_context.eip = 0x59685c46; // Context frame in
+ // base::MessagePumpForIO::DoRunLoop
+ raw_context.esp = frame0_esp.Value();
+ raw_context.ebp = frame0_ebp.Value();
+
+ StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
+ StackwalkerX86 walker(&system_info, &raw_context, &stack_region,
+ &local_modules, &frame_symbolizer);
+ vector<const CodeModule*> modules_without_symbols;
+ vector<const CodeModule*> modules_with_corrupt_symbols;
+ ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
+ &modules_with_corrupt_symbols));
+ ASSERT_EQ(0U, modules_without_symbols.size());
+ ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
+ frames = call_stack.frames();
+
+ ASSERT_EQ(3U, frames->size());
+
+ { // To avoid reusing locals by mistake
+ StackFrameX86 *frame = static_cast<StackFrameX86 *>(frames->at(0));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame->trust);
+ ASSERT_EQ(StackFrameX86::CONTEXT_VALID_ALL, frame->context_validity);
+ EXPECT_EQ("base::MessagePumpForIO::DoRunLoop()", frame->function_name);
+ EXPECT_EQ(0x59685c46U, frame->instruction);
+ EXPECT_EQ(0x59685c46U, frame->context.eip);
+ EXPECT_EQ(frame0_esp.Value(), frame->context.esp);
+ EXPECT_EQ(frame0_ebp.Value(), frame->context.ebp);
+ EXPECT_EQ(&chrome_dll, frame->module);
+ ASSERT_TRUE(frame->windows_frame_info != NULL);
+ EXPECT_EQ(WindowsFrameInfo::VALID_ALL, frame->windows_frame_info->valid);
+ EXPECT_EQ(WindowsFrameInfo::STACK_INFO_FRAME_DATA,
+ frame->windows_frame_info->type_);
+ EXPECT_EQ("$T1 .raSearch = $T0 "
+ "$T1 4 - 64 @ = $ebp $T1 4 - ^ = $eip $T1 ^ = $esp $T1 4 + "
+ "= $20 $T0 56 - ^ = $23 $T0 60 - ^ = $24 $T0 64 - ^ =",
+ frame->windows_frame_info->program_string);
+ EXPECT_FALSE(frame->windows_frame_info->allocates_base_pointer);
+ }
+
+ { // To avoid reusing locals by mistake
+ StackFrameX86 *frame = static_cast<StackFrameX86 *>(frames->at(1));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_CFI, frame->trust);
+ ASSERT_EQ((StackFrameX86::CONTEXT_VALID_EIP |
+ StackFrameX86::CONTEXT_VALID_ESP |
+ StackFrameX86::CONTEXT_VALID_EBP),
+ frame->context_validity);
+ EXPECT_EQ("base::MessagePumpWin::Run(base::MessagePump::Delegate *)",
+ frame->function_name);
+ EXPECT_EQ(1500011566U, frame->instruction + 1);
+ EXPECT_EQ(1500011566U, frame->context.eip);
+ EXPECT_EQ(frame1_esp.Value(), frame->context.esp);
+ EXPECT_EQ(frame1_ebp.Value(), frame->context.ebp);
+ EXPECT_EQ(&chrome_dll, frame->module);
+ ASSERT_TRUE(frame->windows_frame_info != NULL);
+ EXPECT_EQ(WindowsFrameInfo::VALID_ALL, frame->windows_frame_info->valid);
+ EXPECT_EQ(WindowsFrameInfo::STACK_INFO_FRAME_DATA,
+ frame->windows_frame_info->type_);
+ EXPECT_EQ("$T0 $ebp = $eip $T0 4 + ^ = $ebp $T0 ^ = $esp $T0 8 + =",
+ frame->windows_frame_info->program_string);
+ EXPECT_FALSE(frame->windows_frame_info->allocates_base_pointer);
+ }
+
+ { // To avoid reusing locals by mistake
+ StackFrameX86 *frame = static_cast<StackFrameX86 *>(frames->at(2));
+ EXPECT_EQ(StackFrame::FRAME_TRUST_CFI, frame->trust);
+ ASSERT_EQ((StackFrameX86::CONTEXT_VALID_EIP |
+ StackFrameX86::CONTEXT_VALID_ESP |
+ StackFrameX86::CONTEXT_VALID_EBP),
+ frame->context_validity);
+ EXPECT_EQ("base::MessageLoop::RunInternal()", frame->function_name);
+ EXPECT_EQ(1500011324U, frame->instruction + 1);
+ EXPECT_EQ(1500011324U, frame->context.eip);
+ EXPECT_EQ(frame2_esp.Value(), frame->context.esp);
+ EXPECT_EQ(frame2_ebp.Value(), frame->context.ebp);
+ EXPECT_EQ(&chrome_dll, frame->module);
+ ASSERT_TRUE(frame->windows_frame_info != NULL);
+ EXPECT_EQ(WindowsFrameInfo::VALID_ALL, frame->windows_frame_info->valid);
+ EXPECT_EQ(WindowsFrameInfo::STACK_INFO_FRAME_DATA,
+ frame->windows_frame_info->type_);
+ EXPECT_EQ("$T1 .raSearch = $T0 "
+ "$T1 4 - 8 @ = $ebp $T1 4 - ^ = $eip $T1 ^ = $esp $T1 4 + = "
+ "$23 $T0 20 - ^ = $24 $T0 24 - ^ =",
+ frame->windows_frame_info->program_string);
+ EXPECT_FALSE(frame->windows_frame_info->allocates_base_pointer);
+ }
+}
+
// Scan the stack for a return address and potentially skip frames when the
// current IP address is not in a known module. Note, that that the span of
// this scan is limited to 120 search words for the context frame and 30