aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorted.mielczarek <ted.mielczarek@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-04-08 23:06:23 +0000
committerted.mielczarek <ted.mielczarek@4c0a9323-5329-0410-9bdc-e9ce6186880e>2010-04-08 23:06:23 +0000
commitb223627d81c083a64f2ccecf2651a18111421280 (patch)
treebfe59da685835b8d13b073f11cddaf4c5997282f /src
parentInclude what you use. (diff)
downloadbreakpad-b223627d81c083a64f2ccecf2651a18111421280.tar.xz
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
Diffstat (limited to 'src')
-rw-r--r--src/config.h.in3
-rw-r--r--src/google_breakpad/processor/basic_source_line_resolver.h13
-rw-r--r--src/google_breakpad/processor/network_source_line_resolver.h168
-rw-r--r--src/google_breakpad/processor/source_line_resolver_interface.h24
-rw-r--r--src/processor/basic_code_module.h15
-rw-r--r--src/processor/basic_source_line_resolver.cc265
-rw-r--r--src/processor/basic_source_line_resolver_unittest.cc104
-rw-r--r--src/processor/binarystream.cc123
-rw-r--r--src/processor/binarystream.h88
-rw-r--r--src/processor/binarystream_unittest.cc432
-rw-r--r--src/processor/cfi_frame_info.cc23
-rw-r--r--src/processor/cfi_frame_info.h4
-rw-r--r--src/processor/cfi_frame_info_unittest.cc14
-rw-r--r--src/processor/network_interface.h62
-rw-r--r--src/processor/network_source_line_protocol.h162
-rw-r--r--src/processor/network_source_line_resolver.cc435
-rw-r--r--src/processor/network_source_line_resolver_server_unittest.cc207
-rw-r--r--src/processor/network_source_line_resolver_unittest.cc535
-rw-r--r--src/processor/network_source_line_server.cc435
-rw-r--r--src/processor/network_source_line_server.h136
-rw-r--r--src/processor/network_source_line_server_unittest.cc955
-rw-r--r--src/processor/source_daemon.cc127
-rw-r--r--src/processor/stackwalker.cc8
-rw-r--r--src/processor/tokenize.cc76
-rw-r--r--src/processor/tokenize.h61
-rw-r--r--src/processor/udp_network.cc185
-rw-r--r--src/processor/udp_network.h73
-rw-r--r--src/processor/windows_frame_info.h68
28 files changed, 4571 insertions, 230 deletions
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 <dlfcn.h> 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 T> 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 <sys/socket.h>
+
+#include <map>
+#include <set>
+
+#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<string> module_cache_;
+ // cached list of modules for which we don't have symbols,
+ // so we can short-circuit that as well.
+ std::set<string> 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<u_int64_t, StackFrame> 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<u_int64_t, string> 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 <string>
#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<int, string> 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<char*> *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<WindowsFrameInfo> >
- 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<WindowsFrameInfo> 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<WindowsFrameInfo> 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<char*> *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<unsigned int>(max_tokens);
-}
-
bool BasicSourceLineResolver::Module::ParseFile(char *file_line) {
// FILE <id> <filename>
file_line += 5; // skip prefix
vector<char*> 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<char*> 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) {
// <address> <line number> <source file id>
vector<char*> 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<char*> 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<WindowsFrameInfo>
+ 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<char*> 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<WindowsFrameInfo> 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 <cstdio>
#include <string>
+
+#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<u_int32_t> &actual) {
CFIFrameInfo::RegisterValueMap<u_int32_t>::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<u_int32_t>::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<WindowsFrameInfo> windows_frame_info;
@@ -271,8 +275,8 @@ static bool RunTests() {
ASSERT_TRUE(cfi_frame_info.get()
->FindCallerRegs<u_int32_t>(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<u_int32_t>(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 <arpa/inet.h>
+#include <limits.h>
+
+#include <vector>
+
+#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<char> 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<u_int64_t>(lower) | (static_cast<u_int64_t>(upper) << 32);
+ return *this;
+}
+
+binarystream &binarystream::operator<<(const std::string &str) {
+ if (str.length() > USHRT_MAX) {
+ // truncate to 16-bit length
+ *this << static_cast<u_int16_t>(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<u_int32_t>(u64 & 0xFFFFFFFF);
+ u_int32_t upper = static_cast<u_int32_t>(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 <sstream>
+#include <string>
+
+#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 <ios>
+#include <string>
+#include <vector>
+
+#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<char> 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 <cstring>
+#include <sstream>
#include "processor/cfi_frame_info.h"
#include "processor/postfix_evaluator-inl.h"
@@ -94,6 +95,28 @@ template bool CFIFrameInfo::FindCallerRegs<u_int64_t>(
const MemoryRegion &memory,
RegisterValueMap<u_int64_t> *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<char> 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<ValueType> *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<u_int64_t>(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<u_int64_t>(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:
+// <foo:N>, which should be read as "foo, an N byte unsigned integer
+// in network byte order". Strings will be described as <foo:S>.
+//
+// A client request looks like:
+// <seq:2><command:1><command data>
+// Where <seq> is a sequence number as described above, <command>
+// is one of the commands listed below, and <command data> is arbitrary
+// data, defined per-command.
+//
+// A server response looks like:
+// <seq:2><status:1><data>
+// Where <seq> is the same sequence number from the request,
+// <status> 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. <data> is arbitrary data, defined
+// per-command below.
+//
+// Strings are sent as a 2-byte integer encoding the length, followed
+// by <length> bytes.
+//
+// Valid Commands:
+//==================================================
+// <HAS:1><module name:S><debug file:S><debug identifier:S>
+// example: <HAS><0x8>test.dll<0x8>test.pdb<0xA>0123456789
+//
+// Query whether the module with this filename and debug information
+// has been previously loaded.
+//
+// Server Response:
+// <loaded:1>
+// Where <loaded> is one of MODULE_LOADED or MODULE_NOT_LOADED
+//
+//==================================================
+// <LOAD:1><module name:S><debug file:S><debug identifier:S>
+// example: <LOAD><0x8>test.dll<0x8>test.pdb<0xA>0123456789
+//
+// Request that the server find and load symbols for this module.
+//
+// Server Response:
+// <result:1>
+// Where <result> 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
+//
+//==================================================
+// <GET:1><module name:S><debug file:S><debug identifier:S><module base address:8><instruction address:8>
+// example: <GET><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:
+// <function:S><func_base:8><source_file:S><source_line:4><source_line_base:8>
+// - As many fields as available are filled in. Fields that are not available
+// will contain an empty string, or a zero for numeric values.
+
+//==================================================
+// <GETSTACKWIN:1><module name:S><debug file:S><debug identifier:S><module base address:8><instruction address:8>
+// example: <GETSTACKWIN><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:
+// <stack:S>
+// 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.
+
+//==================================================
+// <GETSTACKCFI:1><module name:S><debug file:S><debug identifier:S><module base address:8><instruction address:8>
+// example: <GETSTACKCFI><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:
+// <stack:S>
+// 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 <stdlib.h>
+#include <string.h>
+
+#include <cassert>
+#include <sstream>
+#include <vector>
+
+#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<CFIFrameInfo> 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 = "<loaded on server>";
+ 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<char> 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 <string>
+
+#include <signal.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#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 <string>
+
+#include "breakpad_googletest_includes.h"
+#include "google_breakpad/processor/network_source_line_resolver.h"
+#include "google_breakpad/processor/stack_frame.h"
+#include "google_breakpad/processor/symbol_supplier.h"
+#include "processor/basic_code_module.h"
+#include "processor/binarystream.h"
+#include "processor/cfi_frame_info.h"
+#include "processor/network_interface.h"
+#include "processor/network_source_line_protocol.h"
+#include "processor/windows_frame_info.h"
+
+namespace google_breakpad {
+class MockNetwork : public NetworkInterface {
+ public:
+ MockNetwork() {}
+
+ MOCK_METHOD1(Init, bool(bool listen));
+ MOCK_METHOD2(Send, bool(const char *data, size_t length));
+ MOCK_METHOD1(WaitToReceive, bool(int timeout));
+ MOCK_METHOD3(Receive, bool(char *buffer, size_t buffer_size,
+ ssize_t &received));
+};
+}
+
+namespace {
+using std::string;
+using google_breakpad::binarystream;
+using google_breakpad::BasicCodeModule;
+using google_breakpad::CFIFrameInfo;
+using google_breakpad::MockNetwork;
+using google_breakpad::NetworkSourceLineResolver;
+using google_breakpad::StackFrame;
+using google_breakpad::SymbolSupplier;
+using google_breakpad::WindowsFrameInfo;
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::Return;
+using namespace google_breakpad::source_line_protocol;
+
+class TestNetworkSourceLineResolver : public ::testing::Test {
+public:
+ TestNetworkSourceLineResolver() : resolver(NULL) {}
+
+ void SetUp() {
+ EXPECT_CALL(net, Init(false)).WillOnce(Return(true));
+ resolver = new NetworkSourceLineResolver(&net, 0);
+ }
+
+ NetworkSourceLineResolver *resolver;
+ MockNetwork net;
+};
+
+bool GeneratePositiveHasResponse(char *buffer, size_t buffer_size,
+ ssize_t &received) {
+ binarystream stream;
+ stream << u_int16_t(0) << OK << MODULE_LOADED;
+ string s = stream.str();
+ received = s.length();
+ memcpy(buffer, s.c_str(), s.length());
+ return true;
+}
+
+bool GenerateNegativeHasResponse(char *buffer, size_t buffer_size,
+ ssize_t &received) {
+ binarystream stream;
+ stream << u_int16_t(1) << OK << MODULE_NOT_LOADED;
+ string s = stream.str();
+ received = s.length();
+ memcpy(buffer, s.c_str(), s.length());
+ return true;
+}
+
+TEST_F(TestNetworkSourceLineResolver, TestHasMessages) {
+ EXPECT_CALL(net, Send(_,_))
+ .WillOnce(Return(true))
+ .WillOnce(Return(true));
+ EXPECT_CALL(net, WaitToReceive(0))
+ .WillOnce(Return(true))
+ .WillOnce(Return(true));
+ EXPECT_CALL(net, Receive(_,_,_))
+ .WillOnce(Invoke(GeneratePositiveHasResponse))
+ .WillOnce(Invoke(GenerateNegativeHasResponse));
+ ASSERT_NE(resolver, (NetworkSourceLineResolver*)NULL);
+ BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", "");
+ EXPECT_TRUE(resolver->HasModule(&module));
+ BasicCodeModule module2(0x0, 0x0, "test2.dll", "test2.pdb", "FFFF", "", "");
+ EXPECT_FALSE(resolver->HasModule(&module2));
+ // calling again should hit the cache, and not the network
+ EXPECT_TRUE(resolver->HasModule(&module));
+}
+
+bool GenerateErrorResponse(char *buffer, size_t buffer_size,
+ ssize_t &received) {
+ binarystream stream;
+ stream << u_int16_t(0) << ERROR;
+ string s = stream.str();
+ received = s.length();
+ memcpy(buffer, s.c_str(), s.length());
+ return true;
+}
+
+TEST_F(TestNetworkSourceLineResolver, TestHasErrorResponse) {
+ EXPECT_CALL(net, Send(_,_))
+ .WillOnce(Return(true));
+ EXPECT_CALL(net, WaitToReceive(0))
+ .WillOnce(Return(true));
+ EXPECT_CALL(net, Receive(_,_,_))
+ .WillOnce(Invoke(GenerateErrorResponse));
+ ASSERT_NE(resolver, (NetworkSourceLineResolver*)NULL);
+ BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", "");
+ // error packet should function as a not found
+ EXPECT_FALSE(resolver->HasModule(&module));
+}
+
+// GenerateLoadResponse will generate (LOAD_NOT_FOUND, LOAD_INTERRUPT,
+// LOAD_FAIL, LOAD_OK) in order.
+class LoadHelper {
+ public:
+ LoadHelper() : response(LOAD_NOT_FOUND), sequence(0) {}
+ bool GenerateLoadResponse(char *buffer, size_t buffer_size,
+ ssize_t &received) {
+ binarystream stream;
+ stream << sequence << OK << response;
+ string s = stream.str();
+ received = s.length();
+ memcpy(buffer, s.c_str(), s.length());
+ ++sequence;
+ ++response;
+ return true;
+ }
+ u_int8_t response;
+ u_int16_t sequence;
+};
+
+TEST_F(TestNetworkSourceLineResolver, TestLoadMessages) {
+ EXPECT_CALL(net, Send(_,_))
+ .Times(4)
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(net, WaitToReceive(0))
+ .Times(4)
+ .WillRepeatedly(Return(true));
+ LoadHelper helper;
+ EXPECT_CALL(net, Receive(_,_,_))
+ .Times(4)
+ .WillRepeatedly(Invoke(&helper, &LoadHelper::GenerateLoadResponse));
+ ASSERT_NE(resolver, (NetworkSourceLineResolver*)NULL);
+ BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", "");
+ string s;
+ EXPECT_EQ(SymbolSupplier::NOT_FOUND,
+ resolver->GetSymbolFile(&module, NULL, &s));
+ BasicCodeModule module2(0x0, 0x0, "test2.dll", "test2.pdb", "FFFF", "", "");
+ EXPECT_EQ(SymbolSupplier::INTERRUPT,
+ resolver->GetSymbolFile(&module2, NULL, &s));
+ BasicCodeModule module3(0x0, 0x0, "test3.dll", "test3.pdb", "0000", "", "");
+ // a FAIL result from the network should come back as NOT_FOUND
+ EXPECT_EQ(SymbolSupplier::NOT_FOUND,
+ resolver->GetSymbolFile(&module3, NULL, &s));
+ BasicCodeModule module4(0x0, 0x0, "test4.dll", "test4.pdb", "1010", "", "");
+ EXPECT_EQ(SymbolSupplier::FOUND,
+ resolver->GetSymbolFile(&module4, NULL, &s));
+ // calling this should hit the cache, and not the network
+ EXPECT_TRUE(resolver->HasModule(&module4));
+}
+
+TEST_F(TestNetworkSourceLineResolver, TestLoadErrorResponse) {
+ EXPECT_CALL(net, Send(_,_))
+ .WillOnce(Return(true));
+ EXPECT_CALL(net, WaitToReceive(0))
+ .WillOnce(Return(true));
+ EXPECT_CALL(net, Receive(_,_,_))
+ .WillOnce(Invoke(GenerateErrorResponse));
+ ASSERT_NE(resolver, (NetworkSourceLineResolver*)NULL);
+ BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", "");
+ string s;
+ // error packet should function as NOT_FOUND response
+ EXPECT_EQ(SymbolSupplier::NOT_FOUND,
+ resolver->GetSymbolFile(&module, NULL, &s));
+}
+
+class GetHelper {
+ public:
+ GetHelper() : sequence(1) {}
+ bool GenerateGetResponse(char *buffer, size_t buffer_size,
+ ssize_t &received) {
+ binarystream stream;
+ stream << sequence << OK;
+ switch(sequence) {
+ case 1:
+ // return full info
+ stream << string("test()") << u_int64_t(0x1000) << string("test.c")
+ << u_int32_t(1) << u_int64_t(0x1010);
+ break;
+ case 2:
+ // return full info
+ stream << string("test2()") << u_int64_t(0x2000) << string("test2.c")
+ << u_int32_t(2) << u_int64_t(0x2020);
+ break;
+ case 3:
+ // return just function name
+ stream << string("test3()") << u_int64_t(0x4000) << string("")
+ << u_int32_t(0) << u_int64_t(0);
+ break;
+ }
+ string s = stream.str();
+ received = s.length();
+ memcpy(buffer, s.c_str(), s.length());
+ ++sequence;
+ return true;
+ }
+ u_int16_t sequence;
+};
+
+TEST_F(TestNetworkSourceLineResolver, TestGetMessages) {
+ EXPECT_CALL(net, Send(_,_))
+ .Times(4)
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(net, WaitToReceive(0))
+ .Times(4)
+ .WillRepeatedly(Return(true));
+ GetHelper helper;
+ EXPECT_CALL(net, Receive(_,_,_))
+ .Times(4)
+ .WillOnce(Invoke(GeneratePositiveHasResponse))
+ .WillRepeatedly(Invoke(&helper, &GetHelper::GenerateGetResponse));
+ BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", "");
+ // The resolver has to think the module is loaded before it will respond
+ // to GET requests for that module.
+ EXPECT_TRUE(resolver->HasModule(&module));
+ StackFrame frame;
+ frame.module = &module;
+ frame.instruction = 0x1010;
+ resolver->FillSourceLineInfo(&frame);
+ EXPECT_EQ("test()", frame.function_name);
+ EXPECT_EQ(0x1000, frame.function_base);
+ EXPECT_EQ("test.c", frame.source_file_name);
+ EXPECT_EQ(1, frame.source_line);
+ EXPECT_EQ(0x1010, frame.source_line_base);
+
+ StackFrame frame2;
+ frame2.module = &module;
+ frame2.instruction = 0x2020;
+ resolver->FillSourceLineInfo(&frame2);
+ EXPECT_EQ("test2()", frame2.function_name);
+ EXPECT_EQ(0x2000, frame2.function_base);
+ EXPECT_EQ("test2.c", frame2.source_file_name);
+ EXPECT_EQ(2, frame2.source_line);
+ EXPECT_EQ(0x2020, frame2.source_line_base);
+
+ StackFrame frame3;
+ frame3.module = &module;
+ frame3.instruction = 0x4040;
+ resolver->FillSourceLineInfo(&frame3);
+ EXPECT_EQ("test3()", frame3.function_name);
+ EXPECT_EQ(0x4000, frame3.function_base);
+ EXPECT_EQ("", frame3.source_file_name);
+ EXPECT_EQ(0, frame3.source_line);
+ EXPECT_EQ(0, frame3.source_line_base);
+
+ // this should come from the cache and not hit the network
+ StackFrame frame4;
+ frame4.module = &module;
+ frame4.instruction = 0x1010;
+ resolver->FillSourceLineInfo(&frame4);
+ EXPECT_EQ("test()", frame4.function_name);
+ EXPECT_EQ(0x1000, frame4.function_base);
+ EXPECT_EQ("test.c", frame4.source_file_name);
+ EXPECT_EQ(1, frame4.source_line);
+ EXPECT_EQ(0x1010, frame4.source_line_base);
+
+ // this should also be cached
+ StackFrame frame5;
+ frame5.module = &module;
+ frame5.instruction = 0x4040;
+ resolver->FillSourceLineInfo(&frame5);
+ EXPECT_EQ("test3()", frame5.function_name);
+ EXPECT_EQ(0x4000, frame5.function_base);
+ EXPECT_EQ("", frame5.source_file_name);
+ EXPECT_EQ(0, frame5.source_line);
+ EXPECT_EQ(0, frame5.source_line_base);
+}
+
+class GetStackWinHelper {
+ public:
+ GetStackWinHelper() : sequence(1) {}
+ bool GenerateGetStackWinResponse(char *buffer, size_t buffer_size,
+ ssize_t &received) {
+ binarystream stream;
+ stream << sequence << OK;
+ switch(sequence) {
+ case 1:
+ // return full info including program string
+ stream << string("0 0 0 1 2 3 a ff f00 1 x y =");
+ break;
+ case 2:
+ // return full info, no program string
+ stream << string("0 0 0 1 2 3 a ff f00 0 1");
+ break;
+ case 3:
+ // return empty string
+ stream << string("");
+ break;
+ }
+ string s = stream.str();
+ received = s.length();
+ memcpy(buffer, s.c_str(), s.length());
+ ++sequence;
+ return true;
+ }
+ u_int16_t sequence;
+};
+
+TEST_F(TestNetworkSourceLineResolver, TestGetStackWinMessages) {
+ EXPECT_CALL(net, Send(_,_))
+ .Times(4)
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(net, WaitToReceive(0))
+ .Times(4)
+ .WillRepeatedly(Return(true));
+ GetStackWinHelper helper;
+ EXPECT_CALL(net, Receive(_,_,_))
+ .Times(4)
+ .WillOnce(Invoke(GeneratePositiveHasResponse))
+ .WillRepeatedly(Invoke(&helper,
+ &GetStackWinHelper::GenerateGetStackWinResponse));
+
+ BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", "");
+ // The resolver has to think the module is loaded before it will respond
+ // to GETSTACKWIN requests for that module.
+ EXPECT_TRUE(resolver->HasModule(&module));
+ StackFrame frame;
+ frame.module = &module;
+ frame.instruction = 0x1010;
+ WindowsFrameInfo *info = resolver->FindWindowsFrameInfo(&frame);
+ ASSERT_NE((WindowsFrameInfo*)NULL, info);
+ EXPECT_EQ(0x1, info->prolog_size);
+ EXPECT_EQ(0x2, info->epilog_size);
+ EXPECT_EQ(0x3, info->parameter_size);
+ EXPECT_EQ(0xa, info->saved_register_size);
+ EXPECT_EQ(0xff, info->local_size);
+ EXPECT_EQ(0xf00, info->max_stack_size);
+ EXPECT_EQ("x y =", info->program_string);
+ delete info;
+
+ StackFrame frame2;
+ frame2.module = &module;
+ frame2.instruction = 0x2020;
+ info = resolver->FindWindowsFrameInfo(&frame2);
+ ASSERT_NE((WindowsFrameInfo*)NULL, info);
+ EXPECT_EQ(0x1, info->prolog_size);
+ EXPECT_EQ(0x2, info->epilog_size);
+ EXPECT_EQ(0x3, info->parameter_size);
+ EXPECT_EQ(0xa, info->saved_register_size);
+ EXPECT_EQ(0xff, info->local_size);
+ EXPECT_EQ(0xf00, info->max_stack_size);
+ EXPECT_EQ("", info->program_string);
+ EXPECT_EQ(true, info->allocates_base_pointer);
+ delete info;
+
+ StackFrame frame3;
+ frame3.module = &module;
+ frame3.instruction = 0x4040;
+ info = resolver->FindWindowsFrameInfo(&frame3);
+ EXPECT_EQ((WindowsFrameInfo*)NULL, info);
+
+ // this should come from the cache and not hit the network
+ StackFrame frame4;
+ frame4.module = &module;
+ frame4.instruction = 0x1010;
+ info = resolver->FindWindowsFrameInfo(&frame4);
+ ASSERT_NE((WindowsFrameInfo*)NULL, info);
+ EXPECT_EQ(0x1, info->prolog_size);
+ EXPECT_EQ(0x2, info->epilog_size);
+ EXPECT_EQ(0x3, info->parameter_size);
+ EXPECT_EQ(0xa, info->saved_register_size);
+ EXPECT_EQ(0xff, info->local_size);
+ EXPECT_EQ(0xf00, info->max_stack_size);
+ EXPECT_EQ("x y =", info->program_string);
+ delete info;
+
+ // this should also be cached
+ StackFrame frame5;
+ frame5.module = &module;
+ frame5.instruction = 0x4040;
+ info = resolver->FindWindowsFrameInfo(&frame5);
+ EXPECT_EQ((WindowsFrameInfo*)NULL, info);
+}
+
+class GetStackCFIHelper {
+ public:
+ GetStackCFIHelper() : sequence(1) {}
+ bool GenerateGetStackCFIResponse(char *buffer, size_t buffer_size,
+ ssize_t &received) {
+ binarystream stream;
+ stream << sequence << OK;
+ switch(sequence) {
+ case 1:
+ // return .cfa, .ra, registers
+ stream << string(".cfa: 1234 .ra: .cfa 5 + r0: abc xyz r2: 10 10");
+ break;
+ case 2:
+ // return just .cfa
+ stream << string(".cfa: xyz");
+ break;
+ case 3:
+ // return empty string
+ stream << string("");
+ break;
+ }
+ string s = stream.str();
+ received = s.length();
+ memcpy(buffer, s.c_str(), s.length());
+ ++sequence;
+ return true;
+ }
+ u_int16_t sequence;
+};
+
+TEST_F(TestNetworkSourceLineResolver, TestGetStackCFIMessages) {
+ EXPECT_CALL(net, Send(_,_))
+ .Times(4)
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(net, WaitToReceive(0))
+ .Times(4)
+ .WillRepeatedly(Return(true));
+ GetStackCFIHelper helper;
+ EXPECT_CALL(net, Receive(_,_,_))
+ .Times(4)
+ .WillOnce(Invoke(GeneratePositiveHasResponse))
+ .WillRepeatedly(Invoke(&helper,
+ &GetStackCFIHelper::GenerateGetStackCFIResponse));
+
+ BasicCodeModule module(0x0, 0x0, "test.dll", "test.pdb", "ABCD", "", "");
+ // The resolver has to think the module is loaded before it will respond
+ // to GETSTACKCFI requests for that module.
+ EXPECT_TRUE(resolver->HasModule(&module));
+ StackFrame frame;
+ frame.module = &module;
+ frame.instruction = 0x1010;
+ CFIFrameInfo *info = resolver->FindCFIFrameInfo(&frame);
+ ASSERT_NE((CFIFrameInfo*)NULL, info);
+ // Ostensibly we would check the internal data structure, but
+ // we'd have to either mock out some other classes or get internal access,
+ // so this is easier.
+ EXPECT_EQ(".cfa: 1234 .ra: .cfa 5 + r0: abc xyz r2: 10 10",
+ info->Serialize());
+ delete info;
+
+ StackFrame frame2;
+ frame2.module = &module;
+ frame2.instruction = 0x2020;
+ info = resolver->FindCFIFrameInfo(&frame2);
+ ASSERT_NE((CFIFrameInfo*)NULL, info);
+ EXPECT_EQ(".cfa: xyz", info->Serialize());
+ delete info;
+
+ StackFrame frame3;
+ frame3.module = &module;
+ frame3.instruction = 0x4040;
+ info = resolver->FindCFIFrameInfo(&frame3);
+ EXPECT_EQ((CFIFrameInfo*)NULL, info);
+
+ // this should come from the cache and not hit the network
+ StackFrame frame4;
+ frame4.module = &module;
+ frame4.instruction = 0x1010;
+ info = resolver->FindCFIFrameInfo(&frame4);
+ ASSERT_NE((CFIFrameInfo*)NULL, info);
+ EXPECT_EQ(".cfa: 1234 .ra: .cfa 5 + r0: abc xyz r2: 10 10",
+ info->Serialize());
+ delete info;
+
+ // this should also be cached
+ StackFrame frame5;
+ frame5.module = &module;
+ frame5.instruction = 0x4040;
+ info = resolver->FindCFIFrameInfo(&frame5);
+ EXPECT_EQ((CFIFrameInfo*)NULL, info);
+}
+
+TEST_F(TestNetworkSourceLineResolver, TestBogusData) {
+ EXPECT_FALSE(resolver->HasModule(NULL));
+
+ StackFrame frame;
+ frame.module = NULL;
+ frame.instruction = 0x1000;
+ resolver->FillSourceLineInfo(&frame);
+ EXPECT_EQ("", frame.function_name);
+ EXPECT_EQ(0x0, frame.function_base);
+ EXPECT_EQ("", frame.source_file_name);
+ EXPECT_EQ(0, frame.source_line);
+ EXPECT_EQ(0x0, frame.source_line_base);
+
+ EXPECT_EQ((WindowsFrameInfo*)NULL, resolver->FindWindowsFrameInfo(&frame));
+}
+
+} // namespace
+
+int main(int argc, char *argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
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 <arpa/inet.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+
+#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<char> 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<string>::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<string, int>::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 <list>
+#include <string>
+#include <vector>
+
+#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<string> modules_used_;
+ // Number of symbol lines loaded, per module.
+ map<string, int> 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 <ios>
+#include <set>
+#include <string>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+#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] "
+ "<symbol paths>\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<string> symbol_paths;
+ for (int argi = optind; argi < argc; ++argi)
+ symbol_paths.push_back(argv[argi]);
+
+ scoped_ptr<SimpleSymbolSupplier> 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 <string.h>
+
+#include <string>
+#include <vector>
+
+namespace google_breakpad {
+
+using std::string;
+using std::vector;
+
+bool Tokenize(char *line,
+ const char *separators,
+ int max_tokens,
+ vector<char*> *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<unsigned int>(max_tokens);
+}
+
+void StringToVector(const string &str, vector<char> &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 <string>
+#include <vector>
+
+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<char*> *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<char> &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 <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include <cstdio>
+#include <cstring>
+#include <string>
+
+#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 <sys/socket.h>
+
+#include <string>
+
+#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 <cstring>
#include <string>
+#include <vector>
#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<char> buffer;
+ StringToVector(string, buffer);
+ std::vector<char*> 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;