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 --- src/config.h.in | 3 + .../processor/basic_source_line_resolver.h | 13 +- .../processor/network_source_line_resolver.h | 168 ++++ .../processor/source_line_resolver_interface.h | 24 +- src/processor/basic_code_module.h | 15 + src/processor/basic_source_line_resolver.cc | 265 +++--- .../basic_source_line_resolver_unittest.cc | 104 ++- src/processor/binarystream.cc | 123 +++ src/processor/binarystream.h | 88 ++ src/processor/binarystream_unittest.cc | 432 ++++++++++ src/processor/cfi_frame_info.cc | 23 + src/processor/cfi_frame_info.h | 4 + src/processor/cfi_frame_info_unittest.cc | 14 + src/processor/network_interface.h | 62 ++ src/processor/network_source_line_protocol.h | 162 ++++ src/processor/network_source_line_resolver.cc | 435 ++++++++++ ...network_source_line_resolver_server_unittest.cc | 207 +++++ .../network_source_line_resolver_unittest.cc | 535 ++++++++++++ src/processor/network_source_line_server.cc | 435 ++++++++++ src/processor/network_source_line_server.h | 136 +++ .../network_source_line_server_unittest.cc | 955 +++++++++++++++++++++ src/processor/source_daemon.cc | 127 +++ src/processor/stackwalker.cc | 8 +- src/processor/tokenize.cc | 76 ++ src/processor/tokenize.h | 61 ++ src/processor/udp_network.cc | 185 ++++ src/processor/udp_network.h | 73 ++ src/processor/windows_frame_info.h | 68 ++ 28 files changed, 4571 insertions(+), 230 deletions(-) create mode 100644 src/google_breakpad/processor/network_source_line_resolver.h create mode 100644 src/processor/binarystream.cc create mode 100644 src/processor/binarystream.h create mode 100644 src/processor/binarystream_unittest.cc create mode 100644 src/processor/network_interface.h create mode 100644 src/processor/network_source_line_protocol.h create mode 100644 src/processor/network_source_line_resolver.cc create mode 100644 src/processor/network_source_line_resolver_server_unittest.cc create mode 100644 src/processor/network_source_line_resolver_unittest.cc create mode 100644 src/processor/network_source_line_server.cc create mode 100644 src/processor/network_source_line_server.h create mode 100644 src/processor/network_source_line_server_unittest.cc create mode 100644 src/processor/source_daemon.cc create mode 100644 src/processor/tokenize.cc create mode 100644 src/processor/tokenize.h create mode 100644 src/processor/udp_network.cc create mode 100644 src/processor/udp_network.h (limited to 'src') diff --git a/src/config.h.in b/src/config.h.in index 5ee5ce6b..d2909318 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -1,5 +1,8 @@ /* src/config.h.in. Generated from configure.ac by autoheader. */ +/* actual length of specific struct sockaddr */ +#undef GET_SA_LEN + /* Define to 1 if you have the header file. */ #undef HAVE_DLFCN_H diff --git a/src/google_breakpad/processor/basic_source_line_resolver.h b/src/google_breakpad/processor/basic_source_line_resolver.h index 831556b5..fe93f4d2 100644 --- a/src/google_breakpad/processor/basic_source_line_resolver.h +++ b/src/google_breakpad/processor/basic_source_line_resolver.h @@ -53,17 +53,18 @@ class BasicSourceLineResolver : public SourceLineResolverInterface { // Adds a module to this resolver, returning true on success. // The given map_file is read into memory, and its symbols will be // retained until the BasicSourceLineResolver is destroyed. - virtual bool LoadModule(const string &module_name, const string &map_file); + virtual bool LoadModule(const CodeModule *module, const string &map_file); // Exactly the same as above, except the given map_buffer is used // for symbols. - virtual bool LoadModuleUsingMapBuffer(const string &module_name, + virtual bool LoadModuleUsingMapBuffer(const CodeModule *module, const string &map_buffer); - virtual bool HasModule(const string &module_name) const; - virtual void FillSourceLineInfo(StackFrame *frame) const; - virtual WindowsFrameInfo *FindWindowsFrameInfo(const StackFrame *frame) const; - virtual CFIFrameInfo *FindCFIFrameInfo(const StackFrame *frame) const; + void UnloadModule(const CodeModule *module); + virtual bool HasModule(const CodeModule *module); + virtual void FillSourceLineInfo(StackFrame *frame); + virtual WindowsFrameInfo *FindWindowsFrameInfo(const StackFrame *frame); + virtual CFIFrameInfo *FindCFIFrameInfo(const StackFrame *frame); private: template class MemAddrMap; diff --git a/src/google_breakpad/processor/network_source_line_resolver.h b/src/google_breakpad/processor/network_source_line_resolver.h new file mode 100644 index 00000000..f2c7732d --- /dev/null +++ b/src/google_breakpad/processor/network_source_line_resolver.h @@ -0,0 +1,168 @@ +// 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. + +// NetworkSourceLineResolver implements SourceLineResolverInterface and +// SymbolSupplier using a UDP-based network protocol to communicate to a +// server process which handles the lower-level details of loading symbols +// and resolving source info. When used, it must be used simultaneously +// as the SourceLineResolver and SymbolSupplier. +// +// See network_source_line_server.h for a description of the protocol used. +// An implementation of the server side of the protocol is provided there +// as NetworkSourceLineServer. + +#ifndef GOOGLE_BREAKPAD_PROCESSOR_NETWORK_SOURCE_LINE_RESOLVER_H_ +#define GOOGLE_BREAKPAD_PROCESSOR_NETWORK_SOURCE_LINE_RESOLVER_H_ + +#include + +#include +#include + +#include "google_breakpad/common/breakpad_types.h" +#include "google_breakpad/processor/source_line_resolver_interface.h" +#include "google_breakpad/processor/stack_frame.h" +#include "google_breakpad/processor/symbol_supplier.h" +#include "processor/binarystream.h" +#include "processor/linked_ptr.h" +#include "processor/network_interface.h" + +namespace google_breakpad { + +using std::string; + +class NetworkSourceLineResolver : public SourceLineResolverInterface, + public SymbolSupplier { + public: + // The server and port to connect to, and the + // maximum time (in milliseconds) to wait for network replies. + NetworkSourceLineResolver(const string &server, + unsigned short port, + int wait_milliseconds); + // The network interface to connect to, and maximum wait time. + NetworkSourceLineResolver(NetworkInterface *net, + int wait_milliseconds); + virtual ~NetworkSourceLineResolver(); + + // SourceLineResolverInterface methods, see source_line_resolver_interface.h + // for more details. + + + // These methods are actually NOOPs in this implementation. + // The server loads modules as a result of the GetSymbolFile call. + // Since we're both the symbol supplier and source line resolver, + // this is an optimization. + virtual bool LoadModule(const CodeModule *module, const string &map_file); + virtual bool LoadModuleUsingMapBuffer(const CodeModule *module, + const string &map_buffer); + + void UnloadModule(const CodeModule *module); + + virtual bool HasModule(const CodeModule *module); + + virtual void FillSourceLineInfo(StackFrame *frame); + virtual WindowsFrameInfo *FindWindowsFrameInfo(const StackFrame *frame); + virtual CFIFrameInfo *FindCFIFrameInfo(const StackFrame *frame); + + // SymbolSupplier methods, see symbol_supplier.h for more details. + // Note that the server will actually load the symbol data + // in response to this request, as an optimization. + virtual SymbolResult GetSymbolFile(const CodeModule *module, + const SystemInfo *system_info, + string *symbol_file); + //FIXME: we'll never return symbol_data here, it doesn't make sense. + // the SymbolSupplier interface should just state that the supplier + // *may* fill in symbol_data if it desires, and clients should + // handle it gracefully either way. + virtual SymbolResult GetSymbolFile(const CodeModule *module, + const SystemInfo *system_info, + string *symbol_file, + string *symbol_data); + + private: + int wait_milliseconds_; + // if false, some part of our network setup failed. + bool initialized_; + // sequence number of the last request we made + u_int16_t sequence_; + NetworkInterface *net_; + // cached list of loaded modules, so we can quickly answer + // HasModule requests for modules we've already queried the + // server about, avoiding another network round-trip. + std::set module_cache_; + // cached list of modules for which we don't have symbols, + // so we can short-circuit that as well. + std::set no_symbols_cache_; + + // Cached list of source line info, to avoid repeated GET requests + // for the same frame. In Multithreaded apps that use the same + // framework across threads, it's pretty common to hit the same + // exact set of frames in multiple threads. + // Data is stored in the cache keyed by instruction pointer + typedef std::map SourceCache; + SourceCache source_line_info_cache_; + + // Cached list of WindowsFrameInfo/CFIFrameInfo, for the same reason. + // Stored as serialized strings to avoid shuffling around pointers. + typedef std::map FrameInfoCache; + + typedef enum { + kWindowsFrameInfo = 0, + kCFIFrameInfo = 1, + } FrameInfoType; + FrameInfoCache frame_info_cache_[2]; + + // Send a message to the server, wait a certain amount of time for a reply. + // Returns true if a response is received, with the response data + // in |response|. + // Returns false if the response times out. + bool SendMessageGetResponse(const binarystream &message, + binarystream &response); + + // See if this stack frame is cached, and fill in the source line info + // if so. + bool FindCachedSourceLineInfo(StackFrame *frame) const; + bool FindCachedFrameInfo(const StackFrame *frame, + FrameInfoType type, + string *info) const; + + // Save this stack frame in the cache + void CacheSourceLineInfo(const StackFrame *frame); + void CacheFrameInfo(const StackFrame *frame, + FrameInfoType type, + const string &info); + + // Disallow unwanted copy ctor and assignment operator + NetworkSourceLineResolver(const NetworkSourceLineResolver&); + void operator=(const NetworkSourceLineResolver&); +}; + +} // namespace google_breakpad + +#endif // GOOGLE_BREAKPAD_PROCESSOR_NETWORK_SOURCE_LINE_RESOLVER_H_ diff --git a/src/google_breakpad/processor/source_line_resolver_interface.h b/src/google_breakpad/processor/source_line_resolver_interface.h index a7ec9b7f..fa45d75f 100644 --- a/src/google_breakpad/processor/source_line_resolver_interface.h +++ b/src/google_breakpad/processor/source_line_resolver_interface.h @@ -36,6 +36,7 @@ #include #include "google_breakpad/common/breakpad_types.h" +#include "google_breakpad/processor/code_module.h" namespace google_breakpad { @@ -53,37 +54,40 @@ class SourceLineResolverInterface { // Adds a module to this resolver, returning true on success. // - // module_name may be an arbitrary string. Typically, it will be the - // filename of the module, optionally with version identifiers. + // module should have at least the code_file, debug_file, + // and debug_identifier members populated. // // map_file should contain line/address mappings for this module. - virtual bool LoadModule(const string &module_name, + virtual bool LoadModule(const CodeModule *module, const string &map_file) = 0; // Same as above, but takes the contents of a pre-read map buffer - virtual bool LoadModuleUsingMapBuffer(const string &module_name, + virtual bool LoadModuleUsingMapBuffer(const CodeModule *module, const string &map_buffer) = 0; - // Returns true if a module with the given name has been loaded. - virtual bool HasModule(const string &module_name) const = 0; + // Request that the specified module be unloaded from this resolver. + // A resolver may choose to ignore such a request. + virtual void UnloadModule(const CodeModule *module) = 0; + + // Returns true if the module has been loaded. + virtual bool HasModule(const CodeModule *module) = 0; // Fills in the function_base, function_name, source_file_name, // and source_line fields of the StackFrame. The instruction and // module_name fields must already be filled in. - virtual void FillSourceLineInfo(StackFrame *frame) const = 0; + virtual void FillSourceLineInfo(StackFrame *frame) = 0; // If Windows stack walking information is available covering // FRAME's instruction address, return a WindowsFrameInfo structure // describing it. If the information is not available, returns NULL. // A NULL return value does not indicate an error. The caller takes // ownership of any returned WindowsFrameInfo object. - virtual WindowsFrameInfo *FindWindowsFrameInfo(const StackFrame *frame) - const = 0; + virtual WindowsFrameInfo *FindWindowsFrameInfo(const StackFrame *frame) = 0; // If CFI stack walking information is available covering ADDRESS, // return a CFIFrameInfo structure describing it. If the information // is not available, return NULL. The caller takes ownership of any // returned CFIFrameInfo object. - virtual CFIFrameInfo *FindCFIFrameInfo(const StackFrame *frame) const = 0; + virtual CFIFrameInfo *FindCFIFrameInfo(const StackFrame *frame) = 0; protected: // SourceLineResolverInterface cannot be instantiated except by subclasses diff --git a/src/processor/basic_code_module.h b/src/processor/basic_code_module.h index 5c9f75f1..e3955d29 100644 --- a/src/processor/basic_code_module.h +++ b/src/processor/basic_code_module.h @@ -63,6 +63,21 @@ class BasicCodeModule : public CodeModule { debug_file_(that->debug_file()), debug_identifier_(that->debug_identifier()), version_(that->version()) {} + + BasicCodeModule(u_int64_t base_address, u_int64_t size, + const string &code_file, + const string &code_identifier, + const string &debug_file, + const string &debug_identifier, + const string &version) + : base_address_(base_address), + size_(size), + code_file_(code_file), + code_identifier_(code_identifier), + debug_file_(debug_file), + debug_identifier_(debug_identifier), + version_(version) + {} virtual ~BasicCodeModule() {} // See code_module.h for descriptions of these methods and the associated diff --git a/src/processor/basic_source_line_resolver.cc b/src/processor/basic_source_line_resolver.cc index 0385f89f..52044dbf 100644 --- a/src/processor/basic_source_line_resolver.cc +++ b/src/processor/basic_source_line_resolver.cc @@ -44,10 +44,11 @@ #include "google_breakpad/processor/basic_source_line_resolver.h" #include "google_breakpad/processor/code_module.h" #include "google_breakpad/processor/stack_frame.h" +#include "processor/cfi_frame_info.h" #include "processor/linked_ptr.h" #include "processor/scoped_ptr.h" #include "processor/windows_frame_info.h" -#include "processor/cfi_frame_info.h" +#include "processor/tokenize.h" using std::map; using std::vector; @@ -55,6 +56,8 @@ using std::make_pair; namespace google_breakpad { +static const char *kWhitespace = " \r\n"; + struct BasicSourceLineResolver::Line { Line(MemAddr addr, MemAddr code_size, int file_id, int source_line) : address(addr) @@ -135,32 +138,6 @@ class BasicSourceLineResolver::Module { friend class BasicSourceLineResolver; typedef map FileMap; - // The types for windows_frame_info_. This is equivalent to MS DIA's - // StackFrameTypeEnum. Each identifies a different type of frame - // information, although all are represented in the symbol file in the - // same format. These are used as indices to the windows_frame_info_ array. - enum WindowsFrameInfoTypes { - WINDOWS_FRAME_INFO_FPO = 0, - WINDOWS_FRAME_INFO_TRAP, // not used here - WINDOWS_FRAME_INFO_TSS, // not used here - WINDOWS_FRAME_INFO_STANDARD, - WINDOWS_FRAME_INFO_FRAME_DATA, - WINDOWS_FRAME_INFO_LAST, // must be the last sequentially-numbered item - WINDOWS_FRAME_INFO_UNKNOWN = -1 - }; - - // Splits line into at most max_tokens space-separated tokens, placing - // them in the tokens vector. line is a 0-terminated string that - // optionally ends with a newline character or combination, which will - // be removed. line must not contain any embedded '\n' or '\r' characters. - // If more tokens than max_tokens are present, the final token is placed - // into the vector without splitting it up at all. This modifies line as - // a side effect. Returns true if exactly max_tokens tokens are returned, - // and false if fewer are returned. This is not considered a failure of - // Tokenize, but may be treated as a failure if the caller expects an - // exact, as opposed to maximum, number of tokens. - static bool Tokenize(char *line, int max_tokens, vector *tokens); - // Parses a file declaration bool ParseFile(char *file_line); @@ -178,9 +155,6 @@ class BasicSourceLineResolver::Module { // it in the appropriate table. bool ParseStackInfo(char *stack_info_line); - // Parses a STACK WIN record, storing it in windows_frame_info_. - bool ParseWindowsFrameInfo(char *stack_info_line); - // Parses a STACK CFI record, storing it in cfi_frame_info_. bool ParseCFIFrameInfo(char *stack_info_line); @@ -198,7 +172,7 @@ class BasicSourceLineResolver::Module { // there may be overlaps between maps of different types, but some // information is only available as certain types. ContainedRangeMap< MemAddr, linked_ptr > - windows_frame_info_[WINDOWS_FRAME_INFO_LAST]; + windows_frame_info_[WindowsFrameInfo::STACK_INFO_LAST]; // DWARF CFI stack walking data. The Module stores the initial rule sets // and rule deltas as strings, just as they appear in the symbol file: @@ -230,53 +204,75 @@ BasicSourceLineResolver::~BasicSourceLineResolver() { delete modules_; } -bool BasicSourceLineResolver::LoadModule(const string &module_name, +bool BasicSourceLineResolver::LoadModule(const CodeModule *module, const string &map_file) { + if (module == NULL) + return false; + // Make sure we don't already have a module with the given name. - if (modules_->find(module_name) != modules_->end()) { - BPLOG(INFO) << "Symbols for module " << module_name << " already loaded"; + if (modules_->find(module->code_file()) != modules_->end()) { + BPLOG(INFO) << "Symbols for module " << module->code_file() + << " already loaded"; return false; } - BPLOG(INFO) << "Loading symbols for module " << module_name << " from " << - map_file; + BPLOG(INFO) << "Loading symbols for module " << module->code_file() + << " from " << map_file; - Module *module = new Module(module_name); - if (!module->LoadMap(map_file)) { - delete module; + Module *basic_module = new Module(module->code_file()); + if (!basic_module->LoadMap(map_file)) { + delete basic_module; return false; } - modules_->insert(make_pair(module_name, module)); + modules_->insert(make_pair(module->code_file(), basic_module)); return true; } bool BasicSourceLineResolver::LoadModuleUsingMapBuffer( - const string &module_name, + const CodeModule *module, const string &map_buffer) { + if (!module) + return false; + // Make sure we don't already have a module with the given name. - if (modules_->find(module_name) != modules_->end()) { - BPLOG(INFO) << "Symbols for module " << module_name << " already loaded"; + if (modules_->find(module->code_file()) != modules_->end()) { + BPLOG(INFO) << "Symbols for module " << module->code_file() + << " already loaded"; return false; } - BPLOG(INFO) << "Loading symbols for module " << module_name << " from buffer"; + BPLOG(INFO) << "Loading symbols for module " << module->code_file() + << " from buffer"; - Module *module = new Module(module_name); - if (!module->LoadMapFromBuffer(map_buffer)) { - delete module; + Module *basic_module = new Module(module->code_file()); + if (!basic_module->LoadMapFromBuffer(map_buffer)) { + delete basic_module; return false; } - modules_->insert(make_pair(module_name, module)); + modules_->insert(make_pair(module->code_file(), basic_module)); return true; } -bool BasicSourceLineResolver::HasModule(const string &module_name) const { - return modules_->find(module_name) != modules_->end(); +void BasicSourceLineResolver::UnloadModule(const CodeModule *module) +{ + if (!module) + return; + + ModuleMap::iterator iter = modules_->find(module->code_file()); + if (iter != modules_->end()) { + modules_->erase(iter); + } +} + +bool BasicSourceLineResolver::HasModule(const CodeModule *module) { + if (!module) + return false; + return modules_->find(module->code_file()) != modules_->end(); } -void BasicSourceLineResolver::FillSourceLineInfo(StackFrame *frame) const { +void BasicSourceLineResolver::FillSourceLineInfo(StackFrame *frame) { if (frame->module) { ModuleMap::const_iterator it = modules_->find(frame->module->code_file()); if (it != modules_->end()) { @@ -286,7 +282,7 @@ void BasicSourceLineResolver::FillSourceLineInfo(StackFrame *frame) const { } WindowsFrameInfo *BasicSourceLineResolver::FindWindowsFrameInfo( - const StackFrame *frame) const { + const StackFrame *frame) { if (frame->module) { ModuleMap::const_iterator it = modules_->find(frame->module->code_file()); if (it != modules_->end()) { @@ -297,7 +293,7 @@ WindowsFrameInfo *BasicSourceLineResolver::FindWindowsFrameInfo( } CFIFrameInfo *BasicSourceLineResolver::FindCFIFrameInfo( - const StackFrame *frame) const { + const StackFrame *frame) { if (frame->module) { ModuleMap::const_iterator it = modules_->find(frame->module->code_file()); if (it != modules_->end()) { @@ -516,15 +512,16 @@ WindowsFrameInfo *BasicSourceLineResolver::Module::FindWindowsFrameInfo( MemAddr address = frame->instruction - frame->module->base_address(); scoped_ptr result(new WindowsFrameInfo()); - // We only know about WINDOWS_FRAME_INFO_FRAME_DATA and - // WINDOWS_FRAME_INFO_FPO. Prefer them in this order. - // WINDOWS_FRAME_INFO_FRAME_DATA is the newer type that includes its - // own program string. WINDOWS_FRAME_INFO_FPO is the older type + // We only know about WindowsFrameInfo::STACK_INFO_FRAME_DATA and + // WindowsFrameInfo::STACK_INFO_FPO. Prefer them in this order. + // WindowsFrameInfo::STACK_INFO_FRAME_DATA is the newer type that + // includes its own program string. + // WindowsFrameInfo::STACK_INFO_FPO is the older type // corresponding to the FPO_DATA struct. See stackwalker_x86.cc. linked_ptr frame_info; - if ((windows_frame_info_[WINDOWS_FRAME_INFO_FRAME_DATA] + if ((windows_frame_info_[WindowsFrameInfo::STACK_INFO_FRAME_DATA] .RetrieveRange(address, &frame_info)) - || (windows_frame_info_[WINDOWS_FRAME_INFO_FPO] + || (windows_frame_info_[WindowsFrameInfo::STACK_INFO_FPO] .RetrieveRange(address, &frame_info))) { result->CopyFrom(*frame_info.get()); return result.release(); @@ -600,40 +597,12 @@ bool BasicSourceLineResolver::Module::ParseCFIRuleSet( return parser.Parse(rule_set); } -// static -bool BasicSourceLineResolver::Module::Tokenize(char *line, int max_tokens, - vector *tokens) { - tokens->clear(); - tokens->reserve(max_tokens); - - int remaining = max_tokens; - - // Split tokens on the space character. Look for newlines too to - // strip them out before exhausting max_tokens. - char *save_ptr; - char *token = strtok_r(line, " \r\n", &save_ptr); - while (token && --remaining > 0) { - tokens->push_back(token); - if (remaining > 1) - token = strtok_r(NULL, " \r\n", &save_ptr); - } - - // If there's anything left, just add it as a single token. - if (!remaining > 0) { - if ((token = strtok_r(NULL, "\r\n", &save_ptr))) { - tokens->push_back(token); - } - } - - return tokens->size() == static_cast(max_tokens); -} - bool BasicSourceLineResolver::Module::ParseFile(char *file_line) { // FILE file_line += 5; // skip prefix vector tokens; - if (!Tokenize(file_line, 2, &tokens)) { + if (!Tokenize(file_line, kWhitespace, 2, &tokens)) { return false; } @@ -657,7 +626,7 @@ BasicSourceLineResolver::Module::ParseFunction(char *function_line) { function_line += 5; // skip prefix vector tokens; - if (!Tokenize(function_line, 4, &tokens)) { + if (!Tokenize(function_line, kWhitespace, 4, &tokens)) { return NULL; } @@ -673,7 +642,7 @@ BasicSourceLineResolver::Line* BasicSourceLineResolver::Module::ParseLine( char *line_line) { //
vector tokens; - if (!Tokenize(line_line, 4, &tokens)) { + if (!Tokenize(line_line, kWhitespace, 4, &tokens)) { return NULL; } @@ -695,7 +664,7 @@ bool BasicSourceLineResolver::Module::ParsePublicSymbol(char *public_line) { public_line += 7; vector tokens; - if (!Tokenize(public_line, 3, &tokens)) { + if (!Tokenize(public_line, kWhitespace, 3, &tokens)) { return false; } @@ -727,87 +696,51 @@ bool BasicSourceLineResolver::Module::ParseStackInfo(char *stack_info_line) { while (*stack_info_line == ' ') stack_info_line++; const char *platform = stack_info_line; - while (!strchr(" \r\n", *stack_info_line)) + while (!strchr(kWhitespace, *stack_info_line)) stack_info_line++; *stack_info_line++ = '\0'; // MSVC stack frame info. - if (strcmp(platform, "WIN") == 0) - return ParseWindowsFrameInfo(stack_info_line); - - // DWARF CFI stack frame info - else if (strcmp(platform, "CFI") == 0) + if (strcmp(platform, "WIN") == 0) { + int type; + u_int64_t rva, code_size; + linked_ptr + stack_frame_info(WindowsFrameInfo::ParseFromString(stack_info_line, + type, + rva, + code_size)); + if (stack_frame_info == NULL) + return false; + + // TODO(mmentovai): I wanted to use StoreRange's return value as this + // method's return value, but MSVC infrequently outputs stack info that + // violates the containment rules. This happens with a section of code + // in strncpy_s in test_app.cc (testdata/minidump2). There, problem looks + // like this: + // STACK WIN 4 4242 1a a 0 ... (STACK WIN 4 base size prolog 0 ...) + // STACK WIN 4 4243 2e 9 0 ... + // ContainedRangeMap treats these two blocks as conflicting. In reality, + // when the prolog lengths are taken into account, the actual code of + // these blocks doesn't conflict. However, we can't take the prolog lengths + // into account directly here because we'd wind up with a different set + // of range conflicts when MSVC outputs stack info like this: + // STACK WIN 4 1040 73 33 0 ... + // STACK WIN 4 105a 59 19 0 ... + // because in both of these entries, the beginning of the code after the + // prolog is at 0x1073, and the last byte of contained code is at 0x10b2. + // Perhaps we could get away with storing ranges by rva + prolog_size + // if ContainedRangeMap were modified to allow replacement of + // already-stored values. + + windows_frame_info_[type].StoreRange(rva, code_size, stack_frame_info); + return true; + } else if (strcmp(platform, "CFI") == 0) { + // DWARF CFI stack frame info return ParseCFIFrameInfo(stack_info_line); - - // Something unrecognized. - else - return false; -} - -bool BasicSourceLineResolver::Module::ParseWindowsFrameInfo( - char *stack_info_line) { - // The format of a STACK WIN record is documented at: - // - // http://code.google.com/p/google-breakpad/wiki/SymbolFiles - - vector tokens; - if (!Tokenize(stack_info_line, 11, &tokens)) - return false; - - int type = strtol(tokens[0], NULL, 16); - if (type < 0 || type > WINDOWS_FRAME_INFO_LAST - 1) - return false; - - u_int64_t rva = strtoull(tokens[1], NULL, 16); - u_int64_t code_size = strtoull(tokens[2], NULL, 16); - u_int32_t prolog_size = strtoul(tokens[3], NULL, 16); - u_int32_t epilog_size = strtoul(tokens[4], NULL, 16); - u_int32_t parameter_size = strtoul(tokens[5], NULL, 16); - u_int32_t saved_register_size = strtoul(tokens[6], NULL, 16); - u_int32_t local_size = strtoul(tokens[7], NULL, 16); - u_int32_t max_stack_size = strtoul(tokens[8], NULL, 16); - int has_program_string = strtoul(tokens[9], NULL, 16); - - const char *program_string = ""; - int allocates_base_pointer = 0; - if (has_program_string) { - program_string = tokens[10]; } else { - allocates_base_pointer = strtoul(tokens[10], NULL, 16); - } - - // TODO(mmentovai): I wanted to use StoreRange's return value as this - // method's return value, but MSVC infrequently outputs stack info that - // violates the containment rules. This happens with a section of code - // in strncpy_s in test_app.cc (testdata/minidump2). There, problem looks - // like this: - // STACK WIN 4 4242 1a a 0 ... (STACK WIN 4 base size prolog 0 ...) - // STACK WIN 4 4243 2e 9 0 ... - // ContainedRangeMap treats these two blocks as conflicting. In reality, - // when the prolog lengths are taken into account, the actual code of - // these blocks doesn't conflict. However, we can't take the prolog lengths - // into account directly here because we'd wind up with a different set - // of range conflicts when MSVC outputs stack info like this: - // STACK WIN 4 1040 73 33 0 ... - // STACK WIN 4 105a 59 19 0 ... - // because in both of these entries, the beginning of the code after the - // prolog is at 0x1073, and the last byte of contained code is at 0x10b2. - // Perhaps we could get away with storing ranges by rva + prolog_size - // if ContainedRangeMap were modified to allow replacement of - // already-stored values. - - linked_ptr stack_frame_info( - new WindowsFrameInfo(prolog_size, - epilog_size, - parameter_size, - saved_register_size, - local_size, - max_stack_size, - allocates_base_pointer, - program_string)); - windows_frame_info_[type].StoreRange(rva, code_size, stack_frame_info); - - return true; + // Something unrecognized. + return false; + } } bool BasicSourceLineResolver::Module::ParseCFIFrameInfo( diff --git a/src/processor/basic_source_line_resolver_unittest.cc b/src/processor/basic_source_line_resolver_unittest.cc index 126ce6d6..693a5ce5 100644 --- a/src/processor/basic_source_line_resolver_unittest.cc +++ b/src/processor/basic_source_line_resolver_unittest.cc @@ -29,6 +29,8 @@ #include #include + +#include "breakpad_googletest_includes.h" #include "google_breakpad/processor/basic_source_line_resolver.h" #include "google_breakpad/processor/code_module.h" #include "google_breakpad/processor/stack_frame.h" @@ -39,16 +41,6 @@ #include "processor/windows_frame_info.h" #include "processor/cfi_frame_info.h" -#define ASSERT_TRUE(cond) \ - if (!(cond)) { \ - fprintf(stderr, "FAILED: %s at %s:%d\n", #cond, __FILE__, __LINE__); \ - return false; \ - } - -#define ASSERT_FALSE(cond) ASSERT_TRUE(!(cond)) - -#define ASSERT_EQ(e1, e2) ASSERT_TRUE((e1) == (e2)) - namespace { using std::string; @@ -120,9 +112,11 @@ static bool VerifyRegisters( const CFIFrameInfo::RegisterValueMap &actual) { CFIFrameInfo::RegisterValueMap::const_iterator a; a = actual.find(".cfa"); - ASSERT_TRUE(a != actual.end()); + if (a == actual.end()) + return false; a = actual.find(".ra"); - ASSERT_TRUE(a != actual.end()); + if (a == actual.end()) + return false; for (a = actual.begin(); a != actual.end(); a++) { CFIFrameInfo::RegisterValueMap::const_iterator e = expected.find(a->first); @@ -146,10 +140,11 @@ static bool VerifyRegisters( static bool VerifyEmpty(const StackFrame &frame) { - ASSERT_TRUE(frame.function_name.empty()); - ASSERT_TRUE(frame.source_file_name.empty()); - ASSERT_EQ(frame.source_line, 0); - return true; + if (frame.function_name.empty() && + frame.source_file_name.empty() && + frame.source_line == 0) + return true; + return false; } static void ClearSourceLineInfo(StackFrame *frame) { @@ -159,17 +154,26 @@ static void ClearSourceLineInfo(StackFrame *frame) { frame->source_line = 0; } -static bool RunTests() { - string testdata_dir = string(getenv("srcdir") ? getenv("srcdir") : ".") + - "/src/processor/testdata"; +class TestBasicSourceLineResolver : public ::testing::Test { +public: + void SetUp() { + testdata_dir = string(getenv("srcdir") ? getenv("srcdir") : ".") + + "/src/processor/testdata"; + } BasicSourceLineResolver resolver; - ASSERT_TRUE(resolver.LoadModule("module1", testdata_dir + "/module1.out")); - ASSERT_TRUE(resolver.HasModule("module1")); - ASSERT_TRUE(resolver.LoadModule("module2", testdata_dir + "/module2.out")); - ASSERT_TRUE(resolver.HasModule("module2")); + string testdata_dir; +}; +TEST_F(TestBasicSourceLineResolver, TestLoadAndResolve) +{ TestCodeModule module1("module1"); + ASSERT_TRUE(resolver.LoadModule(&module1, testdata_dir + "/module1.out")); + ASSERT_TRUE(resolver.HasModule(&module1)); + TestCodeModule module2("module2"); + ASSERT_TRUE(resolver.LoadModule(&module2, testdata_dir + "/module2.out")); + ASSERT_TRUE(resolver.HasModule(&module2)); + StackFrame frame; scoped_ptr windows_frame_info; @@ -271,8 +275,8 @@ static bool RunTests() { ASSERT_TRUE(cfi_frame_info.get() ->FindCallerRegs(current_registers, memory, &caller_registers)); - VerifyRegisters(__FILE__, __LINE__, - expected_caller_registers, caller_registers); + ASSERT_TRUE(VerifyRegisters(__FILE__, __LINE__, + expected_caller_registers, caller_registers)); frame.instruction = 0x3d41; current_registers["$esp"] = 0x10014; @@ -281,8 +285,8 @@ static bool RunTests() { ASSERT_TRUE(cfi_frame_info.get() ->FindCallerRegs(current_registers, memory, &caller_registers)); - VerifyRegisters(__FILE__, __LINE__, - expected_caller_registers, caller_registers); + ASSERT_TRUE(VerifyRegisters(__FILE__, __LINE__, + expected_caller_registers, caller_registers)); frame.instruction = 0x3d43; current_registers["$ebp"] = 0x10014; @@ -334,8 +338,6 @@ static bool RunTests() { resolver.FillSourceLineInfo(&frame); ASSERT_EQ(frame.function_name, string("LargeFunction")); - TestCodeModule module2("module2"); - frame.instruction = 0x2181; frame.module = &module2; resolver.FillSourceLineInfo(&frame); @@ -364,27 +366,41 @@ static bool RunTests() { frame.module = &module2; resolver.FillSourceLineInfo(&frame); ASSERT_EQ(frame.function_name, "Public2_2"); +} - ASSERT_FALSE(resolver.LoadModule("module3", +TEST_F(TestBasicSourceLineResolver, TestInvalidLoads) +{ + TestCodeModule module3("module3"); + ASSERT_FALSE(resolver.LoadModule(&module3, testdata_dir + "/module3_bad.out")); - ASSERT_FALSE(resolver.HasModule("module3")); - ASSERT_FALSE(resolver.LoadModule("module4", + ASSERT_FALSE(resolver.HasModule(&module3)); + TestCodeModule module4("module4"); + ASSERT_FALSE(resolver.LoadModule(&module4, testdata_dir + "/module4_bad.out")); - ASSERT_FALSE(resolver.HasModule("module4")); - ASSERT_FALSE(resolver.LoadModule("module5", + ASSERT_FALSE(resolver.HasModule(&module4)); + TestCodeModule module5("module5"); + ASSERT_FALSE(resolver.LoadModule(&module5, testdata_dir + "/invalid-filename")); - ASSERT_FALSE(resolver.HasModule("module5")); - ASSERT_FALSE(resolver.HasModule("invalid-module")); - return true; + ASSERT_FALSE(resolver.HasModule(&module5)); + TestCodeModule invalidmodule("invalid-module"); + ASSERT_FALSE(resolver.HasModule(&invalidmodule)); } -} // namespace +TEST_F(TestBasicSourceLineResolver, TestUnload) +{ + TestCodeModule module1("module1"); + ASSERT_FALSE(resolver.HasModule(&module1)); + ASSERT_TRUE(resolver.LoadModule(&module1, testdata_dir + "/module1.out")); + ASSERT_TRUE(resolver.HasModule(&module1)); + resolver.UnloadModule(&module1); + ASSERT_FALSE(resolver.HasModule(&module1)); + ASSERT_TRUE(resolver.LoadModule(&module1, testdata_dir + "/module1.out")); + ASSERT_TRUE(resolver.HasModule(&module1)); +} -int main(int argc, char **argv) { - BPLOG_INIT(&argc, &argv); +} // namespace - if (!RunTests()) { - return 1; - } - return 0; +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/src/processor/binarystream.cc b/src/processor/binarystream.cc new file mode 100644 index 00000000..c8ee2186 --- /dev/null +++ b/src/processor/binarystream.cc @@ -0,0 +1,123 @@ +// 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. + +#include +#include + +#include + +#include "processor/binarystream.h" + +namespace google_breakpad { +using std::string; +using std::vector; + +binarystream &binarystream::operator>>(std::string &str) { + u_int16_t length; + *this >> length; + if (eof()) + return *this; + if (length == 0) { + str.clear(); + return *this; + } + vector buffer(length); + stream_.read(&buffer[0], length); + if (!eof()) + str.assign(&buffer[0], length); + return *this; +} + +binarystream &binarystream::operator>>(u_int8_t &u8) { + stream_.read((char *)&u8, 1); + return *this; +} + +binarystream &binarystream::operator>>(u_int16_t &u16) { + u_int16_t temp; + stream_.read((char *)&temp, 2); + if (!eof()) + u16 = ntohs(temp); + return *this; +} + +binarystream &binarystream::operator>>(u_int32_t &u32) { + u_int32_t temp; + stream_.read((char *)&temp, 4); + if (!eof()) + u32 = ntohl(temp); + return *this; +} + +binarystream &binarystream::operator>>(u_int64_t &u64) { + u_int32_t lower, upper; + *this >> lower >> upper; + if (!eof()) + u64 = static_cast(lower) | (static_cast(upper) << 32); + return *this; +} + +binarystream &binarystream::operator<<(const std::string &str) { + if (str.length() > USHRT_MAX) { + // truncate to 16-bit length + *this << static_cast(USHRT_MAX); + stream_.write(str.c_str(), USHRT_MAX); + } else { + *this << (u_int16_t)(str.length() & 0xFFFF); + stream_.write(str.c_str(), str.length()); + } + return *this; +} + +binarystream &binarystream::operator<<(u_int8_t u8) { + stream_.write((const char*)&u8, 1); + return *this; +} + +binarystream &binarystream::operator<<(u_int16_t u16) { + u16 = htons(u16); + stream_.write((const char*)&u16, 2); + return *this; +} + +binarystream &binarystream::operator<<(u_int32_t u32) { + u32 = htonl(u32); + stream_.write((const char*)&u32, 4); + return *this; +} + +binarystream &binarystream::operator<<(u_int64_t u64) { + // write 64-bit ints as two 32-bit ints, so we can byte-swap them easily + u_int32_t lower = static_cast(u64 & 0xFFFFFFFF); + u_int32_t upper = static_cast(u64 >> 32); + *this << lower << upper; + return *this; +} + +} // namespace google_breakpad diff --git a/src/processor/binarystream.h b/src/processor/binarystream.h new file mode 100644 index 00000000..8cb3af98 --- /dev/null +++ b/src/processor/binarystream.h @@ -0,0 +1,88 @@ +// 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. + +// binarystream implements part of the std::iostream interface as a +// wrapper around std::stringstream to allow reading and writing +// std::string and integers of known size. + +#ifndef GOOGLE_BREAKPAD_PROCESSOR_BINARYSTREAM_H_ +#define GOOGLE_BREAKPAD_PROCESSOR_BINARYSTREAM_H_ + +#include +#include + +#include "google_breakpad/common/breakpad_types.h" + +namespace google_breakpad { +using std::ios_base; +using std::ios; + +class binarystream { + public: + explicit binarystream(ios_base::openmode which = ios_base::out|ios_base::in) + : stream_(which) {} + explicit binarystream(const std::string &str, + ios_base::openmode which = ios_base::out|ios_base::in) + : stream_(str, which) {} + explicit binarystream(const char *str, size_t size, + ios_base::openmode which = ios_base::out|ios_base::in) + : stream_(std::string(str, size), which) {} + + binarystream &operator>>(std::string &str); + binarystream &operator>>(u_int8_t &u8); + binarystream &operator>>(u_int16_t &u16); + binarystream &operator>>(u_int32_t &u32); + binarystream &operator>>(u_int64_t &u64); + + // Note: strings are truncated at 65535 characters + binarystream &operator<<(const std::string &str); + binarystream &operator<<(u_int8_t u8); + binarystream &operator<<(u_int16_t u16); + binarystream &operator<<(u_int32_t u32); + binarystream &operator<<(u_int64_t u64); + + // Forward a few methods directly from the stream object + bool eof() const { return stream_.eof(); } + void clear() { stream_.clear(); } + std::string str() const { return stream_.str(); } + void str(const std::string &s) { stream_.str(s); } + + // Seek both read and write pointers to the beginning of the stream. + void rewind() { + stream_.seekg (0, ios::beg); + stream_.seekp (0, ios::beg); + } + + private: + std::stringstream stream_; +}; + +} // namespace google_breakpad + +#endif // GOOGLE_BREAKPAD_PROCESSOR_BINARYSTREAM_H_ diff --git a/src/processor/binarystream_unittest.cc b/src/processor/binarystream_unittest.cc new file mode 100644 index 00000000..2ed76e28 --- /dev/null +++ b/src/processor/binarystream_unittest.cc @@ -0,0 +1,432 @@ +// 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. + +#include +#include +#include + +#include "breakpad_googletest_includes.h" +#include "processor/binarystream.h" + +namespace { +using std::ios_base; +using std::string; +using std::vector; +using google_breakpad::binarystream; + + +class BinaryStreamBasicTest : public ::testing::Test { +protected: + binarystream stream; +}; + +TEST_F(BinaryStreamBasicTest, ReadU8) { + u_int8_t u8 = 0; + ASSERT_FALSE(stream.eof()); + stream >> u8; + ASSERT_TRUE(stream.eof()); + EXPECT_EQ(0, u8); + stream.rewind(); + stream.clear(); + stream << (u_int8_t)1; + ASSERT_FALSE(stream.eof()); + stream >> u8; + EXPECT_EQ(1, u8); + EXPECT_FALSE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, ReadU16) { + u_int16_t u16 = 0; + ASSERT_FALSE(stream.eof()); + stream >> u16; + ASSERT_TRUE(stream.eof()); + EXPECT_EQ(0, u16); + stream.rewind(); + stream.clear(); + stream << (u_int16_t)1; + ASSERT_FALSE(stream.eof()); + stream >> u16; + EXPECT_EQ(1, u16); + EXPECT_FALSE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, ReadU32) { + u_int32_t u32 = 0; + ASSERT_FALSE(stream.eof()); + stream >> u32; + ASSERT_TRUE(stream.eof()); + EXPECT_EQ(0, u32); + stream.rewind(); + stream.clear(); + stream << (u_int32_t)1; + ASSERT_FALSE(stream.eof()); + stream >> u32; + EXPECT_EQ(1, u32); + EXPECT_FALSE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, ReadU64) { + u_int64_t u64 = 0; + ASSERT_FALSE(stream.eof()); + stream >> u64; + ASSERT_TRUE(stream.eof()); + EXPECT_EQ(0, u64); + stream.rewind(); + stream.clear(); + stream << (u_int64_t)1; + ASSERT_FALSE(stream.eof()); + stream >> u64; + EXPECT_EQ(1, u64); + EXPECT_FALSE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, ReadString) { + string s(""); + ASSERT_FALSE(stream.eof()); + stream >> s; + ASSERT_TRUE(stream.eof()); + EXPECT_EQ("", s); + // write an empty string to the stream, read it back + s = "abcd"; + stream.rewind(); + stream.clear(); + stream << string(""); + stream >> s; + EXPECT_EQ("", s); + EXPECT_FALSE(stream.eof()); + stream.rewind(); + stream.clear(); + stream << string("test"); + ASSERT_FALSE(stream.eof()); + stream >> s; + EXPECT_EQ("test", s); + EXPECT_FALSE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, ReadEmptyString) { + string s("abc"); + stream << string(""); + stream >> s; + EXPECT_EQ("", s); +} + +TEST_F(BinaryStreamBasicTest, ReadMultiU8) { + const u_int8_t ea = 0, eb = 100, ec = 200, ed = 0xFF; + u_int8_t a, b, c, d, e; + stream << ea << eb << ec << ed; + stream >> a >> b >> c >> d; + ASSERT_FALSE(stream.eof()); + EXPECT_EQ(ea, a); + EXPECT_EQ(eb, b); + EXPECT_EQ(ec, c); + EXPECT_EQ(ed, d); + ASSERT_FALSE(stream.eof()); + e = 0; + stream >> e; + EXPECT_EQ(0, e); + ASSERT_TRUE(stream.eof()); + // try reading all at once, including one past eof + stream.rewind(); + stream.clear(); + ASSERT_FALSE(stream.eof()); + a = b = c = d = e = 0; + stream << ea << eb << ec << ed; + stream >> a >> b >> c >> d >> e; + EXPECT_EQ(ea, a); + EXPECT_EQ(eb, b); + EXPECT_EQ(ec, c); + EXPECT_EQ(ed, d); + EXPECT_EQ(0, e); + EXPECT_TRUE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, ReadMultiU16) { + const u_int16_t ea = 0, eb = 0x100, ec = 0x8000, ed = 0xFFFF; + u_int16_t a, b, c, d, e; + stream << ea << eb << ec << ed; + stream >> a >> b >> c >> d; + ASSERT_FALSE(stream.eof()); + EXPECT_EQ(ea, a); + EXPECT_EQ(eb, b); + EXPECT_EQ(ec, c); + EXPECT_EQ(ed, d); + ASSERT_FALSE(stream.eof()); + e = 0; + stream >> e; + EXPECT_EQ(0, e); + EXPECT_TRUE(stream.eof()); + // try reading all at once, including one past eof + stream.rewind(); + stream.clear(); + ASSERT_FALSE(stream.eof()); + a = b = c = d = e = 0; + stream << ea << eb << ec << ed; + stream >> a >> b >> c >> d >> e; + EXPECT_EQ(ea, a); + EXPECT_EQ(eb, b); + EXPECT_EQ(ec, c); + EXPECT_EQ(ed, d); + EXPECT_EQ(0, e); + EXPECT_TRUE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, ReadMultiU32) { + const u_int32_t ea = 0, eb = 0x10000, ec = 0x8000000, ed = 0xFFFFFFFF; + u_int32_t a, b, c, d, e; + stream << ea << eb << ec << ed; + stream >> a >> b >> c >> d; + ASSERT_FALSE(stream.eof()); + EXPECT_EQ(ea, a); + EXPECT_EQ(eb, b); + EXPECT_EQ(ec, c); + EXPECT_EQ(ed, d); + ASSERT_FALSE(stream.eof()); + e = 0; + stream >> e; + EXPECT_EQ(0, e); + EXPECT_TRUE(stream.eof()); + // try reading all at once, including one past eof + stream.rewind(); + stream.clear(); + ASSERT_FALSE(stream.eof()); + a = b = c = d = e = 0; + stream << ea << eb << ec << ed; + stream >> a >> b >> c >> d >> e; + EXPECT_EQ(ea, a); + EXPECT_EQ(eb, b); + EXPECT_EQ(ec, c); + EXPECT_EQ(ed, d); + EXPECT_EQ(0, e); + EXPECT_TRUE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, ReadMultiU64) { + const u_int64_t ea = 0, eb = 0x10000, ec = 0x100000000ULL, + ed = 0xFFFFFFFFFFFFFFFFULL; + u_int64_t a, b, c, d, e; + stream << ea << eb << ec << ed; + stream >> a >> b >> c >> d; + ASSERT_FALSE(stream.eof()); + EXPECT_EQ(ea, a); + EXPECT_EQ(eb, b); + EXPECT_EQ(ec, c); + EXPECT_EQ(ed, d); + ASSERT_FALSE(stream.eof()); + e = 0; + stream >> e; + EXPECT_EQ(0, e); + EXPECT_TRUE(stream.eof()); + // try reading all at once, including one past eof + stream.rewind(); + stream.clear(); + ASSERT_FALSE(stream.eof()); + a = b = c = d = e = 0; + stream << ea << eb << ec << ed; + stream >> a >> b >> c >> d >> e; + EXPECT_EQ(ea, a); + EXPECT_EQ(eb, b); + EXPECT_EQ(ec, c); + EXPECT_EQ(ed, d); + EXPECT_EQ(0, e); + EXPECT_TRUE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, ReadMixed) { + const u_int8_t e8 = 0x10; + const u_int16_t e16 = 0x2020; + const u_int32_t e32 = 0x30303030; + const u_int64_t e64 = 0x4040404040404040ULL; + const string es = "test"; + u_int8_t u8 = 0; + u_int16_t u16 = 0; + u_int32_t u32 = 0; + u_int64_t u64 = 0; + string s("test"); + stream << e8 << e16 << e32 << e64 << es; + stream >> u8 >> u16 >> u32 >> u64 >> s; + EXPECT_FALSE(stream.eof()); + EXPECT_EQ(e8, u8); + EXPECT_EQ(e16, u16); + EXPECT_EQ(e32, u32); + EXPECT_EQ(e64, u64); + EXPECT_EQ(es, s); +} + +TEST_F(BinaryStreamBasicTest, ReadStringMissing) { + // ensure that reading a string where only the length is present fails + u_int16_t u16 = 8; + stream << u16; + stream.rewind(); + string s(""); + stream >> s; + EXPECT_EQ("", s); + EXPECT_TRUE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, ReadStringTruncated) { + // ensure that reading a string where not all the data is present fails + u_int16_t u16 = 8; + stream << u16; + stream << (u_int8_t)'t' << (u_int8_t)'e' << (u_int8_t)'s' << (u_int8_t)'t'; + stream.rewind(); + string s(""); + stream >> s; + EXPECT_EQ("", s); + EXPECT_TRUE(stream.eof()); +} + +TEST_F(BinaryStreamBasicTest, StreamByteLength) { + // Test that the stream buffer contains the right amount of data + stream << (u_int8_t)0 << (u_int16_t)1 << (u_int32_t)2 << (u_int64_t)3 + << string("test"); + string s = stream.str(); + EXPECT_EQ(21, s.length()); +} + +TEST_F(BinaryStreamBasicTest, AppendStreamResultsByteLength) { + // Test that appending the str() results from two streams + // gives the right byte length + binarystream stream2; + stream << (u_int8_t)0 << (u_int16_t)1; + stream2 << (u_int32_t)0 << (u_int64_t)2 + << string("test"); + string s = stream.str(); + string s2 = stream2.str(); + s.append(s2); + EXPECT_EQ(21, s.length()); +} + +TEST_F(BinaryStreamBasicTest, StreamSetStr) { + const string es("test"); + stream << es; + binarystream stream2; + stream2.str(stream.str()); + string s; + stream2 >> s; + EXPECT_FALSE(stream2.eof()); + EXPECT_EQ("test", s); + s = ""; + stream2.str(stream.str()); + stream2.rewind(); + stream2 >> s; + EXPECT_FALSE(stream2.eof()); + EXPECT_EQ("test", s); +} + +class BinaryStreamU8Test : public ::testing::Test { +protected: + binarystream stream; + + void SetUp() { + stream << (u_int8_t)1; + } +}; + +TEST_F(BinaryStreamU8Test, ReadU16) { + u_int16_t u16 = 0; + ASSERT_FALSE(stream.eof()); + stream >> u16; + ASSERT_TRUE(stream.eof()); + EXPECT_EQ(0, u16); +} + +TEST_F(BinaryStreamU8Test, ReadU32) { + u_int32_t u32 = 0; + ASSERT_FALSE(stream.eof()); + stream >> u32; + ASSERT_TRUE(stream.eof()); + EXPECT_EQ(0, u32); +} + +TEST_F(BinaryStreamU8Test, ReadU64) { + u_int64_t u64 = 0; + ASSERT_FALSE(stream.eof()); + stream >> u64; + ASSERT_TRUE(stream.eof()); + EXPECT_EQ(0, u64); +} + +TEST_F(BinaryStreamU8Test, ReadString) { + string s(""); + ASSERT_FALSE(stream.eof()); + stream >> s; + ASSERT_TRUE(stream.eof()); + EXPECT_EQ("", s); +} + + +TEST(BinaryStreamTest, InitWithData) { + const char *data = "abcd"; + binarystream stream(data); + u_int8_t a, b, c, d; + stream >> a >> b >> c >> d; + ASSERT_FALSE(stream.eof()); + EXPECT_EQ('a', a); + EXPECT_EQ('b', b); + EXPECT_EQ('c', c); + EXPECT_EQ('d', d); +} + +TEST(BinaryStreamTest, InitWithDataLeadingNull) { + const char *data = "\0abcd"; + binarystream stream(data, 5); + u_int8_t z, a, b, c, d; + stream >> z >> a >> b >> c >> d; + ASSERT_FALSE(stream.eof()); + EXPECT_EQ(0, z); + EXPECT_EQ('a', a); + EXPECT_EQ('b', b); + EXPECT_EQ('c', c); + EXPECT_EQ('d', d); +} + +TEST(BinaryStreamTest, InitWithDataVector) { + vector data; + data.push_back('a'); + data.push_back('b'); + data.push_back('c'); + data.push_back('d'); + data.push_back('e'); + data.resize(4); + binarystream stream(&data[0], data.size()); + u_int8_t a, b, c, d; + stream >> a >> b >> c >> d; + ASSERT_FALSE(stream.eof()); + EXPECT_EQ('a', a); + EXPECT_EQ('b', b); + EXPECT_EQ('c', c); + EXPECT_EQ('d', d); +} + +} // namespace + +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/processor/cfi_frame_info.cc b/src/processor/cfi_frame_info.cc index 0cca6646..668bdeb8 100644 --- a/src/processor/cfi_frame_info.cc +++ b/src/processor/cfi_frame_info.cc @@ -33,6 +33,7 @@ // See cfi_frame_info.h for details. #include +#include #include "processor/cfi_frame_info.h" #include "processor/postfix_evaluator-inl.h" @@ -94,6 +95,28 @@ template bool CFIFrameInfo::FindCallerRegs( const MemoryRegion &memory, RegisterValueMap *caller_registers) const; +string CFIFrameInfo::Serialize() const { + std::ostringstream stream; + + if (!cfa_rule_.empty()) { + stream << ".cfa: " << cfa_rule_; + } + if (!ra_rule_.empty()) { + if (stream.tellp() != 0) + stream << " "; + stream << ".ra: " << ra_rule_; + } + for (RuleMap::const_iterator iter = register_rules_.begin(); + iter != register_rules_.end(); + ++iter) { + if (stream.tellp() != 0) + stream << " "; + stream << iter->first << ": " << iter->second; + } + + return stream.str(); +} + bool CFIRuleParser::Parse(const string &rule_set) { size_t rule_set_len = rule_set.size(); scoped_array working_copy(new char[rule_set_len + 1]); diff --git a/src/processor/cfi_frame_info.h b/src/processor/cfi_frame_info.h index f537296a..fe06fb4f 100644 --- a/src/processor/cfi_frame_info.h +++ b/src/processor/cfi_frame_info.h @@ -100,6 +100,10 @@ class CFIFrameInfo { const MemoryRegion &memory, RegisterValueMap *caller_registers) const; + // Serialize the rules in this object into a string in the format + // of STACK CFI records. + string Serialize() const; + private: // A map from register names onto evaluation rules. diff --git a/src/processor/cfi_frame_info_unittest.cc b/src/processor/cfi_frame_info_unittest.cc index 5f776fe8..979d4a3b 100644 --- a/src/processor/cfi_frame_info_unittest.cc +++ b/src/processor/cfi_frame_info_unittest.cc @@ -89,6 +89,7 @@ TEST_F(Simple, NoCFA) { cfi.SetRARule("0"); ASSERT_FALSE(cfi.FindCallerRegs(registers, memory, &caller_registers)); + ASSERT_EQ(".ra: 0", cfi.Serialize()); } // FindCallerRegs should fail if no .ra rule is provided. @@ -98,6 +99,7 @@ TEST_F(Simple, NoRA) { cfi.SetCFARule("0"); ASSERT_FALSE(cfi.FindCallerRegs(registers, memory, &caller_registers)); + ASSERT_EQ(".cfa: 0", cfi.Serialize()); } TEST_F(Simple, SetCFAAndRARule) { @@ -110,6 +112,9 @@ TEST_F(Simple, SetCFAAndRARule) { ASSERT_EQ(2U, caller_registers.size()); ASSERT_EQ(330903416631436410ULL, caller_registers[".cfa"]); ASSERT_EQ(5870666104170902211ULL, caller_registers[".ra"]); + + ASSERT_EQ(".cfa: 330903416631436410 .ra: 5870666104170902211", + cfi.Serialize()); } TEST_F(Simple, SetManyRules) { @@ -130,6 +135,13 @@ TEST_F(Simple, SetManyRules) { ASSERT_EQ(31740999U, caller_registers["vodkathumbscrewingly"]); ASSERT_EQ(-22136316ULL, caller_registers["pubvexingfjordschmaltzy"]); ASSERT_EQ(12U, caller_registers["uncopyrightables"]); + ASSERT_EQ(".cfa: $temp1 68737028 = $temp2 61072337 = $temp1 $temp2 - " + ".ra: .cfa 99804755 + " + "pubvexingfjordschmaltzy: .cfa 29801007 - " + "register1: .cfa 54370437 * " + "uncopyrightables: 92642917 .cfa / " + "vodkathumbscrewingly: 24076308 .cfa +", + cfi.Serialize()); } TEST_F(Simple, RulesOverride) { @@ -143,6 +155,8 @@ TEST_F(Simple, RulesOverride) { ASSERT_EQ(2U, caller_registers.size()); ASSERT_EQ(2828089117179001ULL, caller_registers[".cfa"]); ASSERT_EQ(5870666104170902211ULL, caller_registers[".ra"]); + ASSERT_EQ(".cfa: 2828089117179001 .ra: 5870666104170902211", + cfi.Serialize()); } class Scope: public CFIFixture, public Test { }; diff --git a/src/processor/network_interface.h b/src/processor/network_interface.h new file mode 100644 index 00000000..3871b4f5 --- /dev/null +++ b/src/processor/network_interface.h @@ -0,0 +1,62 @@ +// 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. + +// NetworkInterface is an abstract interface for network connections. +// Its purpose is to make the network portion of certain classes +// easier to mock for testing. A concrete implementation of this +// interface can be found in udp_network.h. + +#ifndef GOOGLE_BREAKPAD_PROCESSOR_NETWORK_INTERFACE_H_ +#define GOOGLE_BREAKPAD_PROCESSOR_NETWORK_INTERFACE_H_ +namespace google_breakpad { + +class NetworkInterface { + public: + // Prepare a network connection. + // If listen is true, prepare the socket to listen for incoming + // connections. + // Returns true for success, false for failure. + virtual bool Init(bool listen) = 0; + + // Send length bytes of data to the current address. + // Returns true for success, false for failure. + virtual bool Send(const char *data, size_t length) = 0; + + // Wait at most timeout milliseconds, returning when data is available or + // time has expired. + // Returns true if data is available, false if a timeout or error occurred. + virtual bool WaitToReceive(int timeout) = 0; + + // Read data into buffer. received will contain the number of bytes received. + // Returns true for success, false for failure. + virtual bool Receive(char *buffer, size_t buffer_size, ssize_t &received) = 0; +}; + +} // namespace google_breakpad +#endif // GOOGLE_BREAKPAD_PROCESSOR_NETWORK_INTERFACE_H_ diff --git a/src/processor/network_source_line_protocol.h b/src/processor/network_source_line_protocol.h new file mode 100644 index 00000000..b9744a8a --- /dev/null +++ b/src/processor/network_source_line_protocol.h @@ -0,0 +1,162 @@ +// 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. + +// This file contains constants used in the network source line server +// protocol. + +// Brief protocol description: +// +// Requests are sent via UDP. All requests begin with a sequence number +// that is prepended to the response. The sequence number is opaque +// to the server, it is provided for client tracking of requests. +// +// In this file, request and response fields will be described as: +// , which should be read as "foo, an N byte unsigned integer +// in network byte order". Strings will be described as . +// +// A client request looks like: +// +// Where is a sequence number as described above, +// is one of the commands listed below, and is arbitrary +// data, defined per-command. +// +// A server response looks like: +// +// Where is the same sequence number from the request, +// is one of the OK or ERROR values defined below, +// with OK signifying that the request was formatted properly and +// the response contains data, and ERROR signifying that the request +// was malformed in some way. is arbitrary data, defined +// per-command below. +// +// Strings are sent as a 2-byte integer encoding the length, followed +// by bytes. +// +// Valid Commands: +//================================================== +// +// example: <0x8>test.dll<0x8>test.pdb<0xA>0123456789 +// +// Query whether the module with this filename and debug information +// has been previously loaded. +// +// Server Response: +// +// Where is one of MODULE_LOADED or MODULE_NOT_LOADED +// +//================================================== +// +// example: <0x8>test.dll<0x8>test.pdb<0xA>0123456789 +// +// Request that the server find and load symbols for this module. +// +// Server Response: +// +// Where is one of: +// LOAD_NOT_FOUND +// - Symbols not found +// LOAD_INTERRUPT +// - Processing should be interrupted, symbols may be available later +// LOAD_FAIL +// - Found symbols, but failed to load them +// LOAD_OK +// - Found and loaded symbols successfully +// +//================================================== +// +// example: <0x8>test.dll<0x8>test.pdb<0x9>0123456789<0x0000000000010000><0x0000000000011A2B> +// +// Look up source line info for this module, loaded at this base address, +// for the code at this instruction address. +// +// Server Response: +// +// - As many fields as available are filled in. Fields that are not available +// will contain an empty string, or a zero for numeric values. + +//================================================== +// +// example: <0x8>test.dll<0x8>test.pdb<0x9>0123456789<0x0000000000010000><0x0000000000011A2B> +// +// Look up Windows stack frame info for this module, loaded at this base +// address, for the code at this instruction address. +// +// Server Response: +// +// The stack info is formatted as in the symbol file format, with +// "STACK " omitted, as documented at: +// http://code.google.com/p/google-breakpad/wiki/SymbolFiles +// If no Windows stack frame info is available, an empty string is returned. + +//================================================== +// +// example: <0x8>test.dll<0x8>test.pdb<0x9>0123456789<0x0000000000010000><0x0000000000011A2B> +// +// Look up CFI stack frame info for this module, loaded at this base +// address, for the code at this instruction address. +// +// Server Response: +// +// The stack info is formatted as in the symbol file format, with +// "STACK " omitted, as documented at: +// http://code.google.com/p/google-breakpad/wiki/SymbolFiles +// If no CFI stack frame info is available, an empty string is returned. + +#ifndef GOOGLE_BREAKPAD_PROCESSOR_NETWORK_SOURCE_LINE_PROTOCOL_H_ +#define GOOGLE_BREAKPAD_PROCESSOR_NETWORK_SOURCE_LINE_PROTOCOL_H_ + +#include "google_breakpad/common/breakpad_types.h" + +namespace google_breakpad { +namespace source_line_protocol { + +// Response status codes +const u_int8_t OK = 1; +const u_int8_t ERROR = 0; + +// Commands +const u_int8_t HAS = 1; +const u_int8_t LOAD = 2; +const u_int8_t GET = 3; +const u_int8_t GETSTACKWIN = 4; +const u_int8_t GETSTACKCFI = 5; + +// HAS responses +const u_int8_t MODULE_NOT_LOADED = 0; +const u_int8_t MODULE_LOADED = 1; + +// LOAD responses +const u_int8_t LOAD_NOT_FOUND = 0; +const u_int8_t LOAD_INTERRUPT = 1; +const u_int8_t LOAD_FAIL = 2; +const u_int8_t LOAD_OK = 3; + +} // namespace source_line_protocol +} // namespace google_breakpad +#endif // GOOGLE_BREAKPAD_PROCESSOR_NETWORK_SOURCE_LINE_PROTOCOL_H_ diff --git a/src/processor/network_source_line_resolver.cc b/src/processor/network_source_line_resolver.cc new file mode 100644 index 00000000..5fc5b1ea --- /dev/null +++ b/src/processor/network_source_line_resolver.cc @@ -0,0 +1,435 @@ +// 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. + +#include +#include + +#include +#include +#include + +#include "google_breakpad/processor/network_source_line_resolver.h" +#include "google_breakpad/processor/stack_frame.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/logging.h" +#include "processor/scoped_ptr.h" +#include "processor/udp_network.h" +#include "processor/windows_frame_info.h" + +namespace google_breakpad { + +using std::string; +using std::vector; +using std::dec; +using std::hex; +// Style guide forbids "using namespace", so at least shorten it. +namespace P = source_line_protocol; + +NetworkSourceLineResolver::NetworkSourceLineResolver(const string &server, + unsigned short port, + int wait_milliseconds) + : wait_milliseconds_(wait_milliseconds), + initialized_(false), + sequence_(0), + net_(new UDPNetwork(server, port)) { + if (net_->Init(false)) + initialized_ = true; +} + +NetworkSourceLineResolver::NetworkSourceLineResolver(NetworkInterface *net, + int wait_milliseconds) + : wait_milliseconds_(wait_milliseconds), + initialized_(false), + sequence_(0), + net_(net) { + if (net_ && net->Init(false)) + initialized_ = true; +} + +NetworkSourceLineResolver::~NetworkSourceLineResolver() { + initialized_ = false; +} + +bool NetworkSourceLineResolver::LoadModule(const CodeModule *module, + const string &map_file) { + // Just lie here and say it was loaded. The server always loads + // symbols immediately when they're found, since clients always + // will want to load them after finding them anyway. Since this class + // acts as both the symbol supplier and source line resolver, + // it's just a little optimization. + return true; +} + +bool NetworkSourceLineResolver::LoadModuleUsingMapBuffer( + const CodeModule *module, + const string &map_buffer) { + // see above + return true; +} + +void NetworkSourceLineResolver::UnloadModule(const CodeModule *module) { + // no-op +} + +bool NetworkSourceLineResolver::HasModule(const CodeModule *module) { + if (!initialized_ || !module) + return false; + + // cache seen modules so the network round trip can be skipped + if (module_cache_.find(module->code_file()) != module_cache_.end()) + return true; + + // also cache modules for which symbols aren't found + if (no_symbols_cache_.find(module->debug_file() + module->debug_identifier()) + != no_symbols_cache_.end()) + return false; + + binarystream message; + message << P::HAS + << module->code_file() + << module->debug_file() + << module->debug_identifier(); + binarystream response; + bool got_response = SendMessageGetResponse(message, response); + u_int8_t response_data; + response >> response_data; + + bool found = false; + if (got_response && !response.eof() && response_data == P::MODULE_LOADED) { + module_cache_.insert(module->code_file()); + found = true; + } + return found; +} + +void NetworkSourceLineResolver::FillSourceLineInfo( + StackFrame *frame) { + if (!initialized_) + return; + + // if don't this module isn't loaded, can't fill source line info + if (!frame->module || + module_cache_.find(frame->module->code_file()) == module_cache_.end()) + return; + + // if this frame has already been seen, return the cached copy + if (FindCachedSourceLineInfo(frame)) { + BPLOG(INFO) << "Using cached source line info"; + return; + } + + binarystream message; + message << P::GET + << frame->module->code_file() + << frame->module->debug_file() + << frame->module->debug_identifier() + << frame->module->base_address() + << frame->instruction; + binarystream response; + bool got_response = SendMessageGetResponse(message, response); + if (!got_response) + return; + + string function_name, source_file; + u_int32_t source_line; + u_int64_t function_base, source_line_base; + response >> function_name >> function_base + >> source_file >> source_line >> source_line_base; + + if (response.eof()) { + BPLOG(ERROR) << "GET response malformed"; + return; + } else { + BPLOG(INFO) << "GET response: " << function_name << " " + << hex << function_base << " " << source_file << " " + << dec << source_line << " " << hex + << source_line_base; + } + + frame->function_name = function_name; + frame->function_base = function_base; + frame->source_file_name = source_file; + frame->source_line = source_line; + frame->source_line_base = source_line_base; + + CacheSourceLineInfo(frame); +} + +WindowsFrameInfo* +NetworkSourceLineResolver::FindWindowsFrameInfo(const StackFrame *frame) { + if (!initialized_) + return NULL; + + // if this module isn't loaded, can't get frame info + if (!frame->module || + module_cache_.find(frame->module->code_file()) == module_cache_.end()) + return NULL; + + // check the cache first + string stack_info; + + if (FindCachedFrameInfo(frame, kWindowsFrameInfo, &stack_info)) { + BPLOG(INFO) << "Using cached windows frame info"; + } else { + binarystream message; + message << P::GETSTACKWIN + << frame->module->code_file() + << frame->module->debug_file() + << frame->module->debug_identifier() + << frame->module->base_address() + << frame->instruction; + binarystream response; + if (SendMessageGetResponse(message, response)) { + response >> stack_info; + CacheFrameInfo(frame, kWindowsFrameInfo, stack_info); + } + } + + WindowsFrameInfo *info = NULL; + if (!stack_info.empty()) { + int type; + u_int64_t rva, code_size; + info = WindowsFrameInfo::ParseFromString(stack_info, + type, + rva, + code_size); + } + + return info; +} + +CFIFrameInfo* +NetworkSourceLineResolver::FindCFIFrameInfo(const StackFrame *frame) +{ + if (!initialized_) + return NULL; + + // if this module isn't loaded, can't get frame info + if (!frame->module || + module_cache_.find(frame->module->code_file()) == module_cache_.end()) + return NULL; + + string stack_info; + + if (FindCachedFrameInfo(frame, kCFIFrameInfo, &stack_info)) { + BPLOG(INFO) << "Using cached CFI frame info"; + } else { + binarystream message; + message << P::GETSTACKCFI + << frame->module->code_file() + << frame->module->debug_file() + << frame->module->debug_identifier() + << frame->module->base_address() + << frame->instruction; + binarystream response; + if (SendMessageGetResponse(message, response)) { + response >> stack_info; + CacheFrameInfo(frame, kCFIFrameInfo, stack_info); + } + } + + if (!stack_info.empty()) { + scoped_ptr info(new CFIFrameInfo()); + CFIFrameInfoParseHandler handler(info.get()); + CFIRuleParser parser(&handler); + if (parser.Parse(stack_info)) + return info.release(); + } + + return NULL; +} + +SymbolSupplier::SymbolResult +NetworkSourceLineResolver::GetSymbolFile(const CodeModule *module, + const SystemInfo *system_info, + string *symbol_file) { + BPLOG_IF(ERROR, !symbol_file) << "NetworkSourceLineResolver::GetSymbolFile " + "requires |symbol_file|"; + assert(symbol_file); + + if (!initialized_) + return NOT_FOUND; + + if (no_symbols_cache_.find(module->debug_file() + module->debug_identifier()) + != no_symbols_cache_.end()) + return NOT_FOUND; + + binarystream message; + message << P::LOAD + << module->code_file() + << module->debug_file() + << module->debug_identifier(); + binarystream response; + bool got_response = SendMessageGetResponse(message, response); + if (!got_response) { + // Didn't get a response, which is the same as not having symbols. + // Don't cache this, though, to force a retry if the client asks for + // symbols for the same file again. + return NOT_FOUND; + } + u_int8_t response_data; + response >> response_data; + + if (response.eof()) { + BPLOG(ERROR) << "Malformed LOAD response"; + return NOT_FOUND; + } + + if (response_data == P::LOAD_NOT_FOUND || response_data == P::LOAD_FAIL) { + // Received NOT or FAIL, symbols not present or failed to load them. + // Same problem to the client any way you look at it. + // Cache this module to avoid pointless retry. + no_symbols_cache_.insert(module->debug_file() + module->debug_identifier()); + return NOT_FOUND; + } else if (response_data == P::LOAD_INTERRUPT) { + return INTERRUPT; + } + + // otherwise, OK + module_cache_.insert(module->code_file()); + *symbol_file = ""; + return FOUND; +} + +SymbolSupplier::SymbolResult +NetworkSourceLineResolver::GetSymbolFile(const CodeModule *module, + const SystemInfo *system_info, + string *symbol_file, + string *symbol_data) { + if(symbol_data) + symbol_data->clear(); + return GetSymbolFile(module, system_info, symbol_file); +} + +bool NetworkSourceLineResolver::SendMessageGetResponse( + const binarystream &message, + binarystream &response) { + binarystream sequence_stream; + u_int16_t sent_sequence = sequence_; + sequence_stream << sequence_; + ++sequence_; + string message_string = sequence_stream.str(); + message_string.append(message.str()); + BPLOG(INFO) << "Sending " << message_string.length() << " bytes"; + if (!net_->Send(message_string.c_str(), message_string.length())) + return false; + + bool done = false; + while (!done) { + if (!net_->WaitToReceive(wait_milliseconds_)) + return false; + + vector buffer(1024); + ssize_t received_bytes; + if (!net_->Receive(&buffer[0], buffer.size(), received_bytes)) + return false; + + BPLOG(INFO) << "received " << received_bytes << " bytes"; + buffer.resize(received_bytes); + + response.str(string(&buffer[0], buffer.size())); + response.rewind(); + u_int16_t read_sequence; + u_int8_t status; + response >> read_sequence >> status; + if (response.eof()) { + BPLOG(ERROR) << "malformed response, missing sequence number or status"; + return false; + } + if (read_sequence < sent_sequence) // old packet + continue; + + if (read_sequence != sent_sequence) { + // not expecting this packet, just error + BPLOG(ERROR) << "error, got sequence number " << read_sequence + << ", expected " << sent_sequence; + return false; + } + + // This is the expected packet, so even if it's an error this loop is done + done = true; + + if (status != P::OK) { + BPLOG(ERROR) << "received an ER response packet"; + return false; + } + // the caller will process the rest of response + } + return true; +} + +bool NetworkSourceLineResolver::FindCachedSourceLineInfo(StackFrame *frame) + const +{ + SourceCache::const_iterator iter = + source_line_info_cache_.find(frame->instruction); + if (iter == source_line_info_cache_.end()) + return false; + + const StackFrame &f = iter->second; + frame->function_name = f.function_name; + frame->function_base = f.function_base; + frame->source_file_name = f.source_file_name; + frame->source_line = f.source_line; + frame->source_line_base = f.source_line_base; + return true; +} + +bool NetworkSourceLineResolver::FindCachedFrameInfo( + const StackFrame *frame, + FrameInfoType type, + string *info) const +{ + FrameInfoCache::const_iterator iter = + frame_info_cache_[type].find(frame->instruction); + if (iter == frame_info_cache_[type].end()) + return false; + + *info = iter->second; + return true; +} + +void NetworkSourceLineResolver::CacheSourceLineInfo(const StackFrame *frame) { + StackFrame f(*frame); + // can't hang onto this pointer, the caller owns it + f.module = NULL; + source_line_info_cache_[frame->instruction] = f; +} + +void NetworkSourceLineResolver::CacheFrameInfo( + const StackFrame *frame, + FrameInfoType type, + const string &info) { + frame_info_cache_[type][frame->instruction] = info; +} + +} // namespace google_breakpad diff --git a/src/processor/network_source_line_resolver_server_unittest.cc b/src/processor/network_source_line_resolver_server_unittest.cc new file mode 100644 index 00000000..916cbd06 --- /dev/null +++ b/src/processor/network_source_line_resolver_server_unittest.cc @@ -0,0 +1,207 @@ +// 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. + +// Full system test for NetworkSourceLineResolver / NetworkSourceLineServer. +// Forks a background process to run a NetworkSourceLineServer, then +// instantiates a MinidumpProcessor with a NetworkSourceLineResolver to +// connect to the background server and process a minidump. + +#include + +#include +#include +#include + +#include "breakpad_googletest_includes.h" +#include "google_breakpad/processor/basic_source_line_resolver.h" +#include "google_breakpad/processor/call_stack.h" +#include "google_breakpad/processor/minidump_processor.h" +#include "google_breakpad/processor/process_state.h" +#include "google_breakpad/processor/network_source_line_resolver.h" +#include "processor/simple_symbol_supplier.h" +#include "processor/network_source_line_server.h" +#include "processor/simple_symbol_supplier.h" +#include "processor/udp_network.h" + +namespace { + +using std::string; +using google_breakpad::BasicSourceLineResolver; +using google_breakpad::CallStack; +using google_breakpad::MinidumpProcessor; +using google_breakpad::NetworkSourceLineResolver; +using google_breakpad::NetworkSourceLineServer; +using google_breakpad::ProcessState; +using google_breakpad::SimpleSymbolSupplier; +using google_breakpad::UDPNetwork; + +static const char *kSystemInfoOS = "Windows NT"; +static const char *kSystemInfoOSShort = "windows"; +static const char *kSystemInfoOSVersion = "5.1.2600 Service Pack 2"; +static const char *kSystemInfoCPU = "x86"; +static const char *kSystemInfoCPUInfo = + "GenuineIntel family 6 model 13 stepping 8"; + +bool exitProcess = false; + +void signal_handler(int signal) { + if (signal == SIGINT) + exitProcess = true; +} + +void RunSourceLineServer(int fd) { + // Set a signal handler so the parent process + // can signal this process to end. + signal(SIGINT, signal_handler); + + BasicSourceLineResolver resolver; + SimpleSymbolSupplier supplier(string(getenv("srcdir") ? + getenv("srcdir") : ".") + + "/src/processor/testdata/symbols/"); + UDPNetwork net("localhost", + 0, // pick a free port + true); // IPv4 only + + NetworkSourceLineServer server(&supplier, &resolver, &net, + 0); // no source line limit + unsigned short port = -1; + bool initialized = server.Initialize(); + if (initialized) + port = net.port(); + + // send port number back to parent + ssize_t written = write(fd, &port, sizeof(port)); + close(fd); + + if (!initialized || written != sizeof(port)) + return; + + while (!exitProcess) { + server.RunOnce(100); + } +} + +TEST(NetworkSourceLineResolverServer, SystemTest) { + int fds[2]; + ASSERT_EQ(0, pipe(fds)); + // Fork a background process to run the server. + pid_t pid = fork(); + if (pid == 0) { + close(fds[0]); + RunSourceLineServer(fds[1]); + exit(0); + } + ASSERT_NE(-1, pid); + // Wait for the background process to return info about the port. + close(fds[1]); + unsigned short port; + ssize_t nbytes = read(fds[0], &port, sizeof(port)); + ASSERT_EQ(sizeof(port), nbytes); + ASSERT_NE(-1, port); + + NetworkSourceLineResolver resolver("localhost", port, + 5000); // wait at most 5 seconds for reply + MinidumpProcessor processor(&resolver, &resolver); + // this is all copied from minidump_processor_unittest.cc + string minidump_file = string(getenv("srcdir") ? getenv("srcdir") : ".") + + "/src/processor/testdata/minidump2.dmp"; + + ProcessState state; + ASSERT_EQ(processor.Process(minidump_file, &state), + google_breakpad::PROCESS_OK); + ASSERT_EQ(state.system_info()->os, kSystemInfoOS); + ASSERT_EQ(state.system_info()->os_short, kSystemInfoOSShort); + ASSERT_EQ(state.system_info()->os_version, kSystemInfoOSVersion); + ASSERT_EQ(state.system_info()->cpu, kSystemInfoCPU); + ASSERT_EQ(state.system_info()->cpu_info, kSystemInfoCPUInfo); + ASSERT_TRUE(state.crashed()); + ASSERT_EQ(state.crash_reason(), "EXCEPTION_ACCESS_VIOLATION"); + ASSERT_EQ(state.crash_address(), 0x45U); + ASSERT_EQ(state.threads()->size(), size_t(1)); + ASSERT_EQ(state.requesting_thread(), 0); + + CallStack *stack = state.threads()->at(0); + ASSERT_TRUE(stack); + ASSERT_EQ(stack->frames()->size(), 4U); + + ASSERT_TRUE(stack->frames()->at(0)->module); + ASSERT_EQ(stack->frames()->at(0)->module->base_address(), 0x400000U); + ASSERT_EQ(stack->frames()->at(0)->module->code_file(), "c:\\test_app.exe"); + ASSERT_EQ(stack->frames()->at(0)->function_name, + "`anonymous namespace'::CrashFunction"); + ASSERT_EQ(stack->frames()->at(0)->source_file_name, "c:\\test_app.cc"); + ASSERT_EQ(stack->frames()->at(0)->source_line, 58); + + ASSERT_TRUE(stack->frames()->at(1)->module); + ASSERT_EQ(stack->frames()->at(1)->module->base_address(), 0x400000U); + ASSERT_EQ(stack->frames()->at(1)->module->code_file(), "c:\\test_app.exe"); + ASSERT_EQ(stack->frames()->at(1)->function_name, "main"); + ASSERT_EQ(stack->frames()->at(1)->source_file_name, "c:\\test_app.cc"); + ASSERT_EQ(stack->frames()->at(1)->source_line, 65); + + // This comes from the CRT + ASSERT_TRUE(stack->frames()->at(2)->module); + ASSERT_EQ(stack->frames()->at(2)->module->base_address(), 0x400000U); + ASSERT_EQ(stack->frames()->at(2)->module->code_file(), "c:\\test_app.exe"); + ASSERT_EQ(stack->frames()->at(2)->function_name, "__tmainCRTStartup"); + ASSERT_EQ(stack->frames()->at(2)->source_file_name, + "f:\\sp\\vctools\\crt_bld\\self_x86\\crt\\src\\crt0.c"); + ASSERT_EQ(stack->frames()->at(2)->source_line, 327); + + // OS frame, kernel32.dll + ASSERT_TRUE(stack->frames()->at(3)->module); + ASSERT_EQ(stack->frames()->at(3)->module->base_address(), 0x7c800000U); + ASSERT_EQ(stack->frames()->at(3)->module->code_file(), + "C:\\WINDOWS\\system32\\kernel32.dll"); + ASSERT_EQ(stack->frames()->at(3)->function_name, "BaseProcessStart"); + ASSERT_TRUE(stack->frames()->at(3)->source_file_name.empty()); + ASSERT_EQ(stack->frames()->at(3)->source_line, 0); + + ASSERT_EQ(state.modules()->module_count(), 13U); + ASSERT_TRUE(state.modules()->GetMainModule()); + ASSERT_EQ(state.modules()->GetMainModule()->code_file(), "c:\\test_app.exe"); + ASSERT_FALSE(state.modules()->GetModuleForAddress(0)); + ASSERT_EQ(state.modules()->GetMainModule(), + state.modules()->GetModuleForAddress(0x400000)); + ASSERT_EQ(state.modules()->GetModuleForAddress(0x7c801234)->debug_file(), + "kernel32.pdb"); + ASSERT_EQ(state.modules()->GetModuleForAddress(0x77d43210)->version(), + "5.1.2600.2622"); + + // Kill background process + kill(pid, SIGINT); + ASSERT_EQ(pid, waitpid(pid, NULL, 0)); +} + +} + +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} 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(); +} diff --git a/src/processor/network_source_line_server.cc b/src/processor/network_source_line_server.cc new file mode 100644 index 00000000..1a6ea31b --- /dev/null +++ b/src/processor/network_source_line_server.cc @@ -0,0 +1,435 @@ +// 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. + +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "google_breakpad/processor/code_module.h" +#include "google_breakpad/processor/stack_frame.h" +#include "processor/basic_code_module.h" +#include "processor/binarystream.h" +#include "processor/cfi_frame_info.h" +#include "processor/logging.h" +#include "processor/network_source_line_protocol.h" +#include "processor/network_source_line_server.h" +#include "processor/tokenize.h" +#include "processor/windows_frame_info.h" + +namespace google_breakpad { + +using std::dec; +using std::find; +using std::hex; +// Style guide forbids "using namespace", so at least shorten it. +namespace P = source_line_protocol; + +bool NetworkSourceLineServer::Initialize() { + if (net_->Init(true)) + initialized_ = true; + return initialized_; +} + +bool NetworkSourceLineServer::RunForever() { + if (!initialized_ && !Initialize()) + return false; + + BPLOG(INFO) << "Running forever..."; + while (true) { + RunOnce(5000); + } + // not reached + return true; +} + +bool NetworkSourceLineServer::RunOnce(int wait_milliseconds) { + if (!initialized_ && !Initialize()) + return false; + + if (!net_->WaitToReceive(wait_milliseconds)) + return false; + //TODO(ted): loop, processing packets until wait_milliseconds + // is actually exhausted? + + vector buffer(1024); + ssize_t received_bytes; + if (!net_->Receive(&buffer[0], buffer.size(), received_bytes)) + return false; + buffer.resize(received_bytes); + + binarystream request(&buffer[0], buffer.size()); + binarystream response; + if (!HandleRequest(request, response)) + return false; + + string response_string = response.str(); + if (!net_->Send(response_string.c_str(), response_string.length())) + return false; + return true; +} + +bool NetworkSourceLineServer::HandleRequest(binarystream &request, + binarystream &response) { + u_int16_t sequence_number; + u_int8_t command; + request >> sequence_number >> command; + if (request.eof()) { + BPLOG(ERROR) << "Malformed request, missing sequence number or command"; + return false; + } + + response.rewind(); + response << sequence_number; + switch(command) { + case P::HAS: + HandleHas(request, response); + break; + case P::LOAD: + HandleLoad(request, response); + break; + case P::GET: + HandleGet(request, response); + break; + case P::GETSTACKWIN: + HandleGetStackWin(request, response); + break; + case P::GETSTACKCFI: + HandleGetStackCFI(request, response); + break; + default: + BPLOG(ERROR) << "Unknown command " << int(command); + response << P::ERROR; + break; + } + return true; +} + +void NetworkSourceLineServer::HandleHas(binarystream &message, + binarystream &response) { + string module_name, debug_file, debug_id; + message >> module_name >> debug_file >> debug_id; + if (message.eof()) { + BPLOG(ERROR) << "HAS message malformed"; + response << P::ERROR; + return; + } + BPLOG(INFO) << "Received message HAS " << module_name + << " " << debug_file + << " " << debug_id; + // Need to lie about the module name here, since BasicSourceLineResolver + // uses only the module name for unique modules, but we want to allow + // multiple versions of the same named module in here. + BasicCodeModule module((u_int64_t)0, (u_int64_t)0, + module_name + "|" + debug_file + "|" + debug_id, "", + debug_file, debug_id, ""); + u_int8_t data; + if (resolver_) { + data = resolver_->HasModule(&module) + ? P::MODULE_LOADED : P::MODULE_NOT_LOADED; + } else { + data = P::MODULE_NOT_LOADED; + } + response << P::OK << data; +} + +void NetworkSourceLineServer::HandleLoad(binarystream &message, + binarystream &response) { + string module_name, debug_file, debug_id; + message >> module_name >> debug_file >> debug_id; + if (message.eof()) { + BPLOG(ERROR) << "LOAD message malformed"; + response << P::ERROR; + return; + } + BPLOG(INFO) << "Received message LOAD " << module_name + << " " << debug_file + << " " << debug_id; + + u_int8_t reply; + // stub out the bare minimum here + BasicCodeModule module((u_int64_t)0, (u_int64_t)0, + module_name + "|" + debug_file + "|" + debug_id, "", + debug_file, debug_id, ""); + if (resolver_->HasModule(&module)) { + // just short-circuit the rest of this, since it's already loaded + BPLOG(INFO) << "Got LOAD for already loaded " << module_name; + UsedModule(module); + reply = P::LOAD_OK; + } else { + BPLOG(INFO) << "Looking up symbols for (" << module_name << ", " + << debug_file << ", " << debug_id << ")"; + string symbol_data, symbol_file; + SymbolSupplier::SymbolResult symbol_result; + if (supplier_) { + symbol_result = supplier_->GetSymbolFile(&module, NULL, + &symbol_file, &symbol_data); + } else { + symbol_result = SymbolSupplier::NOT_FOUND; + } + + switch (symbol_result) { + case SymbolSupplier::FOUND: { + BPLOG(INFO) << "Found symbols for " << module_name; + reply = P::LOAD_OK; + // also go ahead and load the symbols while we're here, + // since the client is just going to ask us to do this right away + // and we already have |symbol_data| here. + int numlines = CountNewlines(symbol_data); + if (!resolver_->LoadModuleUsingMapBuffer(&module, + symbol_data)) { + BPLOG(INFO) << "Failed to load symbols for " << module_name; + reply = P::LOAD_FAIL; + } else { + // save some info about this module + symbol_lines_ += numlines; + UsedModule(module); + module_symbol_lines_[module.code_file()] = numlines; + + BPLOG(INFO) << "Loaded symbols for " << module_name + << " (" << dec << numlines << " lines, " + << symbol_lines_ << " total)"; + + if (max_symbol_lines_ != 0 && symbol_lines_ > max_symbol_lines_) { + // try unloading some modules to reclaim memory + // (but not the one that was just loaded) + BPLOG(INFO) << "Exceeded limit of " << dec << max_symbol_lines_ + << " symbol lines loaded, trying to unload modules"; + TryUnloadModules(module); + } + } + } + break; + case SymbolSupplier::NOT_FOUND: + BPLOG(INFO) << "Symbols not found for " << module_name; + reply = P::LOAD_NOT_FOUND; + break; + case SymbolSupplier::INTERRUPT: + BPLOG(INFO) << "Symbol provider returned interrupt for " << module_name; + reply = P::LOAD_INTERRUPT; + break; + } + } + response << P::OK << reply; +} + +void NetworkSourceLineServer::HandleGet(binarystream &message, + binarystream &response) { + string module_name, debug_file, debug_id; + u_int64_t module_base, instruction; + message >> module_name >> debug_file >> debug_id + >> module_base >> instruction; + if (message.eof()) { + BPLOG(ERROR) << "GET message malformed"; + response << P::ERROR; + return; + } + + BPLOG(INFO) << "Received message GET " << module_name << " " + << debug_file << " " << debug_id << " " + << hex << module_base << " " << instruction; + + StackFrame frame; + if (resolver_) { + BasicCodeModule module(module_base, (u_int64_t)0, + module_name + "|" + debug_file + "|" + debug_id, "", + debug_file, debug_id, ""); + frame.module = &module; + frame.instruction = instruction; + resolver_->FillSourceLineInfo(&frame); + UsedModule(module); + } + + response << P::OK << frame.function_name << frame.function_base + << frame.source_file_name << u_int32_t(frame.source_line) + << frame.source_line_base; + BPLOG(INFO) << "Sending GET response: " << frame.function_name << " " + << hex << frame.function_base << " " + << frame.source_file_name << " " + << dec << frame.source_line << " " + << hex << frame.source_line_base; +} + +void NetworkSourceLineServer::HandleGetStackWin(binarystream &message, + binarystream &response) { + string module_name, debug_file, debug_id; + u_int64_t module_base, instruction; + message >> module_name >> debug_file >> debug_id + >> module_base >> instruction; + if (message.eof()) { + BPLOG(ERROR) << "GETSTACKWIN message malformed"; + response << P::ERROR; + return; + } + + BPLOG(INFO) << "Received message GETSTACKWIN " << module_name << " " + << debug_file << " " << debug_id << " " + << hex << module_base << " " << instruction; + + + WindowsFrameInfo *frame_info = NULL; + if (resolver_) { + StackFrame frame; + BasicCodeModule module(module_base, (u_int64_t)0, + module_name + "|" + debug_file + "|" + debug_id, "", + debug_file, debug_id, ""); + frame.module = &module; + frame.instruction = instruction; + frame_info = resolver_->FindWindowsFrameInfo(&frame); + UsedModule(module); + } + + response << P::OK << FormatWindowsFrameInfo(frame_info); + BPLOG(INFO) << "Sending GETSTACKWIN response: " + << FormatWindowsFrameInfo(frame_info); + delete frame_info; +} + +string NetworkSourceLineServer::FormatWindowsFrameInfo( + WindowsFrameInfo *frame_info) { + if (frame_info == NULL) + return ""; + + std::ostringstream stream; + // Put "0" as the type, rva and code size because the client doesn't + // actually care what these values are, but it's easier to keep the + // format consistent with the symbol files so the parsing code can be + // shared. + stream << "0 0 0 " << hex + << frame_info->prolog_size << " " + << frame_info->epilog_size << " " + << frame_info->parameter_size << " " + << frame_info->saved_register_size << " " + << frame_info->local_size << " " + << frame_info->max_stack_size << " "; + if (!frame_info->program_string.empty()) { + stream << 1 << " " << frame_info->program_string; + } else { + stream << 0 << " " << frame_info->allocates_base_pointer; + } + return stream.str(); +} + +void NetworkSourceLineServer::HandleGetStackCFI(binarystream &message, + binarystream &response) { + string module_name, debug_file, debug_id; + u_int64_t module_base, instruction; + message >> module_name >> debug_file >> debug_id + >> module_base >> instruction; + if (message.eof()) { + BPLOG(ERROR) << "GETSTACKCFI message malformed"; + response << P::ERROR; + return; + } + + BPLOG(INFO) << "Received message GETSTACKCFI " << module_name << " " + << debug_file << " " << debug_id << " " + << hex << module_base << " " << instruction; + + + CFIFrameInfo *frame_info = NULL; + if (resolver_) { + StackFrame frame; + BasicCodeModule module(module_base, (u_int64_t)0, + module_name + "|" + debug_file + "|" + debug_id, "", + debug_file, debug_id, ""); + frame.module = &module; + frame.instruction = instruction; + frame_info = resolver_->FindCFIFrameInfo(&frame); + UsedModule(module); + } + + string frame_info_string; + if (frame_info != NULL) + frame_info_string = frame_info->Serialize(); + response << P::OK << frame_info_string; + BPLOG(INFO) << "Sending GETSTACKCFI response: " + << frame_info_string; + delete frame_info; +} + +int NetworkSourceLineServer::CountNewlines(const string &str) { + int count = 0; + string::const_iterator iter = str.begin(); + while (iter != str.end()) { + if (*iter == '\n') + count++; + iter++; + } + return count; +} + +void NetworkSourceLineServer::UsedModule(const CodeModule &module) { + list::iterator iter = find(modules_used_.begin(), + modules_used_.end(), + module.code_file()); + if (iter == modules_used_.end()) { + modules_used_.push_front(module.code_file()); + } else { + modules_used_.splice(modules_used_.begin(), + modules_used_, + iter); + } +} + +void NetworkSourceLineServer::TryUnloadModules( + const CodeModule &just_loaded_module) { + if (!resolver_) + return; + + while (symbol_lines_ > max_symbol_lines_) { + // never unload just_loaded_module + if (modules_used_.back() == just_loaded_module.code_file()) + break; + + string module_to_unload = modules_used_.back(); + modules_used_.pop_back(); + BasicCodeModule module(0, 0, module_to_unload, "", "", "", ""); + BPLOG(INFO) << "Unloading module " << module_to_unload; + resolver_->UnloadModule(&module); + + // reduce the symbol line count + map::iterator iter = + module_symbol_lines_.find(module_to_unload); + if (iter != module_symbol_lines_.end()) { + symbol_lines_ -= iter->second; + module_symbol_lines_.erase(iter); + } + } +} + +} // namespace google_breakpad diff --git a/src/processor/network_source_line_server.h b/src/processor/network_source_line_server.h new file mode 100644 index 00000000..6ea4bba8 --- /dev/null +++ b/src/processor/network_source_line_server.h @@ -0,0 +1,136 @@ +// 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. + +// NetworkSourceLineServer implements a UDP-based network protocol +// to allow clients to query for source line information. +// +// A brief protocol description can be found in network_source_line_protocol.h + +#ifndef GOOGLE_BREAKPAD_PROCESSOR_NETWORK_SOURCE_LINE_SERVER_H_ +#define GOOGLE_BREAKPAD_PROCESSOR_NETWORK_SOURCE_LINE_SERVER_H_ + +#include +#include +#include + +#include "google_breakpad/common/breakpad_types.h" +#include "google_breakpad/processor/basic_source_line_resolver.h" +#include "google_breakpad/processor/symbol_supplier.h" +#include "processor/binarystream.h" +#include "processor/network_interface.h" +#include "processor/udp_network.h" + +namespace google_breakpad { + +using std::list; +using std::string; +using std::vector; + +class NetworkSourceLineServer { + public: + explicit NetworkSourceLineServer(SymbolSupplier *supplier, + SourceLineResolverInterface *resolver, + unsigned short listen_port, + bool ip4only, + const string &listen_address, + u_int64_t max_symbol_lines) + : initialized_(false), + net_(new UDPNetwork(listen_address, listen_port, ip4only)), + resolver_(resolver), + supplier_(supplier), + max_symbol_lines_(max_symbol_lines), + symbol_lines_(0) {}; + + NetworkSourceLineServer(SymbolSupplier *supplier, + SourceLineResolverInterface *resolver, + NetworkInterface *net, + u_int64_t max_symbol_lines = 0) + : initialized_(false), + net_(net), + resolver_(resolver), + supplier_(supplier), + max_symbol_lines_(max_symbol_lines), + symbol_lines_(0) {}; + + // Initialize network connection. Will be called automatically by + // RunOnce or RunForever if not already initialized. + // Returns false if network setup fails. + bool Initialize(); + + // Run forever serving connections. + // Returns false only if network setup fails. + bool RunForever(); + + // Look for incoming connections and serve them. + // Wait at most |wait_milliseconds| before returning. Return true + // if any connections were served, and false otherwise. + bool RunOnce(int wait_milliseconds); + + private: + bool initialized_; + NetworkInterface *net_; + SourceLineResolverInterface *resolver_; + SymbolSupplier *supplier_; + // Maximum number of symbol lines to store in memory. + // The number of lines in a symbol file is used as a rough + // proxy for memory usage when parsed and loaded. When + // this limit is surpassed, modules will be unloaded until + // the sum of currently loaded modules is again lower + // than this limit. + const u_int64_t max_symbol_lines_; + // Current number of symbol lines loaded + u_int64_t symbol_lines_; + // List of modules loaded, in most-to-least recently used order + list modules_used_; + // Number of symbol lines loaded, per module. + map module_symbol_lines_; + + void HandleHas(binarystream &message, binarystream &response); + void HandleLoad(binarystream &message, binarystream &response); + void HandleGet(binarystream &message, binarystream &response); + void HandleGetStackWin(binarystream &message, binarystream &response); + void HandleGetStackCFI(binarystream &message, binarystream &response); + string FormatWindowsFrameInfo(WindowsFrameInfo *frame_info); + + int CountNewlines(const string &str); + + // Move this module to the front of the used list. + void UsedModule(const CodeModule &module); + // Try to unload some modules to reclaim memory. + // Do not unload the module passed in, as this was just loaded. + void TryUnloadModules(const CodeModule &just_loaded_module); + + protected: + // protected so we can easily unit test it + bool HandleRequest(binarystream &request, binarystream &response); +}; + +} // namespace google_breakpad + +#endif // GOOGLE_BREAKPAD_PROCESSOR_NETWORK_SOURCE_LINE_SERVER_H_ diff --git a/src/processor/network_source_line_server_unittest.cc b/src/processor/network_source_line_server_unittest.cc new file mode 100644 index 00000000..5e969ccf --- /dev/null +++ b/src/processor/network_source_line_server_unittest.cc @@ -0,0 +1,955 @@ +// 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 NetworkSourceLineServer. + +#include +#include +#include + +#include "breakpad_googletest_includes.h" +#include "google_breakpad/processor/code_module.h" +#include "google_breakpad/processor/source_line_resolver_interface.h" +#include "google_breakpad/processor/stack_frame.h" +#include "google_breakpad/processor/symbol_supplier.h" +#include "processor/binarystream.h" +#include "processor/cfi_frame_info.h" +#include "processor/network_source_line_server.h" +#include "processor/network_source_line_protocol.h" +#include "processor/windows_frame_info.h" + +namespace { +using std::ios_base; +using std::set; +using std::string; +using google_breakpad::CFIFrameInfo; +using google_breakpad::CodeModule; +using google_breakpad::binarystream; +using google_breakpad::NetworkInterface; +using google_breakpad::NetworkSourceLineServer; +using google_breakpad::SourceLineResolverInterface; +using google_breakpad::StackFrame; +using google_breakpad::SymbolSupplier; +using google_breakpad::SystemInfo; +using google_breakpad::WindowsFrameInfo; +using ::testing::_; +using ::testing::DoAll; +using ::testing::Invoke; +using ::testing::Property; +using ::testing::Return; +using ::testing::SetArgumentPointee; +// Style guide forbids "using namespace", so at least shorten it. +namespace P = google_breakpad::source_line_protocol; + +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)); +}; + +class MockSymbolSupplier : public SymbolSupplier { +public: + MockSymbolSupplier() {} + + MOCK_METHOD3(GetSymbolFile, SymbolResult(const CodeModule *module, + const SystemInfo *system_info, + string *symbol_file)); + MOCK_METHOD4(GetSymbolFile, SymbolResult(const CodeModule *module, + const SystemInfo *system_info, + string *symbol_file, + string *symbol_data)); +}; + +class MockSourceLineResolver : public SourceLineResolverInterface { + public: + MockSourceLineResolver() {} + virtual ~MockSourceLineResolver() {} + + MOCK_METHOD2(LoadModule, bool(const CodeModule *module, + const string &map_file)); + MOCK_METHOD2(LoadModuleUsingMapBuffer, bool(const CodeModule *module, + const string &map_buffer)); + MOCK_METHOD1(UnloadModule, void(const CodeModule *module)); + MOCK_METHOD1(HasModule, bool(const CodeModule *module)); + MOCK_METHOD1(FillSourceLineInfo, void(StackFrame *frame)); + MOCK_METHOD1(FindWindowsFrameInfo, + WindowsFrameInfo*(const StackFrame *frame)); + MOCK_METHOD1(FindCFIFrameInfo, + CFIFrameInfo*(const StackFrame *frame)); +}; + +class TestNetworkSourceLineServer : public NetworkSourceLineServer { + public: + // Override visibility for testing. It's a lot easier to just + // call into this method and verify the result than it would be + // to mock out the calls to the NetworkInterface, even though + // that would ostensibly be more correct and test the code more + // thoroughly. Perhaps if someone has time and figures out a + // clean way to do it this could be changed. + using NetworkSourceLineServer::HandleRequest; + + TestNetworkSourceLineServer(SymbolSupplier *supplier, + SourceLineResolverInterface *resolver, + NetworkInterface *net, + u_int64_t max_symbol_lines = 0) + : NetworkSourceLineServer(supplier, resolver, net, max_symbol_lines) + + {} +}; + +class NetworkSourceLineServerTest : public ::testing::Test { + public: + MockSymbolSupplier supplier; + MockSourceLineResolver resolver; + MockNetwork net; + TestNetworkSourceLineServer *server; + + NetworkSourceLineServerTest() : server(NULL) {} + + void SetUp() { + server = new TestNetworkSourceLineServer(&supplier, &resolver, &net); + } +}; + +TEST_F(NetworkSourceLineServerTest, TestInit) { + EXPECT_CALL(net, Init(true)).WillOnce(Return(true)); + EXPECT_CALL(net, WaitToReceive(0)).WillOnce(Return(false)); + ASSERT_TRUE(server->Initialize()); + EXPECT_FALSE(server->RunOnce(0)); +} + +TEST_F(NetworkSourceLineServerTest, TestMalformedRequest) { + binarystream request; + // send a request without a full sequence number + request << u_int8_t(1); + binarystream response; + EXPECT_FALSE(server->HandleRequest(request, response)); + request.rewind(); + // send a request without a command + request << u_int16_t(1); + EXPECT_FALSE(server->HandleRequest(request, response)); +} + +TEST_F(NetworkSourceLineServerTest, TestUnknownCommand) { + binarystream request; + // send a request with an unknown command + request << u_int16_t(1) << u_int8_t(100); + binarystream response; + ASSERT_TRUE(server->HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status; + response >> response_sequence >> response_status; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(u_int16_t(1), response_sequence); + EXPECT_EQ(P::ERROR, int(response_status)); +} + +TEST_F(NetworkSourceLineServerTest, TestHasBasic) { + EXPECT_CALL(resolver, HasModule(_)) + .WillOnce(Return(false)) + .WillOnce(Return(true)); + + binarystream request; + const u_int16_t sequence = 0xA0A0; + // first request should come back as not loaded + request << sequence << P::HAS << string("test.dll") << string("test.pdb") + << string("ABCD1234"); + binarystream response; + ASSERT_TRUE(server->HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status, response_data; + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ(P::MODULE_NOT_LOADED, int(response_data)); + // second request should come back as loaded + binarystream request2; + request2 << sequence << P::HAS << string("loaded.dll") << string("loaded.pdb") + << string("ABCD1234"); + ASSERT_TRUE(server->HandleRequest(request2, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ(P::MODULE_LOADED, int(response_data)); +} + +TEST_F(NetworkSourceLineServerTest, TestMalformedHasRequest) { + binarystream request; + // send request with just command, missing all data + const u_int16_t sequence = 0xA0A0; + request << sequence << P::HAS; + binarystream response; + ASSERT_TRUE(server->HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status; + response >> response_sequence >> response_status; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::ERROR, int(response_status)); + // send request with just module name + binarystream request2; + request2 << sequence << P::HAS << string("test.dll"); + ASSERT_TRUE(server->HandleRequest(request2, response)); + response >> response_sequence >> response_status; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::ERROR, int(response_status)); + // send request with module name, debug file, missing debug id + binarystream request3; + request3 << sequence << P::HAS << string("test.dll") << string("test.pdb"); + ASSERT_TRUE(server->HandleRequest(request3, response)); + response >> response_sequence >> response_status; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::ERROR, int(response_status)); +} + +TEST_F(NetworkSourceLineServerTest, TestHasLoad) { + EXPECT_CALL(resolver, HasModule(_)) + .WillOnce(Return(false)) + .WillOnce(Return(false)) + .WillOnce(Return(true)); + EXPECT_CALL(resolver, LoadModuleUsingMapBuffer(_,_)) + .WillOnce(Return(true)); + EXPECT_CALL(supplier, GetSymbolFile(_,_,_,_)) + .WillOnce(Return(SymbolSupplier::FOUND)); + + // verify that the module is not loaded, with a HAS request + binarystream request; + const u_int16_t sequence = 0xA0A0; + request << sequence << P::HAS << string("found.dll") << string("found.pdb") + << string("ABCD1234"); + binarystream response; + ASSERT_TRUE(server->HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status, response_data; + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(P::MODULE_NOT_LOADED, int(response_data)); + // now send a load request for this module + binarystream request2; + const u_int16_t sequence2 = 0xB0B0; + request2 << sequence2 << P::LOAD << string("found.dll") << string("found.pdb") + << string("ABCD1234"); + ASSERT_TRUE(server->HandleRequest(request2, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence2, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); + // sending another HAS message should now show it as loaded + binarystream request3; + const u_int16_t sequence3 = 0xC0C0; + request3 << sequence3 << P::HAS << string("found.dll") << string("found.pdb") + << string("ABCD1234"); + ASSERT_TRUE(server->HandleRequest(request3, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence3, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ(P::MODULE_LOADED, int(response_data)); +} + +TEST_F(NetworkSourceLineServerTest, TestLoad) { + EXPECT_CALL(resolver, HasModule(_)) + .Times(3) + .WillRepeatedly(Return(false)); + EXPECT_CALL(resolver, LoadModuleUsingMapBuffer(_,_)) + .WillOnce(Return(false)); + EXPECT_CALL(supplier, GetSymbolFile(_,_,_,_)) + .WillOnce(Return(SymbolSupplier::NOT_FOUND)) + .WillOnce(Return(SymbolSupplier::INTERRUPT)) + .WillOnce(Return(SymbolSupplier::FOUND)); + + // notfound.dll should return LOAD_NOT_FOUND + binarystream request; + const u_int16_t sequence = 0xA0A0; + request << sequence << P::LOAD << string("notfound.dll") + << string("notfound.pdb") << string("ABCD1234"); + binarystream response; + ASSERT_TRUE(server->HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status, response_data; + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ(int(P::LOAD_NOT_FOUND), int(response_data)); + // interrupt.dll should return LOAD_INTERRUPT + binarystream request2; + const u_int16_t sequence2 = 0xB0B0; + request2 << sequence2 << P::LOAD << string("interrupt.dll") + << string("interrupt.pdb") << string("0000"); + ASSERT_TRUE(server->HandleRequest(request2, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence2, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ(int(P::LOAD_INTERRUPT), int(response_data)); + // fail.dll should return LOAD_FAIL + binarystream request3; + const u_int16_t sequence3 = 0xC0C0; + request3 << sequence3 << P::LOAD << string("fail.dll") << string("fail.pdb") + << string("FFFFFFFF"); + ASSERT_TRUE(server->HandleRequest(request3, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence3, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ(int(P::LOAD_FAIL), int(response_data)); +} + +TEST_F(NetworkSourceLineServerTest, TestMalformedLoadRequest) { + binarystream request; + // send request with just command, missing all data + const u_int16_t sequence = 0xA0A0; + request << sequence << P::LOAD; + binarystream response; + ASSERT_TRUE(server->HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status; + response >> response_sequence >> response_status; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::ERROR, int(response_status)); + // send request with just module name + binarystream request2; + request2 << sequence << P::LOAD << string("test.dll"); + ASSERT_TRUE(server->HandleRequest(request2, response)); + response >> response_sequence >> response_status; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::ERROR, int(response_status)); + // send request with module name, debug file, missing debug id + binarystream request3; + request3 << sequence << P::LOAD << string("test.dll") << string("test.pdb"); + ASSERT_TRUE(server->HandleRequest(request3, response)); + response >> response_sequence >> response_status; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::ERROR, int(response_status)); +} + +void FillFullSourceLineInfo(StackFrame *frame) { + frame->function_name = "function1"; + frame->function_base = 0x1200; + frame->source_file_name = "function1.cc"; + frame->source_line = 1; + frame->source_line_base = 0x1230; +} + +void FillPartialSourceLineInfo(StackFrame *frame) { + frame->function_name = "function2"; + frame->function_base = 0xFFF0; +} + +TEST_F(NetworkSourceLineServerTest, TestGet) { + EXPECT_CALL(resolver, FillSourceLineInfo(_)) + .WillOnce(Invoke(FillFullSourceLineInfo)) + .WillOnce(Invoke(FillPartialSourceLineInfo)); + + binarystream request; + const u_int16_t sequence = 0xA0A0; + request << sequence << P::GET << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0x1234); + binarystream response; + ASSERT_TRUE(server->HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status; + string function, source_file; + u_int32_t source_line; + u_int64_t function_base, source_line_base; + response >> response_sequence >> response_status >> function + >> function_base >> source_file >> source_line >> source_line_base; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ("function1", function); + EXPECT_EQ(0x1200, function_base); + EXPECT_EQ("function1.cc", source_file); + EXPECT_EQ(1, source_line); + EXPECT_EQ(0x1230, source_line_base); + + binarystream request2; + const u_int16_t sequence2 = 0xC0C0; + request2 << sequence2 << P::GET << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0xFFFF); + ASSERT_TRUE(server->HandleRequest(request2, response)); + response >> response_sequence >> response_status >> function + >> function_base >> source_file >> source_line >> source_line_base; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence2, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ("function2", function); + EXPECT_EQ(0xFFF0, function_base); + EXPECT_EQ("", source_file); + EXPECT_EQ(0, source_line); + EXPECT_EQ(0, source_line_base); +} + +WindowsFrameInfo* GetFullWindowsFrameInfo(const StackFrame *frame) { + // return frame info with program string + return new WindowsFrameInfo(1, 2, 3, 0xA, 0xFF, 0xF00, + true, + "x y ="); +} + +WindowsFrameInfo* GetPartialWindowsFrameInfo(const StackFrame *frame) { + // return frame info, no program string + return new WindowsFrameInfo(1, 2, 3, 4, 5, 6, true, ""); +} + +TEST_F(NetworkSourceLineServerTest, TestGetStackWin) { + EXPECT_CALL(resolver, FindWindowsFrameInfo(_)) + .WillOnce(Invoke(GetFullWindowsFrameInfo)) + .WillOnce(Invoke(GetPartialWindowsFrameInfo)) + .WillOnce(Return((WindowsFrameInfo*)NULL)); + + binarystream request; + const u_int16_t sequence = 0xA0A0; + request << sequence << P::GETSTACKWIN << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0x1234); + binarystream response; + ASSERT_TRUE(server->HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status; + string stack_info; + response >> response_sequence >> response_status + >> stack_info; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ("0 0 0 1 2 3 a ff f00 1 x y =", stack_info); + + binarystream request2; + const u_int16_t sequence2 = 0xB0B0; + request2 << sequence2 << P::GETSTACKWIN << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0xABCD); + ASSERT_TRUE(server->HandleRequest(request2, response)); + response >> response_sequence >> response_status + >> stack_info; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence2, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ("0 0 0 1 2 3 4 5 6 0 1", stack_info); + + binarystream request3; + const u_int16_t sequence3 = 0xC0C0; + request3 << sequence3 << P::GETSTACKWIN << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0xFFFF); + ASSERT_TRUE(server->HandleRequest(request3, response)); + response >> response_sequence >> response_status + >> stack_info; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence3, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ("", stack_info); +} + + +CFIFrameInfo* GetCFIFrameInfoJustCFA(const StackFrame *frame) { + CFIFrameInfo* cfi = new CFIFrameInfo(); + cfi->SetCFARule("12345678"); + return cfi; +} + +CFIFrameInfo* GetCFIFrameInfoCFARA(const StackFrame *frame) { + CFIFrameInfo* cfi = new CFIFrameInfo(); + cfi->SetCFARule("12345678"); + cfi->SetRARule("abcdefgh"); + return cfi; +} + +CFIFrameInfo* GetCFIFrameInfoLots(const StackFrame *frame) { + CFIFrameInfo* cfi = new CFIFrameInfo(); + cfi->SetCFARule("12345678"); + cfi->SetRARule("abcdefgh"); + cfi->SetRegisterRule("r0", "foo bar"); + cfi->SetRegisterRule("b0", "123 abc +"); + return cfi; +} + +TEST_F(NetworkSourceLineServerTest, TestGetStackCFI) { + EXPECT_CALL(resolver, FindCFIFrameInfo(_)) + .WillOnce(Return((CFIFrameInfo*)NULL)) + .WillOnce(Invoke(GetCFIFrameInfoJustCFA)) + .WillOnce(Invoke(GetCFIFrameInfoCFARA)) + .WillOnce(Invoke(GetCFIFrameInfoLots)); + + binarystream request; + const u_int16_t sequence = 0xA0A0; + request << sequence << P::GETSTACKCFI << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0x1234); + binarystream response; + ASSERT_TRUE(server->HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status; + string stack_info; + response >> response_sequence >> response_status + >> stack_info; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ("", stack_info); + + binarystream request2; + const u_int16_t sequence2 = 0xB0B0; + request2 << sequence2 << P::GETSTACKCFI << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0xABCD); + ASSERT_TRUE(server->HandleRequest(request2, response)); + response >> response_sequence >> response_status + >> stack_info; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence2, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ(".cfa: 12345678", stack_info); + + binarystream request3; + const u_int16_t sequence3 = 0xC0C0; + request3 << sequence3 << P::GETSTACKCFI << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0xFFFF); + ASSERT_TRUE(server->HandleRequest(request3, response)); + response >> response_sequence >> response_status + >> stack_info; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence3, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ(".cfa: 12345678 .ra: abcdefgh", stack_info); + + binarystream request4; + const u_int16_t sequence4 = 0xD0D0; + request4 << sequence4 << P::GETSTACKCFI << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0xFFFF); + ASSERT_TRUE(server->HandleRequest(request4, response)); + response >> response_sequence >> response_status + >> stack_info; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence4, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ(".cfa: 12345678 .ra: abcdefgh b0: 123 abc + r0: foo bar", + stack_info); +} + +TEST_F(NetworkSourceLineServerTest, TestMalformedGetRequest) { + //TODO +} + +TEST(TestMissingMembers, TestServerWithoutSymbolSupplier) { + // Should provide reasonable responses without a SymbolSupplier + MockSourceLineResolver resolver; + MockNetwork net; + TestNetworkSourceLineServer server(NULL, &resolver, &net); + + // All LOAD requests should return LOAD_NOT_FOUND + binarystream request; + binarystream response; + const u_int16_t sequence = 0xB0B0; + u_int16_t response_sequence; + u_int8_t response_status, response_data; + request << sequence << P::LOAD << string("found.dll") << string("found.pdb") + << string("ABCD1234"); + ASSERT_TRUE(server.HandleRequest(request, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_NOT_FOUND), int(response_data)); +} + +TEST(TestMissingMembers, TestServerWithoutResolver) { + // Should provide reasonable responses without a SourceLineResolver + MockSymbolSupplier supplier; + MockNetwork net; + TestNetworkSourceLineServer server(&supplier, NULL, &net); + + // GET requests should return empty info + binarystream request; + binarystream response; + const u_int16_t sequence = 0xA0A0; + u_int16_t response_sequence; + u_int8_t response_status; + request << sequence << P::GET << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0x1234); + ASSERT_TRUE(server.HandleRequest(request, response)); + string function, source_file; + u_int32_t source_line; + u_int64_t function_base, source_line_base; + response >> response_sequence >> response_status >> function + >> function_base >> source_file >> source_line >> source_line_base; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ("", function); + EXPECT_EQ(0x0, function_base); + EXPECT_EQ("", source_file); + EXPECT_EQ(0, source_line); + EXPECT_EQ(0x0, source_line_base); + + // GETSTACKWIN requests should return an empty string + binarystream request2; + const u_int16_t sequence2 = 0xB0B0; + request << sequence2 << P::GETSTACKWIN << string("loaded.dll") + << string("loaded.pdb") << string("ABCD1234") + << u_int64_t(0x1000) << u_int64_t(0x1234); + ASSERT_TRUE(server.HandleRequest(request, response)); + string response_string; + response >> response_sequence >> response_status >> response_string; + EXPECT_EQ(sequence2, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + EXPECT_EQ("", response_string); +} + +class TestModuleManagement : public ::testing::Test { +public: + MockSymbolSupplier supplier; + MockSourceLineResolver resolver; + MockNetwork net; + TestNetworkSourceLineServer server; + + // Init server with symbol line limit of 25 + TestModuleManagement() : server(&supplier, &resolver, &net, 25) {} +}; + +TEST_F(TestModuleManagement, TestModuleUnloading) { + EXPECT_CALL(supplier, GetSymbolFile(_,_,_,_)) + .Times(3) + .WillRepeatedly(DoAll(SetArgumentPointee<3>(string("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n")), + Return(SymbolSupplier::FOUND))); + EXPECT_CALL(resolver, HasModule(_)) + .Times(3) + .WillRepeatedly(Return(false)); + EXPECT_CALL(resolver, LoadModuleUsingMapBuffer(_,_)) + .Times(3) + .WillRepeatedly(Return(true)); + EXPECT_CALL(resolver, UnloadModule(Property(&CodeModule::code_file, + string("one.dll|one.pdb|1111")))) + .Times(1); + + // load three modules, each with 10 lines of symbols. + // the third module will overflow the server's symbol line limit, + // and should cause the first module to be unloaded. + binarystream request; + const u_int16_t sequence = 0x1010; + request << sequence << P::LOAD << string("one.dll") << string("one.pdb") + << string("1111"); + binarystream response; + ASSERT_TRUE(server.HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status, response_data; + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); + + binarystream request2; + const u_int16_t sequence2 = 0x2020; + request2 << sequence2 << P::LOAD << string("two.dll") << string("two.pdb") + << string("2222"); + ASSERT_TRUE(server.HandleRequest(request2, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence2, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); + + binarystream request3; + const u_int16_t sequence3 = 0x3030; + request3 << sequence3 << P::LOAD << string("three.dll") << string("three.pdb") + << string("3333"); + ASSERT_TRUE(server.HandleRequest(request3, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence3, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); +} + +TEST_F(TestModuleManagement, TestSymbolLimitTooLow) { + // load module with symbol count > limit, + // ensure that it doesn't get unloaded even though it's the only module + EXPECT_CALL(supplier, GetSymbolFile(_,_,_,_)) + .WillOnce(DoAll(SetArgumentPointee<3>(string("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n")), + Return(SymbolSupplier::FOUND))); + EXPECT_CALL(resolver, HasModule(_)) + .WillOnce(Return(false)); + EXPECT_CALL(resolver, LoadModuleUsingMapBuffer(_,_)) + .WillOnce(Return(true)); + EXPECT_CALL(resolver, UnloadModule(_)) + .Times(0); + + binarystream request; + const u_int16_t sequence = 0x1010; + request << sequence << P::LOAD << string("one.dll") << string("one.pdb") + << string("1111"); + binarystream response; + ASSERT_TRUE(server.HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status, response_data; + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); +} + +TEST_F(TestModuleManagement, TestModuleLoadLRU) { + // load 2 modules, then re-load the first one, + // then load a third one, causing the second one to be unloaded + EXPECT_CALL(supplier, GetSymbolFile(_,_,_,_)) + .Times(3) + .WillRepeatedly(DoAll(SetArgumentPointee<3>(string("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n")), + Return(SymbolSupplier::FOUND))); + EXPECT_CALL(resolver, HasModule(_)) + .WillOnce(Return(false)) // load module 1 + .WillOnce(Return(false)) // load module 2 + .WillOnce(Return(true)) // module 1 already loaded + .WillOnce(Return(false)); // load module 3 + EXPECT_CALL(resolver, LoadModuleUsingMapBuffer(_,_)) + .Times(3) + .WillRepeatedly(Return(true)); + EXPECT_CALL(resolver, UnloadModule(Property(&CodeModule::code_file, + string("two.dll|two.pdb|2222")))) + .Times(1); + + binarystream request; + const u_int16_t sequence = 0x1010; + request << sequence << P::LOAD << string("one.dll") << string("one.pdb") + << string("1111"); + binarystream response; + ASSERT_TRUE(server.HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status, response_data; + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); + + binarystream request2; + const u_int16_t sequence2 = 0x2020; + request2 << sequence2 << P::LOAD << string("two.dll") << string("two.pdb") + << string("2222"); + ASSERT_TRUE(server.HandleRequest(request2, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence2, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); + + binarystream request3; + const u_int16_t sequence3 = 0x3030; + request3 << sequence3 << P::LOAD << string("one.dll") << string("one.pdb") + << string("1111"); + ASSERT_TRUE(server.HandleRequest(request3, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence3, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); + + binarystream request4; + const u_int16_t sequence4 = 0x4040; + request4 << sequence4 << P::LOAD << string("three.dll") << string("three.pdb") + << string("3333"); + ASSERT_TRUE(server.HandleRequest(request4, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence4, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); +} + +TEST_F(TestModuleManagement, TestModuleGetLRU) { + // load 2 modules, then issue a GET for the first one, + // then load a third one, causing the second one to be unloaded + EXPECT_CALL(supplier, GetSymbolFile(_,_,_,_)) + .Times(3) + .WillRepeatedly(DoAll(SetArgumentPointee<3>(string("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n")), + Return(SymbolSupplier::FOUND))); + EXPECT_CALL(resolver, HasModule(_)) + .Times(3) + .WillRepeatedly(Return(false)); + EXPECT_CALL(resolver, LoadModuleUsingMapBuffer(_,_)) + .Times(3) + .WillRepeatedly(Return(true)); + EXPECT_CALL(resolver, FillSourceLineInfo(_)) + .Times(1); + EXPECT_CALL(resolver, UnloadModule(Property(&CodeModule::code_file, + string("two.dll|two.pdb|2222")))) + .Times(1); + + binarystream request; + const u_int16_t sequence = 0x1010; + request << sequence << P::LOAD << string("one.dll") << string("one.pdb") + << string("1111"); + binarystream response; + ASSERT_TRUE(server.HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status, response_data; + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); + + binarystream request2; + const u_int16_t sequence2 = 0x2020; + request2 << sequence2 << P::LOAD << string("two.dll") << string("two.pdb") + << string("2222"); + ASSERT_TRUE(server.HandleRequest(request2, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence2, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); + + binarystream request3; + const u_int16_t sequence3 = 0x3030; + request3 << sequence3 << P::GET << string("one.dll") + << string("one.pdb") << string("1111") + << u_int64_t(0x1000) << u_int64_t(0x1234); + ASSERT_TRUE(server.HandleRequest(request3, response)); + string function, source_file; + u_int32_t source_line; + u_int64_t function_base, source_line_base; + response >> response_sequence >> response_status >> function + >> function_base >> source_file >> source_line >> source_line_base; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence3, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + // Don't care about the rest of the response, really. + + binarystream request4; + const u_int16_t sequence4 = 0x4040; + request4 << sequence4 << P::LOAD << string("three.dll") << string("three.pdb") + << string("3333"); + ASSERT_TRUE(server.HandleRequest(request4, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence4, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); +} + +TEST_F(TestModuleManagement, TestModuleGetStackWinLRU) { + // load 2 modules, then issue a GETSTACKWIN for the first one, + // then load a third one, causing the second one to be unloaded + EXPECT_CALL(supplier, GetSymbolFile(_,_,_,_)) + .Times(3) + .WillRepeatedly(DoAll(SetArgumentPointee<3>(string("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n")), + Return(SymbolSupplier::FOUND))); + EXPECT_CALL(resolver, HasModule(_)) + .Times(3) + .WillRepeatedly(Return(false)); + EXPECT_CALL(resolver, LoadModuleUsingMapBuffer(_,_)) + .Times(3) + .WillRepeatedly(Return(true)); + EXPECT_CALL(resolver, FindWindowsFrameInfo(_)) + .WillOnce(Return((WindowsFrameInfo*)NULL)); + EXPECT_CALL(resolver, UnloadModule(Property(&CodeModule::code_file, + string("two.dll|two.pdb|2222")))) + .Times(1); + + binarystream request; + const u_int16_t sequence = 0x1010; + request << sequence << P::LOAD << string("one.dll") << string("one.pdb") + << string("1111"); + binarystream response; + ASSERT_TRUE(server.HandleRequest(request, response)); + u_int16_t response_sequence; + u_int8_t response_status, response_data; + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); + + binarystream request2; + const u_int16_t sequence2 = 0x2020; + request2 << sequence2 << P::LOAD << string("two.dll") << string("two.pdb") + << string("2222"); + ASSERT_TRUE(server.HandleRequest(request2, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence2, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); + + binarystream request3; + const u_int16_t sequence3 = 0x3030; + request3 << sequence3 << P::GETSTACKWIN << string("one.dll") + << string("one.pdb") << string("1111") + << u_int64_t(0x1000) << u_int64_t(0x1234); + ASSERT_TRUE(server.HandleRequest(request3, response)); + string stack_info; + response >> response_sequence >> response_status + >> stack_info; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence3, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + // Don't care about the rest of the response, really. + + binarystream request4; + const u_int16_t sequence4 = 0x4040; + request4 << sequence4 << P::LOAD << string("three.dll") << string("three.pdb") + << string("3333"); + ASSERT_TRUE(server.HandleRequest(request4, response)); + response >> response_sequence >> response_status >> response_data; + ASSERT_FALSE(response.eof()); + EXPECT_EQ(sequence4, response_sequence); + EXPECT_EQ(P::OK, int(response_status)); + ASSERT_EQ(int(P::LOAD_OK), int(response_data)); +} + +} // namespace + +int main(int argc, char *argv[]) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/processor/source_daemon.cc b/src/processor/source_daemon.cc new file mode 100644 index 00000000..f47b1f3f --- /dev/null +++ b/src/processor/source_daemon.cc @@ -0,0 +1,127 @@ +// 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. + +// source_daemon.cc: Listen for incoming UDP requests for source line +// info, load symbol files and respond with source info. +// +// Author: Ted Mielczarek + +#include +#include +#include + +#include +#include + +#include "google_breakpad/processor/basic_source_line_resolver.h" +#include "processor/logging.h" +#include "processor/network_source_line_server.h" +#include "processor/scoped_ptr.h" +#include "processor/simple_symbol_supplier.h" + +using std::string; +using std::vector; +using google_breakpad::BasicSourceLineResolver; +using google_breakpad::NetworkSourceLineServer; +using google_breakpad::scoped_ptr; +using google_breakpad::SimpleSymbolSupplier; + +void usage(char *progname) { + printf("Usage: %s [-p port] [-a listen address] " + "[-m maximum symbol lines to keep in memory] " + "\n", progname); +} + +int main(int argc, char **argv) { + BPLOG_INIT(&argc, &argv); + + unsigned short listen_port = 0; + char listen_address[1024] = ""; + u_int64_t max_symbol_lines = 0; + int arg; + while((arg = getopt(argc, argv, "p:a:m:")) != -1) { + switch(arg) { + case 'p': { + int port_arg = atoi(optarg); + if (port_arg > -1 && port_arg < 65535) { + listen_port = port_arg; + } else { + fprintf(stderr, "Invalid port number for -p!\n"); + usage(argv[0]); + return 1; + } + } + break; + case 'a': + strncpy(listen_address, optarg, sizeof(listen_address)); + break; + case 'm': + max_symbol_lines = atoll(optarg); + break; + case '?': + fprintf(stderr, "Option -%c requires an argument\n", (char)optopt); + usage(argv[0]); + break; + default: + fprintf(stderr, "Unknown option: -%c\n", (char)arg); + usage(argv[0]); + return 1; + } + } + + if (optind >= argc) { + usage(argv[0]); + return 1; + } + + vector symbol_paths; + for (int argi = optind; argi < argc; ++argi) + symbol_paths.push_back(argv[argi]); + + scoped_ptr symbol_supplier; + if (!symbol_paths.empty()) { + symbol_supplier.reset(new SimpleSymbolSupplier(symbol_paths)); + } + BasicSourceLineResolver resolver; + + NetworkSourceLineServer server(symbol_supplier.get(), &resolver, listen_port, + // default to IPv4 if no listen address + // is specified + listen_address[0] == '\0', + listen_address, + max_symbol_lines); + if (!server.Initialize()) { + BPLOG(ERROR) << "Failed to initialize server."; + return 1; + } + + server.RunForever(); + // not reached + return 0; +} diff --git a/src/processor/stackwalker.cc b/src/processor/stackwalker.cc index 3b9a313a..a45407db 100644 --- a/src/processor/stackwalker.cc +++ b/src/processor/stackwalker.cc @@ -92,7 +92,7 @@ bool Stackwalker::Walk(CallStack *stack) { if (module) { frame->module = module; if (resolver_ && - !resolver_->HasModule(frame->module->code_file()) && + !resolver_->HasModule(frame->module) && no_symbol_modules_.find( module->code_file()) == no_symbol_modules_.end() && supplier_) { @@ -103,7 +103,7 @@ bool Stackwalker::Walk(CallStack *stack) { switch (symbol_result) { case SymbolSupplier::FOUND: - resolver_->LoadModuleUsingMapBuffer(frame->module->code_file(), + resolver_->LoadModuleUsingMapBuffer(frame->module, symbol_data); break; case SymbolSupplier::NOT_FOUND: @@ -201,14 +201,14 @@ bool Stackwalker::InstructionAddressSeemsValid(u_int64_t address) { return true; } - if (!resolver_->HasModule(module->code_file())) { + if (!resolver_->HasModule(module)) { string symbol_data, symbol_file; SymbolSupplier::SymbolResult symbol_result = supplier_->GetSymbolFile(module, system_info_, &symbol_file, &symbol_data); if (symbol_result != SymbolSupplier::FOUND || - !resolver_->LoadModuleUsingMapBuffer(module->code_file(), + !resolver_->LoadModuleUsingMapBuffer(module, symbol_data)) { // we don't have symbols, but we're inside a loaded module return true; diff --git a/src/processor/tokenize.cc b/src/processor/tokenize.cc new file mode 100644 index 00000000..8b1cdb3a --- /dev/null +++ b/src/processor/tokenize.cc @@ -0,0 +1,76 @@ +// 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. + +#include + +#include +#include + +namespace google_breakpad { + +using std::string; +using std::vector; + +bool Tokenize(char *line, + const char *separators, + int max_tokens, + vector *tokens) { + tokens->clear(); + tokens->reserve(max_tokens); + + int remaining = max_tokens; + + // Split tokens on the separator character. + // strip them out before exhausting max_tokens. + char *save_ptr; + char *token = strtok_r(line, separators, &save_ptr); + while (token && --remaining > 0) { + tokens->push_back(token); + if (remaining > 1) + token = strtok_r(NULL, separators, &save_ptr); + } + + // If there's anything left, just add it as a single token. + if (!remaining > 0) { + if ((token = strtok_r(NULL, "\r\n", &save_ptr))) { + tokens->push_back(token); + } + } + + return tokens->size() == static_cast(max_tokens); +} + +void StringToVector(const string &str, vector &vec) { + vec.reserve(str.length() + 1); + std::copy(str.begin(), str.end(), + vec.begin()); + vec[str.length()] = '\0'; +} + +} // namespace google_breakpad diff --git a/src/processor/tokenize.h b/src/processor/tokenize.h new file mode 100644 index 00000000..1562b823 --- /dev/null +++ b/src/processor/tokenize.h @@ -0,0 +1,61 @@ +// 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. +// +// Implements a Tokenize function for splitting up strings. + +#ifndef GOOGLE_BREAKPAD_PROCESSOR_TOKENIZE_H_ +#define GOOGLE_BREAKPAD_PROCESSOR_TOKENIZE_H_ + +#include +#include + +namespace google_breakpad { + +// Splits line into at most max_tokens tokens, separated by any of the +// characters in separators and placing them in the tokens vector. +// line is a 0-terminated string that optionally ends with a newline +// character or combination, which will be removed. +// If more tokens than max_tokens are present, the final token is placed +// into the vector without splitting it up at all. This modifies line as +// a side effect. Returns true if exactly max_tokens tokens are returned, +// and false if fewer are returned. This is not considered a failure of +// Tokenize, but may be treated as a failure if the caller expects an +// exact, as opposed to maximum, number of tokens. + +bool Tokenize(char *line, + const char *separators, + int max_tokens, + std::vector *tokens); +// For convenience, since you need a char* to pass to Tokenize. +// You can call StringToVector on a std::string, and use &vec[0]. +void StringToVector(const std::string &str, std::vector &vec); + +} // namespace google_breakpad + +#endif // GOOGLE_BREAKPAD_PROCESSOR_TOKENIZE_H_ diff --git a/src/processor/udp_network.cc b/src/processor/udp_network.cc new file mode 100644 index 00000000..52072841 --- /dev/null +++ b/src/processor/udp_network.cc @@ -0,0 +1,185 @@ +// 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. + +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "processor/logging.h" +#include "processor/udp_network.h" + +namespace google_breakpad { +using std::string; +using std::dec; + +UDPNetwork::~UDPNetwork() { + if (socket_ != -1) { + close(socket_); + socket_ = -1; + } +} + +bool UDPNetwork::Init(bool listen) { + struct addrinfo hints; + struct addrinfo *results; + memset(&hints, 0, sizeof(hints)); + if (ip4only_) + hints.ai_family = AF_INET; + else + // don't care if it's IPv4 or IPv6 + hints.ai_family = AF_UNSPEC; + // want a UDP socket + hints.ai_socktype = SOCK_DGRAM; + if (listen) + hints.ai_flags = AI_PASSIVE; + + const char *hostname = NULL; + if (!server_.empty()) + hostname = server_.c_str(); + char portname[6]; + sprintf(portname, "%u", port_); + if (!listen) { + BPLOG(INFO) << "Initializing network connection to " << server_ + << ":" << dec << port_; + } + if (getaddrinfo(hostname, portname, &hints, &results) != 0) { + BPLOG(ERROR) << "failed to get address info for address " << server_ + << ": " << strerror(errno); + return false; + } + // save the address of the first result. + //TODO(ted): could support multiple DNS entries, round-robin them for + // fail-over etc + memcpy(&address_, results->ai_addr, GET_SA_LEN(results->ai_addr)); + + socket_ = socket(results->ai_family, results->ai_socktype, + results->ai_protocol); + freeaddrinfo(results); + + if (socket_ == -1) { + BPLOG(ERROR) << "failed to create socket: " << strerror(errno); + return false; + } + + if (listen) { + char address_string[INET_ADDRSTRLEN]; + void *addr = NULL; + if (((struct sockaddr*)&address_)->sa_family == AF_INET) + addr = &((struct sockaddr_in*)&address_)->sin_addr; + else if (((struct sockaddr*)&address_)->sa_family == AF_INET6) + addr = &((struct sockaddr_in6*)&address_)->sin6_addr; + if (inet_ntop(((struct sockaddr*)&address_)->sa_family, addr, + address_string, sizeof(address_string)) != NULL) + BPLOG(INFO) << "Listening on address " << address_string; + + if (bind(socket_, + (struct sockaddr *)&address_, + GET_SA_LEN(address_)) == -1) { + BPLOG(ERROR) << "Failed to bind socket"; + close(socket_); + return false; + } + socklen_t bound_addr_len = GET_SA_LEN(address_); + if (getsockname(socket_, (struct sockaddr *)&address_, &bound_addr_len) + == 0) { + if (((struct sockaddr*)&address_)->sa_family == AF_INET) + port_ = ntohs(((struct sockaddr_in*)&address_)->sin_port); + else if (((struct sockaddr*)&address_)->sa_family == AF_INET6) + port_ = ntohs(((struct sockaddr_in6*)&address_)->sin6_port); + } + BPLOG(INFO) << "Listening on port " << port_; + } + return true; +} + +bool UDPNetwork::Send(const char *data, size_t length) { + int total_sent = 0; + while (total_sent < length) { + int bytes_sent = sendto(socket_, + data + total_sent, + length - total_sent, + 0, + (struct sockaddr *)&address_, + GET_SA_LEN(address_)); + if (bytes_sent < 0) { + BPLOG(ERROR) << "error sending message: " + << strerror(errno) << " (" << errno << ")"; + break; + } + total_sent += bytes_sent; + } + return total_sent == length; +} + +bool UDPNetwork::WaitToReceive(int wait_time) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(socket_, &readfds); + struct timeval timeout; + + timeout.tv_sec = wait_time / 1000; + timeout.tv_usec = (wait_time % 1000) * 1000; + int ret = select(socket_+1, &readfds, NULL, NULL, &timeout); + if (ret == 0) { + return false; + } else if (ret == -1) { + if (errno != EINTR) + BPLOG(ERROR) << "error in select(): " << strerror(errno); + return false; + } else if (!FD_ISSET(socket_, &readfds)) { + BPLOG(ERROR) << "select returned, but our socket isn't ready?"; + return false; + } + + return true; +} + +bool UDPNetwork::Receive(char *buffer, size_t buffer_size, ssize_t &received) { + socklen_t fromlen = GET_SA_LEN(address_); + received = recvfrom(socket_, buffer, buffer_size, 0, + (struct sockaddr *)&address_, + &fromlen); + + if (received == -1) { + BPLOG(ERROR) << "Error in recvfrom reading response: " + << strerror(errno); + } + return received != -1; +} + +} // namespace google_breakpad diff --git a/src/processor/udp_network.h b/src/processor/udp_network.h new file mode 100644 index 00000000..6e8488bc --- /dev/null +++ b/src/processor/udp_network.h @@ -0,0 +1,73 @@ +// 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. + +// UDPNetwork implements NetworkInterface using UDP sockets. + +#ifndef _GOOGLE_BREAKPAD_PROCESSOR_UDP_NETWORK_H_ +#define _GOOGLE_BREAKPAD_PROCESSOR_UDP_NETWORK_H_ + +#include + +#include + +#include "processor/network_interface.h" + +namespace google_breakpad { + +class UDPNetwork : public NetworkInterface { + public: + // Initialize a UDP Network socket at this address and port. + // address can be empty to indicate that any local address is acceptable. + UDPNetwork(const std::string address, + unsigned short port, + bool ip4only = false) + : server_(address), + port_(port), + ip4only_(ip4only), + socket_(-1) {}; + + ~UDPNetwork(); + + virtual bool Init(bool listen); + virtual bool Send(const char *data, size_t length); + virtual bool WaitToReceive(int timeout); + virtual bool Receive(char *buffer, size_t buffer_size, ssize_t &received); + + unsigned short port() { return port_; } + + private: + std::string server_; + unsigned short port_; + bool ip4only_; + struct sockaddr_storage address_; + int socket_; +}; + +} // namespace google_breakpad +#endif // GOOGLE_BREAKPAD_PROCESSOR_UDP_NETWORK_H_ diff --git a/src/processor/windows_frame_info.h b/src/processor/windows_frame_info.h index 0665dcdf..cca78b88 100644 --- a/src/processor/windows_frame_info.h +++ b/src/processor/windows_frame_info.h @@ -38,9 +38,13 @@ #ifndef PROCESSOR_WINDOWS_FRAME_INFO_H__ #define PROCESSOR_WINDOWS_FRAME_INFO_H__ +#include #include +#include #include "google_breakpad/common/breakpad_types.h" +#include "processor/logging.h" +#include "processor/tokenize.h" namespace google_breakpad { @@ -52,6 +56,20 @@ struct WindowsFrameInfo { VALID_ALL = -1 }; + // The types for stack_info_. This is equivalent to MS DIA's + // StackFrameTypeEnum. Each identifies a different type of frame + // information, although all are represented in the symbol file in the + // same format. These are used as indices to the stack_info_ array. + enum StackInfoTypes { + STACK_INFO_FPO = 0, + STACK_INFO_TRAP, // not used here + STACK_INFO_TSS, // not used here + STACK_INFO_STANDARD, + STACK_INFO_FRAME_DATA, + STACK_INFO_LAST, // must be the last sequentially-numbered item + STACK_INFO_UNKNOWN = -1 + }; + WindowsFrameInfo() : valid(VALID_NONE), prolog_size(0), epilog_size(0), @@ -80,6 +98,56 @@ struct WindowsFrameInfo { allocates_base_pointer(set_allocates_base_pointer), program_string(set_program_string) {} + // Parse a textual serialization of a WindowsFrameInfo object from + // a string. Returns NULL if parsing fails, or a new object + // otherwise. type, rva and code_size are present in the STACK line, + // but not the StackFrameInfo structure, so return them as outparams. + static WindowsFrameInfo *ParseFromString(const std::string string, + int &type, + u_int64_t &rva, + u_int64_t &code_size) { + // The format of a STACK WIN record is documented at: + // + // http://code.google.com/p/google-breakpad/wiki/SymbolFiles + + std::vector buffer; + StringToVector(string, buffer); + std::vector tokens; + if (!Tokenize(&buffer[0], " \r\n", 11, &tokens)) + return NULL; + + type = strtol(tokens[0], NULL, 16); + if (type < 0 || type > STACK_INFO_LAST - 1) + return NULL; + + rva = strtoull(tokens[1], NULL, 16); + code_size = strtoull(tokens[2], NULL, 16); + u_int32_t prolog_size = strtoul(tokens[3], NULL, 16); + u_int32_t epilog_size = strtoul(tokens[4], NULL, 16); + u_int32_t parameter_size = strtoul(tokens[5], NULL, 16); + u_int32_t saved_register_size = strtoul(tokens[6], NULL, 16); + u_int32_t local_size = strtoul(tokens[7], NULL, 16); + u_int32_t max_stack_size = strtoul(tokens[8], NULL, 16); + int has_program_string = strtoul(tokens[9], NULL, 16); + + const char *program_string = ""; + int allocates_base_pointer = 0; + if (has_program_string) { + program_string = tokens[10]; + } else { + allocates_base_pointer = strtoul(tokens[10], NULL, 16); + } + + return new WindowsFrameInfo(prolog_size, + epilog_size, + parameter_size, + saved_register_size, + local_size, + max_stack_size, + allocates_base_pointer, + program_string); + } + // CopyFrom makes "this" WindowsFrameInfo object identical to "that". void CopyFrom(const WindowsFrameInfo &that) { valid = that.valid; -- cgit v1.2.1