breakpad/src/processor/network_source_line_resolver_unittest.cc

536 lines
18 KiB
C++

// 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 <string>
#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();
}