From b223627d81c083a64f2ccecf2651a18111421280 Mon Sep 17 00:00:00 2001 From: "ted.mielczarek" Date: Thu, 8 Apr 2010 23:06:23 +0000 Subject: provide a network source line resolver + server. r=mark,jimb at http://breakpad.appspot.com/36001 git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@569 4c0a9323-5329-0410-9bdc-e9ce6186880e --- .../network_source_line_resolver_unittest.cc | 535 +++++++++++++++++++++ 1 file changed, 535 insertions(+) create mode 100644 src/processor/network_source_line_resolver_unittest.cc (limited to 'src/processor/network_source_line_resolver_unittest.cc') diff --git a/src/processor/network_source_line_resolver_unittest.cc b/src/processor/network_source_line_resolver_unittest.cc new file mode 100644 index 00000000..bf0aae78 --- /dev/null +++ b/src/processor/network_source_line_resolver_unittest.cc @@ -0,0 +1,535 @@ +// Copyright (c) 2010, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +// Unit tests for NetworkSourceLineResolver. + +#include + +#include "breakpad_googletest_includes.h" +#include "google_breakpad/processor/network_source_line_resolver.h" +#include "google_breakpad/processor/stack_frame.h" +#include "google_breakpad/processor/symbol_supplier.h" +#include "processor/basic_code_module.h" +#include "processor/binarystream.h" +#include "processor/cfi_frame_info.h" +#include "processor/network_interface.h" +#include "processor/network_source_line_protocol.h" +#include "processor/windows_frame_info.h" + +namespace google_breakpad { +class MockNetwork : public NetworkInterface { + public: + MockNetwork() {} + + MOCK_METHOD1(Init, bool(bool listen)); + MOCK_METHOD2(Send, bool(const char *data, size_t length)); + MOCK_METHOD1(WaitToReceive, bool(int timeout)); + MOCK_METHOD3(Receive, bool(char *buffer, size_t buffer_size, + ssize_t &received)); +}; +} + +namespace { +using std::string; +using google_breakpad::binarystream; +using google_breakpad::BasicCodeModule; +using google_breakpad::CFIFrameInfo; +using google_breakpad::MockNetwork; +using google_breakpad::NetworkSourceLineResolver; +using google_breakpad::StackFrame; +using google_breakpad::SymbolSupplier; +using google_breakpad::WindowsFrameInfo; +using ::testing::_; +using ::testing::Invoke; +using ::testing::Return; +using namespace google_breakpad::source_line_protocol; + +class TestNetworkSourceLineResolver : public ::testing::Test { +public: + TestNetworkSourceLineResolver() : resolver(NULL) {} + + void SetUp() { + EXPECT_CALL(net, Init(false)).WillOnce(Return(true)); + resolver = new NetworkSourceLineResolver(&net, 0); + } + + NetworkSourceLineResolver *resolver; + MockNetwork net; +}; + +bool GeneratePositiveHasResponse(char *buffer, size_t buffer_size, + ssize_t &received) { + binarystream stream; + stream << u_int16_t(0) << OK << MODULE_LOADED; + string s = stream.str(); + received = s.length(); + memcpy(buffer, s.c_str(), s.length()); + return true; +} + +bool GenerateNegativeHasResponse(char *buffer, size_t buffer_size, + ssize_t &received) { + binarystream stream; + stream << u_int16_t(1) << OK << MODULE_NOT_LOADED; + string s = stream.str(); + received = s.length(); + memcpy(buffer, s.c_str(), s.length()); + return true; +} + +TEST_F(TestNetworkSourceLineResolver, TestHasMessages) { + EXPECT_CALL(net, Send(_,_)) + .WillOnce(Return(true)) + .WillOnce(Return(true)); + EXPECT_CALL(net, WaitToReceive(0)) + .WillOnce(Return(true)) + .WillOnce(Return(true)); + EXPECT_CALL(net, Receive(_,_,_)) + .WillOnce(Invoke(GeneratePositiveHasResponse)) + .WillOnce(Invoke(GenerateNegativeHasResponse)); + ASSERT_NE(resolver, (NetworkSourceLineResolver*)NULL); + BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", ""); + EXPECT_TRUE(resolver->HasModule(&module)); + BasicCodeModule module2(0x0, 0x0, "test2.dll", "test2.pdb", "FFFF", "", ""); + EXPECT_FALSE(resolver->HasModule(&module2)); + // calling again should hit the cache, and not the network + EXPECT_TRUE(resolver->HasModule(&module)); +} + +bool GenerateErrorResponse(char *buffer, size_t buffer_size, + ssize_t &received) { + binarystream stream; + stream << u_int16_t(0) << ERROR; + string s = stream.str(); + received = s.length(); + memcpy(buffer, s.c_str(), s.length()); + return true; +} + +TEST_F(TestNetworkSourceLineResolver, TestHasErrorResponse) { + EXPECT_CALL(net, Send(_,_)) + .WillOnce(Return(true)); + EXPECT_CALL(net, WaitToReceive(0)) + .WillOnce(Return(true)); + EXPECT_CALL(net, Receive(_,_,_)) + .WillOnce(Invoke(GenerateErrorResponse)); + ASSERT_NE(resolver, (NetworkSourceLineResolver*)NULL); + BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", ""); + // error packet should function as a not found + EXPECT_FALSE(resolver->HasModule(&module)); +} + +// GenerateLoadResponse will generate (LOAD_NOT_FOUND, LOAD_INTERRUPT, +// LOAD_FAIL, LOAD_OK) in order. +class LoadHelper { + public: + LoadHelper() : response(LOAD_NOT_FOUND), sequence(0) {} + bool GenerateLoadResponse(char *buffer, size_t buffer_size, + ssize_t &received) { + binarystream stream; + stream << sequence << OK << response; + string s = stream.str(); + received = s.length(); + memcpy(buffer, s.c_str(), s.length()); + ++sequence; + ++response; + return true; + } + u_int8_t response; + u_int16_t sequence; +}; + +TEST_F(TestNetworkSourceLineResolver, TestLoadMessages) { + EXPECT_CALL(net, Send(_,_)) + .Times(4) + .WillRepeatedly(Return(true)); + EXPECT_CALL(net, WaitToReceive(0)) + .Times(4) + .WillRepeatedly(Return(true)); + LoadHelper helper; + EXPECT_CALL(net, Receive(_,_,_)) + .Times(4) + .WillRepeatedly(Invoke(&helper, &LoadHelper::GenerateLoadResponse)); + ASSERT_NE(resolver, (NetworkSourceLineResolver*)NULL); + BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", ""); + string s; + EXPECT_EQ(SymbolSupplier::NOT_FOUND, + resolver->GetSymbolFile(&module, NULL, &s)); + BasicCodeModule module2(0x0, 0x0, "test2.dll", "test2.pdb", "FFFF", "", ""); + EXPECT_EQ(SymbolSupplier::INTERRUPT, + resolver->GetSymbolFile(&module2, NULL, &s)); + BasicCodeModule module3(0x0, 0x0, "test3.dll", "test3.pdb", "0000", "", ""); + // a FAIL result from the network should come back as NOT_FOUND + EXPECT_EQ(SymbolSupplier::NOT_FOUND, + resolver->GetSymbolFile(&module3, NULL, &s)); + BasicCodeModule module4(0x0, 0x0, "test4.dll", "test4.pdb", "1010", "", ""); + EXPECT_EQ(SymbolSupplier::FOUND, + resolver->GetSymbolFile(&module4, NULL, &s)); + // calling this should hit the cache, and not the network + EXPECT_TRUE(resolver->HasModule(&module4)); +} + +TEST_F(TestNetworkSourceLineResolver, TestLoadErrorResponse) { + EXPECT_CALL(net, Send(_,_)) + .WillOnce(Return(true)); + EXPECT_CALL(net, WaitToReceive(0)) + .WillOnce(Return(true)); + EXPECT_CALL(net, Receive(_,_,_)) + .WillOnce(Invoke(GenerateErrorResponse)); + ASSERT_NE(resolver, (NetworkSourceLineResolver*)NULL); + BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", ""); + string s; + // error packet should function as NOT_FOUND response + EXPECT_EQ(SymbolSupplier::NOT_FOUND, + resolver->GetSymbolFile(&module, NULL, &s)); +} + +class GetHelper { + public: + GetHelper() : sequence(1) {} + bool GenerateGetResponse(char *buffer, size_t buffer_size, + ssize_t &received) { + binarystream stream; + stream << sequence << OK; + switch(sequence) { + case 1: + // return full info + stream << string("test()") << u_int64_t(0x1000) << string("test.c") + << u_int32_t(1) << u_int64_t(0x1010); + break; + case 2: + // return full info + stream << string("test2()") << u_int64_t(0x2000) << string("test2.c") + << u_int32_t(2) << u_int64_t(0x2020); + break; + case 3: + // return just function name + stream << string("test3()") << u_int64_t(0x4000) << string("") + << u_int32_t(0) << u_int64_t(0); + break; + } + string s = stream.str(); + received = s.length(); + memcpy(buffer, s.c_str(), s.length()); + ++sequence; + return true; + } + u_int16_t sequence; +}; + +TEST_F(TestNetworkSourceLineResolver, TestGetMessages) { + EXPECT_CALL(net, Send(_,_)) + .Times(4) + .WillRepeatedly(Return(true)); + EXPECT_CALL(net, WaitToReceive(0)) + .Times(4) + .WillRepeatedly(Return(true)); + GetHelper helper; + EXPECT_CALL(net, Receive(_,_,_)) + .Times(4) + .WillOnce(Invoke(GeneratePositiveHasResponse)) + .WillRepeatedly(Invoke(&helper, &GetHelper::GenerateGetResponse)); + BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", ""); + // The resolver has to think the module is loaded before it will respond + // to GET requests for that module. + EXPECT_TRUE(resolver->HasModule(&module)); + StackFrame frame; + frame.module = &module; + frame.instruction = 0x1010; + resolver->FillSourceLineInfo(&frame); + EXPECT_EQ("test()", frame.function_name); + EXPECT_EQ(0x1000, frame.function_base); + EXPECT_EQ("test.c", frame.source_file_name); + EXPECT_EQ(1, frame.source_line); + EXPECT_EQ(0x1010, frame.source_line_base); + + StackFrame frame2; + frame2.module = &module; + frame2.instruction = 0x2020; + resolver->FillSourceLineInfo(&frame2); + EXPECT_EQ("test2()", frame2.function_name); + EXPECT_EQ(0x2000, frame2.function_base); + EXPECT_EQ("test2.c", frame2.source_file_name); + EXPECT_EQ(2, frame2.source_line); + EXPECT_EQ(0x2020, frame2.source_line_base); + + StackFrame frame3; + frame3.module = &module; + frame3.instruction = 0x4040; + resolver->FillSourceLineInfo(&frame3); + EXPECT_EQ("test3()", frame3.function_name); + EXPECT_EQ(0x4000, frame3.function_base); + EXPECT_EQ("", frame3.source_file_name); + EXPECT_EQ(0, frame3.source_line); + EXPECT_EQ(0, frame3.source_line_base); + + // this should come from the cache and not hit the network + StackFrame frame4; + frame4.module = &module; + frame4.instruction = 0x1010; + resolver->FillSourceLineInfo(&frame4); + EXPECT_EQ("test()", frame4.function_name); + EXPECT_EQ(0x1000, frame4.function_base); + EXPECT_EQ("test.c", frame4.source_file_name); + EXPECT_EQ(1, frame4.source_line); + EXPECT_EQ(0x1010, frame4.source_line_base); + + // this should also be cached + StackFrame frame5; + frame5.module = &module; + frame5.instruction = 0x4040; + resolver->FillSourceLineInfo(&frame5); + EXPECT_EQ("test3()", frame5.function_name); + EXPECT_EQ(0x4000, frame5.function_base); + EXPECT_EQ("", frame5.source_file_name); + EXPECT_EQ(0, frame5.source_line); + EXPECT_EQ(0, frame5.source_line_base); +} + +class GetStackWinHelper { + public: + GetStackWinHelper() : sequence(1) {} + bool GenerateGetStackWinResponse(char *buffer, size_t buffer_size, + ssize_t &received) { + binarystream stream; + stream << sequence << OK; + switch(sequence) { + case 1: + // return full info including program string + stream << string("0 0 0 1 2 3 a ff f00 1 x y ="); + break; + case 2: + // return full info, no program string + stream << string("0 0 0 1 2 3 a ff f00 0 1"); + break; + case 3: + // return empty string + stream << string(""); + break; + } + string s = stream.str(); + received = s.length(); + memcpy(buffer, s.c_str(), s.length()); + ++sequence; + return true; + } + u_int16_t sequence; +}; + +TEST_F(TestNetworkSourceLineResolver, TestGetStackWinMessages) { + EXPECT_CALL(net, Send(_,_)) + .Times(4) + .WillRepeatedly(Return(true)); + EXPECT_CALL(net, WaitToReceive(0)) + .Times(4) + .WillRepeatedly(Return(true)); + GetStackWinHelper helper; + EXPECT_CALL(net, Receive(_,_,_)) + .Times(4) + .WillOnce(Invoke(GeneratePositiveHasResponse)) + .WillRepeatedly(Invoke(&helper, + &GetStackWinHelper::GenerateGetStackWinResponse)); + + BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", ""); + // The resolver has to think the module is loaded before it will respond + // to GETSTACKWIN requests for that module. + EXPECT_TRUE(resolver->HasModule(&module)); + StackFrame frame; + frame.module = &module; + frame.instruction = 0x1010; + WindowsFrameInfo *info = resolver->FindWindowsFrameInfo(&frame); + ASSERT_NE((WindowsFrameInfo*)NULL, info); + EXPECT_EQ(0x1, info->prolog_size); + EXPECT_EQ(0x2, info->epilog_size); + EXPECT_EQ(0x3, info->parameter_size); + EXPECT_EQ(0xa, info->saved_register_size); + EXPECT_EQ(0xff, info->local_size); + EXPECT_EQ(0xf00, info->max_stack_size); + EXPECT_EQ("x y =", info->program_string); + delete info; + + StackFrame frame2; + frame2.module = &module; + frame2.instruction = 0x2020; + info = resolver->FindWindowsFrameInfo(&frame2); + ASSERT_NE((WindowsFrameInfo*)NULL, info); + EXPECT_EQ(0x1, info->prolog_size); + EXPECT_EQ(0x2, info->epilog_size); + EXPECT_EQ(0x3, info->parameter_size); + EXPECT_EQ(0xa, info->saved_register_size); + EXPECT_EQ(0xff, info->local_size); + EXPECT_EQ(0xf00, info->max_stack_size); + EXPECT_EQ("", info->program_string); + EXPECT_EQ(true, info->allocates_base_pointer); + delete info; + + StackFrame frame3; + frame3.module = &module; + frame3.instruction = 0x4040; + info = resolver->FindWindowsFrameInfo(&frame3); + EXPECT_EQ((WindowsFrameInfo*)NULL, info); + + // this should come from the cache and not hit the network + StackFrame frame4; + frame4.module = &module; + frame4.instruction = 0x1010; + info = resolver->FindWindowsFrameInfo(&frame4); + ASSERT_NE((WindowsFrameInfo*)NULL, info); + EXPECT_EQ(0x1, info->prolog_size); + EXPECT_EQ(0x2, info->epilog_size); + EXPECT_EQ(0x3, info->parameter_size); + EXPECT_EQ(0xa, info->saved_register_size); + EXPECT_EQ(0xff, info->local_size); + EXPECT_EQ(0xf00, info->max_stack_size); + EXPECT_EQ("x y =", info->program_string); + delete info; + + // this should also be cached + StackFrame frame5; + frame5.module = &module; + frame5.instruction = 0x4040; + info = resolver->FindWindowsFrameInfo(&frame5); + EXPECT_EQ((WindowsFrameInfo*)NULL, info); +} + +class GetStackCFIHelper { + public: + GetStackCFIHelper() : sequence(1) {} + bool GenerateGetStackCFIResponse(char *buffer, size_t buffer_size, + ssize_t &received) { + binarystream stream; + stream << sequence << OK; + switch(sequence) { + case 1: + // return .cfa, .ra, registers + stream << string(".cfa: 1234 .ra: .cfa 5 + r0: abc xyz r2: 10 10"); + break; + case 2: + // return just .cfa + stream << string(".cfa: xyz"); + break; + case 3: + // return empty string + stream << string(""); + break; + } + string s = stream.str(); + received = s.length(); + memcpy(buffer, s.c_str(), s.length()); + ++sequence; + return true; + } + u_int16_t sequence; +}; + +TEST_F(TestNetworkSourceLineResolver, TestGetStackCFIMessages) { + EXPECT_CALL(net, Send(_,_)) + .Times(4) + .WillRepeatedly(Return(true)); + EXPECT_CALL(net, WaitToReceive(0)) + .Times(4) + .WillRepeatedly(Return(true)); + GetStackCFIHelper helper; + EXPECT_CALL(net, Receive(_,_,_)) + .Times(4) + .WillOnce(Invoke(GeneratePositiveHasResponse)) + .WillRepeatedly(Invoke(&helper, + &GetStackCFIHelper::GenerateGetStackCFIResponse)); + + BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", ""); + // The resolver has to think the module is loaded before it will respond + // to GETSTACKCFI requests for that module. + EXPECT_TRUE(resolver->HasModule(&module)); + StackFrame frame; + frame.module = &module; + frame.instruction = 0x1010; + CFIFrameInfo *info = resolver->FindCFIFrameInfo(&frame); + ASSERT_NE((CFIFrameInfo*)NULL, info); + // Ostensibly we would check the internal data structure, but + // we'd have to either mock out some other classes or get internal access, + // so this is easier. + EXPECT_EQ(".cfa: 1234 .ra: .cfa 5 + r0: abc xyz r2: 10 10", + info->Serialize()); + delete info; + + StackFrame frame2; + frame2.module = &module; + frame2.instruction = 0x2020; + info = resolver->FindCFIFrameInfo(&frame2); + ASSERT_NE((CFIFrameInfo*)NULL, info); + EXPECT_EQ(".cfa: xyz", info->Serialize()); + delete info; + + StackFrame frame3; + frame3.module = &module; + frame3.instruction = 0x4040; + info = resolver->FindCFIFrameInfo(&frame3); + EXPECT_EQ((CFIFrameInfo*)NULL, info); + + // this should come from the cache and not hit the network + StackFrame frame4; + frame4.module = &module; + frame4.instruction = 0x1010; + info = resolver->FindCFIFrameInfo(&frame4); + ASSERT_NE((CFIFrameInfo*)NULL, info); + EXPECT_EQ(".cfa: 1234 .ra: .cfa 5 + r0: abc xyz r2: 10 10", + info->Serialize()); + delete info; + + // this should also be cached + StackFrame frame5; + frame5.module = &module; + frame5.instruction = 0x4040; + info = resolver->FindCFIFrameInfo(&frame5); + EXPECT_EQ((CFIFrameInfo*)NULL, info); +} + +TEST_F(TestNetworkSourceLineResolver, TestBogusData) { + EXPECT_FALSE(resolver->HasModule(NULL)); + + StackFrame frame; + frame.module = NULL; + frame.instruction = 0x1000; + resolver->FillSourceLineInfo(&frame); + EXPECT_EQ("", frame.function_name); + EXPECT_EQ(0x0, frame.function_base); + EXPECT_EQ("", frame.source_file_name); + EXPECT_EQ(0, frame.source_line); + EXPECT_EQ(0x0, frame.source_line_base); + + EXPECT_EQ((WindowsFrameInfo*)NULL, resolver->FindWindowsFrameInfo(&frame)); +} + +} // namespace + +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} -- cgit v1.2.1