Move dead submodules in-tree

Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-31 02:33:02 -04:00
parent c0cceff365
commit 6c655321e6
Signed by untrusted user: crueter
GPG key ID: A5A7629F109C8FD1
4081 changed files with 1185566 additions and 45 deletions

View file

@ -0,0 +1,92 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// address_map-inl.h: Address map implementation.
//
// See address_map.h for documentation.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_ADDRESS_MAP_INL_H__
#define PROCESSOR_ADDRESS_MAP_INL_H__
#include "processor/address_map.h"
#include <assert.h>
#include "processor/logging.h"
namespace google_breakpad {
template<typename AddressType, typename EntryType>
bool AddressMap<AddressType, EntryType>::Store(const AddressType& address,
const EntryType& entry) {
// Ensure that the specified address doesn't conflict with something already
// in the map.
if (map_.find(address) != map_.end()) {
BPLOG(INFO) << "Store failed, address " << HexString(address) <<
" is already present";
return false;
}
map_.insert(MapValue(address, entry));
return true;
}
template<typename AddressType, typename EntryType>
bool AddressMap<AddressType, EntryType>::Retrieve(
const AddressType& address,
EntryType* entry, AddressType* entry_address) const {
BPLOG_IF(ERROR, !entry) << "AddressMap::Retrieve requires |entry|";
assert(entry);
// upper_bound gives the first element whose key is greater than address,
// but we want the first element whose key is less than or equal to address.
// Decrement the iterator to get there, but not if the upper_bound already
// points to the beginning of the map - in that case, address is lower than
// the lowest stored key, so return false.
MapConstIterator iterator = map_.upper_bound(address);
if (iterator == map_.begin())
return false;
--iterator;
*entry = iterator->second;
if (entry_address)
*entry_address = iterator->first;
return true;
}
template<typename AddressType, typename EntryType>
void AddressMap<AddressType, EntryType>::Clear() {
map_.clear();
}
} // namespace google_breakpad
#endif // PROCESSOR_ADDRESS_MAP_INL_H__

View file

@ -0,0 +1,84 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// address_map.h: Address maps.
//
// An address map contains a set of objects keyed by address. Objects are
// retrieved from the map by returning the object with the highest key less
// than or equal to the lookup key.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_ADDRESS_MAP_H__
#define PROCESSOR_ADDRESS_MAP_H__
#include <map>
namespace google_breakpad {
// Forward declarations (for later friend declarations).
template<class, class> class AddressMapSerializer;
template<typename AddressType, typename EntryType>
class AddressMap {
public:
AddressMap() : map_() {}
// Inserts an entry into the map. Returns false without storing the entry
// if an entry is already stored in the map at the same address as specified
// by the address argument.
bool Store(const AddressType& address, const EntryType& entry);
// Locates the entry stored at the highest address less than or equal to
// the address argument. If there is no such range, returns false. The
// entry is returned in entry, which is a required argument. If
// entry_address is not NULL, it will be set to the address that the entry
// was stored at.
bool Retrieve(const AddressType& address,
EntryType* entry, AddressType* entry_address) const;
// Empties the address map, restoring it to the same state as when it was
// initially created.
void Clear();
private:
friend class AddressMapSerializer<AddressType, EntryType>;
friend class ModuleComparer;
// Convenience types.
typedef std::map<AddressType, EntryType> AddressToEntryMap;
typedef typename AddressToEntryMap::const_iterator MapConstIterator;
typedef typename AddressToEntryMap::value_type MapValue;
// Maps the address of each entry to an EntryType.
AddressToEntryMap map_;
};
} // namespace google_breakpad
#endif // PROCESSOR_ADDRESS_MAP_H__

View file

@ -0,0 +1,199 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// address_map_unittest.cc: Unit tests for AddressMap.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <limits.h>
#include <stdio.h>
#include "processor/address_map-inl.h"
#include "processor/linked_ptr.h"
#include "processor/logging.h"
#define ASSERT_TRUE(condition) \
if (!(condition)) { \
fprintf(stderr, "FAIL: %s @ %s:%d\n", #condition, __FILE__, __LINE__); \
return false; \
}
#define ASSERT_FALSE(condition) ASSERT_TRUE(!(condition))
#define ASSERT_EQ(e1, e2) ASSERT_TRUE((e1) == (e2))
namespace {
using google_breakpad::AddressMap;
using google_breakpad::linked_ptr;
// A CountedObject holds an int. A global (not thread safe!) count of
// allocated CountedObjects is maintained to help test memory management.
class CountedObject {
public:
explicit CountedObject(int id) : id_(id) { ++count_; }
~CountedObject() { --count_; }
static int count() { return count_; }
int id() const { return id_; }
private:
static int count_;
int id_;
};
int CountedObject::count_;
typedef int AddressType;
typedef AddressMap< AddressType, linked_ptr<CountedObject> > TestMap;
static bool DoAddressMapTest() {
ASSERT_EQ(CountedObject::count(), 0);
TestMap test_map;
linked_ptr<CountedObject> entry;
AddressType address;
// Check that a new map is truly empty.
ASSERT_FALSE(test_map.Retrieve(0, &entry, &address));
ASSERT_FALSE(test_map.Retrieve(INT_MIN, &entry, &address));
ASSERT_FALSE(test_map.Retrieve(INT_MAX, &entry, &address));
// Check that Clear clears the map without leaking.
ASSERT_EQ(CountedObject::count(), 0);
ASSERT_TRUE(test_map.Store(1,
linked_ptr<CountedObject>(new CountedObject(0))));
ASSERT_TRUE(test_map.Retrieve(1, &entry, &address));
ASSERT_EQ(CountedObject::count(), 1);
test_map.Clear();
ASSERT_EQ(CountedObject::count(), 1); // still holding entry in this scope
// Check that a cleared map is truly empty.
ASSERT_FALSE(test_map.Retrieve(0, &entry, &address));
ASSERT_FALSE(test_map.Retrieve(INT_MIN, &entry, &address));
ASSERT_FALSE(test_map.Retrieve(INT_MAX, &entry, &address));
// Check a single-element map.
ASSERT_TRUE(test_map.Store(10,
linked_ptr<CountedObject>(new CountedObject(1))));
ASSERT_FALSE(test_map.Retrieve(9, &entry, &address));
ASSERT_TRUE(test_map.Retrieve(10, &entry, &address));
ASSERT_EQ(CountedObject::count(), 1);
ASSERT_EQ(entry->id(), 1);
ASSERT_EQ(address, 10);
ASSERT_TRUE(test_map.Retrieve(11, &entry, &address));
ASSERT_TRUE(test_map.Retrieve(11, &entry, NULL)); // NULL ok here
// Add some more elements.
ASSERT_TRUE(test_map.Store(5,
linked_ptr<CountedObject>(new CountedObject(2))));
ASSERT_EQ(CountedObject::count(), 2);
ASSERT_TRUE(test_map.Store(20,
linked_ptr<CountedObject>(new CountedObject(3))));
ASSERT_TRUE(test_map.Store(15,
linked_ptr<CountedObject>(new CountedObject(4))));
ASSERT_FALSE(test_map.Store(10,
linked_ptr<CountedObject>(new CountedObject(5)))); // already in map
ASSERT_TRUE(test_map.Store(16,
linked_ptr<CountedObject>(new CountedObject(6))));
ASSERT_TRUE(test_map.Store(14,
linked_ptr<CountedObject>(new CountedObject(7))));
// Nothing was stored with a key under 5. Don't use ASSERT inside loops
// because it won't show exactly which key/entry/address failed.
for (AddressType key = 0; key < 5; ++key) {
if (test_map.Retrieve(key, &entry, &address)) {
fprintf(stderr,
"FAIL: retrieve %d expected false observed true @ %s:%d\n",
key, __FILE__, __LINE__);
return false;
}
}
// Check everything that was stored.
const int id_verify[] = { 0, 0, 0, 0, 0, // unused
2, 2, 2, 2, 2, // 5 - 9
1, 1, 1, 1, 7, // 10 - 14
4, 6, 6, 6, 6, // 15 - 19
3, 3, 3, 3, 3, // 20 - 24
3, 3, 3, 3, 3 }; // 25 - 29
const AddressType address_verify[] = { 0, 0, 0, 0, 0, // unused
5, 5, 5, 5, 5, // 5 - 9
10, 10, 10, 10, 14, // 10 - 14
15, 16, 16, 16, 16, // 15 - 19
20, 20, 20, 20, 20, // 20 - 24
20, 20, 20, 20, 20 }; // 25 - 29
for (AddressType key = 5; key < 30; ++key) {
if (!test_map.Retrieve(key, &entry, &address)) {
fprintf(stderr,
"FAIL: retrieve %d expected true observed false @ %s:%d\n",
key, __FILE__, __LINE__);
return false;
}
if (entry->id() != id_verify[key]) {
fprintf(stderr,
"FAIL: retrieve %d expected entry %d observed %d @ %s:%d\n",
key, id_verify[key], entry->id(), __FILE__, __LINE__);
return false;
}
if (address != address_verify[key]) {
fprintf(stderr,
"FAIL: retrieve %d expected address %d observed %d @ %s:%d\n",
key, address_verify[key], address, __FILE__, __LINE__);
return false;
}
}
// The stored objects should still be in the map.
ASSERT_EQ(CountedObject::count(), 6);
return true;
}
static bool RunTests() {
if (!DoAddressMapTest())
return false;
// Leak check.
ASSERT_EQ(CountedObject::count(), 0);
return true;
}
} // namespace
int main(int argc, char** argv) {
BPLOG_INIT(&argc, &argv);
return RunTests() ? 0 : 1;
}

View file

@ -0,0 +1,120 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// basic_code_module.h: Carries information about code modules that are loaded
// into a process.
//
// This is a basic concrete implementation of CodeModule. It cannot be
// instantiated directly, only based on other objects that implement
// the CodeModule interface. It exists to provide a CodeModule implementation
// a place to store information when the life of the original object (such as
// a MinidumpModule) cannot be guaranteed.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_BASIC_CODE_MODULE_H__
#define PROCESSOR_BASIC_CODE_MODULE_H__
#include <string>
#include "common/using_std_string.h"
#include "google_breakpad/processor/code_module.h"
namespace google_breakpad {
class BasicCodeModule : public CodeModule {
public:
// Creates a new BasicCodeModule given any existing CodeModule
// implementation. This is useful to make a copy of the data relevant to
// the CodeModule interface without requiring all of the resources that
// other CodeModule implementations may require.
explicit BasicCodeModule(const CodeModule *that)
: base_address_(that->base_address()),
size_(that->size()),
shrink_down_delta_(that->shrink_down_delta()),
code_file_(that->code_file()),
code_identifier_(that->code_identifier()),
debug_file_(that->debug_file()),
debug_identifier_(that->debug_identifier()),
version_(that->version()),
is_unloaded_(that->is_unloaded()) {}
BasicCodeModule(uint64_t base_address, uint64_t size,
const string& code_file,
const string& code_identifier,
const string& debug_file,
const string& debug_identifier,
const string& version,
const bool is_unloaded = false)
: base_address_(base_address),
size_(size),
shrink_down_delta_(0),
code_file_(code_file),
code_identifier_(code_identifier),
debug_file_(debug_file),
debug_identifier_(debug_identifier),
version_(version),
is_unloaded_(is_unloaded)
{}
virtual ~BasicCodeModule() {}
// See code_module.h for descriptions of these methods and the associated
// members.
virtual uint64_t base_address() const { return base_address_; }
virtual uint64_t size() const { return size_; }
virtual uint64_t shrink_down_delta() const { return shrink_down_delta_; }
virtual void SetShrinkDownDelta(uint64_t shrink_down_delta) {
shrink_down_delta_ = shrink_down_delta;
}
virtual string code_file() const { return code_file_; }
virtual string code_identifier() const { return code_identifier_; }
virtual string debug_file() const { return debug_file_; }
virtual string debug_identifier() const { return debug_identifier_; }
virtual string version() const { return version_; }
virtual CodeModule* Copy() const { return new BasicCodeModule(this); }
virtual bool is_unloaded() const { return is_unloaded_; }
private:
uint64_t base_address_;
uint64_t size_;
uint64_t shrink_down_delta_;
string code_file_;
string code_identifier_;
string debug_file_;
string debug_identifier_;
string version_;
bool is_unloaded_;
// Disallow copy constructor and assignment operator.
BasicCodeModule(const BasicCodeModule& that);
void operator=(const BasicCodeModule& that);
};
} // namespace google_breakpad
#endif // PROCESSOR_BASIC_CODE_MODULE_H__

View file

@ -0,0 +1,155 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// basic_code_modules.cc: Contains all of the CodeModule objects that
// were loaded into a single process.
//
// See basic_code_modules.h for documentation.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "processor/basic_code_modules.h"
#include <assert.h>
#include <vector>
#include "google_breakpad/processor/code_module.h"
#include "processor/linked_ptr.h"
#include "processor/logging.h"
#include "processor/range_map-inl.h"
namespace google_breakpad {
using std::vector;
BasicCodeModules::BasicCodeModules(const CodeModules* that,
MergeRangeStrategy strategy)
: main_address_(0), map_() {
BPLOG_IF(ERROR, !that) << "BasicCodeModules::BasicCodeModules requires "
"|that|";
assert(that);
map_.SetMergeStrategy(strategy);
const CodeModule *main_module = that->GetMainModule();
if (main_module)
main_address_ = main_module->base_address();
unsigned int count = that->module_count();
for (unsigned int i = 0; i < count; ++i) {
// Make a copy of the module and insert it into the map. Use
// GetModuleAtIndex because ordering is unimportant when slurping the
// entire list, and GetModuleAtIndex may be faster than
// GetModuleAtSequence.
linked_ptr<const CodeModule> module(that->GetModuleAtIndex(i)->Copy());
if (!map_.StoreRange(module->base_address(), module->size(), module)) {
BPLOG(ERROR) << "Module " << module->code_file()
<< " could not be stored";
}
}
// Report modules with shrunk ranges.
for (unsigned int i = 0; i < count; ++i) {
linked_ptr<const CodeModule> module(that->GetModuleAtIndex(i)->Copy());
uint64_t delta = 0;
if (map_.RetrieveRange(module->base_address() + module->size() - 1,
&module, NULL /* base */, &delta, NULL /* size */) &&
delta > 0) {
BPLOG(INFO) << "The range for module " << module->code_file()
<< " was shrunk down by " << HexString(delta) << " bytes.";
linked_ptr<CodeModule> shrunk_range_module(module->Copy());
shrunk_range_module->SetShrinkDownDelta(delta);
shrunk_range_modules_.push_back(shrunk_range_module);
}
}
// TODO(ivanpe): Report modules with conflicting ranges. The list of such
// modules should be copied from |that|.
}
BasicCodeModules::BasicCodeModules() : main_address_(0), map_() { }
BasicCodeModules::~BasicCodeModules() {
}
unsigned int BasicCodeModules::module_count() const {
return map_.GetCount();
}
const CodeModule* BasicCodeModules::GetModuleForAddress(
uint64_t address) const {
linked_ptr<const CodeModule> module;
if (!map_.RetrieveRange(address, &module, NULL /* base */, NULL /* delta */,
NULL /* size */)) {
BPLOG(INFO) << "No module at " << HexString(address);
return NULL;
}
return module.get();
}
const CodeModule* BasicCodeModules::GetMainModule() const {
return GetModuleForAddress(main_address_);
}
const CodeModule* BasicCodeModules::GetModuleAtSequence(
unsigned int sequence) const {
linked_ptr<const CodeModule> module;
if (!map_.RetrieveRangeAtIndex(sequence, &module, NULL /* base */,
NULL /* delta */, NULL /* size */)) {
BPLOG(ERROR) << "RetrieveRangeAtIndex failed for sequence " << sequence;
return NULL;
}
return module.get();
}
const CodeModule* BasicCodeModules::GetModuleAtIndex(
unsigned int index) const {
// This class stores everything in a RangeMap, without any more-efficient
// way to walk the list of CodeModule objects. Implement GetModuleAtIndex
// using GetModuleAtSequence, which meets all of the requirements, and
// in addition, guarantees ordering.
return GetModuleAtSequence(index);
}
const CodeModules* BasicCodeModules::Copy() const {
return new BasicCodeModules(this, map_.GetMergeStrategy());
}
vector<linked_ptr<const CodeModule> >
BasicCodeModules::GetShrunkRangeModules() const {
return shrunk_range_modules_;
}
} // namespace google_breakpad

View file

@ -0,0 +1,96 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// basic_code_modules.h: Contains all of the CodeModule objects that
// were loaded into a single process.
//
// This is a basic concrete implementation of CodeModules. It cannot be
// instantiated directly, only based on other objects that implement
// the CodeModules interface. It exists to provide a CodeModules
// implementation a place to store information when the life of the original
// object (such as a MinidumpModuleList) cannot be guaranteed.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_BASIC_CODE_MODULES_H__
#define PROCESSOR_BASIC_CODE_MODULES_H__
#include <stddef.h>
#include <vector>
#include "google_breakpad/processor/code_modules.h"
#include "processor/linked_ptr.h"
#include "processor/range_map.h"
namespace google_breakpad {
class BasicCodeModules : public CodeModules {
public:
// Creates a new BasicCodeModules object given any existing CodeModules
// implementation. This is useful to make a copy of the data relevant to
// the CodeModules and CodeModule interfaces without requiring all of the
// resources that other implementations may require. A copy will be
// made of each contained CodeModule using CodeModule::Copy.
BasicCodeModules(const CodeModules *that, MergeRangeStrategy strategy);
virtual ~BasicCodeModules();
// See code_modules.h for descriptions of these methods.
virtual unsigned int module_count() const;
virtual const CodeModule* GetModuleForAddress(uint64_t address) const;
virtual const CodeModule* GetMainModule() const;
virtual const CodeModule* GetModuleAtSequence(unsigned int sequence) const;
virtual const CodeModule* GetModuleAtIndex(unsigned int index) const;
virtual const CodeModules* Copy() const;
virtual std::vector<linked_ptr<const CodeModule> >
GetShrunkRangeModules() const;
protected:
BasicCodeModules();
// The base address of the main module.
uint64_t main_address_;
// The map used to contain each CodeModule, keyed by each CodeModule's
// address range.
RangeMap<uint64_t, linked_ptr<const CodeModule> > map_;
// A vector of all CodeModules that were shrunk downs due to
// address range conflicts.
std::vector<linked_ptr<const CodeModule> > shrunk_range_modules_;
private:
// Disallow copy constructor and assignment operator.
BasicCodeModules(const BasicCodeModules& that);
void operator=(const BasicCodeModules& that);
};
} // namespace google_breakpad
#endif // PROCESSOR_BASIC_CODE_MODULES_H__

View file

@ -0,0 +1,930 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// basic_source_line_resolver.cc: BasicSourceLineResolver implementation.
//
// See basic_source_line_resolver.h and basic_source_line_resolver_types.h
// for documentation.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits>
#include <map>
#include <memory>
#include <utility>
#include <vector>
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "processor/basic_source_line_resolver_types.h"
#include "processor/module_factory.h"
#include "processor/tokenize.h"
using std::deque;
using std::make_pair;
using std::map;
using std::unique_ptr;
using std::vector;
namespace google_breakpad {
#ifdef _WIN32
#ifdef _MSC_VER
#define strtok_r strtok_s
#endif
#define strtoull _strtoui64
#endif
namespace {
// Utility function to tokenize given the presence of an optional initial
// field. In this case, optional_field is the expected string for the optional
// field, and max_tokens is the maximum number of tokens including the optional
// field. Refer to the documentation for Tokenize for descriptions of the other
// arguments.
bool TokenizeWithOptionalField(char* line,
const char* optional_field,
const char* separators,
int max_tokens,
vector<char*>* tokens) {
// First tokenize assuming the optional field is not present. If we then see
// the optional field, additionally tokenize the last token into two tokens.
if (!Tokenize(line, separators, max_tokens - 1, tokens)) {
return false;
}
if (strcmp(tokens->front(), optional_field) == 0) {
// The optional field is present. Split the last token in two to recover the
// field prior to the last.
vector<char*> last_tokens;
if (!Tokenize(tokens->back(), separators, 2, &last_tokens)) {
return false;
}
// Replace the previous last token with the two new tokens.
tokens->pop_back();
tokens->push_back(last_tokens[0]);
tokens->push_back(last_tokens[1]);
}
return true;
}
} // namespace
static const char* kWhitespace = " \r\n";
static const int kMaxErrorsPrinted = 5;
static const int kMaxErrorsBeforeBailing = 100;
BasicSourceLineResolver::BasicSourceLineResolver() :
SourceLineResolverBase(new BasicModuleFactory) { }
// static
void BasicSourceLineResolver::Module::LogParseError(
const string& message,
int line_number,
int* num_errors) {
if (++(*num_errors) <= kMaxErrorsPrinted) {
if (line_number > 0) {
BPLOG(ERROR) << "Line " << line_number << ": " << message;
} else {
BPLOG(ERROR) << message;
}
}
}
bool BasicSourceLineResolver::Module::LoadMapFromMemory(
char* memory_buffer,
size_t memory_buffer_size) {
linked_ptr<Function> cur_func;
int line_number = 0;
int num_errors = 0;
int inline_num_errors = 0;
char* save_ptr;
// If the length is 0, we can still pretend we have a symbol file. This is
// for scenarios that want to test symbol lookup, but don't necessarily care
// if certain modules do not have any information, like system libraries.
if (memory_buffer_size == 0) {
return true;
}
// Make sure the last character is null terminator.
size_t last_null_terminator = memory_buffer_size - 1;
if (memory_buffer[last_null_terminator] != '\0') {
memory_buffer[last_null_terminator] = '\0';
}
// Skip any null terminators at the end of the memory buffer, and make sure
// there are no other null terminators in the middle of the memory buffer.
bool has_null_terminator_in_the_middle = false;
while (last_null_terminator > 0 &&
memory_buffer[last_null_terminator - 1] == '\0') {
last_null_terminator--;
}
for (size_t i = 0; i < last_null_terminator; i++) {
if (memory_buffer[i] == '\0') {
memory_buffer[i] = '_';
has_null_terminator_in_the_middle = true;
}
}
if (has_null_terminator_in_the_middle) {
LogParseError(
"Null terminator is not expected in the middle of the symbol data",
line_number,
&num_errors);
}
char* buffer;
buffer = strtok_r(memory_buffer, "\r\n", &save_ptr);
while (buffer != NULL) {
++line_number;
if (strncmp(buffer, "FILE ", 5) == 0) {
if (!ParseFile(buffer)) {
LogParseError("ParseFile on buffer failed", line_number, &num_errors);
}
} else if (strncmp(buffer, "STACK ", 6) == 0) {
if (!ParseStackInfo(buffer)) {
LogParseError("ParseStackInfo failed", line_number, &num_errors);
}
} else if (strncmp(buffer, "FUNC ", 5) == 0) {
cur_func.reset(ParseFunction(buffer));
if (!cur_func.get()) {
LogParseError("ParseFunction failed", line_number, &num_errors);
} else {
// StoreRange will fail if the function has an invalid address or size.
// We'll silently ignore this, the function and any corresponding lines
// will be destroyed when cur_func is released.
functions_.StoreRange(cur_func->address, cur_func->size, cur_func);
}
} else if (strncmp(buffer, "PUBLIC ", 7) == 0) {
// Clear cur_func: public symbols don't contain line number information.
cur_func.reset();
if (!ParsePublicSymbol(buffer)) {
LogParseError("ParsePublicSymbol failed", line_number, &num_errors);
}
} else if (strncmp(buffer, "MODULE ", 7) == 0) {
// Ignore these. They're not of any use to BasicSourceLineResolver,
// which is fed modules by a SymbolSupplier. These lines are present to
// aid other tools in properly placing symbol files so that they can
// be accessed by a SymbolSupplier.
//
// MODULE <guid> <age> <filename>
} else if (strncmp(buffer, "INFO ", 5) == 0) {
// Ignore these as well, they're similarly just for housekeeping.
//
// INFO CODE_ID <code id> <filename>
} else if (strncmp(buffer, "INLINE ", 7) == 0) {
linked_ptr<Inline> in = ParseInline(buffer);
if (!in.get())
LogParseError("ParseInline failed", line_number, &inline_num_errors);
else
cur_func->AppendInline(in);
} else if (strncmp(buffer, "INLINE_ORIGIN ", 14) == 0) {
if (!ParseInlineOrigin(buffer)) {
LogParseError("ParseInlineOrigin failed", line_number,
&inline_num_errors);
}
} else {
if (!cur_func.get()) {
LogParseError("Found source line data without a function",
line_number, &num_errors);
} else {
Line* line = ParseLine(buffer);
if (!line) {
LogParseError("ParseLine failed", line_number, &num_errors);
} else {
cur_func->lines.StoreRange(line->address, line->size,
linked_ptr<Line>(line));
}
}
}
if (num_errors > kMaxErrorsBeforeBailing) {
break;
}
buffer = strtok_r(NULL, "\r\n", &save_ptr);
}
is_corrupt_ = num_errors > 0;
return true;
}
void BasicSourceLineResolver::Module::ConstructInlineFrames(
StackFrame* frame,
MemAddr address,
const ContainedRangeMap<uint64_t, linked_ptr<Inline>>& inline_map,
deque<unique_ptr<StackFrame>>* inlined_frames) const {
vector<const linked_ptr<Inline>*> inlines;
if (!inline_map.RetrieveRanges(address, inlines)) {
return;
}
for (const linked_ptr<Inline>* const in : inlines) {
unique_ptr<StackFrame> new_frame =
unique_ptr<StackFrame>(new StackFrame(*frame));
auto origin = inline_origins_.find(in->get()->origin_id);
if (origin != inline_origins_.end()) {
new_frame->function_name = origin->second->name;
} else {
new_frame->function_name = "<name omitted>";
}
// Store call site file and line in current frame, which will be updated
// later.
new_frame->source_line = in->get()->call_site_line;
if (in->get()->has_call_site_file_id) {
auto file = files_.find(in->get()->call_site_file_id);
if (file != files_.end()) {
new_frame->source_file_name = file->second;
}
}
// Use the starting address of the inlined range as inlined function base.
new_frame->function_base = new_frame->module->base_address();
for (const auto& range : in->get()->inline_ranges) {
if (address >= range.first && address < range.first + range.second) {
new_frame->function_base += range.first;
break;
}
}
new_frame->trust = StackFrame::FRAME_TRUST_INLINE;
// The inlines vector has an order from innermost entry to outermost entry.
// By push_back, we will have inlined_frames with the same order.
inlined_frames->push_back(std::move(new_frame));
}
// Update the source file and source line for each inlined frame.
if (!inlined_frames->empty()) {
string parent_frame_source_file_name = frame->source_file_name;
int parent_frame_source_line = frame->source_line;
frame->source_file_name = inlined_frames->back()->source_file_name;
frame->source_line = inlined_frames->back()->source_line;
for (unique_ptr<StackFrame>& inlined_frame : *inlined_frames) {
std::swap(inlined_frame->source_file_name, parent_frame_source_file_name);
std::swap(inlined_frame->source_line, parent_frame_source_line);
}
}
}
void BasicSourceLineResolver::Module::LookupAddress(
StackFrame* frame,
deque<unique_ptr<StackFrame>>* inlined_frames) const {
MemAddr address = frame->instruction - frame->module->base_address();
// First, look for a FUNC record that covers address. Use
// RetrieveNearestRange instead of RetrieveRange so that, if there
// is no such function, we can use the next function to bound the
// extent of the PUBLIC symbol we find, below. This does mean we
// need to check that address indeed falls within the function we
// find; do the range comparison in an overflow-friendly way.
linked_ptr<Function> func;
linked_ptr<PublicSymbol> public_symbol;
MemAddr function_base;
MemAddr function_size;
MemAddr public_address;
if (functions_.RetrieveNearestRange(address, &func, &function_base,
NULL /* delta */, &function_size) &&
address >= function_base && address - function_base < function_size) {
frame->function_name = func->name;
frame->function_base = frame->module->base_address() + function_base;
frame->is_multiple = func->is_multiple;
linked_ptr<Line> line;
MemAddr line_base;
if (func->lines.RetrieveRange(address, &line, &line_base, NULL /* delta */,
NULL /* size */)) {
FileMap::const_iterator it = files_.find(line->source_file_id);
if (it != files_.end()) {
frame->source_file_name = files_.find(line->source_file_id)->second;
}
frame->source_line = line->line;
frame->source_line_base = frame->module->base_address() + line_base;
}
// Check if this is inlined function call.
if (inlined_frames) {
ConstructInlineFrames(frame, address, func->inlines, inlined_frames);
}
} else if (public_symbols_.Retrieve(address,
&public_symbol, &public_address) &&
(!func.get() || public_address > function_base)) {
frame->function_name = public_symbol->name;
frame->function_base = frame->module->base_address() + public_address;
frame->is_multiple = public_symbol->is_multiple;
}
}
WindowsFrameInfo* BasicSourceLineResolver::Module::FindWindowsFrameInfo(
const StackFrame* frame) const {
MemAddr address = frame->instruction - frame->module->base_address();
scoped_ptr<WindowsFrameInfo> result(new WindowsFrameInfo());
// 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_[WindowsFrameInfo::STACK_INFO_FRAME_DATA]
.RetrieveRange(address, &frame_info))
|| (windows_frame_info_[WindowsFrameInfo::STACK_INFO_FPO]
.RetrieveRange(address, &frame_info))) {
result->CopyFrom(*frame_info.get());
return result.release();
}
// Even without a relevant STACK line, many functions contain
// information about how much space their parameters consume on the
// stack. Use RetrieveNearestRange instead of RetrieveRange, so that
// we can use the function to bound the extent of the PUBLIC symbol,
// below. However, this does mean we need to check that ADDRESS
// falls within the retrieved function's range; do the range
// comparison in an overflow-friendly way.
linked_ptr<Function> function;
MemAddr function_base, function_size;
if (functions_.RetrieveNearestRange(address, &function, &function_base,
NULL /* delta */, &function_size) &&
address >= function_base && address - function_base < function_size) {
result->parameter_size = function->parameter_size;
result->valid |= WindowsFrameInfo::VALID_PARAMETER_SIZE;
return result.release();
}
// PUBLIC symbols might have a parameter size. Use the function we
// found above to limit the range the public symbol covers.
linked_ptr<PublicSymbol> public_symbol;
MemAddr public_address;
if (public_symbols_.Retrieve(address, &public_symbol, &public_address) &&
(!function.get() || public_address > function_base)) {
result->parameter_size = public_symbol->parameter_size;
}
return NULL;
}
CFIFrameInfo* BasicSourceLineResolver::Module::FindCFIFrameInfo(
const StackFrame* frame) const {
MemAddr address = frame->instruction - frame->module->base_address();
MemAddr initial_base, initial_size;
string initial_rules;
// Find the initial rule whose range covers this address. That
// provides an initial set of register recovery rules. Then, walk
// forward from the initial rule's starting address to frame's
// instruction address, applying delta rules.
if (!cfi_initial_rules_.RetrieveRange(address, &initial_rules, &initial_base,
NULL /* delta */, &initial_size)) {
return NULL;
}
// Create a frame info structure, and populate it with the rules from
// the STACK CFI INIT record.
scoped_ptr<CFIFrameInfo> rules(new CFIFrameInfo());
if (!ParseCFIRuleSet(initial_rules, rules.get()))
return NULL;
// Find the first delta rule that falls within the initial rule's range.
map<MemAddr, string>::const_iterator delta =
cfi_delta_rules_.lower_bound(initial_base);
// Apply delta rules up to and including the frame's address.
while (delta != cfi_delta_rules_.end() && delta->first <= address) {
ParseCFIRuleSet(delta->second, rules.get());
delta++;
}
return rules.release();
}
bool BasicSourceLineResolver::Module::ParseFile(char* file_line) {
long index;
char* filename;
if (SymbolParseHelper::ParseFile(file_line, &index, &filename)) {
files_.insert(make_pair(index, string(filename)));
return true;
}
return false;
}
bool BasicSourceLineResolver::Module::ParseInlineOrigin(
char* inline_origin_line) {
bool has_file_id;
long origin_id;
long source_file_id;
char* origin_name;
if (SymbolParseHelper::ParseInlineOrigin(inline_origin_line, &has_file_id,
&origin_id, &source_file_id,
&origin_name)) {
inline_origins_.insert(make_pair(
origin_id,
new InlineOrigin(has_file_id, source_file_id, origin_name)));
return true;
}
return false;
}
linked_ptr<BasicSourceLineResolver::Inline>
BasicSourceLineResolver::Module::ParseInline(char* inline_line) {
bool has_call_site_file_id;
long inline_nest_level;
long call_site_line;
long call_site_file_id;
long origin_id;
vector<std::pair<MemAddr, MemAddr>> ranges;
if (SymbolParseHelper::ParseInline(inline_line, &has_call_site_file_id,
&inline_nest_level, &call_site_line,
&call_site_file_id, &origin_id, &ranges)) {
return linked_ptr<Inline>(new Inline(has_call_site_file_id,
inline_nest_level, call_site_line,
call_site_file_id, origin_id, ranges));
}
return linked_ptr<Inline>();
}
BasicSourceLineResolver::Function*
BasicSourceLineResolver::Module::ParseFunction(char* function_line) {
bool is_multiple;
uint64_t address;
uint64_t size;
long stack_param_size;
char* name;
if (SymbolParseHelper::ParseFunction(function_line, &is_multiple, &address,
&size, &stack_param_size, &name)) {
return new Function(name, address, size, stack_param_size, is_multiple);
}
return NULL;
}
BasicSourceLineResolver::Line* BasicSourceLineResolver::Module::ParseLine(
char* line_line) {
uint64_t address;
uint64_t size;
long line_number;
long source_file;
if (SymbolParseHelper::ParseLine(line_line, &address, &size, &line_number,
&source_file)) {
return new Line(address, size, source_file, line_number);
}
return NULL;
}
bool BasicSourceLineResolver::Module::ParsePublicSymbol(char* public_line) {
bool is_multiple;
uint64_t address;
long stack_param_size;
char* name;
if (SymbolParseHelper::ParsePublicSymbol(public_line, &is_multiple, &address,
&stack_param_size, &name)) {
// A few public symbols show up with an address of 0. This has been seen
// in the dumped output of ntdll.pdb for symbols such as _CIlog, _CIpow,
// RtlDescribeChunkLZNT1, and RtlReserveChunkLZNT1. They would conflict
// with one another if they were allowed into the public_symbols_ map,
// but since the address is obviously invalid, gracefully accept them
// as input without putting them into the map.
if (address == 0) {
return true;
}
linked_ptr<PublicSymbol> symbol(new PublicSymbol(name, address,
stack_param_size,
is_multiple));
return public_symbols_.Store(address, symbol);
}
return false;
}
bool BasicSourceLineResolver::Module::ParseStackInfo(char* stack_info_line) {
// Skip "STACK " prefix.
stack_info_line += 6;
// Find the token indicating what sort of stack frame walking
// information this is.
while (*stack_info_line == ' ')
stack_info_line++;
const char* platform = 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) {
int type = 0;
uint64_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);
} else {
// Something unrecognized.
return false;
}
}
bool BasicSourceLineResolver::Module::ParseCFIFrameInfo(
char* stack_info_line) {
char* cursor;
// Is this an INIT record or a delta record?
char* init_or_address = strtok_r(stack_info_line, " \r\n", &cursor);
if (!init_or_address)
return false;
if (strcmp(init_or_address, "INIT") == 0) {
// This record has the form "STACK INIT <address> <size> <rules...>".
char* address_field = strtok_r(NULL, " \r\n", &cursor);
if (!address_field) return false;
char* size_field = strtok_r(NULL, " \r\n", &cursor);
if (!size_field) return false;
char* initial_rules = strtok_r(NULL, "\r\n", &cursor);
if (!initial_rules) return false;
MemAddr address = strtoul(address_field, NULL, 16);
MemAddr size = strtoul(size_field, NULL, 16);
cfi_initial_rules_.StoreRange(address, size, initial_rules);
return true;
}
// This record has the form "STACK <address> <rules...>".
char* address_field = init_or_address;
char* delta_rules = strtok_r(NULL, "\r\n", &cursor);
if (!delta_rules) return false;
MemAddr address = strtoul(address_field, NULL, 16);
cfi_delta_rules_[address] = delta_rules;
return true;
}
bool BasicSourceLineResolver::Function::AppendInline(linked_ptr<Inline> in) {
// This happends if in's parent wasn't added due to a malformed INLINE record.
if (in->inline_nest_level > last_added_inline_nest_level + 1)
return false;
last_added_inline_nest_level = in->inline_nest_level;
// Store all ranges into current level of inlines.
for (auto range : in->inline_ranges)
inlines.StoreRange(range.first, range.second, in);
return true;
}
// static
bool SymbolParseHelper::ParseFile(char* file_line, long* index,
char** filename) {
// FILE <id> <filename>
assert(strncmp(file_line, "FILE ", 5) == 0);
file_line += 5; // skip prefix
vector<char*> tokens;
if (!Tokenize(file_line, kWhitespace, 2, &tokens)) {
return false;
}
char* after_number;
*index = strtol(tokens[0], &after_number, 10);
if (!IsValidAfterNumber(after_number) || *index < 0 ||
*index == std::numeric_limits<long>::max()) {
return false;
}
*filename = tokens[1];
if (!*filename) {
return false;
}
return true;
}
// static
bool SymbolParseHelper::ParseInlineOrigin(char* inline_origin_line,
bool* has_file_id,
long* origin_id,
long* file_id,
char** name) {
// Old INLINE_ORIGIN format:
// INLINE_ORIGIN <origin_id> <file_id> <name>
// New INLINE_ORIGIN format:
// INLINE_ORIGIN <origin_id> <name>
assert(strncmp(inline_origin_line, "INLINE_ORIGIN ", 14) == 0);
inline_origin_line += 14; // skip prefix
vector<char*> tokens;
// Split the line into two parts so that the first token is "<origin_id>", and
// second token is either "<file_id> <name>"" or "<name>"" depending on the
// format version.
if (!Tokenize(inline_origin_line, kWhitespace, 2, &tokens)) {
return false;
}
char* after_number;
*origin_id = strtol(tokens[0], &after_number, 10);
if (!IsValidAfterNumber(after_number) || *origin_id < 0 ||
*origin_id == std::numeric_limits<long>::max()) {
return false;
}
// If the field after origin_id is a number, then it's old format.
char* remaining_line = tokens[1];
*has_file_id = true;
for (size_t i = 0;
i < strlen(remaining_line) && remaining_line[i] != ' ' && *has_file_id;
++i) {
// If the file id is -1, it might be an artificial function that doesn't
// have file id. So, we consider -1 as a valid special case.
if (remaining_line[i] == '-' && i == 0) {
continue;
}
*has_file_id = isdigit(remaining_line[i]);
}
if (*has_file_id) {
// If it's old format, split "<file_id> <name>" to {"<field_id>", "<name>"}.
if (!Tokenize(remaining_line, kWhitespace, 2, &tokens)) {
return false;
}
*file_id = strtol(tokens[0], &after_number, 10);
// If the file id is -1, it might be an artificial function that doesn't
// have file id. So, we consider -1 as a valid special case.
if (!IsValidAfterNumber(after_number) || *file_id < -1 ||
*file_id == std::numeric_limits<long>::max()) {
return false;
}
}
*name = tokens[1];
if (!*name) {
return false;
}
return true;
}
// static
bool SymbolParseHelper::ParseInline(
char* inline_line,
bool* has_call_site_file_id,
long* inline_nest_level,
long* call_site_line,
long* call_site_file_id,
long* origin_id,
vector<std::pair<MemAddr, MemAddr>>* ranges) {
// Old INLINE format:
// INLINE <inline_nest_level> <call_site_line> <origin_id> [<address> <size>]+
// New INLINE format:
// INLINE <inline_nest_level> <call_site_line> <call_site_file_id> <origin_id>
// [<address> <size>]+
assert(strncmp(inline_line, "INLINE ", 7) == 0);
inline_line += 7; // skip prefix
vector<char*> tokens;
// Increase max_tokens if necessary.
Tokenize(inline_line, kWhitespace, 512, &tokens);
// Determine the version of INLINE record by parity of the vector length.
*has_call_site_file_id = tokens.size() % 2 == 0;
// The length of the vector should be at least 5.
if (tokens.size() < 5) {
return false;
}
char* after_number;
size_t next_idx = 0;
*inline_nest_level = strtol(tokens[next_idx++], &after_number, 10);
if (!IsValidAfterNumber(after_number) || *inline_nest_level < 0 ||
*inline_nest_level == std::numeric_limits<long>::max()) {
return false;
}
*call_site_line = strtol(tokens[next_idx++], &after_number, 10);
if (!IsValidAfterNumber(after_number) || *call_site_line < 0 ||
*call_site_line == std::numeric_limits<long>::max()) {
return false;
}
if (*has_call_site_file_id) {
*call_site_file_id = strtol(tokens[next_idx++], &after_number, 10);
// If the file id is -1, it might be an artificial function that doesn't
// have file id. So, we consider -1 as a valid special case.
if (!IsValidAfterNumber(after_number) || *call_site_file_id < -1 ||
*call_site_file_id == std::numeric_limits<long>::max()) {
return false;
}
}
*origin_id = strtol(tokens[next_idx++], &after_number, 10);
if (!IsValidAfterNumber(after_number) || *origin_id < 0 ||
*origin_id == std::numeric_limits<long>::max()) {
return false;
}
while (next_idx < tokens.size()) {
MemAddr address = strtoull(tokens[next_idx++], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
address == std::numeric_limits<unsigned long long>::max()) {
return false;
}
MemAddr size = strtoull(tokens[next_idx++], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
size == std::numeric_limits<unsigned long long>::max()) {
return false;
}
ranges->push_back({address, size});
}
return true;
}
// static
bool SymbolParseHelper::ParseFunction(char* function_line, bool* is_multiple,
uint64_t* address, uint64_t* size,
long* stack_param_size, char** name) {
// FUNC [<multiple>] <address> <size> <stack_param_size> <name>
assert(strncmp(function_line, "FUNC ", 5) == 0);
function_line += 5; // skip prefix
vector<char*> tokens;
if (!TokenizeWithOptionalField(function_line, "m", kWhitespace, 5, &tokens)) {
return false;
}
*is_multiple = strcmp(tokens[0], "m") == 0;
int next_token = *is_multiple ? 1 : 0;
char* after_number;
*address = strtoull(tokens[next_token++], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
*address == std::numeric_limits<unsigned long long>::max()) {
return false;
}
*size = strtoull(tokens[next_token++], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
*size == std::numeric_limits<unsigned long long>::max()) {
return false;
}
*stack_param_size = strtol(tokens[next_token++], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
*stack_param_size == std::numeric_limits<long>::max() ||
*stack_param_size < 0) {
return false;
}
*name = tokens[next_token++];
return true;
}
// static
bool SymbolParseHelper::ParseLine(char* line_line, uint64_t* address,
uint64_t* size, long* line_number,
long* source_file) {
// <address> <size> <line number> <source file id>
vector<char*> tokens;
if (!Tokenize(line_line, kWhitespace, 4, &tokens)) {
return false;
}
char* after_number;
*address = strtoull(tokens[0], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
*address == std::numeric_limits<unsigned long long>::max()) {
return false;
}
*size = strtoull(tokens[1], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
*size == std::numeric_limits<unsigned long long>::max()) {
return false;
}
*line_number = strtol(tokens[2], &after_number, 10);
if (!IsValidAfterNumber(after_number) ||
*line_number == std::numeric_limits<long>::max()) {
return false;
}
*source_file = strtol(tokens[3], &after_number, 10);
if (!IsValidAfterNumber(after_number) || *source_file < 0 ||
*source_file == std::numeric_limits<long>::max()) {
return false;
}
// Valid line numbers normally start from 1, however there are functions that
// are associated with a source file but not associated with any line number
// (block helper function) and for such functions the symbol file contains 0
// for the line numbers. Hence, 0 should be treated as a valid line number.
// For more information on block helper functions, please, take a look at:
// http://clang.llvm.org/docs/Block-ABI-Apple.html
if (*line_number < 0) {
return false;
}
return true;
}
// static
bool SymbolParseHelper::ParsePublicSymbol(char* public_line, bool* is_multiple,
uint64_t* address,
long* stack_param_size,
char** name) {
// PUBLIC [<multiple>] <address> <stack_param_size> <name>
assert(strncmp(public_line, "PUBLIC ", 7) == 0);
public_line += 7; // skip prefix
vector<char*> tokens;
if (!TokenizeWithOptionalField(public_line, "m", kWhitespace, 4, &tokens)) {
return false;
}
*is_multiple = strcmp(tokens[0], "m") == 0;
int next_token = *is_multiple ? 1 : 0;
char* after_number;
*address = strtoull(tokens[next_token++], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
*address == std::numeric_limits<unsigned long long>::max()) {
return false;
}
*stack_param_size = strtol(tokens[next_token++], &after_number, 16);
if (!IsValidAfterNumber(after_number) ||
*stack_param_size == std::numeric_limits<long>::max() ||
*stack_param_size < 0) {
return false;
}
*name = tokens[next_token++];
return true;
}
// static
bool SymbolParseHelper::IsValidAfterNumber(char* after_number) {
if (after_number != NULL && strchr(kWhitespace, *after_number) != NULL) {
return true;
}
return false;
}
} // namespace google_breakpad

View file

@ -0,0 +1,209 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// basic_source_line_types.h: definition of nested classes/structs in
// BasicSourceLineResolver. It moves the definitions out of
// basic_source_line_resolver.cc, so that other classes could have access
// to these private nested types without including basic_source_line_resolver.cc
//
// Author: Siyang Xie (lambxsy@google.com)
#ifndef PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_TYPES_H__
#define PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_TYPES_H__
#include <map>
#include <string>
#include "common/scoped_ptr.h"
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "processor/source_line_resolver_base_types.h"
#include "processor/address_map-inl.h"
#include "processor/range_map-inl.h"
#include "processor/contained_range_map-inl.h"
#include "processor/linked_ptr.h"
#include "google_breakpad/processor/stack_frame.h"
#include "processor/cfi_frame_info.h"
#include "processor/windows_frame_info.h"
namespace google_breakpad {
struct
BasicSourceLineResolver::Function : public SourceLineResolverBase::Function {
Function(const string& function_name,
MemAddr function_address,
MemAddr code_size,
int set_parameter_size,
bool is_mutiple)
: Base(function_name,
function_address,
code_size,
set_parameter_size,
is_mutiple),
inlines(true),
last_added_inline_nest_level(0) {}
// Append inline into corresponding RangeMap.
// This function assumes it's called in the order of reading INLINE records.
bool AppendInline(linked_ptr<Inline> in);
ContainedRangeMap<MemAddr, linked_ptr<Inline>> inlines;
RangeMap<MemAddr, linked_ptr<Line>> lines;
private:
typedef SourceLineResolverBase::Function Base;
// The last added inline_nest_level from INLINE record.
int last_added_inline_nest_level;
};
class BasicSourceLineResolver::Module : public SourceLineResolverBase::Module {
public:
explicit Module(const string& name) : name_(name), is_corrupt_(false) { }
virtual ~Module() { }
// Loads a map from the given buffer in char* type.
// Does NOT have ownership of memory_buffer.
// The passed in |memory buffer| is of size |memory_buffer_size|. If it is
// not null terminated, LoadMapFromMemory() will null terminate it by
// modifying the passed in buffer.
virtual bool LoadMapFromMemory(char* memory_buffer,
size_t memory_buffer_size);
// Tells whether the loaded symbol data is corrupt. Return value is
// undefined, if the symbol data hasn't been loaded yet.
virtual bool IsCorrupt() const { return is_corrupt_; }
// Looks up the given relative address, and fills the StackFrame struct
// with the result.
virtual void LookupAddress(
StackFrame* frame,
std::deque<std::unique_ptr<StackFrame>>* inlined_frame) const;
// Construct inlined frames for |frame| and store them in |inline_frames|.
// |frame|'s source line and source file name may be updated if an inlined
// frame is found inside |frame|. As a result, the innermost inlined frame
// will be the first one in |inline_frames|.
virtual void ConstructInlineFrames(
StackFrame* frame,
MemAddr address,
const ContainedRangeMap<uint64_t, linked_ptr<Inline>>& inline_map,
std::deque<std::unique_ptr<StackFrame>>* inline_frames) const;
// If Windows stack walking information is available covering 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;
// 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;
private:
// Friend declarations.
friend class BasicSourceLineResolver;
friend class ModuleComparer;
friend class ModuleSerializer;
typedef std::map<int, string> FileMap;
// Logs parse errors. |*num_errors| is increased every time LogParseError is
// called.
static void LogParseError(
const string& message,
int line_number,
int* num_errors);
// Parses a file declaration
bool ParseFile(char* file_line);
// Parses an inline origin declaration.
bool ParseInlineOrigin(char* inline_origin_line);
// Parses an inline declaration.
linked_ptr<Inline> ParseInline(char* inline_line);
// Parses a function declaration, returning a new Function object.
Function* ParseFunction(char* function_line);
// Parses a line declaration, returning a new Line object.
Line* ParseLine(char* line_line);
// Parses a PUBLIC symbol declaration, storing it in public_symbols_.
// Returns false if an error occurs.
bool ParsePublicSymbol(char* public_line);
// Parses a STACK WIN or STACK CFI frame info declaration, storing
// it in the appropriate table.
bool ParseStackInfo(char* stack_info_line);
// Parses a STACK CFI record, storing it in cfi_frame_info_.
bool ParseCFIFrameInfo(char* stack_info_line);
string name_;
FileMap files_;
std::map<int, linked_ptr<InlineOrigin>> inline_origins_;
RangeMap< MemAddr, linked_ptr<Function> > functions_;
AddressMap< MemAddr, linked_ptr<PublicSymbol> > public_symbols_;
bool is_corrupt_;
// Each element in the array is a ContainedRangeMap for a type
// listed in WindowsFrameInfoTypes. These are split by type because
// 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_[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:
// although the file may contain hundreds of thousands of STACK CFI
// records, walking a stack will only ever use a few of them, so it's
// best to delay parsing a record until it's actually needed.
// STACK CFI INIT records: for each range, an initial set of register
// recovery rules. The RangeMap's itself gives the starting and ending
// addresses.
RangeMap<MemAddr, string> cfi_initial_rules_;
// STACK CFI records: at a given address, the changes to the register
// recovery rules that take effect at that address. The map key is the
// starting address; the ending address is the key of the next entry in
// this map, or the end of the range as given by the cfi_initial_rules_
// entry (which FindCFIFrameInfo looks up first).
std::map<MemAddr, string> cfi_delta_rules_;
};
} // namespace google_breakpad
#endif // PROCESSOR_BASIC_SOURCE_LINE_RESOLVER_TYPES_H__

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,57 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// call_stack.cc: A call stack comprised of stack frames.
//
// See call_stack.h for documentation.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/stack_frame.h"
namespace google_breakpad {
CallStack::~CallStack() {
Clear();
}
void CallStack::Clear() {
for (vector<StackFrame*>::const_iterator iterator = frames_.begin();
iterator != frames_.end();
++iterator) {
delete *iterator;
}
tid_ = 0;
}
} // namespace google_breakpad

View file

@ -0,0 +1,118 @@
// -*- mode: C++ -*-
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
// cfi_frame_info-inl.h: Definitions for cfi_frame_info.h inlined functions.
#ifndef PROCESSOR_CFI_FRAME_INFO_INL_H_
#define PROCESSOR_CFI_FRAME_INFO_INL_H_
#include <string.h>
namespace google_breakpad {
template <typename RegisterType, class RawContextType>
bool SimpleCFIWalker<RegisterType, RawContextType>::FindCallerRegisters(
const MemoryRegion& memory,
const CFIFrameInfo& cfi_frame_info,
const RawContextType& callee_context,
int callee_validity,
RawContextType* caller_context,
int* caller_validity) const {
typedef CFIFrameInfo::RegisterValueMap<RegisterType> ValueMap;
ValueMap callee_registers;
ValueMap caller_registers;
// Just for brevity.
typename ValueMap::const_iterator caller_none = caller_registers.end();
// Populate callee_registers with register values from callee_context.
for (size_t i = 0; i < map_size_; i++) {
const RegisterSet& r = register_map_[i];
if (callee_validity & r.validity_flag)
callee_registers[r.name] = callee_context.*r.context_member;
}
// Apply the rules, and see what register values they yield.
if (!cfi_frame_info.FindCallerRegs<RegisterType>(callee_registers, memory,
&caller_registers))
return false;
// Populate *caller_context with the values the rules placed in
// caller_registers.
memset(caller_context, 0xda, sizeof(*caller_context));
*caller_validity = 0;
for (size_t i = 0; i < map_size_; i++) {
const RegisterSet& r = register_map_[i];
typename ValueMap::const_iterator caller_entry;
// Did the rules provide a value for this register by its name?
caller_entry = caller_registers.find(r.name);
if (caller_entry != caller_none) {
caller_context->*r.context_member = caller_entry->second;
*caller_validity |= r.validity_flag;
continue;
}
// Did the rules provide a value for this register under its
// alternate name?
if (r.alternate_name) {
caller_entry = caller_registers.find(r.alternate_name);
if (caller_entry != caller_none) {
caller_context->*r.context_member = caller_entry->second;
*caller_validity |= r.validity_flag;
continue;
}
}
// Is this a callee-saves register? The walker assumes that these
// still hold the caller's value if the CFI doesn't mention them.
//
// Note that other frame walkers may fail to recover callee-saves
// registers; for example, the x86 "traditional" strategy only
// recovers %eip, %esp, and %ebp, even though %ebx, %esi, and %edi
// are callee-saves, too. It is not correct to blindly set the
// valid bit for all callee-saves registers, without first
// checking its validity bit in the callee.
if (r.callee_saves && (callee_validity & r.validity_flag) != 0) {
caller_context->*r.context_member = callee_context.*r.context_member;
*caller_validity |= r.validity_flag;
continue;
}
// Otherwise, the register's value is unknown.
}
return true;
}
} // namespace google_breakpad
#endif // PROCESSOR_CFI_FRAME_INFO_INL_H_

View file

@ -0,0 +1,189 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
// cfi_frame_info.cc: Implementation of CFIFrameInfo class.
// See cfi_frame_info.h for details.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "processor/cfi_frame_info.h"
#include <string.h>
#include <sstream>
#include "common/scoped_ptr.h"
#include "processor/postfix_evaluator-inl.h"
namespace google_breakpad {
#ifdef _MSC_VER
#define strtok_r strtok_s
#endif
template<typename V>
bool CFIFrameInfo::FindCallerRegs(const RegisterValueMap<V>& registers,
const MemoryRegion& memory,
RegisterValueMap<V>* caller_registers) const {
// If there are not rules for both .ra and .cfa in effect at this address,
// don't use this CFI data for stack walking.
if (cfa_rule_.empty() || ra_rule_.empty())
return false;
RegisterValueMap<V> working;
PostfixEvaluator<V> evaluator(&working, &memory);
caller_registers->clear();
// First, compute the CFA.
V cfa;
working = registers;
if (!evaluator.EvaluateForValue(cfa_rule_, &cfa))
return false;
// Then, compute the return address.
V ra;
working = registers;
working[".cfa"] = cfa;
if (!evaluator.EvaluateForValue(ra_rule_, &ra))
return false;
// Now, compute values for all the registers register_rules_ mentions.
for (RuleMap::const_iterator it = register_rules_.begin();
it != register_rules_.end(); it++) {
V value;
working = registers;
working[".cfa"] = cfa;
if (!evaluator.EvaluateForValue(it->second, &value))
continue;
(*caller_registers)[it->first] = value;
}
(*caller_registers)[".ra"] = ra;
(*caller_registers)[".cfa"] = cfa;
return true;
}
// Explicit instantiations for 32-bit and 64-bit architectures.
template bool CFIFrameInfo::FindCallerRegs<uint32_t>(
const RegisterValueMap<uint32_t>& registers,
const MemoryRegion& memory,
RegisterValueMap<uint32_t>* caller_registers) const;
template bool CFIFrameInfo::FindCallerRegs<uint64_t>(
const RegisterValueMap<uint64_t>& registers,
const MemoryRegion& memory,
RegisterValueMap<uint64_t>* caller_registers) const;
string CFIFrameInfo::Serialize() const {
std::ostringstream stream;
if (!cfa_rule_.empty()) {
stream << ".cfa: " << cfa_rule_;
}
if (!ra_rule_.empty()) {
if (static_cast<std::streamoff>(stream.tellp()) != 0)
stream << " ";
stream << ".ra: " << ra_rule_;
}
for (RuleMap::const_iterator iter = register_rules_.begin();
iter != register_rules_.end();
++iter) {
if (static_cast<std::streamoff>(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]);
memcpy(working_copy.get(), rule_set.data(), rule_set_len);
working_copy[rule_set_len] = '\0';
name_.clear();
expression_.clear();
char* cursor;
static const char token_breaks[] = " \t\r\n";
char* token = strtok_r(working_copy.get(), token_breaks, &cursor);
for (;;) {
// End of rule set?
if (!token) return Report();
// Register/pseudoregister name?
size_t token_len = strlen(token);
if (token_len >= 1 && token[token_len - 1] == ':') {
// Names can't be empty.
if (token_len < 2) return false;
// If there is any pending content, report it.
if (!name_.empty() || !expression_.empty()) {
if (!Report()) return false;
}
name_.assign(token, token_len - 1);
expression_.clear();
} else {
// Another expression component.
assert(token_len > 0); // strtok_r guarantees this, I think.
if (!expression_.empty())
expression_ += ' ';
expression_ += token;
}
token = strtok_r(NULL, token_breaks, &cursor);
}
}
bool CFIRuleParser::Report() {
if (name_.empty() || expression_.empty()) return false;
if (name_ == ".cfa") handler_->CFARule(expression_);
else if (name_ == ".ra") handler_->RARule(expression_);
else handler_->RegisterRule(name_, expression_);
return true;
}
void CFIFrameInfoParseHandler::CFARule(const string& expression) {
frame_info_->SetCFARule(expression);
}
void CFIFrameInfoParseHandler::RARule(const string& expression) {
frame_info_->SetRARule(expression);
}
void CFIFrameInfoParseHandler::RegisterRule(const string& name,
const string& expression) {
frame_info_->SetRegisterRule(name, expression);
}
} // namespace google_breakpad

View file

@ -0,0 +1,274 @@
// -*- mode: C++ -*-
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
// cfi_frame_info.h: Define the CFIFrameInfo class, which holds the
// set of 'STACK CFI'-derived register recovery rules that apply at a
// given instruction.
#ifndef PROCESSOR_CFI_FRAME_INFO_H_
#define PROCESSOR_CFI_FRAME_INFO_H_
#include <map>
#include <string>
#include "common/using_std_string.h"
#include "google_breakpad/common/breakpad_types.h"
namespace google_breakpad {
using std::map;
class MemoryRegion;
// A set of rules for recovering the calling frame's registers'
// values, when the PC is at a given address in the current frame's
// function. See the description of 'STACK CFI' records at:
//
// https://chromium.googlesource.com/breakpad/breakpad/+/master/docs/symbol_files.md
//
// To prepare an instance of CFIFrameInfo for use at a given
// instruction, first populate it with the rules from the 'STACK CFI
// INIT' record that covers that instruction, and then apply the
// changes given by the 'STACK CFI' records up to our instruction's
// address. Then, use the FindCallerRegs member function to apply the
// rules to the callee frame's register values, yielding the caller
// frame's register values.
class CFIFrameInfo {
public:
// A map from register names onto values.
template<typename ValueType> class RegisterValueMap:
public map<string, ValueType> { };
// Set the expression for computing a call frame address, return
// address, or register's value. At least the CFA rule and the RA
// rule must be set before calling FindCallerRegs.
void SetCFARule(const string& expression) { cfa_rule_ = expression; }
void SetRARule(const string& expression) { ra_rule_ = expression; }
void SetRegisterRule(const string& register_name, const string& expression) {
register_rules_[register_name] = expression;
}
// Compute the values of the calling frame's registers, according to
// this rule set. Use ValueType in expression evaluation; this
// should be uint32_t on machines with 32-bit addresses, or
// uint64_t on machines with 64-bit addresses.
//
// Return true on success, false otherwise.
//
// MEMORY provides access to the contents of the stack. REGISTERS is
// a dictionary mapping the names of registers whose values are
// known in the current frame to their values. CALLER_REGISTERS is
// populated with the values of the recoverable registers in the
// frame that called the current frame.
//
// In addition, CALLER_REGISTERS[".ra"] will be the return address,
// and CALLER_REGISTERS[".cfa"] will be the call frame address.
// These may be helpful in computing the caller's PC and stack
// pointer, if their values are not explicitly specified.
template<typename ValueType>
bool FindCallerRegs(const RegisterValueMap<ValueType>& registers,
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.
typedef map<string, string> RuleMap;
// In this type, a "postfix expression" is an expression of the sort
// interpreted by google_breakpad::PostfixEvaluator.
// A postfix expression for computing the current frame's CFA (call
// frame address). The CFA is a reference address for the frame that
// remains unchanged throughout the frame's lifetime. You should
// evaluate this expression with a dictionary initially populated
// with the values of the current frame's known registers.
string cfa_rule_;
// The following expressions should be evaluated with a dictionary
// initially populated with the values of the current frame's known
// registers, and with ".cfa" set to the result of evaluating the
// cfa_rule expression, above.
// A postfix expression for computing the current frame's return
// address.
string ra_rule_;
// For a register named REG, rules[REG] is a postfix expression
// which leaves the value of REG in the calling frame on the top of
// the stack. You should evaluate this expression
RuleMap register_rules_;
};
// A parser for STACK CFI-style rule sets.
// This may seem bureaucratic: there's no legitimate run-time reason
// to use a parser/handler pattern for this, as it's not a likely
// reuse boundary. But doing so makes finer-grained unit testing
// possible.
class CFIRuleParser {
public:
class Handler {
public:
Handler() { }
virtual ~Handler() { }
// The input specifies EXPRESSION as the CFA/RA computation rule.
virtual void CFARule(const string& expression) = 0;
virtual void RARule(const string& expression) = 0;
// The input specifies EXPRESSION as the recovery rule for register NAME.
virtual void RegisterRule(const string& name, const string& expression) = 0;
};
// Construct a parser which feeds its results to HANDLER.
CFIRuleParser(Handler* handler) : handler_(handler) { }
// Parse RULE_SET as a set of CFA computation and RA/register
// recovery rules, as appearing in STACK CFI records. Report the
// results of parsing by making the appropriate calls to handler_.
// Return true if parsing was successful, false otherwise.
bool Parse(const string& rule_set);
private:
// Report any accumulated rule to handler_
bool Report();
// The handler to which the parser reports its findings.
Handler* handler_;
// Working data.
string name_, expression_;
};
// A handler for rule set parsing that populates a CFIFrameInfo with
// the results.
class CFIFrameInfoParseHandler: public CFIRuleParser::Handler {
public:
// Populate FRAME_INFO with the results of parsing.
CFIFrameInfoParseHandler(CFIFrameInfo* frame_info)
: frame_info_(frame_info) { }
void CFARule(const string& expression);
void RARule(const string& expression);
void RegisterRule(const string& name, const string& expression);
private:
CFIFrameInfo* frame_info_;
};
// A utility class template for simple 'STACK CFI'-driven stack walkers.
// Given a CFIFrameInfo instance, a table describing the architecture's
// register set, and a context holding the last frame's registers, an
// instance of this class can populate a new context with the caller's
// registers.
//
// This class template doesn't use any internal knowledge of CFIFrameInfo
// or the other stack walking structures; it just uses the public interface
// of CFIFrameInfo to do the usual things. But the logic it handles should
// be common to many different architectures' stack walkers, so wrapping it
// up in a class should allow the walkers to share code.
//
// RegisterType should be the type of this architecture's registers, either
// uint32_t or uint64_t. RawContextType should be the raw context
// structure type for this architecture.
template <typename RegisterType, class RawContextType>
class SimpleCFIWalker {
public:
// A structure describing one architecture register.
struct RegisterSet {
// The register name, as it appears in STACK CFI rules.
const char* name;
// An alternate name that the register's value might be found
// under in a register value dictionary, or NULL. When generating
// names, prefer NAME to this value. It's common to list ".cfa" as
// an alternative name for the stack pointer, and ".ra" as an
// alternative name for the instruction pointer.
const char* alternate_name;
// True if the callee is expected to preserve the value of this
// register. If this flag is true for some register R, and the STACK
// CFI records provide no rule to recover R, then SimpleCFIWalker
// assumes that the callee has not changed R's value, and the caller's
// value for R is that currently in the callee's context.
bool callee_saves;
// The ContextValidity flag representing the register's presence.
int validity_flag;
// A pointer to the RawContextType member that holds the
// register's value.
RegisterType RawContextType::*context_member;
};
// Create a simple CFI-based frame walker, given a description of the
// architecture's register set. REGISTER_MAP is an array of
// RegisterSet structures; MAP_SIZE is the number of elements in the
// array.
SimpleCFIWalker(const RegisterSet* register_map, size_t map_size)
: register_map_(register_map), map_size_(map_size) { }
// Compute the calling frame's raw context given the callee's raw
// context.
//
// Given:
//
// - MEMORY, holding the stack's contents,
// - CFI_FRAME_INFO, describing the called function,
// - CALLEE_CONTEXT, holding the called frame's registers, and
// - CALLEE_VALIDITY, indicating which registers in CALLEE_CONTEXT are valid,
//
// fill in CALLER_CONTEXT with the caller's register values, and set
// CALLER_VALIDITY to indicate which registers are valid in
// CALLER_CONTEXT. Return true on success, or false on failure.
bool FindCallerRegisters(const MemoryRegion& memory,
const CFIFrameInfo& cfi_frame_info,
const RawContextType& callee_context,
int callee_validity,
RawContextType* caller_context,
int* caller_validity) const;
private:
const RegisterSet* register_map_;
size_t map_size_;
};
} // namespace google_breakpad
#include "cfi_frame_info-inl.h"
#endif // PROCESSOR_CFI_FRAME_INFO_H_

View file

@ -0,0 +1,552 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
// cfi_frame_info_unittest.cc: Unit tests for CFIFrameInfo,
// CFIRuleParser, CFIFrameInfoParseHandler, and SimpleCFIWalker.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <string.h>
#include "breakpad_googletest_includes.h"
#include "common/using_std_string.h"
#include "processor/cfi_frame_info.h"
#include "google_breakpad/processor/memory_region.h"
using google_breakpad::CFIFrameInfo;
using google_breakpad::CFIFrameInfoParseHandler;
using google_breakpad::CFIRuleParser;
using google_breakpad::MemoryRegion;
using google_breakpad::SimpleCFIWalker;
using testing::_;
using testing::A;
using testing::AtMost;
using testing::DoAll;
using testing::Return;
using testing::SetArgumentPointee;
using testing::Test;
class MockMemoryRegion: public MemoryRegion {
public:
MOCK_CONST_METHOD0(GetBase, uint64_t());
MOCK_CONST_METHOD0(GetSize, uint32_t());
MOCK_CONST_METHOD2(GetMemoryAtAddress, bool(uint64_t, uint8_t*));
MOCK_CONST_METHOD2(GetMemoryAtAddress, bool(uint64_t, uint16_t*));
MOCK_CONST_METHOD2(GetMemoryAtAddress, bool(uint64_t, uint32_t*));
MOCK_CONST_METHOD2(GetMemoryAtAddress, bool(uint64_t, uint64_t*));
MOCK_CONST_METHOD0(Print, void());
};
// Handy definitions for all tests.
struct CFIFixture {
// Set up the mock memory object to expect no references.
void ExpectNoMemoryReferences() {
EXPECT_CALL(memory, GetBase()).Times(0);
EXPECT_CALL(memory, GetSize()).Times(0);
EXPECT_CALL(memory, GetMemoryAtAddress(_, A<uint8_t*>())).Times(0);
EXPECT_CALL(memory, GetMemoryAtAddress(_, A<uint16_t*>())).Times(0);
EXPECT_CALL(memory, GetMemoryAtAddress(_, A<uint32_t*>())).Times(0);
EXPECT_CALL(memory, GetMemoryAtAddress(_, A<uint64_t*>())).Times(0);
}
CFIFrameInfo cfi;
MockMemoryRegion memory;
CFIFrameInfo::RegisterValueMap<uint64_t> registers, caller_registers;
};
class Simple: public CFIFixture, public Test { };
// FindCallerRegs should fail if no .cfa rule is provided.
TEST_F(Simple, NoCFA) {
ExpectNoMemoryReferences();
cfi.SetRARule("0");
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(".ra: 0", cfi.Serialize());
}
// FindCallerRegs should fail if no .ra rule is provided.
TEST_F(Simple, NoRA) {
ExpectNoMemoryReferences();
cfi.SetCFARule("0");
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(".cfa: 0", cfi.Serialize());
}
TEST_F(Simple, SetCFAAndRARule) {
ExpectNoMemoryReferences();
cfi.SetCFARule("330903416631436410");
cfi.SetRARule("5870666104170902211");
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
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) {
ExpectNoMemoryReferences();
cfi.SetCFARule("$temp1 68737028 = $temp2 61072337 = $temp1 $temp2 -");
cfi.SetRARule(".cfa 99804755 +");
cfi.SetRegisterRule("register1", ".cfa 54370437 *");
cfi.SetRegisterRule("vodkathumbscrewingly", "24076308 .cfa +");
cfi.SetRegisterRule("pubvexingfjordschmaltzy", ".cfa 29801007 -");
cfi.SetRegisterRule("uncopyrightables", "92642917 .cfa /");
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(6U, caller_registers.size());
ASSERT_EQ(7664691U, caller_registers[".cfa"]);
ASSERT_EQ(107469446U, caller_registers[".ra"]);
ASSERT_EQ(416732599139967ULL, caller_registers["register1"]);
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) {
ExpectNoMemoryReferences();
cfi.SetCFARule("330903416631436410");
cfi.SetRARule("5870666104170902211");
cfi.SetCFARule("2828089117179001");
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
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 { };
// There should be no value for .cfa in scope when evaluating the CFA rule.
TEST_F(Scope, CFALacksCFA) {
ExpectNoMemoryReferences();
cfi.SetCFARule(".cfa");
cfi.SetRARule("0");
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
}
// There should be no value for .ra in scope when evaluating the CFA rule.
TEST_F(Scope, CFALacksRA) {
ExpectNoMemoryReferences();
cfi.SetCFARule(".ra");
cfi.SetRARule("0");
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
}
// The current frame's registers should be in scope when evaluating
// the CFA rule.
TEST_F(Scope, CFASeesCurrentRegs) {
ExpectNoMemoryReferences();
registers[".baraminology"] = 0x06a7bc63e4f13893ULL;
registers[".ornithorhynchus"] = 0x5e0bf850bafce9d2ULL;
cfi.SetCFARule(".baraminology .ornithorhynchus +");
cfi.SetRARule("0");
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(2U, caller_registers.size());
ASSERT_EQ(0x06a7bc63e4f13893ULL + 0x5e0bf850bafce9d2ULL,
caller_registers[".cfa"]);
}
// .cfa should be in scope in the return address expression.
TEST_F(Scope, RASeesCFA) {
ExpectNoMemoryReferences();
cfi.SetCFARule("48364076");
cfi.SetRARule(".cfa");
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(2U, caller_registers.size());
ASSERT_EQ(48364076U, caller_registers[".ra"]);
}
// There should be no value for .ra in scope when evaluating the CFA rule.
TEST_F(Scope, RALacksRA) {
ExpectNoMemoryReferences();
cfi.SetCFARule("0");
cfi.SetRARule(".ra");
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
}
// The current frame's registers should be in scope in the return
// address expression.
TEST_F(Scope, RASeesCurrentRegs) {
ExpectNoMemoryReferences();
registers["noachian"] = 0x54dc4a5d8e5eb503ULL;
cfi.SetCFARule("10359370");
cfi.SetRARule("noachian");
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(2U, caller_registers.size());
ASSERT_EQ(0x54dc4a5d8e5eb503ULL, caller_registers[".ra"]);
}
// .cfa should be in scope for register rules.
TEST_F(Scope, RegistersSeeCFA) {
ExpectNoMemoryReferences();
cfi.SetCFARule("6515179");
cfi.SetRARule(".cfa");
cfi.SetRegisterRule("rogerian", ".cfa");
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(3U, caller_registers.size());
ASSERT_EQ(6515179U, caller_registers["rogerian"]);
}
// The return address should not be in scope for register rules.
TEST_F(Scope, RegsLackRA) {
ExpectNoMemoryReferences();
cfi.SetCFARule("42740329");
cfi.SetRARule("27045204");
cfi.SetRegisterRule("$r1", ".ra");
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(caller_registers.end(), caller_registers.find("$r1"));
}
// Register rules can see the current frame's register values.
TEST_F(Scope, RegsSeeRegs) {
ExpectNoMemoryReferences();
registers["$r1"] = 0x6ed3582c4bedb9adULL;
registers["$r2"] = 0xd27d9e742b8df6d0ULL;
cfi.SetCFARule("88239303");
cfi.SetRARule("30503835");
cfi.SetRegisterRule("$r1", "$r1 42175211 = $r2");
cfi.SetRegisterRule("$r2", "$r2 21357221 = $r1");
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(4U, caller_registers.size());
ASSERT_EQ(0xd27d9e742b8df6d0ULL, caller_registers["$r1"]);
ASSERT_EQ(0x6ed3582c4bedb9adULL, caller_registers["$r2"]);
}
// Each rule's temporaries are separate.
TEST_F(Scope, SeparateTempsRA) {
ExpectNoMemoryReferences();
cfi.SetCFARule("$temp1 76569129 = $temp1");
cfi.SetRARule("0");
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
cfi.SetCFARule("$temp1 76569129 = $temp1");
cfi.SetRARule("$temp1");
ASSERT_FALSE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
}
class MockCFIRuleParserHandler: public CFIRuleParser::Handler {
public:
MOCK_METHOD1(CFARule, void(const string&));
MOCK_METHOD1(RARule, void(const string&));
MOCK_METHOD2(RegisterRule, void(const string&, const string&));
};
// A fixture class for testing CFIRuleParser.
class CFIParserFixture {
public:
CFIParserFixture() : parser(&mock_handler) {
// Expect no parsing results to be reported to mock_handler. Individual
// tests can override this.
EXPECT_CALL(mock_handler, CFARule(_)).Times(0);
EXPECT_CALL(mock_handler, RARule(_)).Times(0);
EXPECT_CALL(mock_handler, RegisterRule(_, _)).Times(0);
}
MockCFIRuleParserHandler mock_handler;
CFIRuleParser parser;
};
class Parser: public CFIParserFixture, public Test { };
TEST_F(Parser, Empty) {
EXPECT_FALSE(parser.Parse(""));
}
TEST_F(Parser, LoneColon) {
EXPECT_FALSE(parser.Parse(":"));
}
TEST_F(Parser, CFANoExpr) {
EXPECT_FALSE(parser.Parse(".cfa:"));
}
TEST_F(Parser, CFANoColonNoExpr) {
EXPECT_FALSE(parser.Parse(".cfa"));
}
TEST_F(Parser, RANoExpr) {
EXPECT_FALSE(parser.Parse(".ra:"));
}
TEST_F(Parser, RANoColonNoExpr) {
EXPECT_FALSE(parser.Parse(".ra"));
}
TEST_F(Parser, RegNoExpr) {
EXPECT_FALSE(parser.Parse("reg:"));
}
TEST_F(Parser, NoName) {
EXPECT_FALSE(parser.Parse("expr"));
}
TEST_F(Parser, NoNameTwo) {
EXPECT_FALSE(parser.Parse("expr1 expr2"));
}
TEST_F(Parser, StartsWithExpr) {
EXPECT_FALSE(parser.Parse("expr1 reg: expr2"));
}
TEST_F(Parser, CFA) {
EXPECT_CALL(mock_handler, CFARule("spleen")).WillOnce(Return());
EXPECT_TRUE(parser.Parse(".cfa: spleen"));
}
TEST_F(Parser, RA) {
EXPECT_CALL(mock_handler, RARule("notoriety")).WillOnce(Return());
EXPECT_TRUE(parser.Parse(".ra: notoriety"));
}
TEST_F(Parser, Reg) {
EXPECT_CALL(mock_handler, RegisterRule("nemo", "mellifluous"))
.WillOnce(Return());
EXPECT_TRUE(parser.Parse("nemo: mellifluous"));
}
TEST_F(Parser, CFARARegs) {
EXPECT_CALL(mock_handler, CFARule("cfa expression")).WillOnce(Return());
EXPECT_CALL(mock_handler, RARule("ra expression")).WillOnce(Return());
EXPECT_CALL(mock_handler, RegisterRule("galba", "praetorian"))
.WillOnce(Return());
EXPECT_CALL(mock_handler, RegisterRule("otho", "vitellius"))
.WillOnce(Return());
EXPECT_TRUE(parser.Parse(".cfa: cfa expression .ra: ra expression "
"galba: praetorian otho: vitellius"));
}
TEST_F(Parser, Whitespace) {
EXPECT_CALL(mock_handler, RegisterRule("r1", "r1 expression"))
.WillOnce(Return());
EXPECT_CALL(mock_handler, RegisterRule("r2", "r2 expression"))
.WillOnce(Return());
EXPECT_TRUE(parser.Parse(" r1:\tr1\nexpression \tr2:\t\rr2\r\n "
"expression \n"));
}
TEST_F(Parser, WhitespaceLoneColon) {
EXPECT_FALSE(parser.Parse(" \n:\t "));
}
TEST_F(Parser, EmptyName) {
EXPECT_CALL(mock_handler, RegisterRule("reg", _))
.Times(AtMost(1))
.WillRepeatedly(Return());
EXPECT_FALSE(parser.Parse("reg: expr1 : expr2"));
}
TEST_F(Parser, RuleLoneColon) {
EXPECT_CALL(mock_handler, RegisterRule("r1", "expr"))
.Times(AtMost(1))
.WillRepeatedly(Return());
EXPECT_FALSE(parser.Parse(" r1: expr :"));
}
TEST_F(Parser, RegNoExprRule) {
EXPECT_CALL(mock_handler, RegisterRule("r1", "expr"))
.Times(AtMost(1))
.WillRepeatedly(Return());
EXPECT_FALSE(parser.Parse("r0: r1: expr"));
}
class ParseHandlerFixture: public CFIFixture {
public:
ParseHandlerFixture() : CFIFixture(), handler(&cfi) { }
CFIFrameInfoParseHandler handler;
};
class ParseHandler: public ParseHandlerFixture, public Test { };
TEST_F(ParseHandler, CFARARule) {
handler.CFARule("reg-for-cfa");
handler.RARule("reg-for-ra");
registers["reg-for-cfa"] = 0x268a9a4a3821a797ULL;
registers["reg-for-ra"] = 0x6301b475b8b91c02ULL;
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(0x268a9a4a3821a797ULL, caller_registers[".cfa"]);
ASSERT_EQ(0x6301b475b8b91c02ULL, caller_registers[".ra"]);
}
TEST_F(ParseHandler, RegisterRules) {
handler.CFARule("reg-for-cfa");
handler.RARule("reg-for-ra");
handler.RegisterRule("reg1", "reg-for-reg1");
handler.RegisterRule("reg2", "reg-for-reg2");
handler.RegisterRule("reg3", "reg3");
registers["reg-for-cfa"] = 0x268a9a4a3821a797ULL;
registers["reg-for-ra"] = 0x6301b475b8b91c02ULL;
registers["reg-for-reg1"] = 0x06cde8e2ff062481ULL;
registers["reg-for-reg2"] = 0xff0c4f76403173e2ULL;
ASSERT_TRUE(cfi.FindCallerRegs<uint64_t>(registers, memory,
&caller_registers));
ASSERT_EQ(0x268a9a4a3821a797ULL, caller_registers[".cfa"]);
ASSERT_EQ(0x6301b475b8b91c02ULL, caller_registers[".ra"]);
ASSERT_EQ(0x06cde8e2ff062481ULL, caller_registers["reg1"]);
ASSERT_EQ(0xff0c4f76403173e2ULL, caller_registers["reg2"]);
ASSERT_EQ(caller_registers.end(), caller_registers.find("reg3"));
}
struct SimpleCFIWalkerFixture {
struct RawContext {
uint64_t r0, r1, r2, r3, r4, sp, pc;
};
enum Validity {
R0_VALID = 0x01,
R1_VALID = 0x02,
R2_VALID = 0x04,
R3_VALID = 0x08,
R4_VALID = 0x10,
SP_VALID = 0x20,
PC_VALID = 0x40
};
typedef SimpleCFIWalker<uint64_t, RawContext> CFIWalker;
SimpleCFIWalkerFixture()
: walker(register_map,
sizeof(register_map) / sizeof(register_map[0])) { }
static CFIWalker::RegisterSet register_map[7];
CFIFrameInfo call_frame_info;
CFIWalker walker;
MockMemoryRegion memory;
RawContext callee_context, caller_context;
};
SimpleCFIWalkerFixture::CFIWalker::RegisterSet
SimpleCFIWalkerFixture::register_map[7] = {
{ "r0", NULL, true, R0_VALID, &RawContext::r0 },
{ "r1", NULL, true, R1_VALID, &RawContext::r1 },
{ "r2", NULL, false, R2_VALID, &RawContext::r2 },
{ "r3", NULL, false, R3_VALID, &RawContext::r3 },
{ "r4", NULL, true, R4_VALID, &RawContext::r4 },
{ "sp", ".cfa", true, SP_VALID, &RawContext::sp },
{ "pc", ".ra", true, PC_VALID, &RawContext::pc },
};
class SimpleWalker: public SimpleCFIWalkerFixture, public Test { };
TEST_F(SimpleWalker, Walk) {
// Stack_top is the current stack pointer, pointing to the lowest
// address of a frame that looks like this (all 64-bit words):
//
// sp -> saved r0
// garbage
// return address
// cfa ->
//
// r0 has been saved on the stack.
// r1 has been saved in r2.
// r2 and r3 are not recoverable.
// r4 is not recoverable, even though it is a callee-saves register.
// Some earlier frame's unwinder must have failed to recover it.
uint64_t stack_top = 0x83254944b20d5512ULL;
// Saved r0.
EXPECT_CALL(memory,
GetMemoryAtAddress(stack_top, A<uint64_t*>()))
.WillRepeatedly(DoAll(SetArgumentPointee<1>(0xdc1975eba8602302ULL),
Return(true)));
// Saved return address.
EXPECT_CALL(memory,
GetMemoryAtAddress(stack_top + 16, A<uint64_t*>()))
.WillRepeatedly(DoAll(SetArgumentPointee<1>(0xba5ad6d9acce28deULL),
Return(true)));
call_frame_info.SetCFARule("sp 24 +");
call_frame_info.SetRARule(".cfa 8 - ^");
call_frame_info.SetRegisterRule("r0", ".cfa 24 - ^");
call_frame_info.SetRegisterRule("r1", "r2");
callee_context.r0 = 0x94e030ca79edd119ULL;
callee_context.r1 = 0x937b4d7e95ce52d9ULL;
callee_context.r2 = 0x5fe0027416b8b62aULL; // caller's r1
// callee_context.r3 is not valid in callee.
// callee_context.r4 is not valid in callee.
callee_context.sp = stack_top;
callee_context.pc = 0x25b21b224311d280ULL;
int callee_validity = R0_VALID | R1_VALID | R2_VALID | SP_VALID | PC_VALID;
memset(&caller_context, 0, sizeof(caller_context));
int caller_validity;
EXPECT_TRUE(walker.FindCallerRegisters(memory, call_frame_info,
callee_context, callee_validity,
&caller_context, &caller_validity));
EXPECT_EQ(R0_VALID | R1_VALID | SP_VALID | PC_VALID, caller_validity);
EXPECT_EQ(0xdc1975eba8602302ULL, caller_context.r0);
EXPECT_EQ(0x5fe0027416b8b62aULL, caller_context.r1);
EXPECT_EQ(stack_top + 24, caller_context.sp);
EXPECT_EQ(0xba5ad6d9acce28deULL, caller_context.pc);
}

View file

@ -0,0 +1,214 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// contained_range_map-inl.h: Hierarchically-organized range map implementation.
//
// See contained_range_map.h for documentation.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_CONTAINED_RANGE_MAP_INL_H__
#define PROCESSOR_CONTAINED_RANGE_MAP_INL_H__
#include "processor/contained_range_map.h"
#include <assert.h>
#include "processor/logging.h"
namespace google_breakpad {
template<typename AddressType, typename EntryType>
ContainedRangeMap<AddressType, EntryType>::~ContainedRangeMap() {
// Clear frees the children pointed to by the map, and frees the map itself.
Clear();
}
template<typename AddressType, typename EntryType>
bool ContainedRangeMap<AddressType, EntryType>::StoreRange(
const AddressType& base, const AddressType& size, const EntryType& entry) {
AddressType high = base + size - 1;
// Check for undersize or overflow.
if (size <= 0 || high < base) {
//TODO(nealsid) We are commenting this out in order to prevent
// excessive logging. We plan to move to better logging as this
// failure happens quite often and is expected(see comment in
// basic_source_line_resolver.cc:671).
// BPLOG(INFO) << "StoreRange failed, " << HexString(base) << "+"
// << HexString(size) << ", " << HexString(high);
return false;
}
if (!map_)
map_ = new AddressToRangeMap();
MapIterator iterator_base = map_->lower_bound(base);
MapIterator iterator_high = map_->lower_bound(high);
MapIterator iterator_end = map_->end();
if (iterator_base == iterator_high && iterator_base != iterator_end &&
base >= iterator_base->second->base_) {
// The new range is entirely within an existing child range.
// If the new range's geometry is exactly equal to an existing child
// range's, it violates the containment rules, and an attempt to store
// it must fail. iterator_base->first contains the key, which was the
// containing child's high address.
if (!allow_equal_range_ && iterator_base->second->base_ == base &&
iterator_base->first == high) {
// TODO(nealsid): See the TODO above on why this is commented out.
// BPLOG(INFO) << "StoreRange failed, identical range is already "
// "present: " << HexString(base) << "+" <<
// HexString(size);
return false;
}
// Pass the new range on to the child to attempt to store.
return iterator_base->second->StoreRange(base, size, entry);
}
// iterator_high might refer to an irrelevant range: one whose base address
// is higher than the new range's high address. Set contains_high to true
// only if iterator_high refers to a range that is at least partially
// within the new range.
bool contains_high = iterator_high != iterator_end &&
high >= iterator_high->second->base_;
// If the new range encompasses any existing child ranges, it must do so
// fully. Partial containment isn't allowed.
if ((iterator_base != iterator_end && base > iterator_base->second->base_) ||
(contains_high && high < iterator_high->first)) {
// TODO(mmentovai): Some symbol files will trip this check frequently
// on STACK lines. Too many messages will be produced. These are more
// suitable for a DEBUG channel than an INFO channel.
// BPLOG(INFO) << "StoreRange failed, new range partially contains "
// "existing range: " << HexString(base) << "+" <<
// HexString(size);
return false;
}
// When copying and erasing contained ranges, the "end" iterator needs to
// point one past the last item of the range to copy. If contains_high is
// false, the iterator's already in the right place; the increment is safe
// because contains_high can't be true if iterator_high == iterator_end.
if (contains_high)
++iterator_high;
// Optimization: if the iterators are equal, no child ranges would be
// moved. Create the new child range with a NULL map to conserve space
// in leaf nodes, of which there will be many.
AddressToRangeMap* child_map = NULL;
if (iterator_base != iterator_high) {
// The children of this range that are contained by the new range must
// be transferred over to the new range. Create the new child range map
// and copy the pointers to range maps it should contain into it.
child_map = new AddressToRangeMap(iterator_base, iterator_high);
// Remove the copied child pointers from this range's map of children.
map_->erase(iterator_base, iterator_high);
}
// Store the new range in the map by its high address. Any children that
// the new child range contains were formerly children of this range but
// are now this range's grandchildren. Ownership of these is transferred
// to the new child range.
ContainedRangeMap* new_child =
new ContainedRangeMap(base, entry, child_map, allow_equal_range_);
map_->insert(MapValue(high, new_child));
return true;
}
template<typename AddressType, typename EntryType>
bool ContainedRangeMap<AddressType, EntryType>::RetrieveRange(
const AddressType& address, EntryType* entry) const {
BPLOG_IF(ERROR, !entry) << "ContainedRangeMap::RetrieveRange requires "
"|entry|";
assert(entry);
// If nothing was ever stored, then there's nothing to retrieve.
if (!map_)
return false;
// Get an iterator to the child range whose high address is equal to or
// greater than the supplied address. If the supplied address is higher
// than all of the high addresses in the range, then this range does not
// contain a child at address, so return false. If the supplied address
// is lower than the base address of the child range, then it is not within
// the child range, so return false.
MapConstIterator iterator = map_->lower_bound(address);
if (iterator == map_->end() || address < iterator->second->base_)
return false;
// The child in iterator->second contains the specified address. Find out
// if it has a more-specific descendant that also contains it. If it does,
// it will set |entry| appropriately. If not, set |entry| to the child.
if (!iterator->second->RetrieveRange(address, entry))
*entry = iterator->second->entry_;
return true;
}
template <typename AddressType, typename EntryType>
bool ContainedRangeMap<AddressType, EntryType>::RetrieveRanges(
const AddressType& address,
std::vector<const EntryType*>& entries) const {
// If nothing was ever stored, then there's nothing to retrieve.
if (!map_)
return false;
MapIterator iterator = map_->lower_bound(address);
if (iterator == map_->end() || address < iterator->second->base_)
return false;
iterator->second->RetrieveRanges(address, entries);
entries.push_back(&iterator->second->entry_);
return true;
}
template<typename AddressType, typename EntryType>
void ContainedRangeMap<AddressType, EntryType>::Clear() {
if (map_) {
MapConstIterator end = map_->end();
for (MapConstIterator child = map_->begin(); child != end; ++child)
delete child->second;
delete map_;
map_ = NULL;
}
}
} // namespace google_breakpad
#endif // PROCESSOR_CONTAINED_RANGE_MAP_INL_H__

View file

@ -0,0 +1,167 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// contained_range_map.h: Hierarchically-organized range maps.
//
// A contained range map is similar to a standard range map, except it allows
// objects to be organized hierarchically. A contained range map allows
// objects to contain other objects. It is not sensitive to the order that
// objects are added to the map: larger, more general, containing objects
// may be added either before or after smaller, more specific, contained
// ones.
//
// Contained range maps guarantee that each object may only contain smaller
// objects than itself, and that a parent object may only contain child
// objects located entirely within the parent's address space. Attempts
// to introduce objects (via StoreRange) that violate these rules will fail.
// Retrieval (via RetrieveRange) always returns the most specific (smallest)
// object that contains the address being queried. Note that while it is
// not possible to insert two objects into a map that have exactly the same
// geometry (base address and size), it is possible to completely mask a
// larger object by inserting smaller objects that entirely fill the larger
// object's address space.
//
// Internally, contained range maps are implemented as a tree. Each tree
// node except for the root node describes an object in the map. Each node
// maintains its list of children in a map similar to a standard range map,
// keyed by the highest address that each child occupies. Each node's
// children occupy address ranges entirely within the node. The root node
// is the only node directly accessible to the user, and represents the
// entire address space.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_CONTAINED_RANGE_MAP_H__
#define PROCESSOR_CONTAINED_RANGE_MAP_H__
#include <map>
#include <vector>
namespace google_breakpad {
// Forward declarations (for later friend declarations of specialized template).
template<class, class> class ContainedRangeMapSerializer;
template<typename AddressType, typename EntryType>
class ContainedRangeMap {
public:
// The default constructor creates a ContainedRangeMap with no geometry
// and no entry, and as such is only suitable for the root node of a
// ContainedRangeMap tree.
explicit ContainedRangeMap(bool allow_equal_range = false)
: base_(), entry_(), map_(NULL), allow_equal_range_(allow_equal_range) {}
~ContainedRangeMap();
// Inserts a range into the map. If the new range is encompassed by
// an existing child range, the new range is passed into the child range's
// StoreRange method. If the new range encompasses any existing child
// ranges, those child ranges are moved to the new range, becoming
// grandchildren of this ContainedRangeMap. Returns false for a
// parameter error, or if the ContainedRangeMap hierarchy guarantees
// would be violated.
bool StoreRange(const AddressType& base,
const AddressType& size,
const EntryType& entry);
// Retrieves the most specific (smallest) descendant range encompassing
// the specified address. This method will only return entries held by
// child ranges, and not the entry contained by |this|. This is necessary
// to support a sparsely-populated root range. If no descendant range
// encompasses the address, returns false.
bool RetrieveRange(const AddressType& address, EntryType* entries) const;
// Retrieves the vector of entries encompassing the specified address from the
// innermost entry to the outermost entry.
bool RetrieveRanges(const AddressType& address,
std::vector<const EntryType*>& entries) const;
// Removes all children. Note that Clear only removes descendants,
// leaving the node on which it is called intact. Because the only
// meaningful things contained by a root node are descendants, this
// is sufficient to restore an entire ContainedRangeMap to its initial
// empty state when called on the root node.
void Clear();
private:
friend class ContainedRangeMapSerializer<AddressType, EntryType>;
friend class ModuleComparer;
// AddressToRangeMap stores pointers. This makes reparenting simpler in
// StoreRange, because it doesn't need to copy entire objects.
typedef std::map<AddressType, ContainedRangeMap*> AddressToRangeMap;
typedef typename AddressToRangeMap::const_iterator MapConstIterator;
typedef typename AddressToRangeMap::iterator MapIterator;
typedef typename AddressToRangeMap::value_type MapValue;
// Creates a new ContainedRangeMap with the specified base address, entry,
// and initial child map, which may be NULL. This is only used internally
// by ContainedRangeMap when it creates a new child.
ContainedRangeMap(const AddressType& base,
const EntryType& entry,
AddressToRangeMap* map,
bool allow_equal_range)
: base_(base),
entry_(entry),
map_(map),
allow_equal_range_(allow_equal_range) {}
// The base address of this range. The high address does not need to
// be stored, because it is used as the key to an object in its parent's
// map, and all ContainedRangeMaps except for the root range are contained
// within maps. The root range does not actually contain an entry, so its
// base_ field is meaningless, and the fact that it has no parent and thus
// no key is unimportant. For this reason, the base_ field should only be
// is accessed on child ContainedRangeMap objects, and never on |this|.
const AddressType base_;
// The entry corresponding to this range. The root range does not
// actually contain an entry, so its entry_ field is meaningless. For
// this reason, the entry_ field should only be accessed on child
// ContainedRangeMap objects, and never on |this|.
const EntryType entry_;
// The map containing child ranges, keyed by each child range's high
// address. This is a pointer to avoid allocating map structures for
// leaf nodes, where they are not needed.
AddressToRangeMap* map_;
// Whether or not we allow storing an entry into a range that equals to
// existing range in the map. Default is false.
// If this is true, the newly added range will become a child of existing
// innermost range which has same base and size.
bool allow_equal_range_;
};
} // namespace google_breakpad
#endif // PROCESSOR_CONTAINED_RANGE_MAP_H__

View file

@ -0,0 +1,383 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// contained_range_map_unittest.cc: Unit tests for ContainedRangeMap
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <stdio.h>
#include "processor/contained_range_map-inl.h"
#include "processor/logging.h"
#define ASSERT_TRUE(condition) \
if (!(condition)) { \
fprintf(stderr, "FAIL: %s @ %s:%d\n", #condition, __FILE__, __LINE__); \
return false; \
}
#define ASSERT_FALSE(condition) ASSERT_TRUE(!(condition))
namespace {
using google_breakpad::ContainedRangeMap;
// The first is the querying address, the second is the entries vector result.
using EntriesTestPair = std::pair<unsigned, std::vector<int>>;
using EntriesTestPairVec = std::vector<EntriesTestPair>;
static bool RunTestsWithRetrieveRange(
const ContainedRangeMap<unsigned int, int>& crm,
const int* test_data,
unsigned int test_length) {
// Now, do the RetrieveRange tests. This further validates that the
// objects were stored properly and that retrieval returns the correct
// object.
// If GENERATE_TEST_DATA is defined, instead of the retrieval tests, a
// new test_data array will be printed. Exercise caution when doing this.
// Be sure to verify the results manually!
#ifdef GENERATE_TEST_DATA
printf(" const int test_data[] = {\n");
#endif // GENERATE_TEST_DATA
for (unsigned int address = 0; address < test_length; ++address) {
int value;
if (!crm.RetrieveRange(address, &value))
value = 0;
#ifndef GENERATE_TEST_DATA
// Don't use ASSERT inside the loop because it won't show the failed
// |address|, and the line number will always be the same. That makes
// it difficult to figure out which test failed.
if (value != test_data[address]) {
fprintf(stderr, "FAIL: retrieve %d expected %d observed %d @ %s:%d\n",
address, test_data[address], value, __FILE__, __LINE__);
return false;
}
#else // !GENERATE_TEST_DATA
printf(" %d%c%s // %d\n", value, address == test_high - 1 ? ' ' : ',',
value < 10 ? " " : "", address);
#endif // !GENERATE_TEST_DATA
}
#ifdef GENERATE_TEST_DATA
printf(" };\n");
#endif // GENERATE_TEST_DATA
return true;
}
static bool RunTestsWithRetrieveRangeVector(
const ContainedRangeMap<unsigned int, int>& crm,
const EntriesTestPairVec& entries_tests) {
for (const EntriesTestPair& entries_test : entries_tests) {
std::vector<const int*> entries;
crm.RetrieveRanges(entries_test.first, entries);
if (entries.size() != entries_test.second.size()) {
fprintf(stderr,
"FAIL: retrieving entries at address %u has size %zu "
"expected to have size %zu "
"@ %s: %d\n",
entries_test.first, entries.size(), entries_test.second.size(),
__FILE__, __LINE__);
return false;
}
for (size_t i = 0; i < entries.size(); ++i) {
if (*entries[i] != entries_test.second[i]) {
fprintf(stderr,
"FAIL: retrieving entries at address %u entries[%zu] is %d "
"expected %d"
"@ %s: %d\n",
entries_test.first, i, *entries[i], entries_test.second[i],
__FILE__, __LINE__);
return false;
}
}
}
return true;
}
static bool RunTestsWithNoEqualRange() {
ContainedRangeMap<unsigned int, int> crm;
// First, do the StoreRange tests. This validates the containment
// rules.
ASSERT_TRUE (crm.StoreRange(10, 10, 1));
ASSERT_FALSE(crm.StoreRange(10, 10, 2)); // exactly equal to 1
ASSERT_FALSE(crm.StoreRange(11, 10, 3)); // begins inside 1 and extends up
ASSERT_FALSE(crm.StoreRange( 9, 10, 4)); // begins below 1 and ends inside
ASSERT_TRUE (crm.StoreRange(11, 9, 5)); // contained by existing
ASSERT_TRUE (crm.StoreRange(12, 7, 6));
ASSERT_TRUE (crm.StoreRange( 9, 12, 7)); // contains existing
ASSERT_TRUE (crm.StoreRange( 9, 13, 8));
ASSERT_TRUE (crm.StoreRange( 8, 14, 9));
ASSERT_TRUE (crm.StoreRange(30, 3, 10));
ASSERT_TRUE (crm.StoreRange(33, 3, 11));
ASSERT_TRUE (crm.StoreRange(30, 6, 12)); // storable but totally masked
ASSERT_TRUE (crm.StoreRange(40, 8, 13)); // will be totally masked
ASSERT_TRUE (crm.StoreRange(40, 4, 14));
ASSERT_TRUE (crm.StoreRange(44, 4, 15));
ASSERT_FALSE(crm.StoreRange(32, 10, 16)); // begins in #10, ends in #14
ASSERT_FALSE(crm.StoreRange(50, 0, 17)); // zero length
ASSERT_TRUE (crm.StoreRange(50, 10, 18));
ASSERT_TRUE (crm.StoreRange(50, 1, 19));
ASSERT_TRUE (crm.StoreRange(59, 1, 20));
ASSERT_TRUE (crm.StoreRange(60, 1, 21));
ASSERT_TRUE (crm.StoreRange(69, 1, 22));
ASSERT_TRUE (crm.StoreRange(60, 10, 23));
ASSERT_TRUE (crm.StoreRange(68, 1, 24));
ASSERT_TRUE (crm.StoreRange(61, 1, 25));
ASSERT_TRUE (crm.StoreRange(61, 8, 26));
ASSERT_FALSE(crm.StoreRange(59, 9, 27));
ASSERT_FALSE(crm.StoreRange(59, 10, 28));
ASSERT_FALSE(crm.StoreRange(59, 11, 29));
ASSERT_TRUE (crm.StoreRange(70, 10, 30));
ASSERT_TRUE (crm.StoreRange(74, 2, 31));
ASSERT_TRUE (crm.StoreRange(77, 2, 32));
ASSERT_FALSE(crm.StoreRange(72, 6, 33));
ASSERT_TRUE (crm.StoreRange(80, 3, 34));
ASSERT_TRUE (crm.StoreRange(81, 1, 35));
ASSERT_TRUE (crm.StoreRange(82, 1, 36));
ASSERT_TRUE (crm.StoreRange(83, 3, 37));
ASSERT_TRUE (crm.StoreRange(84, 1, 38));
ASSERT_TRUE (crm.StoreRange(83, 1, 39));
ASSERT_TRUE (crm.StoreRange(86, 5, 40));
ASSERT_TRUE (crm.StoreRange(88, 1, 41));
ASSERT_TRUE (crm.StoreRange(90, 1, 42));
ASSERT_TRUE (crm.StoreRange(86, 1, 43));
ASSERT_TRUE (crm.StoreRange(87, 1, 44));
ASSERT_TRUE (crm.StoreRange(89, 1, 45));
ASSERT_TRUE (crm.StoreRange(87, 4, 46));
ASSERT_TRUE (crm.StoreRange(87, 3, 47));
ASSERT_FALSE(crm.StoreRange(86, 2, 48));
// Each element in test_data contains the expected result when calling
// RetrieveRange on an address.
const int test_data[] = {
0, // 0
0, // 1
0, // 2
0, // 3
0, // 4
0, // 5
0, // 6
0, // 7
9, // 8
7, // 9
1, // 10
5, // 11
6, // 12
6, // 13
6, // 14
6, // 15
6, // 16
6, // 17
6, // 18
5, // 19
7, // 20
8, // 21
0, // 22
0, // 23
0, // 24
0, // 25
0, // 26
0, // 27
0, // 28
0, // 29
10, // 30
10, // 31
10, // 32
11, // 33
11, // 34
11, // 35
0, // 36
0, // 37
0, // 38
0, // 39
14, // 40
14, // 41
14, // 42
14, // 43
15, // 44
15, // 45
15, // 46
15, // 47
0, // 48
0, // 49
19, // 50
18, // 51
18, // 52
18, // 53
18, // 54
18, // 55
18, // 56
18, // 57
18, // 58
20, // 59
21, // 60
25, // 61
26, // 62
26, // 63
26, // 64
26, // 65
26, // 66
26, // 67
24, // 68
22, // 69
30, // 70
30, // 71
30, // 72
30, // 73
31, // 74
31, // 75
30, // 76
32, // 77
32, // 78
30, // 79
34, // 80
35, // 81
36, // 82
39, // 83
38, // 84
37, // 85
43, // 86
44, // 87
41, // 88
45, // 89
42, // 90
0, // 91
0, // 92
0, // 93
0, // 94
0, // 95
0, // 96
0, // 97
0, // 98
0 // 99
};
unsigned int test_length = sizeof(test_data) / sizeof(int);
return RunTestsWithRetrieveRange(crm, test_data, test_length);
}
static bool RunTestsWithEqualRange() {
ContainedRangeMap<unsigned int, int> crm(true);
// First, do the StoreRange tests. This validates the containment
// rules.
ASSERT_TRUE (crm.StoreRange(1, 3, 1));
ASSERT_TRUE (crm.StoreRange(1, 3, 2)); // exactly equal to 1
ASSERT_TRUE (crm.StoreRange(1, 3, 3)); // exactly equal to 1, 2
ASSERT_TRUE (crm.StoreRange(1, 3, 4)); // exactly equal to 1, 2, 3
ASSERT_FALSE(crm.StoreRange(0, 3, 5)); // partial overlap.
ASSERT_FALSE(crm.StoreRange(2, 3, 6)); // partial overlap.
ASSERT_TRUE (crm.StoreRange(5, 3, 7));
ASSERT_TRUE (crm.StoreRange(5, 3, 8)); // exactly equal to 7
ASSERT_TRUE (crm.StoreRange(5, 3, 9)); // exactly equal to 7, 8
ASSERT_TRUE (crm.StoreRange(5, 4, 10)); // encompasses 7, 8, 9
ASSERT_TRUE (crm.StoreRange(5, 5, 11)); // encompasses 7, 8, 9, 10
ASSERT_TRUE (crm.StoreRange(10, 3, 12));
ASSERT_TRUE (crm.StoreRange(10, 3, 13)); // exactly equal to 12
ASSERT_TRUE (crm.StoreRange(11, 2, 14)); // encompasses by 12
ASSERT_TRUE (crm.StoreRange(11, 1, 15)); // encompasses by 12, 13
ASSERT_TRUE (crm.StoreRange(14, 3, 16));
ASSERT_TRUE (crm.StoreRange(14, 3, 17)); // exactly equal to 14
ASSERT_TRUE (crm.StoreRange(14, 1, 18)); // encompasses by 14, 15
ASSERT_TRUE (crm.StoreRange(14, 2, 19)); // encompasses by 14, 15 and encompasses 16
ASSERT_TRUE (crm.StoreRange(14, 1, 20)); // exactly equal to 18
ASSERT_TRUE (crm.StoreRange(14, 2, 21)); // exactly equal to 19
// Each element in test_data contains the expected result when calling
// RetrieveRange on an address.
const int test_data[] = {
0, // 0
4, // 1
4, // 2
4, // 3
0, // 4
9, // 5
9, // 6
9, // 7
10, // 8
11, // 9
13, // 10
15, // 11
14, // 12
0, // 13
20, // 14
21, // 15
17, // 16
0, // 17
};
unsigned int test_length = sizeof(test_data) / sizeof(int);
EntriesTestPairVec entries_tests = {
{0, {}},
{1, {4, 3, 2, 1}},
{2, {4, 3, 2, 1}},
{3, {4, 3, 2, 1}},
{4, {}},
{5, {9, 8, 7, 10, 11}},
{6, {9, 8, 7, 10, 11}},
{7, {9, 8, 7, 10, 11}},
{8, {10, 11}},
{9, {11}},
{10, {13, 12}},
{11, {15, 14, 13, 12}},
{12, {14, 13, 12}},
{13, {}},
{14, {20, 18, 21, 19, 17, 16}},
{15, {21, 19, 17, 16}},
{16, {17, 16}},
{17, {}},
};
return RunTestsWithRetrieveRange(crm, test_data, test_length) &&
RunTestsWithRetrieveRangeVector(crm, entries_tests);
}
static bool RunTests() {
return RunTestsWithNoEqualRange() && RunTestsWithEqualRange();
}
} // namespace
int main(int argc, char** argv) {
BPLOG_INIT(&argc, &argv);
return RunTests() ? 0 : 1;
}

View file

@ -0,0 +1,70 @@
// Copyright 2018 Google LLC
//
// 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 LLC 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.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "processor/convert_old_arm64_context.h"
#include <string.h>
namespace google_breakpad {
void ConvertOldARM64Context(const MDRawContextARM64_Old& old,
MDRawContextARM64* context) {
context->context_flags = MD_CONTEXT_ARM64;
if (old.context_flags & MD_CONTEXT_ARM64_INTEGER_OLD) {
context->context_flags |=
MD_CONTEXT_ARM64_INTEGER | MD_CONTEXT_ARM64_CONTROL;
}
if (old.context_flags & MD_CONTEXT_ARM64_FLOATING_POINT_OLD) {
context->context_flags |= MD_CONTEXT_ARM64_FLOATING_POINT;
}
context->cpsr = old.cpsr;
static_assert(sizeof(old.iregs) == sizeof(context->iregs),
"iregs size mismatch");
memcpy(context->iregs, old.iregs, sizeof(context->iregs));
static_assert(sizeof(old.float_save.regs) == sizeof(context->float_save.regs),
"float_save.regs size mismatch");
memcpy(context->float_save.regs,
old.float_save.regs,
sizeof(context->float_save.regs));
context->float_save.fpcr = old.float_save.fpcr;
context->float_save.fpsr = old.float_save.fpsr;
memset(context->bcr, 0, sizeof(context->bcr));
memset(context->bvr, 0, sizeof(context->bvr));
memset(context->wcr, 0, sizeof(context->wcr));
memset(context->wvr, 0, sizeof(context->wvr));
}
} // namespace google_breakpad

View file

@ -0,0 +1,41 @@
// Copyright 2018 Google LLC
//
// 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 LLC 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.
#ifndef PROCESSOR_CONVERT_OLD_ARM64_CONTEXT_H__
#define PROCESSOR_CONVERT_OLD_ARM64_CONTEXT_H__
#include "google_breakpad/common/minidump_cpu_arm64.h"
namespace google_breakpad {
void ConvertOldARM64Context(const MDRawContextARM64_Old& old,
MDRawContextARM64* context);
} // namespace google_breakpad
#endif // PROCESSOR_CONVERT_OLD_ARM64_CONTEXT_H__

View file

@ -0,0 +1,487 @@
// Copyright (c) 2022, Google LLC
//
// 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 LLC 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.
// disassembler_objdump.: Disassembler that invokes objdump for disassembly.
//
// Author: Mark Brand
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "processor/disassembler_objdump.h"
#include <unistd.h>
#include <sys/wait.h>
#include <array>
#include <fstream>
#include <iostream>
#include <iterator>
#include <regex>
#include <sstream>
#include <vector>
#include "common/linux/eintr_wrapper.h"
#include "common/linux/scoped_pipe.h"
#include "common/linux/scoped_tmpfile.h"
#include "processor/logging.h"
namespace google_breakpad {
namespace {
const size_t kMaxX86InstructionLength = 15;
bool IsInstructionPrefix(const string& token) {
if (token == "lock" || token == "rep" || token == "repz" ||
token == "repnz") {
return true;
}
return false;
}
bool IsOperandSize(const string& token) {
if (token == "BYTE" || token == "WORD" || token == "DWORD" ||
token == "QWORD" || token == "PTR") {
return true;
}
return false;
}
bool GetSegmentAddressX86(const DumpContext& context, string segment_name,
uint64_t& address) {
if (segment_name == "ds") {
address = context.GetContextX86()->ds;
} else if (segment_name == "es") {
address = context.GetContextX86()->es;
} else if (segment_name == "fs") {
address = context.GetContextX86()->fs;
} else if (segment_name == "gs") {
address = context.GetContextX86()->gs;
} else {
BPLOG(ERROR) << "Unsupported segment register: " << segment_name;
return false;
}
return true;
}
bool GetSegmentAddressAMD64(const DumpContext& context, string segment_name,
uint64_t& address) {
if (segment_name == "ds") {
address = 0;
} else if (segment_name == "es") {
address = 0;
} else {
BPLOG(ERROR) << "Unsupported segment register: " << segment_name;
return false;
}
return true;
}
bool GetSegmentAddress(const DumpContext& context, string segment_name,
uint64_t& address) {
if (context.GetContextCPU() == MD_CONTEXT_X86) {
return GetSegmentAddressX86(context, segment_name, address);
} else if (context.GetContextCPU() == MD_CONTEXT_AMD64) {
return GetSegmentAddressAMD64(context, segment_name, address);
} else {
BPLOG(ERROR) << "Unsupported architecture for GetSegmentAddress\n";
return false;
}
}
bool GetRegisterValueX86(const DumpContext& context, string register_name,
uint64_t& value) {
if (register_name == "eax") {
value = context.GetContextX86()->eax;
} else if (register_name == "ebx") {
value = context.GetContextX86()->ebx;
} else if (register_name == "ecx") {
value = context.GetContextX86()->ecx;
} else if (register_name == "edx") {
value = context.GetContextX86()->edx;
} else if (register_name == "edi") {
value = context.GetContextX86()->edi;
} else if (register_name == "esi") {
value = context.GetContextX86()->esi;
} else if (register_name == "ebp") {
value = context.GetContextX86()->ebp;
} else if (register_name == "esp") {
value = context.GetContextX86()->esp;
} else if (register_name == "eip") {
value = context.GetContextX86()->eip;
} else {
BPLOG(ERROR) << "Unsupported register: " << register_name;
return false;
}
return true;
}
bool GetRegisterValueAMD64(const DumpContext& context, string register_name,
uint64_t& value) {
if (register_name == "rax") {
value = context.GetContextAMD64()->rax;
} else if (register_name == "rbx") {
value = context.GetContextAMD64()->rbx;
} else if (register_name == "rcx") {
value = context.GetContextAMD64()->rcx;
} else if (register_name == "rdx") {
value = context.GetContextAMD64()->rdx;
} else if (register_name == "rdi") {
value = context.GetContextAMD64()->rdi;
} else if (register_name == "rsi") {
value = context.GetContextAMD64()->rsi;
} else if (register_name == "rbp") {
value = context.GetContextAMD64()->rbp;
} else if (register_name == "rsp") {
value = context.GetContextAMD64()->rsp;
} else if (register_name == "r8") {
value = context.GetContextAMD64()->r8;
} else if (register_name == "r9") {
value = context.GetContextAMD64()->r9;
} else if (register_name == "r10") {
value = context.GetContextAMD64()->r10;
} else if (register_name == "r11") {
value = context.GetContextAMD64()->r11;
} else if (register_name == "r12") {
value = context.GetContextAMD64()->r12;
} else if (register_name == "r13") {
value = context.GetContextAMD64()->r13;
} else if (register_name == "r14") {
value = context.GetContextAMD64()->r14;
} else if (register_name == "r15") {
value = context.GetContextAMD64()->r15;
} else if (register_name == "rip") {
value = context.GetContextAMD64()->rip;
} else {
BPLOG(ERROR) << "Unsupported register: " << register_name;
return false;
}
return true;
}
// Lookup the value of `register_name` in `context`, store it into `value` on
// success.
// Support for non-full-size registers not implemented, since we're only using
// this to evaluate address expressions.
bool GetRegisterValue(const DumpContext& context, string register_name,
uint64_t& value) {
if (context.GetContextCPU() == MD_CONTEXT_X86) {
return GetRegisterValueX86(context, register_name, value);
} else if (context.GetContextCPU() == MD_CONTEXT_AMD64) {
return GetRegisterValueAMD64(context, register_name, value);
} else {
BPLOG(ERROR) << "Unsupported architecture for GetRegisterValue\n";
return false;
}
}
} // namespace
// static
bool DisassemblerObjdump::DisassembleInstruction(uint32_t cpu,
const uint8_t* raw_bytes,
unsigned int raw_bytes_len,
string& instruction) {
// Always initialize outputs
instruction = "";
if (!raw_bytes || raw_bytes_len == 0) {
// There's no need to perform any operation in this case, as there's
// clearly no instruction there.
return false;
}
string architecture;
if (cpu == MD_CONTEXT_X86) {
architecture = "i386";
} else if (cpu == MD_CONTEXT_AMD64) {
architecture = "i386:x86-64";
} else {
BPLOG(ERROR) << "Unsupported architecture.";
return false;
}
// Create a temporary file for the raw instruction bytes to pass to
// objdump, and write the bytes to the input file.
ScopedTmpFile raw_bytes_file;
if (!raw_bytes_file.InitData(raw_bytes, raw_bytes_len)) {
BPLOG(ERROR) << "Failed creating temporary file.";
return false;
}
// Create a pipe to use to read the disassembly back from objdump.
ScopedPipe disassembly_pipe;
if (!disassembly_pipe.Init()) {
BPLOG(ERROR) << "Failed creating pipe for output.";
return false;
}
pid_t child_pid = fork();
if (child_pid < 0) {
BPLOG(ERROR) << "Fork failed.";
return false;
}
if (child_pid == 0) {
// In the child process, set up the input and output file descriptors.
if (dup2(raw_bytes_file.GetFd(), STDIN_FILENO) < 0 ||
disassembly_pipe.Dup2WriteFd(STDOUT_FILENO) < 0 ||
disassembly_pipe.Dup2WriteFd(STDERR_FILENO) < 0) {
BPLOG(ERROR) << "Failed dup'ing file descriptors.";
exit(-1);
}
// We need to close the read end of the pipe in the child process so that
// when the parent closes it, the pipe is disconnected.
disassembly_pipe.CloseReadFd();
// We use "/proc/self/fd/0" here to allow objdump to parse an unnamed file,
// since objdump does not have a mode to read from stdin. This cannot be
// used with a pipe, since objdump requires that the input is a standard
// file.
execlp("objdump", "objdump", "-D", "--no-show-raw-insn", "-b", "binary",
"-M", "intel", "-m", architecture.c_str(), "/proc/self/fd/0",
nullptr);
BPLOG(ERROR) << "Failed to exec objdump.";
exit(-1);
} else {
// In the parent process, parse the objdump output.
// Match the instruction line, from:
// 0: lock cmpxchg DWORD PTR [esi+0x10],eax
// extract the string "lock cmpxchg DWORD PTR [esi+0x10],eax"
std::regex instruction_regex(
"^\\s+[0-9a-f]+:\\s+" // " 0:"
"((?:\\s*\\S*)+)$"); // "lock cmpxchg..."
std::string line;
std::smatch match;
while (disassembly_pipe.ReadLine(line)) {
if (std::regex_match(line, match, instruction_regex)) {
instruction = match[1].str();
break;
}
}
// Close the read pipe so that objdump will exit (in case we broke out of
// the loop above before reading all of the output).
disassembly_pipe.CloseReadFd();
// Now wait for objdump to exit.
int status = 0;
HANDLE_EINTR(waitpid(child_pid, &status, 0));
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
BPLOG(ERROR) << "objdump didn't run successfully.";
return false;
}
if (instruction == "") {
BPLOG(ERROR) << "Failed to find instruction in objdump output.";
return false;
}
}
return true;
}
// static
bool DisassemblerObjdump::TokenizeInstruction(const string& instruction,
string& operation, string& dest,
string& src) {
// Always initialize outputs.
operation = "";
dest = "";
src = "";
// Split the instruction into tokens by either whitespace or comma.
std::regex token_regex("((?:[^\\s,]+)|,)(?:\\s)*");
std::sregex_iterator tokens_begin(instruction.begin(), instruction.end(),
token_regex);
bool found_comma = false;
for (auto tokens_iter = tokens_begin; tokens_iter != std::sregex_iterator();
++tokens_iter) {
auto token = (*tokens_iter)[1].str();
if (operation.size() == 0) {
if (IsInstructionPrefix(token))
continue;
operation = token;
} else if (dest.size() == 0) {
if (IsOperandSize(token))
continue;
dest = token;
} else if (!found_comma) {
if (token == ",") {
found_comma = true;
} else {
BPLOG(ERROR) << "Failed to parse operands from objdump output, expected"
" comma but found \""
<< token << "\"";
return false;
}
} else if (src.size() == 0) {
if (IsOperandSize(token))
continue;
src = token;
} else {
if (token == ",") {
BPLOG(ERROR) << "Failed to parse operands from objdump output, found "
"unexpected comma after last operand.";
return false;
} else {
// We just ignore other junk after the last operand unless it's a
// comma, which would indicate we're probably still in the middle
// of the operands and something has gone wrong
}
}
}
if (found_comma && src.size() == 0) {
BPLOG(ERROR) << "Failed to parse operands from objdump output, found comma "
"but no src operand.";
return false;
}
return true;
}
// static
bool DisassemblerObjdump::CalculateAddress(const DumpContext& context,
const string& expression,
uint64_t& address) {
address = 0;
// Extract the components of the expression.
// fs:[esi+edi*4+0x80] -> ["fs", "esi", "edi", "4", "-", "0x80"]
std::regex expression_regex(
"^(?:(\\ws):)?" // "fs:"
"\\[(\\w+)" // "[esi"
"(?:\\+(\\w+)(?:\\*(\\d+)))?" // "+edi*4"
"(?:([\\+-])(0x[0-9a-f]+))?" // "-0x80"
"\\]$"); // "]"
std::smatch match;
if (!std::regex_match(expression, match, expression_regex) ||
match.size() != 7) {
return false;
}
string segment_name = match[1].str();
string register_name = match[2].str();
string index_name = match[3].str();
string index_stride = match[4].str();
string offset_sign = match[5].str();
string offset = match[6].str();
uint64_t segment_address = 0;
uint64_t register_value = 0;
uint64_t index_value = 0;
uint64_t index_stride_value = 1;
uint64_t offset_value = 0;
if (segment_name.size() &&
!GetSegmentAddress(context, segment_name, segment_address)) {
return false;
}
if (!GetRegisterValue(context, register_name, register_value)) {
return false;
}
if (index_name.size() &&
!GetRegisterValue(context, index_name, index_value)) {
return false;
}
if (index_stride.size()) {
index_stride_value = strtoull(index_stride.c_str(), nullptr, 0);
}
if (offset.size()) {
offset_value = strtoull(offset.c_str(), nullptr, 0);
}
address =
segment_address + register_value + (index_value * index_stride_value);
if (offset_sign == "+") {
address += offset_value;
} else if (offset_sign == "-") {
address -= offset_value;
}
return true;
}
DisassemblerObjdump::DisassemblerObjdump(const uint32_t cpu,
const MemoryRegion* memory_region,
uint64_t address) {
if (address < memory_region->GetBase() ||
memory_region->GetBase() + memory_region->GetSize() <= address) {
return;
}
uint8_t ip_bytes[kMaxX86InstructionLength] = {0};
size_t ip_bytes_length;
for (ip_bytes_length = 0; ip_bytes_length < kMaxX86InstructionLength;
++ip_bytes_length) {
// We have to read byte-by-byte here, since we still want to try and
// disassemble an instruction even if we don't have enough bytes.
if (!memory_region->GetMemoryAtAddress(address + ip_bytes_length,
&ip_bytes[ip_bytes_length])) {
break;
}
}
string instruction;
if (!DisassembleInstruction(cpu, ip_bytes, kMaxX86InstructionLength,
instruction)) {
return;
}
if (!TokenizeInstruction(instruction, operation_, dest_, src_)) {
return;
}
}
bool DisassemblerObjdump::CalculateSrcAddress(const DumpContext& context,
uint64_t& address) {
return CalculateAddress(context, src_, address);
}
bool DisassemblerObjdump::CalculateDestAddress(const DumpContext& context,
uint64_t& address) {
return CalculateAddress(context, dest_, address);
}
} // namespace google_breakpad

View file

@ -0,0 +1,142 @@
// Copyright (c) 2022, Google LLC
//
// 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 LLC 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.
// disassembler_objdump.h: Disassembler that invokes objdump for disassembly.
//
// Author: Mark Brand
#ifndef GOOGLE_BREAKPAD_PROCESSOR_DISASSEMBLER_OBJDUMP_H_
#define GOOGLE_BREAKPAD_PROCESSOR_DISASSEMBLER_OBJDUMP_H_
#include <string>
#include "common/using_std_string.h"
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/processor/dump_context.h"
#include "google_breakpad/processor/memory_region.h"
namespace google_breakpad {
// Uses objdump to disassemble a single instruction.
//
// Currently supports disassembly for x86 and x86_64 on linux hosts only; on
// unsupported platform or for unsupported architectures disassembly will fail.
//
// If disassembly is successful, then this allows extracting the instruction
// opcode, source and destination operands, and computing the source and
// destination addresses for instructions that operate on memory.
//
// Example:
// DisassemblerObjdump disassembler(context->GetContextCPU(), memory_region,
// instruction_ptr);
// if (disassembler.IsValid()) {
// uint64_t src_address = 0;
// std::cerr << disassembler.operation() << " " << disassembler.src()
// << ", " << disassembler.dest() << std::endl;
// if (disassembler.CalculateSrcAddress(*context, src_address)) {
// std::cerr << "[src_address = " << std::hex << src_address << "]\n";
// }
// }
class DisassemblerObjdump {
public:
// Construct an ObjdumpDisassembler for the provided `cpu` type, where this is
// one of MD_CONTEXT_X86 or MD_CONTEXT_AMD64. Provided that `address` is
// within `memory_region`, and the memory referenced is a valid instruction,
// this will then be initialized with the disassembly for that instruction.
DisassemblerObjdump(uint32_t cpu,
const MemoryRegion* memory_region,
uint64_t address);
~DisassemblerObjdump() = default;
// If the source operand of the instruction is a memory operand, compute the
// address referred to by the operand, and store this in `address`. On success
// returns true, otherwise (if computation fails, or if the source operand is
// not a memory operand) returns false and sets `address` to 0.
bool CalculateSrcAddress(const DumpContext& context, uint64_t& address);
// If the destination operand of the instruction is a memory operand, compute
// the address referred to by the operand, and store this in `address`. On
// success returns true, otherwise (if computation fails, or if the source
// operand is not a memory operand) returns false and sets `address` to 0.
bool CalculateDestAddress(const DumpContext& context, uint64_t& address);
// If the instruction was disassembled successfully, this will be true.
bool IsValid() const { return operation_.size() != 0; }
// Returns the operation part of the disassembly, without any prefixes:
// "pop" eax
// lock "xchg" eax, edx
const string& operation() const { return operation_; }
// Returns the destination operand of the disassembly, without memory operand
// size prefixes:
// mov DWORD PTR "[rax + 16]", edx
const string& dest() const { return dest_; }
// Returns the source operand of the disassembly, without memory operand
// size prefixes:
// mov rax, QWORD PTR "[rdx]"
const string& src() const { return src_; }
private:
friend class DisassemblerObjdumpForTest;
// Writes out the provided `raw_bytes` to a temporary file, and executes objdump
// to disassemble according to `cpu`, which must be either MD_CONTEXT_X86 or
// MD_CONTEXT_AMD64. Once objdump has completed, parses out the instruction
// string from the first instruction in the output and stores it in
// `instruction`.
static bool DisassembleInstruction(uint32_t cpu, const uint8_t* raw_bytes,
unsigned int raw_bytes_len,
string& instruction);
// Splits an `instruction` into three parts, the "main" `operation` and
// the `dest` and `src` operands.
// Example:
// instruction = "lock cmpxchg QWORD PTR [rdi], rsi"
// operation = "cmpxchg", dest = "[rdi]", src = "rsi"
static bool TokenizeInstruction(const string& instruction, string& operation,
string& dest, string& src);
// Compute the address referenced by `expression` in `context`.
// Supports memory operands in the form
// (segment:)[base_reg(+index_reg*index_stride)(+-offset)]
// Returns false if evaluation fails, or if the operand is not a supported
// memory operand.
static bool CalculateAddress(const DumpContext& context,
const string& expression,
uint64_t& address);
// The parsed components of the disassembly for the instruction.
string operation_ = "";
string dest_ = "";
string src_ = "";
};
} // namespace google_breakpad
#endif // GOOGLE_BREAKPAD_PROCESSOR_DISASSEMBLER_OBJDUMP_H_

View file

@ -0,0 +1,468 @@
// Copyright (c) 2022, Google LLC
//
// 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 LLC 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.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <unistd.h>
#include <vector>
#include "breakpad_googletest_includes.h"
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/common/minidump_cpu_amd64.h"
#include "google_breakpad/common/minidump_cpu_x86.h"
#include "google_breakpad/processor/dump_context.h"
#include "google_breakpad/processor/memory_region.h"
#include "processor/disassembler_objdump.h"
namespace google_breakpad {
class DisassemblerObjdumpForTest : public DisassemblerObjdump {
public:
using DisassemblerObjdump::CalculateAddress;
using DisassemblerObjdump::DisassembleInstruction;
using DisassemblerObjdump::TokenizeInstruction;
};
class TestMemoryRegion : public MemoryRegion {
public:
TestMemoryRegion(uint64_t base, std::vector<uint8_t> bytes);
~TestMemoryRegion() override = default;
uint64_t GetBase() const override;
uint32_t GetSize() const override;
bool GetMemoryAtAddress(uint64_t address, uint8_t* value) const override;
bool GetMemoryAtAddress(uint64_t address, uint16_t* value) const override;
bool GetMemoryAtAddress(uint64_t address, uint32_t* value) const override;
bool GetMemoryAtAddress(uint64_t address, uint64_t* value) const override;
void Print() const override;
private:
uint64_t base_;
std::vector<uint8_t> bytes_;
};
TestMemoryRegion::TestMemoryRegion(uint64_t address, std::vector<uint8_t> bytes)
: base_(address), bytes_(bytes) {}
uint64_t TestMemoryRegion::GetBase() const {
return base_;
}
uint32_t TestMemoryRegion::GetSize() const {
return static_cast<uint32_t>(bytes_.size());
}
bool TestMemoryRegion::GetMemoryAtAddress(uint64_t address,
uint8_t* value) const {
if (address < GetBase() ||
address + sizeof(uint8_t) > GetBase() + GetSize()) {
return false;
}
memcpy(value, &bytes_[address - GetBase()], sizeof(uint8_t));
return true;
}
// We don't use the following functions, so no need to implement.
bool TestMemoryRegion::GetMemoryAtAddress(uint64_t address,
uint16_t* value) const {
return false;
}
bool TestMemoryRegion::GetMemoryAtAddress(uint64_t address,
uint32_t* value) const {
return false;
}
bool TestMemoryRegion::GetMemoryAtAddress(uint64_t address,
uint64_t* value) const {
return false;
}
void TestMemoryRegion::Print() const {}
const uint32_t kX86TestDs = 0x01000000;
const uint32_t kX86TestEs = 0x02000000;
const uint32_t kX86TestFs = 0x03000000;
const uint32_t kX86TestGs = 0x04000000;
const uint32_t kX86TestEax = 0x00010101;
const uint32_t kX86TestEbx = 0x00020202;
const uint32_t kX86TestEcx = 0x00030303;
const uint32_t kX86TestEdx = 0x00040404;
const uint32_t kX86TestEsi = 0x00050505;
const uint32_t kX86TestEdi = 0x00060606;
const uint32_t kX86TestEsp = 0x00070707;
const uint32_t kX86TestEbp = 0x00080808;
const uint32_t kX86TestEip = 0x23230000;
const uint64_t kAMD64TestRax = 0x0000010101010101ul;
const uint64_t kAMD64TestRbx = 0x0000020202020202ul;
const uint64_t kAMD64TestRcx = 0x0000030303030303ul;
const uint64_t kAMD64TestRdx = 0x0000040404040404ul;
const uint64_t kAMD64TestRsi = 0x0000050505050505ul;
const uint64_t kAMD64TestRdi = 0x0000060606060606ul;
const uint64_t kAMD64TestRsp = 0x0000070707070707ul;
const uint64_t kAMD64TestRbp = 0x0000080808080808ul;
const uint64_t kAMD64TestR8 = 0x0000090909090909ul;
const uint64_t kAMD64TestR9 = 0x00000a0a0a0a0a0aul;
const uint64_t kAMD64TestR10 = 0x00000b0b0b0b0b0bul;
const uint64_t kAMD64TestR11 = 0x00000c0c0c0c0c0cul;
const uint64_t kAMD64TestR12 = 0x00000d0d0d0d0d0dul;
const uint64_t kAMD64TestR13 = 0x00000e0e0e0e0e0eul;
const uint64_t kAMD64TestR14 = 0x00000f0f0f0f0f0ful;
const uint64_t kAMD64TestR15 = 0x0000001010101010ul;
const uint64_t kAMD64TestRip = 0x0000000023230000ul;
class TestDumpContext : public DumpContext {
public:
TestDumpContext(bool x86_64 = false);
~TestDumpContext() override;
};
TestDumpContext::TestDumpContext(bool x86_64) {
if (!x86_64) {
MDRawContextX86* raw_context = new MDRawContextX86();
memset(raw_context, 0, sizeof(*raw_context));
raw_context->context_flags = MD_CONTEXT_X86_FULL;
raw_context->ds = kX86TestDs;
raw_context->es = kX86TestEs;
raw_context->fs = kX86TestFs;
raw_context->gs = kX86TestGs;
raw_context->eax = kX86TestEax;
raw_context->ebx = kX86TestEbx;
raw_context->ecx = kX86TestEcx;
raw_context->edx = kX86TestEdx;
raw_context->esi = kX86TestEsi;
raw_context->edi = kX86TestEdi;
raw_context->esp = kX86TestEsp;
raw_context->ebp = kX86TestEbp;
raw_context->eip = kX86TestEip;
SetContextFlags(raw_context->context_flags);
SetContextX86(raw_context);
this->valid_ = true;
} else {
MDRawContextAMD64* raw_context = new MDRawContextAMD64();
memset(raw_context, 0, sizeof(*raw_context));
raw_context->context_flags = MD_CONTEXT_AMD64_FULL;
raw_context->rax = kAMD64TestRax;
raw_context->rbx = kAMD64TestRbx;
raw_context->rcx = kAMD64TestRcx;
raw_context->rdx = kAMD64TestRdx;
raw_context->rsi = kAMD64TestRsi;
raw_context->rdi = kAMD64TestRdi;
raw_context->rsp = kAMD64TestRsp;
raw_context->rbp = kAMD64TestRbp;
raw_context->r8 = kAMD64TestR8;
raw_context->r9 = kAMD64TestR9;
raw_context->r10 = kAMD64TestR10;
raw_context->r11 = kAMD64TestR11;
raw_context->r12 = kAMD64TestR12;
raw_context->r13 = kAMD64TestR13;
raw_context->r14 = kAMD64TestR14;
raw_context->r15 = kAMD64TestR15;
raw_context->rip = kAMD64TestRip;
SetContextFlags(raw_context->context_flags);
SetContextAMD64(raw_context);
this->valid_ = true;
}
}
TestDumpContext::~TestDumpContext() {
FreeContext();
}
TEST(DisassemblerObjdumpTest, DisassembleInstructionX86) {
string instruction;
ASSERT_FALSE(DisassemblerObjdumpForTest::DisassembleInstruction(
MD_CONTEXT_X86, nullptr, 0, instruction));
std::vector<uint8_t> pop_eax = {0x58};
ASSERT_TRUE(DisassemblerObjdumpForTest::DisassembleInstruction(
MD_CONTEXT_X86, pop_eax.data(), pop_eax.size(), instruction));
ASSERT_EQ(instruction, "pop eax");
}
TEST(DisassemblerObjdumpTest, DisassembleInstructionAMD64) {
string instruction;
ASSERT_FALSE(DisassemblerObjdumpForTest::DisassembleInstruction(
MD_CONTEXT_AMD64, nullptr, 0, instruction));
std::vector<uint8_t> pop_rax = {0x58};
ASSERT_TRUE(DisassemblerObjdumpForTest::DisassembleInstruction(
MD_CONTEXT_AMD64, pop_rax.data(), pop_rax.size(), instruction));
ASSERT_EQ(instruction, "pop rax");
}
TEST(DisassemblerObjdumpTest, TokenizeInstruction) {
string operation, dest, src;
ASSERT_TRUE(DisassemblerObjdumpForTest::TokenizeInstruction(
"pop eax", operation, dest, src));
ASSERT_EQ(operation, "pop");
ASSERT_EQ(dest, "eax");
ASSERT_TRUE(DisassemblerObjdumpForTest::TokenizeInstruction(
"mov eax, ebx", operation, dest, src));
ASSERT_EQ(operation, "mov");
ASSERT_EQ(dest, "eax");
ASSERT_EQ(src, "ebx");
ASSERT_TRUE(DisassemblerObjdumpForTest::TokenizeInstruction(
"pop rax", operation, dest, src));
ASSERT_EQ(operation, "pop");
ASSERT_EQ(dest, "rax");
ASSERT_TRUE(DisassemblerObjdumpForTest::TokenizeInstruction(
"mov rax, rbx", operation, dest, src));
ASSERT_EQ(operation, "mov");
ASSERT_EQ(dest, "rax");
ASSERT_EQ(src, "rbx");
// Test the three parsing failure paths
ASSERT_FALSE(DisassemblerObjdumpForTest::TokenizeInstruction(
"mov rax,", operation, dest, src));
ASSERT_FALSE(DisassemblerObjdumpForTest::TokenizeInstruction(
"mov rax rbx", operation, dest, src));
ASSERT_FALSE(DisassemblerObjdumpForTest::TokenizeInstruction(
"mov rax, rbx, rcx", operation, dest, src));
// This is of course a nonsense instruction, but test that we do remove
// multiple instruction prefixes and can handle multiple memory operands.
ASSERT_TRUE(DisassemblerObjdumpForTest::TokenizeInstruction(
"rep lock mov DWORD PTR rax, QWORD PTR rbx", operation, dest, src));
ASSERT_EQ(operation, "mov");
ASSERT_EQ(dest, "rax");
ASSERT_EQ(src, "rbx");
// Test that we ignore junk following a valid instruction
ASSERT_TRUE(DisassemblerObjdumpForTest::TokenizeInstruction(
"mov rax, rbx ; junk here", operation, dest, src));
ASSERT_EQ(operation, "mov");
ASSERT_EQ(dest, "rax");
ASSERT_EQ(src, "rbx");
}
namespace x86 {
const TestMemoryRegion load_reg(kX86TestEip, {0x8b, 0x06}); // mov eax, [esi];
const TestMemoryRegion load_reg_index(kX86TestEip,
{0x8b, 0x04,
0xbe}); // mov eax, [esi+edi*4];
const TestMemoryRegion load_reg_offset(kX86TestEip,
{0x8b, 0x46,
0x10}); // mov eax, [esi+0x10];
const TestMemoryRegion load_reg_index_offset(
kX86TestEip,
{0x8b, 0x44, 0xbe, 0xf0}); // mov eax, [esi+edi*4-0x10];
const TestMemoryRegion rep_stosb(kX86TestEip, {0xf3, 0xaa}); // rep stosb;
const TestMemoryRegion lock_cmpxchg(kX86TestEip,
{0xf0, 0x0f, 0xb1, 0x46,
0x10}); // lock cmpxchg [esi + 0x10], eax;
const TestMemoryRegion call_reg_offset(kX86TestEip,
{0xff, 0x96, 0x99, 0x99, 0x99,
0x09}); // call [esi+0x9999999];
} // namespace x86
TEST(DisassemblerObjdumpTest, X86LoadReg) {
TestDumpContext context;
DisassemblerObjdump dis(context.GetContextCPU(), &x86::load_reg, kX86TestEip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_FALSE(dis.CalculateDestAddress(context, dest_address));
ASSERT_TRUE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(src_address, kX86TestEsi);
}
TEST(DisassemblerObjdumpTest, X86LoadRegIndex) {
TestDumpContext context;
DisassemblerObjdump dis(context.GetContextCPU(), &x86::load_reg_index,
kX86TestEip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_FALSE(dis.CalculateDestAddress(context, dest_address));
ASSERT_TRUE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(src_address, kX86TestEsi + (kX86TestEdi * 4));
}
TEST(DisassemblerObjdumpTest, X86LoadRegOffset) {
TestDumpContext context;
DisassemblerObjdump dis(context.GetContextCPU(), &x86::load_reg_offset,
kX86TestEip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_FALSE(dis.CalculateDestAddress(context, dest_address));
ASSERT_TRUE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(src_address, kX86TestEsi + 0x10);
}
TEST(DisassemblerObjdumpTest, X86LoadRegIndexOffset) {
TestDumpContext context;
DisassemblerObjdump dis(context.GetContextCPU(), &x86::load_reg_index_offset,
kX86TestEip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_FALSE(dis.CalculateDestAddress(context, dest_address));
ASSERT_TRUE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(src_address, kX86TestEsi + (kX86TestEdi * 4) - 0x10);
}
TEST(DisassemblerObjdumpTest, X86RepStosb) {
TestDumpContext context;
DisassemblerObjdump dis(context.GetContextCPU(), &x86::rep_stosb,
kX86TestEip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_TRUE(dis.CalculateDestAddress(context, dest_address));
ASSERT_FALSE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(dest_address, kX86TestEs + kX86TestEdi);
}
TEST(DisassemblerObjdumpTest, X86LockCmpxchg) {
TestDumpContext context;
DisassemblerObjdump dis(context.GetContextCPU(), &x86::lock_cmpxchg,
kX86TestEip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_TRUE(dis.CalculateDestAddress(context, dest_address));
ASSERT_FALSE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(dest_address, kX86TestEsi + 0x10);
}
TEST(DisassemblerObjdumpTest, X86CallRegOffset) {
TestDumpContext context;
DisassemblerObjdump dis(context.GetContextCPU(), &x86::call_reg_offset,
kX86TestEip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_TRUE(dis.CalculateDestAddress(context, dest_address));
ASSERT_FALSE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(dest_address, kX86TestEsi + 0x9999999);
}
namespace amd64 {
const TestMemoryRegion load_reg(kAMD64TestRip,
{0x48, 0x8b, 0x06}); // mov rax, [rsi];
const TestMemoryRegion load_reg_index(kAMD64TestRip,
{0x48, 0x8b, 0x04,
0xbe}); // mov rax, [rsi+rdi*4];
const TestMemoryRegion load_rip_relative(kAMD64TestRip,
{0x48, 0x8b, 0x05, 0x10, 0x00, 0x00,
0x00}); // mov rax, [rip+0x10];
const TestMemoryRegion load_reg_index_offset(
kAMD64TestRip,
{0x48, 0x8b, 0x44, 0xbe, 0xf0}); // mov rax, [rsi+rdi*4-0x10];
const TestMemoryRegion rep_stosb(kAMD64TestRip, {0xf3, 0xaa}); // rep stosb;
const TestMemoryRegion lock_cmpxchg(kAMD64TestRip,
{0xf0, 0x48, 0x0f, 0xb1, 0x46,
0x10}); // lock cmpxchg [rsi + 0x10], rax;
const TestMemoryRegion call_reg_offset(kAMD64TestRip,
{0xff, 0x96, 0x99, 0x99, 0x99,
0x09}); // call [rsi+0x9999999];
} // namespace amd64
TEST(DisassemblerObjdumpTest, AMD64LoadReg) {
TestDumpContext context(true);
DisassemblerObjdump dis(context.GetContextCPU(), &amd64::load_reg,
kAMD64TestRip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_FALSE(dis.CalculateDestAddress(context, dest_address));
ASSERT_TRUE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(src_address, kAMD64TestRsi);
}
TEST(DisassemblerObjdumpTest, AMD64LoadRegIndex) {
TestDumpContext context(true);
DisassemblerObjdump dis(context.GetContextCPU(), &amd64::load_reg_index,
kAMD64TestRip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_FALSE(dis.CalculateDestAddress(context, dest_address));
ASSERT_TRUE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(src_address, kAMD64TestRsi + (kAMD64TestRdi * 4));
}
TEST(DisassemblerObjdumpTest, AMD64LoadRipRelative) {
TestDumpContext context(true);
DisassemblerObjdump dis(context.GetContextCPU(), &amd64::load_rip_relative,
kAMD64TestRip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_FALSE(dis.CalculateDestAddress(context, dest_address));
ASSERT_TRUE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(src_address, kAMD64TestRip + 0x10);
}
TEST(DisassemblerObjdumpTest, AMD64LoadRegIndexOffset) {
TestDumpContext context(true);
DisassemblerObjdump dis(context.GetContextCPU(),
&amd64::load_reg_index_offset, kAMD64TestRip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_FALSE(dis.CalculateDestAddress(context, dest_address));
ASSERT_TRUE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(src_address, kAMD64TestRsi + (kAMD64TestRdi * 4) - 0x10);
}
TEST(DisassemblerObjdumpTest, AMD64RepStosb) {
TestDumpContext context(true);
DisassemblerObjdump dis(context.GetContextCPU(), &amd64::rep_stosb,
kAMD64TestRip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_TRUE(dis.CalculateDestAddress(context, dest_address));
ASSERT_FALSE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(dest_address, kAMD64TestRdi);
}
TEST(DisassemblerObjdumpTest, AMD64LockCmpxchg) {
TestDumpContext context(true);
DisassemblerObjdump dis(context.GetContextCPU(), &amd64::lock_cmpxchg,
kAMD64TestRip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_TRUE(dis.CalculateDestAddress(context, dest_address));
ASSERT_FALSE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(dest_address, kAMD64TestRsi + 0x10);
}
TEST(DisassemblerObjdumpTest, AMD64CallRegOffset) {
TestDumpContext context(true);
DisassemblerObjdump dis(context.GetContextCPU(), &amd64::call_reg_offset,
kAMD64TestRip);
uint64_t src_address = 0, dest_address = 0;
ASSERT_TRUE(dis.CalculateDestAddress(context, dest_address));
ASSERT_FALSE(dis.CalculateSrcAddress(context, src_address));
ASSERT_EQ(dest_address, kAMD64TestRsi + 0x9999999);
}
} // namespace google_breakpad

View file

@ -0,0 +1,253 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// disassembler_x86.cc: simple x86 disassembler.
//
// Provides single step disassembly of x86 bytecode and flags instructions
// that utilize known bad register values.
//
// Author: Cris Neckar
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "processor/disassembler_x86.h"
#include <string.h>
namespace google_breakpad {
DisassemblerX86::DisassemblerX86(const uint8_t* bytecode,
uint32_t size,
uint32_t virtual_address) :
bytecode_(bytecode),
size_(size),
virtual_address_(virtual_address),
current_byte_offset_(0),
current_inst_offset_(0),
instr_valid_(false),
register_valid_(false),
pushed_bad_value_(false),
end_of_block_(false),
flags_(0) {
libdis::x86_init(libdis::opt_none, NULL, NULL);
}
DisassemblerX86::~DisassemblerX86() {
if (instr_valid_)
libdis::x86_oplist_free(&current_instr_);
libdis::x86_cleanup();
}
uint32_t DisassemblerX86::NextInstruction() {
if (instr_valid_)
libdis::x86_oplist_free(&current_instr_);
if (current_byte_offset_ >= size_) {
instr_valid_ = false;
return 0;
}
uint32_t instr_size = 0;
instr_size = libdis::x86_disasm((unsigned char*)bytecode_, size_,
virtual_address_, current_byte_offset_,
&current_instr_);
if (instr_size == 0) {
instr_valid_ = false;
return 0;
}
current_byte_offset_ += instr_size;
current_inst_offset_++;
instr_valid_ = libdis::x86_insn_is_valid(&current_instr_);
if (!instr_valid_)
return 0;
if (current_instr_.type == libdis::insn_return)
end_of_block_ = true;
libdis::x86_op_t* src = libdis::x86_get_src_operand(&current_instr_);
libdis::x86_op_t* dest = libdis::x86_get_dest_operand(&current_instr_);
if (register_valid_) {
switch (current_instr_.group) {
// Flag branches based off of bad registers and calls that occur
// after pushing bad values.
case libdis::insn_controlflow:
switch (current_instr_.type) {
case libdis::insn_jmp:
case libdis::insn_jcc:
case libdis::insn_call:
case libdis::insn_callcc:
if (dest) {
switch (dest->type) {
case libdis::op_expression:
if (dest->data.expression.base.id == bad_register_.id)
flags_ |= DISX86_BAD_BRANCH_TARGET;
break;
case libdis::op_register:
if (dest->data.reg.id == bad_register_.id)
flags_ |= DISX86_BAD_BRANCH_TARGET;
break;
default:
if (pushed_bad_value_ &&
(current_instr_.type == libdis::insn_call ||
current_instr_.type == libdis::insn_callcc))
flags_ |= DISX86_BAD_ARGUMENT_PASSED;
break;
}
}
break;
default:
break;
}
break;
// Flag block data operations that use bad registers for src or dest.
case libdis::insn_string:
if (dest && dest->type == libdis::op_expression &&
dest->data.expression.base.id == bad_register_.id)
flags_ |= DISX86_BAD_BLOCK_WRITE;
if (src && src->type == libdis::op_expression &&
src->data.expression.base.id == bad_register_.id)
flags_ |= DISX86_BAD_BLOCK_READ;
break;
// Flag comparisons based on bad data.
case libdis::insn_comparison:
if ((dest && dest->type == libdis::op_expression &&
dest->data.expression.base.id == bad_register_.id) ||
(src && src->type == libdis::op_expression &&
src->data.expression.base.id == bad_register_.id) ||
(dest && dest->type == libdis::op_register &&
dest->data.reg.id == bad_register_.id) ||
(src && src->type == libdis::op_register &&
src->data.reg.id == bad_register_.id))
flags_ |= DISX86_BAD_COMPARISON;
break;
// Flag any other instruction which derefs a bad register for
// src or dest.
default:
if (dest && dest->type == libdis::op_expression &&
dest->data.expression.base.id == bad_register_.id)
flags_ |= DISX86_BAD_WRITE;
if (src && src->type == libdis::op_expression &&
src->data.expression.base.id == bad_register_.id)
flags_ |= DISX86_BAD_READ;
break;
}
}
// When a register is marked as tainted check if it is pushed.
// TODO(cdn): may also want to check for MOVs into EBP offsets.
if (register_valid_ && dest && current_instr_.type == libdis::insn_push) {
switch (dest->type) {
case libdis::op_expression:
if (dest->data.expression.base.id == bad_register_.id ||
dest->data.expression.index.id == bad_register_.id)
pushed_bad_value_ = true;
break;
case libdis::op_register:
if (dest->data.reg.id == bad_register_.id)
pushed_bad_value_ = true;
break;
default:
break;
}
}
// Check if a tainted register value is clobbered.
// For conditional MOVs and XCHGs assume that
// there is a hit.
if (register_valid_) {
switch (current_instr_.type) {
case libdis::insn_xor:
if (src && src->type == libdis::op_register &&
dest && dest->type == libdis::op_register &&
src->data.reg.id == bad_register_.id &&
src->data.reg.id == dest->data.reg.id)
register_valid_ = false;
break;
case libdis::insn_pop:
case libdis::insn_mov:
case libdis::insn_movcc:
if (dest && dest->type == libdis::op_register &&
dest->data.reg.id == bad_register_.id)
register_valid_ = false;
break;
case libdis::insn_popregs:
register_valid_ = false;
break;
case libdis::insn_xchg:
case libdis::insn_xchgcc:
if (dest && dest->type == libdis::op_register &&
src && src->type == libdis::op_register) {
if (dest->data.reg.id == bad_register_.id)
memcpy(&bad_register_, &src->data.reg, sizeof(libdis::x86_reg_t));
else if (src->data.reg.id == bad_register_.id)
memcpy(&bad_register_, &dest->data.reg, sizeof(libdis::x86_reg_t));
}
break;
default:
break;
}
}
return instr_size;
}
bool DisassemblerX86::setBadRead() {
if (!instr_valid_)
return false;
libdis::x86_op_t* operand = libdis::x86_get_src_operand(&current_instr_);
if (!operand || operand->type != libdis::op_expression)
return false;
memcpy(&bad_register_, &operand->data.expression.base,
sizeof(libdis::x86_reg_t));
register_valid_ = true;
return true;
}
bool DisassemblerX86::setBadWrite() {
if (!instr_valid_)
return false;
libdis::x86_op_t* operand = libdis::x86_get_dest_operand(&current_instr_);
if (!operand || operand->type != libdis::op_expression)
return false;
memcpy(&bad_register_, &operand->data.expression.base,
sizeof(libdis::x86_reg_t));
register_valid_ = true;
return true;
}
} // namespace google_breakpad

View file

@ -0,0 +1,127 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// disassembler_x86.h: Basic x86 bytecode disassembler
//
// Provides a simple disassembler which wraps libdisasm. This allows simple
// tests to be run against bytecode to test for various properties.
//
// Author: Cris Neckar
#ifndef GOOGLE_BREAKPAD_PROCESSOR_DISASSEMBLER_X86_H_
#define GOOGLE_BREAKPAD_PROCESSOR_DISASSEMBLER_X86_H_
#include <stddef.h>
#include <sys/types.h>
#include "google_breakpad/common/breakpad_types.h"
namespace libdis {
#include "third_party/libdisasm/libdis.h"
}
namespace google_breakpad {
enum {
DISX86_NONE = 0x0,
DISX86_BAD_BRANCH_TARGET = 0x1,
DISX86_BAD_ARGUMENT_PASSED = 0x2,
DISX86_BAD_WRITE = 0x4,
DISX86_BAD_BLOCK_WRITE = 0x8,
DISX86_BAD_READ = 0x10,
DISX86_BAD_BLOCK_READ = 0x20,
DISX86_BAD_COMPARISON = 0x40
};
class DisassemblerX86 {
public:
// TODO(cdn): Modify this class to take a MemoryRegion instead of just
// a raw buffer. This will make it easier to use this on arbitrary
// minidumps without first copying out the code segment.
DisassemblerX86(const uint8_t* bytecode, uint32_t, uint32_t);
~DisassemblerX86();
// This walks to the next instruction in the memory region and
// sets flags based on the type of instruction and previous state
// including any registers marked as bad through setBadRead()
// or setBadWrite(). This method can be called in a loop to
// disassemble until the end of a region.
uint32_t NextInstruction();
// Indicates whether the current disassembled instruction was valid.
bool currentInstructionValid() { return instr_valid_; }
// Returns the current instruction as defined in libdis.h,
// or NULL if the current instruction is not valid.
const libdis::x86_insn_t* currentInstruction() {
return instr_valid_ ? &current_instr_ : NULL;
}
// Returns the type of the current instruction as defined in libdis.h.
libdis::x86_insn_group currentInstructionGroup() {
return current_instr_.group;
}
// Indicates whether a return instruction has been encountered.
bool endOfBlock() { return end_of_block_; }
// The flags set so far for the disassembly.
uint16_t flags() { return flags_; }
// This sets an indicator that the register used to determine
// src or dest for the current instruction is tainted. These can
// be used after examining the current instruction to indicate,
// for example that a bad read or write occurred and the pointer
// stored in the register is currently invalid.
bool setBadRead();
bool setBadWrite();
protected:
const uint8_t* bytecode_;
uint32_t size_;
uint32_t virtual_address_;
uint32_t current_byte_offset_;
uint32_t current_inst_offset_;
bool instr_valid_;
libdis::x86_insn_t current_instr_;
// TODO(cdn): Maybe also track an expression's index register.
// ex: mov eax, [ebx + ecx]; ebx is base, ecx is index.
bool register_valid_;
libdis::x86_reg_t bad_register_;
bool pushed_bad_value_;
bool end_of_block_;
uint16_t flags_;
};
} // namespace google_breakpad
#endif // GOOGLE_BREAKPAD_PROCESSOR_DISASSEMBLER_X86_H_

View file

@ -0,0 +1,236 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <unistd.h>
#include "breakpad_googletest_includes.h"
#include "processor/disassembler_x86.h"
#include "third_party/libdisasm/libdis.h"
namespace {
using google_breakpad::DisassemblerX86;
unsigned char just_return[] = "\xc3"; // retn
unsigned char invalid_instruction[] = "\x00"; // invalid
unsigned char read_eax_jmp_eax[] =
"\x8b\x18" // mov ebx, [eax];
"\x33\xc9" // xor ebx, ebx;
"\xff\x20" // jmp eax;
"\xc3"; // retn;
unsigned char write_eax_arg_to_call[] =
"\x89\xa8\x00\x02\x00\x00" // mov [eax+200], ebp;
"\xc1\xeb\x02" // shr ebx, 2;
"\x50" // push eax;
"\xe8\xd1\x24\x77\x88" // call something;
"\xc3"; // retn;
unsigned char read_edi_stosb[] =
"\x8b\x07" // mov eax, [edi];
"\x8b\xc8" // mov ecx, eax;
"\xf3\xaa" // rep stosb;
"\xc3"; // retn;
unsigned char read_clobber_write[] =
"\x03\x18" // add ebx, [eax];
"\x8b\xc1" // mov eax, ecx;
"\x89\x10" // mov [eax], edx;
"\xc3"; // retn;
unsigned char read_xchg_write[] =
"\x03\x18" // add ebx, [eax];
"\x91" // xchg eax, ecx;
"\x89\x18" // mov [eax], ebx;
"\x89\x11" // mov [ecx], edx;
"\xc3"; // retn;
unsigned char read_cmp[] =
"\x03\x18" // add ebx, [eax];
"\x83\xf8\x00" // cmp eax, 0;
"\x74\x04" // je +4;
"\xc3"; // retn;
TEST(DisassemblerX86Test, SimpleReturnInstruction) {
DisassemblerX86 dis(just_return, sizeof(just_return)-1, 0);
EXPECT_EQ(1U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_TRUE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_controlflow, dis.currentInstructionGroup());
const libdis::x86_insn_t* instruction = dis.currentInstruction();
EXPECT_EQ(libdis::insn_controlflow, instruction->group);
EXPECT_EQ(libdis::insn_return, instruction->type);
EXPECT_EQ(0U, dis.NextInstruction());
EXPECT_FALSE(dis.currentInstructionValid());
EXPECT_EQ(NULL, dis.currentInstruction());
}
TEST(DisassemblerX86Test, SimpleInvalidInstruction) {
DisassemblerX86 dis(invalid_instruction, sizeof(invalid_instruction)-1, 0);
EXPECT_EQ(0U, dis.NextInstruction());
EXPECT_FALSE(dis.currentInstructionValid());
}
TEST(DisassemblerX86Test, BadReadLeadsToBranch) {
DisassemblerX86 dis(read_eax_jmp_eax, sizeof(read_eax_jmp_eax)-1, 0);
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_move, dis.currentInstructionGroup());
EXPECT_TRUE(dis.setBadRead());
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_logic, dis.currentInstructionGroup());
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(google_breakpad::DISX86_BAD_BRANCH_TARGET, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_controlflow, dis.currentInstructionGroup());
}
TEST(DisassemblerX86Test, BadWriteLeadsToPushedArg) {
DisassemblerX86 dis(write_eax_arg_to_call,
sizeof(write_eax_arg_to_call)-1, 0);
EXPECT_EQ(6U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_move, dis.currentInstructionGroup());
EXPECT_TRUE(dis.setBadWrite());
EXPECT_EQ(3U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_arithmetic, dis.currentInstructionGroup());
EXPECT_EQ(1U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(5U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(google_breakpad::DISX86_BAD_ARGUMENT_PASSED, dis.flags());
EXPECT_EQ(libdis::insn_controlflow, dis.currentInstructionGroup());
EXPECT_FALSE(dis.endOfBlock());
}
TEST(DisassemblerX86Test, BadReadLeadsToBlockWrite) {
DisassemblerX86 dis(read_edi_stosb, sizeof(read_edi_stosb)-1, 0);
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_move, dis.currentInstructionGroup());
EXPECT_TRUE(dis.setBadRead());
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_move, dis.currentInstructionGroup());
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(google_breakpad::DISX86_BAD_BLOCK_WRITE, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_string, dis.currentInstructionGroup());
}
TEST(DisassemblerX86Test, BadReadClobberThenWrite) {
DisassemblerX86 dis(read_clobber_write, sizeof(read_clobber_write)-1, 0);
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_arithmetic, dis.currentInstructionGroup());
EXPECT_TRUE(dis.setBadRead());
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_move, dis.currentInstructionGroup());
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_move, dis.currentInstructionGroup());
}
TEST(DisassemblerX86Test, BadReadXCHGThenWrite) {
DisassemblerX86 dis(read_xchg_write, sizeof(read_xchg_write)-1, 0);
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_arithmetic, dis.currentInstructionGroup());
EXPECT_TRUE(dis.setBadRead());
EXPECT_EQ(1U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_move, dis.currentInstructionGroup());
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_move, dis.currentInstructionGroup());
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(google_breakpad::DISX86_BAD_WRITE, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_move, dis.currentInstructionGroup());
}
TEST(DisassemblerX86Test, BadReadThenCMP) {
DisassemblerX86 dis(read_cmp, sizeof(read_cmp)-1, 0);
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(0U, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_arithmetic, dis.currentInstructionGroup());
EXPECT_TRUE(dis.setBadRead());
EXPECT_EQ(3U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(google_breakpad::DISX86_BAD_COMPARISON, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_comparison, dis.currentInstructionGroup());
EXPECT_EQ(2U, dis.NextInstruction());
EXPECT_TRUE(dis.currentInstructionValid());
EXPECT_EQ(google_breakpad::DISX86_BAD_COMPARISON, dis.flags());
EXPECT_FALSE(dis.endOfBlock());
EXPECT_EQ(libdis::insn_controlflow, dis.currentInstructionGroup());
}
}

View file

@ -0,0 +1,881 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// dump_context.cc: A (mini/micro)dump context.
//
// See dump_context.h for documentation.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/dump_context.h"
#include <assert.h>
#ifdef _WIN32
#include <io.h>
#else // _WIN32
#include <unistd.h>
#endif // _WIN32
#include "common/stdio_wrapper.h"
#include "processor/logging.h"
namespace google_breakpad {
DumpContext::DumpContext() : context_(),
context_flags_(0) { }
DumpContext::~DumpContext() {
FreeContext();
}
uint32_t DumpContext::GetContextCPU() const {
if (!valid_) {
// Don't log a message, GetContextCPU can be legitimately called with
// valid_ false by FreeContext, which is called by Read.
return 0;
}
return context_flags_ & MD_CONTEXT_CPU_MASK;
}
uint32_t DumpContext::GetContextFlags() const {
return context_flags_;
}
const MDRawContextX86* DumpContext::GetContextX86() const {
if (GetContextCPU() != MD_CONTEXT_X86) {
BPLOG(ERROR) << "DumpContext cannot get x86 context";
return NULL;
}
return context_.x86;
}
const MDRawContextPPC* DumpContext::GetContextPPC() const {
if (GetContextCPU() != MD_CONTEXT_PPC) {
BPLOG(ERROR) << "DumpContext cannot get ppc context";
return NULL;
}
return context_.ppc;
}
const MDRawContextPPC64* DumpContext::GetContextPPC64() const {
if (GetContextCPU() != MD_CONTEXT_PPC64) {
BPLOG(ERROR) << "DumpContext cannot get ppc64 context";
return NULL;
}
return context_.ppc64;
}
const MDRawContextAMD64* DumpContext::GetContextAMD64() const {
if (GetContextCPU() != MD_CONTEXT_AMD64) {
BPLOG(ERROR) << "DumpContext cannot get amd64 context";
return NULL;
}
return context_.amd64;
}
const MDRawContextSPARC* DumpContext::GetContextSPARC() const {
if (GetContextCPU() != MD_CONTEXT_SPARC) {
BPLOG(ERROR) << "DumpContext cannot get sparc context";
return NULL;
}
return context_.ctx_sparc;
}
const MDRawContextARM* DumpContext::GetContextARM() const {
if (GetContextCPU() != MD_CONTEXT_ARM) {
BPLOG(ERROR) << "DumpContext cannot get arm context";
return NULL;
}
return context_.arm;
}
const MDRawContextARM64* DumpContext::GetContextARM64() const {
if (GetContextCPU() != MD_CONTEXT_ARM64) {
BPLOG(ERROR) << "DumpContext cannot get arm64 context";
return NULL;
}
return context_.arm64;
}
const MDRawContextMIPS* DumpContext::GetContextMIPS() const {
if ((GetContextCPU() != MD_CONTEXT_MIPS) &&
(GetContextCPU() != MD_CONTEXT_MIPS64)) {
BPLOG(ERROR) << "DumpContext cannot get MIPS context";
return NULL;
}
return context_.ctx_mips;
}
const MDRawContextRISCV* DumpContext::GetContextRISCV() const {
if (GetContextCPU() != MD_CONTEXT_RISCV) {
BPLOG(ERROR) << "DumpContext cannot get RISCV context";
return NULL;
}
return context_.riscv;
}
const MDRawContextRISCV64* DumpContext::GetContextRISCV64() const {
if (GetContextCPU() != MD_CONTEXT_RISCV64) {
BPLOG(ERROR) << "DumpContext cannot get RISCV64 context";
return NULL;
}
return context_.riscv64;
}
bool DumpContext::GetInstructionPointer(uint64_t* ip) const {
BPLOG_IF(ERROR, !ip) << "DumpContext::GetInstructionPointer requires |ip|";
assert(ip);
*ip = 0;
if (!valid_) {
BPLOG(ERROR) << "Invalid DumpContext for GetInstructionPointer";
return false;
}
switch (GetContextCPU()) {
case MD_CONTEXT_AMD64:
*ip = GetContextAMD64()->rip;
break;
case MD_CONTEXT_ARM:
*ip = GetContextARM()->iregs[MD_CONTEXT_ARM_REG_PC];
break;
case MD_CONTEXT_ARM64:
*ip = GetContextARM64()->iregs[MD_CONTEXT_ARM64_REG_PC];
break;
case MD_CONTEXT_PPC:
*ip = GetContextPPC()->srr0;
break;
case MD_CONTEXT_PPC64:
*ip = GetContextPPC64()->srr0;
break;
case MD_CONTEXT_SPARC:
*ip = GetContextSPARC()->pc;
break;
case MD_CONTEXT_X86:
*ip = GetContextX86()->eip;
break;
case MD_CONTEXT_MIPS:
case MD_CONTEXT_MIPS64:
*ip = GetContextMIPS()->epc;
break;
case MD_CONTEXT_RISCV:
*ip = GetContextRISCV()->pc;
break;
case MD_CONTEXT_RISCV64:
*ip = GetContextRISCV64()->pc;
break;
default:
// This should never happen.
BPLOG(ERROR) << "Unknown CPU architecture in GetInstructionPointer";
return false;
}
return true;
}
bool DumpContext::GetStackPointer(uint64_t* sp) const {
BPLOG_IF(ERROR, !sp) << "DumpContext::GetStackPointer requires |sp|";
assert(sp);
*sp = 0;
if (!valid_) {
BPLOG(ERROR) << "Invalid DumpContext for GetStackPointer";
return false;
}
switch (GetContextCPU()) {
case MD_CONTEXT_AMD64:
*sp = GetContextAMD64()->rsp;
break;
case MD_CONTEXT_ARM:
*sp = GetContextARM()->iregs[MD_CONTEXT_ARM_REG_SP];
break;
case MD_CONTEXT_ARM64:
*sp = GetContextARM64()->iregs[MD_CONTEXT_ARM64_REG_SP];
break;
case MD_CONTEXT_PPC:
*sp = GetContextPPC()->gpr[MD_CONTEXT_PPC_REG_SP];
break;
case MD_CONTEXT_PPC64:
*sp = GetContextPPC64()->gpr[MD_CONTEXT_PPC64_REG_SP];
break;
case MD_CONTEXT_SPARC:
*sp = GetContextSPARC()->g_r[MD_CONTEXT_SPARC_REG_SP];
break;
case MD_CONTEXT_X86:
*sp = GetContextX86()->esp;
break;
case MD_CONTEXT_MIPS:
case MD_CONTEXT_MIPS64:
*sp = GetContextMIPS()->iregs[MD_CONTEXT_MIPS_REG_SP];
break;
case MD_CONTEXT_RISCV:
*sp = GetContextRISCV()->sp;
break;
case MD_CONTEXT_RISCV64:
*sp = GetContextRISCV64()->sp;
break;
default:
// This should never happen.
BPLOG(ERROR) << "Unknown CPU architecture in GetStackPointer";
return false;
}
return true;
}
void DumpContext::SetContextFlags(uint32_t context_flags) {
context_flags_ = context_flags;
}
void DumpContext::SetContextX86(MDRawContextX86* x86) {
context_.x86 = x86;
}
void DumpContext::SetContextPPC(MDRawContextPPC* ppc) {
context_.ppc = ppc;
}
void DumpContext::SetContextPPC64(MDRawContextPPC64* ppc64) {
context_.ppc64 = ppc64;
}
void DumpContext::SetContextAMD64(MDRawContextAMD64* amd64) {
context_.amd64 = amd64;
}
void DumpContext::SetContextSPARC(MDRawContextSPARC* ctx_sparc) {
context_.ctx_sparc = ctx_sparc;
}
void DumpContext::SetContextARM(MDRawContextARM* arm) {
context_.arm = arm;
}
void DumpContext::SetContextARM64(MDRawContextARM64* arm64) {
context_.arm64 = arm64;
}
void DumpContext::SetContextMIPS(MDRawContextMIPS* ctx_mips) {
context_.ctx_mips = ctx_mips;
}
void DumpContext::SetContextRISCV(MDRawContextRISCV* riscv) {
context_.riscv = riscv;
}
void DumpContext::SetContextRISCV64(MDRawContextRISCV64* riscv64) {
context_.riscv64 = riscv64;
}
void DumpContext::FreeContext() {
switch (GetContextCPU()) {
case MD_CONTEXT_X86:
delete context_.x86;
break;
case MD_CONTEXT_PPC:
delete context_.ppc;
break;
case MD_CONTEXT_PPC64:
delete context_.ppc64;
break;
case MD_CONTEXT_AMD64:
delete context_.amd64;
break;
case MD_CONTEXT_SPARC:
delete context_.ctx_sparc;
break;
case MD_CONTEXT_ARM:
delete context_.arm;
break;
case MD_CONTEXT_ARM64:
delete context_.arm64;
break;
case MD_CONTEXT_MIPS:
case MD_CONTEXT_MIPS64:
delete context_.ctx_mips;
break;
case MD_CONTEXT_RISCV:
delete context_.riscv;
break;
case MD_CONTEXT_RISCV64:
delete context_.riscv64;
break;
default:
// There is no context record (valid_ is false) or there's a
// context record for an unknown CPU (shouldn't happen, only known
// records are stored by Read).
break;
}
context_flags_ = 0;
context_.base = NULL;
}
void DumpContext::Print() {
if (!valid_) {
BPLOG(ERROR) << "DumpContext cannot print invalid data";
return;
}
switch (GetContextCPU()) {
case MD_CONTEXT_X86: {
const MDRawContextX86* context_x86 = GetContextX86();
printf("MDRawContextX86\n");
printf(" context_flags = 0x%x\n",
context_x86->context_flags);
printf(" dr0 = 0x%x\n", context_x86->dr0);
printf(" dr1 = 0x%x\n", context_x86->dr1);
printf(" dr2 = 0x%x\n", context_x86->dr2);
printf(" dr3 = 0x%x\n", context_x86->dr3);
printf(" dr6 = 0x%x\n", context_x86->dr6);
printf(" dr7 = 0x%x\n", context_x86->dr7);
printf(" float_save.control_word = 0x%x\n",
context_x86->float_save.control_word);
printf(" float_save.status_word = 0x%x\n",
context_x86->float_save.status_word);
printf(" float_save.tag_word = 0x%x\n",
context_x86->float_save.tag_word);
printf(" float_save.error_offset = 0x%x\n",
context_x86->float_save.error_offset);
printf(" float_save.error_selector = 0x%x\n",
context_x86->float_save.error_selector);
printf(" float_save.data_offset = 0x%x\n",
context_x86->float_save.data_offset);
printf(" float_save.data_selector = 0x%x\n",
context_x86->float_save.data_selector);
printf(" float_save.register_area[%2d] = 0x",
MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE);
for (unsigned int register_index = 0;
register_index < MD_FLOATINGSAVEAREA_X86_REGISTERAREA_SIZE;
++register_index) {
printf("%02x", context_x86->float_save.register_area[register_index]);
}
printf("\n");
printf(" float_save.cr0_npx_state = 0x%x\n",
context_x86->float_save.cr0_npx_state);
printf(" gs = 0x%x\n", context_x86->gs);
printf(" fs = 0x%x\n", context_x86->fs);
printf(" es = 0x%x\n", context_x86->es);
printf(" ds = 0x%x\n", context_x86->ds);
printf(" edi = 0x%x\n", context_x86->edi);
printf(" esi = 0x%x\n", context_x86->esi);
printf(" ebx = 0x%x\n", context_x86->ebx);
printf(" edx = 0x%x\n", context_x86->edx);
printf(" ecx = 0x%x\n", context_x86->ecx);
printf(" eax = 0x%x\n", context_x86->eax);
printf(" ebp = 0x%x\n", context_x86->ebp);
printf(" eip = 0x%x\n", context_x86->eip);
printf(" cs = 0x%x\n", context_x86->cs);
printf(" eflags = 0x%x\n", context_x86->eflags);
printf(" esp = 0x%x\n", context_x86->esp);
printf(" ss = 0x%x\n", context_x86->ss);
printf(" extended_registers[%3d] = 0x",
MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE);
for (unsigned int register_index = 0;
register_index < MD_CONTEXT_X86_EXTENDED_REGISTERS_SIZE;
++register_index) {
printf("%02x", context_x86->extended_registers[register_index]);
}
printf("\n\n");
break;
}
case MD_CONTEXT_PPC: {
const MDRawContextPPC* context_ppc = GetContextPPC();
printf("MDRawContextPPC\n");
printf(" context_flags = 0x%x\n",
context_ppc->context_flags);
printf(" srr0 = 0x%x\n", context_ppc->srr0);
printf(" srr1 = 0x%x\n", context_ppc->srr1);
for (unsigned int gpr_index = 0;
gpr_index < MD_CONTEXT_PPC_GPR_COUNT;
++gpr_index) {
printf(" gpr[%2d] = 0x%x\n",
gpr_index, context_ppc->gpr[gpr_index]);
}
printf(" cr = 0x%x\n", context_ppc->cr);
printf(" xer = 0x%x\n", context_ppc->xer);
printf(" lr = 0x%x\n", context_ppc->lr);
printf(" ctr = 0x%x\n", context_ppc->ctr);
printf(" mq = 0x%x\n", context_ppc->mq);
printf(" vrsave = 0x%x\n", context_ppc->vrsave);
for (unsigned int fpr_index = 0;
fpr_index < MD_FLOATINGSAVEAREA_PPC_FPR_COUNT;
++fpr_index) {
printf(" float_save.fpregs[%2d] = 0x%" PRIx64 "\n",
fpr_index, context_ppc->float_save.fpregs[fpr_index]);
}
printf(" float_save.fpscr = 0x%x\n",
context_ppc->float_save.fpscr);
// TODO(mmentovai): print the 128-bit quantities in
// context_ppc->vector_save. This isn't done yet because printf
// doesn't support 128-bit quantities, and printing them using
// PRIx64 as two 64-bit quantities requires knowledge of the CPU's
// byte ordering.
printf(" vector_save.save_vrvalid = 0x%x\n",
context_ppc->vector_save.save_vrvalid);
printf("\n");
break;
}
case MD_CONTEXT_PPC64: {
const MDRawContextPPC64* context_ppc64 = GetContextPPC64();
printf("MDRawContextPPC64\n");
printf(" context_flags = 0x%" PRIx64 "\n",
context_ppc64->context_flags);
printf(" srr0 = 0x%" PRIx64 "\n",
context_ppc64->srr0);
printf(" srr1 = 0x%" PRIx64 "\n",
context_ppc64->srr1);
for (unsigned int gpr_index = 0;
gpr_index < MD_CONTEXT_PPC64_GPR_COUNT;
++gpr_index) {
printf(" gpr[%2d] = 0x%" PRIx64 "\n",
gpr_index, context_ppc64->gpr[gpr_index]);
}
printf(" cr = 0x%" PRIx64 "\n", context_ppc64->cr);
printf(" xer = 0x%" PRIx64 "\n",
context_ppc64->xer);
printf(" lr = 0x%" PRIx64 "\n", context_ppc64->lr);
printf(" ctr = 0x%" PRIx64 "\n",
context_ppc64->ctr);
printf(" vrsave = 0x%" PRIx64 "\n",
context_ppc64->vrsave);
for (unsigned int fpr_index = 0;
fpr_index < MD_FLOATINGSAVEAREA_PPC_FPR_COUNT;
++fpr_index) {
printf(" float_save.fpregs[%2d] = 0x%" PRIx64 "\n",
fpr_index, context_ppc64->float_save.fpregs[fpr_index]);
}
printf(" float_save.fpscr = 0x%x\n",
context_ppc64->float_save.fpscr);
// TODO(mmentovai): print the 128-bit quantities in
// context_ppc64->vector_save. This isn't done yet because printf
// doesn't support 128-bit quantities, and printing them using
// PRIx64 as two 64-bit quantities requires knowledge of the CPU's
// byte ordering.
printf(" vector_save.save_vrvalid = 0x%x\n",
context_ppc64->vector_save.save_vrvalid);
printf("\n");
break;
}
case MD_CONTEXT_AMD64: {
const MDRawContextAMD64* context_amd64 = GetContextAMD64();
printf("MDRawContextAMD64\n");
printf(" p1_home = 0x%" PRIx64 "\n",
context_amd64->p1_home);
printf(" p2_home = 0x%" PRIx64 "\n",
context_amd64->p2_home);
printf(" p3_home = 0x%" PRIx64 "\n",
context_amd64->p3_home);
printf(" p4_home = 0x%" PRIx64 "\n",
context_amd64->p4_home);
printf(" p5_home = 0x%" PRIx64 "\n",
context_amd64->p5_home);
printf(" p6_home = 0x%" PRIx64 "\n",
context_amd64->p6_home);
printf(" context_flags = 0x%x\n",
context_amd64->context_flags);
printf(" mx_csr = 0x%x\n",
context_amd64->mx_csr);
printf(" cs = 0x%x\n", context_amd64->cs);
printf(" ds = 0x%x\n", context_amd64->ds);
printf(" es = 0x%x\n", context_amd64->es);
printf(" fs = 0x%x\n", context_amd64->fs);
printf(" gs = 0x%x\n", context_amd64->gs);
printf(" ss = 0x%x\n", context_amd64->ss);
printf(" eflags = 0x%x\n", context_amd64->eflags);
printf(" dr0 = 0x%" PRIx64 "\n", context_amd64->dr0);
printf(" dr1 = 0x%" PRIx64 "\n", context_amd64->dr1);
printf(" dr2 = 0x%" PRIx64 "\n", context_amd64->dr2);
printf(" dr3 = 0x%" PRIx64 "\n", context_amd64->dr3);
printf(" dr6 = 0x%" PRIx64 "\n", context_amd64->dr6);
printf(" dr7 = 0x%" PRIx64 "\n", context_amd64->dr7);
printf(" rax = 0x%" PRIx64 "\n", context_amd64->rax);
printf(" rcx = 0x%" PRIx64 "\n", context_amd64->rcx);
printf(" rdx = 0x%" PRIx64 "\n", context_amd64->rdx);
printf(" rbx = 0x%" PRIx64 "\n", context_amd64->rbx);
printf(" rsp = 0x%" PRIx64 "\n", context_amd64->rsp);
printf(" rbp = 0x%" PRIx64 "\n", context_amd64->rbp);
printf(" rsi = 0x%" PRIx64 "\n", context_amd64->rsi);
printf(" rdi = 0x%" PRIx64 "\n", context_amd64->rdi);
printf(" r8 = 0x%" PRIx64 "\n", context_amd64->r8);
printf(" r9 = 0x%" PRIx64 "\n", context_amd64->r9);
printf(" r10 = 0x%" PRIx64 "\n", context_amd64->r10);
printf(" r11 = 0x%" PRIx64 "\n", context_amd64->r11);
printf(" r12 = 0x%" PRIx64 "\n", context_amd64->r12);
printf(" r13 = 0x%" PRIx64 "\n", context_amd64->r13);
printf(" r14 = 0x%" PRIx64 "\n", context_amd64->r14);
printf(" r15 = 0x%" PRIx64 "\n", context_amd64->r15);
printf(" rip = 0x%" PRIx64 "\n", context_amd64->rip);
// TODO: print xmm, vector, debug registers
printf("\n");
break;
}
case MD_CONTEXT_SPARC: {
const MDRawContextSPARC* context_sparc = GetContextSPARC();
printf("MDRawContextSPARC\n");
printf(" context_flags = 0x%x\n",
context_sparc->context_flags);
for (unsigned int g_r_index = 0;
g_r_index < MD_CONTEXT_SPARC_GPR_COUNT;
++g_r_index) {
printf(" g_r[%2d] = 0x%" PRIx64 "\n",
g_r_index, context_sparc->g_r[g_r_index]);
}
printf(" ccr = 0x%" PRIx64 "\n", context_sparc->ccr);
printf(" pc = 0x%" PRIx64 "\n", context_sparc->pc);
printf(" npc = 0x%" PRIx64 "\n", context_sparc->npc);
printf(" y = 0x%" PRIx64 "\n", context_sparc->y);
printf(" asi = 0x%" PRIx64 "\n", context_sparc->asi);
printf(" fprs = 0x%" PRIx64 "\n", context_sparc->fprs);
for (unsigned int fpr_index = 0;
fpr_index < MD_FLOATINGSAVEAREA_SPARC_FPR_COUNT;
++fpr_index) {
printf(" float_save.regs[%2d] = 0x%" PRIx64 "\n",
fpr_index, context_sparc->float_save.regs[fpr_index]);
}
printf(" float_save.filler = 0x%" PRIx64 "\n",
context_sparc->float_save.filler);
printf(" float_save.fsr = 0x%" PRIx64 "\n",
context_sparc->float_save.fsr);
break;
}
case MD_CONTEXT_ARM: {
const MDRawContextARM* context_arm = GetContextARM();
const char * const names[] = {
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
"r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc",
};
printf("MDRawContextARM\n");
printf(" context_flags = 0x%x\n",
context_arm->context_flags);
for (unsigned int ireg_index = 0;
ireg_index < MD_CONTEXT_ARM_GPR_COUNT;
++ireg_index) {
printf(" %-3s = 0x%x\n",
names[ireg_index], context_arm->iregs[ireg_index]);
}
printf(" cpsr = 0x%x\n", context_arm->cpsr);
printf(" float_save.fpscr = 0x%" PRIx64 "\n",
context_arm->float_save.fpscr);
for (unsigned int fpr_index = 0;
fpr_index < MD_FLOATINGSAVEAREA_ARM_FPR_COUNT;
++fpr_index) {
printf(" float_save.regs[%2d] = 0x%" PRIx64 "\n",
fpr_index, context_arm->float_save.regs[fpr_index]);
}
for (unsigned int fpe_index = 0;
fpe_index < MD_FLOATINGSAVEAREA_ARM_FPEXTRA_COUNT;
++fpe_index) {
printf(" float_save.extra[%2d] = 0x%" PRIx32 "\n",
fpe_index, context_arm->float_save.extra[fpe_index]);
}
break;
}
case MD_CONTEXT_ARM64: {
const MDRawContextARM64* context_arm64 = GetContextARM64();
printf("MDRawContextARM64\n");
printf(" context_flags = 0x%x\n",
context_arm64->context_flags);
for (unsigned int ireg_index = 0;
ireg_index < MD_CONTEXT_ARM64_GPR_COUNT;
++ireg_index) {
printf(" iregs[%2d] = 0x%" PRIx64 "\n",
ireg_index, context_arm64->iregs[ireg_index]);
}
printf(" cpsr = 0x%x\n", context_arm64->cpsr);
printf(" float_save.fpsr = 0x%x\n", context_arm64->float_save.fpsr);
printf(" float_save.fpcr = 0x%x\n", context_arm64->float_save.fpcr);
for (unsigned int freg_index = 0;
freg_index < MD_FLOATINGSAVEAREA_ARM64_FPR_COUNT;
++freg_index) {
uint128_struct fp_value = context_arm64->float_save.regs[freg_index];
printf(" float_save.regs[%2d] = 0x%" PRIx64 "%" PRIx64 "\n",
freg_index, fp_value.high, fp_value.low);
}
break;
}
case MD_CONTEXT_MIPS:
case MD_CONTEXT_MIPS64: {
const MDRawContextMIPS* context_mips = GetContextMIPS();
printf("MDRawContextMIPS\n");
printf(" context_flags = 0x%x\n",
context_mips->context_flags);
for (int ireg_index = 0;
ireg_index < MD_CONTEXT_MIPS_GPR_COUNT;
++ireg_index) {
printf(" iregs[%2d] = 0x%" PRIx64 "\n",
ireg_index, context_mips->iregs[ireg_index]);
}
printf(" mdhi = 0x%" PRIx64 "\n",
context_mips->mdhi);
printf(" mdlo = 0x%" PRIx64 "\n",
context_mips->mdhi);
for (int dsp_index = 0;
dsp_index < MD_CONTEXT_MIPS_DSP_COUNT;
++dsp_index) {
printf(" hi[%1d] = 0x%" PRIx32 "\n",
dsp_index, context_mips->hi[dsp_index]);
printf(" lo[%1d] = 0x%" PRIx32 "\n",
dsp_index, context_mips->lo[dsp_index]);
}
printf(" dsp_control = 0x%" PRIx32 "\n",
context_mips->dsp_control);
printf(" epc = 0x%" PRIx64 "\n",
context_mips->epc);
printf(" badvaddr = 0x%" PRIx64 "\n",
context_mips->badvaddr);
printf(" status = 0x%" PRIx32 "\n",
context_mips->status);
printf(" cause = 0x%" PRIx32 "\n",
context_mips->cause);
for (int fpr_index = 0;
fpr_index < MD_FLOATINGSAVEAREA_MIPS_FPR_COUNT;
++fpr_index) {
printf(" float_save.regs[%2d] = 0x%" PRIx64 "\n",
fpr_index, context_mips->float_save.regs[fpr_index]);
}
printf(" float_save.fpcsr = 0x%" PRIx32 "\n",
context_mips->float_save.fpcsr);
printf(" float_save.fir = 0x%" PRIx32 "\n",
context_mips->float_save.fir);
break;
}
case MD_CONTEXT_RISCV: {
const MDRawContextRISCV* context_riscv = GetContextRISCV();
printf("MDRawContextRISCV\n");
printf(" context_flags = 0x%x\n",
context_riscv->context_flags);
printf(" pc = 0x%" PRIx32 "\n",
context_riscv->pc);
printf(" ra = 0x%" PRIx32 "\n",
context_riscv->ra);
printf(" sp = 0x%" PRIx32 "\n",
context_riscv->sp);
printf(" gp = 0x%" PRIx32 "\n",
context_riscv->gp);
printf(" tp = 0x%" PRIx32 "\n",
context_riscv->tp);
printf(" t0 = 0x%" PRIx32 "\n",
context_riscv->t0);
printf(" t1 = 0x%" PRIx32 "\n",
context_riscv->t1);
printf(" t2 = 0x%" PRIx32 "\n",
context_riscv->t2);
printf(" s0 = 0x%" PRIx32 "\n",
context_riscv->s0);
printf(" s1 = 0x%" PRIx32 "\n",
context_riscv->s1);
printf(" a0 = 0x%" PRIx32 "\n",
context_riscv->a0);
printf(" a1 = 0x%" PRIx32 "\n",
context_riscv->a1);
printf(" a2 = 0x%" PRIx32 "\n",
context_riscv->a2);
printf(" a3 = 0x%" PRIx32 "\n",
context_riscv->a3);
printf(" a4 = 0x%" PRIx32 "\n",
context_riscv->a4);
printf(" a5 = 0x%" PRIx32 "\n",
context_riscv->a5);
printf(" a6 = 0x%" PRIx32 "\n",
context_riscv->a6);
printf(" a7 = 0x%" PRIx32 "\n",
context_riscv->a7);
printf(" s2 = 0x%" PRIx32 "\n",
context_riscv->s2);
printf(" s3 = 0x%" PRIx32 "\n",
context_riscv->s3);
printf(" s4 = 0x%" PRIx32 "\n",
context_riscv->s4);
printf(" s5 = 0x%" PRIx32 "\n",
context_riscv->s5);
printf(" s6 = 0x%" PRIx32 "\n",
context_riscv->s6);
printf(" s7 = 0x%" PRIx32 "\n",
context_riscv->s7);
printf(" s8 = 0x%" PRIx32 "\n",
context_riscv->s8);
printf(" s9 = 0x%" PRIx32 "\n",
context_riscv->s9);
printf(" s10 = 0x%" PRIx32 "\n",
context_riscv->s10);
printf(" s11 = 0x%" PRIx32 "\n",
context_riscv->s11);
printf(" t3 = 0x%" PRIx32 "\n",
context_riscv->t3);
printf(" t4 = 0x%" PRIx32 "\n",
context_riscv->t4);
printf(" t5 = 0x%" PRIx32 "\n",
context_riscv->t5);
printf(" t6 = 0x%" PRIx32 "\n",
context_riscv->t6);
#if defined(__riscv)
for (unsigned int freg_index = 0; freg_index < MD_CONTEXT_RISCV_FPR_COUNT;
++freg_index) {
// Breakpad only supports RISCV32 with 32 bit floating point.
uint32_t fp_value = context_riscv->fpregs[freg_index];
printf(" fpregs[%2d] = 0x%" PRIx32 "\n", freg_index,
fp_value);
}
printf(" fcsr = 0x%" PRIx32 "\n", context_riscv->fcsr);
#endif
break;
}
case MD_CONTEXT_RISCV64: {
const MDRawContextRISCV64* context_riscv64 = GetContextRISCV64();
printf("MDRawContextRISCV64\n");
printf(" context_flags = 0x%x\n",
context_riscv64->context_flags);
printf(" pc = 0x%" PRIx64 "\n",
context_riscv64->pc);
printf(" ra = 0x%" PRIx64 "\n",
context_riscv64->ra);
printf(" sp = 0x%" PRIx64 "\n",
context_riscv64->sp);
printf(" gp = 0x%" PRIx64 "\n",
context_riscv64->gp);
printf(" tp = 0x%" PRIx64 "\n",
context_riscv64->tp);
printf(" t0 = 0x%" PRIx64 "\n",
context_riscv64->t0);
printf(" t1 = 0x%" PRIx64 "\n",
context_riscv64->t1);
printf(" t2 = 0x%" PRIx64 "\n",
context_riscv64->t2);
printf(" s0 = 0x%" PRIx64 "\n",
context_riscv64->s0);
printf(" s1 = 0x%" PRIx64 "\n",
context_riscv64->s1);
printf(" a0 = 0x%" PRIx64 "\n",
context_riscv64->a0);
printf(" a1 = 0x%" PRIx64 "\n",
context_riscv64->a1);
printf(" a2 = 0x%" PRIx64 "\n",
context_riscv64->a2);
printf(" a3 = 0x%" PRIx64 "\n",
context_riscv64->a3);
printf(" a4 = 0x%" PRIx64 "\n",
context_riscv64->a4);
printf(" a5 = 0x%" PRIx64 "\n",
context_riscv64->a5);
printf(" a6 = 0x%" PRIx64 "\n",
context_riscv64->a6);
printf(" a7 = 0x%" PRIx64 "\n",
context_riscv64->a7);
printf(" s2 = 0x%" PRIx64 "\n",
context_riscv64->s2);
printf(" s3 = 0x%" PRIx64 "\n",
context_riscv64->s3);
printf(" s4 = 0x%" PRIx64 "\n",
context_riscv64->s4);
printf(" s5 = 0x%" PRIx64 "\n",
context_riscv64->s5);
printf(" s6 = 0x%" PRIx64 "\n",
context_riscv64->s6);
printf(" s7 = 0x%" PRIx64 "\n",
context_riscv64->s7);
printf(" s8 = 0x%" PRIx64 "\n",
context_riscv64->s8);
printf(" s9 = 0x%" PRIx64 "\n",
context_riscv64->s9);
printf(" s10 = 0x%" PRIx64 "\n",
context_riscv64->s10);
printf(" s11 = 0x%" PRIx64 "\n",
context_riscv64->s11);
printf(" t3 = 0x%" PRIx64 "\n",
context_riscv64->t3);
printf(" t4 = 0x%" PRIx64 "\n",
context_riscv64->t4);
printf(" t5 = 0x%" PRIx64 "\n",
context_riscv64->t5);
printf(" t6 = 0x%" PRIx64 "\n",
context_riscv64->t6);
#if defined(__riscv)
for (unsigned int freg_index = 0; freg_index < MD_CONTEXT_RISCV_FPR_COUNT;
++freg_index) {
// Breakpad only supports RISCV64 with 64 bit floating point.
uint64_t fp_value = context_riscv64->fpregs[freg_index];
printf(" fpregs[%2d] = 0x%" PRIx64 "\n", freg_index,
fp_value);
}
printf(" fcsr = 0x%" PRIx32 "\n", context_riscv64->fcsr);
#endif
break;
}
default: {
break;
}
}
}
} // namespace google_breakpad

View file

@ -0,0 +1,42 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// dump_object.cc: A base class for all mini/micro dump object.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/dump_object.h"
namespace google_breakpad {
DumpObject::DumpObject() : valid_(false) {
}
} // namespace google_breakpad

View file

@ -0,0 +1,123 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// exploitability_engine.cc: Generic exploitability engine.
//
// See exploitable_engine.h for documentation.
//
// Author: Cris Neckar
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <cassert>
#include "common/scoped_ptr.h"
#include "google_breakpad/processor/exploitability.h"
#include "google_breakpad/processor/minidump.h"
#include "google_breakpad/processor/process_state.h"
#include "processor/exploitability_linux.h"
#include "processor/exploitability_win.h"
#include "processor/logging.h"
namespace google_breakpad {
Exploitability::Exploitability(Minidump *dump,
ProcessState *process_state)
: dump_(dump),
process_state_(process_state) {}
ExploitabilityRating Exploitability::CheckExploitability() {
return CheckPlatformExploitability();
}
Exploitability *Exploitability::ExploitabilityForPlatform(
Minidump *dump,
ProcessState *process_state) {
return ExploitabilityForPlatform(dump, process_state, false);
}
Exploitability *Exploitability::ExploitabilityForPlatform(
Minidump *dump,
ProcessState *process_state,
bool enable_objdump) {
Exploitability *platform_exploitability = NULL;
MinidumpSystemInfo *minidump_system_info = dump->GetSystemInfo();
if (!minidump_system_info)
return NULL;
const MDRawSystemInfo *raw_system_info =
minidump_system_info->system_info();
if (!raw_system_info)
return NULL;
switch (raw_system_info->platform_id) {
case MD_OS_WIN32_NT:
case MD_OS_WIN32_WINDOWS: {
platform_exploitability = new ExploitabilityWin(dump, process_state);
break;
}
case MD_OS_LINUX: {
platform_exploitability = new ExploitabilityLinux(dump,
process_state,
enable_objdump);
break;
}
case MD_OS_MAC_OS_X:
case MD_OS_IOS:
case MD_OS_UNIX:
case MD_OS_SOLARIS:
case MD_OS_ANDROID:
case MD_OS_PS3:
case MD_OS_FUCHSIA:
default: {
platform_exploitability = NULL;
break;
}
}
BPLOG_IF(ERROR, !platform_exploitability) <<
"No Exploitability module for platform: " <<
process_state->system_info()->os;
return platform_exploitability;
}
bool Exploitability::AddressIsAscii(uint64_t address) {
for (int i = 0; i < 8; i++) {
uint8_t byte = (address >> (8*i)) & 0xff;
if ((byte >= ' ' && byte <= '~') || byte == 0)
continue;
return false;
}
return true;
}
} // namespace google_breakpad

View file

@ -0,0 +1,320 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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.
// exploitability_linux.cc: Linux specific exploitability engine.
//
// Provides a guess at the exploitability of the crash for the Linux
// platform given a minidump and process_state.
//
// Author: Matthew Riley
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "processor/exploitability_linux.h"
#include <string.h>
#include "google_breakpad/common/minidump_exception_linux.h"
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/process_state.h"
#include "google_breakpad/processor/stack_frame.h"
#ifdef __linux__
#include "processor/disassembler_objdump.h"
#endif
#include "processor/logging.h"
namespace {
// Prefixes for memory mapping names.
constexpr char kHeapPrefix[] = "[heap";
constexpr char kStackPrefix[] = "[stack";
// This function in libc is called if the program was compiled with
// -fstack-protector and a function's stack canary changes.
constexpr char kStackCheckFailureFunction[] = "__stack_chk_fail";
// This function in libc is called if the program was compiled with
// -D_FORTIFY_SOURCE=2, a function like strcpy() is called, and the runtime
// can determine that the call would overflow the target buffer.
constexpr char kBoundsCheckFailureFunction[] = "__chk_fail";
} // namespace
namespace google_breakpad {
ExploitabilityLinux::ExploitabilityLinux(Minidump* dump,
ProcessState* process_state)
: Exploitability(dump, process_state),
enable_objdump_(false) { }
ExploitabilityLinux::ExploitabilityLinux(Minidump* dump,
ProcessState* process_state,
bool enable_objdump)
: Exploitability(dump, process_state),
enable_objdump_(enable_objdump) { }
ExploitabilityRating ExploitabilityLinux::CheckPlatformExploitability() {
// Check the crashing thread for functions suggesting a buffer overflow or
// stack smash.
if (process_state_->requesting_thread() != -1) {
CallStack* crashing_thread =
process_state_->threads()->at(process_state_->requesting_thread());
const vector<StackFrame*>& crashing_thread_frames =
*crashing_thread->frames();
for (size_t i = 0; i < crashing_thread_frames.size(); ++i) {
if (crashing_thread_frames[i]->function_name ==
kStackCheckFailureFunction) {
return EXPLOITABILITY_HIGH;
}
if (crashing_thread_frames[i]->function_name ==
kBoundsCheckFailureFunction) {
return EXPLOITABILITY_HIGH;
}
}
}
// Getting exception data. (It should exist for all minidumps.)
MinidumpException* exception = dump_->GetException();
if (exception == NULL) {
BPLOG(INFO) << "No exception record.";
return EXPLOITABILITY_ERR_PROCESSING;
}
const MDRawExceptionStream* raw_exception_stream = exception->exception();
if (raw_exception_stream == NULL) {
BPLOG(INFO) << "No raw exception stream.";
return EXPLOITABILITY_ERR_PROCESSING;
}
// Checking for benign exceptions that caused the crash.
if (this->BenignCrashTrigger(raw_exception_stream)) {
return EXPLOITABILITY_NONE;
}
// Check if the instruction pointer is in a valid instruction region
// by finding if it maps to an executable part of memory.
uint64_t instruction_ptr = 0;
uint64_t stack_ptr = 0;
const MinidumpContext* context = exception->GetContext();
if (context == NULL) {
BPLOG(INFO) << "No exception context.";
return EXPLOITABILITY_ERR_PROCESSING;
}
// Getting the instruction pointer.
if (!context->GetInstructionPointer(&instruction_ptr)) {
BPLOG(INFO) << "Failed to retrieve instruction pointer.";
return EXPLOITABILITY_ERR_PROCESSING;
}
// Getting the stack pointer.
if (!context->GetStackPointer(&stack_ptr)) {
BPLOG(INFO) << "Failed to retrieve stack pointer.";
return EXPLOITABILITY_ERR_PROCESSING;
}
// Checking for the instruction pointer in a valid instruction region,
// a misplaced stack pointer, and an executable stack or heap.
if (!this->InstructionPointerInCode(instruction_ptr) ||
this->StackPointerOffStack(stack_ptr) ||
this->ExecutableStackOrHeap()) {
return EXPLOITABILITY_HIGH;
}
// Check for write to read only memory or invalid memory, shelling out
// to objdump is enabled.
if (enable_objdump_ && this->EndedOnIllegalWrite(instruction_ptr)) {
return EXPLOITABILITY_HIGH;
}
// There was no strong evidence suggesting exploitability, but the minidump
// does not appear totally benign either.
return EXPLOITABILITY_INTERESTING;
}
bool ExploitabilityLinux::EndedOnIllegalWrite(uint64_t instruction_ptr) {
#ifndef __linux__
BPLOG(INFO) << "MinGW does not support fork and exec. Terminating method.";
return false;
#else
// Get memory region containing instruction pointer.
MinidumpMemoryList* memory_list = dump_->GetMemoryList();
MinidumpMemoryRegion* memory_region =
memory_list ?
memory_list->GetMemoryRegionForAddress(instruction_ptr) : NULL;
if (!memory_region) {
BPLOG(INFO) << "No memory region around instruction pointer.";
return false;
}
// Get exception data to find architecture.
string architecture = "";
MinidumpException* exception = dump_->GetException();
// This should never evaluate to true, since this should not be reachable
// without checking for exception data earlier.
if (!exception) {
BPLOG(INFO) << "No exception data.";
return false;
}
const MDRawExceptionStream* raw_exception_stream = exception->exception();
const MinidumpContext* context = exception->GetContext();
// This should not evaluate to true, for the same reason mentioned above.
if (!raw_exception_stream || !context) {
BPLOG(INFO) << "No exception or architecture data.";
return false;
}
DisassemblerObjdump disassembler(context->GetContextCPU(), memory_region,
instruction_ptr);
if (!disassembler.IsValid()) {
BPLOG(INFO) << "Disassembling fault instruction failed.";
return false;
}
// Check if the operation is a write to memory.
// First, the instruction must one that can write to memory.
auto instruction = disassembler.operation();
if (!instruction.compare("mov") || !instruction.compare("inc") ||
!instruction.compare("dec") || !instruction.compare("and") ||
!instruction.compare("or") || !instruction.compare("xor") ||
!instruction.compare("not") || !instruction.compare("neg") ||
!instruction.compare("add") || !instruction.compare("sub") ||
!instruction.compare("shl") || !instruction.compare("shr")) {
uint64_t write_address = 0;
// Check that the destination is a memory address. CalculateDestAddress will
// return false if the destination is not a memory address.
if (!disassembler.CalculateDestAddress(*context, write_address)) {
return false;
}
// If the program crashed as a result of a write, the destination of
// the write must have been an address that did not permit writing.
// However, if the address is under 4k, due to program protections,
// the crash does not suggest exploitability for writes with such a
// low target address.
return write_address > 4096;
} else {
return false;
}
#endif // __linux__
}
bool ExploitabilityLinux::StackPointerOffStack(uint64_t stack_ptr) {
MinidumpLinuxMapsList* linux_maps_list = dump_->GetLinuxMapsList();
// Inconclusive if there are no mappings available.
if (!linux_maps_list) {
return false;
}
const MinidumpLinuxMaps* linux_maps =
linux_maps_list->GetLinuxMapsForAddress(stack_ptr);
// Checks if the stack pointer maps to a valid mapping and if the mapping
// is not the stack. If the mapping has no name, it is inconclusive whether
// it is off the stack.
return !linux_maps || (linux_maps->GetPathname().compare("") &&
linux_maps->GetPathname().compare(
0, strlen(kStackPrefix), kStackPrefix));
}
bool ExploitabilityLinux::ExecutableStackOrHeap() {
MinidumpLinuxMapsList* linux_maps_list = dump_->GetLinuxMapsList();
if (linux_maps_list) {
for (size_t i = 0; i < linux_maps_list->get_maps_count(); i++) {
const MinidumpLinuxMaps* linux_maps =
linux_maps_list->GetLinuxMapsAtIndex(i);
// Check for executable stack or heap for each mapping.
if (linux_maps && (!linux_maps->GetPathname().compare(
0, strlen(kStackPrefix), kStackPrefix) ||
!linux_maps->GetPathname().compare(
0, strlen(kHeapPrefix), kHeapPrefix)) &&
linux_maps->IsExecutable()) {
return true;
}
}
}
return false;
}
bool ExploitabilityLinux::InstructionPointerInCode(uint64_t instruction_ptr) {
// Get Linux memory mapping from /proc/self/maps. Checking whether the
// region the instruction pointer is in has executable permission can tell
// whether it is in a valid code region. If there is no mapping for the
// instruction pointer, it is indicative that the instruction pointer is
// not within a module, which implies that it is outside a valid area.
MinidumpLinuxMapsList* linux_maps_list = dump_->GetLinuxMapsList();
const MinidumpLinuxMaps* linux_maps =
linux_maps_list ?
linux_maps_list->GetLinuxMapsForAddress(instruction_ptr) : NULL;
return linux_maps ? linux_maps->IsExecutable() : false;
}
bool ExploitabilityLinux::BenignCrashTrigger(
const MDRawExceptionStream* raw_exception_stream) {
// Check the cause of crash.
// If the exception of the crash is a benign exception,
// it is probably not exploitable.
switch (raw_exception_stream->exception_record.exception_code) {
case MD_EXCEPTION_CODE_LIN_SIGHUP:
case MD_EXCEPTION_CODE_LIN_SIGINT:
case MD_EXCEPTION_CODE_LIN_SIGQUIT:
case MD_EXCEPTION_CODE_LIN_SIGTRAP:
case MD_EXCEPTION_CODE_LIN_SIGABRT:
case MD_EXCEPTION_CODE_LIN_SIGFPE:
case MD_EXCEPTION_CODE_LIN_SIGKILL:
case MD_EXCEPTION_CODE_LIN_SIGUSR1:
case MD_EXCEPTION_CODE_LIN_SIGUSR2:
case MD_EXCEPTION_CODE_LIN_SIGPIPE:
case MD_EXCEPTION_CODE_LIN_SIGALRM:
case MD_EXCEPTION_CODE_LIN_SIGTERM:
case MD_EXCEPTION_CODE_LIN_SIGCHLD:
case MD_EXCEPTION_CODE_LIN_SIGCONT:
case MD_EXCEPTION_CODE_LIN_SIGSTOP:
case MD_EXCEPTION_CODE_LIN_SIGTSTP:
case MD_EXCEPTION_CODE_LIN_SIGTTIN:
case MD_EXCEPTION_CODE_LIN_SIGTTOU:
case MD_EXCEPTION_CODE_LIN_SIGURG:
case MD_EXCEPTION_CODE_LIN_SIGXCPU:
case MD_EXCEPTION_CODE_LIN_SIGXFSZ:
case MD_EXCEPTION_CODE_LIN_SIGVTALRM:
case MD_EXCEPTION_CODE_LIN_SIGPROF:
case MD_EXCEPTION_CODE_LIN_SIGWINCH:
case MD_EXCEPTION_CODE_LIN_SIGIO:
case MD_EXCEPTION_CODE_LIN_SIGPWR:
case MD_EXCEPTION_CODE_LIN_SIGSYS:
case MD_EXCEPTION_CODE_LIN_DUMP_REQUESTED:
return true;
default:
return false;
}
}
} // namespace google_breakpad

View file

@ -0,0 +1,93 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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.
// exploitability_linux.h: Linux specific exploitability engine.
//
// Provides a guess at the exploitability of the crash for the Linux
// platform given a minidump and process_state.
//
// Author: Matthew Riley
#ifndef GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_LINUX_H_
#define GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_LINUX_H_
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/processor/exploitability.h"
namespace google_breakpad {
class ExploitabilityLinux : public Exploitability {
public:
ExploitabilityLinux(Minidump* dump,
ProcessState* process_state);
// Parameters are the minidump to analyze, the object representing process
// state, and whether to enable objdump disassembly.
// Enabling objdump will allow exploitability analysis to call out to
// objdump for diassembly. It is used to check the identity of the
// instruction that caused the program to crash. If there are any
// portability concerns, this should not be enabled.
ExploitabilityLinux(Minidump* dump,
ProcessState* process_state,
bool enable_objdump);
virtual ExploitabilityRating CheckPlatformExploitability();
private:
friend class ExploitabilityLinuxTest;
// Takes the address of the instruction pointer and returns
// whether the instruction pointer lies in a valid instruction region.
bool InstructionPointerInCode(uint64_t instruction_ptr);
// Checks the exception that triggered the creation of the
// minidump and reports whether the exception suggests no exploitability.
bool BenignCrashTrigger(const MDRawExceptionStream* raw_exception_stream);
// This method checks if the crash occurred during a write to read-only or
// invalid memory. It does so by checking if the instruction at the
// instruction pointer is a write instruction, and if the target of the
// instruction is at a spot in memory that prohibits writes.
bool EndedOnIllegalWrite(uint64_t instruction_ptr);
// Checks if the stack pointer points to a memory mapping that is not
// labelled as the stack.
bool StackPointerOffStack(uint64_t stack_ptr);
// Checks if the stack or heap are marked executable according
// to the memory mappings.
bool ExecutableStackOrHeap();
// Whether this exploitability engine is permitted to shell out to objdump
// to disassemble raw bytes.
bool enable_objdump_;
};
} // namespace google_breakpad
#endif // GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_LINUX_H_

View file

@ -0,0 +1,182 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <stdlib.h>
#include <unistd.h>
#include <type_traits>
#include <string>
#include "breakpad_googletest_includes.h"
#include "common/using_std_string.h"
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "google_breakpad/processor/minidump_processor.h"
#include "google_breakpad/processor/process_state.h"
#ifdef __linux__
#include "processor/exploitability_linux.h"
#endif // __linux__
#include "processor/simple_symbol_supplier.h"
#ifdef __linux__
namespace google_breakpad {
class ExploitabilityLinuxTestMinidumpContext : public MinidumpContext {
public:
explicit ExploitabilityLinuxTestMinidumpContext(
const MDRawContextAMD64& context) : MinidumpContext(NULL) {
valid_ = true;
SetContextAMD64(new MDRawContextAMD64(context));
SetContextFlags(MD_CONTEXT_AMD64);
}
};
} // namespace google_breakpad
#endif // __linux__
namespace {
using google_breakpad::BasicSourceLineResolver;
#ifdef __linux__
using google_breakpad::ExploitabilityLinuxTestMinidumpContext;
#endif // __linux__
using google_breakpad::MinidumpProcessor;
using google_breakpad::ProcessState;
using google_breakpad::SimpleSymbolSupplier;
string TestDataDir() {
return string(getenv("srcdir") ? getenv("srcdir") : ".") +
"/src/processor/testdata";
}
// Find the given dump file in <srcdir>/src/processor/testdata, process it,
// and get the exploitability rating. Returns EXPLOITABILITY_ERR_PROCESSING
// if the crash dump can't be processed.
google_breakpad::ExploitabilityRating
ExploitabilityFor(const string& filename) {
SimpleSymbolSupplier supplier(TestDataDir() + "/symbols");
BasicSourceLineResolver resolver;
MinidumpProcessor processor(&supplier, &resolver, true);
processor.set_enable_objdump_for_exploitability(true);
ProcessState state;
string minidump_file = TestDataDir() + "/" + filename;
if (processor.Process(minidump_file, &state) !=
google_breakpad::PROCESS_OK) {
return google_breakpad::EXPLOITABILITY_ERR_PROCESSING;
}
return state.exploitability();
}
TEST(ExploitabilityTest, TestWindowsEngine) {
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("ascii_read_av.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("ascii_read_av_block_write.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("ascii_read_av_clobber_write.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("ascii_read_av_conditional.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("ascii_read_av_then_jmp.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("ascii_read_av_xchg_write.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("ascii_write_av.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("ascii_write_av_arg_to_call.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_NONE,
ExploitabilityFor("null_read_av.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_NONE,
ExploitabilityFor("null_write_av.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_NONE,
ExploitabilityFor("stack_exhaustion.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("exec_av_on_stack.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_MEDIUM,
ExploitabilityFor("write_av_non_null.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_LOW,
ExploitabilityFor("read_av_non_null.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_LOW,
ExploitabilityFor("read_av_clobber_write.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_LOW,
ExploitabilityFor("read_av_conditional.dmp"));
}
TEST(ExploitabilityTest, TestLinuxEngine) {
ASSERT_EQ(google_breakpad::EXPLOITABILITY_INTERESTING,
ExploitabilityFor("linux_null_read_av.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_overflow.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_stacksmash.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_NONE,
ExploitabilityFor("linux_divide_by_zero.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_INTERESTING,
ExploitabilityFor("linux_null_dereference.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_jmp_to_0.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_outside_module.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_NONE,
ExploitabilityFor("linux_raise_sigabrt.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_INTERESTING,
ExploitabilityFor("linux_inside_module_exe_region1.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_INTERESTING,
ExploitabilityFor("linux_inside_module_exe_region2.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_INTERESTING,
ExploitabilityFor("linux_stack_pointer_in_stack.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_INTERESTING,
ExploitabilityFor("linux_stack_pointer_in_stack_alt_name.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_stack_pointer_in_module.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_executable_stack.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_executable_heap.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_jmp_to_module_not_exe_region.dmp"));
#ifdef __linux__
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_write_to_nonwritable_module.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_write_to_nonwritable_region_math.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_write_to_outside_module.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_HIGH,
ExploitabilityFor("linux_write_to_outside_module_via_math.dmp"));
ASSERT_EQ(google_breakpad::EXPLOITABILITY_INTERESTING,
ExploitabilityFor("linux_write_to_under_4k.dmp"));
#endif // __linux__
}
} // namespace

View file

@ -0,0 +1,285 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// exploitability_win.cc: Windows specific exploitability engine.
//
// Provides a guess at the exploitability of the crash for the Windows
// platform given a minidump and process_state.
//
// Author: Cris Neckar
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <vector>
#include "processor/exploitability_win.h"
#include "common/scoped_ptr.h"
#include "google_breakpad/common/minidump_exception_win32.h"
#include "google_breakpad/processor/minidump.h"
#include "processor/disassembler_x86.h"
#include "processor/logging.h"
#include "third_party/libdisasm/libdis.h"
namespace google_breakpad {
// The cutoff that we use to judge if and address is likely an offset
// from various interesting addresses.
static const uint64_t kProbableNullOffset = 4096;
static const uint64_t kProbableStackOffset = 8192;
// The various cutoffs for the different ratings.
static const size_t kHighCutoff = 100;
static const size_t kMediumCutoff = 80;
static const size_t kLowCutoff = 50;
static const size_t kInterestingCutoff = 25;
// Predefined incremental values for conditional weighting.
static const size_t kTinyBump = 5;
static const size_t kSmallBump = 20;
static const size_t kMediumBump = 50;
static const size_t kLargeBump = 70;
static const size_t kHugeBump = 90;
// The maximum number of bytes to disassemble past the program counter.
static const size_t kDisassembleBytesBeyondPC = 2048;
ExploitabilityWin::ExploitabilityWin(Minidump* dump,
ProcessState* process_state)
: Exploitability(dump, process_state) { }
ExploitabilityRating ExploitabilityWin::CheckPlatformExploitability() {
MinidumpException* exception = dump_->GetException();
if (!exception) {
BPLOG(INFO) << "Minidump does not have exception record.";
return EXPLOITABILITY_ERR_PROCESSING;
}
const MDRawExceptionStream* raw_exception = exception->exception();
if (!raw_exception) {
BPLOG(INFO) << "Could not obtain raw exception info.";
return EXPLOITABILITY_ERR_PROCESSING;
}
const MinidumpContext* context = exception->GetContext();
if (!context) {
BPLOG(INFO) << "Could not obtain exception context.";
return EXPLOITABILITY_ERR_PROCESSING;
}
MinidumpMemoryList* memory_list = dump_->GetMemoryList();
bool memory_available = true;
if (!memory_list) {
BPLOG(INFO) << "Minidump memory segments not available.";
memory_available = false;
}
uint64_t address = process_state_->crash_address();
uint32_t exception_code = raw_exception->exception_record.exception_code;
uint32_t exploitability_weight = 0;
uint64_t stack_ptr = 0;
uint64_t instruction_ptr = 0;
// Getting the instruction pointer.
if (!context->GetInstructionPointer(&instruction_ptr)) {
return EXPLOITABILITY_ERR_PROCESSING;
}
// Getting the stack pointer.
if (!context->GetStackPointer(&stack_ptr)) {
return EXPLOITABILITY_ERR_PROCESSING;
}
// Check if we are executing on the stack.
if (instruction_ptr <= (stack_ptr + kProbableStackOffset) &&
instruction_ptr >= (stack_ptr - kProbableStackOffset))
exploitability_weight += kHugeBump;
switch (exception_code) {
// This is almost certainly recursion.
case MD_EXCEPTION_CODE_WIN_STACK_OVERFLOW:
exploitability_weight += kTinyBump;
break;
// These exceptions tend to be benign and we can generally ignore them.
case MD_EXCEPTION_CODE_WIN_INTEGER_DIVIDE_BY_ZERO:
case MD_EXCEPTION_CODE_WIN_INTEGER_OVERFLOW:
case MD_EXCEPTION_CODE_WIN_FLOAT_DIVIDE_BY_ZERO:
case MD_EXCEPTION_CODE_WIN_FLOAT_INEXACT_RESULT:
case MD_EXCEPTION_CODE_WIN_FLOAT_OVERFLOW:
case MD_EXCEPTION_CODE_WIN_FLOAT_UNDERFLOW:
case MD_EXCEPTION_CODE_WIN_IN_PAGE_ERROR:
exploitability_weight += kTinyBump;
break;
// These exceptions will typically mean that we have jumped where we
// shouldn't.
case MD_EXCEPTION_CODE_WIN_ILLEGAL_INSTRUCTION:
case MD_EXCEPTION_CODE_WIN_FLOAT_INVALID_OPERATION:
case MD_EXCEPTION_CODE_WIN_PRIVILEGED_INSTRUCTION:
exploitability_weight += kLargeBump;
break;
// These represent bugs in exception handlers.
case MD_EXCEPTION_CODE_WIN_INVALID_DISPOSITION:
case MD_EXCEPTION_CODE_WIN_NONCONTINUABLE_EXCEPTION:
exploitability_weight += kSmallBump;
break;
case MD_EXCEPTION_CODE_WIN_HEAP_CORRUPTION:
case MD_EXCEPTION_CODE_WIN_STACK_BUFFER_OVERRUN:
exploitability_weight += kHugeBump;
break;
case MD_EXCEPTION_CODE_WIN_GUARD_PAGE_VIOLATION:
exploitability_weight += kLargeBump;
break;
case MD_EXCEPTION_CODE_WIN_ACCESS_VIOLATION:
bool near_null = (address <= kProbableNullOffset);
bool bad_read = false;
bool bad_write = false;
if (raw_exception->exception_record.number_parameters >= 1) {
MDAccessViolationTypeWin av_type =
static_cast<MDAccessViolationTypeWin>
(raw_exception->exception_record.exception_information[0]);
switch (av_type) {
case MD_ACCESS_VIOLATION_WIN_READ:
bad_read = true;
if (near_null)
exploitability_weight += kSmallBump;
else
exploitability_weight += kMediumBump;
break;
case MD_ACCESS_VIOLATION_WIN_WRITE:
bad_write = true;
if (near_null)
exploitability_weight += kSmallBump;
else
exploitability_weight += kHugeBump;
break;
case MD_ACCESS_VIOLATION_WIN_EXEC:
if (near_null)
exploitability_weight += kSmallBump;
else
exploitability_weight += kHugeBump;
break;
default:
BPLOG(INFO) << "Unrecognized access violation type.";
return EXPLOITABILITY_ERR_PROCESSING;
}
MinidumpMemoryRegion* instruction_region = 0;
if (memory_available) {
instruction_region =
memory_list->GetMemoryRegionForAddress(instruction_ptr);
}
if (!near_null && instruction_region &&
context->GetContextCPU() == MD_CONTEXT_X86 &&
(bad_read || bad_write)) {
// Perform checks related to memory around instruction pointer.
uint32_t memory_offset =
instruction_ptr - instruction_region->GetBase();
uint32_t available_memory =
instruction_region->GetSize() - memory_offset;
available_memory = available_memory > kDisassembleBytesBeyondPC ?
kDisassembleBytesBeyondPC : available_memory;
if (available_memory) {
const uint8_t* raw_memory =
instruction_region->GetMemory() + memory_offset;
DisassemblerX86 disassembler(raw_memory,
available_memory,
instruction_ptr);
disassembler.NextInstruction();
if (bad_read)
disassembler.setBadRead();
else
disassembler.setBadWrite();
if (disassembler.currentInstructionValid()) {
// Check if the faulting instruction falls into one of
// several interesting groups.
switch (disassembler.currentInstructionGroup()) {
case libdis::insn_controlflow:
exploitability_weight += kLargeBump;
break;
case libdis::insn_string:
exploitability_weight += kHugeBump;
break;
default:
break;
}
// Loop the disassembler through the code and check if it
// IDed any interesting conditions in the near future.
// Multiple flags may be set so treat each equally.
while (disassembler.NextInstruction() &&
disassembler.currentInstructionValid() &&
!disassembler.endOfBlock())
continue;
if (disassembler.flags() & DISX86_BAD_BRANCH_TARGET)
exploitability_weight += kLargeBump;
if (disassembler.flags() & DISX86_BAD_ARGUMENT_PASSED)
exploitability_weight += kTinyBump;
if (disassembler.flags() & DISX86_BAD_WRITE)
exploitability_weight += kMediumBump;
if (disassembler.flags() & DISX86_BAD_BLOCK_WRITE)
exploitability_weight += kMediumBump;
if (disassembler.flags() & DISX86_BAD_READ)
exploitability_weight += kTinyBump;
if (disassembler.flags() & DISX86_BAD_BLOCK_READ)
exploitability_weight += kTinyBump;
if (disassembler.flags() & DISX86_BAD_COMPARISON)
exploitability_weight += kTinyBump;
}
}
}
if (!near_null && AddressIsAscii(address))
exploitability_weight += kMediumBump;
} else {
BPLOG(INFO) << "Access violation type parameter missing.";
return EXPLOITABILITY_ERR_PROCESSING;
}
}
// Based on the calculated weight we return a simplified classification.
BPLOG(INFO) << "Calculated exploitability weight: " << exploitability_weight;
if (exploitability_weight >= kHighCutoff)
return EXPLOITABILITY_HIGH;
if (exploitability_weight >= kMediumCutoff)
return EXPLOITABLITY_MEDIUM;
if (exploitability_weight >= kLowCutoff)
return EXPLOITABILITY_LOW;
if (exploitability_weight >= kInterestingCutoff)
return EXPLOITABILITY_INTERESTING;
return EXPLOITABILITY_NONE;
}
} // namespace google_breakpad

View file

@ -0,0 +1,54 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// exploitability_win.h: Windows specific exploitability engine.
//
// Provides a guess at the exploitability of the crash for the Windows
// platform given a minidump and process_state.
//
// Author: Cris Neckar
#ifndef GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_WIN_H_
#define GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_WIN_H_
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/processor/exploitability.h"
namespace google_breakpad {
class ExploitabilityWin : public Exploitability {
public:
ExploitabilityWin(Minidump *dump,
ProcessState *process_state);
virtual ExploitabilityRating CheckPlatformExploitability();
};
} // namespace google_breakpad
#endif // GOOGLE_BREAKPAD_PROCESSOR_EXPLOITABILITY_WIN_H_

View file

@ -0,0 +1,364 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// fast_source_line_resolver.cc: FastSourceLineResolver is a concrete class that
// implements SourceLineResolverInterface. Both FastSourceLineResolver and
// BasicSourceLineResolver inherit from SourceLineResolverBase class to reduce
// code redundancy.
//
// See fast_source_line_resolver.h and fast_source_line_resolver_types.h
// for more documentation.
//
// Author: Siyang Xie (lambxsy@google.com)
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/fast_source_line_resolver.h"
#include "processor/fast_source_line_resolver_types.h"
#include <cassert>
#include <map>
#include <string>
#include <utility>
#include "common/scoped_ptr.h"
#include "common/using_std_string.h"
#include "processor/logging.h"
#include "processor/module_factory.h"
#include "processor/simple_serializer-inl.h"
using std::deque;
using std::unique_ptr;
namespace google_breakpad {
FastSourceLineResolver::FastSourceLineResolver()
: SourceLineResolverBase(new FastModuleFactory) { }
bool FastSourceLineResolver::ShouldDeleteMemoryBufferAfterLoadModule() {
return false;
}
void FastSourceLineResolver::Module::LookupAddress(
StackFrame* frame,
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) const {
MemAddr address = frame->instruction - frame->module->base_address();
// First, look for a FUNC record that covers address. Use
// RetrieveNearestRange instead of RetrieveRange so that, if there
// is no such function, we can use the next function to bound the
// extent of the PUBLIC symbol we find, below. This does mean we
// need to check that address indeed falls within the function we
// find; do the range comparison in an overflow-friendly way.
scoped_ptr<Function> func(new Function);
const Function* func_ptr = 0;
scoped_ptr<PublicSymbol> public_symbol(new PublicSymbol);
const PublicSymbol* public_symbol_ptr = 0;
MemAddr function_base;
MemAddr function_size;
MemAddr public_address;
if (functions_.RetrieveNearestRange(address, func_ptr,
&function_base, &function_size) &&
address >= function_base && address - function_base < function_size) {
func->CopyFrom(func_ptr);
frame->function_name = func->name;
frame->function_base = frame->module->base_address() + function_base;
frame->is_multiple = func->is_multiple;
scoped_ptr<Line> line(new Line);
const Line* line_ptr = 0;
MemAddr line_base;
if (func->lines.RetrieveRange(address, line_ptr, &line_base, NULL)) {
line->CopyFrom(line_ptr);
FileMap::iterator it = files_.find(line->source_file_id);
if (it != files_.end()) {
frame->source_file_name =
files_.find(line->source_file_id).GetValuePtr();
}
frame->source_line = line->line;
frame->source_line_base = frame->module->base_address() + line_base;
}
// Check if this is inlined function call.
if (inlined_frames) {
ConstructInlineFrames(frame, address, func->inlines, inlined_frames);
}
} else if (public_symbols_.Retrieve(address,
public_symbol_ptr, &public_address) &&
(!func_ptr || public_address > function_base)) {
public_symbol->CopyFrom(public_symbol_ptr);
frame->function_name = public_symbol->name;
frame->function_base = frame->module->base_address() + public_address;
frame->is_multiple = public_symbol->is_multiple;
}
}
void FastSourceLineResolver::Module::ConstructInlineFrames(
StackFrame* frame,
MemAddr address,
const StaticContainedRangeMap<MemAddr, char>& inline_map,
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) const {
std::vector<const char*> inline_ptrs;
if (!inline_map.RetrieveRanges(address, inline_ptrs)) {
return;
}
for (const char* inline_ptr : inline_ptrs) {
scoped_ptr<Inline> in(new Inline);
in->CopyFrom(inline_ptr);
unique_ptr<StackFrame> new_frame =
unique_ptr<StackFrame>(new StackFrame(*frame));
auto origin_iter = inline_origins_.find(in->origin_id);
if (origin_iter != inline_origins_.end()) {
scoped_ptr<InlineOrigin> origin(new InlineOrigin);
origin->CopyFrom(origin_iter.GetValuePtr());
new_frame->function_name = origin->name;
} else {
new_frame->function_name = "<name omitted>";
}
// Store call site file and line in current frame, which will be updated
// later.
new_frame->source_line = in->call_site_line;
if (in->has_call_site_file_id) {
auto file_iter = files_.find(in->call_site_file_id);
if (file_iter != files_.end()) {
new_frame->source_file_name = file_iter.GetValuePtr();
}
}
// Use the starting adress of the inlined range as inlined function base.
new_frame->function_base = new_frame->module->base_address();
for (const auto& range : in->inline_ranges) {
if (address >= range.first && address < range.first + range.second) {
new_frame->function_base += range.first;
break;
}
}
new_frame->trust = StackFrame::FRAME_TRUST_INLINE;
// The inlines vector has an order from innermost entry to outermost entry.
// By push_back, we will have inlined_frames with the same order.
inlined_frames->push_back(std::move(new_frame));
}
// Update the source file and source line for each inlined frame.
if (!inlined_frames->empty()) {
string parent_frame_source_file_name = frame->source_file_name;
int parent_frame_source_line = frame->source_line;
frame->source_file_name = inlined_frames->back()->source_file_name;
frame->source_line = inlined_frames->back()->source_line;
for (unique_ptr<StackFrame>& inlined_frame : *inlined_frames) {
std::swap(inlined_frame->source_file_name, parent_frame_source_file_name);
std::swap(inlined_frame->source_line, parent_frame_source_line);
}
}
}
// WFI: WindowsFrameInfo.
// Returns a WFI object reading from a raw memory chunk of data
WindowsFrameInfo FastSourceLineResolver::CopyWFI(const char* raw) {
const WindowsFrameInfo::StackInfoTypes type =
static_cast<const WindowsFrameInfo::StackInfoTypes>(
*reinterpret_cast<const int32_t*>(raw));
// The first 8 bytes of int data are unused.
// They correspond to "StackInfoTypes type_;" and "int valid;"
// data member of WFI.
const uint32_t* para_uint32 = reinterpret_cast<const uint32_t*>(
raw + 2 * sizeof(int32_t));
uint32_t prolog_size = para_uint32[0];;
uint32_t epilog_size = para_uint32[1];
uint32_t parameter_size = para_uint32[2];
uint32_t saved_register_size = para_uint32[3];
uint32_t local_size = para_uint32[4];
uint32_t max_stack_size = para_uint32[5];
const char* boolean = reinterpret_cast<const char*>(para_uint32 + 6);
bool allocates_base_pointer = (*boolean != 0);
string program_string = boolean + 1;
return WindowsFrameInfo(type,
prolog_size,
epilog_size,
parameter_size,
saved_register_size,
local_size,
max_stack_size,
allocates_base_pointer,
program_string);
}
// Loads a map from the given buffer in char* type.
// Does NOT take ownership of mem_buffer.
// In addition, treat mem_buffer as const char*.
bool FastSourceLineResolver::Module::LoadMapFromMemory(
char* memory_buffer,
size_t memory_buffer_size) {
if (!memory_buffer) return false;
// Read the "is_corrupt" flag.
const char* mem_buffer = memory_buffer;
mem_buffer = SimpleSerializer<bool>::Read(mem_buffer, &is_corrupt_);
const uint32_t* map_sizes = reinterpret_cast<const uint32_t*>(mem_buffer);
unsigned int header_size = kNumberMaps_ * sizeof(unsigned int);
// offsets[]: an array of offset addresses (with respect to mem_buffer),
// for each "Static***Map" component of Module.
// "Static***Map": static version of std::map or map wrapper, i.e., StaticMap,
// StaticAddressMap, StaticContainedRangeMap, and StaticRangeMap.
unsigned int offsets[kNumberMaps_];
offsets[0] = header_size;
for (int i = 1; i < kNumberMaps_; ++i) {
offsets[i] = offsets[i - 1] + map_sizes[i - 1];
}
unsigned int expected_size = sizeof(bool) + offsets[kNumberMaps_ - 1] +
map_sizes[kNumberMaps_ - 1] + 1;
if (expected_size != memory_buffer_size &&
// Allow for having an extra null terminator.
expected_size != memory_buffer_size - 1) {
// This could either be a random corruption or the serialization format was
// changed without updating the version in kSerializedBreakpadFileExtension.
BPLOG(ERROR) << "Memory buffer is either corrupt or an unsupported version"
<< ", expected size: " << expected_size
<< ", actual size: " << memory_buffer_size;
return false;
}
BPLOG(INFO) << "Memory buffer size looks good, size: " << memory_buffer_size;
// Use pointers to construct Static*Map data members in Module:
int map_id = 0;
files_ = StaticMap<int, char>(mem_buffer + offsets[map_id++]);
functions_ =
StaticRangeMap<MemAddr, Function>(mem_buffer + offsets[map_id++]);
public_symbols_ =
StaticAddressMap<MemAddr, PublicSymbol>(mem_buffer + offsets[map_id++]);
for (int i = 0; i < WindowsFrameInfo::STACK_INFO_LAST; ++i) {
windows_frame_info_[i] =
StaticContainedRangeMap<MemAddr, char>(mem_buffer + offsets[map_id++]);
}
cfi_initial_rules_ =
StaticRangeMap<MemAddr, char>(mem_buffer + offsets[map_id++]);
cfi_delta_rules_ = StaticMap<MemAddr, char>(mem_buffer + offsets[map_id++]);
inline_origins_ = StaticMap<int, char>(mem_buffer + offsets[map_id++]);
return true;
}
WindowsFrameInfo* FastSourceLineResolver::Module::FindWindowsFrameInfo(
const StackFrame* frame) const {
MemAddr address = frame->instruction - frame->module->base_address();
scoped_ptr<WindowsFrameInfo> result(new WindowsFrameInfo());
// 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.
const char* frame_info_ptr;
if ((windows_frame_info_[WindowsFrameInfo::STACK_INFO_FRAME_DATA]
.RetrieveRange(address, frame_info_ptr))
|| (windows_frame_info_[WindowsFrameInfo::STACK_INFO_FPO]
.RetrieveRange(address, frame_info_ptr))) {
result->CopyFrom(CopyWFI(frame_info_ptr));
return result.release();
}
// Even without a relevant STACK line, many functions contain
// information about how much space their parameters consume on the
// stack. Use RetrieveNearestRange instead of RetrieveRange, so that
// we can use the function to bound the extent of the PUBLIC symbol,
// below. However, this does mean we need to check that ADDRESS
// falls within the retrieved function's range; do the range
// comparison in an overflow-friendly way.
scoped_ptr<Function> function(new Function);
const Function* function_ptr = 0;
MemAddr function_base, function_size;
if (functions_.RetrieveNearestRange(address, function_ptr,
&function_base, &function_size) &&
address >= function_base && address - function_base < function_size) {
function->CopyFrom(function_ptr);
result->parameter_size = function->parameter_size;
result->valid |= WindowsFrameInfo::VALID_PARAMETER_SIZE;
return result.release();
}
// PUBLIC symbols might have a parameter size. Use the function we
// found above to limit the range the public symbol covers.
scoped_ptr<PublicSymbol> public_symbol(new PublicSymbol);
const PublicSymbol* public_symbol_ptr = 0;
MemAddr public_address;
if (public_symbols_.Retrieve(address, public_symbol_ptr, &public_address) &&
(!function_ptr || public_address > function_base)) {
public_symbol->CopyFrom(public_symbol_ptr);
result->parameter_size = public_symbol->parameter_size;
}
return NULL;
}
CFIFrameInfo* FastSourceLineResolver::Module::FindCFIFrameInfo(
const StackFrame* frame) const {
MemAddr address = frame->instruction - frame->module->base_address();
MemAddr initial_base, initial_size;
const char* initial_rules = NULL;
// Find the initial rule whose range covers this address. That
// provides an initial set of register recovery rules. Then, walk
// forward from the initial rule's starting address to frame's
// instruction address, applying delta rules.
if (!cfi_initial_rules_.RetrieveRange(address, initial_rules,
&initial_base, &initial_size)) {
return NULL;
}
// Create a frame info structure, and populate it with the rules from
// the STACK CFI INIT record.
scoped_ptr<CFIFrameInfo> rules(new CFIFrameInfo());
if (!ParseCFIRuleSet(initial_rules, rules.get()))
return NULL;
// Find the first delta rule that falls within the initial rule's range.
StaticMap<MemAddr, char>::iterator delta =
cfi_delta_rules_.lower_bound(initial_base);
// Apply delta rules up to and including the frame's address.
while (delta != cfi_delta_rules_.end() && delta.GetKey() <= address) {
ParseCFIRuleSet(delta.GetValuePtr(), rules.get());
delta++;
}
return rules.release();
}
} // namespace google_breakpad

View file

@ -0,0 +1,249 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// fast_source_line_resolver_types.h: definition of nested classes/structs in
// FastSourceLineResolver. It moves the definitions out of
// fast_source_line_resolver.cc, so that other classes could have access
// to these private nested types without including fast_source_line_resolver.cc
//
// Author: lambxsy@google.com (Siyang Xie)
#ifndef PROCESSOR_FAST_SOURCE_LINE_RESOLVER_TYPES_H__
#define PROCESSOR_FAST_SOURCE_LINE_RESOLVER_TYPES_H__
#include <cstdint>
#include <map>
#include <string>
#include "google_breakpad/processor/fast_source_line_resolver.h"
#include "google_breakpad/processor/stack_frame.h"
#include "processor/cfi_frame_info.h"
#include "processor/contained_range_map.h"
#include "processor/simple_serializer-inl.h"
#include "processor/source_line_resolver_base_types.h"
#include "processor/static_address_map-inl.h"
#include "processor/static_contained_range_map-inl.h"
#include "processor/static_map.h"
#include "processor/static_range_map-inl.h"
#include "processor/windows_frame_info.h"
namespace google_breakpad {
#define DESERIALIZE(raw_ptr, field) \
field = *(reinterpret_cast<const decltype(field)*>(raw_ptr)); \
raw_ptr += sizeof(field);
struct FastSourceLineResolver::Line : public SourceLineResolverBase::Line {
void CopyFrom(const Line* line_ptr) {
const char* raw = reinterpret_cast<const char*>(line_ptr);
CopyFrom(raw);
}
// De-serialize the memory data of a Line.
void CopyFrom(const char* raw) {
DESERIALIZE(raw, address);
DESERIALIZE(raw, size);
DESERIALIZE(raw, source_file_id);
DESERIALIZE(raw, line);
}
};
struct FastSourceLineResolver::Function :
public SourceLineResolverBase::Function {
void CopyFrom(const Function* func_ptr) {
const char* raw = reinterpret_cast<const char*>(func_ptr);
CopyFrom(raw);
}
// De-serialize the memory data of a Function.
void CopyFrom(const char* raw) {
size_t name_size = strlen(raw) + 1;
name = raw;
raw += name_size;
DESERIALIZE(raw, address);
DESERIALIZE(raw, size);
DESERIALIZE(raw, parameter_size);
raw = SimpleSerializer<bool>::Read(raw, &is_multiple);
int32_t inline_size;
DESERIALIZE(raw, inline_size);
inlines = StaticContainedRangeMap<MemAddr, char>(raw);
lines = StaticRangeMap<MemAddr, Line>(raw + inline_size);
}
StaticContainedRangeMap<MemAddr, char> inlines;
StaticRangeMap<MemAddr, Line> lines;
};
struct FastSourceLineResolver::Inline : public SourceLineResolverBase::Inline {
void CopyFrom(const Inline* inline_ptr) {
const char* raw = reinterpret_cast<const char*>(inline_ptr);
CopyFrom(raw);
}
// De-serialize the memory data of a Inline.
void CopyFrom(const char* raw) {
DESERIALIZE(raw, has_call_site_file_id);
DESERIALIZE(raw, inline_nest_level);
DESERIALIZE(raw, call_site_line);
DESERIALIZE(raw, call_site_file_id);
DESERIALIZE(raw, origin_id);
uint32_t inline_range_size;
DESERIALIZE(raw, inline_range_size);
for (size_t i = 0; i < inline_range_size; i += 2) {
std::pair<MemAddr, MemAddr> range;
DESERIALIZE(raw, range.first);
DESERIALIZE(raw, range.second);
inline_ranges.push_back(range);
}
}
};
struct FastSourceLineResolver::InlineOrigin
: public SourceLineResolverBase::InlineOrigin {
void CopyFrom(const InlineOrigin* origin_ptr) {
const char* raw = reinterpret_cast<const char*>(origin_ptr);
CopyFrom(raw);
}
// De-serialize the memory data of a Line.
void CopyFrom(const char* raw) {
DESERIALIZE(raw, has_file_id);
DESERIALIZE(raw, source_file_id);
name = raw;
}
};
struct FastSourceLineResolver::PublicSymbol :
public SourceLineResolverBase::PublicSymbol {
void CopyFrom(const PublicSymbol* public_symbol_ptr) {
const char* raw = reinterpret_cast<const char*>(public_symbol_ptr);
CopyFrom(raw);
}
// De-serialize the memory data of a PublicSymbol.
void CopyFrom(const char* raw) {
size_t name_size = strlen(raw) + 1;
name = raw;
raw += name_size;
DESERIALIZE(raw, address);
DESERIALIZE(raw, parameter_size);
raw = SimpleSerializer<bool>::Read(raw, &is_multiple);
}
};
#undef DESERIALIZE
class FastSourceLineResolver::Module: public SourceLineResolverBase::Module {
public:
explicit Module(const string& name) : name_(name), is_corrupt_(false) { }
virtual ~Module() { }
// Looks up the given relative address, and fills the StackFrame struct
// with the result.
virtual void LookupAddress(
StackFrame* frame,
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) const;
// Construct inlined frames for |frame| and store them in |inline_frames|.
// |frame|'s source line and source file name may be updated if an inlined
// frame is found inside |frame|. As a result, the innermost inlined frame
// will be the first one in |inline_frames|.
virtual void ConstructInlineFrames(
StackFrame* frame,
MemAddr address,
const StaticContainedRangeMap<MemAddr, char>& inline_map,
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) const;
// Loads a map from the given buffer in char* type.
virtual bool LoadMapFromMemory(char* memory_buffer,
size_t memory_buffer_size);
// Tells whether the loaded symbol data is corrupt. Return value is
// undefined, if the symbol data hasn't been loaded yet.
virtual bool IsCorrupt() const { return is_corrupt_; }
// If Windows stack walking information is available covering 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;
// 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;
// Number of serialized map components of Module.
static const int kNumberMaps_ = 6 + WindowsFrameInfo::STACK_INFO_LAST;
private:
friend class FastSourceLineResolver;
friend class ModuleComparer;
typedef StaticMap<int, char> FileMap;
string name_;
StaticMap<int, char> files_;
StaticRangeMap<MemAddr, Function> functions_;
StaticAddressMap<MemAddr, PublicSymbol> public_symbols_;
bool is_corrupt_;
// Each element in the array is a ContainedRangeMap for a type
// listed in WindowsFrameInfoTypes. These are split by type because
// there may be overlaps between maps of different types, but some
// information is only available as certain types.
StaticContainedRangeMap<MemAddr, char>
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:
// although the file may contain hundreds of thousands of STACK CFI
// records, walking a stack will only ever use a few of them, so it's
// best to delay parsing a record until it's actually needed.
//
// STACK CFI INIT records: for each range, an initial set of register
// recovery rules. The RangeMap's itself gives the starting and ending
// addresses.
StaticRangeMap<MemAddr, char> cfi_initial_rules_;
// STACK CFI records: at a given address, the changes to the register
// recovery rules that take effect at that address. The map key is the
// starting address; the ending address is the key of the next entry in
// this map, or the end of the range as given by the cfi_initial_rules_
// entry (which FindCFIFrameInfo looks up first).
StaticMap<MemAddr, char> cfi_delta_rules_;
// INLINE_ORIGIN records: used as a function name string pool for INLINE
// records.
StaticMap<int, char> inline_origins_;
};
} // namespace google_breakpad
#endif // PROCESSOR_FAST_SOURCE_LINE_RESOLVER_TYPES_H__

View file

@ -0,0 +1,603 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// fast_source_line_resolver_unittest.cc: Unit tests for FastSourceLineResolver.
// Two different approaches for testing fast source line resolver:
// First, use the same unit test data for basic source line resolver.
// Second, read data from symbol files, load them as basic modules, and then
// serialize them and load the serialized data as fast modules. Then compare
// modules to assure the fast module contains exactly the same data as
// basic module.
//
// Author: Siyang Xie (lambxsy@google.com)
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <assert.h>
#include <stdio.h>
#include <sstream>
#include <string>
#include "breakpad_googletest_includes.h"
#include "common/using_std_string.h"
#include "google_breakpad/processor/code_module.h"
#include "google_breakpad/processor/stack_frame.h"
#include "google_breakpad/processor/memory_region.h"
#include "processor/logging.h"
#include "processor/module_serializer.h"
#include "processor/module_comparer.h"
namespace {
using google_breakpad::SourceLineResolverBase;
using google_breakpad::BasicSourceLineResolver;
using google_breakpad::FastSourceLineResolver;
using google_breakpad::ModuleSerializer;
using google_breakpad::ModuleComparer;
using google_breakpad::CFIFrameInfo;
using google_breakpad::CodeModule;
using google_breakpad::MemoryRegion;
using google_breakpad::StackFrame;
using google_breakpad::WindowsFrameInfo;
using google_breakpad::scoped_ptr;
class TestCodeModule : public CodeModule {
public:
explicit TestCodeModule(string code_file) : code_file_(code_file) {}
virtual ~TestCodeModule() {}
virtual uint64_t base_address() const { return 0; }
virtual uint64_t size() const { return 0xb000; }
virtual string code_file() const { return code_file_; }
virtual string code_identifier() const { return ""; }
virtual string debug_file() const { return ""; }
virtual string debug_identifier() const { return ""; }
virtual string version() const { return ""; }
virtual CodeModule* Copy() const {
return new TestCodeModule(code_file_);
}
virtual bool is_unloaded() const { return false; }
virtual uint64_t shrink_down_delta() const { return 0; }
virtual void SetShrinkDownDelta(uint64_t shrink_down_delta) {}
private:
string code_file_;
};
// A mock memory region object, for use by the STACK CFI tests.
class MockMemoryRegion: public MemoryRegion {
uint64_t GetBase() const { return 0x10000; }
uint32_t GetSize() const { return 0x01000; }
bool GetMemoryAtAddress(uint64_t address, uint8_t* value) const {
*value = address & 0xff;
return true;
}
bool GetMemoryAtAddress(uint64_t address, uint16_t* value) const {
*value = address & 0xffff;
return true;
}
bool GetMemoryAtAddress(uint64_t address, uint32_t* value) const {
switch (address) {
case 0x10008: *value = 0x98ecadc3; break; // saved %ebx
case 0x1000c: *value = 0x878f7524; break; // saved %esi
case 0x10010: *value = 0x6312f9a5; break; // saved %edi
case 0x10014: *value = 0x10038; break; // caller's %ebp
case 0x10018: *value = 0xf6438648; break; // return address
default: *value = 0xdeadbeef; break; // junk
}
return true;
}
bool GetMemoryAtAddress(uint64_t address, uint64_t* value) const {
*value = address;
return true;
}
void Print() const {
assert(false);
}
};
// Verify that, for every association in ACTUAL, EXPECTED has the same
// association. (That is, ACTUAL's associations should be a subset of
// EXPECTED's.) Also verify that ACTUAL has associations for ".ra" and
// ".cfa".
static bool VerifyRegisters(
const char* file, int line,
const CFIFrameInfo::RegisterValueMap<uint32_t>& expected,
const CFIFrameInfo::RegisterValueMap<uint32_t>& actual) {
CFIFrameInfo::RegisterValueMap<uint32_t>::const_iterator a;
a = actual.find(".cfa");
if (a == actual.end())
return false;
a = actual.find(".ra");
if (a == actual.end())
return false;
for (a = actual.begin(); a != actual.end(); a++) {
CFIFrameInfo::RegisterValueMap<uint32_t>::const_iterator e =
expected.find(a->first);
if (e == expected.end()) {
fprintf(stderr, "%s:%d: unexpected register '%s' recovered, value 0x%x\n",
file, line, a->first.c_str(), a->second);
return false;
}
if (e->second != a->second) {
fprintf(stderr,
"%s:%d: register '%s' recovered value was 0x%x, expected 0x%x\n",
file, line, a->first.c_str(), a->second, e->second);
return false;
}
// Don't complain if this doesn't recover all registers. Although
// the DWARF spec says that unmentioned registers are undefined,
// GCC uses omission to mean that they are unchanged.
}
return true;
}
static bool VerifyEmpty(const StackFrame& frame) {
if (frame.function_name.empty() &&
frame.source_file_name.empty() &&
frame.source_line == 0)
return true;
return false;
}
static void ClearSourceLineInfo(StackFrame* frame) {
frame->function_name.clear();
frame->module = NULL;
frame->source_file_name.clear();
frame->source_line = 0;
}
class TestFastSourceLineResolver : public ::testing::Test {
public:
void SetUp() {
testdata_dir = string(getenv("srcdir") ? getenv("srcdir") : ".") +
"/src/processor/testdata";
}
string symbol_file(int file_index) {
std::stringstream ss;
ss << testdata_dir << "/module" << file_index << ".out";
return ss.str();
}
ModuleSerializer serializer;
BasicSourceLineResolver basic_resolver;
FastSourceLineResolver fast_resolver;
ModuleComparer module_comparer;
string testdata_dir;
};
// Test adapted from basic_source_line_resolver_unittest.
TEST_F(TestFastSourceLineResolver, TestLoadAndResolve) {
TestCodeModule module1("module1");
ASSERT_TRUE(basic_resolver.LoadModule(&module1, symbol_file(1)));
ASSERT_TRUE(basic_resolver.HasModule(&module1));
// Convert module1 to fast_module:
ASSERT_TRUE(serializer.ConvertOneModule(
module1.code_file(), &basic_resolver, &fast_resolver));
ASSERT_TRUE(fast_resolver.HasModule(&module1));
TestCodeModule module2("module2");
ASSERT_TRUE(basic_resolver.LoadModule(&module2, symbol_file(2)));
ASSERT_TRUE(basic_resolver.HasModule(&module2));
// Convert module2 to fast_module:
ASSERT_TRUE(serializer.ConvertOneModule(
module2.code_file(), &basic_resolver, &fast_resolver));
ASSERT_TRUE(fast_resolver.HasModule(&module2));
StackFrame frame;
scoped_ptr<WindowsFrameInfo> windows_frame_info;
scoped_ptr<CFIFrameInfo> cfi_frame_info;
frame.instruction = 0x1000;
frame.module = NULL;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_FALSE(frame.module);
ASSERT_TRUE(frame.function_name.empty());
ASSERT_EQ(frame.function_base, 0U);
ASSERT_TRUE(frame.source_file_name.empty());
ASSERT_EQ(frame.source_line, 0);
ASSERT_EQ(frame.source_line_base, 0U);
ASSERT_EQ(frame.is_multiple, false);
frame.module = &module1;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_EQ(frame.function_name, "Function1_1");
ASSERT_TRUE(frame.module);
ASSERT_EQ(frame.module->code_file(), "module1");
ASSERT_EQ(frame.function_base, 0x1000U);
ASSERT_EQ(frame.source_file_name, "file1_1.cc");
ASSERT_EQ(frame.source_line, 44);
ASSERT_EQ(frame.source_line_base, 0x1000U);
ASSERT_EQ(frame.is_multiple, true);
windows_frame_info.reset(fast_resolver.FindWindowsFrameInfo(&frame));
ASSERT_TRUE(windows_frame_info.get());
ASSERT_FALSE(windows_frame_info->allocates_base_pointer);
ASSERT_EQ(windows_frame_info->program_string,
"$eip 4 + ^ = $esp $ebp 8 + = $ebp $ebp ^ =");
ClearSourceLineInfo(&frame);
frame.instruction = 0x800;
frame.module = &module1;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_TRUE(VerifyEmpty(frame));
windows_frame_info.reset(fast_resolver.FindWindowsFrameInfo(&frame));
ASSERT_FALSE(windows_frame_info.get());
frame.instruction = 0x1280;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_EQ(frame.function_name, "Function1_3");
ASSERT_TRUE(frame.source_file_name.empty());
ASSERT_EQ(frame.source_line, 0);
windows_frame_info.reset(fast_resolver.FindWindowsFrameInfo(&frame));
ASSERT_TRUE(windows_frame_info.get());
ASSERT_EQ(windows_frame_info->type_, WindowsFrameInfo::STACK_INFO_UNKNOWN);
ASSERT_FALSE(windows_frame_info->allocates_base_pointer);
ASSERT_TRUE(windows_frame_info->program_string.empty());
frame.instruction = 0x1380;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_EQ(frame.function_name, "Function1_4");
ASSERT_TRUE(frame.source_file_name.empty());
ASSERT_EQ(frame.source_line, 0);
windows_frame_info.reset(fast_resolver.FindWindowsFrameInfo(&frame));
ASSERT_TRUE(windows_frame_info.get());
ASSERT_EQ(windows_frame_info->type_, WindowsFrameInfo::STACK_INFO_FRAME_DATA);
ASSERT_FALSE(windows_frame_info->allocates_base_pointer);
ASSERT_FALSE(windows_frame_info->program_string.empty());
frame.instruction = 0x2000;
windows_frame_info.reset(fast_resolver.FindWindowsFrameInfo(&frame));
ASSERT_FALSE(windows_frame_info.get());
// module1 has STACK CFI records covering 3d40..3def;
// module2 has STACK CFI records covering 3df0..3e9f;
// check that FindCFIFrameInfo doesn't claim to find any outside those ranges.
frame.instruction = 0x3d3f;
frame.module = &module1;
cfi_frame_info.reset(fast_resolver.FindCFIFrameInfo(&frame));
ASSERT_FALSE(cfi_frame_info.get());
frame.instruction = 0x3e9f;
frame.module = &module1;
cfi_frame_info.reset(fast_resolver.FindCFIFrameInfo(&frame));
ASSERT_FALSE(cfi_frame_info.get());
CFIFrameInfo::RegisterValueMap<uint32_t> current_registers;
CFIFrameInfo::RegisterValueMap<uint32_t> caller_registers;
CFIFrameInfo::RegisterValueMap<uint32_t> expected_caller_registers;
MockMemoryRegion memory;
// Regardless of which instruction evaluation takes place at, it
// should produce the same values for the caller's registers.
expected_caller_registers[".cfa"] = 0x1001c;
expected_caller_registers[".ra"] = 0xf6438648;
expected_caller_registers["$ebp"] = 0x10038;
expected_caller_registers["$ebx"] = 0x98ecadc3;
expected_caller_registers["$esi"] = 0x878f7524;
expected_caller_registers["$edi"] = 0x6312f9a5;
frame.instruction = 0x3d40;
frame.module = &module1;
current_registers.clear();
current_registers["$esp"] = 0x10018;
current_registers["$ebp"] = 0x10038;
current_registers["$ebx"] = 0x98ecadc3;
current_registers["$esi"] = 0x878f7524;
current_registers["$edi"] = 0x6312f9a5;
cfi_frame_info.reset(fast_resolver.FindCFIFrameInfo(&frame));
ASSERT_TRUE(cfi_frame_info.get());
ASSERT_TRUE(cfi_frame_info.get()
->FindCallerRegs<uint32_t>(current_registers, memory,
&caller_registers));
ASSERT_TRUE(VerifyRegisters(__FILE__, __LINE__,
expected_caller_registers, caller_registers));
frame.instruction = 0x3d41;
current_registers["$esp"] = 0x10014;
cfi_frame_info.reset(fast_resolver.FindCFIFrameInfo(&frame));
ASSERT_TRUE(cfi_frame_info.get());
ASSERT_TRUE(cfi_frame_info.get()
->FindCallerRegs<uint32_t>(current_registers, memory,
&caller_registers));
ASSERT_TRUE(VerifyRegisters(__FILE__, __LINE__,
expected_caller_registers, caller_registers));
frame.instruction = 0x3d43;
current_registers["$ebp"] = 0x10014;
cfi_frame_info.reset(fast_resolver.FindCFIFrameInfo(&frame));
ASSERT_TRUE(cfi_frame_info.get());
ASSERT_TRUE(cfi_frame_info.get()
->FindCallerRegs<uint32_t>(current_registers, memory,
&caller_registers));
VerifyRegisters(__FILE__, __LINE__,
expected_caller_registers, caller_registers);
frame.instruction = 0x3d54;
current_registers["$ebx"] = 0x6864f054U;
cfi_frame_info.reset(fast_resolver.FindCFIFrameInfo(&frame));
ASSERT_TRUE(cfi_frame_info.get());
ASSERT_TRUE(cfi_frame_info.get()
->FindCallerRegs<uint32_t>(current_registers, memory,
&caller_registers));
VerifyRegisters(__FILE__, __LINE__,
expected_caller_registers, caller_registers);
frame.instruction = 0x3d5a;
current_registers["$esi"] = 0x6285f79aU;
cfi_frame_info.reset(fast_resolver.FindCFIFrameInfo(&frame));
ASSERT_TRUE(cfi_frame_info.get());
ASSERT_TRUE(cfi_frame_info.get()
->FindCallerRegs<uint32_t>(current_registers, memory,
&caller_registers));
VerifyRegisters(__FILE__, __LINE__,
expected_caller_registers, caller_registers);
frame.instruction = 0x3d84;
current_registers["$edi"] = 0x64061449U;
cfi_frame_info.reset(fast_resolver.FindCFIFrameInfo(&frame));
ASSERT_TRUE(cfi_frame_info.get());
ASSERT_TRUE(cfi_frame_info.get()
->FindCallerRegs<uint32_t>(current_registers, memory,
&caller_registers));
VerifyRegisters(__FILE__, __LINE__,
expected_caller_registers, caller_registers);
frame.instruction = 0x2900;
frame.module = &module1;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_EQ(frame.function_name, string("PublicSymbol"));
EXPECT_EQ(frame.is_multiple, true);
frame.instruction = 0x4000;
frame.module = &module1;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_EQ(frame.function_name, string("LargeFunction"));
frame.instruction = 0x2181;
frame.module = &module2;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_EQ(frame.function_name, "Function2_2");
ASSERT_EQ(frame.function_base, 0x2170U);
ASSERT_TRUE(frame.module);
ASSERT_EQ(frame.module->code_file(), "module2");
ASSERT_EQ(frame.source_file_name, "file2_2.cc");
ASSERT_EQ(frame.source_line, 21);
ASSERT_EQ(frame.source_line_base, 0x2180U);
ASSERT_EQ(frame.is_multiple, false);
windows_frame_info.reset(fast_resolver.FindWindowsFrameInfo(&frame));
ASSERT_TRUE(windows_frame_info.get());
ASSERT_EQ(windows_frame_info->type_, WindowsFrameInfo::STACK_INFO_FRAME_DATA);
ASSERT_EQ(windows_frame_info->prolog_size, 1U);
frame.instruction = 0x216f;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_EQ(frame.function_name, "Public2_1");
EXPECT_EQ(frame.is_multiple, false);
ClearSourceLineInfo(&frame);
frame.instruction = 0x219f;
frame.module = &module2;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_TRUE(frame.function_name.empty());
frame.instruction = 0x21a0;
frame.module = &module2;
fast_resolver.FillSourceLineInfo(&frame, nullptr);
ASSERT_EQ(frame.function_name, "Public2_2");
}
// Test adapted from basic_source_line_resolver_unittest.
TEST_F(TestFastSourceLineResolver, TestLoadAndResolveOldInlines) {
TestCodeModule module("linux_inline");
ASSERT_TRUE(basic_resolver.LoadModule(
&module, testdata_dir +
"/symbols/linux_inline/BBA6FA10B8AAB33D00000000000000000/"
"linux_inline.old.sym"));
ASSERT_TRUE(basic_resolver.HasModule(&module));
// Convert module1 to fast_module:
ASSERT_TRUE(serializer.ConvertOneModule(module.code_file(), &basic_resolver,
&fast_resolver));
ASSERT_TRUE(fast_resolver.HasModule(&module));
StackFrame frame;
std::deque<std::unique_ptr<StackFrame>> inlined_frames;
frame.instruction = 0x161b6;
frame.module = &module;
fast_resolver.FillSourceLineInfo(&frame, &inlined_frames);
// main frame.
ASSERT_EQ(frame.function_name, "main");
ASSERT_EQ(frame.function_base, 0x15b30U);
ASSERT_EQ(frame.source_file_name, "linux_inline.cpp");
ASSERT_EQ(frame.source_line, 42);
ASSERT_EQ(frame.source_line_base, 0x161b6U);
ASSERT_EQ(frame.is_multiple, false);
ASSERT_EQ(inlined_frames.size(), 3UL);
// Inlined frames inside main frame.
ASSERT_EQ(inlined_frames[2]->function_name, "foo()");
ASSERT_EQ(inlined_frames[2]->function_base, 0x15b45U);
ASSERT_EQ(inlined_frames[2]->source_file_name, "linux_inline.cpp");
ASSERT_EQ(inlined_frames[2]->source_line, 39);
ASSERT_EQ(inlined_frames[2]->source_line_base, 0x161b6U);
ASSERT_EQ(inlined_frames[2]->trust, StackFrame::FRAME_TRUST_INLINE);
ASSERT_EQ(inlined_frames[1]->function_name, "bar()");
ASSERT_EQ(inlined_frames[1]->function_base, 0x15b72U);
ASSERT_EQ(inlined_frames[1]->source_file_name, "linux_inline.cpp");
ASSERT_EQ(inlined_frames[1]->source_line, 32);
ASSERT_EQ(inlined_frames[1]->source_line_base, 0x161b6U);
ASSERT_EQ(inlined_frames[1]->trust, StackFrame::FRAME_TRUST_INLINE);
ASSERT_EQ(inlined_frames[0]->function_name, "func()");
ASSERT_EQ(inlined_frames[0]->function_base, 0x15b83U);
ASSERT_EQ(inlined_frames[0]->source_file_name, "linux_inline.cpp");
ASSERT_EQ(inlined_frames[0]->source_line, 27);
ASSERT_EQ(inlined_frames[0]->source_line_base, 0x161b6U);
ASSERT_EQ(inlined_frames[0]->trust, StackFrame::FRAME_TRUST_INLINE);
}
// Test adapted from basic_source_line_resolver_unittest.
TEST_F(TestFastSourceLineResolver, TestLoadAndResolveNewInlines) {
TestCodeModule module("linux_inline");
ASSERT_TRUE(basic_resolver.LoadModule(
&module, testdata_dir +
"/symbols/linux_inline/BBA6FA10B8AAB33D00000000000000000/"
"linux_inline.new.sym"));
ASSERT_TRUE(basic_resolver.HasModule(&module));
// Convert module1 to fast_module:
ASSERT_TRUE(serializer.ConvertOneModule(module.code_file(), &basic_resolver,
&fast_resolver));
ASSERT_TRUE(fast_resolver.HasModule(&module));
StackFrame frame;
std::deque<std::unique_ptr<StackFrame>> inlined_frames;
frame.instruction = 0x161b6;
frame.module = &module;
fast_resolver.FillSourceLineInfo(&frame, &inlined_frames);
// main frame.
ASSERT_EQ(frame.function_name, "main");
ASSERT_EQ(frame.function_base, 0x15b30U);
ASSERT_EQ(frame.source_file_name, "a.cpp");
ASSERT_EQ(frame.source_line, 42);
ASSERT_EQ(frame.source_line_base, 0x161b6U);
ASSERT_EQ(frame.is_multiple, false);
ASSERT_EQ(inlined_frames.size(), 3UL);
// Inlined frames inside main frame.
ASSERT_EQ(inlined_frames[2]->function_name, "foo()");
ASSERT_EQ(inlined_frames[2]->function_base, 0x15b45U);
ASSERT_EQ(inlined_frames[2]->source_file_name, "b.cpp");
ASSERT_EQ(inlined_frames[2]->source_line, 39);
ASSERT_EQ(inlined_frames[2]->source_line_base, 0x161b6U);
ASSERT_EQ(inlined_frames[2]->trust, StackFrame::FRAME_TRUST_INLINE);
ASSERT_EQ(inlined_frames[1]->function_name, "bar()");
ASSERT_EQ(inlined_frames[1]->function_base, 0x15b72U);
ASSERT_EQ(inlined_frames[1]->source_file_name, "c.cpp");
ASSERT_EQ(inlined_frames[1]->source_line, 32);
ASSERT_EQ(inlined_frames[1]->source_line_base, 0x161b6U);
ASSERT_EQ(inlined_frames[1]->trust, StackFrame::FRAME_TRUST_INLINE);
ASSERT_EQ(inlined_frames[0]->function_name, "func()");
ASSERT_EQ(inlined_frames[0]->function_base, 0x15b83U);
ASSERT_EQ(inlined_frames[0]->source_file_name, "linux_inline.cpp");
ASSERT_EQ(inlined_frames[0]->source_line, 27);
ASSERT_EQ(inlined_frames[0]->source_line_base, 0x161b6U);
ASSERT_EQ(inlined_frames[0]->trust, StackFrame::FRAME_TRUST_INLINE);
}
TEST_F(TestFastSourceLineResolver, TestInvalidLoads) {
TestCodeModule module3("module3");
ASSERT_TRUE(basic_resolver.LoadModule(&module3,
testdata_dir + "/module3_bad.out"));
ASSERT_TRUE(basic_resolver.HasModule(&module3));
ASSERT_TRUE(basic_resolver.IsModuleCorrupt(&module3));
// Convert module3 to fast_module:
ASSERT_TRUE(serializer.ConvertOneModule(module3.code_file(),
&basic_resolver,
&fast_resolver));
ASSERT_TRUE(fast_resolver.HasModule(&module3));
ASSERT_TRUE(fast_resolver.IsModuleCorrupt(&module3));
TestCodeModule module4("module4");
ASSERT_TRUE(basic_resolver.LoadModule(&module4,
testdata_dir + "/module4_bad.out"));
ASSERT_TRUE(basic_resolver.HasModule(&module4));
ASSERT_TRUE(basic_resolver.IsModuleCorrupt(&module4));
// Convert module4 to fast_module:
ASSERT_TRUE(serializer.ConvertOneModule(module4.code_file(),
&basic_resolver,
&fast_resolver));
ASSERT_TRUE(fast_resolver.HasModule(&module4));
ASSERT_TRUE(fast_resolver.IsModuleCorrupt(&module4));
TestCodeModule module5("module5");
ASSERT_FALSE(fast_resolver.LoadModule(&module5,
testdata_dir + "/invalid-filename"));
ASSERT_FALSE(fast_resolver.HasModule(&module5));
TestCodeModule invalidmodule("invalid-module");
ASSERT_FALSE(fast_resolver.HasModule(&invalidmodule));
}
TEST_F(TestFastSourceLineResolver, TestUnload) {
TestCodeModule module1("module1");
ASSERT_FALSE(basic_resolver.HasModule(&module1));
ASSERT_TRUE(basic_resolver.LoadModule(&module1, symbol_file(1)));
ASSERT_TRUE(basic_resolver.HasModule(&module1));
// Convert module1 to fast_module.
ASSERT_TRUE(serializer.ConvertOneModule(module1.code_file(),
&basic_resolver,
&fast_resolver));
ASSERT_TRUE(fast_resolver.HasModule(&module1));
basic_resolver.UnloadModule(&module1);
fast_resolver.UnloadModule(&module1);
ASSERT_FALSE(fast_resolver.HasModule(&module1));
ASSERT_TRUE(basic_resolver.LoadModule(&module1, symbol_file(1)));
ASSERT_TRUE(basic_resolver.HasModule(&module1));
// Convert module1 to fast_module.
ASSERT_TRUE(serializer.ConvertOneModule(module1.code_file(),
&basic_resolver,
&fast_resolver));
ASSERT_TRUE(fast_resolver.HasModule(&module1));
}
TEST_F(TestFastSourceLineResolver, CompareModule) {
char* symbol_data;
size_t symbol_data_size;
string symbol_data_string;
string filename;
for (int module_index = 0; module_index < 3; ++module_index) {
std::stringstream ss;
ss << testdata_dir << "/module" << module_index << ".out";
filename = ss.str();
ASSERT_TRUE(SourceLineResolverBase::ReadSymbolFile(
symbol_file(module_index), &symbol_data, &symbol_data_size));
symbol_data_string.assign(symbol_data, symbol_data_size);
delete [] symbol_data;
ASSERT_TRUE(module_comparer.Compare(symbol_data_string));
}
}
} // namespace
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View file

@ -0,0 +1,192 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// A "smart" pointer type with reference tracking. Every pointer to a
// particular object is kept on a circular linked list. When the last pointer
// to an object is destroyed or reassigned, the object is deleted.
//
// Used properly, this deletes the object when the last reference goes away.
// There are several caveats:
// - Like all reference counting schemes, cycles lead to leaks.
// - Each smart pointer is actually two pointers (8 bytes instead of 4).
// - Every time a pointer is assigned, the entire list of pointers to that
// object is traversed. This class is therefore NOT SUITABLE when there
// will often be more than two or three pointers to a particular object.
// - References are only tracked as long as linked_ptr<> objects are copied.
// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS
// will happen (double deletion).
//
// A good use of this class is storing object references in STL containers.
// You can safely put linked_ptr<> in a vector<>.
// Other uses may not be as good.
//
// Note: If you use an incomplete type with linked_ptr<>, the class
// *containing* linked_ptr<> must have a constructor and destructor (even
// if they do nothing!).
#ifndef PROCESSOR_LINKED_PTR_H__
#define PROCESSOR_LINKED_PTR_H__
namespace google_breakpad {
// This is used internally by all instances of linked_ptr<>. It needs to be
// a non-template class because different types of linked_ptr<> can refer to
// the same object (linked_ptr<Superclass>(obj) vs linked_ptr<Subclass>(obj)).
// So, it needs to be possible for different types of linked_ptr to participate
// in the same circular linked list, so we need a single class type here.
//
// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr<T>.
class linked_ptr_internal {
public:
// Create a new circle that includes only this instance.
void join_new() {
next_ = this;
}
// Join an existing circle.
void join(linked_ptr_internal const* ptr) {
linked_ptr_internal const* p = ptr;
while (p->next_ != ptr) p = p->next_;
p->next_ = this;
next_ = ptr;
}
// Leave whatever circle we're part of. Returns true iff we were the
// last member of the circle. Once this is done, you can join() another.
bool depart() {
if (next_ == this) return true;
linked_ptr_internal const* p = next_;
while (p->next_ != this) p = p->next_;
p->next_ = next_;
return false;
}
private:
mutable linked_ptr_internal const* next_;
};
template <typename T>
class linked_ptr {
public:
typedef T element_type;
// Take over ownership of a raw pointer. This should happen as soon as
// possible after the object is created.
explicit linked_ptr(T* ptr = NULL) { capture(ptr); }
~linked_ptr() { depart(); }
// Copy an existing linked_ptr<>, adding ourselves to the list of references.
template <typename U> linked_ptr(linked_ptr<U> const& ptr) { copy(&ptr); }
linked_ptr(linked_ptr const& ptr) { copy(&ptr); }
// Assignment releases the old value and acquires the new.
template <typename U> linked_ptr& operator=(linked_ptr<U> const& ptr) {
depart();
copy(&ptr);
return *this;
}
linked_ptr& operator=(linked_ptr const& ptr) {
if (&ptr != this) {
depart();
copy(&ptr);
}
return *this;
}
// Smart pointer members.
void reset(T* ptr = NULL) { depart(); capture(ptr); }
T* get() const { return value_; }
T* operator->() const { return value_; }
T& operator*() const { return *value_; }
// Release ownership of the pointed object and returns it.
// Sole ownership by this linked_ptr object is required.
T* release() {
link_.depart();
T* v = value_;
value_ = NULL;
return v;
}
bool operator==(T* p) const { return value_ == p; }
bool operator!=(T* p) const { return value_ != p; }
template <typename U>
bool operator==(linked_ptr<U> const& ptr) const {
return value_ == ptr.get();
}
template <typename U>
bool operator!=(linked_ptr<U> const& ptr) const {
return value_ != ptr.get();
}
private:
template <typename U>
friend class linked_ptr;
T* value_;
linked_ptr_internal link_;
void depart() {
if (link_.depart()) delete value_;
}
void capture(T* ptr) {
value_ = ptr;
link_.join_new();
}
template <typename U> void copy(linked_ptr<U> const* ptr) {
value_ = ptr->get();
if (value_)
link_.join(&ptr->link_);
else
link_.join_new();
}
};
template<typename T> inline
bool operator==(T* ptr, const linked_ptr<T>& x) {
return ptr == x.get();
}
template<typename T> inline
bool operator!=(T* ptr, const linked_ptr<T>& x) {
return ptr != x.get();
}
// A function to convert T* into linked_ptr<T>
// Doing e.g. make_linked_ptr(new FooBarBaz<type>(arg)) is a shorter notation
// for linked_ptr<FooBarBaz<type> >(new FooBarBaz<type>(arg))
template <typename T>
linked_ptr<T> make_linked_ptr(T* ptr) {
return linked_ptr<T>(ptr);
}
} // namespace google_breakpad
#endif // PROCESSOR_LINKED_PTR_H__

View file

@ -0,0 +1,117 @@
// Copyright 2007 Google LLC
//
// 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 LLC 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.
// logging.cc: Breakpad logging
//
// See logging.h for documentation.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <string>
#include "common/stdio_wrapper.h"
#include "common/using_std_string.h"
#include "processor/logging.h"
#include "processor/pathname_stripper.h"
namespace google_breakpad {
LogStream::LogStream(std::ostream& stream, Severity severity,
const char* file, int line)
: stream_(stream) {
time_t clock;
time(&clock);
struct tm tm_struct;
#ifdef _WIN32
localtime_s(&tm_struct, &clock);
#else
localtime_r(&clock, &tm_struct);
#endif
char time_string[20];
strftime(time_string, sizeof(time_string), "%Y-%m-%d %H:%M:%S", &tm_struct);
const char* severity_string = "UNKNOWN_SEVERITY";
switch (severity) {
case SEVERITY_INFO:
severity_string = "INFO";
break;
case SEVERITY_ERROR:
severity_string = "ERROR";
break;
case SEVERITY_CRITICAL:
severity_string = "CRITICAL";
break;
}
stream_ << time_string << ": " << PathnameStripper::File(file) << ":" <<
line << ": " << severity_string << ": ";
}
LogStream::~LogStream() {
stream_ << std::endl;
}
string HexString(uint32_t number) {
char buffer[11];
snprintf(buffer, sizeof(buffer), "0x%x", number);
return string(buffer);
}
string HexString(uint64_t number) {
char buffer[19];
snprintf(buffer, sizeof(buffer), "0x%" PRIx64, number);
return string(buffer);
}
string HexString(int number) {
char buffer[19];
snprintf(buffer, sizeof(buffer), "0x%x", number);
return string(buffer);
}
int ErrnoString(string* error_string) {
assert(error_string);
// strerror isn't necessarily thread-safe. strerror_r would be preferrable,
// but GNU libc uses a nonstandard strerror_r by default, which returns a
// char* (rather than an int success indicator) and doesn't necessarily
// use the supplied buffer.
error_string->assign(strerror(errno));
return errno;
}
} // namespace google_breakpad

View file

@ -0,0 +1,187 @@
// Copyright 2007 Google LLC
//
// 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 LLC 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.
// logging.h: Breakpad logging
//
// Breakpad itself uses Breakpad logging with statements of the form:
// BPLOG(severity) << "message";
// severity may be INFO, ERROR, or other values defined in this file.
//
// BPLOG is an overridable macro so that users can customize Breakpad's
// logging. Left at the default, logging messages are sent to stderr along
// with a timestamp and the source code location that produced a message.
// The streams may be changed by redefining BPLOG_*_STREAM, the logging
// behavior may be changed by redefining BPLOG_*, and the entire logging
// system may be overridden by redefining BPLOG(severity). These
// redefinitions may be passed to the preprocessor as a command-line flag
// (-D).
//
// If an additional header is required to override Breakpad logging, it can
// be specified by the BP_LOGGING_INCLUDE macro. If defined, this header
// will #include the header specified by that macro.
//
// If any initialization is needed before logging, it can be performed by
// a function called through the BPLOG_INIT macro. Each main function of
// an executable program in the Breakpad processor library calls
// BPLOG_INIT(&argc, &argv); before any logging can be performed; define
// BPLOG_INIT appropriately if initialization is required.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_LOGGING_H__
#define PROCESSOR_LOGGING_H__
#include <iostream>
#include <string>
#include "common/using_std_string.h"
#include "google_breakpad/common/breakpad_types.h"
#ifdef BP_LOGGING_INCLUDE
#include BP_LOGGING_INCLUDE
#endif // BP_LOGGING_INCLUDE
namespace google_breakpad {
// These are defined in Microsoft headers.
#ifdef SEVERITY_ERROR
#undef SEVERITY_ERROR
#endif
#ifdef ERROR
#undef ERROR
#endif
class LogStream {
public:
enum Severity {
SEVERITY_INFO,
SEVERITY_ERROR,
SEVERITY_CRITICAL
};
// Begin logging a message to the stream identified by |stream|, at the
// indicated severity. The file and line parameters should be set so as to
// identify the line of source code that is producing a message.
LogStream(std::ostream& stream, Severity severity,
const char* file, int line);
// Finish logging by printing a newline and flushing the output stream.
~LogStream();
template<typename T> std::ostream& operator<<(const T& t) {
return stream_ << t;
}
private:
std::ostream& stream_;
// Disallow copy constructor and assignment operator
explicit LogStream(const LogStream& that);
void operator=(const LogStream& that);
};
// This class is used to explicitly ignore values in the conditional logging
// macros. This avoids compiler warnings like "value computed is not used"
// and "statement has no effect".
class LogMessageVoidify {
public:
LogMessageVoidify() {}
// This has to be an operator with a precedence lower than << but higher
// than ?:
void operator&(std::ostream&) {}
};
// Returns number formatted as a hexadecimal string, such as "0x7b".
string HexString(uint32_t number);
string HexString(uint64_t number);
string HexString(int number);
// Returns the error code as set in the global errno variable, and sets
// error_string, a required argument, to a string describing that error
// code.
int ErrnoString(string* error_string);
} // namespace google_breakpad
#ifndef BPLOG_INIT
#define BPLOG_INIT(pargc, pargv)
#endif // BPLOG_INIT
#ifndef BPLOG_LAZY_STREAM
#define BPLOG_LAZY_STREAM(stream, condition) \
!(condition) ? (void) 0 : \
google_breakpad::LogMessageVoidify() & (BPLOG_ ## stream)
#endif
#ifndef BPLOG_MINIMUM_SEVERITY
#define BPLOG_MINIMUM_SEVERITY SEVERITY_INFO
#endif
#define BPLOG_LOG_IS_ON(severity) \
((google_breakpad::LogStream::SEVERITY_ ## severity) >= \
(google_breakpad::LogStream::BPLOG_MINIMUM_SEVERITY))
#ifndef BPLOG
#define BPLOG(severity) BPLOG_LAZY_STREAM(severity, BPLOG_LOG_IS_ON(severity))
#endif // BPLOG
#ifndef BPLOG_INFO
#ifndef BPLOG_INFO_STREAM
#define BPLOG_INFO_STREAM std::clog
#endif // BPLOG_INFO_STREAM
#define BPLOG_INFO google_breakpad::LogStream(BPLOG_INFO_STREAM, \
google_breakpad::LogStream::SEVERITY_INFO, \
__FILE__, __LINE__)
#endif // BPLOG_INFO
#ifndef BPLOG_ERROR
#ifndef BPLOG_ERROR_STREAM
#define BPLOG_ERROR_STREAM std::cerr
#endif // BPLOG_ERROR_STREAM
#define BPLOG_ERROR google_breakpad::LogStream(BPLOG_ERROR_STREAM, \
google_breakpad::LogStream::SEVERITY_ERROR, \
__FILE__, __LINE__)
#endif // BPLOG_ERROR
#ifndef BPLOG_CRITICAL
#ifndef BPLOG_CRITICAL_STREAM
#define BPLOG_CRITICAL_STREAM std::cerr
#endif // BPLOG_CRITICAL_STREAM
#define BPLOG_CRITICAL google_breakpad::LogStream(BPLOG_CRITICAL_STREAM, \
google_breakpad::LogStream::SEVERITY_CRITICAL, \
__FILE__, __LINE__)
#endif // BPLOG_CRITICAL
#ifndef BPLOG_IF
#define BPLOG_IF(severity, condition) \
BPLOG_LAZY_STREAM(severity, ((condition) && BPLOG_LOG_IS_ON(severity)))
#endif // BPLOG_IF
#endif // PROCESSOR_LOGGING_H__

View file

@ -0,0 +1,265 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// map_serializers_inl.h: implementation for serializing std::map and its
// wrapper classes.
//
// See map_serializers.h for documentation.
//
// Author: Siyang Xie (lambxsy@google.com)
#ifndef PROCESSOR_MAP_SERIALIZERS_INL_H__
#define PROCESSOR_MAP_SERIALIZERS_INL_H__
#include <map>
#include <string>
#include "processor/map_serializers.h"
#include "processor/simple_serializer.h"
#include "processor/address_map-inl.h"
#include "processor/range_map-inl.h"
#include "processor/contained_range_map-inl.h"
#include "processor/logging.h"
namespace google_breakpad {
template<typename Key, typename Value>
size_t StdMapSerializer<Key, Value>::SizeOf(
const std::map<Key, Value>& m) const {
size_t size = 0;
size_t header_size = (1 + m.size()) * sizeof(uint32_t);
size += header_size;
typename std::map<Key, Value>::const_iterator iter;
for (iter = m.begin(); iter != m.end(); ++iter) {
size += key_serializer_.SizeOf(iter->first);
size += value_serializer_.SizeOf(iter->second);
}
return size;
}
template<typename Key, typename Value>
char* StdMapSerializer<Key, Value>::Write(const std::map<Key, Value>& m,
char* dest) const {
if (!dest) {
BPLOG(ERROR) << "StdMapSerializer failed: write to NULL address.";
return NULL;
}
char* start_address = dest;
// Write header:
// Number of nodes.
dest = SimpleSerializer<uint32_t>::Write(m.size(), dest);
// Nodes offsets.
uint32_t* offsets = reinterpret_cast<uint32_t*>(dest);
dest += sizeof(uint32_t) * m.size();
char* key_address = dest;
dest += sizeof(Key) * m.size();
// Traverse map.
typename std::map<Key, Value>::const_iterator iter;
int index = 0;
for (iter = m.begin(); iter != m.end(); ++iter, ++index) {
offsets[index] = static_cast<uint32_t>(dest - start_address);
key_address = key_serializer_.Write(iter->first, key_address);
dest = value_serializer_.Write(iter->second, dest);
}
return dest;
}
template<typename Key, typename Value>
char* StdMapSerializer<Key, Value>::Serialize(
const std::map<Key, Value>& m, unsigned int* size) const {
// Compute size of memory to be allocated.
unsigned int size_to_alloc = SizeOf(m);
// Allocate memory.
char* serialized_data = new char[size_to_alloc];
if (!serialized_data) {
BPLOG(INFO) << "StdMapSerializer memory allocation failed.";
if (size) *size = 0;
return NULL;
}
// Write serialized data into memory.
Write(m, serialized_data);
if (size) *size = size_to_alloc;
return serialized_data;
}
template<typename Address, typename Entry>
size_t RangeMapSerializer<Address, Entry>::SizeOf(
const RangeMap<Address, Entry>& m) const {
size_t size = 0;
size_t header_size = (1 + m.map_.size()) * sizeof(uint32_t);
size += header_size;
typename std::map<Address, Range>::const_iterator iter;
for (iter = m.map_.begin(); iter != m.map_.end(); ++iter) {
// Size of key (high address).
size += address_serializer_.SizeOf(iter->first);
// Size of base (low address).
size += address_serializer_.SizeOf(iter->second.base());
// Size of entry.
size += entry_serializer_.SizeOf(iter->second.entry());
}
return size;
}
template<typename Address, typename Entry>
char* RangeMapSerializer<Address, Entry>::Write(
const RangeMap<Address, Entry>& m, char* dest) const {
if (!dest) {
BPLOG(ERROR) << "RangeMapSerializer failed: write to NULL address.";
return NULL;
}
char* start_address = dest;
// Write header:
// Number of nodes.
dest = SimpleSerializer<uint32_t>::Write(m.map_.size(), dest);
// Nodes offsets.
uint32_t* offsets = reinterpret_cast<uint32_t*>(dest);
dest += sizeof(uint32_t) * m.map_.size();
char* key_address = dest;
dest += sizeof(Address) * m.map_.size();
// Traverse map.
typename std::map<Address, Range>::const_iterator iter;
int index = 0;
for (iter = m.map_.begin(); iter != m.map_.end(); ++iter, ++index) {
offsets[index] = static_cast<uint32_t>(dest - start_address);
key_address = address_serializer_.Write(iter->first, key_address);
dest = address_serializer_.Write(iter->second.base(), dest);
dest = entry_serializer_.Write(iter->second.entry(), dest);
}
return dest;
}
template<typename Address, typename Entry>
char* RangeMapSerializer<Address, Entry>::Serialize(
const RangeMap<Address, Entry>& m, unsigned int* size) const {
// Compute size of memory to be allocated.
unsigned int size_to_alloc = SizeOf(m);
// Allocate memory.
char* serialized_data = new char[size_to_alloc];
if (!serialized_data) {
BPLOG(INFO) << "RangeMapSerializer memory allocation failed.";
if (size) *size = 0;
return NULL;
}
// Write serialized data into memory.
Write(m, serialized_data);
if (size) *size = size_to_alloc;
return serialized_data;
}
template<class AddrType, class EntryType>
size_t ContainedRangeMapSerializer<AddrType, EntryType>::SizeOf(
const ContainedRangeMap<AddrType, EntryType>* m) const {
size_t size = 0;
size_t header_size = addr_serializer_.SizeOf(m->base_)
+ entry_serializer_.SizeOf(m->entry_)
+ sizeof(uint32_t);
size += header_size;
// In case m.map_ == NULL, we treat it as an empty map:
size += sizeof(uint32_t);
if (m->map_) {
size += m->map_->size() * sizeof(uint32_t);
typename Map::const_iterator iter;
for (iter = m->map_->begin(); iter != m->map_->end(); ++iter) {
size += addr_serializer_.SizeOf(iter->first);
// Recursive calculation of size:
size += SizeOf(iter->second);
}
}
return size;
}
template<class AddrType, class EntryType>
char* ContainedRangeMapSerializer<AddrType, EntryType>::Write(
const ContainedRangeMap<AddrType, EntryType>* m, char* dest) const {
if (!dest) {
BPLOG(ERROR) << "StdMapSerializer failed: write to NULL address.";
return NULL;
}
dest = addr_serializer_.Write(m->base_, dest);
dest = SimpleSerializer<uint32_t>::Write(entry_serializer_.SizeOf(m->entry_),
dest);
dest = entry_serializer_.Write(m->entry_, dest);
// Write map<<AddrType, ContainedRangeMap*>:
char* map_address = dest;
if (m->map_ == NULL) {
dest = SimpleSerializer<uint32_t>::Write(0, dest);
} else {
dest = SimpleSerializer<uint32_t>::Write(m->map_->size(), dest);
uint32_t* offsets = reinterpret_cast<uint32_t*>(dest);
dest += sizeof(uint32_t) * m->map_->size();
char* key_address = dest;
dest += sizeof(AddrType) * m->map_->size();
// Traverse map.
typename Map::const_iterator iter;
int index = 0;
for (iter = m->map_->begin(); iter != m->map_->end(); ++iter, ++index) {
offsets[index] = static_cast<uint32_t>(dest - map_address);
key_address = addr_serializer_.Write(iter->first, key_address);
// Recursively write.
dest = Write(iter->second, dest);
}
}
return dest;
}
template<class AddrType, class EntryType>
char* ContainedRangeMapSerializer<AddrType, EntryType>::Serialize(
const ContainedRangeMap<AddrType, EntryType>* m, unsigned int* size) const {
unsigned int size_to_alloc = SizeOf(m);
// Allocating memory.
char* serialized_data = new char[size_to_alloc];
if (!serialized_data) {
BPLOG(INFO) << "ContainedRangeMapSerializer memory allocation failed.";
if (size) *size = 0;
return NULL;
}
Write(m, serialized_data);
if (size) *size = size_to_alloc;
return serialized_data;
}
} // namespace google_breakpad
#endif // PROCESSOR_MAP_SERIALIZERS_INL_H__

View file

@ -0,0 +1,167 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// map_serializers.h: defines templates for serializing std::map and its
// wrappers: AddressMap, RangeMap, and ContainedRangeMap.
//
// Author: Siyang Xie (lambxsy@google.com)
#ifndef PROCESSOR_MAP_SERIALIZERS_H__
#define PROCESSOR_MAP_SERIALIZERS_H__
#include <map>
#include <string>
#include "processor/simple_serializer.h"
#include "processor/address_map-inl.h"
#include "processor/range_map-inl.h"
#include "processor/contained_range_map-inl.h"
namespace google_breakpad {
// StdMapSerializer allocates memory and serializes an std::map instance into a
// chunk of memory data.
template<typename Key, typename Value>
class StdMapSerializer {
public:
// Calculate the memory size of serialized data.
size_t SizeOf(const std::map<Key, Value>& m) const;
// Writes the serialized data to memory with start address = dest,
// and returns the "end" of data, i.e., return the address follow the final
// byte of data.
// NOTE: caller has to allocate enough memory before invoke Write() method.
char* Write(const std::map<Key, Value>& m, char* dest) const;
// Serializes a std::map object into a chunk of memory data with format
// described in "StaticMap.h" comment.
// Returns a pointer to the serialized data. If size != NULL, *size is set
// to the size of serialized data, i.e., SizeOf(m).
// Caller has the ownership of memory allocated as "new char[]".
char* Serialize(const std::map<Key, Value>& m, unsigned int* size) const;
private:
SimpleSerializer<Key> key_serializer_;
SimpleSerializer<Value> value_serializer_;
};
// AddressMapSerializer allocates memory and serializes an AddressMap into a
// chunk of memory data.
template<typename Addr, typename Entry>
class AddressMapSerializer {
public:
// Calculate the memory size of serialized data.
size_t SizeOf(const AddressMap<Addr, Entry>& m) const {
return std_map_serializer_.SizeOf(m.map_);
}
// Write the serialized data to specified memory location. Return the "end"
// of data, i.e., return the address after the final byte of data.
// NOTE: caller has to allocate enough memory before invoke Write() method.
char* Write(const AddressMap<Addr, Entry>& m, char* dest) const {
return std_map_serializer_.Write(m.map_, dest);
}
// Serializes an AddressMap object into a chunk of memory data.
// Returns a pointer to the serialized data. If size != NULL, *size is set
// to the size of serialized data, i.e., SizeOf(m).
// Caller has the ownership of memory allocated as "new char[]".
char* Serialize(const AddressMap<Addr, Entry>& m, unsigned int* size) const {
return std_map_serializer_.Serialize(m.map_, size);
}
private:
// AddressMapSerializer is a simple wrapper of StdMapSerializer, just as
// AddressMap is a simple wrapper of std::map.
StdMapSerializer<Addr, Entry> std_map_serializer_;
};
// RangeMapSerializer allocates memory and serializes a RangeMap instance into a
// chunk of memory data.
template<typename Address, typename Entry>
class RangeMapSerializer {
public:
// Calculate the memory size of serialized data.
size_t SizeOf(const RangeMap<Address, Entry>& m) const;
// Write the serialized data to specified memory location. Return the "end"
// of data, i.e., return the address after the final byte of data.
// NOTE: caller has to allocate enough memory before invoke Write() method.
char* Write(const RangeMap<Address, Entry>& m, char* dest) const;
// Serializes a RangeMap object into a chunk of memory data.
// Returns a pointer to the serialized data. If size != NULL, *size is set
// to the size of serialized data, i.e., SizeOf(m).
// Caller has the ownership of memory allocated as "new char[]".
char* Serialize(const RangeMap<Address, Entry>& m, unsigned int* size) const;
private:
// Convenient type name for Range.
typedef typename RangeMap<Address, Entry>::Range Range;
// Serializer for RangeMap's key and Range::base_.
SimpleSerializer<Address> address_serializer_;
// Serializer for RangeMap::Range::entry_.
SimpleSerializer<Entry> entry_serializer_;
};
// ContainedRangeMapSerializer allocates memory and serializes a
// ContainedRangeMap instance into a chunk of memory data.
template<class AddrType, class EntryType>
class ContainedRangeMapSerializer {
public:
// Calculate the memory size of serialized data.
size_t SizeOf(const ContainedRangeMap<AddrType, EntryType>* m) const;
// Write the serialized data to specified memory location. Return the "end"
// of data, i.e., return the address after the final byte of data.
// NOTE: caller has to allocate enough memory before invoke Write() method.
char* Write(const ContainedRangeMap<AddrType, EntryType>* m,
char* dest) const;
// Serializes a ContainedRangeMap object into a chunk of memory data.
// Returns a pointer to the serialized data. If size != NULL, *size is set
// to the size of serialized data, i.e., SizeOf(m).
// Caller has the ownership of memory allocated as "new char[]".
char* Serialize(const ContainedRangeMap<AddrType, EntryType>* m,
unsigned int* size) const;
private:
// Convenient type name for the underlying map type.
typedef std::map<AddrType, ContainedRangeMap<AddrType, EntryType>*> Map;
// Serializer for addresses and entries stored in ContainedRangeMap.
SimpleSerializer<AddrType> addr_serializer_;
SimpleSerializer<EntryType> entry_serializer_;
};
} // namespace google_breakpad
#endif // PROCESSOR_MAP_SERIALIZERS_H__

View file

@ -0,0 +1,389 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// map_serializers_unittest.cc: Unit tests for std::map serializer and
// std::map wrapper serializers.
//
// Author: Siyang Xie (lambxsy@google.com)
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <climits>
#include <map>
#include <string>
#include <utility>
#include <iostream>
#include <sstream>
#include "breakpad_googletest_includes.h"
#include "map_serializers-inl.h"
#include "processor/address_map-inl.h"
#include "processor/range_map-inl.h"
#include "processor/contained_range_map-inl.h"
typedef int32_t AddrType;
typedef int32_t EntryType;
class TestStdMapSerializer : public ::testing::Test {
protected:
void SetUp() {
serialized_size_ = 0;
serialized_data_ = NULL;
}
void TearDown() {
delete [] serialized_data_;
}
std::map<AddrType, EntryType> std_map_;
google_breakpad::StdMapSerializer<AddrType, EntryType> serializer_;
uint32_t serialized_size_;
char* serialized_data_;
};
TEST_F(TestStdMapSerializer, EmptyMapTestCase) {
const int32_t correct_data[] = { 0 };
uint32_t correct_size = sizeof(correct_data);
// std_map_ is empty.
serialized_data_ = serializer_.Serialize(std_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
TEST_F(TestStdMapSerializer, MapWithTwoElementsTestCase) {
const int32_t correct_data[] = {
// # of nodes
2,
// Offsets
20, 24,
// Keys
1, 3,
// Values
2, 6
};
uint32_t correct_size = sizeof(correct_data);
std_map_.insert(std::make_pair(1, 2));
std_map_.insert(std::make_pair(3, 6));
serialized_data_ = serializer_.Serialize(std_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
TEST_F(TestStdMapSerializer, MapWithFiveElementsTestCase) {
const int32_t correct_data[] = {
// # of nodes
5,
// Offsets
44, 48, 52, 56, 60,
// Keys
1, 2, 3, 4, 5,
// Values
11, 12, 13, 14, 15
};
uint32_t correct_size = sizeof(correct_data);
for (int i = 1; i < 6; ++i)
std_map_.insert(std::make_pair(i, 10 + i));
serialized_data_ = serializer_.Serialize(std_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
class TestAddressMapSerializer : public ::testing::Test {
protected:
void SetUp() {
serialized_size_ = 0;
serialized_data_ = 0;
}
void TearDown() {
delete [] serialized_data_;
}
google_breakpad::AddressMap<AddrType, EntryType> address_map_;
google_breakpad::AddressMapSerializer<AddrType, EntryType> serializer_;
uint32_t serialized_size_;
char* serialized_data_;
};
TEST_F(TestAddressMapSerializer, EmptyMapTestCase) {
const int32_t correct_data[] = { 0 };
uint32_t correct_size = sizeof(correct_data);
// std_map_ is empty.
serialized_data_ = serializer_.Serialize(address_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
TEST_F(TestAddressMapSerializer, MapWithTwoElementsTestCase) {
const int32_t correct_data[] = {
// # of nodes
2,
// Offsets
20, 24,
// Keys
1, 3,
// Values
2, 6
};
uint32_t correct_size = sizeof(correct_data);
address_map_.Store(1, 2);
address_map_.Store(3, 6);
serialized_data_ = serializer_.Serialize(address_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
TEST_F(TestAddressMapSerializer, MapWithFourElementsTestCase) {
const int32_t correct_data[] = {
// # of nodes
4,
// Offsets
36, 40, 44, 48,
// Keys
-6, -4, 8, 123,
// Values
2, 3, 5, 8
};
uint32_t correct_size = sizeof(correct_data);
address_map_.Store(-6, 2);
address_map_.Store(-4, 3);
address_map_.Store(8, 5);
address_map_.Store(123, 8);
serialized_data_ = serializer_.Serialize(address_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
class TestRangeMapSerializer : public ::testing::Test {
protected:
void SetUp() {
serialized_size_ = 0;
serialized_data_ = 0;
}
void TearDown() {
delete [] serialized_data_;
}
google_breakpad::RangeMap<AddrType, EntryType> range_map_;
google_breakpad::RangeMapSerializer<AddrType, EntryType> serializer_;
uint32_t serialized_size_;
char* serialized_data_;
};
TEST_F(TestRangeMapSerializer, EmptyMapTestCase) {
const int32_t correct_data[] = { 0 };
uint32_t correct_size = sizeof(correct_data);
// range_map_ is empty.
serialized_data_ = serializer_.Serialize(range_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
TEST_F(TestRangeMapSerializer, MapWithOneRangeTestCase) {
const int32_t correct_data[] = {
// # of nodes
1,
// Offsets
12,
// Keys: high address
10,
// Values: (low address, entry) pairs
1, 6
};
uint32_t correct_size = sizeof(correct_data);
range_map_.StoreRange(1, 10, 6);
serialized_data_ = serializer_.Serialize(range_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
TEST_F(TestRangeMapSerializer, MapWithThreeRangesTestCase) {
const int32_t correct_data[] = {
// # of nodes
3,
// Offsets
28, 36, 44,
// Keys: high address
5, 9, 20,
// Values: (low address, entry) pairs
2, 1, 6, 2, 10, 3
};
uint32_t correct_size = sizeof(correct_data);
ASSERT_TRUE(range_map_.StoreRange(2, 4, 1));
ASSERT_TRUE(range_map_.StoreRange(6, 4, 2));
ASSERT_TRUE(range_map_.StoreRange(10, 11, 3));
serialized_data_ = serializer_.Serialize(range_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
class TestContainedRangeMapSerializer : public ::testing::Test {
protected:
void SetUp() {
serialized_size_ = 0;
serialized_data_ = 0;
}
void TearDown() {
delete [] serialized_data_;
}
google_breakpad::ContainedRangeMap<AddrType, EntryType> crm_map_;
google_breakpad::ContainedRangeMapSerializer<AddrType, EntryType> serializer_;
uint32_t serialized_size_;
char* serialized_data_;
};
TEST_F(TestContainedRangeMapSerializer, EmptyMapTestCase) {
const int32_t correct_data[] = {
0, // base address of root
4, // size of entry
0, // entry stored at root
0 // empty map stored at root
};
uint32_t correct_size = sizeof(correct_data);
// crm_map_ is empty.
serialized_data_ = serializer_.Serialize(&crm_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
TEST_F(TestContainedRangeMapSerializer, MapWithOneRangeTestCase) {
const int32_t correct_data[] = {
0, // base address of root
4, // size of entry
0, // entry stored at root
// Map stored at root node:
1, // # of nodes
12, // offset
9, // key
// value: a child ContainedRangeMap
3, // base address of child CRM
4, // size of entry
-1, // entry stored in child CRM
0 // empty sub-map stored in child CRM
};
uint32_t correct_size = sizeof(correct_data);
crm_map_.StoreRange(3, 7, -1);
serialized_data_ = serializer_.Serialize(&crm_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
TEST_F(TestContainedRangeMapSerializer, MapWithTwoLevelsTestCase) {
// Tree structure of ranges:
// root level 0
// |
// map
// / \ level 1: child1, child2
// 2~8 10~20
// | |
// map map
// / \ |
// 3~4 6~7 16-20 level 2: grandchild1, grandchild2, grandchild3
const int32_t correct_data[] = {
// root: base, entry_size, entry
0, 4, 0,
// root's map: # of nodes, offset1, offset2, key1, key2
2, 20, 84, 8, 20,
// child1: base, entry_size, entry:
2, 4, -1,
// child1's map: # of nodes, offset1, offset2, key1, key2
2, 20, 36, 4, 7,
// grandchild1: base, entry_size, entry, empty_map
3, 4, -1, 0,
// grandchild2: base, entry_size, entry, empty_map
6, 4, -1, 0,
// child2: base, entry_size, entry:
10, 4, -1,
// child2's map: # of nodes, offset1, key1
1, 12, 20,
// grandchild3: base, entry_size, entry, empty_map
16, 4, -1, 0
};
uint32_t correct_size = sizeof(correct_data);
// Store child1.
ASSERT_TRUE(crm_map_.StoreRange(2, 7, -1));
// Store child2.
ASSERT_TRUE(crm_map_.StoreRange(10, 11, -1));
// Store grandchild1.
ASSERT_TRUE(crm_map_.StoreRange(3, 2, -1));
// Store grandchild2.
ASSERT_TRUE(crm_map_.StoreRange(6, 2, -1));
// Store grandchild3.
ASSERT_TRUE(crm_map_.StoreRange(16, 5, -1));
serialized_data_ = serializer_.Serialize(&crm_map_, &serialized_size_);
EXPECT_EQ(correct_size, serialized_size_);
EXPECT_EQ(memcmp(correct_data, serialized_data_, correct_size), 0);
}
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View file

@ -0,0 +1,408 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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.
// microdump.cc: A microdump reader.
//
// See microdump.h for documentation.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/microdump.h"
#include <stdio.h>
#include <string.h>
#include <memory>
#include <sstream>
#include <string>
#include <vector>
#include "google_breakpad/common/minidump_cpu_arm.h"
#include "google_breakpad/processor/code_module.h"
#include "processor/basic_code_module.h"
#include "processor/convert_old_arm64_context.h"
#include "processor/linked_ptr.h"
#include "processor/logging.h"
#include "processor/range_map-inl.h"
namespace {
static const char kGoogleBreakpadKey[] = "google-breakpad";
static const char kMicrodumpBegin[] = "-----BEGIN BREAKPAD MICRODUMP-----";
static const char kMicrodumpEnd[] = "-----END BREAKPAD MICRODUMP-----";
static const char kOsKey[] = ": O ";
static const char kCpuKey[] = ": C ";
static const char kCrashReasonKey[] = ": R ";
static const char kGpuKey[] = ": G ";
static const char kMmapKey[] = ": M ";
static const char kStackKey[] = ": S ";
static const char kStackFirstLineKey[] = ": S 0 ";
static const char kArmArchitecture[] = "arm";
static const char kArm64Architecture[] = "arm64";
static const char kX86Architecture[] = "x86";
static const char kMipsArchitecture[] = "mips";
static const char kMips64Architecture[] = "mips64";
static const char kGpuUnknown[] = "UNKNOWN";
template<typename T>
T HexStrToL(const string& str) {
uint64_t res = 0;
std::istringstream ss(str);
ss >> std::hex >> res;
return static_cast<T>(res);
}
std::vector<uint8_t> ParseHexBuf(const string& str) {
std::vector<uint8_t> buf;
for (size_t i = 0; i < str.length(); i += 2) {
buf.push_back(HexStrToL<uint8_t>(str.substr(i, 2)));
}
return buf;
}
bool GetLine(std::istringstream* istream, string* str) {
if (std::getline(*istream, *str)) {
// Trim any trailing newline from the end of the line. Allows us
// to seamlessly handle both Windows/DOS and Unix formatted input. The
// adb tool generally writes logcat dumps in Windows/DOS format.
if (!str->empty() && str->at(str->size() - 1) == '\r') {
str->erase(str->size() - 1);
}
return true;
}
return false;
}
} // namespace
namespace google_breakpad {
//
// MicrodumpModules
//
void MicrodumpModules::Add(const CodeModule* module) {
linked_ptr<const CodeModule> module_ptr(module);
if (!map_.StoreRange(module->base_address(), module->size(), module_ptr)) {
BPLOG(ERROR) << "Module " << module->code_file() <<
" could not be stored";
}
}
void MicrodumpModules::SetEnableModuleShrink(bool is_enabled) {
map_.SetMergeStrategy(is_enabled ? MergeRangeStrategy::kTruncateUpper
: MergeRangeStrategy::kExclusiveRanges);
}
//
// MicrodumpContext
//
void MicrodumpContext::SetContextARM(MDRawContextARM* arm) {
DumpContext::SetContextFlags(MD_CONTEXT_ARM);
DumpContext::SetContextARM(arm);
valid_ = true;
}
void MicrodumpContext::SetContextARM64(MDRawContextARM64* arm64) {
DumpContext::SetContextFlags(MD_CONTEXT_ARM64);
DumpContext::SetContextARM64(arm64);
valid_ = true;
}
void MicrodumpContext::SetContextX86(MDRawContextX86* x86) {
DumpContext::SetContextFlags(MD_CONTEXT_X86);
DumpContext::SetContextX86(x86);
valid_ = true;
}
void MicrodumpContext::SetContextMIPS(MDRawContextMIPS* mips32) {
DumpContext::SetContextFlags(MD_CONTEXT_MIPS);
DumpContext::SetContextMIPS(mips32);
valid_ = true;
}
void MicrodumpContext::SetContextMIPS64(MDRawContextMIPS* mips64) {
DumpContext::SetContextFlags(MD_CONTEXT_MIPS64);
DumpContext::SetContextMIPS(mips64);
valid_ = true;
}
//
// MicrodumpMemoryRegion
//
MicrodumpMemoryRegion::MicrodumpMemoryRegion() : base_address_(0) { }
void MicrodumpMemoryRegion::Init(uint64_t base_address,
const std::vector<uint8_t>& contents) {
base_address_ = base_address;
contents_ = contents;
}
uint64_t MicrodumpMemoryRegion::GetBase() const { return base_address_; }
uint32_t MicrodumpMemoryRegion::GetSize() const { return contents_.size(); }
bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address,
uint8_t* value) const {
return GetMemoryLittleEndian(address, value);
}
bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address,
uint16_t* value) const {
return GetMemoryLittleEndian(address, value);
}
bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address,
uint32_t* value) const {
return GetMemoryLittleEndian(address, value);
}
bool MicrodumpMemoryRegion::GetMemoryAtAddress(uint64_t address,
uint64_t* value) const {
return GetMemoryLittleEndian(address, value);
}
template<typename ValueType>
bool MicrodumpMemoryRegion::GetMemoryLittleEndian(uint64_t address,
ValueType* value) const {
if (address < base_address_ ||
address - base_address_ + sizeof(ValueType) > contents_.size())
return false;
ValueType v = 0;
uint64_t start = address - base_address_;
// The loop condition is odd, but it's correct for size_t.
for (size_t i = sizeof(ValueType) - 1; i < sizeof(ValueType); i--)
v = (v << 8) | static_cast<uint8_t>(contents_[start + i]);
*value = v;
return true;
}
void MicrodumpMemoryRegion::Print() const {
// Not reached, just needed to honor the base class contract.
assert(false);
}
//
// Microdump
//
Microdump::Microdump(const string& contents)
: context_(new MicrodumpContext()),
stack_region_(new MicrodumpMemoryRegion()),
modules_(new MicrodumpModules()),
system_info_(new SystemInfo()),
crash_reason_(),
crash_address_(0u) {
assert(!contents.empty());
bool in_microdump = false;
string line;
uint64_t stack_start = 0;
std::vector<uint8_t> stack_content;
string arch;
std::istringstream stream(contents);
while (GetLine(&stream, &line)) {
if (line.find(kGoogleBreakpadKey) == string::npos) {
continue;
}
if (line.find(kMicrodumpBegin) != string::npos) {
in_microdump = true;
continue;
}
if (!in_microdump) {
continue;
}
if (line.find(kMicrodumpEnd) != string::npos) {
break;
}
size_t pos;
if ((pos = line.find(kOsKey)) != string::npos) {
string os_str(line, pos + strlen(kOsKey));
std::istringstream os_tokens(os_str);
string os_id;
string num_cpus;
string os_version;
// This reflect the actual HW arch and might not match the arch emulated
// for the execution (e.g., running a 32-bit binary on a 64-bit cpu).
string hw_arch;
os_tokens >> os_id;
os_tokens >> arch;
os_tokens >> num_cpus;
os_tokens >> hw_arch;
GetLine(&os_tokens, &os_version);
os_version.erase(0, 1); // remove leading space.
system_info_->cpu = arch;
system_info_->cpu_count = HexStrToL<uint8_t>(num_cpus);
system_info_->os_version = os_version;
if (os_id == "L") {
system_info_->os = "Linux";
system_info_->os_short = "linux";
} else if (os_id == "A") {
system_info_->os = "Android";
system_info_->os_short = "android";
modules_->SetEnableModuleShrink(true);
}
// OS line also contains release and version for future use.
} else if ((pos = line.find(kStackKey)) != string::npos) {
if (line.find(kStackFirstLineKey) != string::npos) {
// The first line of the stack (S 0 stack header) provides the value of
// the stack pointer, the start address of the stack being dumped and
// the length of the stack. We could use it in future to double check
// that we received all the stack as expected.
continue;
}
string stack_str(line, pos + strlen(kStackKey));
std::istringstream stack_tokens(stack_str);
string start_addr_str;
string raw_content;
stack_tokens >> start_addr_str;
stack_tokens >> raw_content;
uint64_t start_addr = HexStrToL<uint64_t>(start_addr_str);
if (stack_start != 0) {
// Verify that the stack chunks in the microdump are contiguous.
assert(start_addr == stack_start + stack_content.size());
} else {
stack_start = start_addr;
}
std::vector<uint8_t> chunk = ParseHexBuf(raw_content);
stack_content.insert(stack_content.end(), chunk.begin(), chunk.end());
} else if ((pos = line.find(kCpuKey)) != string::npos) {
string cpu_state_str(line, pos + strlen(kCpuKey));
std::vector<uint8_t> cpu_state_raw = ParseHexBuf(cpu_state_str);
if (strcmp(arch.c_str(), kArmArchitecture) == 0) {
if (cpu_state_raw.size() != sizeof(MDRawContextARM)) {
std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size()
<< " bytes instead of " << sizeof(MDRawContextARM)
<< std::endl;
continue;
}
MDRawContextARM* arm = new MDRawContextARM();
memcpy(arm, &cpu_state_raw[0], cpu_state_raw.size());
context_->SetContextARM(arm);
} else if (strcmp(arch.c_str(), kArm64Architecture) == 0) {
if (cpu_state_raw.size() == sizeof(MDRawContextARM64)) {
MDRawContextARM64* arm = new MDRawContextARM64();
memcpy(arm, &cpu_state_raw[0], cpu_state_raw.size());
context_->SetContextARM64(arm);
} else if (cpu_state_raw.size() == sizeof(MDRawContextARM64_Old)) {
MDRawContextARM64_Old old_arm;
memcpy(&old_arm, &cpu_state_raw[0], cpu_state_raw.size());
MDRawContextARM64* new_arm = new MDRawContextARM64();
ConvertOldARM64Context(old_arm, new_arm);
context_->SetContextARM64(new_arm);
} else {
std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size()
<< " bytes instead of " << sizeof(MDRawContextARM64)
<< std::endl;
continue;
}
} else if (strcmp(arch.c_str(), kX86Architecture) == 0) {
if (cpu_state_raw.size() != sizeof(MDRawContextX86)) {
std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size()
<< " bytes instead of " << sizeof(MDRawContextX86)
<< std::endl;
continue;
}
MDRawContextX86* x86 = new MDRawContextX86();
memcpy(x86, &cpu_state_raw[0], cpu_state_raw.size());
context_->SetContextX86(x86);
} else if (strcmp(arch.c_str(), kMipsArchitecture) == 0) {
if (cpu_state_raw.size() != sizeof(MDRawContextMIPS)) {
std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size()
<< " bytes instead of " << sizeof(MDRawContextMIPS)
<< std::endl;
continue;
}
MDRawContextMIPS* mips32 = new MDRawContextMIPS();
memcpy(mips32, &cpu_state_raw[0], cpu_state_raw.size());
context_->SetContextMIPS(mips32);
} else if (strcmp(arch.c_str(), kMips64Architecture) == 0) {
if (cpu_state_raw.size() != sizeof(MDRawContextMIPS)) {
std::cerr << "Malformed CPU context. Got " << cpu_state_raw.size()
<< " bytes instead of " << sizeof(MDRawContextMIPS)
<< std::endl;
continue;
}
MDRawContextMIPS* mips64 = new MDRawContextMIPS();
memcpy(mips64, &cpu_state_raw[0], cpu_state_raw.size());
context_->SetContextMIPS64(mips64);
} else {
std::cerr << "Unsupported architecture: " << arch << std::endl;
}
} else if ((pos = line.find(kCrashReasonKey)) != string::npos) {
string crash_reason_str(line, pos + strlen(kCrashReasonKey));
std::istringstream crash_reason_tokens(crash_reason_str);
string signal;
string address;
crash_reason_tokens >> signal;
crash_reason_tokens >> crash_reason_;
crash_reason_tokens >> address;
crash_address_ = HexStrToL<uint64_t>(address);
} else if ((pos = line.find(kGpuKey)) != string::npos) {
string gpu_str(line, pos + strlen(kGpuKey));
if (strcmp(gpu_str.c_str(), kGpuUnknown) != 0) {
std::istringstream gpu_tokens(gpu_str);
std::getline(gpu_tokens, system_info_->gl_version, '|');
std::getline(gpu_tokens, system_info_->gl_vendor, '|');
std::getline(gpu_tokens, system_info_->gl_renderer, '|');
}
} else if ((pos = line.find(kMmapKey)) != string::npos) {
string mmap_line(line, pos + strlen(kMmapKey));
std::istringstream mmap_tokens(mmap_line);
string addr, offset, size, identifier, filename;
mmap_tokens >> addr;
mmap_tokens >> offset;
mmap_tokens >> size;
mmap_tokens >> identifier;
mmap_tokens >> filename;
modules_->Add(new BasicCodeModule(
HexStrToL<uint64_t>(addr), // base_address
HexStrToL<uint64_t>(size), // size
filename, // code_file
identifier, // code_identifier
filename, // debug_file
identifier, // debug_identifier
"")); // version
}
}
stack_region_->Init(stack_start, stack_content);
}
} // namespace google_breakpad

View file

@ -0,0 +1,100 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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.
// microdump_processor.cc: A microdump processor.
//
// See microdump_processor.h for documentation.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/microdump_processor.h"
#include <assert.h>
#include <string>
#include "common/using_std_string.h"
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/microdump.h"
#include "google_breakpad/processor/process_state.h"
#include "google_breakpad/processor/stackwalker.h"
#include "google_breakpad/processor/stack_frame_symbolizer.h"
#include "processor/logging.h"
namespace google_breakpad {
MicrodumpProcessor::MicrodumpProcessor(StackFrameSymbolizer* frame_symbolizer)
: frame_symbolizer_(frame_symbolizer) {
assert(frame_symbolizer);
}
MicrodumpProcessor::~MicrodumpProcessor() {}
ProcessResult MicrodumpProcessor::Process(Microdump *microdump,
ProcessState* process_state) {
assert(process_state);
process_state->Clear();
process_state->modules_ = microdump->GetModules()->Copy();
scoped_ptr<Stackwalker> stackwalker(
Stackwalker::StackwalkerForCPU(
&process_state->system_info_,
microdump->GetContext(),
microdump->GetMemory(),
process_state->modules_,
/* unloaded_modules= */ NULL,
frame_symbolizer_));
scoped_ptr<CallStack> stack(new CallStack());
if (stackwalker.get()) {
if (!stackwalker->Walk(stack.get(),
&process_state->modules_without_symbols_,
&process_state->modules_with_corrupt_symbols_)) {
BPLOG(INFO) << "Processing was interrupted.";
return PROCESS_SYMBOL_SUPPLIER_INTERRUPTED;
}
} else {
BPLOG(ERROR) << "No stackwalker found for microdump.";
return PROCESS_ERROR_NO_THREAD_LIST;
}
process_state->threads_.push_back(stack.release());
process_state->thread_memory_regions_.push_back(microdump->GetMemory());
process_state->crashed_ = true;
process_state->requesting_thread_ = 0;
process_state->system_info_ = *microdump->GetSystemInfo();
process_state->crash_reason_ = microdump->GetCrashReason();
process_state->crash_address_ = microdump->GetCrashAddress();
return PROCESS_OK;
}
} // namespace google_breakpad

View file

@ -0,0 +1,288 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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 test for MicrodumpProcessor.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#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/microdump.h"
#include "google_breakpad/processor/microdump_processor.h"
#include "google_breakpad/processor/process_state.h"
#include "google_breakpad/processor/stack_frame.h"
#include "google_breakpad/processor/stack_frame_symbolizer.h"
#include "processor/simple_symbol_supplier.h"
#include "processor/stackwalker_unittest_utils.h"
namespace {
using google_breakpad::BasicSourceLineResolver;
using google_breakpad::Microdump;
using google_breakpad::MicrodumpProcessor;
using google_breakpad::ProcessState;
using google_breakpad::SimpleSymbolSupplier;
using google_breakpad::StackFrameSymbolizer;
class MicrodumpProcessorTest : public ::testing::Test {
public:
MicrodumpProcessorTest()
: files_path_(string(getenv("srcdir") ? getenv("srcdir") : ".") +
"/src/processor/testdata/") {
}
void ReadFile(const string& file_name, string* file_contents) {
assert(file_contents);
std::ifstream file_stream(file_name.c_str(), std::ios::in);
ASSERT_TRUE(file_stream.good());
std::vector<char> bytes;
file_stream.seekg(0, std::ios_base::end);
ASSERT_TRUE(file_stream.good());
bytes.resize(file_stream.tellg());
file_stream.seekg(0, std::ios_base::beg);
ASSERT_TRUE(file_stream.good());
file_stream.read(&bytes[0], bytes.size());
ASSERT_TRUE(file_stream.good());
*file_contents = string(&bytes[0], bytes.size());
}
google_breakpad::ProcessResult ProcessMicrodump(
const string& symbols_file,
const string& microdump_contents,
ProcessState* state) {
SimpleSymbolSupplier supplier(symbols_file);
BasicSourceLineResolver resolver;
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
MicrodumpProcessor processor(&frame_symbolizer);
Microdump microdump(microdump_contents);
return processor.Process(&microdump, state);
}
void AnalyzeDump(const string& microdump_file_name, bool omit_symbols,
int expected_cpu_count, ProcessState* state) {
string symbols_file = omit_symbols ? "" : files_path_ + "symbols/microdump";
string microdump_file_path = files_path_ + microdump_file_name;
string microdump_contents;
ReadFile(microdump_file_path, &microdump_contents);
google_breakpad::ProcessResult result =
ProcessMicrodump(symbols_file, microdump_contents, state);
ASSERT_EQ(google_breakpad::PROCESS_OK, result);
ASSERT_TRUE(state->crashed());
ASSERT_EQ(0, state->requesting_thread());
ASSERT_EQ(1U, state->threads()->size());
ASSERT_EQ(expected_cpu_count, state->system_info()->cpu_count);
ASSERT_EQ("android", state->system_info()->os_short);
ASSERT_EQ("Android", state->system_info()->os);
}
string files_path_;
};
TEST_F(MicrodumpProcessorTest, TestProcess_Invalid) {
ProcessState state;
google_breakpad::ProcessResult result =
ProcessMicrodump("", "This is not a valid microdump", &state);
ASSERT_EQ(google_breakpad::PROCESS_ERROR_NO_THREAD_LIST, result);
}
TEST_F(MicrodumpProcessorTest, TestProcess_WithoutCrashReason) {
ProcessState state;
AnalyzeDump("microdump-arm64.dmp", true /* omit_symbols */,
2 /* expected_cpu_count */, &state);
ASSERT_EQ(state.crash_reason(), "");
ASSERT_EQ(state.crash_address(), 0x0u);
}
TEST_F(MicrodumpProcessorTest, TestProcess_WithCrashReason) {
ProcessState state;
AnalyzeDump("microdump-withcrashreason.dmp", true /* omit_symbols */,
8 /* expected_cpu_count */, &state);
ASSERT_EQ(state.crash_reason(), "SIGTRAP");
ASSERT_EQ(state.crash_address(), 0x4A7CB000u);
}
TEST_F(MicrodumpProcessorTest, TestProcess_MissingSymbols) {
ProcessState state;
AnalyzeDump("microdump-arm64.dmp", true /* omit_symbols */,
2 /* expected_cpu_count */, &state);
ASSERT_EQ(8U, state.modules()->module_count());
ASSERT_EQ("arm64", state.system_info()->cpu);
ASSERT_EQ("OS 64 VERSION INFO", state.system_info()->os_version);
ASSERT_EQ(1U, state.threads()->size());
ASSERT_EQ(11U, state.threads()->at(0)->frames()->size());
ASSERT_EQ("",
state.threads()->at(0)->frames()->at(0)->function_name);
ASSERT_EQ("",
state.threads()->at(0)->frames()->at(3)->function_name);
}
TEST_F(MicrodumpProcessorTest, TestProcess_UnsupportedArch) {
string microdump_contents =
"W/google-breakpad(26491): -----BEGIN BREAKPAD MICRODUMP-----\n"
"W/google-breakpad(26491): O A \"unsupported-arch\"\n"
"W/google-breakpad(26491): S 0 A48BD840 A48BD000 00002000\n";
ProcessState state;
google_breakpad::ProcessResult result =
ProcessMicrodump("", microdump_contents, &state);
ASSERT_EQ(google_breakpad::PROCESS_ERROR_NO_THREAD_LIST, result);
}
TEST_F(MicrodumpProcessorTest, TestProcessArm) {
ProcessState state;
AnalyzeDump("microdump-arm.dmp", false /* omit_symbols */,
2 /* expected_cpu_count*/, &state);
ASSERT_EQ(6U, state.modules()->module_count());
ASSERT_EQ("arm", state.system_info()->cpu);
ASSERT_EQ("OpenGL ES 3.0 V@104.0 AU@ (GIT@Id3510ff6dc)",
state.system_info()->gl_version);
ASSERT_EQ("Qualcomm", state.system_info()->gl_vendor);
ASSERT_EQ("Adreno (TM) 330", state.system_info()->gl_renderer);
ASSERT_EQ("OS VERSION INFO", state.system_info()->os_version);
ASSERT_EQ(8U, state.threads()->at(0)->frames()->size());
ASSERT_EQ("MicrodumpWriterTest_Setup_Test::TestBody",
state.threads()->at(0)->frames()->at(0)->function_name);
ASSERT_EQ("testing::Test::Run",
state.threads()->at(0)->frames()->at(1)->function_name);
ASSERT_EQ("main",
state.threads()->at(0)->frames()->at(6)->function_name);
ASSERT_EQ("breakpad_unittests",
state.threads()->at(0)->frames()->at(6)->module->code_file());
}
TEST_F(MicrodumpProcessorTest, TestProcessArm64) {
ProcessState state;
AnalyzeDump("microdump-arm64.dmp", false /* omit_symbols */,
2 /* expected_cpu_count*/, &state);
ASSERT_EQ(8U, state.modules()->module_count());
ASSERT_EQ("arm64", state.system_info()->cpu);
ASSERT_EQ("OS 64 VERSION INFO", state.system_info()->os_version);
ASSERT_EQ(9U, state.threads()->at(0)->frames()->size());
ASSERT_EQ("MicrodumpWriterTest_Setup_Test::TestBody",
state.threads()->at(0)->frames()->at(0)->function_name);
ASSERT_EQ("testing::Test::Run",
state.threads()->at(0)->frames()->at(2)->function_name);
ASSERT_EQ("main",
state.threads()->at(0)->frames()->at(7)->function_name);
ASSERT_EQ("breakpad_unittests",
state.threads()->at(0)->frames()->at(7)->module->code_file());
}
TEST_F(MicrodumpProcessorTest, TestProcessX86) {
ProcessState state;
AnalyzeDump("microdump-x86.dmp", false /* omit_symbols */,
4 /* expected_cpu_count */, &state);
ASSERT_EQ(124U, state.modules()->module_count());
ASSERT_EQ("x86", state.system_info()->cpu);
ASSERT_EQ("asus/WW_Z00A/Z00A:5.0/LRX21V/2.19.40.22_20150627_5104_user:user/"
"release-keys", state.system_info()->os_version);
ASSERT_EQ(17U, state.threads()->at(0)->frames()->size());
ASSERT_EQ("libc.so",
state.threads()->at(0)->frames()->at(0)->module->debug_file());
// TODO(mmandlis): Get symbols for the test X86 microdump and test function
// names.
}
TEST_F(MicrodumpProcessorTest, TestProcessMultiple) {
ProcessState state;
AnalyzeDump("microdump-multiple.dmp", false /* omit_symbols */,
6 /* expected_cpu_count */, &state);
ASSERT_EQ(156U, state.modules()->module_count());
ASSERT_EQ("arm", state.system_info()->cpu);
ASSERT_EQ("lge/p1_tmo_us/p1:6.0/MRA58K/1603210524c8d:user/release-keys",
state.system_info()->os_version);
ASSERT_EQ(5U, state.threads()->at(0)->frames()->size());
}
TEST_F(MicrodumpProcessorTest, TestProcessMips) {
ProcessState state;
AnalyzeDump("microdump-mips32.dmp", false /* omit_symbols */,
2 /* expected_cpu_count */, &state);
ASSERT_EQ(7U, state.modules()->module_count());
ASSERT_EQ("mips", state.system_info()->cpu);
ASSERT_EQ("3.0.8-g893bf16 #7 SMP PREEMPT Fri Jul 10 15:20:59 PDT 2015",
state.system_info()->os_version);
ASSERT_EQ(4U, state.threads()->at(0)->frames()->size());
ASSERT_EQ("blaTest",
state.threads()->at(0)->frames()->at(0)->function_name);
ASSERT_EQ("Crash",
state.threads()->at(0)->frames()->at(1)->function_name);
ASSERT_EQ("main",
state.threads()->at(0)->frames()->at(2)->function_name);
ASSERT_EQ("crash_example",
state.threads()->at(0)->frames()->at(0)->module->debug_file());
}
TEST_F(MicrodumpProcessorTest, TestProcessMips64) {
ProcessState state;
AnalyzeDump("microdump-mips64.dmp", false /* omit_symbols */,
1 /* expected_cpu_count */, &state);
ASSERT_EQ(8U, state.modules()->module_count());
ASSERT_EQ("mips64", state.system_info()->cpu);
ASSERT_EQ("3.10.0-gf185e20 #112 PREEMPT Mon Oct 5 11:12:49 PDT 2015",
state.system_info()->os_version);
ASSERT_EQ(4U, state.threads()->at(0)->frames()->size());
ASSERT_EQ("blaTest",
state.threads()->at(0)->frames()->at(0)->function_name);
ASSERT_EQ("Crash",
state.threads()->at(0)->frames()->at(1)->function_name);
ASSERT_EQ("main",
state.threads()->at(0)->frames()->at(2)->function_name);
ASSERT_EQ("crash_example",
state.threads()->at(0)->frames()->at(0)->module->debug_file());
}
} // namespace
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View file

@ -0,0 +1,187 @@
// Copyright 2014 Google LLC
//
// 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 LLC 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.
// microdump_stackwalk.cc: Process a microdump with MicrodumpProcessor, printing
// the results, including stack traces.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fstream>
#include <string>
#include <vector>
#include "common/path_helper.h"
#include "common/scoped_ptr.h"
#include "common/using_std_string.h"
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "google_breakpad/processor/microdump.h"
#include "google_breakpad/processor/microdump_processor.h"
#include "google_breakpad/processor/process_state.h"
#include "google_breakpad/processor/stack_frame_symbolizer.h"
#include "processor/logging.h"
#include "processor/simple_symbol_supplier.h"
#include "processor/stackwalk_common.h"
namespace {
struct Options {
bool machine_readable;
bool output_stack_contents;
string microdump_file;
std::vector<string> symbol_paths;
};
using google_breakpad::BasicSourceLineResolver;
using google_breakpad::Microdump;
using google_breakpad::MicrodumpProcessor;
using google_breakpad::ProcessResult;
using google_breakpad::ProcessState;
using google_breakpad::scoped_ptr;
using google_breakpad::SimpleSymbolSupplier;
using google_breakpad::StackFrameSymbolizer;
// Processes |options.microdump_file| using
// MicrodumpProcessor. |options.symbol_path|, if non-empty, is the
// base directory of a symbol storage area, laid out in the format
// required by SimpleSymbolSupplier. If such a storage area is
// specified, it is made available for use by the MicrodumpProcessor.
//
// Returns the value of MicrodumpProcessor::Process. If processing succeeds,
// prints identifying OS and CPU information from the microdump, crash
// information and call stacks for the crashing thread.
// All information is printed to stdout.
int PrintMicrodumpProcess(const Options& options) {
std::ifstream file_stream(options.microdump_file);
std::vector<char> bytes;
file_stream.seekg(0, std::ios_base::end);
bytes.resize(file_stream.tellg());
if (bytes.empty()) {
BPLOG(ERROR) << "Microdump is empty.";
return 1;
}
file_stream.seekg(0, std::ios_base::beg);
file_stream.read(&bytes[0], bytes.size());
string microdump_content(&bytes[0], bytes.size());
scoped_ptr<SimpleSymbolSupplier> symbol_supplier;
if (!options.symbol_paths.empty()) {
symbol_supplier.reset(new SimpleSymbolSupplier(options.symbol_paths));
}
BasicSourceLineResolver resolver;
StackFrameSymbolizer frame_symbolizer(symbol_supplier.get(), &resolver);
ProcessState process_state;
MicrodumpProcessor microdump_processor(&frame_symbolizer);
Microdump microdump(microdump_content);
ProcessResult res = microdump_processor.Process(&microdump,
&process_state);
if (res == google_breakpad::PROCESS_OK) {
if (options.machine_readable) {
PrintProcessStateMachineReadable(process_state);
} else {
// Microdump has only one thread, |output_requesting_thread_only|'s value
// has no effect.
PrintProcessState(process_state, options.output_stack_contents,
/*output_requesting_thread_only=*/false, &resolver);
}
return 0;
}
BPLOG(ERROR) << "MicrodumpProcessor::Process failed (code = " << res << ")";
return 1;
}
} // namespace
static void Usage(int argc, const char *argv[], bool error) {
fprintf(error ? stderr : stdout,
"Usage: %s [options] <microdump-file> [symbol-path ...]\n"
"\n"
"Output a stack trace for the provided microdump\n"
"\n"
"Options:\n"
"\n"
" -m Output in machine-readable format\n"
" -s Output stack contents\n",
google_breakpad::BaseName(argv[0]).c_str());
}
static void SetupOptions(int argc, const char *argv[], Options* options) {
int ch;
options->machine_readable = false;
options->output_stack_contents = false;
while ((ch = getopt(argc, (char * const*)argv, "hms")) != -1) {
switch (ch) {
case 'h':
Usage(argc, argv, false);
exit(0);
break;
case 'm':
options->machine_readable = true;
break;
case 's':
options->output_stack_contents = true;
break;
case '?':
Usage(argc, argv, true);
exit(1);
break;
}
}
if ((argc - optind) == 0) {
fprintf(stderr, "%s: Missing microdump file\n", argv[0]);
Usage(argc, argv, true);
exit(1);
}
options->microdump_file = argv[optind];
for (int argi = optind + 1; argi < argc; ++argi)
options->symbol_paths.push_back(argv[argi]);
}
int main(int argc, const char* argv[]) {
Options options;
SetupOptions(argc, argv, &options);
return PrintMicrodumpProcess(options);
}

View file

@ -0,0 +1,42 @@
#!/bin/sh
# Copyright 2014 Google LLC
#
# 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 LLC 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.
. "${0%/*}/microdump_stackwalk_test_vars" || exit 1 # for MICRODUMP_SUPPORTED_ARCHS.
testdata_dir=$srcdir/src/processor/testdata
set -e # Bail out with an error if any of the commands below fails.
for ARCH in $MICRODUMP_SUPPORTED_ARCHS; do
echo "Testing microdump_stackwalk -m for arch $ARCH"
./src/processor/microdump_stackwalk -m $testdata_dir/microdump-${ARCH}.dmp \
$testdata_dir/symbols/microdump | \
tr -d '\015' | \
diff -u $testdata_dir/microdump.stackwalk.machine_readable-${ARCH}.out -
done
exit 0

View file

@ -0,0 +1,42 @@
#!/bin/sh
# Copyright 2014 Google LLC
#
# 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 LLC 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.
. "${0%/*}/microdump_stackwalk_test_vars" || exit 1 # for MICRODUMP_SUPPORTED_ARCHS.
testdata_dir=$srcdir/src/processor/testdata
set -e # Bail out with an error if any of the commands below fails.
for ARCH in $MICRODUMP_SUPPORTED_ARCHS; do
echo "Testing microdump_stackwalk for arch $ARCH"
./src/processor/microdump_stackwalk $testdata_dir/microdump-${ARCH}.dmp \
$testdata_dir/symbols/microdump | \
tr -d '\015' | \
diff -u $testdata_dir/microdump.stackwalk-${ARCH}.out -
done
exit 0

View file

@ -0,0 +1 @@
MICRODUMP_SUPPORTED_ARCHS="arm arm64"

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,285 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// minidump_dump.cc: Print the contents of a minidump file in somewhat
// readable text.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "common/path_helper.h"
#include "common/scoped_ptr.h"
#include "google_breakpad/processor/minidump.h"
#include "processor/logging.h"
namespace {
using google_breakpad::Minidump;
using google_breakpad::MinidumpThreadList;
using google_breakpad::MinidumpThreadNameList;
using google_breakpad::MinidumpModuleList;
using google_breakpad::MinidumpMemoryInfoList;
using google_breakpad::MinidumpMemoryList;
using google_breakpad::MinidumpException;
using google_breakpad::MinidumpAssertion;
using google_breakpad::MinidumpSystemInfo;
using google_breakpad::MinidumpMiscInfo;
using google_breakpad::MinidumpBreakpadInfo;
using google_breakpad::MinidumpCrashpadInfo;
struct Options {
Options()
: minidumpPath(), hexdump(false), hexdump_width(16) {}
string minidumpPath;
bool hexdump;
unsigned int hexdump_width;
};
static void DumpRawStream(Minidump *minidump,
uint32_t stream_type,
const char *stream_name,
int *errors) {
uint32_t length = 0;
if (!minidump->SeekToStreamType(stream_type, &length)) {
return;
}
printf("Stream %s:\n", stream_name);
if (length == 0) {
printf("\n");
return;
}
std::vector<char> contents(length);
if (!minidump->ReadBytes(&contents[0], length)) {
++*errors;
BPLOG(ERROR) << "minidump.ReadBytes failed";
return;
}
size_t current_offset = 0;
while (current_offset < length) {
size_t remaining = length - current_offset;
// Printf requires an int and direct casting from size_t results
// in compatibility warnings.
uint32_t int_remaining = remaining;
printf("%.*s", int_remaining, &contents[current_offset]);
char *next_null = reinterpret_cast<char*>(
memchr(&contents[current_offset], 0, remaining));
if (next_null == NULL)
break;
printf("\\0\n");
size_t null_offset = next_null - &contents[0];
current_offset = null_offset + 1;
}
printf("\n\n");
}
static bool PrintMinidumpDump(const Options& options) {
Minidump minidump(options.minidumpPath,
options.hexdump);
if (!minidump.Read()) {
BPLOG(ERROR) << "minidump.Read() failed";
return false;
}
minidump.Print();
int errors = 0;
MinidumpThreadList *thread_list = minidump.GetThreadList();
if (!thread_list) {
++errors;
BPLOG(ERROR) << "minidump.GetThreadList() failed";
} else {
thread_list->Print();
}
MinidumpThreadNameList *thread_name_list = minidump.GetThreadNameList();
if (thread_name_list) {
thread_name_list->Print();
}
// It's useful to be able to see the full list of modules here even if it
// would cause minidump_stackwalk to fail.
MinidumpModuleList::set_max_modules(UINT32_MAX);
MinidumpModuleList *module_list = minidump.GetModuleList();
if (!module_list) {
++errors;
BPLOG(ERROR) << "minidump.GetModuleList() failed";
} else {
module_list->Print();
}
MinidumpMemoryList *memory_list = minidump.GetMemoryList();
if (!memory_list) {
++errors;
BPLOG(ERROR) << "minidump.GetMemoryList() failed";
} else {
memory_list->Print();
}
MinidumpException *exception = minidump.GetException();
if (!exception) {
BPLOG(INFO) << "minidump.GetException() failed";
} else {
exception->Print();
}
MinidumpAssertion *assertion = minidump.GetAssertion();
if (!assertion) {
BPLOG(INFO) << "minidump.GetAssertion() failed";
} else {
assertion->Print();
}
MinidumpSystemInfo *system_info = minidump.GetSystemInfo();
if (!system_info) {
++errors;
BPLOG(ERROR) << "minidump.GetSystemInfo() failed";
} else {
system_info->Print();
}
MinidumpMiscInfo *misc_info = minidump.GetMiscInfo();
if (!misc_info) {
++errors;
BPLOG(ERROR) << "minidump.GetMiscInfo() failed";
} else {
misc_info->Print();
}
MinidumpBreakpadInfo *breakpad_info = minidump.GetBreakpadInfo();
if (!breakpad_info) {
// Breakpad info is optional, so don't treat this as an error.
BPLOG(INFO) << "minidump.GetBreakpadInfo() failed";
} else {
breakpad_info->Print();
}
MinidumpMemoryInfoList *memory_info_list = minidump.GetMemoryInfoList();
if (!memory_info_list) {
++errors;
BPLOG(ERROR) << "minidump.GetMemoryInfoList() failed";
} else {
memory_info_list->Print();
}
MinidumpCrashpadInfo *crashpad_info = minidump.GetCrashpadInfo();
if (crashpad_info) {
// Crashpad info is optional, so don't treat absence as an error.
crashpad_info->Print();
}
DumpRawStream(&minidump,
MD_LINUX_CMD_LINE,
"MD_LINUX_CMD_LINE",
&errors);
DumpRawStream(&minidump,
MD_LINUX_ENVIRON,
"MD_LINUX_ENVIRON",
&errors);
DumpRawStream(&minidump,
MD_LINUX_LSB_RELEASE,
"MD_LINUX_LSB_RELEASE",
&errors);
DumpRawStream(&minidump,
MD_LINUX_PROC_STATUS,
"MD_LINUX_PROC_STATUS",
&errors);
DumpRawStream(&minidump,
MD_LINUX_CPU_INFO,
"MD_LINUX_CPU_INFO",
&errors);
DumpRawStream(&minidump,
MD_LINUX_MAPS,
"MD_LINUX_MAPS",
&errors);
return errors == 0;
}
//=============================================================================
static void
Usage(int argc, char *argv[], bool error) {
FILE *fp = error ? stderr : stdout;
fprintf(fp,
"Usage: %s [options...] <minidump>\n"
"Dump data in a minidump.\n"
"\n"
"Options:\n"
" <minidump> should be a minidump.\n"
" -x:\t Display memory in a hexdump like format\n"
" -h:\t Usage\n",
google_breakpad::BaseName(argv[0]).c_str());
}
//=============================================================================
static void
SetupOptions(int argc, char *argv[], Options *options) {
int ch;
while ((ch = getopt(argc, (char * const*)argv, "xh")) != -1) {
switch (ch) {
case 'x':
options->hexdump = true;
break;
case 'h':
Usage(argc, argv, false);
exit(0);
default:
Usage(argc, argv, true);
exit(1);
break;
}
}
if ((argc - optind) != 1) {
fprintf(stderr, "%s: Missing minidump file\n", argv[0]);
exit(1);
}
options->minidumpPath = argv[optind];
}
} // namespace
int main(int argc, char *argv[]) {
Options options;
BPLOG_INIT(&argc, &argv);
SetupOptions(argc, argv, &options);
return PrintMinidumpDump(options) ? 0 : 1;
}

View file

@ -0,0 +1,35 @@
#!/bin/sh
# Copyright 2006 Google LLC
#
# 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 LLC 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.
testdata_dir=$srcdir/src/processor/testdata
./src/processor/minidump_dump $testdata_dir/minidump2.dmp | \
tr -d '\015' | \
diff -u $testdata_dir/minidump2.dump.out -
exit $?

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,830 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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 test for MinidumpProcessor. Uses a pre-generated minidump and
// corresponding symbol file, and checks the stack frames for correctness.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <stdlib.h>
#include <string>
#include <iostream>
#include <fstream>
#include <map>
#include <utility>
#include "breakpad_googletest_includes.h"
#include "common/scoped_ptr.h"
#include "common/using_std_string.h"
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/code_module.h"
#include "google_breakpad/processor/code_modules.h"
#include "google_breakpad/processor/minidump.h"
#include "google_breakpad/processor/minidump_processor.h"
#include "google_breakpad/processor/process_state.h"
#include "google_breakpad/processor/stack_frame.h"
#include "google_breakpad/processor/symbol_supplier.h"
#include "processor/logging.h"
#include "processor/stackwalker_unittest_utils.h"
using std::map;
namespace google_breakpad {
class MockMinidump : public Minidump {
public:
MockMinidump() : Minidump("") {
}
MOCK_METHOD0(Read, bool());
MOCK_CONST_METHOD0(path, string());
MOCK_CONST_METHOD0(header, const MDRawHeader*());
MOCK_METHOD0(GetThreadList, MinidumpThreadList*());
MOCK_METHOD0(GetSystemInfo, MinidumpSystemInfo*());
MOCK_METHOD0(GetMiscInfo, MinidumpMiscInfo*());
MOCK_METHOD0(GetBreakpadInfo, MinidumpBreakpadInfo*());
MOCK_METHOD0(GetException, MinidumpException*());
MOCK_METHOD0(GetAssertion, MinidumpAssertion*());
MOCK_METHOD0(GetModuleList, MinidumpModuleList*());
MOCK_METHOD0(GetUnloadedModuleList, MinidumpUnloadedModuleList*());
MOCK_METHOD0(GetMemoryList, MinidumpMemoryList*());
};
class MockMinidumpUnloadedModule : public MinidumpUnloadedModule {
public:
MockMinidumpUnloadedModule() : MinidumpUnloadedModule(NULL) {}
};
class MockMinidumpUnloadedModuleList : public MinidumpUnloadedModuleList {
public:
MockMinidumpUnloadedModuleList() : MinidumpUnloadedModuleList(NULL) {}
~MockMinidumpUnloadedModuleList() {}
MOCK_CONST_METHOD0(Copy, CodeModules*());
MOCK_CONST_METHOD1(GetModuleForAddress,
const MinidumpUnloadedModule*(uint64_t));
};
class MockMinidumpThreadList : public MinidumpThreadList {
public:
MockMinidumpThreadList() : MinidumpThreadList(NULL) {}
MOCK_CONST_METHOD0(thread_count, unsigned int());
MOCK_CONST_METHOD1(GetThreadAtIndex, MinidumpThread*(unsigned int));
};
class MockMinidumpMemoryList : public MinidumpMemoryList {
public:
MockMinidumpMemoryList() : MinidumpMemoryList(NULL) {}
MOCK_METHOD1(GetMemoryRegionForAddress, MinidumpMemoryRegion*(uint64_t));
};
class MockMinidumpThread : public MinidumpThread {
public:
MockMinidumpThread() : MinidumpThread(NULL) {}
MOCK_CONST_METHOD1(GetThreadID, bool(uint32_t*));
MOCK_METHOD0(GetContext, MinidumpContext*());
MOCK_METHOD0(GetMemory, MinidumpMemoryRegion*());
MOCK_CONST_METHOD0(GetStartOfStackMemoryRange, uint64_t());
};
// This is crappy, but MinidumpProcessor really does want a
// MinidumpMemoryRegion.
class MockMinidumpMemoryRegion : public MinidumpMemoryRegion {
public:
MockMinidumpMemoryRegion(uint64_t base, const string& contents) :
MinidumpMemoryRegion(NULL) {
region_.Init(base, contents);
}
uint64_t GetBase() const { return region_.GetBase(); }
uint32_t GetSize() const { return region_.GetSize(); }
bool GetMemoryAtAddress(uint64_t address, uint8_t* value) const {
return region_.GetMemoryAtAddress(address, value);
}
bool GetMemoryAtAddress(uint64_t address, uint16_t* value) const {
return region_.GetMemoryAtAddress(address, value);
}
bool GetMemoryAtAddress(uint64_t address, uint32_t* value) const {
return region_.GetMemoryAtAddress(address, value);
}
bool GetMemoryAtAddress(uint64_t address, uint64_t* value) const {
return region_.GetMemoryAtAddress(address, value);
}
MockMemoryRegion region_;
};
// A test miscelaneous info stream, just returns values from the
// MDRawMiscInfo fed to it.
class TestMinidumpMiscInfo : public MinidumpMiscInfo {
public:
explicit TestMinidumpMiscInfo(const MDRawMiscInfo& misc_info) :
MinidumpMiscInfo(NULL) {
valid_ = true;
misc_info_ = misc_info;
}
};
} // namespace google_breakpad
namespace {
using google_breakpad::BasicSourceLineResolver;
using google_breakpad::CallStack;
using google_breakpad::CodeModule;
using google_breakpad::MinidumpContext;
using google_breakpad::MinidumpMemoryRegion;
using google_breakpad::MinidumpMiscInfo;
using google_breakpad::MinidumpProcessor;
using google_breakpad::MinidumpSystemInfo;
using google_breakpad::MinidumpThreadList;
using google_breakpad::MinidumpThread;
using google_breakpad::MockMinidump;
using google_breakpad::MockMinidumpMemoryList;
using google_breakpad::MockMinidumpMemoryRegion;
using google_breakpad::MockMinidumpThread;
using google_breakpad::MockMinidumpThreadList;
using google_breakpad::MockMinidumpUnloadedModule;
using google_breakpad::MockMinidumpUnloadedModuleList;
using google_breakpad::ProcessState;
using google_breakpad::scoped_ptr;
using google_breakpad::SymbolSupplier;
using google_breakpad::SystemInfo;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::Mock;
using ::testing::Ne;
using ::testing::Property;
using ::testing::Return;
using ::testing::SetArgumentPointee;
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";
#define ASSERT_TRUE_ABORT(cond) \
if (!(cond)) { \
fprintf(stderr, "FAILED: %s at %s:%d\n", #cond, __FILE__, __LINE__); \
abort(); \
}
#define ASSERT_EQ_ABORT(e1, e2) ASSERT_TRUE_ABORT((e1) == (e2))
static string GetTestDataPath() {
char* srcdir = getenv("srcdir");
return string(srcdir ? srcdir : ".") + "/src/processor/testdata/";
}
class TestSymbolSupplier : public SymbolSupplier {
public:
TestSymbolSupplier() : interrupt_(false) {}
virtual SymbolResult GetSymbolFile(const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file);
virtual SymbolResult GetSymbolFile(const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file,
string* symbol_data);
virtual SymbolResult GetCStringSymbolData(const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file,
char** symbol_data,
size_t* symbol_data_size);
virtual void FreeSymbolData(const CodeModule* module);
// When set to true, causes the SymbolSupplier to return INTERRUPT
void set_interrupt(bool interrupt) { interrupt_ = interrupt; }
private:
bool interrupt_;
map<string, char*> memory_buffers_;
};
SymbolSupplier::SymbolResult TestSymbolSupplier::GetSymbolFile(
const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file) {
ASSERT_TRUE_ABORT(module);
ASSERT_TRUE_ABORT(system_info);
ASSERT_EQ_ABORT(system_info->cpu, kSystemInfoCPU);
ASSERT_EQ_ABORT(system_info->cpu_info, kSystemInfoCPUInfo);
ASSERT_EQ_ABORT(system_info->os, kSystemInfoOS);
ASSERT_EQ_ABORT(system_info->os_short, kSystemInfoOSShort);
ASSERT_EQ_ABORT(system_info->os_version, kSystemInfoOSVersion);
if (interrupt_) {
return INTERRUPT;
}
if (module && module->code_file() == "c:\\test_app.exe") {
*symbol_file = GetTestDataPath() + "symbols/test_app.pdb/" +
module->debug_identifier() + "/test_app.sym";
return FOUND;
}
return NOT_FOUND;
}
SymbolSupplier::SymbolResult TestSymbolSupplier::GetSymbolFile(
const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file,
string* symbol_data) {
SymbolSupplier::SymbolResult s = GetSymbolFile(module, system_info,
symbol_file);
if (s == FOUND) {
std::ifstream in(symbol_file->c_str());
std::getline(in, *symbol_data, string::traits_type::to_char_type(
string::traits_type::eof()));
in.close();
}
return s;
}
SymbolSupplier::SymbolResult TestSymbolSupplier::GetCStringSymbolData(
const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file,
char** symbol_data,
size_t* symbol_data_size) {
string symbol_data_string;
SymbolSupplier::SymbolResult s = GetSymbolFile(module,
system_info,
symbol_file,
&symbol_data_string);
if (s == FOUND) {
*symbol_data_size = symbol_data_string.size() + 1;
*symbol_data = new char[*symbol_data_size];
if (*symbol_data == NULL) {
BPLOG(ERROR) << "Memory allocation failed for module: "
<< module->code_file() << " size: " << *symbol_data_size;
return INTERRUPT;
}
memcpy(*symbol_data, symbol_data_string.c_str(), symbol_data_string.size());
(*symbol_data)[symbol_data_string.size()] = '\0';
memory_buffers_.insert(make_pair(module->code_file(), *symbol_data));
}
return s;
}
void TestSymbolSupplier::FreeSymbolData(const CodeModule* module) {
map<string, char*>::iterator it = memory_buffers_.find(module->code_file());
if (it != memory_buffers_.end()) {
delete [] it->second;
memory_buffers_.erase(it);
}
}
// A test system info stream, just returns values from the
// MDRawSystemInfo fed to it.
class TestMinidumpSystemInfo : public MinidumpSystemInfo {
public:
explicit TestMinidumpSystemInfo(MDRawSystemInfo info) :
MinidumpSystemInfo(NULL) {
valid_ = true;
system_info_ = info;
csd_version_ = new string("");
}
};
// A test minidump context, just returns the MDRawContextX86
// fed to it.
class TestMinidumpContext : public MinidumpContext {
public:
explicit TestMinidumpContext(const MDRawContextX86& context) :
MinidumpContext(NULL) {
valid_ = true;
SetContextX86(new MDRawContextX86(context));
SetContextFlags(MD_CONTEXT_X86);
}
};
class MinidumpProcessorTest : public ::testing::Test {
};
TEST_F(MinidumpProcessorTest, TestUnloadedModules) {
MockMinidump dump;
EXPECT_CALL(dump, path()).WillRepeatedly(Return("mock minidump"));
EXPECT_CALL(dump, Read()).WillRepeatedly(Return(true));
MDRawHeader fake_header;
fake_header.time_date_stamp = 0;
EXPECT_CALL(dump, header()).WillRepeatedly(Return(&fake_header));
MDRawSystemInfo raw_system_info;
memset(&raw_system_info, 0, sizeof(raw_system_info));
raw_system_info.processor_architecture = MD_CPU_ARCHITECTURE_X86;
raw_system_info.platform_id = MD_OS_WIN32_NT;
TestMinidumpSystemInfo dump_system_info(raw_system_info);
EXPECT_CALL(dump, GetSystemInfo()).
WillRepeatedly(Return(&dump_system_info));
// No loaded modules
MockMinidumpUnloadedModuleList unloaded_module_list;
EXPECT_CALL(dump, GetUnloadedModuleList()).
WillOnce(Return(&unloaded_module_list));
MockMinidumpMemoryList memory_list;
EXPECT_CALL(dump, GetMemoryList()).
WillOnce(Return(&memory_list));
MockMinidumpThreadList thread_list;
EXPECT_CALL(dump, GetThreadList()).
WillOnce(Return(&thread_list));
EXPECT_CALL(thread_list, thread_count()).
WillRepeatedly(Return(1));
MockMinidumpThread thread;
EXPECT_CALL(thread_list, GetThreadAtIndex(0)).
WillOnce(Return(&thread));
EXPECT_CALL(thread, GetThreadID(_)).
WillRepeatedly(DoAll(SetArgumentPointee<0>(1),
Return(true)));
MDRawContextX86 thread_raw_context;
memset(&thread_raw_context, 0,
sizeof(thread_raw_context));
thread_raw_context.context_flags = MD_CONTEXT_X86_FULL;
const uint32_t kExpectedEIP = 0xabcd1234;
thread_raw_context.eip = kExpectedEIP;
TestMinidumpContext thread_context(thread_raw_context);
EXPECT_CALL(thread, GetContext()).
WillRepeatedly(Return(&thread_context));
// The memory contents don't really matter here, since it won't be used.
MockMinidumpMemoryRegion thread_memory(0x1234, "xxx");
EXPECT_CALL(thread, GetMemory()).
WillRepeatedly(Return(&thread_memory));
EXPECT_CALL(thread, GetStartOfStackMemoryRange()).
Times(0);
EXPECT_CALL(memory_list, GetMemoryRegionForAddress(_)).
Times(0);
MockMinidumpUnloadedModuleList* unloaded_module_list_copy =
new MockMinidumpUnloadedModuleList();
EXPECT_CALL(unloaded_module_list, Copy()).
WillOnce(Return(unloaded_module_list_copy));
MockMinidumpUnloadedModule unloaded_module;
EXPECT_CALL(*unloaded_module_list_copy, GetModuleForAddress(kExpectedEIP)).
WillOnce(Return(&unloaded_module));
MinidumpProcessor processor(reinterpret_cast<SymbolSupplier*>(NULL), NULL);
ProcessState state;
EXPECT_EQ(processor.Process(&dump, &state),
google_breakpad::PROCESS_OK);
// The single frame should be populated with the unloaded module.
ASSERT_EQ(1U, state.threads()->size());
ASSERT_EQ(1U, state.threads()->at(0)->frames()->size());
ASSERT_EQ(kExpectedEIP, state.threads()->at(0)->frames()->at(0)->instruction);
ASSERT_EQ(&unloaded_module, state.threads()->at(0)->frames()->at(0)->module);
}
TEST_F(MinidumpProcessorTest, TestCorruptMinidumps) {
MockMinidump dump;
TestSymbolSupplier supplier;
BasicSourceLineResolver resolver;
MinidumpProcessor processor(&supplier, &resolver);
ProcessState state;
EXPECT_EQ(processor.Process("nonexistent minidump", &state),
google_breakpad::PROCESS_ERROR_MINIDUMP_NOT_FOUND);
EXPECT_CALL(dump, path()).WillRepeatedly(Return("mock minidump"));
EXPECT_CALL(dump, Read()).WillRepeatedly(Return(true));
MDRawHeader fakeHeader;
fakeHeader.time_date_stamp = 0;
EXPECT_CALL(dump, header()).
WillOnce(Return(reinterpret_cast<MDRawHeader*>(NULL))).
WillRepeatedly(Return(&fakeHeader));
EXPECT_EQ(processor.Process(&dump, &state),
google_breakpad::PROCESS_ERROR_NO_MINIDUMP_HEADER);
EXPECT_CALL(dump, GetThreadList()).
WillOnce(Return(reinterpret_cast<MinidumpThreadList*>(NULL)));
EXPECT_CALL(dump, GetSystemInfo()).
WillRepeatedly(Return(reinterpret_cast<MinidumpSystemInfo*>(NULL)));
EXPECT_EQ(processor.Process(&dump, &state),
google_breakpad::PROCESS_ERROR_NO_THREAD_LIST);
}
// This test case verifies that the symbol supplier is only consulted
// once per minidump per module.
TEST_F(MinidumpProcessorTest, TestSymbolSupplierLookupCounts) {
MockSymbolSupplier supplier;
BasicSourceLineResolver resolver;
MinidumpProcessor processor(&supplier, &resolver);
string minidump_file = GetTestDataPath() + "minidump2.dmp";
ProcessState state;
EXPECT_CALL(supplier, GetCStringSymbolData(
Property(&google_breakpad::CodeModule::code_file,
"c:\\test_app.exe"),
_, _, _, _)).WillOnce(Return(SymbolSupplier::NOT_FOUND));
EXPECT_CALL(supplier, GetCStringSymbolData(
Property(&google_breakpad::CodeModule::code_file,
Ne("c:\\test_app.exe")),
_, _, _, _)).WillRepeatedly(Return(SymbolSupplier::NOT_FOUND));
// Avoid GMOCK WARNING "Uninteresting mock function call - returning
// directly" for FreeSymbolData().
EXPECT_CALL(supplier, FreeSymbolData(_)).Times(AnyNumber());
ASSERT_EQ(processor.Process(minidump_file, &state),
google_breakpad::PROCESS_OK);
ASSERT_TRUE(Mock::VerifyAndClearExpectations(&supplier));
// We need to verify that across minidumps, the processor will refetch
// symbol files, even with the same symbol supplier.
EXPECT_CALL(supplier, GetCStringSymbolData(
Property(&google_breakpad::CodeModule::code_file,
"c:\\test_app.exe"),
_, _, _, _)).WillOnce(Return(SymbolSupplier::NOT_FOUND));
EXPECT_CALL(supplier, GetCStringSymbolData(
Property(&google_breakpad::CodeModule::code_file,
Ne("c:\\test_app.exe")),
_, _, _, _)).WillRepeatedly(Return(SymbolSupplier::NOT_FOUND));
// Avoid GMOCK WARNING "Uninteresting mock function call - returning
// directly" for FreeSymbolData().
EXPECT_CALL(supplier, FreeSymbolData(_)).Times(AnyNumber());
ASSERT_EQ(processor.Process(minidump_file, &state),
google_breakpad::PROCESS_OK);
}
TEST_F(MinidumpProcessorTest, TestBasicProcessing) {
TestSymbolSupplier supplier;
BasicSourceLineResolver resolver;
MinidumpProcessor processor(&supplier, &resolver);
string minidump_file = GetTestDataPath() + "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_WRITE");
ASSERT_EQ(state.crash_address(), 0x45U);
ASSERT_EQ(state.threads()->size(), size_t(1));
EXPECT_EQ((*state.threads())[0]->tid(), 3060U);
ASSERT_EQ(state.requesting_thread(), 0);
EXPECT_EQ(1171480435U, state.time_date_stamp());
EXPECT_EQ(1171480435U, state.process_create_time());
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);
// No debug info available for 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_TRUE(stack->frames()->at(3)->function_name.empty());
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");
// Test that disabled exploitability engine defaults to
// EXPLOITABILITY_NOT_ANALYZED.
ASSERT_EQ(google_breakpad::EXPLOITABILITY_NOT_ANALYZED,
state.exploitability());
// Test that the symbol supplier can interrupt processing
state.Clear();
supplier.set_interrupt(true);
ASSERT_EQ(processor.Process(minidump_file, &state),
google_breakpad::PROCESS_SYMBOL_SUPPLIER_INTERRUPTED);
}
TEST_F(MinidumpProcessorTest, TestThreadMissingMemory) {
MockMinidump dump;
EXPECT_CALL(dump, path()).WillRepeatedly(Return("mock minidump"));
EXPECT_CALL(dump, Read()).WillRepeatedly(Return(true));
MDRawHeader fake_header;
fake_header.time_date_stamp = 0;
EXPECT_CALL(dump, header()).WillRepeatedly(Return(&fake_header));
MDRawSystemInfo raw_system_info;
memset(&raw_system_info, 0, sizeof(raw_system_info));
raw_system_info.processor_architecture = MD_CPU_ARCHITECTURE_X86;
raw_system_info.platform_id = MD_OS_WIN32_NT;
TestMinidumpSystemInfo dump_system_info(raw_system_info);
EXPECT_CALL(dump, GetSystemInfo()).
WillRepeatedly(Return(&dump_system_info));
MockMinidumpThreadList thread_list;
EXPECT_CALL(dump, GetThreadList()).
WillOnce(Return(&thread_list));
MockMinidumpMemoryList memory_list;
EXPECT_CALL(dump, GetMemoryList()).
WillOnce(Return(&memory_list));
// Return a thread missing stack memory.
MockMinidumpThread no_memory_thread;
EXPECT_CALL(no_memory_thread, GetThreadID(_)).
WillRepeatedly(DoAll(SetArgumentPointee<0>(1),
Return(true)));
EXPECT_CALL(no_memory_thread, GetMemory()).
WillRepeatedly(Return(reinterpret_cast<MinidumpMemoryRegion*>(NULL)));
const uint64_t kTestStartOfMemoryRange = 0x1234;
EXPECT_CALL(no_memory_thread, GetStartOfStackMemoryRange()).
WillRepeatedly(Return(kTestStartOfMemoryRange));
EXPECT_CALL(memory_list, GetMemoryRegionForAddress(kTestStartOfMemoryRange)).
WillRepeatedly(Return(reinterpret_cast<MinidumpMemoryRegion*>(NULL)));
MDRawContextX86 no_memory_thread_raw_context;
memset(&no_memory_thread_raw_context, 0,
sizeof(no_memory_thread_raw_context));
no_memory_thread_raw_context.context_flags = MD_CONTEXT_X86_FULL;
const uint32_t kExpectedEIP = 0xabcd1234;
no_memory_thread_raw_context.eip = kExpectedEIP;
TestMinidumpContext no_memory_thread_context(no_memory_thread_raw_context);
EXPECT_CALL(no_memory_thread, GetContext()).
WillRepeatedly(Return(&no_memory_thread_context));
EXPECT_CALL(thread_list, thread_count()).
WillRepeatedly(Return(1));
EXPECT_CALL(thread_list, GetThreadAtIndex(0)).
WillOnce(Return(&no_memory_thread));
MinidumpProcessor processor(reinterpret_cast<SymbolSupplier*>(NULL), NULL);
ProcessState state;
EXPECT_EQ(processor.Process(&dump, &state),
google_breakpad::PROCESS_OK);
// Should have a single thread with a single frame in it.
ASSERT_EQ(1U, state.threads()->size());
ASSERT_EQ(1U, state.threads()->at(0)->frames()->size());
ASSERT_EQ(kExpectedEIP, state.threads()->at(0)->frames()->at(0)->instruction);
}
TEST_F(MinidumpProcessorTest, GetProcessCreateTime) {
const uint32_t kProcessCreateTime = 2000;
const uint32_t kTimeDateStamp = 5000;
MockMinidump dump;
EXPECT_CALL(dump, path()).WillRepeatedly(Return("mock minidump"));
EXPECT_CALL(dump, Read()).WillRepeatedly(Return(true));
// Set time of crash.
MDRawHeader fake_header;
fake_header.time_date_stamp = kTimeDateStamp;
EXPECT_CALL(dump, header()).WillRepeatedly(Return(&fake_header));
// Set process create time.
MDRawMiscInfo raw_misc_info;
memset(&raw_misc_info, 0, sizeof(raw_misc_info));
raw_misc_info.process_create_time = kProcessCreateTime;
raw_misc_info.flags1 |= MD_MISCINFO_FLAGS1_PROCESS_TIMES;
google_breakpad::TestMinidumpMiscInfo dump_misc_info(raw_misc_info);
EXPECT_CALL(dump, GetMiscInfo()).WillRepeatedly(Return(&dump_misc_info));
// No threads
MockMinidumpThreadList thread_list;
EXPECT_CALL(dump, GetThreadList()).WillOnce(Return(&thread_list));
EXPECT_CALL(thread_list, thread_count()).WillRepeatedly(Return(0));
MinidumpProcessor processor(reinterpret_cast<SymbolSupplier*>(NULL), NULL);
ProcessState state;
EXPECT_EQ(google_breakpad::PROCESS_OK, processor.Process(&dump, &state));
// Verify the time stamps.
ASSERT_EQ(kTimeDateStamp, state.time_date_stamp());
ASSERT_EQ(kProcessCreateTime, state.process_create_time());
}
TEST_F(MinidumpProcessorTest, TestThreadMissingContext) {
MockMinidump dump;
EXPECT_CALL(dump, path()).WillRepeatedly(Return("mock minidump"));
EXPECT_CALL(dump, Read()).WillRepeatedly(Return(true));
MDRawHeader fake_header;
fake_header.time_date_stamp = 0;
EXPECT_CALL(dump, header()).WillRepeatedly(Return(&fake_header));
MDRawSystemInfo raw_system_info;
memset(&raw_system_info, 0, sizeof(raw_system_info));
raw_system_info.processor_architecture = MD_CPU_ARCHITECTURE_X86;
raw_system_info.platform_id = MD_OS_WIN32_NT;
TestMinidumpSystemInfo dump_system_info(raw_system_info);
EXPECT_CALL(dump, GetSystemInfo()).
WillRepeatedly(Return(&dump_system_info));
MockMinidumpThreadList thread_list;
EXPECT_CALL(dump, GetThreadList()).
WillOnce(Return(&thread_list));
MockMinidumpMemoryList memory_list;
EXPECT_CALL(dump, GetMemoryList()).
WillOnce(Return(&memory_list));
// Return a thread missing a thread context.
MockMinidumpThread no_context_thread;
EXPECT_CALL(no_context_thread, GetThreadID(_)).
WillRepeatedly(DoAll(SetArgumentPointee<0>(1),
Return(true)));
EXPECT_CALL(no_context_thread, GetContext()).
WillRepeatedly(Return(reinterpret_cast<MinidumpContext*>(NULL)));
// The memory contents don't really matter here, since it won't be used.
MockMinidumpMemoryRegion no_context_thread_memory(0x1234, "xxx");
EXPECT_CALL(no_context_thread, GetMemory()).
WillRepeatedly(Return(&no_context_thread_memory));
EXPECT_CALL(no_context_thread, GetStartOfStackMemoryRange()).
Times(0);
EXPECT_CALL(memory_list, GetMemoryRegionForAddress(_)).
Times(0);
EXPECT_CALL(thread_list, thread_count()).
WillRepeatedly(Return(1));
EXPECT_CALL(thread_list, GetThreadAtIndex(0)).
WillOnce(Return(&no_context_thread));
MinidumpProcessor processor(reinterpret_cast<SymbolSupplier*>(NULL), NULL);
ProcessState state;
EXPECT_EQ(processor.Process(&dump, &state),
google_breakpad::PROCESS_OK);
// Should have a single thread with zero frames.
ASSERT_EQ(1U, state.threads()->size());
ASSERT_EQ(0U, state.threads()->at(0)->frames()->size());
}
TEST_F(MinidumpProcessorTest, Test32BitCrashingAddress) {
TestSymbolSupplier supplier;
BasicSourceLineResolver resolver;
MinidumpProcessor processor(&supplier, &resolver);
string minidump_file = GetTestDataPath() + "minidump_32bit_crash_addr.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_WRITE");
ASSERT_EQ(state.crash_address(), 0x45U);
}
TEST_F(MinidumpProcessorTest, TestXStateAmd64ContextMinidump) {
// This tests if we can passively process a minidump with cet registers in its
// context. Dump is captured from a toy executable and is readable by windbg.
MinidumpProcessor processor(nullptr, nullptr /*&supplier, &resolver*/);
string minidump_file = GetTestDataPath()
+ "tiny-exe-with-cet-xsave.dmp";
ProcessState state;
ASSERT_EQ(processor.Process(minidump_file, &state),
google_breakpad::PROCESS_OK);
ASSERT_EQ(state.system_info()->os, "Windows NT");
ASSERT_EQ(state.system_info()->os_version, "10.0.22000 282");
ASSERT_EQ(state.system_info()->cpu, "amd64");
ASSERT_EQ(state.system_info()->cpu_info,
"family 6 model 140 stepping 1");
ASSERT_FALSE(state.crashed());
ASSERT_EQ(state.threads()->size(), size_t(1));
// TODO: verify cetumsr and cetussp once these are supported by
// breakpad.
}
TEST_F(MinidumpProcessorTest, TestFastFailException) {
// This tests if we can understand fastfail exception subcodes.
// Dump is captured from a toy executable and is readable by windbg.
MinidumpProcessor processor(nullptr, nullptr /*&supplier, &resolver*/);
string minidump_file = GetTestDataPath()
+ "tiny-exe-fastfail.dmp";
ProcessState state;
ASSERT_EQ(processor.Process(minidump_file, &state),
google_breakpad::PROCESS_OK);
ASSERT_TRUE(state.crashed());
ASSERT_EQ(state.threads()->size(), size_t(4));
ASSERT_EQ(state.crash_reason(), "FAST_FAIL_FATAL_APP_EXIT");
}
#ifdef __linux__
TEST_F(MinidumpProcessorTest, TestNonCanonicalAddress) {
// This tests if we can correctly fixup non-canonical address GPF fault
// addresses.
// Dump is captured from a toy executable and is readable by windbg.
MinidumpProcessor processor(nullptr, nullptr /*&supplier, &resolver*/);
processor.set_enable_objdump(true);
string minidump_file = GetTestDataPath()
+ "write_av_non_canonical.dmp";
ProcessState state;
ASSERT_EQ(processor.Process(minidump_file, &state),
google_breakpad::PROCESS_OK);
ASSERT_TRUE(state.crashed());
ASSERT_EQ(state.crash_address(), 0xfefefefefefefefeU);
}
#endif // __linux__
} // namespace
int main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View file

@ -0,0 +1,197 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// minidump_stackwalk.cc: Process a minidump with MinidumpProcessor, printing
// the results, including stack traces.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <limits>
#include <string>
#include <vector>
#include "common/path_helper.h"
#include "common/scoped_ptr.h"
#include "common/using_std_string.h"
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "google_breakpad/processor/minidump.h"
#include "google_breakpad/processor/minidump_processor.h"
#include "google_breakpad/processor/process_state.h"
#include "processor/logging.h"
#include "processor/simple_symbol_supplier.h"
#include "processor/stackwalk_common.h"
namespace {
struct Options {
bool machine_readable;
bool output_stack_contents;
bool output_requesting_thread_only;
bool brief;
string minidump_file;
std::vector<string> symbol_paths;
};
using google_breakpad::BasicSourceLineResolver;
using google_breakpad::Minidump;
using google_breakpad::MinidumpMemoryList;
using google_breakpad::MinidumpThreadList;
using google_breakpad::MinidumpProcessor;
using google_breakpad::ProcessState;
using google_breakpad::SimpleSymbolSupplier;
using google_breakpad::scoped_ptr;
// Processes |options.minidump_file| using MinidumpProcessor.
// |options.symbol_path|, if non-empty, is the base directory of a
// symbol storage area, laid out in the format required by
// SimpleSymbolSupplier. If such a storage area is specified, it is
// made available for use by the MinidumpProcessor.
//
// Returns the value of MinidumpProcessor::Process. If processing succeeds,
// prints identifying OS and CPU information from the minidump, crash
// information if the minidump was produced as a result of a crash, and
// call stacks for each thread contained in the minidump. All information
// is printed to stdout.
bool PrintMinidumpProcess(const Options& options) {
scoped_ptr<SimpleSymbolSupplier> symbol_supplier;
if (!options.symbol_paths.empty()) {
// TODO(mmentovai): check existence of symbol_path if specified?
symbol_supplier.reset(new SimpleSymbolSupplier(options.symbol_paths));
}
BasicSourceLineResolver resolver;
MinidumpProcessor minidump_processor(symbol_supplier.get(), &resolver);
// Increase the maximum number of threads and regions.
MinidumpThreadList::set_max_threads(std::numeric_limits<uint32_t>::max());
MinidumpMemoryList::set_max_regions(std::numeric_limits<uint32_t>::max());
// Process the minidump.
Minidump dump(options.minidump_file);
if (!dump.Read()) {
BPLOG(ERROR) << "Minidump " << dump.path() << " could not be read";
return false;
}
ProcessState process_state;
if (minidump_processor.Process(&dump, &process_state) !=
google_breakpad::PROCESS_OK) {
BPLOG(ERROR) << "MinidumpProcessor::Process failed";
return false;
}
if (options.machine_readable) {
PrintProcessStateMachineReadable(process_state);
} else if (options.brief) {
PrintRequestingThreadBrief(process_state);
} else {
PrintProcessState(process_state, options.output_stack_contents,
options.output_requesting_thread_only, &resolver);
}
return true;
}
} // namespace
static void Usage(int argc, const char *argv[], bool error) {
fprintf(error ? stderr : stdout,
"Usage: %s [options] <minidump-file> [symbol-path ...]\n"
"\n"
"Output a stack trace for the provided minidump\n"
"\n"
"Options:\n"
"\n"
" -m Output in machine-readable format\n"
" -s Output stack contents\n"
" -c Output thread that causes crash or dump only\n"
" -b Brief of the thread that causes crash or dump\n",
google_breakpad::BaseName(argv[0]).c_str());
}
static void SetupOptions(int argc, const char *argv[], Options* options) {
int ch;
options->machine_readable = false;
options->output_stack_contents = false;
options->output_requesting_thread_only = false;
options->brief = false;
while ((ch = getopt(argc, (char* const*)argv, "bchms")) != -1) {
switch (ch) {
case 'h':
Usage(argc, argv, false);
exit(0);
break;
case 'b':
options->brief = true;
break;
case 'c':
options->output_requesting_thread_only = true;
break;
case 'm':
options->machine_readable = true;
break;
case 's':
options->output_stack_contents = true;
break;
case '?':
Usage(argc, argv, true);
exit(1);
break;
}
}
if ((argc - optind) == 0) {
fprintf(stderr, "%s: Missing minidump file\n", argv[0]);
Usage(argc, argv, true);
exit(1);
}
options->minidump_file = argv[optind];
for (int argi = optind + 1; argi < argc; ++argi)
options->symbol_paths.push_back(argv[argi]);
}
int main(int argc, const char* argv[]) {
Options options;
SetupOptions(argc, argv, &options);
return PrintMinidumpProcess(options) ? 0 : 1;
}

View file

@ -0,0 +1,36 @@
#!/bin/sh
# Copyright 2007 Google LLC
#
# 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 LLC 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.
testdata_dir=$srcdir/src/processor/testdata
./src/processor/minidump_stackwalk -m $testdata_dir/minidump2.dmp \
$testdata_dir/symbols | \
tr -d '\015' | \
diff -u $testdata_dir/minidump2.stackwalk.machine_readable.out -
exit $?

View file

@ -0,0 +1,36 @@
#!/bin/sh
# Copyright 2006 Google LLC
#
# 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 LLC 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.
testdata_dir=$srcdir/src/processor/testdata
./src/processor/minidump_stackwalk $testdata_dir/minidump2.dmp \
$testdata_dir/symbols | \
tr -d '\015' | \
diff -u $testdata_dir/minidump2.stackwalk.out -
exit $?

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,305 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// module_comparer.cc: ModuleComparer implementation.
// See module_comparer.h for documentation.
//
// Author: lambxsy@google.com (Siyang Xie)
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "processor/module_comparer.h"
#include <map>
#include <string>
#include "common/scoped_ptr.h"
#include "processor/basic_code_module.h"
#include "processor/logging.h"
#define ASSERT_TRUE(condition) \
if (!(condition)) { \
BPLOG(ERROR) << "FAIL: " << #condition << " @ " \
<< __FILE__ << ":" << __LINE__; \
return false; \
}
#define ASSERT_FALSE(condition) ASSERT_TRUE(!(condition))
namespace google_breakpad {
bool ModuleComparer::Compare(const string& symbol_data) {
scoped_ptr<BasicModule> basic_module(new BasicModule("test_module"));
scoped_ptr<FastModule> fast_module(new FastModule("test_module"));
// Load symbol data into basic_module
scoped_array<char> buffer(new char[symbol_data.size() + 1]);
memcpy(buffer.get(), symbol_data.c_str(), symbol_data.size());
buffer.get()[symbol_data.size()] = '\0';
ASSERT_TRUE(basic_module->LoadMapFromMemory(buffer.get(),
symbol_data.size() + 1));
buffer.reset();
// Serialize BasicSourceLineResolver::Module.
size_t serialized_size = 0;
scoped_array<char> serialized_data(
serializer_.Serialize(*(basic_module.get()), &serialized_size));
ASSERT_TRUE(serialized_data.get());
BPLOG(INFO) << "Serialized size = " << serialized_size << " Bytes";
// Load FastSourceLineResolver::Module using serialized data.
ASSERT_TRUE(fast_module->LoadMapFromMemory(serialized_data.get(),
serialized_size));
ASSERT_TRUE(fast_module->IsCorrupt() == basic_module->IsCorrupt());
// Compare FastSourceLineResolver::Module with
// BasicSourceLineResolver::Module.
ASSERT_TRUE(CompareModule(basic_module.get(), fast_module.get()));
return true;
}
// Traversal the content of module and do comparison
bool ModuleComparer::CompareModule(const BasicModule *basic_module,
const FastModule *fast_module) const {
// Compare name_.
ASSERT_TRUE(basic_module->name_ == fast_module->name_);
// Compare files_:
{
BasicModule::FileMap::const_iterator iter1 = basic_module->files_.begin();
FastModule::FileMap::iterator iter2 = fast_module->files_.begin();
while (iter1 != basic_module->files_.end()
&& iter2 != fast_module->files_.end()) {
ASSERT_TRUE(iter1->first == iter2.GetKey());
string tmp(iter2.GetValuePtr());
ASSERT_TRUE(iter1->second == tmp);
++iter1;
++iter2;
}
ASSERT_TRUE(iter1 == basic_module->files_.end());
ASSERT_TRUE(iter2 == fast_module->files_.end());
}
// Compare functions_:
{
RangeMap<MemAddr, linked_ptr<BasicFunc> >::MapConstIterator iter1;
StaticRangeMap<MemAddr, FastFunc>::MapConstIterator iter2;
iter1 = basic_module->functions_.map_.begin();
iter2 = fast_module->functions_.map_.begin();
while (iter1 != basic_module->functions_.map_.end()
&& iter2 != fast_module->functions_.map_.end()) {
ASSERT_TRUE(iter1->first == iter2.GetKey());
ASSERT_TRUE(iter1->second.base() == iter2.GetValuePtr()->base());
ASSERT_TRUE(CompareFunction(
iter1->second.entry().get(), iter2.GetValuePtr()->entryptr()));
++iter1;
++iter2;
}
ASSERT_TRUE(iter1 == basic_module->functions_.map_.end());
ASSERT_TRUE(iter2 == fast_module->functions_.map_.end());
}
// Compare public_symbols_:
{
AddressMap<MemAddr, linked_ptr<BasicPubSymbol> >::MapConstIterator iter1;
StaticAddressMap<MemAddr, FastPubSymbol>::MapConstIterator iter2;
iter1 = basic_module->public_symbols_.map_.begin();
iter2 = fast_module->public_symbols_.map_.begin();
while (iter1 != basic_module->public_symbols_.map_.end()
&& iter2 != fast_module->public_symbols_.map_.end()) {
ASSERT_TRUE(iter1->first == iter2.GetKey());
ASSERT_TRUE(ComparePubSymbol(
iter1->second.get(), iter2.GetValuePtr()));
++iter1;
++iter2;
}
ASSERT_TRUE(iter1 == basic_module->public_symbols_.map_.end());
ASSERT_TRUE(iter2 == fast_module->public_symbols_.map_.end());
}
// Compare windows_frame_info_[]:
for (int i = 0; i < WindowsFrameInfo::STACK_INFO_LAST; ++i) {
ASSERT_TRUE(CompareCRM(&(basic_module->windows_frame_info_[i]),
&(fast_module->windows_frame_info_[i])));
}
// Compare cfi_initial_rules_:
{
RangeMap<MemAddr, string>::MapConstIterator iter1;
StaticRangeMap<MemAddr, char>::MapConstIterator iter2;
iter1 = basic_module->cfi_initial_rules_.map_.begin();
iter2 = fast_module->cfi_initial_rules_.map_.begin();
while (iter1 != basic_module->cfi_initial_rules_.map_.end()
&& iter2 != fast_module->cfi_initial_rules_.map_.end()) {
ASSERT_TRUE(iter1->first == iter2.GetKey());
ASSERT_TRUE(iter1->second.base() == iter2.GetValuePtr()->base());
string tmp(iter2.GetValuePtr()->entryptr());
ASSERT_TRUE(iter1->second.entry() == tmp);
++iter1;
++iter2;
}
ASSERT_TRUE(iter1 == basic_module->cfi_initial_rules_.map_.end());
ASSERT_TRUE(iter2 == fast_module->cfi_initial_rules_.map_.end());
}
// Compare cfi_delta_rules_:
{
map<MemAddr, string>::const_iterator iter1;
StaticMap<MemAddr, char>::iterator iter2;
iter1 = basic_module->cfi_delta_rules_.begin();
iter2 = fast_module->cfi_delta_rules_.begin();
while (iter1 != basic_module->cfi_delta_rules_.end()
&& iter2 != fast_module->cfi_delta_rules_.end()) {
ASSERT_TRUE(iter1->first == iter2.GetKey());
string tmp(iter2.GetValuePtr());
ASSERT_TRUE(iter1->second == tmp);
++iter1;
++iter2;
}
ASSERT_TRUE(iter1 == basic_module->cfi_delta_rules_.end());
ASSERT_TRUE(iter2 == fast_module->cfi_delta_rules_.end());
}
return true;
}
bool ModuleComparer::CompareFunction(const BasicFunc *basic_func,
const FastFunc *fast_func_raw) const {
FastFunc* fast_func = new FastFunc();
fast_func->CopyFrom(fast_func_raw);
ASSERT_TRUE(basic_func->name == fast_func->name);
ASSERT_TRUE(basic_func->address == fast_func->address);
ASSERT_TRUE(basic_func->size == fast_func->size);
// compare range map of lines:
RangeMap<MemAddr, linked_ptr<BasicLine> >::MapConstIterator iter1;
StaticRangeMap<MemAddr, FastLine>::MapConstIterator iter2;
iter1 = basic_func->lines.map_.begin();
iter2 = fast_func->lines.map_.begin();
while (iter1 != basic_func->lines.map_.end()
&& iter2 != fast_func->lines.map_.end()) {
ASSERT_TRUE(iter1->first == iter2.GetKey());
ASSERT_TRUE(iter1->second.base() == iter2.GetValuePtr()->base());
ASSERT_TRUE(CompareLine(iter1->second.entry().get(),
iter2.GetValuePtr()->entryptr()));
++iter1;
++iter2;
}
ASSERT_TRUE(iter1 == basic_func->lines.map_.end());
ASSERT_TRUE(iter2 == fast_func->lines.map_.end());
delete fast_func;
return true;
}
bool ModuleComparer::CompareLine(const BasicLine *basic_line,
const FastLine *fast_line_raw) const {
FastLine *fast_line = new FastLine;
fast_line->CopyFrom(fast_line_raw);
ASSERT_TRUE(basic_line->address == fast_line->address);
ASSERT_TRUE(basic_line->size == fast_line->size);
ASSERT_TRUE(basic_line->source_file_id == fast_line->source_file_id);
ASSERT_TRUE(basic_line->line == fast_line->line);
delete fast_line;
return true;
}
bool ModuleComparer::ComparePubSymbol(const BasicPubSymbol* basic_ps,
const FastPubSymbol* fastps_raw) const {
FastPubSymbol *fast_ps = new FastPubSymbol;
fast_ps->CopyFrom(fastps_raw);
ASSERT_TRUE(basic_ps->name == fast_ps->name);
ASSERT_TRUE(basic_ps->address == fast_ps->address);
ASSERT_TRUE(basic_ps->parameter_size == fast_ps->parameter_size);
delete fast_ps;
return true;
}
bool ModuleComparer::CompareWFI(const WindowsFrameInfo& wfi1,
const WindowsFrameInfo& wfi2) const {
ASSERT_TRUE(wfi1.type_ == wfi2.type_);
ASSERT_TRUE(wfi1.valid == wfi2.valid);
ASSERT_TRUE(wfi1.prolog_size == wfi2.prolog_size);
ASSERT_TRUE(wfi1.epilog_size == wfi2.epilog_size);
ASSERT_TRUE(wfi1.parameter_size == wfi2.parameter_size);
ASSERT_TRUE(wfi1.saved_register_size == wfi2.saved_register_size);
ASSERT_TRUE(wfi1.local_size == wfi2.local_size);
ASSERT_TRUE(wfi1.max_stack_size == wfi2.max_stack_size);
ASSERT_TRUE(wfi1.allocates_base_pointer == wfi2.allocates_base_pointer);
ASSERT_TRUE(wfi1.program_string == wfi2.program_string);
return true;
}
// Compare ContainedRangeMap
bool ModuleComparer::CompareCRM(
const ContainedRangeMap<MemAddr, linked_ptr<WFI> >* basic_crm,
const StaticContainedRangeMap<MemAddr, char>* fast_crm) const {
ASSERT_TRUE(basic_crm->base_ == fast_crm->base_);
if (!basic_crm->entry_.get() || !fast_crm->entry_ptr_) {
// empty entry:
ASSERT_TRUE(!basic_crm->entry_.get() && !fast_crm->entry_ptr_);
} else {
WFI newwfi;
newwfi.CopyFrom(fast_resolver_->CopyWFI(fast_crm->entry_ptr_));
ASSERT_TRUE(CompareWFI(*(basic_crm->entry_.get()), newwfi));
}
if ((!basic_crm->map_ || basic_crm->map_->empty())
|| fast_crm->map_.empty()) {
ASSERT_TRUE((!basic_crm->map_ || basic_crm->map_->empty())
&& fast_crm->map_.empty());
} else {
ContainedRangeMap<MemAddr, linked_ptr<WFI> >::MapConstIterator iter1;
StaticContainedRangeMap<MemAddr, char>::MapConstIterator iter2;
iter1 = basic_crm->map_->begin();
iter2 = fast_crm->map_.begin();
while (iter1 != basic_crm->map_->end()
&& iter2 != fast_crm->map_.end()) {
ASSERT_TRUE(iter1->first == iter2.GetKey());
StaticContainedRangeMap<MemAddr, char>* child =
new StaticContainedRangeMap<MemAddr, char>(
reinterpret_cast<const char*>(iter2.GetValuePtr()));
ASSERT_TRUE(CompareCRM(iter1->second, child));
delete child;
++iter1;
++iter2;
}
ASSERT_TRUE(iter1 == basic_crm->map_->end());
ASSERT_TRUE(iter2 == fast_crm->map_.end());
}
return true;
}
} // namespace google_breakpad

View file

@ -0,0 +1,97 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// module_comparer.h: ModuleComparer reads a string format of symbol file, and
// loads the symbol into both BasicSourceLineResolver::Module and
// FastSourceLineResolve::Module. It then traverses both Modules and compare
// the content of data to verify the correctness of new fast module.
// ModuleCompare class is a tool to verify correctness of a loaded
// FastSourceLineResolver::Module instance, i.e., in-memory representation of
// parsed symbol. ModuleComparer class should be used for testing purpose only,
// e.g., in fast_source_line_resolver_unittest.
//
// Author: lambxsy@google.com (Siyang Xie)
#ifndef PROCESSOR_MODULE_COMPARER_H__
#define PROCESSOR_MODULE_COMPARER_H__
#include <string>
#include "processor/basic_source_line_resolver_types.h"
#include "processor/fast_source_line_resolver_types.h"
#include "processor/module_serializer.h"
#include "processor/windows_frame_info.h"
namespace google_breakpad {
class ModuleComparer {
public:
ModuleComparer(): fast_resolver_(new FastSourceLineResolver),
basic_resolver_(new BasicSourceLineResolver) { }
~ModuleComparer() {
delete fast_resolver_;
delete basic_resolver_;
}
// BasicSourceLineResolver loads its module using the symbol data,
// ModuleSerializer serialize the loaded module into a memory chunk,
// FastSourceLineResolver loads its module using the serialized memory chunk,
// Then, traverse both modules together and compare underlying data
// return true if both modules contain exactly same data.
bool Compare(const string& symbol_data);
private:
typedef BasicSourceLineResolver::Module BasicModule;
typedef FastSourceLineResolver::Module FastModule;
typedef BasicSourceLineResolver::Function BasicFunc;
typedef FastSourceLineResolver::Function FastFunc;
typedef BasicSourceLineResolver::Line BasicLine;
typedef FastSourceLineResolver::Line FastLine;
typedef BasicSourceLineResolver::PublicSymbol BasicPubSymbol;
typedef FastSourceLineResolver::PublicSymbol FastPubSymbol;
typedef WindowsFrameInfo WFI;
bool CompareModule(const BasicModule *oldmodule,
const FastModule *newmodule) const;
bool CompareFunction(const BasicFunc *oldfunc, const FastFunc *newfunc) const;
bool CompareLine(const BasicLine *oldline, const FastLine *newline) const;
bool ComparePubSymbol(const BasicPubSymbol*, const FastPubSymbol*) const;
bool CompareWFI(const WindowsFrameInfo&, const WindowsFrameInfo&) const;
// Compare ContainedRangeMap
bool CompareCRM(const ContainedRangeMap<MemAddr, linked_ptr<WFI> >*,
const StaticContainedRangeMap<MemAddr, char>*) const;
FastSourceLineResolver *fast_resolver_;
BasicSourceLineResolver *basic_resolver_;
ModuleSerializer serializer_;
};
} // namespace google_breakpad
#endif // PROCESSOR_MODULE_COMPARER_H__

View file

@ -0,0 +1,71 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// module_factory.h: ModuleFactory a factory that provides
// an interface for creating a Module and deferring instantiation to subclasses
// BasicModuleFactory and FastModuleFactory.
// Author: Siyang Xie (lambxsy@google.com)
#ifndef PROCESSOR_MODULE_FACTORY_H__
#define PROCESSOR_MODULE_FACTORY_H__
#include "processor/basic_source_line_resolver_types.h"
#include "processor/fast_source_line_resolver_types.h"
#include "processor/source_line_resolver_base_types.h"
namespace google_breakpad {
class ModuleFactory {
public:
virtual ~ModuleFactory() { };
virtual SourceLineResolverBase::Module* CreateModule(
const string& name) const = 0;
};
class BasicModuleFactory : public ModuleFactory {
public:
virtual ~BasicModuleFactory() { }
virtual BasicSourceLineResolver::Module* CreateModule(
const string& name) const {
return new BasicSourceLineResolver::Module(name);
}
};
class FastModuleFactory : public ModuleFactory {
public:
virtual ~FastModuleFactory() { }
virtual FastSourceLineResolver::Module* CreateModule(
const string& name) const {
return new FastSourceLineResolver::Module(name);
}
};
} // namespace google_breakpad
#endif // PROCESSOR_MODULE_FACTORY_H__

View file

@ -0,0 +1,219 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// module_serializer.cc: ModuleSerializer implementation.
//
// See module_serializer.h for documentation.
//
// Author: Siyang Xie (lambxsy@google.com)
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "processor/module_serializer.h"
#include <map>
#include <string>
#include "processor/basic_code_module.h"
#include "processor/logging.h"
namespace google_breakpad {
// Definition of static member variables in SimplerSerializer<Funcion> and
// SimplerSerializer<Inline>, which are declared in file
// "simple_serializer-inl.h"
RangeMapSerializer<MemAddr, linked_ptr<BasicSourceLineResolver::Line>>
SimpleSerializer<BasicSourceLineResolver::Function>::range_map_serializer_;
ContainedRangeMapSerializer<MemAddr,
linked_ptr<BasicSourceLineResolver::Inline>>
SimpleSerializer<
BasicSourceLineResolver::Function>::inline_range_map_serializer_;
size_t ModuleSerializer::SizeOf(const BasicSourceLineResolver::Module& module) {
size_t total_size_alloc_ = 0;
// Size of the "is_corrupt" flag.
total_size_alloc_ += SimpleSerializer<bool>::SizeOf(module.is_corrupt_);
// Compute memory size for each map component in Module class.
int map_index = 0;
map_sizes_[map_index++] = files_serializer_.SizeOf(module.files_);
map_sizes_[map_index++] = functions_serializer_.SizeOf(module.functions_);
map_sizes_[map_index++] = pubsym_serializer_.SizeOf(module.public_symbols_);
for (int i = 0; i < WindowsFrameInfo::STACK_INFO_LAST; ++i)
map_sizes_[map_index++] =
wfi_serializer_.SizeOf(&(module.windows_frame_info_[i]));
map_sizes_[map_index++] = cfi_init_rules_serializer_.SizeOf(
module.cfi_initial_rules_);
map_sizes_[map_index++] = cfi_delta_rules_serializer_.SizeOf(
module.cfi_delta_rules_);
map_sizes_[map_index++] =
inline_origin_serializer_.SizeOf(module.inline_origins_);
// Header size.
total_size_alloc_ += kNumberMaps_ * sizeof(uint32_t);
for (int i = 0; i < kNumberMaps_; ++i) {
total_size_alloc_ += map_sizes_[i];
}
// Extra one byte for null terminator for C-string copy safety.
total_size_alloc_ += SimpleSerializer<char>::SizeOf(0);
return total_size_alloc_;
}
char* ModuleSerializer::Write(const BasicSourceLineResolver::Module& module,
char* dest) {
// Write the is_corrupt flag.
dest = SimpleSerializer<bool>::Write(module.is_corrupt_, dest);
// Write header.
memcpy(dest, map_sizes_, kNumberMaps_ * sizeof(uint32_t));
dest += kNumberMaps_ * sizeof(uint32_t);
// Write each map.
dest = files_serializer_.Write(module.files_, dest);
dest = functions_serializer_.Write(module.functions_, dest);
dest = pubsym_serializer_.Write(module.public_symbols_, dest);
for (int i = 0; i < WindowsFrameInfo::STACK_INFO_LAST; ++i)
dest = wfi_serializer_.Write(&(module.windows_frame_info_[i]), dest);
dest = cfi_init_rules_serializer_.Write(module.cfi_initial_rules_, dest);
dest = cfi_delta_rules_serializer_.Write(module.cfi_delta_rules_, dest);
dest = inline_origin_serializer_.Write(module.inline_origins_, dest);
// Write a null terminator.
dest = SimpleSerializer<char>::Write(0, dest);
return dest;
}
char* ModuleSerializer::Serialize(const BasicSourceLineResolver::Module& module,
size_t* size) {
// Compute size of memory to allocate.
const size_t size_to_alloc = SizeOf(module);
// Allocate memory for serialized data.
char* serialized_data = new char[size_to_alloc];
if (!serialized_data) {
BPLOG(ERROR) << "ModuleSerializer: memory allocation failed, "
<< "size to alloc: " << size_to_alloc;
if (size) *size = 0;
return NULL;
}
// Write serialized data to allocated memory chunk.
char* end_address = Write(module, serialized_data);
// Verify the allocated memory size is equal to the size of data been written.
const size_t size_written =
static_cast<size_t>(end_address - serialized_data);
if (size_to_alloc != size_written) {
BPLOG(ERROR) << "size_to_alloc differs from size_written: "
<< size_to_alloc << " vs " << size_written;
}
// Set size and return the start address of memory chunk.
if (size)
*size = size_to_alloc;
return serialized_data;
}
bool ModuleSerializer::SerializeModuleAndLoadIntoFastResolver(
const BasicSourceLineResolver::ModuleMap::const_iterator& iter,
FastSourceLineResolver* fast_resolver) {
BPLOG(INFO) << "Converting symbol " << iter->first.c_str();
// Cast SourceLineResolverBase::Module* to BasicSourceLineResolver::Module*.
BasicSourceLineResolver::Module* basic_module =
dynamic_cast<BasicSourceLineResolver::Module*>(iter->second);
size_t size = 0;
scoped_array<char> symbol_data(Serialize(*basic_module, &size));
if (!symbol_data.get()) {
BPLOG(ERROR) << "Serialization failed for module: " << basic_module->name_;
return false;
}
BPLOG(INFO) << "Serialized Symbol Size " << size;
// Copy the data into string.
// Must pass string to LoadModuleUsingMapBuffer(), instead of passing char* to
// LoadModuleUsingMemoryBuffer(), becaused of data ownership/lifetime issue.
string symbol_data_string(symbol_data.get(), size);
symbol_data.reset();
scoped_ptr<CodeModule> code_module(
new BasicCodeModule(0, 0, iter->first, "", "", "", ""));
return fast_resolver->LoadModuleUsingMapBuffer(code_module.get(),
symbol_data_string);
}
void ModuleSerializer::ConvertAllModules(
const BasicSourceLineResolver* basic_resolver,
FastSourceLineResolver* fast_resolver) {
// Check for NULL pointer.
if (!basic_resolver || !fast_resolver)
return;
// Traverse module list in basic resolver.
BasicSourceLineResolver::ModuleMap::const_iterator iter;
iter = basic_resolver->modules_->begin();
for (; iter != basic_resolver->modules_->end(); ++iter)
SerializeModuleAndLoadIntoFastResolver(iter, fast_resolver);
}
bool ModuleSerializer::ConvertOneModule(
const string& moduleid,
const BasicSourceLineResolver* basic_resolver,
FastSourceLineResolver* fast_resolver) {
// Check for NULL pointer.
if (!basic_resolver || !fast_resolver)
return false;
BasicSourceLineResolver::ModuleMap::const_iterator iter;
iter = basic_resolver->modules_->find(moduleid);
if (iter == basic_resolver->modules_->end())
return false;
return SerializeModuleAndLoadIntoFastResolver(iter, fast_resolver);
}
char* ModuleSerializer::SerializeSymbolFileData(const string& symbol_data,
size_t* size) {
scoped_ptr<BasicSourceLineResolver::Module> module(
new BasicSourceLineResolver::Module("no name"));
scoped_array<char> buffer(new char[symbol_data.size() + 1]);
memcpy(buffer.get(), symbol_data.c_str(), symbol_data.size());
buffer.get()[symbol_data.size()] = '\0';
if (!module->LoadMapFromMemory(buffer.get(), symbol_data.size() + 1)) {
return NULL;
}
buffer.reset(NULL);
return Serialize(*(module.get()), size);
}
} // namespace google_breakpad

View file

@ -0,0 +1,128 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// module_serializer.h: ModuleSerializer serializes a loaded symbol,
// i.e., a loaded BasicSouceLineResolver::Module instance, into a memory
// chunk of data. The serialized data can be read and loaded by
// FastSourceLineResolver without CPU & memory-intensive parsing.
//
// Author: Siyang Xie (lambxsy@google.com)
#ifndef PROCESSOR_MODULE_SERIALIZER_H__
#define PROCESSOR_MODULE_SERIALIZER_H__
#include <map>
#include <string>
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "google_breakpad/processor/fast_source_line_resolver.h"
#include "processor/basic_source_line_resolver_types.h"
#include "processor/fast_source_line_resolver_types.h"
#include "processor/linked_ptr.h"
#include "processor/map_serializers-inl.h"
#include "processor/simple_serializer-inl.h"
#include "processor/windows_frame_info.h"
namespace google_breakpad {
// ModuleSerializer serializes a loaded BasicSourceLineResolver::Module into a
// chunk of memory data. ModuleSerializer also provides interface to compute
// memory size of the serialized data, write serialized data directly into
// memory, convert ASCII format symbol data into serialized binary data, and
// convert loaded BasicSourceLineResolver::Module into
// FastSourceLineResolver::Module.
class ModuleSerializer {
public:
// Compute the size of memory required to serialize a module. Return the
// total size needed for serialization.
size_t SizeOf(const BasicSourceLineResolver::Module& module);
// Write a module into an allocated memory chunk with required size.
// Return the "end" of data, i.e., the address after the final byte of data.
char* Write(const BasicSourceLineResolver::Module& module, char* dest);
// Serializes a loaded Module object into a chunk of memory data and returns
// the address of memory chunk. If size != NULL, *size is set to the memory
// size allocated for the serialized data.
// Caller takes the ownership of the memory chunk (allocated on heap), and
// owner should call delete [] to free the memory after use.
char* Serialize(const BasicSourceLineResolver::Module& module,
size_t* size = nullptr);
// Given the string format symbol_data, produces a chunk of serialized data.
// Caller takes ownership of the serialized data (on heap), and owner should
// call delete [] to free the memory after use.
char* SerializeSymbolFileData(const string& symbol_data,
size_t* size = nullptr);
// Serializes one loaded module with given moduleid in the basic source line
// resolver, and loads the serialized data into the fast source line resolver.
// Return false if the basic source line doesn't have a module with the given
// moduleid.
bool ConvertOneModule(const string& moduleid,
const BasicSourceLineResolver* basic_resolver,
FastSourceLineResolver* fast_resolver);
// Serializes all the loaded modules in a basic source line resolver, and
// loads the serialized data into a fast source line resolver.
void ConvertAllModules(const BasicSourceLineResolver* basic_resolver,
FastSourceLineResolver* fast_resolver);
private:
// Convenient type names.
typedef BasicSourceLineResolver::Line Line;
typedef BasicSourceLineResolver::Function Function;
typedef BasicSourceLineResolver::PublicSymbol PublicSymbol;
typedef BasicSourceLineResolver::InlineOrigin InlineOrigin;
// Internal implementation for ConvertOneModule and ConvertAllModules methods.
bool SerializeModuleAndLoadIntoFastResolver(
const BasicSourceLineResolver::ModuleMap::const_iterator& iter,
FastSourceLineResolver* fast_resolver);
// Number of Maps that Module class contains.
static const int32_t kNumberMaps_ =
FastSourceLineResolver::Module::kNumberMaps_;
// Memory sizes required to serialize map components in Module.
uint32_t map_sizes_[kNumberMaps_];
// Serializers for each individual map component in Module class.
StdMapSerializer<int, string> files_serializer_;
RangeMapSerializer<MemAddr, linked_ptr<Function> > functions_serializer_;
AddressMapSerializer<MemAddr, linked_ptr<PublicSymbol> > pubsym_serializer_;
ContainedRangeMapSerializer<MemAddr,
linked_ptr<WindowsFrameInfo> > wfi_serializer_;
RangeMapSerializer<MemAddr, string> cfi_init_rules_serializer_;
StdMapSerializer<MemAddr, string> cfi_delta_rules_serializer_;
StdMapSerializer<int, linked_ptr<InlineOrigin>> inline_origin_serializer_;
};
} // namespace google_breakpad
#endif // PROCESSOR_MODULE_SERIALIZER_H__

View file

@ -0,0 +1,59 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// pathname_stripper.cc: Manipulates pathnames into their component parts.
//
// See pathname_stripper.h for documentation.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "processor/pathname_stripper.h"
namespace google_breakpad {
// static
string PathnameStripper::File(const string& path) {
string::size_type slash = path.rfind('/');
string::size_type backslash = path.rfind('\\');
string::size_type file_start = 0;
if (slash != string::npos &&
(backslash == string::npos || slash > backslash)) {
file_start = slash + 1;
} else if (backslash != string::npos) {
file_start = backslash + 1;
}
return path.substr(file_start);
}
} // namespace google_breakpad

View file

@ -0,0 +1,52 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// pathname_stripper.h: Manipulates pathnames into their component parts.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_PATHNAME_STRIPPER_H__
#define PROCESSOR_PATHNAME_STRIPPER_H__
#include <string>
#include "common/using_std_string.h"
namespace google_breakpad {
class PathnameStripper {
public:
// Given path, a pathname with components separated by slashes (/) or
// backslashes (\), returns the trailing component, without any separator.
// If path ends in a separator character, returns an empty string.
static string File(const string& path);
};
} // namespace google_breakpad
#endif // PROCESSOR_PATHNAME_STRIPPER_H__

View file

@ -0,0 +1,90 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <stdio.h>
#include "processor/pathname_stripper.h"
#include "processor/logging.h"
#define ASSERT_TRUE(condition) \
if (!(condition)) { \
fprintf(stderr, "FAIL: %s @ %s:%d\n", #condition, __FILE__, __LINE__); \
return false; \
}
#define ASSERT_EQ(e1, e2) ASSERT_TRUE((e1) == (e2))
namespace {
using google_breakpad::PathnameStripper;
static bool RunTests() {
ASSERT_EQ(PathnameStripper::File("/dir/file"), "file");
ASSERT_EQ(PathnameStripper::File("\\dir\\file"), "file");
ASSERT_EQ(PathnameStripper::File("/dir\\file"), "file");
ASSERT_EQ(PathnameStripper::File("\\dir/file"), "file");
ASSERT_EQ(PathnameStripper::File("dir/file"), "file");
ASSERT_EQ(PathnameStripper::File("dir\\file"), "file");
ASSERT_EQ(PathnameStripper::File("dir/\\file"), "file");
ASSERT_EQ(PathnameStripper::File("dir\\/file"), "file");
ASSERT_EQ(PathnameStripper::File("file"), "file");
ASSERT_EQ(PathnameStripper::File("dir/"), "");
ASSERT_EQ(PathnameStripper::File("dir\\"), "");
ASSERT_EQ(PathnameStripper::File("dir/dir/"), "");
ASSERT_EQ(PathnameStripper::File("dir\\dir\\"), "");
ASSERT_EQ(PathnameStripper::File("dir1/dir2/file"), "file");
ASSERT_EQ(PathnameStripper::File("dir1\\dir2\\file"), "file");
ASSERT_EQ(PathnameStripper::File("dir1/dir2\\file"), "file");
ASSERT_EQ(PathnameStripper::File("dir1\\dir2/file"), "file");
ASSERT_EQ(PathnameStripper::File(""), "");
ASSERT_EQ(PathnameStripper::File("1"), "1");
ASSERT_EQ(PathnameStripper::File("1/2"), "2");
ASSERT_EQ(PathnameStripper::File("1\\2"), "2");
ASSERT_EQ(PathnameStripper::File("/1/2"), "2");
ASSERT_EQ(PathnameStripper::File("\\1\\2"), "2");
ASSERT_EQ(PathnameStripper::File("dir//file"), "file");
ASSERT_EQ(PathnameStripper::File("dir\\\\file"), "file");
ASSERT_EQ(PathnameStripper::File("/dir//file"), "file");
ASSERT_EQ(PathnameStripper::File("\\dir\\\\file"), "file");
ASSERT_EQ(PathnameStripper::File("c:\\dir\\file"), "file");
ASSERT_EQ(PathnameStripper::File("c:\\dir\\file.ext"), "file.ext");
return true;
}
} // namespace
int main(int argc, char** argv) {
BPLOG_INIT(&argc, &argv);
return RunTests() ? 0 : 1;
}

View file

@ -0,0 +1,362 @@
// -*- mode: c++ -*-
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// postfix_evaluator-inl.h: Postfix (reverse Polish) notation expression
// evaluator.
//
// Documentation in postfix_evaluator.h.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_POSTFIX_EVALUATOR_INL_H__
#define PROCESSOR_POSTFIX_EVALUATOR_INL_H__
#include "processor/postfix_evaluator.h"
#include <stdio.h>
#include <sstream>
#include "google_breakpad/processor/memory_region.h"
#include "processor/logging.h"
namespace google_breakpad {
using std::istringstream;
using std::ostringstream;
// A small class used in Evaluate to make sure to clean up the stack
// before returning failure.
class AutoStackClearer {
public:
explicit AutoStackClearer(vector<string>* stack) : stack_(stack) {}
~AutoStackClearer() { stack_->clear(); }
private:
vector<string>* stack_;
};
template<typename ValueType>
bool PostfixEvaluator<ValueType>::EvaluateToken(
const string& token,
const string& expression,
DictionaryValidityType* assigned) {
// There are enough binary operations that do exactly the same thing
// (other than the specific operation, of course) that it makes sense
// to share as much code as possible.
enum BinaryOperation {
BINARY_OP_NONE = 0,
BINARY_OP_ADD,
BINARY_OP_SUBTRACT,
BINARY_OP_MULTIPLY,
BINARY_OP_DIVIDE_QUOTIENT,
BINARY_OP_DIVIDE_MODULUS,
BINARY_OP_ALIGN
};
BinaryOperation operation = BINARY_OP_NONE;
if (token == "+")
operation = BINARY_OP_ADD;
else if (token == "-")
operation = BINARY_OP_SUBTRACT;
else if (token == "*")
operation = BINARY_OP_MULTIPLY;
else if (token == "/")
operation = BINARY_OP_DIVIDE_QUOTIENT;
else if (token == "%")
operation = BINARY_OP_DIVIDE_MODULUS;
else if (token == "@")
operation = BINARY_OP_ALIGN;
if (operation != BINARY_OP_NONE) {
// Get the operands.
ValueType operand1 = ValueType();
ValueType operand2 = ValueType();
if (!PopValues(&operand1, &operand2)) {
BPLOG(ERROR) << "Could not PopValues to get two values for binary "
"operation " << token << ": " << expression;
return false;
}
// Perform the operation.
ValueType result;
switch (operation) {
case BINARY_OP_ADD:
result = operand1 + operand2;
break;
case BINARY_OP_SUBTRACT:
result = operand1 - operand2;
break;
case BINARY_OP_MULTIPLY:
result = operand1 * operand2;
break;
case BINARY_OP_DIVIDE_QUOTIENT:
result = operand1 / operand2;
break;
case BINARY_OP_DIVIDE_MODULUS:
result = operand1 % operand2;
break;
case BINARY_OP_ALIGN:
result =
operand1 & (static_cast<ValueType>(-1) ^ (operand2 - 1));
break;
case BINARY_OP_NONE:
// This will not happen, but compilers will want a default or
// BINARY_OP_NONE case.
BPLOG(ERROR) << "Not reached!";
return false;
break;
}
// Save the result.
PushValue(result);
} else if (token == "^") {
// ^ for unary dereference. Can't dereference without memory.
if (!memory_) {
BPLOG(ERROR) << "Attempt to dereference without memory: " <<
expression;
return false;
}
ValueType address;
if (!PopValue(&address)) {
BPLOG(ERROR) << "Could not PopValue to get value to derefence: " <<
expression;
return false;
}
ValueType value;
if (!memory_->GetMemoryAtAddress(address, &value)) {
BPLOG(ERROR) << "Could not dereference memory at address " <<
HexString(address) << ": " << expression;
return false;
}
PushValue(value);
} else if (token == "=") {
// = for assignment.
ValueType value;
if (!PopValue(&value)) {
BPLOG(INFO) << "Could not PopValue to get value to assign: " <<
expression;
return false;
}
// Assignment is only meaningful when assigning into an identifier.
// The identifier must name a variable, not a constant. Variables
// begin with '$'.
string identifier;
if (PopValueOrIdentifier(NULL, &identifier) != POP_RESULT_IDENTIFIER) {
BPLOG(ERROR) << "PopValueOrIdentifier returned a value, but an "
"identifier is needed to assign " <<
HexString(value) << ": " << expression;
return false;
}
if (identifier.empty() || identifier[0] != '$') {
BPLOG(ERROR) << "Can't assign " << HexString(value) << " to " <<
identifier << ": " << expression;
return false;
}
(*dictionary_)[identifier] = value;
if (assigned)
(*assigned)[identifier] = true;
} else {
// The token is not an operator, it's a literal value or an identifier.
// Push it onto the stack as-is. Use push_back instead of PushValue
// because PushValue pushes ValueType as a string, but token is already
// a string.
stack_.push_back(token);
}
return true;
}
template<typename ValueType>
bool PostfixEvaluator<ValueType>::EvaluateInternal(
const string& expression,
DictionaryValidityType* assigned) {
// Tokenize, splitting on whitespace.
istringstream stream(expression);
string token;
while (stream >> token) {
// Normally, tokens are whitespace-separated, but occasionally, the
// assignment operator is smashed up against the next token, i.e.
// $T0 $ebp 128 + =$eip $T0 4 + ^ =$ebp $T0 ^ =
// This has been observed in program strings produced by MSVS 2010 in LTO
// mode.
if (token.size() > 1 && token[0] == '=') {
if (!EvaluateToken("=", expression, assigned)) {
return false;
}
if (!EvaluateToken(token.substr(1), expression, assigned)) {
return false;
}
} else if (!EvaluateToken(token, expression, assigned)) {
return false;
}
}
return true;
}
template<typename ValueType>
bool PostfixEvaluator<ValueType>::Evaluate(const string& expression,
DictionaryValidityType* assigned) {
// Ensure that the stack is cleared before returning.
AutoStackClearer clearer(&stack_);
if (!EvaluateInternal(expression, assigned))
return false;
// If there's anything left on the stack, it indicates incomplete execution.
// This is a failure case. If the stack is empty, evalution was complete
// and successful.
if (stack_.empty())
return true;
BPLOG(ERROR) << "Incomplete execution: " << expression;
return false;
}
template<typename ValueType>
bool PostfixEvaluator<ValueType>::EvaluateForValue(const string& expression,
ValueType* result) {
// Ensure that the stack is cleared before returning.
AutoStackClearer clearer(&stack_);
if (!EvaluateInternal(expression, NULL))
return false;
// A successful execution should leave exactly one value on the stack.
if (stack_.size() != 1) {
BPLOG(ERROR) << "Expression yielded bad number of results: "
<< "'" << expression << "'";
return false;
}
return PopValue(result);
}
template<typename ValueType>
typename PostfixEvaluator<ValueType>::PopResult
PostfixEvaluator<ValueType>::PopValueOrIdentifier(
ValueType* value, string* identifier) {
// There needs to be at least one element on the stack to pop.
if (!stack_.size())
return POP_RESULT_FAIL;
string token = stack_.back();
stack_.pop_back();
// First, try to treat the value as a literal. Literals may have leading
// '-' sign, and the entire remaining string must be parseable as
// ValueType. If this isn't possible, it can't be a literal, so treat it
// as an identifier instead.
//
// Some versions of the libstdc++, the GNU standard C++ library, have
// stream extractors for unsigned integer values that permit a leading
// '-' sign (6.0.13); others do not (6.0.9). Since we require it, we
// handle it explicitly here.
istringstream token_stream(token);
ValueType literal = ValueType();
bool negative;
if (token_stream.peek() == '-') {
negative = true;
token_stream.get();
} else {
negative = false;
}
if (token_stream >> literal && token_stream.peek() == EOF) {
if (value) {
*value = literal;
}
if (negative)
*value = -*value;
return POP_RESULT_VALUE;
} else {
if (identifier) {
*identifier = token;
}
return POP_RESULT_IDENTIFIER;
}
}
template<typename ValueType>
bool PostfixEvaluator<ValueType>::PopValue(ValueType* value) {
ValueType literal = ValueType();
string token;
PopResult result;
if ((result = PopValueOrIdentifier(&literal, &token)) == POP_RESULT_FAIL) {
return false;
} else if (result == POP_RESULT_VALUE) {
// This is the easy case.
*value = literal;
} else { // result == POP_RESULT_IDENTIFIER
// There was an identifier at the top of the stack. Resolve it to a
// value by looking it up in the dictionary.
typename DictionaryType::const_iterator iterator =
dictionary_->find(token);
if (iterator == dictionary_->end()) {
// The identifier wasn't found in the dictionary. Don't imply any
// default value, just fail.
BPLOG(INFO) << "Identifier " << token << " not in dictionary";
return false;
}
*value = iterator->second;
}
return true;
}
template<typename ValueType>
bool PostfixEvaluator<ValueType>::PopValues(ValueType* value1,
ValueType* value2) {
return PopValue(value2) && PopValue(value1);
}
template<typename ValueType>
void PostfixEvaluator<ValueType>::PushValue(const ValueType& value) {
ostringstream token_stream;
token_stream << value;
stack_.push_back(token_stream.str());
}
} // namespace google_breakpad
#endif // PROCESSOR_POSTFIX_EVALUATOR_INL_H__

View file

@ -0,0 +1,178 @@
// -*- mode: C++ -*-
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// postfix_evaluator.h: Postfix (reverse Polish) notation expression evaluator.
//
// PostfixEvaluator evaluates an expression, using the expression itself
// in postfix (reverse Polish) notation and a dictionary mapping constants
// and variables to their values. The evaluator supports standard
// arithmetic operations, assignment into variables, and when an optional
// MemoryRange is provided, dereferencing. (Any unary key-to-value operation
// may be used with a MemoryRange implementation that returns the appropriate
// values, but PostfixEvaluator was written with dereferencing in mind.)
//
// The expression language is simple. Expressions are supplied as strings,
// with operands and operators delimited by whitespace. Operands may be
// either literal values suitable for ValueType, or constants or variables,
// which reference the dictionary. The supported binary operators are +
// (addition), - (subtraction), * (multiplication), / (quotient of division),
// % (modulus of division), and @ (data alignment). The alignment operator (@)
// accepts a value and an alignment size, and produces a result that is a
// multiple of the alignment size by truncating the input value.
// The unary ^ (dereference) operator is also provided. These operators
// allow any operand to be either a literal value, constant, or variable.
// Assignment (=) of any type of operand into a variable is also supported.
//
// The dictionary is provided as a map with string keys. Keys beginning
// with the '$' character are treated as variables. All other keys are
// treated as constants. Any results must be assigned into variables in the
// dictionary. These variables do not need to exist prior to calling
// Evaluate, unless used in an expression prior to being assigned to. The
// internal stack state is not made available after evaluation, and any
// values remaining on the stack are treated as evidence of incomplete
// execution and cause the evaluator to indicate failure.
//
// PostfixEvaluator is intended to support evaluation of "program strings"
// obtained from MSVC frame data debugging information in pdb files as
// returned by the DIA APIs.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_POSTFIX_EVALUATOR_H__
#define PROCESSOR_POSTFIX_EVALUATOR_H__
#include <map>
#include <string>
#include <vector>
#include "common/using_std_string.h"
namespace google_breakpad {
using std::map;
using std::vector;
class MemoryRegion;
template<typename ValueType>
class PostfixEvaluator {
public:
typedef map<string, ValueType> DictionaryType;
typedef map<string, bool> DictionaryValidityType;
// Create a PostfixEvaluator object that may be used (with Evaluate) on
// one or more expressions. PostfixEvaluator does not take ownership of
// either argument. |memory| may be NULL, in which case dereferencing
// (^) will not be supported. |dictionary| may be NULL, but evaluation
// will fail in that case unless set_dictionary is used before calling
// Evaluate.
PostfixEvaluator(DictionaryType* dictionary, const MemoryRegion* memory)
: dictionary_(dictionary), memory_(memory), stack_() {}
// Evaluate the expression, starting with an empty stack. The results of
// execution will be stored in one (or more) variables in the dictionary.
// Returns false if any failures occur during execution, leaving
// variables in the dictionary in an indeterminate state. If assigned is
// non-NULL, any keys set in the dictionary as a result of evaluation
// will also be set to true in assigned, providing a way to determine if
// an expression modifies any of its input variables.
bool Evaluate(const string& expression, DictionaryValidityType* assigned);
// Like Evaluate, but provides the value left on the stack to the
// caller. If evaluation succeeds and leaves exactly one value on
// the stack, pop that value, store it in *result, and return true.
// Otherwise, return false.
bool EvaluateForValue(const string& expression, ValueType* result);
DictionaryType* dictionary() const { return dictionary_; }
// Reset the dictionary. PostfixEvaluator does not take ownership.
void set_dictionary(DictionaryType* dictionary) {dictionary_ = dictionary; }
private:
// Return values for PopValueOrIdentifier
enum PopResult {
POP_RESULT_FAIL = 0,
POP_RESULT_VALUE,
POP_RESULT_IDENTIFIER
};
// Retrieves the topmost literal value, constant, or variable from the
// stack. Returns POP_RESULT_VALUE if the topmost entry is a literal
// value, and sets |value| accordingly. Returns POP_RESULT_IDENTIFIER
// if the topmost entry is a constant or variable identifier, and sets
// |identifier| accordingly. Returns POP_RESULT_FAIL on failure, such
// as when the stack is empty.
PopResult PopValueOrIdentifier(ValueType* value, string* identifier);
// Retrieves the topmost value on the stack. If the topmost entry is
// an identifier, the dictionary is queried for the identifier's value.
// Returns false on failure, such as when the stack is empty or when
// a nonexistent identifier is named.
bool PopValue(ValueType* value);
// Retrieves the top two values on the stack, in the style of PopValue.
// value2 is popped before value1, so that value1 corresponds to the
// entry that was pushed prior to value2. Returns false on failure.
bool PopValues(ValueType* value1, ValueType* value2);
// Pushes a new value onto the stack.
void PushValue(const ValueType& value);
// Evaluate expression, updating *assigned if it is non-zero. Return
// true if evaluation completes successfully. Do not clear the stack
// upon successful evaluation.
bool EvaluateInternal(const string& expression,
DictionaryValidityType* assigned);
bool EvaluateToken(const string& token,
const string& expression,
DictionaryValidityType* assigned);
// The dictionary mapping constant and variable identifiers (strings) to
// values. Keys beginning with '$' are treated as variable names, and
// PostfixEvaluator is free to create and modify these keys. Weak pointer.
DictionaryType* dictionary_;
// If non-NULL, the MemoryRegion used for dereference (^) operations.
// If NULL, dereferencing is unsupported and will fail. Weak pointer.
const MemoryRegion* memory_;
// The stack contains state information as execution progresses. Values
// are pushed on to it as the expression string is read and as operations
// yield values; values are popped when used as operands to operators.
vector<string> stack_;
};
} // namespace google_breakpad
#endif // PROCESSOR_POSTFIX_EVALUATOR_H__

View file

@ -0,0 +1,406 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// postfix_evaluator_unittest.cc: Unit tests for PostfixEvaluator.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <assert.h>
#include <stdio.h>
#include <map>
#include <string>
#include "processor/postfix_evaluator-inl.h"
#include "common/using_std_string.h"
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/processor/memory_region.h"
#include "processor/logging.h"
namespace {
using std::map;
using google_breakpad::MemoryRegion;
using google_breakpad::PostfixEvaluator;
// FakeMemoryRegion is used to test PostfixEvaluator's dereference (^)
// operator. The result of dereferencing a value is one greater than
// the value.
class FakeMemoryRegion : public MemoryRegion {
public:
virtual uint64_t GetBase() const { return 0; }
virtual uint32_t GetSize() const { return 0; }
virtual bool GetMemoryAtAddress(uint64_t address, uint8_t* value) const {
*value = address + 1;
return true;
}
virtual bool GetMemoryAtAddress(uint64_t address, uint16_t* value) const {
*value = address + 1;
return true;
}
virtual bool GetMemoryAtAddress(uint64_t address, uint32_t* value) const {
*value = address + 1;
return true;
}
virtual bool GetMemoryAtAddress(uint64_t address, uint64_t* value) const {
*value = address + 1;
return true;
}
virtual void Print() const {
assert(false);
}
};
struct EvaluateTest {
// Expression passed to PostfixEvaluator::Evaluate.
const string expression;
// True if the expression is expected to be evaluable, false if evaluation
// is expected to fail.
bool evaluable;
};
struct EvaluateTestSet {
// The dictionary used for all tests in the set.
PostfixEvaluator<unsigned int>::DictionaryType* dictionary;
// The list of tests.
const EvaluateTest* evaluate_tests;
// The number of tests.
unsigned int evaluate_test_count;
// Identifiers and their expected values upon completion of the Evaluate
// tests in the set.
map<string, unsigned int>* validate_data;
};
struct EvaluateForValueTest {
// Expression passed to PostfixEvaluator::Evaluate.
const string expression;
// True if the expression is expected to be evaluable, false if evaluation
// is expected to fail.
bool evaluable;
// If evaluable, the value we expect it to yield.
unsigned int value;
};
static bool RunTests() {
// The first test set checks the basic operations and failure modes.
PostfixEvaluator<unsigned int>::DictionaryType dictionary_0;
const EvaluateTest evaluate_tests_0[] = {
{ "$rAdd 2 2 + =", true }, // $rAdd = 2 + 2 = 4
{ "$rAdd $rAdd 2 + =", true }, // $rAdd = $rAdd + 2 = 6
{ "$rAdd 2 $rAdd + =", true }, // $rAdd = 2 + $rAdd = 8
{ "99", false }, // put some junk on the stack...
{ "$rAdd2 2 2 + =", true }, // ...and make sure things still work
{ "$rAdd2\t2\n2 + =", true }, // same but with different whitespace
{ "$rAdd2 2 2 + = ", true }, // trailing whitespace
{ " $rAdd2 2 2 + =", true }, // leading whitespace
{ "$rAdd2 2 2 + =", true }, // extra whitespace
{ "$T0 2 = +", false }, // too few operands for add
{ "2 + =", false }, // too few operands for add
{ "2 +", false }, // too few operands for add
{ "+", false }, // too few operands for add
{ "^", false }, // too few operands for dereference
{ "=", false }, // too few operands for assignment
{ "2 =", false }, // too few operands for assignment
{ "2 2 + =", false }, // too few operands for assignment
{ "2 2 =", false }, // can't assign into a literal
{ "k 2 =", false }, // can't assign into a constant
{ "2", false }, // leftover data on stack
{ "2 2 +", false }, // leftover data on stack
{ "$rAdd", false }, // leftover data on stack
{ "0 $T1 0 0 + =", false }, // leftover data on stack
{ "$T2 $T2 2 + =", false }, // can't operate on an undefined value
{ "$rMul 9 6 * =", true }, // $rMul = 9 * 6 = 54
{ "$rSub 9 6 - =", true }, // $rSub = 9 - 6 = 3
{ "$rDivQ 9 6 / =", true }, // $rDivQ = 9 / 6 = 1
{ "$rDivM 9 6 % =", true }, // $rDivM = 9 % 6 = 3
{ "$rDeref 9 ^ =", true }, // $rDeref = ^9 = 10 (FakeMemoryRegion)
{ "$rAlign 36 8 @ =", true }, // $rAlign = 36 @ 8
{ "$rAdd3 2 2 + =$rMul2 9 6 * =", true } // smashed-equals tokenization
};
map<string, unsigned int> validate_data_0;
validate_data_0["$rAdd"] = 8;
validate_data_0["$rAdd2"] = 4;
validate_data_0["$rSub"] = 3;
validate_data_0["$rMul"] = 54;
validate_data_0["$rDivQ"] = 1;
validate_data_0["$rDivM"] = 3;
validate_data_0["$rDeref"] = 10;
validate_data_0["$rAlign"] = 32;
validate_data_0["$rAdd3"] = 4;
validate_data_0["$rMul2"] = 54;
// The second test set simulates a couple of MSVC program strings.
// The data is fudged a little bit because the tests use FakeMemoryRegion
// instead of a real stack snapshot, but the program strings are real and
// the implementation doesn't know or care that the data is not real.
PostfixEvaluator<unsigned int>::DictionaryType dictionary_1;
dictionary_1["$ebp"] = 0xbfff0010;
dictionary_1["$eip"] = 0x10000000;
dictionary_1["$esp"] = 0xbfff0000;
dictionary_1[".cbSavedRegs"] = 4;
dictionary_1[".cbParams"] = 4;
dictionary_1[".raSearchStart"] = 0xbfff0020;
const EvaluateTest evaluate_tests_1[] = {
{ "$T0 $ebp = $eip $T0 4 + ^ = $ebp $T0 ^ = $esp $T0 8 + = "
"$L $T0 .cbSavedRegs - = $P $T0 8 + .cbParams + =", true },
// Intermediate state: $T0 = 0xbfff0010, $eip = 0xbfff0015,
// $ebp = 0xbfff0011, $esp = 0xbfff0018,
// $L = 0xbfff000c, $P = 0xbfff001c
{ "$T0 $ebp = $eip $T0 4 + ^ = $ebp $T0 ^ = $esp $T0 8 + = "
"$L $T0 .cbSavedRegs - = $P $T0 8 + .cbParams + = $ebx $T0 28 - ^ =",
true },
// Intermediate state: $T0 = 0xbfff0011, $eip = 0xbfff0016,
// $ebp = 0xbfff0012, $esp = 0xbfff0019,
// $L = 0xbfff000d, $P = 0xbfff001d,
// $ebx = 0xbffefff6
{ "$T0 $ebp = $T2 $esp = $T1 .raSearchStart = $eip $T1 ^ = $ebp $T0 = "
"$esp $T1 4 + = $L $T0 .cbSavedRegs - = $P $T1 4 + .cbParams + = "
"$ebx $T0 28 - ^ =",
true }
};
map<string, unsigned int> validate_data_1;
validate_data_1["$T0"] = 0xbfff0012;
validate_data_1["$T1"] = 0xbfff0020;
validate_data_1["$T2"] = 0xbfff0019;
validate_data_1["$eip"] = 0xbfff0021;
validate_data_1["$ebp"] = 0xbfff0012;
validate_data_1["$esp"] = 0xbfff0024;
validate_data_1["$L"] = 0xbfff000e;
validate_data_1["$P"] = 0xbfff0028;
validate_data_1["$ebx"] = 0xbffefff7;
validate_data_1[".cbSavedRegs"] = 4;
validate_data_1[".cbParams"] = 4;
EvaluateTestSet evaluate_test_sets[] = {
{ &dictionary_0, evaluate_tests_0,
sizeof(evaluate_tests_0) / sizeof(EvaluateTest), &validate_data_0 },
{ &dictionary_1, evaluate_tests_1,
sizeof(evaluate_tests_1) / sizeof(EvaluateTest), &validate_data_1 },
};
unsigned int evaluate_test_set_count = sizeof(evaluate_test_sets) /
sizeof(EvaluateTestSet);
FakeMemoryRegion fake_memory;
PostfixEvaluator<unsigned int> postfix_evaluator =
PostfixEvaluator<unsigned int>(NULL, &fake_memory);
for (unsigned int evaluate_test_set_index = 0;
evaluate_test_set_index < evaluate_test_set_count;
++evaluate_test_set_index) {
EvaluateTestSet* evaluate_test_set =
&evaluate_test_sets[evaluate_test_set_index];
const EvaluateTest* evaluate_tests = evaluate_test_set->evaluate_tests;
unsigned int evaluate_test_count = evaluate_test_set->evaluate_test_count;
// The same dictionary will be used for each test in the set. Earlier
// tests can affect the state of the dictionary for later tests.
postfix_evaluator.set_dictionary(evaluate_test_set->dictionary);
// Use a new validity dictionary for each test set.
PostfixEvaluator<unsigned int>::DictionaryValidityType assigned;
for (unsigned int evaluate_test_index = 0;
evaluate_test_index < evaluate_test_count;
++evaluate_test_index) {
const EvaluateTest* evaluate_test = &evaluate_tests[evaluate_test_index];
// Do the test.
bool result = postfix_evaluator.Evaluate(evaluate_test->expression,
&assigned);
if (result != evaluate_test->evaluable) {
fprintf(stderr, "FAIL: evaluate set %d/%d, test %d/%d, "
"expression \"%s\", expected %s, observed %s\n",
evaluate_test_set_index, evaluate_test_set_count,
evaluate_test_index, evaluate_test_count,
evaluate_test->expression.c_str(),
evaluate_test->evaluable ? "evaluable" : "not evaluable",
result ? "evaluted" : "not evaluated");
return false;
}
}
// Validate the results.
for (map<string, unsigned int>::const_iterator validate_iterator =
evaluate_test_set->validate_data->begin();
validate_iterator != evaluate_test_set->validate_data->end();
++validate_iterator) {
const string identifier = validate_iterator->first;
unsigned int expected_value = validate_iterator->second;
map<string, unsigned int>::const_iterator dictionary_iterator =
evaluate_test_set->dictionary->find(identifier);
// The identifier must exist in the dictionary.
if (dictionary_iterator == evaluate_test_set->dictionary->end()) {
fprintf(stderr, "FAIL: evaluate test set %d/%d, "
"validate identifier \"%s\", "
"expected %d, observed not found\n",
evaluate_test_set_index, evaluate_test_set_count,
identifier.c_str(), expected_value);
return false;
}
// The value in the dictionary must be the same as the expected value.
unsigned int observed_value = dictionary_iterator->second;
if (expected_value != observed_value) {
fprintf(stderr, "FAIL: evaluate test set %d/%d, "
"validate identifier \"%s\", "
"expected %d, observed %d\n",
evaluate_test_set_index, evaluate_test_set_count,
identifier.c_str(), expected_value, observed_value);
return false;
}
// The value must be set in the "assigned" dictionary if it was a
// variable. It must not have been assigned if it was a constant.
bool expected_assigned = identifier[0] == '$';
bool observed_assigned = false;
PostfixEvaluator<unsigned int>::DictionaryValidityType::const_iterator
iterator_assigned = assigned.find(identifier);
if (iterator_assigned != assigned.end()) {
observed_assigned = iterator_assigned->second;
}
if (expected_assigned != observed_assigned) {
fprintf(stderr, "FAIL: evaluate test set %d/%d, "
"validate assignment of \"%s\", "
"expected %d, observed %d\n",
evaluate_test_set_index, evaluate_test_set_count,
identifier.c_str(), expected_assigned, observed_assigned);
return false;
}
}
}
// EvaluateForValue tests.
PostfixEvaluator<unsigned int>::DictionaryType dictionary_2;
dictionary_2["$ebp"] = 0xbfff0010;
dictionary_2["$eip"] = 0x10000000;
dictionary_2["$esp"] = 0xbfff0000;
dictionary_2[".cbSavedRegs"] = 4;
dictionary_2[".cbParams"] = 4;
dictionary_2[".raSearchStart"] = 0xbfff0020;
const EvaluateForValueTest evaluate_for_value_tests_2[] = {
{ "28907223", true, 28907223 }, // simple constant
{ "89854293 40010015 +", true, 89854293 + 40010015 }, // arithmetic
{ "-870245 8769343 +", true, 7899098 }, // negative constants
{ "$ebp $esp - $eip +", true, 0x10000010 }, // variable references
{ "18929794 34015074", false, 0 }, // too many values
{ "$ebp $ebp 4 - =", false, 0 }, // too few values
{ "$new $eip = $new", true, 0x10000000 }, // make new variable
{ "$new 4 +", true, 0x10000004 }, // see prior assignments
{ ".cfa 42 = 10", false, 0 } // can't set constants
};
const int evaluate_for_value_tests_2_size
= (sizeof (evaluate_for_value_tests_2)
/ sizeof (evaluate_for_value_tests_2[0]));
map<string, unsigned int> validate_data_2;
validate_data_2["$eip"] = 0x10000000;
validate_data_2["$ebp"] = 0xbfff000c;
validate_data_2["$esp"] = 0xbfff0000;
validate_data_2["$new"] = 0x10000000;
validate_data_2[".cbSavedRegs"] = 4;
validate_data_2[".cbParams"] = 4;
validate_data_2[".raSearchStart"] = 0xbfff0020;
postfix_evaluator.set_dictionary(&dictionary_2);
for (int i = 0; i < evaluate_for_value_tests_2_size; i++) {
const EvaluateForValueTest* test = &evaluate_for_value_tests_2[i];
unsigned int result;
if (postfix_evaluator.EvaluateForValue(test->expression, &result)
!= test->evaluable) {
fprintf(stderr, "FAIL: evaluate for value test %d, "
"expected evaluation to %s, but it %s\n",
i, test->evaluable ? "succeed" : "fail",
test->evaluable ? "failed" : "succeeded");
return false;
}
if (test->evaluable && result != test->value) {
fprintf(stderr, "FAIL: evaluate for value test %d, "
"expected value to be 0x%x, but it was 0x%x\n",
i, test->value, result);
return false;
}
}
for (map<string, unsigned int>::iterator v = validate_data_2.begin();
v != validate_data_2.end(); v++) {
map<string, unsigned int>::iterator a = dictionary_2.find(v->first);
if (a == dictionary_2.end()) {
fprintf(stderr, "FAIL: evaluate for value dictionary check: "
"expected dict[\"%s\"] to be 0x%x, but it was unset\n",
v->first.c_str(), v->second);
return false;
} else if (a->second != v->second) {
fprintf(stderr, "FAIL: evaluate for value dictionary check: "
"expected dict[\"%s\"] to be 0x%x, but it was 0x%x\n",
v->first.c_str(), v->second, a->second);
return false;
}
dictionary_2.erase(a);
}
map<string, unsigned int>::iterator remaining = dictionary_2.begin();
if (remaining != dictionary_2.end()) {
fprintf(stderr, "FAIL: evaluation of test expressions put unexpected "
"values in dictionary:\n");
for (; remaining != dictionary_2.end(); remaining++)
fprintf(stderr, " dict[\"%s\"] == 0x%x\n",
remaining->first.c_str(), remaining->second);
return false;
}
return true;
}
} // namespace
int main(int argc, char** argv) {
BPLOG_INIT(&argc, &argv);
return RunTests() ? 0 : 1;
}

View file

@ -0,0 +1,110 @@
// Copyright 2013 Google LLC
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/proc_maps_linux.h"
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include "common/using_std_string.h"
#include "processor/logging.h"
#if defined(OS_ANDROID) && !defined(__LP64__)
// In 32-bit mode, Bionic's inttypes.h defines PRI/SCNxPTR as an
// unsigned long int, which is incompatible with Bionic's stdint.h
// defining uintptr_t as an unsigned int:
// https://code.google.com/p/android/issues/detail?id=57218
#undef SCNxPTR
#define SCNxPTR "x"
#endif
namespace google_breakpad {
bool ParseProcMaps(const string& input,
std::vector<MappedMemoryRegion>* regions_out) {
std::vector<MappedMemoryRegion> regions;
// This isn't async safe nor terribly efficient, but it doesn't need to be at
// this point in time.
// Split the string by newlines.
std::vector<string> lines;
string l = "";
for (size_t i = 0; i < input.size(); i++) {
if (input[i] != '\n' && input[i] != '\r') {
l.push_back(input[i]);
} else if (l.size() > 0) {
lines.push_back(l);
l.clear();
}
}
if (l.size() > 0) {
BPLOG(ERROR) << "Input doesn't end in newline";
return false;
}
for (size_t i = 0; i < lines.size(); ++i) {
MappedMemoryRegion region;
const char* line = lines[i].c_str();
char permissions[5] = {'\0'}; // Ensure NUL-terminated string.
int path_index = 0;
// Sample format from man 5 proc:
//
// address perms offset dev inode pathname
// 08048000-08056000 r-xp 00000000 03:0c 64593 /usr/sbin/gpm
//
// The final %n term captures the offset in the input string, which is used
// to determine the path name. It *does not* increment the return value.
// Refer to man 3 sscanf for details.
if (sscanf(line, "%" SCNx64 "-%" SCNx64 " %4c %" SCNx64" %hhx:%hhx %"
SCNd64 " %n", &region.start, &region.end, permissions,
&region.offset, &region.major_device, &region.minor_device,
&region.inode, &path_index) < 7) {
BPLOG(ERROR) << "sscanf failed for line: " << line;
return false;
}
region.permissions = 0;
if (permissions[0] == 'r')
region.permissions |= MappedMemoryRegion::READ;
else if (permissions[0] != '-')
return false;
if (permissions[1] == 'w')
region.permissions |= MappedMemoryRegion::WRITE;
else if (permissions[1] != '-')
return false;
if (permissions[2] == 'x')
region.permissions |= MappedMemoryRegion::EXECUTE;
else if (permissions[2] != '-')
return false;
if (permissions[3] == 'p')
region.permissions |= MappedMemoryRegion::PRIVATE;
else if (permissions[3] != 's' && permissions[3] != 'S') // Shared memory.
return false;
// Pushing then assigning saves us a string copy.
regions.push_back(region);
regions.back().path.assign(line + path_index);
regions.back().line.assign(line);
}
regions_out->swap(regions);
return true;
}
} // namespace google_breakpad

View file

@ -0,0 +1,255 @@
// Copyright 2013 Google LLC
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "breakpad_googletest_includes.h"
#include "common/using_std_string.h"
#include "google_breakpad/processor/proc_maps_linux.h"
namespace {
TEST(ProcMapsTest, Empty) {
std::vector<google_breakpad::MappedMemoryRegion> regions;
EXPECT_TRUE(ParseProcMaps("", &regions));
EXPECT_EQ(0u, regions.size());
}
TEST(ProcMapsTest, NoSpaces) {
static const char kNoSpaces[] =
"00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/cat\n";
std::vector<google_breakpad::MappedMemoryRegion> regions;
ASSERT_TRUE(ParseProcMaps(kNoSpaces, &regions));
ASSERT_EQ(1u, regions.size());
EXPECT_EQ(0x00400000u, regions[0].start);
EXPECT_EQ(0x0040b000u, regions[0].end);
EXPECT_EQ(0x00002200u, regions[0].offset);
EXPECT_EQ("/bin/cat", regions[0].path);
}
TEST(ProcMapsTest, Spaces) {
static const char kSpaces[] =
"00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/space cat\n";
std::vector<google_breakpad::MappedMemoryRegion> regions;
ASSERT_TRUE(ParseProcMaps(kSpaces, &regions));
ASSERT_EQ(1u, regions.size());
EXPECT_EQ(0x00400000u, regions[0].start);
EXPECT_EQ(0x0040b000u, regions[0].end);
EXPECT_EQ(0x00002200u, regions[0].offset);
EXPECT_EQ("/bin/space cat", regions[0].path);
}
TEST(ProcMapsTest, NoNewline) {
static const char kNoSpaces[] =
"00400000-0040b000 r-xp 00002200 fc:00 794418 /bin/cat";
std::vector<google_breakpad::MappedMemoryRegion> regions;
ASSERT_FALSE(ParseProcMaps(kNoSpaces, &regions));
}
TEST(ProcMapsTest, NoPath) {
static const char kNoPath[] =
"00400000-0040b000 rw-p 00000000 00:00 0 \n";
std::vector<google_breakpad::MappedMemoryRegion> regions;
ASSERT_TRUE(ParseProcMaps(kNoPath, &regions));
ASSERT_EQ(1u, regions.size());
EXPECT_EQ(0x00400000u, regions[0].start);
EXPECT_EQ(0x0040b000u, regions[0].end);
EXPECT_EQ(0x00000000u, regions[0].offset);
EXPECT_EQ("", regions[0].path);
}
TEST(ProcMapsTest, Heap) {
static const char kHeap[] =
"022ac000-022cd000 rw-p 00000000 00:00 0 [heap]\n";
std::vector<google_breakpad::MappedMemoryRegion> regions;
ASSERT_TRUE(ParseProcMaps(kHeap, &regions));
ASSERT_EQ(1u, regions.size());
EXPECT_EQ(0x022ac000u, regions[0].start);
EXPECT_EQ(0x022cd000u, regions[0].end);
EXPECT_EQ(0x00000000u, regions[0].offset);
EXPECT_EQ("[heap]", regions[0].path);
}
#if defined(ARCH_CPU_32_BITS)
TEST(ProcMapsTest, Stack32) {
static const char kStack[] =
"beb04000-beb25000 rw-p 00000000 00:00 0 [stack]\n";
std::vector<google_breakpad::MappedMemoryRegion> regions;
ASSERT_TRUE(ParseProcMaps(kStack, &regions));
ASSERT_EQ(1u, regions.size());
EXPECT_EQ(0xbeb04000u, regions[0].start);
EXPECT_EQ(0xbeb25000u, regions[0].end);
EXPECT_EQ(0x00000000u, regions[0].offset);
EXPECT_EQ("[stack]", regions[0].path);
}
#elif defined(ARCH_CPU_64_BITS)
TEST(ProcMapsTest, Stack64) {
static const char kStack[] =
"7fff69c5b000-7fff69c7d000 rw-p 00000000 00:00 0 [stack]\n";
std::vector<google_breakpad::MappedMemoryRegion> regions;
ASSERT_TRUE(ParseProcMaps(kStack, &regions));
ASSERT_EQ(1u, regions.size());
EXPECT_EQ(0x7fff69c5b000u, regions[0].start);
EXPECT_EQ(0x7fff69c7d000u, regions[0].end);
EXPECT_EQ(0x00000000u, regions[0].offset);
EXPECT_EQ("[stack]", regions[0].path);
}
#endif
TEST(ProcMapsTest, Multiple) {
static const char kMultiple[] =
"00400000-0040b000 r-xp 00000000 fc:00 794418 /bin/cat\n"
"0060a000-0060b000 r--p 0000a000 fc:00 794418 /bin/cat\n"
"0060b000-0060c000 rw-p 0000b000 fc:00 794418 /bin/cat\n";
std::vector<google_breakpad::MappedMemoryRegion> regions;
ASSERT_TRUE(ParseProcMaps(kMultiple, &regions));
ASSERT_EQ(3u, regions.size());
EXPECT_EQ(0x00400000u, regions[0].start);
EXPECT_EQ(0x0040b000u, regions[0].end);
EXPECT_EQ(0x00000000u, regions[0].offset);
EXPECT_EQ("/bin/cat", regions[0].path);
EXPECT_EQ(0x0060a000u, regions[1].start);
EXPECT_EQ(0x0060b000u, regions[1].end);
EXPECT_EQ(0x0000a000u, regions[1].offset);
EXPECT_EQ("/bin/cat", regions[1].path);
EXPECT_EQ(0x0060b000u, regions[2].start);
EXPECT_EQ(0x0060c000u, regions[2].end);
EXPECT_EQ(0x0000b000u, regions[2].offset);
EXPECT_EQ("/bin/cat", regions[2].path);
}
TEST(ProcMapsTest, Permissions) {
static struct {
const char* input;
uint8_t permissions;
} kTestCases[] = {
{"00400000-0040b000 ---s 00000000 fc:00 794418 /bin/cat\n", 0},
{"00400000-0040b000 ---S 00000000 fc:00 794418 /bin/cat\n", 0},
{"00400000-0040b000 r--s 00000000 fc:00 794418 /bin/cat\n",
google_breakpad::MappedMemoryRegion::READ},
{"00400000-0040b000 -w-s 00000000 fc:00 794418 /bin/cat\n",
google_breakpad::MappedMemoryRegion::WRITE},
{"00400000-0040b000 --xs 00000000 fc:00 794418 /bin/cat\n",
google_breakpad::MappedMemoryRegion::EXECUTE},
{"00400000-0040b000 rwxs 00000000 fc:00 794418 /bin/cat\n",
google_breakpad::MappedMemoryRegion::READ
| google_breakpad::MappedMemoryRegion::WRITE
| google_breakpad::MappedMemoryRegion::EXECUTE},
{"00400000-0040b000 ---p 00000000 fc:00 794418 /bin/cat\n",
google_breakpad::MappedMemoryRegion::PRIVATE},
{"00400000-0040b000 r--p 00000000 fc:00 794418 /bin/cat\n",
google_breakpad::MappedMemoryRegion::READ
| google_breakpad::MappedMemoryRegion::PRIVATE},
{"00400000-0040b000 -w-p 00000000 fc:00 794418 /bin/cat\n",
google_breakpad::MappedMemoryRegion::WRITE
| google_breakpad::MappedMemoryRegion::PRIVATE},
{"00400000-0040b000 --xp 00000000 fc:00 794418 /bin/cat\n",
google_breakpad::MappedMemoryRegion::EXECUTE
| google_breakpad::MappedMemoryRegion::PRIVATE},
{"00400000-0040b000 rwxp 00000000 fc:00 794418 /bin/cat\n",
google_breakpad::MappedMemoryRegion::READ
| google_breakpad::MappedMemoryRegion::WRITE
| google_breakpad::MappedMemoryRegion::EXECUTE
| google_breakpad::MappedMemoryRegion::PRIVATE},
};
for (size_t i = 0; i < sizeof(kTestCases) / sizeof(kTestCases[0]); ++i) {
std::vector<google_breakpad::MappedMemoryRegion> regions;
EXPECT_TRUE(ParseProcMaps(kTestCases[i].input, &regions));
EXPECT_EQ(1u, regions.size());
if (regions.empty())
continue;
EXPECT_EQ(kTestCases[i].permissions, regions[0].permissions);
}
}
TEST(ProcMapsTest, MissingFields) {
static const char* kTestCases[] = {
"00400000\n", // Missing end + beyond.
"00400000-0040b000\n", // Missing perms + beyond.
"00400000-0040b000 r-xp\n", // Missing offset + beyond.
"00400000-0040b000 r-xp 00000000\n", // Missing device + beyond.
"00400000-0040b000 r-xp 00000000 fc:00\n", // Missing inode + beyond.
"00400000-0040b000 00000000 fc:00 794418 /bin/cat\n", // Missing perms.
"00400000-0040b000 r-xp fc:00 794418 /bin/cat\n", // Missing offset.
"00400000-0040b000 r-xp 00000000 fc:00 /bin/cat\n", // Missing inode.
"00400000 r-xp 00000000 fc:00 794418 /bin/cat\n", // Missing end.
"-0040b000 r-xp 00000000 fc:00 794418 /bin/cat\n", // Missing start.
"00400000-0040b000 r-xp 00000000 794418 /bin/cat\n", // Missing device.
};
for (size_t i = 0; i < sizeof(kTestCases) / sizeof(kTestCases[0]); ++i) {
std::vector<google_breakpad::MappedMemoryRegion> regions;
EXPECT_FALSE(ParseProcMaps(kTestCases[i], &regions));
}
}
TEST(ProcMapsTest, InvalidInput) {
static const char* kTestCases[] = {
"thisisal-0040b000 rwxp 00000000 fc:00 794418 /bin/cat\n",
"0040000d-linvalid rwxp 00000000 fc:00 794418 /bin/cat\n",
"00400000-0040b000 inpu 00000000 fc:00 794418 /bin/cat\n",
"00400000-0040b000 rwxp tforproc fc:00 794418 /bin/cat\n",
"00400000-0040b000 rwxp 00000000 ma:ps 794418 /bin/cat\n",
"00400000-0040b000 rwxp 00000000 fc:00 parse! /bin/cat\n",
};
for (size_t i = 0; i < sizeof(kTestCases) / sizeof(kTestCases[0]); ++i) {
std::vector<google_breakpad::MappedMemoryRegion> regions;
EXPECT_FALSE(ParseProcMaps(kTestCases[i], &regions));
}
}
TEST(ProcMapsTest, ParseProcMapsEmptyString) {
std::vector<google_breakpad::MappedMemoryRegion> regions;
EXPECT_TRUE(ParseProcMaps("", &regions));
EXPECT_EQ(0ULL, regions.size());
}
// Testing a couple of remotely possible weird things in the input:
// - Line ending with \r\n or \n\r.
// - File name contains quotes.
// - File name has whitespaces.
TEST(ProcMapsTest, ParseProcMapsWeirdCorrectInput) {
std::vector<google_breakpad::MappedMemoryRegion> regions;
const string kContents =
"00400000-0040b000 r-xp 00000000 fc:00 2106562 "
" /bin/cat\r\n"
"7f53b7dad000-7f53b7f62000 r-xp 00000000 fc:00 263011 "
" /lib/x86_64-linux-gnu/libc-2.15.so\n\r"
"7f53b816d000-7f53b818f000 r-xp 00000000 fc:00 264284 "
" /lib/x86_64-linux-gnu/ld-2.15.so\n"
"7fff9c7ff000-7fff9c800000 r-xp 00000000 00:00 0 "
" \"vd so\"\n"
"ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 "
" [vsys call]\n";
EXPECT_TRUE(ParseProcMaps(kContents, &regions));
EXPECT_EQ(5ULL, regions.size());
EXPECT_EQ("/bin/cat", regions[0].path);
EXPECT_EQ("/lib/x86_64-linux-gnu/libc-2.15.so", regions[1].path);
EXPECT_EQ("/lib/x86_64-linux-gnu/ld-2.15.so", regions[2].path);
EXPECT_EQ("\"vd so\"", regions[3].path);
EXPECT_EQ("[vsys call]", regions[4].path);
}
} // namespace

View file

@ -0,0 +1,75 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// process_state.cc: A snapshot of a process, in a fully-digested state.
//
// See process_state.h for documentation.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/process_state.h"
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/code_modules.h"
namespace google_breakpad {
ProcessState::~ProcessState() {
Clear();
}
void ProcessState::Clear() {
time_date_stamp_ = 0;
process_create_time_ = 0;
crashed_ = false;
crash_reason_.clear();
crash_address_ = 0;
assertion_.clear();
requesting_thread_ = -1;
for (vector<CallStack*>::const_iterator iterator = threads_.begin();
iterator != threads_.end();
++iterator) {
delete *iterator;
}
threads_.clear();
system_info_.Clear();
thread_names_.clear();
// modules_without_symbols_ and modules_with_corrupt_symbols_ DO NOT own
// the underlying CodeModule pointers. Just clear the vectors.
modules_without_symbols_.clear();
modules_with_corrupt_symbols_.clear();
delete modules_;
modules_ = NULL;
delete unloaded_modules_;
unloaded_modules_ = NULL;
}
} // namespace google_breakpad

View file

@ -0,0 +1,20 @@
If you wish to use these protobufs, you must generate their source files
using protoc from the protobuf project (https://github.com/google/protobuf).
-----
Troubleshooting for Protobuf:
Install:
If you are getting permission errors install, make sure you are not trying to
install from an NFS.
Running protoc:
protoc: error while loading shared libraries: libprotobuf.so.0: cannot open
shared object file: No such file or directory
The issue is that Ubuntu 8.04 doesn't include /usr/local/lib in
library paths.
To fix it for your current terminal session, just type in
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib

View file

@ -0,0 +1,209 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// process_state_proto.proto: A client proto representation of a process,
// in a fully-digested state.
//
// Derived from earlier struct and class based models of a client-side
// processed minidump found under src/google_breakpad/processor. The
// file process_state.h holds the top level representation of this model,
// supported by additional classes. We've added a proto representation
// to ease serialization and parsing for server-side storage of crash
// reports processed on the client.
//
// Author: Jess Gray
syntax = "proto2";
package google_breakpad;
// A proto representation of a process, in a fully-digested state.
// See src/google_breakpad/processor/process_state.h
message ProcessStateProto {
// Next value: 14
// The time-date stamp of the original minidump (time_t format)
optional int64 time_date_stamp = 1;
// The time-date stamp when the process was created (time_t format)
optional int64 process_create_time = 13;
message Crash {
// The type of crash. OS- and possibly CPU- specific. For example,
// "EXCEPTION_ACCESS_VIOLATION" (Windows), "EXC_BAD_ACCESS /
// KERN_INVALID_ADDRESS" (Mac OS X), "SIGSEGV" (other Unix).
required string reason = 1;
// If crash_reason implicates memory, the memory address that caused the
// crash. For data access errors, this will be the data address that
// caused the fault. For code errors, this will be the address of the
// instruction that caused the fault.
required int64 address = 2;
}
optional Crash crash = 2;
// If there was an assertion that was hit, a textual representation
// of that assertion, possibly including the file and line at which
// it occurred.
optional string assertion = 3;
// The index of the thread that requested a dump be written in the
// threads vector. If a dump was produced as a result of a crash, this
// will point to the thread that crashed. If the dump was produced as
// by user code without crashing, and the dump contains extended Breakpad
// information, this will point to the thread that requested the dump.
optional int32 requesting_thread = 4;
message Thread {
// Stack for the given thread
repeated StackFrame frames = 1;
}
// Stacks for each thread (except possibly the exception handler
// thread) at the time of the crash.
repeated Thread threads = 5;
// The modules that were loaded into the process represented by the
// ProcessState.
repeated CodeModule modules = 6;
// System Info: OS and CPU
// A string identifying the operating system, such as "Windows NT",
// "Mac OS X", or "Linux". If the information is present in the dump but
// its value is unknown, this field will contain a numeric value. If
// the information is not present in the dump, this field will be empty.
optional string os = 7;
// A short form of the os string, using lowercase letters and no spaces,
// suitable for use in a filesystem. Possible values are "windows",
// "mac", and "linux". Empty if the information is not present in the dump
// or if the OS given by the dump is unknown. The values stored in this
// field should match those used by MinidumpSystemInfo::GetOS.
optional string os_short = 8;
// A string identifying the version of the operating system, such as
// "5.1.2600 Service Pack 2" or "10.4.8 8L2127". If the dump does not
// contain this information, this field will be empty.
optional string os_version = 9;
// A string identifying the basic CPU family, such as "x86" or "ppc".
// If this information is present in the dump but its value is unknown,
// this field will contain a numeric value. If the information is not
// present in the dump, this field will be empty. The values stored in
// this field should match those used by MinidumpSystemInfo::GetCPU.
optional string cpu = 10;
// A string further identifying the specific CPU, such as
// "GenuineIntel level 6 model 13 stepping 8". If the information is not
// present in the dump, or additional identifying information is not
// defined for the CPU family, this field will be empty.
optional string cpu_info = 11;
// The number of processors in the system. Will be greater than one for
// multi-core systems.
optional int32 cpu_count = 12;
// Leave the ability to add the raw minidump to this representation
}
// Represents a single frame in a stack
// See src/google_breakpad/processor/code_module.h
message StackFrame {
// Next value: 8
// The program counter location as an absolute virtual address. For the
// innermost called frame in a stack, this will be an exact program counter
// or instruction pointer value. For all other frames, this will be within
// the instruction that caused execution to branch to a called function,
// but may not necessarily point to the exact beginning of that instruction.
required int64 instruction = 1;
// The module in which the instruction resides.
optional CodeModule module = 2;
// The function name, may be omitted if debug symbols are not available.
optional string function_name = 3;
// The start address of the function, may be omitted if debug symbols
// are not available.
optional int64 function_base = 4;
// The source file name, may be omitted if debug symbols are not available.
optional string source_file_name = 5;
// The (1-based) source line number, may be omitted if debug symbols are
// not available.
optional int32 source_line = 6;
// The start address of the source line, may be omitted if debug symbols
// are not available.
optional int64 source_line_base = 7;
}
// Carries information about code modules that are loaded into a process.
// See src/google_breakpad/processor/code_module.h
message CodeModule {
// Next value: 8
// The base address of this code module as it was loaded by the process.
optional int64 base_address = 1;
// The size of the code module.
optional int64 size = 2;
// The path or file name that the code module was loaded from.
optional string code_file = 3;
// An identifying string used to discriminate between multiple versions and
// builds of the same code module. This may contain a uuid, timestamp,
// version number, or any combination of this or other information, in an
// implementation-defined format.
optional string code_identifier = 4;
// The filename containing debugging information associated with the code
// module. If debugging information is stored in a file separate from the
// code module itself (as is the case when .pdb or .dSYM files are used),
// this will be different from code_file. If debugging information is
// stored in the code module itself (possibly prior to stripping), this
// will be the same as code_file.
optional string debug_file = 5;
// An identifying string similar to code_identifier, but identifies a
// specific version and build of the associated debug file. This may be
// the same as code_identifier when the debug_file and code_file are
// identical or when the same identifier is used to identify distinct
// debug and code files.
optional string debug_identifier = 6;
// A human-readable representation of the code module's version.
optional string version = 7;
}

View file

@ -0,0 +1,296 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// range_map-inl.h: Range map implementation.
//
// See range_map.h for documentation.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_RANGE_MAP_INL_H__
#define PROCESSOR_RANGE_MAP_INL_H__
#include <assert.h>
#include "common/safe_math.h"
#include "processor/range_map.h"
#include "processor/linked_ptr.h"
#include "processor/logging.h"
namespace google_breakpad {
template<typename AddressType, typename EntryType>
bool RangeMap<AddressType, EntryType>::StoreRange(const AddressType& base,
const AddressType& size,
const EntryType& entry) {
return StoreRangeInternal(base, 0 /* delta */, size, entry);
}
template<typename AddressType, typename EntryType>
bool RangeMap<AddressType, EntryType>::StoreRangeInternal(
const AddressType& base, const AddressType& delta,
const AddressType& size, const EntryType& entry) {
AddressType high;
bool high_ok = false;
if (size > 0) {
std::pair<AddressType, bool> result = AddWithOverflowCheck(base, size - 1);
high = result.first;
high_ok = !result.second;
}
// Check for undersize or overflow.
if (!high_ok) {
// The processor will hit this case too frequently with common symbol
// files in the size == 0 case, which is more suited to a DEBUG channel.
// Filter those out since there's no DEBUG channel at the moment.
BPLOG_IF(INFO, size != 0) << "StoreRangeInternal failed, "
<< HexString(base) << "+" << HexString(size)
<< ", " << HexString(high)
<< ", delta: " << HexString(delta);
return false;
}
// Ensure that this range does not overlap with another one already in the
// map.
MapConstIterator iterator_base = map_.lower_bound(base);
MapConstIterator iterator_high = map_.lower_bound(high);
if (iterator_base != iterator_high) {
// Some other range ends in the space used by this range. It may be
// contained within the space used by this range, or it may extend lower.
if (merge_strategy_ == MergeRangeStrategy::kTruncateLower) {
// kTruncate the range with the lower base address.
AddressType other_base = iterator_base->second.base();
if (base < other_base) {
return StoreRangeInternal(base, delta, other_base - base, entry);
} else if (other_base < base) {
EntryType other_entry;
AddressType other_high, other_size, other_delta;
other_high = iterator_base->first;
RetrieveRange(other_high, &other_entry, &other_base, &other_delta,
&other_size);
map_.erase(iterator_base);
map_.insert(
MapValue(base - 1, Range(other_base, other_delta, other_entry)));
return StoreRangeInternal(base, delta, size, entry);
} else {
return false;
}
} else if (merge_strategy_ == MergeRangeStrategy::kTruncateUpper) {
// Truncate the lower portion of this range.
AddressType additional_delta = iterator_base->first - base + 1;
return StoreRangeInternal(base + additional_delta,
delta + additional_delta,
size - additional_delta, entry);
} else {
// The processor hits this case too frequently with common symbol files.
// This is most appropriate for a DEBUG channel, but since none exists
// now simply comment out this logging.
// AddressType other_base = iterator_base->second.base();
// AddressType other_size = iterator_base->first - other_base + 1;
// BPLOG(INFO) << "StoreRangeInternal failed, an existing range is "
// << "overlapping with the new range: new "
// << HexString(base) << "+" << HexString(size)
// << ", existing " << HexString(other_base) << "+"
// << HexString(other_size);
return false;
}
}
if (iterator_high != map_.end() && iterator_high->second.base() <= high) {
// The range above this one overlaps with this one. It may fully
// contain this range, or it may begin within this range and extend
// higher.
if (merge_strategy_ == MergeRangeStrategy::kTruncateLower) {
AddressType other_base = iterator_high->second.base();
if (base < other_base) {
return StoreRangeInternal(base, delta, other_base - base, entry);
} else if (other_base < base) {
EntryType other_entry;
AddressType other_high, other_size, other_delta;
other_high = iterator_high->first;
RetrieveRange(other_high, &other_entry, &other_base, &other_delta,
&other_size);
map_.erase(iterator_high);
map_.insert(
MapValue(base - 1, Range(other_base, other_delta, other_entry)));
return StoreRangeInternal(base, delta, size, entry);
} else {
return false;
}
} else if (merge_strategy_ == MergeRangeStrategy::kTruncateUpper &&
iterator_high->first > high) {
// Shrink the other range down.
AddressType other_high = iterator_high->first;
AddressType additional_delta = high - iterator_high->second.base() + 1;
EntryType other_entry;
AddressType other_base = AddressType();
AddressType other_size = AddressType();
AddressType other_delta = AddressType();
RetrieveRange(other_high, &other_entry, &other_base, &other_delta,
&other_size);
map_.erase(iterator_high);
map_.insert(MapValue(other_high,
Range(other_base + additional_delta,
other_delta + additional_delta, other_entry)));
// Retry to store this range.
return StoreRangeInternal(base, delta, size, entry);
} else {
// The processor hits this case too frequently with common symbol files.
// This is most appropriate for a DEBUG channel, but since none exists
// now simply comment out this logging.
//
// AddressType other_base = iterator_high->second.base();
// AddressType other_size = iterator_high->first - other_base + 1;
// BPLOG(INFO) << "StoreRangeInternal failed, an existing range "
// << "contains or extends higher than the new range: new "
// << HexString(base) << "+" << HexString(size)
// << ", existing " << HexString(other_base) << "+"
// << HexString(other_size);
return false;
}
}
// Store the range in the map by its high address, so that lower_bound can
// be used to quickly locate a range by address.
map_.insert(MapValue(high, Range(base, delta, entry)));
return true;
}
template<typename AddressType, typename EntryType>
bool RangeMap<AddressType, EntryType>::RetrieveRange(
const AddressType& address, EntryType* entry, AddressType* entry_base,
AddressType* entry_delta, AddressType* entry_size) const {
BPLOG_IF(ERROR, !entry) << "RangeMap::RetrieveRange requires |entry|";
assert(entry);
MapConstIterator iterator = map_.lower_bound(address);
if (iterator == map_.end())
return false;
// The map is keyed by the high address of each range, so |address| is
// guaranteed to be lower than the range's high address. If |range| is
// not directly preceded by another range, it's possible for address to
// be below the range's low address, though. When that happens, address
// references something not within any range, so return false.
if (address < iterator->second.base())
return false;
*entry = iterator->second.entry();
if (entry_base)
*entry_base = iterator->second.base();
if (entry_delta)
*entry_delta = iterator->second.delta();
if (entry_size)
*entry_size = iterator->first - iterator->second.base() + 1;
return true;
}
template<typename AddressType, typename EntryType>
bool RangeMap<AddressType, EntryType>::RetrieveNearestRange(
const AddressType& address, EntryType* entry, AddressType* entry_base,
AddressType* entry_delta, AddressType* entry_size) const {
BPLOG_IF(ERROR, !entry) << "RangeMap::RetrieveNearestRange requires |entry|";
assert(entry);
// If address is within a range, RetrieveRange can handle it.
if (RetrieveRange(address, entry, entry_base, entry_delta, entry_size))
return true;
// upper_bound gives the first element whose key is greater than address,
// but we want the first element whose key is less than or equal to address.
// Decrement the iterator to get there, but not if the upper_bound already
// points to the beginning of the map - in that case, address is lower than
// the lowest stored key, so return false.
MapConstIterator iterator = map_.upper_bound(address);
if (iterator == map_.begin())
return false;
--iterator;
*entry = iterator->second.entry();
if (entry_base)
*entry_base = iterator->second.base();
if (entry_delta)
*entry_delta = iterator->second.delta();
if (entry_size)
*entry_size = iterator->first - iterator->second.base() + 1;
return true;
}
template<typename AddressType, typename EntryType>
bool RangeMap<AddressType, EntryType>::RetrieveRangeAtIndex(
int index, EntryType* entry, AddressType* entry_base,
AddressType* entry_delta, AddressType* entry_size) const {
BPLOG_IF(ERROR, !entry) << "RangeMap::RetrieveRangeAtIndex requires |entry|";
assert(entry);
if (index >= GetCount()) {
BPLOG(ERROR) << "Index out of range: " << index << "/" << GetCount();
return false;
}
// Walk through the map. Although it's ordered, it's not a vector, so it
// can't be addressed directly by index.
MapConstIterator iterator = map_.begin();
for (int this_index = 0; this_index < index; ++this_index)
++iterator;
*entry = iterator->second.entry();
if (entry_base)
*entry_base = iterator->second.base();
if (entry_delta)
*entry_delta = iterator->second.delta();
if (entry_size)
*entry_size = iterator->first - iterator->second.base() + 1;
return true;
}
template<typename AddressType, typename EntryType>
int RangeMap<AddressType, EntryType>::GetCount() const {
return static_cast<int>(map_.size());
}
template<typename AddressType, typename EntryType>
void RangeMap<AddressType, EntryType>::Clear() {
map_.clear();
}
} // namespace google_breakpad
#endif // PROCESSOR_RANGE_MAP_INL_H__

View file

@ -0,0 +1,170 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// range_map.h: Range maps.
//
// A range map associates a range of addresses with a specific object. This
// is useful when certain objects of variable size are located within an
// address space. The range map makes it simple to determine which object is
// associated with a specific address, which may be any address within the
// range associated with an object.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_RANGE_MAP_H__
#define PROCESSOR_RANGE_MAP_H__
#include <map>
namespace google_breakpad {
// Forward declarations (for later friend declarations of specialized template).
template<class, class> class RangeMapSerializer;
// Determines what happens when two ranges overlap.
enum class MergeRangeStrategy {
// When two ranges overlap, the new range fails to be inserted. The default
// strategy.
kExclusiveRanges,
// The range with the lower base address will be truncated such that it's
// high address is one less than the range above it.
kTruncateLower,
// The range with the greater high address has its range truncated such that
// its base address is one higher than the range below it.
kTruncateUpper
};
template<typename AddressType, typename EntryType>
class RangeMap {
public:
RangeMap() : merge_strategy_(MergeRangeStrategy::kExclusiveRanges), map_() {}
void SetMergeStrategy(MergeRangeStrategy strat) { merge_strategy_ = strat; }
MergeRangeStrategy GetMergeStrategy() const { return merge_strategy_; }
// Inserts a range into the map. Returns false for a parameter error,
// or if the location of the range would conflict with a range already
// stored in the map. If enable_shrink_down is true and there is an overlap
// between the current range and some other range (already in the map),
// shrink down the range which ends at a higher address.
bool StoreRange(const AddressType& base, const AddressType& size,
const EntryType& entry);
// Locates the range encompassing the supplied address. If there is no such
// range, returns false. entry_base, entry_delta, and entry_size, if
// non-NULL, are set to the base, delta, and size of the entry's range.
// A positive entry delta (> 0) indicates that there was an overlap and the
// entry was shrunk down (original start address was increased by delta).
bool RetrieveRange(const AddressType& address, EntryType* entry,
AddressType* entry_base, AddressType* entry_delta,
AddressType* entry_size) const;
// Locates the range encompassing the supplied address, if one exists.
// If no range encompasses the supplied address, locates the nearest range
// to the supplied address that is lower than the address. Returns false
// if no range meets these criteria. entry_base, entry_delta, and entry_size,
// if non-NULL, are set to the base, delta, and size of the entry's range.
// A positive entry delta (> 0) indicates that there was an overlap and the
// entry was shrunk down (original start address was increased by delta).
bool RetrieveNearestRange(const AddressType& address, EntryType* entry,
AddressType* entry_base, AddressType* entry_delta,
AddressType* entry_size) const;
// Treating all ranges as a list ordered by the address spaces that they
// occupy, locates the range at the index specified by index. Returns
// false if index is larger than the number of ranges stored. entry_base,
// entry_delta, and entry_size, if non-NULL, are set to the base, delta, and
// size of the entry's range.
// A positive entry delta (> 0) indicates that there was an overlap and the
// entry was shrunk down (original start address was increased by delta).
//
// RetrieveRangeAtIndex is not optimized for speedy operation.
bool RetrieveRangeAtIndex(int index, EntryType* entry,
AddressType* entry_base, AddressType* entry_delta,
AddressType* entry_size) const;
// Returns the number of ranges stored in the RangeMap.
int GetCount() const;
// Empties the range map, restoring it to the state it was when it was
// initially created.
void Clear();
private:
// Friend declarations.
friend class ModuleComparer;
friend class RangeMapSerializer<AddressType, EntryType>;
// Same a StoreRange() with the only exception that the |delta| can be
// passed in.
bool StoreRangeInternal(const AddressType& base, const AddressType& delta,
const AddressType& size, const EntryType& entry);
class Range {
public:
Range(const AddressType& base, const AddressType& delta,
const EntryType& entry)
: base_(base), delta_(delta), entry_(entry) {}
AddressType base() const { return base_; }
AddressType delta() const { return delta_; }
EntryType entry() const { return entry_; }
private:
// The base address of the range. The high address does not need to
// be stored, because RangeMap uses it as the key to the map.
const AddressType base_;
// The delta when the range is shrunk down.
const AddressType delta_;
// The entry corresponding to a range.
const EntryType entry_;
};
// Convenience types.
typedef std::map<AddressType, Range> AddressToRangeMap;
typedef typename AddressToRangeMap::const_iterator MapConstIterator;
typedef typename AddressToRangeMap::value_type MapValue;
MergeRangeStrategy merge_strategy_;
// Maps the high address of each range to a EntryType.
AddressToRangeMap map_;
};
} // namespace google_breakpad
#endif // PROCESSOR_RANGE_MAP_H__

View file

@ -0,0 +1,349 @@
// Copyright 2019 Google LLC
//
// 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 LLC 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.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <limits.h>
#include <stdio.h>
#include "processor/range_map-inl.h"
#include "breakpad_googletest_includes.h"
#include "processor/linked_ptr.h"
#include "processor/logging.h"
namespace {
using google_breakpad::linked_ptr;
using google_breakpad::MergeRangeStrategy;
using google_breakpad::RangeMap;
// A CountedObject holds an int. A global (not thread safe!) count of
// allocated CountedObjects is maintained to help test memory management.
class CountedObject {
public:
explicit CountedObject(int id) : id_(id) { ++count_; }
~CountedObject() { --count_; }
static int count() { return count_; }
int id() const { return id_; }
private:
static int count_;
int id_;
};
int CountedObject::count_;
typedef int AddressType;
typedef RangeMap<AddressType, linked_ptr<CountedObject>> TestMap;
// Same range cannot be stored wice.
TEST(RangeMapTruncateLower, SameRange) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateLower);
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(
range_map.StoreRange(0 /* base address */, 100 /* size */, object_1));
// Same range cannot be stored wice.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_FALSE(
range_map.StoreRange(0 /* base address */, 100 /* size */, object_2));
}
// If a range is completely contained by another range, then the larger range
// should be truncated.
TEST(RangeMapTruncateLower, CompletelyContained) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateLower);
// Larger range is added first.
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(
range_map.StoreRange(0 /* base address */, 100 /* size */, object_1));
// Smaller (contained) range is added second.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(
range_map.StoreRange(10 /* base address */, 80 /* size */, object_2));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The first range contains the second, so the first range should have been
// shrunk to [0, 10]. Range [90, 99] should be free.
EXPECT_FALSE(range_map.RetrieveRange(90, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_FALSE(range_map.RetrieveRange(99, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_TRUE(range_map.RetrieveRange(9, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(0, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(10, retrieved_size);
// Validate the properties of the smaller range (should be untouched).
EXPECT_TRUE(range_map.RetrieveRange(10, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(10, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(80, retrieved_size);
}
// Same as the previous test, however the larger range is added second.
TEST(RangeMapTruncateLower, CompletelyContained_LargerAddedSecond) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateLower);
// Smaller (contained) range is added first.
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(
range_map.StoreRange(10 /* base address */, 80 /* size */, object_1));
// Larger range is added second.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(
range_map.StoreRange(0 /* base address */, 100 /* size */, object_2));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The second range contains the first, so the second range should have been
// truncated to [0, 9]. Range [90, 99] should be free.
EXPECT_FALSE(range_map.RetrieveRange(90, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_FALSE(range_map.RetrieveRange(99, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_TRUE(range_map.RetrieveRange(9, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(0, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(10, retrieved_size);
// Validate the properties of the smaller range (should be untouched).
EXPECT_TRUE(range_map.RetrieveRange(10, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(10, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(80, retrieved_size);
}
TEST(RangeMapTruncateLower, PartialOverlap_AtBeginning) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateLower);
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(
range_map.StoreRange(0 /* base address */, 100 /* size */, object_1));
// Partial overlap at the beginning of the new range.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(
range_map.StoreRange(90 /* base address */, 110 /* size */, object_2));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The first range should be truncated, so 99 should address the second range.
EXPECT_TRUE(range_map.RetrieveRange(99, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(90, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(110, retrieved_size);
// Validate the properties of the truncated range.
EXPECT_TRUE(range_map.RetrieveRange(89, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(0, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(90, retrieved_size);
}
TEST(RangeMapTruncateLower, PartialOverlap_AtEnd) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateLower);
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(
range_map.StoreRange(50 /* base address */, 50 /* size */, object_1));
// Partial overlap at the end of the new range.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(
range_map.StoreRange(0 /* base address */, 70 /* size */, object_2));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The second range should be truncated so 69 addresses the first range.
EXPECT_TRUE(range_map.RetrieveRange(69, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(50, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(50, retrieved_size);
// Validate the properties of the truncated range.
EXPECT_TRUE(range_map.RetrieveRange(49, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(0, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(50, retrieved_size);
}
// A new range is overlapped at both ends. The new range and the range
// that overlaps at the beginning should be truncated. The range that overlaps
// at the end should be left untouched.
TEST(RangeMapTruncateLower, OverlapAtBothEnds) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateLower);
// This should overlap object_3 at the beginning.
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(
range_map.StoreRange(0 /* base address */, 100 /* size */, object_1));
// This should overlap object_3 at the end.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(
range_map.StoreRange(100 /* base address */, 100 /* size */, object_2));
// This should be overlapped on both ends by object_1 and object_2.
linked_ptr<CountedObject> object_3(new CountedObject(3));
EXPECT_TRUE(
range_map.StoreRange(50 /* base address */, 100 /* size */, object_3));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The first range should be truncated.
EXPECT_TRUE(range_map.RetrieveRange(0, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(0, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(50, retrieved_size);
// The second range should be intact.
EXPECT_TRUE(range_map.RetrieveRange(150, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(100, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(100, retrieved_size);
// The third range (in the middle) should be truncated.
EXPECT_TRUE(range_map.RetrieveRange(99, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(3, object->id());
EXPECT_EQ(50, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(50, retrieved_size);
}
TEST(RangeMapTruncateLower, MultipleConflicts) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateLower);
// This should overlap with object_3.
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(
range_map.StoreRange(10 /* base address */, 90 /* size */, object_1));
// This should also overlap with object_3 but after object_1.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(
range_map.StoreRange(100 /* base address */, 100 /* size */, object_2));
// This should be overlapped on both object_1 and object_2.
linked_ptr<CountedObject> object_3(new CountedObject(3));
EXPECT_TRUE(
range_map.StoreRange(0 /* base address */, 300 /* size */, object_3));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The first range should be intact.
EXPECT_TRUE(range_map.RetrieveRange(99, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(10, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(90, retrieved_size);
// The second range should be intact.
EXPECT_TRUE(range_map.RetrieveRange(199, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(100, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(100, retrieved_size);
// The third range should be truncated.
EXPECT_TRUE(range_map.RetrieveRange(9, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(3, object->id());
EXPECT_EQ(0, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(10, retrieved_size);
}
// Adding two ranges without overlap should succeed and the ranges should
// be left intact.
TEST(RangeMapTruncateLower, NoConflicts) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateLower);
// Adding range 1.
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(
range_map.StoreRange(10 /* base address */, 90 /* size */, object_1));
// Adding range 2 - no overlap with range 1.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(
range_map.StoreRange(110 /* base address */, 90 /* size */, object_2));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The first range should be intact.
EXPECT_TRUE(range_map.RetrieveRange(99, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(10, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(90, retrieved_size);
// The second range should be intact.
EXPECT_TRUE(range_map.RetrieveRange(199, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(110, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(90, retrieved_size);
}
} // namespace

View file

@ -0,0 +1,357 @@
// Copyright 2016 Google LLC
//
// 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 LLC 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.
// range_map_shrink_down_unittest.cc: Unit tests for RangeMap that specifically
// test shrink down when ranges overlap.
//
// Author: Ivan Penkov
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <limits.h>
#include <stdio.h>
#include "processor/range_map-inl.h"
#include "breakpad_googletest_includes.h"
#include "processor/linked_ptr.h"
#include "processor/logging.h"
namespace {
using google_breakpad::linked_ptr;
using google_breakpad::MergeRangeStrategy;
using google_breakpad::RangeMap;
// A CountedObject holds an int. A global (not thread safe!) count of
// allocated CountedObjects is maintained to help test memory management.
class CountedObject {
public:
explicit CountedObject(int id) : id_(id) { ++count_; }
~CountedObject() { --count_; }
static int count() { return count_; }
int id() const { return id_; }
private:
static int count_;
int id_;
};
int CountedObject::count_;
typedef int AddressType;
typedef RangeMap<AddressType, linked_ptr<CountedObject>> TestMap;
// Same range cannot be stored wice.
TEST(RangeMapTruncateUpper, SameRange) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateUpper);
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(range_map.StoreRange(0 /* base address */, 100 /* size */,
object_1));
// Same range cannot be stored wice.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_FALSE(range_map.StoreRange(0 /* base address */, 100 /* size */,
object_2));
}
// If a range is completely contained by another range, then the larger range
// should be shrinked down.
TEST(RangeMapTruncateUpper, CompletelyContained) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateUpper);
// Larger range is added first.
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(range_map.StoreRange(0 /* base address */, 100 /* size */,
object_1));
// Smaller (contained) range is added second.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(range_map.StoreRange(10 /* base address */, 80 /* size */,
object_2));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The first range contains the second, so the first range should have been
// shrunk to [90, 99]. Range [0, 9] should be free.
EXPECT_FALSE(range_map.RetrieveRange(0, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_FALSE(range_map.RetrieveRange(9, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_TRUE(range_map.RetrieveRange(90, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(90, retrieved_base);
EXPECT_EQ(90, retrieved_delta);
EXPECT_EQ(10, retrieved_size);
// Validate the properties of the smaller range (should be untouched).
EXPECT_TRUE(range_map.RetrieveRange(10, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(10, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(80, retrieved_size);
}
// Same as the previous test, however the larger range is added second.
TEST(RangeMapTruncateUpper, CompletelyContained_LargerAddedSecond) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateUpper);
// Smaller (contained) range is added first.
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(range_map.StoreRange(10 /* base address */, 80 /* size */,
object_1));
// Larger range is added second.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(range_map.StoreRange(0 /* base address */, 100 /* size */,
object_2));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The second range contains the first, so the second range should have been
// shrunk to [90, 99]. Range [0, 9] should be free.
EXPECT_FALSE(range_map.RetrieveRange(0, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_FALSE(range_map.RetrieveRange(9, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_TRUE(range_map.RetrieveRange(90, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(90, retrieved_base);
EXPECT_EQ(90, retrieved_delta);
EXPECT_EQ(10, retrieved_size);
// Validate the properties of the smaller range (should be untouched).
EXPECT_TRUE(range_map.RetrieveRange(10, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(10, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(80, retrieved_size);
}
TEST(RangeMapTruncateUpper, PartialOverlap_AtBeginning) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateUpper);
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(range_map.StoreRange(0 /* base address */, 100 /* size */,
object_1));
// Partial overlap at the beginning of the new range.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(range_map.StoreRange(90 /* base address */, 110 /* size */,
object_2));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The second range is supposed to be shrunk down so the following address
// should resize in the first range.
EXPECT_TRUE(range_map.RetrieveRange(99, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(0, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(100, retrieved_size);
// Validate the properties of the shrunk down range.
EXPECT_TRUE(range_map.RetrieveRange(100, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(100, retrieved_base);
EXPECT_EQ(10, retrieved_delta);
EXPECT_EQ(100, retrieved_size);
}
TEST(RangeMapTruncateUpper, PartialOverlap_AtEnd) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateUpper);
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(range_map.StoreRange(50 /* base address */, 50 /* size */,
object_1));
// Partial overlap at the end of the new range.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(range_map.StoreRange(0 /* base address */, 70 /* size */,
object_2));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The first range is supposed to be shrunk down so the following address
// should resize in the first range.
EXPECT_TRUE(range_map.RetrieveRange(69, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(0, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(70, retrieved_size);
// Validate the properties of the shrunk down range.
EXPECT_TRUE(range_map.RetrieveRange(70, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(70, retrieved_base);
EXPECT_EQ(20, retrieved_delta);
EXPECT_EQ(30, retrieved_size);
}
// A new range is overlapped at both ends. The new range and the range
// that overlaps at the end should be shrink. The range that overlaps at the
// beginning should be left untouched.
TEST(RangeMapTruncateUpper, OverlapAtBothEnds) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateUpper);
// This should overlap object_3 at the beginning.
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(range_map.StoreRange(0 /* base address */, 100 /* size */,
object_1));
// This should overlap object_3 at the end.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(range_map.StoreRange(100 /* base address */, 100 /* size */,
object_2));
// This should be overlapped on both ends by object_1 and object_2.
linked_ptr<CountedObject> object_3(new CountedObject(3));
EXPECT_TRUE(range_map.StoreRange(50 /* base address */, 100 /* size */,
object_3));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The first range should be intact.
EXPECT_TRUE(range_map.RetrieveRange(0, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(0, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(100, retrieved_size);
// The second range should be shrunk down by 50.
EXPECT_TRUE(range_map.RetrieveRange(150, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(150, retrieved_base);
EXPECT_EQ(50, retrieved_delta);
EXPECT_EQ(50, retrieved_size);
// The third range (in the middle) should be shrunk down by 50.
EXPECT_TRUE(range_map.RetrieveRange(100, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(3, object->id());
EXPECT_EQ(100, retrieved_base);
EXPECT_EQ(50, retrieved_delta);
EXPECT_EQ(50, retrieved_size);
}
TEST(RangeMapTruncateUpper, MultipleConflicts) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateUpper);
// This should overlap with object_3.
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(range_map.StoreRange(10 /* base address */, 90 /* size */,
object_1));
// This should also overlap with object_3 but after object_1.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(range_map.StoreRange(100 /* base address */, 100 /* size */,
object_2));
// This should be overlapped on both object_1 and object_2. Since
// object_3 ends with the higher address it must be shrunk.
linked_ptr<CountedObject> object_3(new CountedObject(3));
EXPECT_TRUE(range_map.StoreRange(0 /* base address */, 300 /* size */,
object_3));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The first range should be intact.
EXPECT_TRUE(range_map.RetrieveRange(99, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(10, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(90, retrieved_size);
// The second range should be intact.
EXPECT_TRUE(range_map.RetrieveRange(199, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(100, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(100, retrieved_size);
// The third range should be shrunk down by 200.
EXPECT_TRUE(range_map.RetrieveRange(299, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(3, object->id());
EXPECT_EQ(200, retrieved_base);
EXPECT_EQ(200, retrieved_delta);
EXPECT_EQ(100, retrieved_size);
}
// Adding two ranges without overlap should succeed and the ranges should
// be left intact.
TEST(RangeMapTruncateUpper, NoConflicts) {
TestMap range_map;
range_map.SetMergeStrategy(MergeRangeStrategy::kTruncateUpper);
// Adding range 1.
linked_ptr<CountedObject> object_1(new CountedObject(1));
EXPECT_TRUE(range_map.StoreRange(10 /* base address */, 90 /* size */,
object_1));
// Adding range 2 - no overlap with range 1.
linked_ptr<CountedObject> object_2(new CountedObject(2));
EXPECT_TRUE(range_map.StoreRange(110 /* base address */, 90 /* size */,
object_2));
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_delta = AddressType();
AddressType retrieved_size = AddressType();
// The first range should be intact.
EXPECT_TRUE(range_map.RetrieveRange(99, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(1, object->id());
EXPECT_EQ(10, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(90, retrieved_size);
// The second range should be intact.
EXPECT_TRUE(range_map.RetrieveRange(199, &object, &retrieved_base,
&retrieved_delta, &retrieved_size));
EXPECT_EQ(2, object->id());
EXPECT_EQ(110, retrieved_base);
EXPECT_EQ(0, retrieved_delta);
EXPECT_EQ(90, retrieved_size);
}
} // namespace

View file

@ -0,0 +1,561 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// range_map_unittest.cc: Unit tests for RangeMap
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <limits.h>
#include <stdio.h>
#include "processor/range_map-inl.h"
#include "common/scoped_ptr.h"
#include "processor/linked_ptr.h"
#include "processor/logging.h"
namespace {
using google_breakpad::AddIgnoringOverflow;
using google_breakpad::linked_ptr;
using google_breakpad::RangeMap;
using google_breakpad::scoped_ptr;
// A CountedObject holds an int. A global (not thread safe!) count of
// allocated CountedObjects is maintained to help test memory management.
class CountedObject {
public:
explicit CountedObject(int id) : id_(id) { ++count_; }
~CountedObject() { --count_; }
static int count() { return count_; }
int id() const { return id_; }
private:
static int count_;
int id_;
};
int CountedObject::count_;
typedef int AddressType;
typedef RangeMap< AddressType, linked_ptr<CountedObject> > TestMap;
// RangeTest contains data to use for store and retrieve tests. See
// RunTests for descriptions of the tests.
struct RangeTest {
// Base address to use for test
AddressType address;
// Size of range to use for test
AddressType size;
// Unique ID of range - unstorable ranges must have unique IDs too
int id;
// Whether this range is expected to be stored successfully or not
bool expect_storable;
};
// A RangeTestSet encompasses multiple RangeTests, which are run in
// sequence on the same RangeMap.
struct RangeTestSet {
// An array of RangeTests
const RangeTest* range_tests;
// The number of tests in the set
unsigned int range_test_count;
};
// StoreTest uses the data in a RangeTest and calls StoreRange on the
// test RangeMap. It returns true if the expected result occurred, and
// false if something else happened.
static bool StoreTest(TestMap* range_map, const RangeTest* range_test) {
linked_ptr<CountedObject> object(new CountedObject(range_test->id));
bool stored = range_map->StoreRange(range_test->address,
range_test->size,
object);
if (stored != range_test->expect_storable) {
fprintf(stderr, "FAILED: "
"StoreRange id %d, expected %s, observed %s\n",
range_test->id,
range_test->expect_storable ? "storable" : "not storable",
stored ? "stored" : "not stored");
return false;
}
return true;
}
// RetrieveTest uses the data in RangeTest and calls RetrieveRange on the
// test RangeMap. If it retrieves the expected value (which can be no
// map entry at the specified range,) it returns true, otherwise, it returns
// false. RetrieveTest will check the values around the base address and
// the high address of a range to guard against off-by-one errors.
static bool RetrieveTest(TestMap* range_map, const RangeTest* range_test) {
for (unsigned int side = 0; side <= 1; ++side) {
// When side == 0, check the low side (base address) of each range.
// When side == 1, check the high side (base + size) of each range.
// Check one-less and one-greater than the target address in addition
// to the target address itself.
// If the size of the range is only 1, don't check one greater than
// the base or one less than the high - for a successfully stored
// range, these tests would erroneously fail because the range is too
// small.
AddressType low_offset = -1;
AddressType high_offset = 1;
if (range_test->size == 1) {
if (!side) // When checking the low side,
high_offset = 0; // don't check one over the target.
else // When checking the high side,
low_offset = 0; // don't check one under the target.
}
for (AddressType offset = low_offset; offset <= high_offset; ++offset) {
AddressType address = AddIgnoringOverflow(
offset, (!side ? range_test->address
: AddIgnoringOverflow(range_test->address,
range_test->size - 1)));
bool expected_result = false; // This is correct for tests not stored.
if (range_test->expect_storable) {
if (offset == 0) // When checking the target address,
expected_result = true; // test should always succeed.
else if (offset == -1) // When checking one below the target,
expected_result = side; // should fail low and succeed high.
else // When checking one above the target,
expected_result = !side; // should succeed low and fail high.
}
linked_ptr<CountedObject> object;
AddressType retrieved_base = AddressType();
AddressType retrieved_size = AddressType();
AddressType retrieved_delta = AddressType();
bool retrieved = range_map->RetrieveRange(address, &object,
&retrieved_base,
&retrieved_delta,
&retrieved_size);
bool observed_result = retrieved && object->id() == range_test->id;
if (observed_result != expected_result) {
fprintf(stderr, "FAILED: "
"RetrieveRange id %d, side %d, offset %d, "
"expected %s, observed %s\n",
range_test->id,
side,
offset,
expected_result ? "true" : "false",
observed_result ? "true" : "false");
return false;
}
// If a range was successfully retrieved, check that the returned
// bounds match the range as stored.
if (observed_result == true &&
(retrieved_base != range_test->address ||
retrieved_size != range_test->size)) {
fprintf(stderr, "FAILED: "
"RetrieveRange id %d, side %d, offset %d, "
"expected base/size %d/%d, observed %d/%d\n",
range_test->id,
side,
offset,
range_test->address, range_test->size,
retrieved_base, retrieved_size);
return false;
}
// Now, check RetrieveNearestRange. The nearest range is always
// expected to be different from the test range when checking one
// less than the low side.
bool expected_nearest = range_test->expect_storable;
if (!side && offset < 0)
expected_nearest = false;
linked_ptr<CountedObject> nearest_object;
AddressType nearest_base = AddressType();
AddressType nearest_delta = AddressType();
AddressType nearest_size = AddressType();
bool retrieved_nearest = range_map->RetrieveNearestRange(address,
&nearest_object,
&nearest_base,
&nearest_delta,
&nearest_size);
// When checking one greater than the high side, RetrieveNearestRange
// should usually return the test range. When a different range begins
// at that address, though, then RetrieveNearestRange should return the
// range at the address instead of the test range.
if (side && offset > 0 && nearest_base == address) {
expected_nearest = false;
}
bool observed_nearest = retrieved_nearest &&
nearest_object->id() == range_test->id;
if (observed_nearest != expected_nearest) {
fprintf(stderr, "FAILED: "
"RetrieveNearestRange id %d, side %d, offset %d, "
"expected %s, observed %s\n",
range_test->id,
side,
offset,
expected_nearest ? "true" : "false",
observed_nearest ? "true" : "false");
return false;
}
// If a range was successfully retrieved, check that the returned
// bounds match the range as stored.
if (expected_nearest &&
(nearest_base != range_test->address ||
nearest_size != range_test->size)) {
fprintf(stderr, "FAILED: "
"RetrieveNearestRange id %d, side %d, offset %d, "
"expected base/size %d/%d, observed %d/%d\n",
range_test->id,
side,
offset,
range_test->address, range_test->size,
nearest_base, nearest_size);
return false;
}
}
}
return true;
}
// Test RetrieveRangeAtIndex, which is supposed to return objects in order
// according to their addresses. This test is performed by looping through
// the map, calling RetrieveRangeAtIndex for all possible indices in sequence,
// and verifying that each call returns a different object than the previous
// call, and that ranges are returned with increasing base addresses. Returns
// false if the test fails.
static bool RetrieveIndexTest(TestMap* range_map, int set) {
linked_ptr<CountedObject> object;
CountedObject* last_object = NULL;
AddressType last_base = 0;
int object_count = range_map->GetCount();
for (int object_index = 0; object_index < object_count; ++object_index) {
AddressType base;
if (!range_map->RetrieveRangeAtIndex(object_index, &object, &base,
NULL /* delta */, NULL /* size */)) {
fprintf(stderr, "FAILED: RetrieveRangeAtIndex set %d index %d, "
"expected success, observed failure\n",
set, object_index);
return false;
}
if (!object.get()) {
fprintf(stderr, "FAILED: RetrieveRangeAtIndex set %d index %d, "
"expected object, observed NULL\n",
set, object_index);
return false;
}
// It's impossible to do these comparisons unless there's a previous
// object to compare against.
if (last_object) {
// The object must be different from the last one.
if (object->id() == last_object->id()) {
fprintf(stderr, "FAILED: RetrieveRangeAtIndex set %d index %d, "
"expected different objects, observed same objects (%d)\n",
set, object_index, object->id());
return false;
}
// Each object must have a base greater than the previous object's base.
if (base <= last_base) {
fprintf(stderr, "FAILED: RetrieveRangeAtIndex set %d index %d, "
"expected different bases, observed same bases (%d)\n",
set, object_index, base);
return false;
}
}
last_object = object.get();
last_base = base;
}
// Make sure that RetrieveRangeAtIndex doesn't allow lookups at indices that
// are too high.
if (range_map->RetrieveRangeAtIndex(object_count, &object, NULL /* base */,
NULL /* delta */, NULL /* size */)) {
fprintf(stderr, "FAILED: RetrieveRangeAtIndex set %d index %d (too large), "
"expected failure, observed success\n",
set, object_count);
return false;
}
return true;
}
// Additional RetriveAtIndex test to expose the bug in RetrieveRangeAtIndex().
// Bug info: RetrieveRangeAtIndex() previously retrieves the high address of
// entry, however, it is supposed to retrieve the base address of entry as
// stated in the comment in range_map.h.
static bool RetriveAtIndexTest2() {
scoped_ptr<TestMap> range_map(new TestMap());
// Store ranges with base address = 2 * object_id:
const int range_size = 2;
for (int object_id = 0; object_id < 100; ++object_id) {
linked_ptr<CountedObject> object(new CountedObject(object_id));
int base_address = 2 * object_id;
range_map->StoreRange(base_address, range_size, object);
}
linked_ptr<CountedObject> object;
int object_count = range_map->GetCount();
for (int object_index = 0; object_index < object_count; ++object_index) {
AddressType base;
if (!range_map->RetrieveRangeAtIndex(object_index, &object, &base,
NULL /* delta */, NULL /* size */)) {
fprintf(stderr, "FAILED: RetrieveAtIndexTest2 index %d, "
"expected success, observed failure\n", object_index);
return false;
}
int expected_base = 2 * object->id();
if (base != expected_base) {
fprintf(stderr, "FAILED: RetriveAtIndexTest2 index %d, "
"expected base %d, observed base %d",
object_index, expected_base, base);
return false;
}
}
return true;
}
// RunTests runs a series of test sets.
static bool RunTests() {
// These tests will be run sequentially. The first set of tests exercises
// most functions of RangeTest, and verifies all of the bounds-checking.
const RangeTest range_tests_0[] = {
{ INT_MIN, 16, 1, true }, // lowest possible range
{ -2, 5, 2, true }, // a range through zero
{ INT_MAX - 9, 11, 3, false }, // tests anti-overflow
{ INT_MAX - 9, 10, 4, true }, // highest possible range
{ 5, 0, 5, false }, // tests anti-zero-size
{ 5, 1, 6, true }, // smallest possible range
{ -20, 15, 7, true }, // entirely negative
{ 10, 10, 10, true }, // causes the following tests to fail
{ 9, 10, 11, false }, // one-less base, one-less high
{ 9, 11, 12, false }, // one-less base, identical high
{ 9, 12, 13, false }, // completely contains existing
{ 10, 9, 14, false }, // identical base, one-less high
{ 10, 10, 15, false }, // exactly identical to existing range
{ 10, 11, 16, false }, // identical base, one-greater high
{ 11, 8, 17, false }, // contained completely within
{ 11, 9, 18, false }, // one-greater base, identical high
{ 11, 10, 19, false }, // one-greater base, one-greater high
{ 9, 2, 20, false }, // overlaps bottom by one
{ 10, 1, 21, false }, // overlaps bottom by one, contained
{ 19, 1, 22, false }, // overlaps top by one, contained
{ 19, 2, 23, false }, // overlaps top by one
{ 9, 1, 24, true }, // directly below without overlap
{ 20, 1, 25, true }, // directly above without overlap
{ 6, 3, 26, true }, // exactly between two ranges, gapless
{ 7, 3, 27, false }, // tries to span two ranges
{ 7, 5, 28, false }, // tries to span three ranges
{ 4, 20, 29, false }, // tries to contain several ranges
{ 30, 50, 30, true },
{ 90, 25, 31, true },
{ 35, 65, 32, false }, // tries to span two noncontiguous
{ 120, 10000, 33, true }, // > 8-bit
{ 20000, 20000, 34, true }, // > 8-bit
{ 0x10001, 0x10001, 35, true }, // > 16-bit
{ 27, -1, 36, false } // tests high < base
};
// Attempt to fill the entire space. The entire space must be filled with
// three stores because AddressType is signed for these tests, so RangeMap
// treats the size as signed and rejects sizes that appear to be negative.
// Even if these tests were run as unsigned, two stores would be needed
// to fill the space because the entire size of the space could only be
// described by using one more bit than would be present in AddressType.
const RangeTest range_tests_1[] = {
{ INT_MIN, INT_MAX, 50, true }, // From INT_MIN to -2, inclusive
{ -1, 2, 51, true }, // From -1 to 0, inclusive
{ 1, INT_MAX, 52, true }, // From 1 to INT_MAX, inclusive
{ INT_MIN, INT_MAX, 53, false }, // Can't fill the space twice
{ -1, 2, 54, false },
{ 1, INT_MAX, 55, false },
{ -3, 6, 56, false }, // -3 to 2, inclusive - spans 3 ranges
};
// A light round of testing to verify that RetrieveRange does the right
// the right thing at the extremities of the range when nothing is stored
// there. Checks are forced without storing anything at the extremities
// by setting size = 0.
const RangeTest range_tests_2[] = {
{ INT_MIN, 0, 100, false }, // makes RetrieveRange check low end
{ -1, 3, 101, true },
{ INT_MAX, 0, 102, false }, // makes RetrieveRange check high end
};
// Similar to the previous test set, but with a couple of ranges closer
// to the extremities.
const RangeTest range_tests_3[] = {
{ INT_MIN + 1, 1, 110, true },
{ INT_MAX - 1, 1, 111, true },
{ INT_MIN, 0, 112, false }, // makes RetrieveRange check low end
{ INT_MAX, 0, 113, false } // makes RetrieveRange check high end
};
// The range map is cleared between sets of tests listed here.
const RangeTestSet range_test_sets[] = {
{ range_tests_0, sizeof(range_tests_0) / sizeof(RangeTest) },
{ range_tests_1, sizeof(range_tests_1) / sizeof(RangeTest) },
{ range_tests_2, sizeof(range_tests_2) / sizeof(RangeTest) },
{ range_tests_3, sizeof(range_tests_3) / sizeof(RangeTest) },
{ range_tests_0, sizeof(range_tests_0) / sizeof(RangeTest) } // Run again
};
// Maintain the range map in a pointer so that deletion can be meaningfully
// tested.
scoped_ptr<TestMap> range_map(new TestMap());
// Run all of the test sets in sequence.
unsigned int range_test_set_count = sizeof(range_test_sets) /
sizeof(RangeTestSet);
for (unsigned int range_test_set_index = 0;
range_test_set_index < range_test_set_count;
++range_test_set_index) {
const RangeTest* range_tests =
range_test_sets[range_test_set_index].range_tests;
unsigned int range_test_count =
range_test_sets[range_test_set_index].range_test_count;
// Run the StoreRange test, which validates StoreRange and initializes
// the RangeMap with data for the RetrieveRange test.
int stored_count = 0; // The number of ranges successfully stored
for (unsigned int range_test_index = 0;
range_test_index < range_test_count;
++range_test_index) {
const RangeTest* range_test = &range_tests[range_test_index];
if (!StoreTest(range_map.get(), range_test))
return false;
if (range_test->expect_storable)
++stored_count;
}
// There should be exactly one CountedObject for everything successfully
// stored in the RangeMap.
if (CountedObject::count() != stored_count) {
fprintf(stderr, "FAILED: "
"stored object counts don't match, expected %d, observed %d\n",
stored_count,
CountedObject::count());
return false;
}
// The RangeMap's own count of objects should also match.
if (range_map->GetCount() != stored_count) {
fprintf(stderr, "FAILED: stored object count doesn't match GetCount, "
"expected %d, observed %d\n",
stored_count, range_map->GetCount());
return false;
}
// Run the RetrieveRange test
for (unsigned int range_test_index = 0;
range_test_index < range_test_count;
++range_test_index) {
const RangeTest* range_test = &range_tests[range_test_index];
if (!RetrieveTest(range_map.get(), range_test))
return false;
}
if (!RetrieveIndexTest(range_map.get(), range_test_set_index))
return false;
// Clear the map between test sets. If this is the final test set,
// delete the map instead to test destruction.
if (range_test_set_index < range_test_set_count - 1)
range_map->Clear();
else
range_map.reset();
// Test that all stored objects are freed when the RangeMap is cleared
// or deleted.
if (CountedObject::count() != 0) {
fprintf(stderr, "FAILED: "
"did not free all objects after %s, %d still allocated\n",
range_test_set_index < range_test_set_count - 1 ? "clear"
: "delete",
CountedObject::count());
return false;
}
}
if (!RetriveAtIndexTest2()) {
fprintf(stderr, "FAILED: did not pass RetrieveAtIndexTest2()\n");
return false;
}
return true;
}
} // namespace
int main(int argc, char** argv) {
BPLOG_INIT(&argc, &argv);
return RunTests() ? 0 : 1;
}

View file

@ -0,0 +1,371 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// simple_serializer-inl.h: template specializations for following types:
// bool, const char *(C-string), string,
// Line, Function, PublicSymbol, WindowsFrameInfo and their linked pointers.
//
// See simple_serializer.h for moredocumentation.
//
// Author: Siyang Xie (lambxsy@google.com)
#ifndef PROCESSOR_SIMPLE_SERIALIZER_INL_H__
#define PROCESSOR_SIMPLE_SERIALIZER_INL_H__
#include "processor/simple_serializer.h"
#include <cstdint>
#include <string>
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "processor/basic_source_line_resolver_types.h"
#include "processor/linked_ptr.h"
#include "processor/map_serializers-inl.h"
#include "processor/windows_frame_info.h"
namespace google_breakpad {
// Specializations of SimpleSerializer: bool
template<>
class SimpleSerializer<bool> {
public:
static size_t SizeOf(bool boolean) { return 1; }
static char* Write(bool boolean, char* dest) {
*dest = static_cast<char>(boolean? 255 : 0);
return ++dest;
}
static const char* Read(const char* source, bool* value) {
*value = ((*source) == 0 ? false : true);
return ++source;
}
};
// Specializations of SimpleSerializer: string
template<>
class SimpleSerializer<string> {
public:
static size_t SizeOf(const string& str) { return str.size() + 1; }
static char* Write(const string& str, char* dest) {
strcpy(dest, str.c_str());
return dest + SizeOf(str);
}
};
// Specializations of SimpleSerializer: C-string
template<>
class SimpleSerializer<const char*> {
public:
static size_t SizeOf(const char* cstring) {
return strlen(cstring) + 1;
}
static char* Write(const char* cstring, char* dest) {
strcpy(dest, cstring);
return dest + SizeOf(cstring);
}
};
// Specializations of SimpleSerializer: Line
template<>
class SimpleSerializer<BasicSourceLineResolver::Line> {
typedef BasicSourceLineResolver::Line Line;
public:
static size_t SizeOf(const Line& line) {
return SimpleSerializer<MemAddr>::SizeOf(line.address)
+ SimpleSerializer<MemAddr>::SizeOf(line.size)
+ SimpleSerializer<int32_t>::SizeOf(line.source_file_id)
+ SimpleSerializer<int32_t>::SizeOf(line.line);
}
static char* Write(const Line& line, char* dest) {
dest = SimpleSerializer<MemAddr>::Write(line.address, dest);
dest = SimpleSerializer<MemAddr>::Write(line.size, dest);
dest = SimpleSerializer<int32_t>::Write(line.source_file_id, dest);
dest = SimpleSerializer<int32_t>::Write(line.line, dest);
return dest;
}
};
// Specializations of SimpleSerializer: InlineOrigin
template <>
class SimpleSerializer<BasicSourceLineResolver::InlineOrigin> {
typedef BasicSourceLineResolver::InlineOrigin InlineOrigin;
public:
static size_t SizeOf(const InlineOrigin& origin) {
return SimpleSerializer<bool>::SizeOf(origin.has_file_id) +
SimpleSerializer<int32_t>::SizeOf(origin.source_file_id) +
SimpleSerializer<string>::SizeOf(origin.name);
}
static char* Write(const InlineOrigin& origin, char* dest) {
dest = SimpleSerializer<bool>::Write(origin.has_file_id, dest);
dest = SimpleSerializer<int32_t>::Write(origin.source_file_id, dest);
dest = SimpleSerializer<string>::Write(origin.name, dest);
return dest;
}
};
// Specializations of SimpleSerializer: PublicSymbol
template<>
class SimpleSerializer<BasicSourceLineResolver::PublicSymbol> {
typedef BasicSourceLineResolver::PublicSymbol PublicSymbol;
public:
static size_t SizeOf(const PublicSymbol& pubsymbol) {
return SimpleSerializer<string>::SizeOf(pubsymbol.name)
+ SimpleSerializer<MemAddr>::SizeOf(pubsymbol.address)
+ SimpleSerializer<int32_t>::SizeOf(pubsymbol.parameter_size)
+ SimpleSerializer<bool>::SizeOf(pubsymbol.is_multiple);
}
static char* Write(const PublicSymbol& pubsymbol, char* dest) {
dest = SimpleSerializer<string>::Write(pubsymbol.name, dest);
dest = SimpleSerializer<MemAddr>::Write(pubsymbol.address, dest);
dest = SimpleSerializer<int32_t>::Write(pubsymbol.parameter_size, dest);
dest = SimpleSerializer<bool>::Write(pubsymbol.is_multiple, dest);
return dest;
}
};
// Specializations of SimpleSerializer: WindowsFrameInfo
template<>
class SimpleSerializer<WindowsFrameInfo> {
public:
static size_t SizeOf(const WindowsFrameInfo& wfi) {
unsigned int size = 0;
size += sizeof(int32_t); // wfi.type_
size += SimpleSerializer<int32_t>::SizeOf(wfi.valid);
size += SimpleSerializer<uint32_t>::SizeOf(wfi.prolog_size);
size += SimpleSerializer<uint32_t>::SizeOf(wfi.epilog_size);
size += SimpleSerializer<uint32_t>::SizeOf(wfi.parameter_size);
size += SimpleSerializer<uint32_t>::SizeOf(wfi.saved_register_size);
size += SimpleSerializer<uint32_t>::SizeOf(wfi.local_size);
size += SimpleSerializer<uint32_t>::SizeOf(wfi.max_stack_size);
size += SimpleSerializer<bool>::SizeOf(wfi.allocates_base_pointer);
size += SimpleSerializer<string>::SizeOf(wfi.program_string);
return size;
}
static char* Write(const WindowsFrameInfo& wfi, char* dest) {
dest = SimpleSerializer<int32_t>::Write(
static_cast<const int32_t>(wfi.type_), dest);
dest = SimpleSerializer<int32_t>::Write(wfi.valid, dest);
dest = SimpleSerializer<uint32_t>::Write(wfi.prolog_size, dest);
dest = SimpleSerializer<uint32_t>::Write(wfi.epilog_size, dest);
dest = SimpleSerializer<uint32_t>::Write(wfi.parameter_size, dest);
dest = SimpleSerializer<uint32_t>::Write(wfi.saved_register_size, dest);
dest = SimpleSerializer<uint32_t>::Write(wfi.local_size, dest);
dest = SimpleSerializer<uint32_t>::Write(wfi.max_stack_size, dest);
dest = SimpleSerializer<bool>::Write(wfi.allocates_base_pointer, dest);
return SimpleSerializer<string>::Write(wfi.program_string, dest);
}
};
// Specializations of SimpleSerializer: Linked_ptr version of
// Line, InlineOrigin, Inline, Function, PublicSymbol, WindowsFrameInfo.
template<>
class SimpleSerializer< linked_ptr<BasicSourceLineResolver::Line> > {
typedef BasicSourceLineResolver::Line Line;
public:
static size_t SizeOf(const linked_ptr<Line>& lineptr) {
if (lineptr.get() == NULL) return 0;
return SimpleSerializer<Line>::SizeOf(*(lineptr.get()));
}
static char* Write(const linked_ptr<Line>& lineptr, char* dest) {
if (lineptr.get())
dest = SimpleSerializer<Line>::Write(*(lineptr.get()), dest);
return dest;
}
};
template <>
class SimpleSerializer<linked_ptr<BasicSourceLineResolver::InlineOrigin>> {
typedef BasicSourceLineResolver::InlineOrigin InlineOrigin;
public:
static size_t SizeOf(const linked_ptr<InlineOrigin>& origin_ptr) {
if (origin_ptr.get() == NULL)
return 0;
return SimpleSerializer<InlineOrigin>::SizeOf(*(origin_ptr.get()));
}
static char* Write(const linked_ptr<InlineOrigin>& origin_ptr, char* dest) {
if (origin_ptr.get())
dest = SimpleSerializer<InlineOrigin>::Write(*(origin_ptr.get()), dest);
return dest;
}
};
// Specializations of SimpleSerializer: Inline
template <>
class SimpleSerializer<linked_ptr<BasicSourceLineResolver::Inline>>;
template <>
class SimpleSerializer<BasicSourceLineResolver::Inline> {
typedef BasicSourceLineResolver::Inline Inline;
public:
inline static size_t SizeOf(const Inline& in);
inline static char* Write(const Inline& in, char* dest);
};
template <>
class SimpleSerializer<linked_ptr<BasicSourceLineResolver::Inline>> {
typedef BasicSourceLineResolver::Inline Inline;
public:
static size_t SizeOf(const linked_ptr<Inline>& inline_ptr) {
if (inline_ptr.get() == NULL)
return 0;
return SimpleSerializer<Inline>::SizeOf(*(inline_ptr.get()));
}
static char* Write(const linked_ptr<Inline>& inline_ptr, char* dest) {
if (inline_ptr.get())
dest = SimpleSerializer<Inline>::Write(*(inline_ptr.get()), dest);
return dest;
}
};
size_t SimpleSerializer<BasicSourceLineResolver::Inline>::SizeOf(
const Inline& in) {
return SimpleSerializer<bool>::SizeOf(in.has_call_site_file_id) +
SimpleSerializer<int32_t>::SizeOf(in.inline_nest_level) +
SimpleSerializer<int32_t>::SizeOf(in.call_site_line) +
SimpleSerializer<int32_t>::SizeOf(in.call_site_file_id) +
SimpleSerializer<int32_t>::SizeOf(in.origin_id) +
sizeof(uint32_t) + // This is to store the size of inline_ranges.
(in.inline_ranges.size() * sizeof(MemAddr) * 2);
}
char* SimpleSerializer<BasicSourceLineResolver::Inline>::Write(const Inline& in,
char* dest) {
dest = SimpleSerializer<bool>::Write(in.has_call_site_file_id, dest);
dest = SimpleSerializer<int32_t>::Write(in.inline_nest_level, dest);
dest = SimpleSerializer<int32_t>::Write(in.call_site_line, dest);
dest = SimpleSerializer<int32_t>::Write(in.call_site_file_id, dest);
dest = SimpleSerializer<int32_t>::Write(in.origin_id, dest);
// Write the size of inline_ranges.
dest = SimpleSerializer<int32_t>::Write(in.inline_ranges.size(), dest);
for (const std::pair<MemAddr, MemAddr>& range : in.inline_ranges) {
dest = SimpleSerializer<MemAddr>::Write(range.first, dest);
dest = SimpleSerializer<MemAddr>::Write(range.second, dest);
}
return dest;
}
template<>
class SimpleSerializer<BasicSourceLineResolver::Function> {
// Convenient type names.
typedef BasicSourceLineResolver::Function Function;
typedef BasicSourceLineResolver::Line Line;
typedef BasicSourceLineResolver::Inline Inline;
public:
static size_t SizeOf(const Function& func) {
unsigned int size = 0;
size += SimpleSerializer<string>::SizeOf(func.name);
size += SimpleSerializer<MemAddr>::SizeOf(func.address);
size += SimpleSerializer<MemAddr>::SizeOf(func.size);
size += SimpleSerializer<int32_t>::SizeOf(func.parameter_size);
size += SimpleSerializer<bool>::SizeOf(func.is_multiple);
// This extra size is used to store the size of serialized func.inlines, so
// we know where to start de-serialize func.lines.
size += sizeof(int32_t);
size += inline_range_map_serializer_.SizeOf(&func.inlines);
size += range_map_serializer_.SizeOf(func.lines);
return size;
}
static char* Write(const Function& func, char* dest) {
dest = SimpleSerializer<string>::Write(func.name, dest);
dest = SimpleSerializer<MemAddr>::Write(func.address, dest);
dest = SimpleSerializer<MemAddr>::Write(func.size, dest);
dest = SimpleSerializer<int32_t>::Write(func.parameter_size, dest);
dest = SimpleSerializer<bool>::Write(func.is_multiple, dest);
char* old_dest = dest;
dest += sizeof(int32_t);
dest = inline_range_map_serializer_.Write(&func.inlines, dest);
// Write the size of serialized func.inlines. The size doesn't include size
// field itself.
SimpleSerializer<MemAddr>::Write(dest - old_dest - sizeof(int32_t),
old_dest);
dest = range_map_serializer_.Write(func.lines, dest);
return dest;
}
private:
// This static member is defined in module_serializer.cc.
static RangeMapSerializer<MemAddr, linked_ptr<Line>> range_map_serializer_;
static ContainedRangeMapSerializer<MemAddr, linked_ptr<Inline>>
inline_range_map_serializer_;
};
template<>
class SimpleSerializer< linked_ptr<BasicSourceLineResolver::Function> > {
typedef BasicSourceLineResolver::Function Function;
public:
static size_t SizeOf(const linked_ptr<Function>& func) {
if (!func.get()) return 0;
return SimpleSerializer<Function>::SizeOf(*(func.get()));
}
static char* Write(const linked_ptr<Function>& func, char* dest) {
if (func.get())
dest = SimpleSerializer<Function>::Write(*(func.get()), dest);
return dest;
}
};
template<>
class SimpleSerializer< linked_ptr<BasicSourceLineResolver::PublicSymbol> > {
typedef BasicSourceLineResolver::PublicSymbol PublicSymbol;
public:
static size_t SizeOf(const linked_ptr<PublicSymbol>& pubsymbol) {
if (pubsymbol.get() == NULL) return 0;
return SimpleSerializer<PublicSymbol>::SizeOf(*(pubsymbol.get()));
}
static char* Write(const linked_ptr<PublicSymbol>& pubsymbol, char* dest) {
if (pubsymbol.get())
dest = SimpleSerializer<PublicSymbol>::Write(*(pubsymbol.get()), dest);
return dest;
}
};
template<>
class SimpleSerializer< linked_ptr<WindowsFrameInfo> > {
public:
static size_t SizeOf(const linked_ptr<WindowsFrameInfo>& wfi) {
if (wfi.get() == NULL) return 0;
return SimpleSerializer<WindowsFrameInfo>::SizeOf(*(wfi.get()));
}
static char* Write(const linked_ptr<WindowsFrameInfo>& wfi, char* dest) {
if (wfi.get())
dest = SimpleSerializer<WindowsFrameInfo>::Write(*(wfi.get()), dest);
return dest;
}
};
} // namespace google_breakpad
#endif // PROCESSOR_SIMPLE_SERIALIZER_INL_H__

View file

@ -0,0 +1,64 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
//
// simple_serializer.h: SimpleSerializer is a template for calculating size and
// writing to specific memory location for objects of primitive types, C-style
// string, string, breakpad types/structs etc.
// All specializations of SimpleSerializer template are defined in the
// "simple_serializer-inl.h" file.
//
// Author: Siyang Xie (lambxsy@google.com)
#ifndef PROCESSOR_SIMPLE_SERIALIZER_H__
#define PROCESSOR_SIMPLE_SERIALIZER_H__
#include <stddef.h>
#include "google_breakpad/common/breakpad_types.h"
namespace google_breakpad {
typedef uint64_t MemAddr;
// Default implementation of SimpleSerializer template.
// Specializations are defined in "simple_serializer-inl.h".
template<class Type> class SimpleSerializer {
public:
// Calculate and return the size of the 'item'.
static size_t SizeOf(const Type& item) { return sizeof(item); }
// Write 'item' to memory location 'dest', and return to the "end" address of
// data written, i.e., the address after the final byte written.
static char* Write(const Type& item, char* dest) {
new (dest) Type(item);
return dest + SizeOf(item);
}
};
} // namespace google_breakpad
#endif // PROCESSOR_SIMPLE_SERIALIZER_H__

View file

@ -0,0 +1,207 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// simple_symbol_supplier.cc: A simple SymbolSupplier implementation
//
// See simple_symbol_supplier.h for documentation.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "processor/simple_symbol_supplier.h"
#include <assert.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <algorithm>
#include <iostream>
#include <fstream>
#include "common/using_std_string.h"
#include "google_breakpad/processor/code_module.h"
#include "google_breakpad/processor/system_info.h"
#include "processor/logging.h"
#include "processor/pathname_stripper.h"
namespace google_breakpad {
static bool file_exists(const string& file_name) {
struct stat sb;
return stat(file_name.c_str(), &sb) == 0;
}
SymbolSupplier::SymbolResult SimpleSymbolSupplier::GetSymbolFile(
const CodeModule* module, const SystemInfo* system_info,
string* symbol_file) {
BPLOG_IF(ERROR, !symbol_file) << "SimpleSymbolSupplier::GetSymbolFile "
"requires |symbol_file|";
assert(symbol_file);
symbol_file->clear();
for (unsigned int path_index = 0; path_index < paths_.size(); ++path_index) {
SymbolResult result;
if ((result = GetSymbolFileAtPathFromRoot(module, system_info,
paths_[path_index],
symbol_file)) != NOT_FOUND) {
return result;
}
}
return NOT_FOUND;
}
SymbolSupplier::SymbolResult SimpleSymbolSupplier::GetSymbolFile(
const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file,
string* symbol_data) {
assert(symbol_data);
symbol_data->clear();
SymbolSupplier::SymbolResult s = GetSymbolFile(module, system_info,
symbol_file);
if (s == FOUND) {
std::ifstream in(symbol_file->c_str());
std::getline(in, *symbol_data, string::traits_type::to_char_type(
string::traits_type::eof()));
in.close();
}
return s;
}
SymbolSupplier::SymbolResult SimpleSymbolSupplier::GetCStringSymbolData(
const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file,
char** symbol_data,
size_t* symbol_data_size) {
assert(symbol_data);
assert(symbol_data_size);
string symbol_data_string;
SymbolSupplier::SymbolResult s =
GetSymbolFile(module, system_info, symbol_file, &symbol_data_string);
if (s == FOUND) {
*symbol_data_size = symbol_data_string.size() + 1;
*symbol_data = new char[*symbol_data_size];
if (*symbol_data == NULL) {
BPLOG(ERROR) << "Memory allocation for size " << *symbol_data_size
<< " failed";
return INTERRUPT;
}
memcpy(*symbol_data, symbol_data_string.c_str(), symbol_data_string.size());
(*symbol_data)[symbol_data_string.size()] = '\0';
memory_buffers_.insert(make_pair(module->code_file(), *symbol_data));
}
return s;
}
void SimpleSymbolSupplier::FreeSymbolData(const CodeModule* module) {
if (!module) {
BPLOG(INFO) << "Cannot free symbol data buffer for NULL module";
return;
}
map<string, char*>::iterator it = memory_buffers_.find(module->code_file());
if (it == memory_buffers_.end()) {
BPLOG(INFO) << "Cannot find symbol data buffer for module "
<< module->code_file();
return;
}
delete [] it->second;
memory_buffers_.erase(it);
}
SymbolSupplier::SymbolResult SimpleSymbolSupplier::GetSymbolFileAtPathFromRoot(
const CodeModule* module, const SystemInfo* system_info,
const string& root_path, string* symbol_file) {
BPLOG_IF(ERROR, !symbol_file) << "SimpleSymbolSupplier::GetSymbolFileAtPath "
"requires |symbol_file|";
assert(symbol_file);
symbol_file->clear();
if (!module)
return NOT_FOUND;
// Start with the base path.
string path = root_path;
// Append the debug (pdb) file name as a directory name.
path.append("/");
string debug_file_name = PathnameStripper::File(module->debug_file());
if (debug_file_name.empty()) {
BPLOG(ERROR) << "Can't construct symbol file path without debug_file "
"(code_file = " <<
PathnameStripper::File(module->code_file()) << ")";
return NOT_FOUND;
}
path.append(debug_file_name);
// Append the identifier as a directory name.
path.append("/");
string identifier = module->debug_identifier();
if (identifier.empty()) {
BPLOG(ERROR) << "Can't construct symbol file path without debug_identifier "
"(code_file = " <<
PathnameStripper::File(module->code_file()) <<
", debug_file = " << debug_file_name << ")";
return NOT_FOUND;
}
path.append(identifier);
// Transform the debug file name into one ending in .sym. If the existing
// name ends in .pdb, strip the .pdb. Otherwise, add .sym to the non-.pdb
// name.
path.append("/");
string debug_file_extension;
if (debug_file_name.size() > 4)
debug_file_extension = debug_file_name.substr(debug_file_name.size() - 4);
std::transform(debug_file_extension.begin(), debug_file_extension.end(),
debug_file_extension.begin(), tolower);
if (debug_file_extension == ".pdb") {
path.append(debug_file_name.substr(0, debug_file_name.size() - 4));
} else {
path.append(debug_file_name);
}
path.append(".sym");
if (!file_exists(path)) {
BPLOG(INFO) << "No symbol file at " << path;
return NOT_FOUND;
}
*symbol_file = path;
return FOUND;
}
} // namespace google_breakpad

View file

@ -0,0 +1,139 @@
// Copyright 2006 Google LLC
//
// 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 LLC 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.
// simple_symbol_supplier.h: A simple SymbolSupplier implementation
//
// SimpleSymbolSupplier is a straightforward implementation of SymbolSupplier
// that stores symbol files in a filesystem tree. A SimpleSymbolSupplier is
// created with one or more base directories, which are the root paths for all
// symbol files. Each symbol file contained therein has a directory entry in
// the base directory with a name identical to the corresponding debugging
// file (pdb). Within each of these directories, there are subdirectories
// named for the debugging file's identifier. For recent pdb files, this is
// a concatenation of the pdb's uuid and age, presented in hexadecimal form,
// without any dashes or separators. The uuid is in uppercase hexadecimal
// and the age is in lowercase hexadecimal. Within that subdirectory,
// SimpleSymbolSupplier expects to find the symbol file, which is named
// identically to the debug file, but with a .sym extension. If the original
// debug file had a name ending in .pdb, the .pdb extension will be replaced
// with .sym. This sample hierarchy is rooted at the "symbols" base
// directory:
//
// symbols
// symbols/test_app.pdb
// symbols/test_app.pdb/63FE4780728D49379B9D7BB6460CB42A1
// symbols/test_app.pdb/63FE4780728D49379B9D7BB6460CB42A1/test_app.sym
// symbols/kernel32.pdb
// symbols/kernel32.pdb/BCE8785C57B44245A669896B6A19B9542
// symbols/kernel32.pdb/BCE8785C57B44245A669896B6A19B9542/kernel32.sym
//
// In this case, the uuid of test_app.pdb is
// 63fe4780-728d-4937-9b9d-7bb6460cb42a and its age is 1.
//
// This scheme was chosen to be roughly analogous to the way that
// symbol files may be accessed from Microsoft Symbol Server. A hierarchy
// used for Microsoft Symbol Server storage is usable as a hierarchy for
// SimpleSymbolServer, provided that the pdb files are transformed to dumped
// format using a tool such as dump_syms, and given a .sym extension.
//
// SimpleSymbolSupplier will iterate over all root paths searching for
// a symbol file existing in that path.
//
// SimpleSymbolSupplier supports any debugging file which can be identified
// by a CodeModule object's debug_file and debug_identifier accessors. The
// expected ultimate source of these CodeModule objects are MinidumpModule
// objects; it is this class that is responsible for assigning appropriate
// values for debug_file and debug_identifier.
//
// Author: Mark Mentovai
#ifndef PROCESSOR_SIMPLE_SYMBOL_SUPPLIER_H__
#define PROCESSOR_SIMPLE_SYMBOL_SUPPLIER_H__
#include <map>
#include <string>
#include <vector>
#include "common/using_std_string.h"
#include "google_breakpad/processor/symbol_supplier.h"
namespace google_breakpad {
using std::map;
using std::vector;
class CodeModule;
class SimpleSymbolSupplier : public SymbolSupplier {
public:
// Creates a new SimpleSymbolSupplier, using path as the root path where
// symbols are stored.
explicit SimpleSymbolSupplier(const string& path) : paths_(1, path) {}
// Creates a new SimpleSymbolSupplier, using paths as a list of root
// paths where symbols may be stored.
explicit SimpleSymbolSupplier(const vector<string>& paths) : paths_(paths) {}
virtual ~SimpleSymbolSupplier() {}
// Returns the path to the symbol file for the given module. See the
// description above.
virtual SymbolResult GetSymbolFile(const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file);
virtual SymbolResult GetSymbolFile(const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file,
string* symbol_data);
// Allocates data buffer on heap and writes symbol data into buffer.
// Symbol supplier ALWAYS takes ownership of the data buffer.
virtual SymbolResult GetCStringSymbolData(const CodeModule* module,
const SystemInfo* system_info,
string* symbol_file,
char** symbol_data,
size_t* symbol_data_size);
// Free the data buffer allocated in the above GetCStringSymbolData();
virtual void FreeSymbolData(const CodeModule* module);
protected:
SymbolResult GetSymbolFileAtPathFromRoot(const CodeModule* module,
const SystemInfo* system_info,
const string& root_path,
string* symbol_file);
private:
map<string, char*> memory_buffers_;
vector<string> paths_;
};
} // namespace google_breakpad
#endif // PROCESSOR_SIMPLE_SYMBOL_SUPPLIER_H__

View file

@ -0,0 +1,351 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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_line_resolver_base.cc: Implementation of SourceLineResolverBase.
//
// See source_line_resolver_base.h and source_line_resolver_base_types.h for
// more documentation.
//
// Author: Siyang Xie (lambxsy@google.com)
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <map>
#include <utility>
#include "google_breakpad/processor/source_line_resolver_base.h"
#include "processor/logging.h"
#include "processor/module_factory.h"
#include "processor/source_line_resolver_base_types.h"
using std::make_pair;
namespace google_breakpad {
SourceLineResolverBase::SourceLineResolverBase(
ModuleFactory* module_factory)
: modules_(new ModuleMap),
corrupt_modules_(new ModuleSet),
memory_buffers_(new MemoryMap),
module_factory_(module_factory) {
}
SourceLineResolverBase::~SourceLineResolverBase() {
ModuleMap::iterator it;
// Iterate through ModuleMap and delete all loaded modules.
for (it = modules_->begin(); it != modules_->end(); ++it) {
// Delete individual module.
delete it->second;
}
// Delete the map of modules.
delete modules_;
modules_ = NULL;
// Delete the set of corrupt modules.
delete corrupt_modules_;
corrupt_modules_ = NULL;
MemoryMap::iterator iter = memory_buffers_->begin();
for (; iter != memory_buffers_->end(); ++iter) {
delete [] iter->second;
}
// Delete the map of memory buffers.
delete memory_buffers_;
memory_buffers_ = NULL;
delete module_factory_;
module_factory_ = NULL;
}
bool SourceLineResolverBase::ReadSymbolFile(const string& map_file,
char** symbol_data,
size_t* symbol_data_size) {
if (symbol_data == NULL || symbol_data_size == NULL) {
BPLOG(ERROR) << "Could not Read file into Null memory pointer";
return false;
}
struct stat buf;
int error_code = stat(map_file.c_str(), &buf);
if (error_code == -1) {
string error_string;
error_code = ErrnoString(&error_string);
BPLOG(ERROR) << "Could not open " << map_file <<
", error " << error_code << ": " << error_string;
return false;
}
off_t file_size = buf.st_size;
// Allocate memory for file contents, plus a null terminator
// since we may use strtok() on the contents.
*symbol_data_size = file_size + 1;
*symbol_data = new char[file_size + 1];
if (*symbol_data == NULL) {
BPLOG(ERROR) << "Could not allocate memory for " << map_file;
return false;
}
BPLOG(INFO) << "Opening " << map_file;
FILE* f = fopen(map_file.c_str(), "rt");
if (!f) {
string error_string;
error_code = ErrnoString(&error_string);
BPLOG(ERROR) << "Could not open " << map_file <<
", error " << error_code << ": " << error_string;
delete [] (*symbol_data);
*symbol_data = NULL;
return false;
}
AutoFileCloser closer(f);
int items_read = 0;
items_read = fread(*symbol_data, 1, file_size, f);
if (items_read != file_size) {
string error_string;
error_code = ErrnoString(&error_string);
BPLOG(ERROR) << "Could not slurp " << map_file <<
", error " << error_code << ": " << error_string;
delete [] (*symbol_data);
*symbol_data = NULL;
return false;
}
(*symbol_data)[file_size] = '\0';
return true;
}
bool SourceLineResolverBase::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->code_file()) != modules_->end()) {
BPLOG(INFO) << "Symbols for module " << module->code_file()
<< " already loaded";
return false;
}
BPLOG(INFO) << "Loading symbols for module " << module->code_file()
<< " from " << map_file;
char* memory_buffer;
size_t memory_buffer_size;
if (!ReadSymbolFile(map_file, &memory_buffer, &memory_buffer_size))
return false;
BPLOG(INFO) << "Read symbol file " << map_file << " succeeded. "
<< "module = " << module->code_file()
<< ", memory_buffer_size = " << memory_buffer_size;
bool load_result = LoadModuleUsingMemoryBuffer(module, memory_buffer,
memory_buffer_size);
if (load_result && !ShouldDeleteMemoryBufferAfterLoadModule()) {
// memory_buffer has to stay alive as long as the module.
memory_buffers_->insert(make_pair(module->code_file(), memory_buffer));
} else {
delete [] memory_buffer;
}
return load_result;
}
bool SourceLineResolverBase::LoadModuleUsingMapBuffer(
const CodeModule* module, const string& map_buffer) {
BPLOG(INFO) << "SourceLineResolverBase::LoadModuleUsingMapBuffer(module = "
<< module->code_file()
<< ", map_buffer.size() = " << map_buffer.size() << ")";
if (module == NULL)
return false;
// Make sure we don't already have a module with the given name.
if (modules_->find(module->code_file()) != modules_->end()) {
BPLOG(INFO) << "Symbols for module " << module->code_file()
<< " already loaded";
return false;
}
size_t memory_buffer_size = map_buffer.size() + 1;
char* memory_buffer = new char[memory_buffer_size];
if (memory_buffer == NULL) {
BPLOG(ERROR) << "Could not allocate memory for " << module->code_file();
return false;
}
// Can't use strcpy, as the data may contain '\0's before the end.
memcpy(memory_buffer, map_buffer.c_str(), map_buffer.size());
memory_buffer[map_buffer.size()] = '\0';
bool load_result = LoadModuleUsingMemoryBuffer(module, memory_buffer,
memory_buffer_size);
if (load_result && !ShouldDeleteMemoryBufferAfterLoadModule()) {
// memory_buffer has to stay alive as long as the module.
memory_buffers_->insert(make_pair(module->code_file(), memory_buffer));
} else {
delete [] memory_buffer;
}
return load_result;
}
bool SourceLineResolverBase::LoadModuleUsingMemoryBuffer(
const CodeModule* module,
char* memory_buffer,
size_t memory_buffer_size) {
if (!module)
return false;
// Make sure we don't already have a module with the given name.
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->code_file()
<< " from memory buffer, size: " << memory_buffer_size;
Module* basic_module = module_factory_->CreateModule(module->code_file());
// Ownership of memory is NOT transfered to Module::LoadMapFromMemory().
if (!basic_module->LoadMapFromMemory(memory_buffer, memory_buffer_size)) {
BPLOG(ERROR) << "Too many error while parsing symbol data for module "
<< module->code_file();
// Returning false from here would be an indication that the symbols for
// this module are missing which would be wrong. Intentionally fall through
// and add the module to both the modules_ and the corrupt_modules_ lists.
assert(basic_module->IsCorrupt());
}
modules_->insert(make_pair(module->code_file(), basic_module));
if (basic_module->IsCorrupt()) {
corrupt_modules_->insert(module->code_file());
}
return true;
}
bool SourceLineResolverBase::ShouldDeleteMemoryBufferAfterLoadModule() {
return true;
}
void SourceLineResolverBase::UnloadModule(const CodeModule* code_module) {
if (!code_module)
return;
ModuleMap::iterator mod_iter = modules_->find(code_module->code_file());
if (mod_iter != modules_->end()) {
Module* symbol_module = mod_iter->second;
delete symbol_module;
corrupt_modules_->erase(mod_iter->first);
modules_->erase(mod_iter);
}
if (ShouldDeleteMemoryBufferAfterLoadModule()) {
// No-op. Because we never store any memory buffers.
} else {
// There may be a buffer stored locally, we need to find and delete it.
MemoryMap::iterator iter = memory_buffers_->find(code_module->code_file());
if (iter != memory_buffers_->end()) {
delete [] iter->second;
memory_buffers_->erase(iter);
}
}
}
bool SourceLineResolverBase::HasModule(const CodeModule* module) {
if (!module)
return false;
return modules_->find(module->code_file()) != modules_->end();
}
bool SourceLineResolverBase::IsModuleCorrupt(const CodeModule* module) {
if (!module)
return false;
return corrupt_modules_->find(module->code_file()) != corrupt_modules_->end();
}
void SourceLineResolverBase::FillSourceLineInfo(
StackFrame* frame,
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) {
if (frame->module) {
ModuleMap::const_iterator it = modules_->find(frame->module->code_file());
if (it != modules_->end()) {
it->second->LookupAddress(frame, inlined_frames);
}
}
}
WindowsFrameInfo* SourceLineResolverBase::FindWindowsFrameInfo(
const StackFrame* frame) {
if (frame->module) {
ModuleMap::const_iterator it = modules_->find(frame->module->code_file());
if (it != modules_->end()) {
return it->second->FindWindowsFrameInfo(frame);
}
}
return NULL;
}
CFIFrameInfo* SourceLineResolverBase::FindCFIFrameInfo(
const StackFrame* frame) {
if (frame->module) {
ModuleMap::const_iterator it = modules_->find(frame->module->code_file());
if (it != modules_->end()) {
return it->second->FindCFIFrameInfo(frame);
}
}
return NULL;
}
bool SourceLineResolverBase::CompareString::operator()(
const string& s1, const string& s2) const {
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool SourceLineResolverBase::Module::ParseCFIRuleSet(
const string& rule_set, CFIFrameInfo* frame_info) const {
CFIFrameInfoParseHandler handler(frame_info);
CFIRuleParser parser(&handler);
return parser.Parse(rule_set);
}
} // namespace google_breakpad

View file

@ -0,0 +1,209 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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_line_resolver_base_types.h: definition of nested classes/structs in
// SourceLineResolverBase. It moves the definitions out of
// source_line_resolver_base.cc, so that other classes may have access
// to these private nested types without including source_line_resolver_base.cc
// In addition, Module is defined as a pure abstract class to be implemented by
// each concrete source line resolver class.
//
// See source_line_resolver_base.h for more documentation.
//
// Author: Siyang Xie (lambxsy@google.com)
#include <stdio.h>
#include <deque>
#include <map>
#include <memory>
#include <string>
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/processor/source_line_resolver_base.h"
#include "google_breakpad/processor/stack_frame.h"
#include "processor/cfi_frame_info.h"
#include "processor/linked_ptr.h"
#include "processor/range_map.h"
#include "processor/windows_frame_info.h"
#ifndef PROCESSOR_SOURCE_LINE_RESOLVER_BASE_TYPES_H__
#define PROCESSOR_SOURCE_LINE_RESOLVER_BASE_TYPES_H__
namespace google_breakpad {
class SourceLineResolverBase::AutoFileCloser {
public:
explicit AutoFileCloser(FILE* file) : file_(file) {}
~AutoFileCloser() {
if (file_)
fclose(file_);
}
private:
FILE* file_;
};
struct SourceLineResolverBase::InlineOrigin {
InlineOrigin() {}
InlineOrigin(bool has_file_id, int32_t source_file_id, const string& name)
: has_file_id(has_file_id),
source_file_id(source_file_id),
name(name) {}
// If it's old format, source file id is set, otherwise not useful.
bool has_file_id;
int32_t source_file_id;
string name;
};
struct SourceLineResolverBase::Inline {
// A vector of (address, size) pair for a INLINE record.
using InlineRanges = std::vector<std::pair<MemAddr, MemAddr>>;
Inline() {}
Inline(bool has_call_site_file_id,
int32_t inline_nest_level,
int32_t call_site_line,
int32_t call_site_file_id,
int32_t origin_id,
InlineRanges inline_ranges)
: has_call_site_file_id(has_call_site_file_id),
inline_nest_level(inline_nest_level),
call_site_line(call_site_line),
call_site_file_id(call_site_file_id),
origin_id(origin_id),
inline_ranges(inline_ranges) {}
// If it's new format, call site file id is set, otherwise not useful.
bool has_call_site_file_id;
int32_t inline_nest_level;
int32_t call_site_line;
int32_t call_site_file_id;
int32_t origin_id;
InlineRanges inline_ranges;
};
struct SourceLineResolverBase::Line {
Line() { }
Line(MemAddr addr, MemAddr code_size, int file_id, int source_line)
: address(addr)
, size(code_size)
, source_file_id(file_id)
, line(source_line) { }
MemAddr address;
MemAddr size;
int32_t source_file_id;
int32_t line;
};
struct SourceLineResolverBase::Function {
Function() { }
Function(const string& function_name,
MemAddr function_address,
MemAddr code_size,
int set_parameter_size,
bool is_multiple)
: name(function_name), address(function_address), size(code_size),
parameter_size(set_parameter_size), is_multiple(is_multiple) { }
string name;
MemAddr address;
MemAddr size;
// The size of parameters passed to this function on the stack.
int32_t parameter_size;
// If the function's instructions correspond to multiple symbols.
bool is_multiple;
};
struct SourceLineResolverBase::PublicSymbol {
PublicSymbol() { }
PublicSymbol(const string& set_name,
MemAddr set_address,
int set_parameter_size,
bool is_multiple)
: name(set_name),
address(set_address),
parameter_size(set_parameter_size),
is_multiple(is_multiple) {}
string name;
MemAddr address;
// If the public symbol is used as a function entry point, parameter_size
// is set to the size of the parameters passed to the funciton on the
// stack, if known.
int32_t parameter_size;
// If the function's instructions correspond to multiple symbols.
bool is_multiple;
};
class SourceLineResolverBase::Module {
public:
virtual ~Module() { };
// Loads a map from the given buffer in char* type.
// Does NOT take ownership of memory_buffer (the caller, source line resolver,
// is the owner of memory_buffer).
// The passed in |memory buffer| is of size |memory_buffer_size|. If it is
// not null terminated, LoadMapFromMemory will null terminate it by modifying
// the passed in buffer.
virtual bool LoadMapFromMemory(char* memory_buffer,
size_t memory_buffer_size) = 0;
// Tells whether the loaded symbol data is corrupt. Return value is
// undefined, if the symbol data hasn't been loaded yet.
virtual bool IsCorrupt() const = 0;
// Looks up the given relative address, and fills the StackFrame struct
// with the result.
virtual void LookupAddress(
StackFrame* frame,
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) const = 0;
// If Windows stack walking information is available covering 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;
// 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;
protected:
virtual bool ParseCFIRuleSet(const string& rule_set,
CFIFrameInfo* frame_info) const;
};
} // namespace google_breakpad
#endif // PROCESSOR_SOURCE_LINE_RESOLVER_BASE_TYPES_H__

View file

@ -0,0 +1,82 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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.
// stack_frame_cpu.h: CPU-specific StackFrame extensions.
//
// See google_breakpad/processor/stack_frame_cpu.h for documentation.
//
// Author: Colin Blundell
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/stack_frame_cpu.h"
namespace google_breakpad {
const uint64_t StackFrameARM64::CONTEXT_VALID_X0;
const uint64_t StackFrameARM64::CONTEXT_VALID_X1;
const uint64_t StackFrameARM64::CONTEXT_VALID_X2;
const uint64_t StackFrameARM64::CONTEXT_VALID_X3;
const uint64_t StackFrameARM64::CONTEXT_VALID_X4;
const uint64_t StackFrameARM64::CONTEXT_VALID_X5;
const uint64_t StackFrameARM64::CONTEXT_VALID_X6;
const uint64_t StackFrameARM64::CONTEXT_VALID_X7;
const uint64_t StackFrameARM64::CONTEXT_VALID_X8;
const uint64_t StackFrameARM64::CONTEXT_VALID_X9;
const uint64_t StackFrameARM64::CONTEXT_VALID_X10;
const uint64_t StackFrameARM64::CONTEXT_VALID_X11;
const uint64_t StackFrameARM64::CONTEXT_VALID_X12;
const uint64_t StackFrameARM64::CONTEXT_VALID_X13;
const uint64_t StackFrameARM64::CONTEXT_VALID_X14;
const uint64_t StackFrameARM64::CONTEXT_VALID_X15;
const uint64_t StackFrameARM64::CONTEXT_VALID_X16;
const uint64_t StackFrameARM64::CONTEXT_VALID_X17;
const uint64_t StackFrameARM64::CONTEXT_VALID_X18;
const uint64_t StackFrameARM64::CONTEXT_VALID_X19;
const uint64_t StackFrameARM64::CONTEXT_VALID_X20;
const uint64_t StackFrameARM64::CONTEXT_VALID_X21;
const uint64_t StackFrameARM64::CONTEXT_VALID_X22;
const uint64_t StackFrameARM64::CONTEXT_VALID_X23;
const uint64_t StackFrameARM64::CONTEXT_VALID_X24;
const uint64_t StackFrameARM64::CONTEXT_VALID_X25;
const uint64_t StackFrameARM64::CONTEXT_VALID_X26;
const uint64_t StackFrameARM64::CONTEXT_VALID_X27;
const uint64_t StackFrameARM64::CONTEXT_VALID_X28;
const uint64_t StackFrameARM64::CONTEXT_VALID_X29;
const uint64_t StackFrameARM64::CONTEXT_VALID_X30;
const uint64_t StackFrameARM64::CONTEXT_VALID_X31;
const uint64_t StackFrameARM64::CONTEXT_VALID_X32;
const uint64_t StackFrameARM64::CONTEXT_VALID_FP;
const uint64_t StackFrameARM64::CONTEXT_VALID_LR;
const uint64_t StackFrameARM64::CONTEXT_VALID_SP;
const uint64_t StackFrameARM64::CONTEXT_VALID_PC;
const uint64_t StackFrameARM64::CONTEXT_VALID_ALL;
} // namespace google_breakpad

View file

@ -0,0 +1,149 @@
// Copyright 2012 Google LLC
//
// 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 LLC 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.
// Implementation of StackFrameSymbolizer, which encapsulates the logic of how
// SourceLineResolverInterface interacts with SymbolSupplier to fill source
// line information in a stack frame, and also looks up WindowsFrameInfo or
// CFIFrameInfo for a stack frame.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/stack_frame_symbolizer.h"
#include <assert.h>
#include "common/scoped_ptr.h"
#include "google_breakpad/processor/code_module.h"
#include "google_breakpad/processor/code_modules.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 "google_breakpad/processor/system_info.h"
#include "processor/linked_ptr.h"
#include "processor/logging.h"
namespace google_breakpad {
StackFrameSymbolizer::StackFrameSymbolizer(
SymbolSupplier* supplier,
SourceLineResolverInterface* resolver) : supplier_(supplier),
resolver_(resolver) { }
StackFrameSymbolizer::SymbolizerResult StackFrameSymbolizer::FillSourceLineInfo(
const CodeModules* modules,
const CodeModules* unloaded_modules,
const SystemInfo* system_info,
StackFrame* frame,
std::deque<std::unique_ptr<StackFrame>>* inlined_frames) {
assert(frame);
const CodeModule* module = NULL;
if (modules) {
module = modules->GetModuleForAddress(frame->instruction);
}
if (!module && unloaded_modules) {
module = unloaded_modules->GetModuleForAddress(frame->instruction);
}
if (!module) return kError;
frame->module = module;
if (!resolver_) return kError; // no resolver.
// If module is known to have missing symbol file, return.
if (no_symbol_modules_.find(module->code_file()) !=
no_symbol_modules_.end()) {
return kError;
}
// If module is already loaded, go ahead to fill source line info and return.
if (resolver_->HasModule(frame->module)) {
resolver_->FillSourceLineInfo(frame, inlined_frames);
return resolver_->IsModuleCorrupt(frame->module) ?
kWarningCorruptSymbols : kNoError;
}
// Module needs to fetch symbol file. First check to see if supplier exists.
if (!supplier_) {
return kError;
}
// Start fetching symbol from supplier.
string symbol_file;
char* symbol_data = NULL;
size_t symbol_data_size;
SymbolSupplier::SymbolResult symbol_result = supplier_->GetCStringSymbolData(
module, system_info, &symbol_file, &symbol_data, &symbol_data_size);
switch (symbol_result) {
case SymbolSupplier::FOUND: {
bool load_success = resolver_->LoadModuleUsingMemoryBuffer(
frame->module,
symbol_data,
symbol_data_size);
if (resolver_->ShouldDeleteMemoryBufferAfterLoadModule()) {
supplier_->FreeSymbolData(module);
}
if (load_success) {
resolver_->FillSourceLineInfo(frame, inlined_frames);
return resolver_->IsModuleCorrupt(frame->module) ?
kWarningCorruptSymbols : kNoError;
} else {
BPLOG(ERROR) << "Failed to load symbol file in resolver.";
no_symbol_modules_.insert(module->code_file());
return kError;
}
}
case SymbolSupplier::NOT_FOUND:
no_symbol_modules_.insert(module->code_file());
return kError;
case SymbolSupplier::INTERRUPT:
return kInterrupt;
default:
BPLOG(ERROR) << "Unknown SymbolResult enum: " << symbol_result;
return kError;
}
return kError;
}
WindowsFrameInfo* StackFrameSymbolizer::FindWindowsFrameInfo(
const StackFrame* frame) {
return resolver_ ? resolver_->FindWindowsFrameInfo(frame) : NULL;
}
CFIFrameInfo* StackFrameSymbolizer::FindCFIFrameInfo(
const StackFrame* frame) {
return resolver_ ? resolver_->FindCFIFrameInfo(frame) : NULL;
}
} // namespace google_breakpad

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// stackwalk_common.cc: Module shared by the {micro,mini}dump_stackwalck
// executables to print the content of dumps (w/ stack traces) on the console.
#ifndef PROCESSOR_STACKWALK_COMMON_H__
#define PROCESSOR_STACKWALK_COMMON_H__
namespace google_breakpad {
class ProcessState;
class SourceLineResolverInterface;
void PrintProcessStateMachineReadable(const ProcessState& process_state);
void PrintProcessState(const ProcessState& process_state,
bool output_stack_contents,
bool output_requesting_thread_only,
SourceLineResolverInterface* resolver);
void PrintRequestingThreadBrief(const ProcessState& process_state);
} // namespace google_breakpad
#endif // PROCESSOR_STACKWALK_COMMON_H__

View file

@ -0,0 +1,358 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// stackwalker.cc: Generic stackwalker.
//
// See stackwalker.h for documentation.
//
// Author: Mark Mentovai
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include "google_breakpad/processor/stackwalker.h"
#include <assert.h>
#include "common/scoped_ptr.h"
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/code_module.h"
#include "google_breakpad/processor/code_modules.h"
#include "google_breakpad/processor/dump_context.h"
#include "google_breakpad/processor/stack_frame.h"
#include "google_breakpad/processor/stack_frame_symbolizer.h"
#include "google_breakpad/processor/system_info.h"
#include "processor/linked_ptr.h"
#include "processor/logging.h"
#include "processor/stackwalker_ppc.h"
#include "processor/stackwalker_ppc64.h"
#include "processor/stackwalker_sparc.h"
#include "processor/stackwalker_x86.h"
#include "processor/stackwalker_amd64.h"
#include "processor/stackwalker_arm.h"
#include "processor/stackwalker_arm64.h"
#include "processor/stackwalker_mips.h"
#include "processor/stackwalker_riscv.h"
#include "processor/stackwalker_riscv64.h"
namespace google_breakpad {
const int Stackwalker::kRASearchWords = 40;
// This default is just a sanity check: a large enough value
// that allow capturing unbounded recursion traces, yet provide a
// guardrail against stack walking bugs. The stack walking invariants
// guarantee that the unwinding process is strictly monotonic and
// practically bounded by the size of the stack memory range.
uint32_t Stackwalker::max_frames_ = 1 << 20; // 1M
bool Stackwalker::max_frames_set_ = false;
uint32_t Stackwalker::max_frames_scanned_ = 1 << 14; // 16k
Stackwalker::Stackwalker(const SystemInfo* system_info,
MemoryRegion* memory,
const CodeModules* modules,
StackFrameSymbolizer* frame_symbolizer)
: system_info_(system_info),
memory_(memory),
modules_(modules),
unloaded_modules_(NULL),
frame_symbolizer_(frame_symbolizer) {
assert(frame_symbolizer_);
}
void InsertSpecialAttentionModule(
StackFrameSymbolizer::SymbolizerResult symbolizer_result,
const CodeModule* module,
vector<const CodeModule*>* modules) {
if (!module) {
return;
}
assert(symbolizer_result == StackFrameSymbolizer::kError ||
symbolizer_result == StackFrameSymbolizer::kWarningCorruptSymbols);
bool found = false;
vector<const CodeModule*>::iterator iter;
for (iter = modules->begin(); iter != modules->end(); ++iter) {
if (*iter == module) {
found = true;
break;
}
}
if (!found) {
BPLOG(INFO) << ((symbolizer_result == StackFrameSymbolizer::kError) ?
"Couldn't load symbols for: " :
"Detected corrupt symbols for: ")
<< module->debug_file() << "|" << module->debug_identifier();
modules->push_back(module);
}
}
bool Stackwalker::Walk(
CallStack* stack,
vector<const CodeModule*>* modules_without_symbols,
vector<const CodeModule*>* modules_with_corrupt_symbols) {
BPLOG_IF(ERROR, !stack) << "Stackwalker::Walk requires |stack|";
assert(stack);
stack->Clear();
BPLOG_IF(ERROR, !modules_without_symbols) << "Stackwalker::Walk requires "
<< "|modules_without_symbols|";
BPLOG_IF(ERROR, !modules_without_symbols) << "Stackwalker::Walk requires "
<< "|modules_with_corrupt_symbols|";
assert(modules_without_symbols);
assert(modules_with_corrupt_symbols);
// Begin with the context frame, and keep getting callers until there are
// no more.
// Keep track of the number of scanned or otherwise dubious frames seen
// so far, as the caller may have set a limit.
uint32_t scanned_frames = 0;
// Take ownership of the pointer returned by GetContextFrame.
scoped_ptr<StackFrame> frame(GetContextFrame());
while (frame.get()) {
// frame already contains a good frame with properly set instruction and
// frame_pointer fields. The frame structure comes from either the
// context frame (above) or a caller frame (below).
std::deque<std::unique_ptr<StackFrame>> inlined_frames;
// Resolve the module information, if a module map was provided.
StackFrameSymbolizer::SymbolizerResult symbolizer_result =
frame_symbolizer_->FillSourceLineInfo(modules_, unloaded_modules_,
system_info_,
frame.get(), &inlined_frames);
switch (symbolizer_result) {
case StackFrameSymbolizer::kInterrupt:
BPLOG(INFO) << "Stack walk is interrupted.";
return false;
break;
case StackFrameSymbolizer::kError:
InsertSpecialAttentionModule(symbolizer_result, frame->module,
modules_without_symbols);
break;
case StackFrameSymbolizer::kWarningCorruptSymbols:
InsertSpecialAttentionModule(symbolizer_result, frame->module,
modules_with_corrupt_symbols);
break;
case StackFrameSymbolizer::kNoError:
break;
default:
assert(false);
break;
}
// Keep track of the number of dubious frames so far.
switch (frame.get()->trust) {
case StackFrame::FRAME_TRUST_NONE:
case StackFrame::FRAME_TRUST_SCAN:
case StackFrame::FRAME_TRUST_CFI_SCAN:
scanned_frames++;
break;
default:
break;
}
// Add all nested inlined frames belonging to this frame from the innermost
// frame to the outermost frame.
while (!inlined_frames.empty()) {
stack->frames_.push_back(inlined_frames.front().release());
inlined_frames.pop_front();
}
// Add the frame to the call stack. Relinquish the ownership claim
// over the frame, because the stack now owns it.
stack->frames_.push_back(frame.release());
if (stack->frames_.size() > max_frames_) {
// Only emit an error message in the case where the limit
// reached is the default limit, not set by the user.
if (!max_frames_set_)
BPLOG(ERROR) << "The stack is over " << max_frames_ << " frames.";
break;
}
// Get the next frame and take ownership.
bool stack_scan_allowed = scanned_frames < max_frames_scanned_;
frame.reset(GetCallerFrame(stack, stack_scan_allowed));
}
return true;
}
// static
Stackwalker* Stackwalker::StackwalkerForCPU(
const SystemInfo* system_info,
DumpContext* context,
MemoryRegion* memory,
const CodeModules* modules,
const CodeModules* unloaded_modules,
StackFrameSymbolizer* frame_symbolizer) {
if (!context) {
BPLOG(ERROR) << "Can't choose a stackwalker implementation without context";
return NULL;
}
Stackwalker* cpu_stackwalker = NULL;
uint32_t cpu = context->GetContextCPU();
switch (cpu) {
case MD_CONTEXT_X86:
cpu_stackwalker = new StackwalkerX86(system_info,
context->GetContextX86(),
memory, modules, frame_symbolizer);
break;
case MD_CONTEXT_PPC:
cpu_stackwalker = new StackwalkerPPC(system_info,
context->GetContextPPC(),
memory, modules, frame_symbolizer);
break;
case MD_CONTEXT_PPC64:
cpu_stackwalker = new StackwalkerPPC64(system_info,
context->GetContextPPC64(),
memory, modules, frame_symbolizer);
break;
case MD_CONTEXT_AMD64:
cpu_stackwalker = new StackwalkerAMD64(system_info,
context->GetContextAMD64(),
memory, modules, frame_symbolizer);
break;
case MD_CONTEXT_SPARC:
cpu_stackwalker = new StackwalkerSPARC(system_info,
context->GetContextSPARC(),
memory, modules, frame_symbolizer);
break;
case MD_CONTEXT_MIPS:
case MD_CONTEXT_MIPS64:
cpu_stackwalker = new StackwalkerMIPS(system_info,
context->GetContextMIPS(),
memory, modules, frame_symbolizer);
break;
case MD_CONTEXT_ARM:
{
int fp_register = -1;
if (system_info->os_short == "ios")
fp_register = MD_CONTEXT_ARM_REG_IOS_FP;
cpu_stackwalker = new StackwalkerARM(system_info,
context->GetContextARM(),
fp_register, memory, modules,
frame_symbolizer);
break;
}
case MD_CONTEXT_ARM64:
cpu_stackwalker = new StackwalkerARM64(system_info,
context->GetContextARM64(),
memory, modules,
frame_symbolizer);
break;
case MD_CONTEXT_RISCV:
cpu_stackwalker = new StackwalkerRISCV(system_info,
context->GetContextRISCV(),
memory, modules,
frame_symbolizer);
break;
case MD_CONTEXT_RISCV64:
cpu_stackwalker = new StackwalkerRISCV64(system_info,
context->GetContextRISCV64(),
memory, modules,
frame_symbolizer);
break;
}
BPLOG_IF(ERROR, !cpu_stackwalker) << "Unknown CPU type " << HexString(cpu) <<
", can't choose a stackwalker "
"implementation";
if (cpu_stackwalker) {
cpu_stackwalker->unloaded_modules_ = unloaded_modules;
}
return cpu_stackwalker;
}
// CONSIDER: check stack alignment?
bool Stackwalker::TerminateWalk(uint64_t caller_ip,
uint64_t caller_sp,
uint64_t callee_sp,
bool first_unwind) const {
// Treat an instruction address less than 4k as end-of-stack.
// (using InstructionAddressSeemsValid() here is very tempting,
// but we need to handle JITted code)
if (caller_ip < (1 << 12)) {
return true;
}
// NOTE: The stack address range is implicitly checked
// when the stack memory is accessed.
// The stack pointer should monotonically increase. For first unwind
// we allow caller_sp == callee_sp to account for architectures where
// the return address is stored in a register (so it's possible to have
// leaf functions which don't move the stack pointer)
if (first_unwind ? (caller_sp < callee_sp) : (caller_sp <= callee_sp)) {
return true;
}
return false;
}
bool Stackwalker::InstructionAddressSeemsValid(uint64_t address) const {
StackFrame frame;
frame.instruction = address;
StackFrameSymbolizer::SymbolizerResult symbolizer_result =
frame_symbolizer_->FillSourceLineInfo(modules_, unloaded_modules_,
system_info_, &frame, nullptr);
if (!frame.module) {
// not inside any loaded module
return false;
}
if (!frame_symbolizer_->HasImplementation()) {
// No valid implementation to symbolize stack frame, but the address is
// within a known module.
return true;
}
if (symbolizer_result != StackFrameSymbolizer::kNoError &&
symbolizer_result != StackFrameSymbolizer::kWarningCorruptSymbols) {
// Some error occurred during symbolization, but the address is within a
// known module
return true;
}
return !frame.function_name.empty();
}
} // namespace google_breakpad

View file

@ -0,0 +1,95 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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.
// stackwalker_address_list.cc: a pseudo stack walker.
//
// See stackwalker_address_list.h for documentation.
//
// Author: Chris Hamilton <chrisha@chromium.org>
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <assert.h>
#include <vector>
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/memory_region.h"
#include "google_breakpad/processor/source_line_resolver_interface.h"
#include "google_breakpad/processor/stack_frame.h"
#include "processor/logging.h"
#include "processor/stackwalker_address_list.h"
namespace google_breakpad {
StackwalkerAddressList::StackwalkerAddressList(
const uint64_t* frames,
size_t frame_count,
const CodeModules* modules,
StackFrameSymbolizer* frame_symbolizer)
: Stackwalker(NULL, NULL, modules, frame_symbolizer),
frames_(frames),
frame_count_(frame_count) {
assert(frames);
assert(frame_symbolizer);
}
StackFrame* StackwalkerAddressList::GetContextFrame() {
if (frame_count_ == 0)
return NULL;
StackFrame* frame = new StackFrame();
frame->instruction = frames_[0];
frame->trust = StackFrame::FRAME_TRUST_PREWALKED;
return frame;
}
StackFrame* StackwalkerAddressList::GetCallerFrame(const CallStack* stack,
bool stack_scan_allowed) {
if (!stack) {
BPLOG(ERROR) << "Can't get caller frame without stack";
return NULL;
}
size_t frame_index = stack->frames()->size();
// There are no more frames to fetch.
if (frame_index >= frame_count_)
return NULL;
// All frames have the highest level of trust because they were
// explicitly provided.
StackFrame* frame = new StackFrame();
frame->instruction = frames_[frame_index];
frame->trust = StackFrame::FRAME_TRUST_PREWALKED;
return frame;
}
} // namespace google_breakpad

View file

@ -0,0 +1,70 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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.
// stackwalker_address_list.h: a pseudo stackwalker.
//
// Doesn't actually walk a stack, rather initializes a CallStack given an
// explicit list of already walked return addresses.
//
// Author: Chris Hamilton <chrisha@chromium.org>
#ifndef PROCESSOR_STACKWALKER_ADDRESS_LIST_H_
#define PROCESSOR_STACKWALKER_ADDRESS_LIST_H_
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/processor/stackwalker.h"
namespace google_breakpad {
class CodeModules;
class StackwalkerAddressList : public Stackwalker {
public:
// Initializes this stack walker with an explicit set of frame addresses.
// |modules| and |frame_symbolizer| are passed directly through to the base
// Stackwalker constructor.
StackwalkerAddressList(const uint64_t* frames,
size_t frame_count,
const CodeModules* modules,
StackFrameSymbolizer* frame_symbolizer);
StackwalkerAddressList(const StackwalkerAddressList&) = delete;
void operator=(const StackwalkerAddressList&) = delete;
private:
// Implementation of Stackwalker.
virtual StackFrame* GetContextFrame();
virtual StackFrame* GetCallerFrame(const CallStack* stack,
bool stack_scan_allowed);
const uint64_t* frames_;
size_t frame_count_;
};
} // namespace google_breakpad
#endif // PROCESSOR_STACKWALKER_ADDRESS_LIST_H_

View file

@ -0,0 +1,201 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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.
// stackwalker_address_list_unittest.cc: Unit tests for the
// StackwalkerAddressList class.
//
// Author: Chris Hamilton <chrisha@chromium.org>
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <string>
#include <vector>
#include "breakpad_googletest_includes.h"
#include "common/using_std_string.h"
#include "google_breakpad/common/minidump_format.h"
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "google_breakpad/processor/call_stack.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 "processor/stackwalker_unittest_utils.h"
#include "processor/stackwalker_address_list.h"
using google_breakpad::BasicSourceLineResolver;
using google_breakpad::CallStack;
using google_breakpad::CodeModule;
using google_breakpad::StackFrameSymbolizer;
using google_breakpad::StackFrame;
using google_breakpad::Stackwalker;
using google_breakpad::StackwalkerAddressList;
using std::vector;
using testing::_;
using testing::AnyNumber;
using testing::DoAll;
using testing::Return;
using testing::SetArgumentPointee;
#define arraysize(f) (sizeof(f) / sizeof(*f))
// Addresses and sizes of a couple dummy modules.
uint64_t kModule1Base = 0x40000000;
uint64_t kModule1Size = 0x10000;
uint64_t kModule2Base = 0x50000000;
uint64_t kModule2Size = 0x10000;
// A handful of addresses that lie within the modules above.
const uint64_t kDummyFrames[] = {
0x50003000, 0x50002000, 0x50001000, 0x40002000, 0x40001000 };
class StackwalkerAddressListTest : public testing::Test {
public:
StackwalkerAddressListTest()
: // Give the two modules reasonable standard locations and names
// for tests to play with.
module1(kModule1Base, kModule1Size, "module1", "version1"),
module2(kModule2Base, kModule2Size, "module2", "version2") {
// Create some modules with some stock debugging information.
modules.Add(&module1);
modules.Add(&module2);
// By default, none of the modules have symbol info; call
// SetModuleSymbols to override this.
EXPECT_CALL(supplier, GetCStringSymbolData(_, _, _, _, _))
.WillRepeatedly(Return(MockSymbolSupplier::NOT_FOUND));
// Avoid GMOCK WARNING "Uninteresting mock function call - returning
// directly" for FreeSymbolData().
EXPECT_CALL(supplier, FreeSymbolData(_)).Times(AnyNumber());
}
// Set the Breakpad symbol information that supplier should return for
// MODULE to INFO.
void SetModuleSymbols(MockCodeModule* module, const string& info) {
size_t buffer_size;
char* buffer = supplier.CopySymbolDataAndOwnTheCopy(info, &buffer_size);
EXPECT_CALL(supplier, GetCStringSymbolData(module, NULL, _, _, _))
.WillRepeatedly(DoAll(SetArgumentPointee<3>(buffer),
SetArgumentPointee<4>(buffer_size),
Return(MockSymbolSupplier::FOUND)));
}
void CheckCallStack(const CallStack& call_stack) {
const std::vector<StackFrame*>* frames = call_stack.frames();
ASSERT_EQ(arraysize(kDummyFrames), frames->size());
for (size_t i = 0; i < arraysize(kDummyFrames); ++i) {
ASSERT_EQ(kDummyFrames[i], frames->at(i)->instruction);
ASSERT_EQ(StackFrame::FRAME_TRUST_PREWALKED, frames->at(i)->trust);
}
ASSERT_EQ(static_cast<const CodeModule*>(&module2), frames->at(0)->module);
ASSERT_EQ(static_cast<const CodeModule*>(&module2), frames->at(1)->module);
ASSERT_EQ(static_cast<const CodeModule*>(&module2), frames->at(2)->module);
ASSERT_EQ(static_cast<const CodeModule*>(&module1), frames->at(3)->module);
ASSERT_EQ(static_cast<const CodeModule*>(&module1), frames->at(4)->module);
}
MockCodeModule module1;
MockCodeModule module2;
MockCodeModules modules;
MockSymbolSupplier supplier;
BasicSourceLineResolver resolver;
};
TEST_F(StackwalkerAddressListTest, ScanWithoutSymbols) {
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAddressList walker(kDummyFrames, arraysize(kDummyFrames),
&modules, &frame_symbolizer);
CallStack call_stack;
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
// The stack starts in module2, so we expect that to be the first module
// found without symbols.
ASSERT_EQ(2U, modules_without_symbols.size());
ASSERT_EQ("module2", modules_without_symbols[0]->debug_file());
ASSERT_EQ("module1", modules_without_symbols[1]->debug_file());
ASSERT_EQ(0u, modules_with_corrupt_symbols.size());
ASSERT_NO_FATAL_FAILURE(CheckCallStack(call_stack));
}
TEST_F(StackwalkerAddressListTest, ScanWithSymbols) {
// File : FILE number(dex) name
// Function: FUNC address(hex) size(hex) parameter_size(hex) name
// Line : address(hex) size(hex) line(dec) filenum(dec)
SetModuleSymbols(&module2,
"FILE 1 module2.cc\n"
"FUNC 3000 100 10 mod2func3\n"
"3000 10 1 1\n"
"FUNC 2000 200 10 mod2func2\n"
"FUNC 1000 300 10 mod2func1\n");
SetModuleSymbols(&module1,
"FUNC 2000 200 10 mod1func2\n"
"FUNC 1000 300 10 mod1func1\n");
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAddressList walker(kDummyFrames, arraysize(kDummyFrames),
&modules, &frame_symbolizer);
CallStack call_stack;
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
ASSERT_EQ(0u, modules_without_symbols.size());
ASSERT_EQ(0u, modules_with_corrupt_symbols.size());
ASSERT_NO_FATAL_FAILURE(CheckCallStack(call_stack));
const std::vector<StackFrame*>* frames = call_stack.frames();
// We have full file/line information for the first function call.
ASSERT_EQ("mod2func3", frames->at(0)->function_name);
ASSERT_EQ(0x50003000u, frames->at(0)->function_base);
ASSERT_EQ("module2.cc", frames->at(0)->source_file_name);
ASSERT_EQ(1, frames->at(0)->source_line);
ASSERT_EQ(0x50003000u, frames->at(0)->source_line_base);
ASSERT_EQ("mod2func2", frames->at(1)->function_name);
ASSERT_EQ(0x50002000u, frames->at(1)->function_base);
ASSERT_EQ("mod2func1", frames->at(2)->function_name);
ASSERT_EQ(0x50001000u, frames->at(2)->function_base);
ASSERT_EQ("mod1func2", frames->at(3)->function_name);
ASSERT_EQ(0x40002000u, frames->at(3)->function_base);
ASSERT_EQ("mod1func1", frames->at(4)->function_name);
ASSERT_EQ(0x40001000u, frames->at(4)->function_base);
}

View file

@ -0,0 +1,380 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// stackwalker_amd64.cc: amd64-specific stackwalker.
//
// See stackwalker_amd64.h for documentation.
//
// Author: Mark Mentovai, Ted Mielczarek
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <assert.h>
#include "common/scoped_ptr.h"
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/memory_region.h"
#include "google_breakpad/processor/source_line_resolver_interface.h"
#include "google_breakpad/processor/stack_frame_cpu.h"
#include "google_breakpad/processor/system_info.h"
#include "processor/cfi_frame_info.h"
#include "processor/logging.h"
#include "processor/stackwalker_amd64.h"
namespace google_breakpad {
const StackwalkerAMD64::CFIWalker::RegisterSet
StackwalkerAMD64::cfi_register_map_[] = {
// It may seem like $rip and $rsp are callee-saves, because the callee is
// responsible for having them restored upon return. But the callee_saves
// flags here really means that the walker should assume they're
// unchanged if the CFI doesn't mention them --- clearly wrong for $rip
// and $rsp.
{ "$rax", NULL, false,
StackFrameAMD64::CONTEXT_VALID_RAX, &MDRawContextAMD64::rax },
{ "$rdx", NULL, false,
StackFrameAMD64::CONTEXT_VALID_RDX, &MDRawContextAMD64::rdx },
{ "$rcx", NULL, false,
StackFrameAMD64::CONTEXT_VALID_RCX, &MDRawContextAMD64::rcx },
{ "$rbx", NULL, true,
StackFrameAMD64::CONTEXT_VALID_RBX, &MDRawContextAMD64::rbx },
{ "$rsi", NULL, false,
StackFrameAMD64::CONTEXT_VALID_RSI, &MDRawContextAMD64::rsi },
{ "$rdi", NULL, false,
StackFrameAMD64::CONTEXT_VALID_RDI, &MDRawContextAMD64::rdi },
{ "$rbp", NULL, true,
StackFrameAMD64::CONTEXT_VALID_RBP, &MDRawContextAMD64::rbp },
{ "$rsp", ".cfa", false,
StackFrameAMD64::CONTEXT_VALID_RSP, &MDRawContextAMD64::rsp },
{ "$r8", NULL, false,
StackFrameAMD64::CONTEXT_VALID_R8, &MDRawContextAMD64::r8 },
{ "$r9", NULL, false,
StackFrameAMD64::CONTEXT_VALID_R9, &MDRawContextAMD64::r9 },
{ "$r10", NULL, false,
StackFrameAMD64::CONTEXT_VALID_R10, &MDRawContextAMD64::r10 },
{ "$r11", NULL, false,
StackFrameAMD64::CONTEXT_VALID_R11, &MDRawContextAMD64::r11 },
{ "$r12", NULL, true,
StackFrameAMD64::CONTEXT_VALID_R12, &MDRawContextAMD64::r12 },
{ "$r13", NULL, true,
StackFrameAMD64::CONTEXT_VALID_R13, &MDRawContextAMD64::r13 },
{ "$r14", NULL, true,
StackFrameAMD64::CONTEXT_VALID_R14, &MDRawContextAMD64::r14 },
{ "$r15", NULL, true,
StackFrameAMD64::CONTEXT_VALID_R15, &MDRawContextAMD64::r15 },
{ "$rip", ".ra", false,
StackFrameAMD64::CONTEXT_VALID_RIP, &MDRawContextAMD64::rip },
};
StackwalkerAMD64::StackwalkerAMD64(const SystemInfo* system_info,
const MDRawContextAMD64* context,
MemoryRegion* memory,
const CodeModules* modules,
StackFrameSymbolizer* resolver_helper)
: Stackwalker(system_info, memory, modules, resolver_helper),
context_(context),
cfi_walker_(cfi_register_map_,
(sizeof(cfi_register_map_) / sizeof(cfi_register_map_[0]))) {
}
uint64_t StackFrameAMD64::ReturnAddress() const {
assert(context_validity & StackFrameAMD64::CONTEXT_VALID_RIP);
return context.rip;
}
StackFrame* StackwalkerAMD64::GetContextFrame() {
if (!context_) {
BPLOG(ERROR) << "Can't get context frame without context";
return NULL;
}
StackFrameAMD64* frame = new StackFrameAMD64();
// The instruction pointer is stored directly in a register, so pull it
// straight out of the CPU context structure.
frame->context = *context_;
frame->context_validity = StackFrameAMD64::CONTEXT_VALID_ALL;
frame->trust = StackFrame::FRAME_TRUST_CONTEXT;
frame->instruction = frame->context.rip;
return frame;
}
StackFrameAMD64* StackwalkerAMD64::GetCallerByCFIFrameInfo(
const vector<StackFrame*>& frames,
CFIFrameInfo* cfi_frame_info) {
StackFrameAMD64* last_frame = static_cast<StackFrameAMD64*>(frames.back());
scoped_ptr<StackFrameAMD64> frame(new StackFrameAMD64());
if (!cfi_walker_
.FindCallerRegisters(*memory_, *cfi_frame_info,
last_frame->context, last_frame->context_validity,
&frame->context, &frame->context_validity))
return NULL;
// Make sure we recovered all the essentials.
static const int essentials = (StackFrameAMD64::CONTEXT_VALID_RIP
| StackFrameAMD64::CONTEXT_VALID_RSP);
if ((frame->context_validity & essentials) != essentials)
return NULL;
if (!frame->context.rip || !frame->context.rsp) {
BPLOG(ERROR) << "invalid rip/rsp";
return NULL;
}
frame->trust = StackFrame::FRAME_TRUST_CFI;
return frame.release();
}
// Returns true if `ptr` is not in x86-64 canonical form.
// https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details
static bool is_non_canonical(uint64_t ptr) {
return ptr > 0x7FFFFFFFFFFF && ptr < 0xFFFF800000000000;
}
StackFrameAMD64* StackwalkerAMD64::GetCallerByFramePointerRecovery(
const vector<StackFrame*>& frames) {
StackFrameAMD64* last_frame = static_cast<StackFrameAMD64*>(frames.back());
uint64_t last_rbp = last_frame->context.rbp;
// Assume the presence of a frame pointer. This is not mandated by the
// AMD64 ABI, c.f. section 3.2.2 footnote 7, though it is typical for
// compilers to still preserve the frame pointer and not treat %rbp as a
// general purpose register.
//
// With this assumption, the CALL instruction pushes the return address
// onto the stack and sets %rip to the procedure to enter. The procedure
// then establishes the stack frame with a prologue that PUSHes the current
// %rbp onto the stack, MOVes the current %rsp to %rbp, and then allocates
// space for any local variables. Using this procedure linking information,
// it is possible to locate frame information for the callee:
//
// %caller_rsp = *(%callee_rbp + 16)
// %caller_rip = *(%callee_rbp + 8)
// %caller_rbp = *(%callee_rbp)
// If rbp is not 8-byte aligned it can't be a frame pointer.
if (last_rbp % 8 != 0) {
return NULL;
}
uint64_t caller_rip, caller_rbp;
if (memory_->GetMemoryAtAddress(last_rbp + 8, &caller_rip) &&
memory_->GetMemoryAtAddress(last_rbp, &caller_rbp)) {
uint64_t caller_rsp = last_rbp + 16;
// If the recovered rip is not a canonical address it can't be
// the return address, so rbp must not have been a frame pointer.
if (is_non_canonical(caller_rip)) {
return NULL;
}
// Check that rbp is within the right frame
if (caller_rsp <= last_rbp || caller_rbp < caller_rsp) {
return NULL;
}
// Sanity check that resulting rbp is still inside stack memory.
uint64_t unused;
if (!memory_->GetMemoryAtAddress(caller_rbp, &unused)) {
return NULL;
}
StackFrameAMD64* frame = new StackFrameAMD64();
frame->trust = StackFrame::FRAME_TRUST_FP;
frame->context = last_frame->context;
frame->context.rip = caller_rip;
frame->context.rsp = caller_rsp;
frame->context.rbp = caller_rbp;
frame->context_validity = StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP |
StackFrameAMD64::CONTEXT_VALID_RBP;
return frame;
}
return NULL;
}
StackFrameAMD64* StackwalkerAMD64::GetCallerBySimulatingReturn(
const vector<StackFrame*>& frames) {
assert(frames.back()->trust == StackFrame::FRAME_TRUST_CONTEXT);
StackFrameAMD64* last_frame = static_cast<StackFrameAMD64*>(frames.back());
uint64_t last_rsp = last_frame->context.rsp;
uint64_t caller_rip_address, caller_rip;
int searchwords = 1;
if (!ScanForReturnAddress(last_rsp, &caller_rip_address, &caller_rip,
searchwords)) {
// No plausible return address at the top of the stack. Unable to simulate
// a return.
return NULL;
}
// Create a new stack frame (ownership will be transferred to the caller)
// and fill it in.
StackFrameAMD64* frame = new StackFrameAMD64();
frame->trust = StackFrame::FRAME_TRUST_LEAF;
frame->context = last_frame->context;
frame->context.rip = caller_rip;
// The caller's %rsp is directly underneath the return address pushed by
// the call.
frame->context.rsp = caller_rip_address + 8;
frame->context_validity = last_frame->context_validity;
return frame;
}
StackFrameAMD64* StackwalkerAMD64::GetCallerByStackScan(
const vector<StackFrame*>& frames) {
StackFrameAMD64* last_frame = static_cast<StackFrameAMD64*>(frames.back());
uint64_t last_rsp = last_frame->context.rsp;
uint64_t caller_rip_address, caller_rip;
if (!ScanForReturnAddress(last_rsp, &caller_rip_address, &caller_rip,
/*is_context_frame=*/last_frame->trust ==
StackFrame::FRAME_TRUST_CONTEXT)) {
// No plausible return address was found.
return NULL;
}
// Create a new stack frame (ownership will be transferred to the caller)
// and fill it in.
StackFrameAMD64* frame = new StackFrameAMD64();
frame->trust = StackFrame::FRAME_TRUST_SCAN;
frame->context = last_frame->context;
frame->context.rip = caller_rip;
// The caller's %rsp is directly underneath the return address pushed by
// the call.
frame->context.rsp = caller_rip_address + 8;
frame->context_validity = StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP;
// Other unwinders give up if they don't have an %rbp value, so see if we
// can pass some plausible value on.
if (last_frame->context_validity & StackFrameAMD64::CONTEXT_VALID_RBP) {
// Functions typically push their caller's %rbp immediately upon entry,
// and then set %rbp to point to that. So if the callee's %rbp is
// pointing to the first word below the alleged return address, presume
// that the caller's %rbp is saved there.
if (caller_rip_address - 8 == last_frame->context.rbp) {
uint64_t caller_rbp = 0;
if (memory_->GetMemoryAtAddress(last_frame->context.rbp, &caller_rbp) &&
caller_rbp > caller_rip_address) {
frame->context.rbp = caller_rbp;
frame->context_validity |= StackFrameAMD64::CONTEXT_VALID_RBP;
}
} else if (last_frame->context.rbp >= caller_rip_address + 8) {
// If the callee's %rbp is plausible as a value for the caller's
// %rbp, presume that the callee left it unchanged.
frame->context.rbp = last_frame->context.rbp;
frame->context_validity |= StackFrameAMD64::CONTEXT_VALID_RBP;
}
}
return frame;
}
StackFrame* StackwalkerAMD64::GetCallerFrame(const CallStack* stack,
bool stack_scan_allowed) {
if (!memory_ || !stack) {
BPLOG(ERROR) << "Can't get caller frame without memory or stack";
return NULL;
}
const vector<StackFrame*>& frames = *stack->frames();
StackFrameAMD64* last_frame = static_cast<StackFrameAMD64*>(frames.back());
scoped_ptr<StackFrameAMD64> new_frame;
// If we have CFI information, use it.
scoped_ptr<CFIFrameInfo> cfi_frame_info(
frame_symbolizer_->FindCFIFrameInfo(last_frame));
if (cfi_frame_info.get())
new_frame.reset(GetCallerByCFIFrameInfo(frames, cfi_frame_info.get()));
// If CFI was not available and this is a Windows x64 stack, check whether
// this is a leaf function which doesn't touch any callee-saved registers.
// According to https://reviews.llvm.org/D24748, LLVM doesn't generate unwind
// info for such functions. According to MSDN, leaf functions can be unwound
// simply by simulating a return.
if (!new_frame.get() &&
last_frame->trust == StackFrame::FRAME_TRUST_CONTEXT &&
system_info_->os_short == "windows") {
new_frame.reset(GetCallerBySimulatingReturn(frames));
}
// If CFI was not available or failed, try using frame pointer recovery.
// Never try to use frame pointer unwinding on Windows x64 stack. MSVC never
// generates code that works with frame pointer chasing, and LLVM does the
// same. Stack scanning would be better.
if (!new_frame.get() && system_info_->os_short != "windows") {
new_frame.reset(GetCallerByFramePointerRecovery(frames));
}
// If all else fails, fall back to stack scanning.
if (stack_scan_allowed && !new_frame.get()) {
new_frame.reset(GetCallerByStackScan(frames));
}
// If nothing worked, tell the caller.
if (!new_frame.get())
return NULL;
if (system_info_->os_short == "nacl") {
// Apply constraints from Native Client's x86-64 sandbox. These
// registers have the 4GB-aligned sandbox base address (from r15)
// added to them, and only the bottom 32 bits are relevant for
// stack walking.
new_frame->context.rip = static_cast<uint32_t>(new_frame->context.rip);
new_frame->context.rsp = static_cast<uint32_t>(new_frame->context.rsp);
new_frame->context.rbp = static_cast<uint32_t>(new_frame->context.rbp);
}
// Should we terminate the stack walk? (end-of-stack or broken invariant)
if (TerminateWalk(new_frame->context.rip, new_frame->context.rsp,
last_frame->context.rsp,
/*first_unwind=*/last_frame->trust ==
StackFrame::FRAME_TRUST_CONTEXT)) {
return NULL;
}
// new_frame->context.rip is the return address, which is the instruction
// after the CALL that caused us to arrive at the callee. Set
// new_frame->instruction to one less than that, so it points within the
// CALL instruction. See StackFrame::instruction for details, and
// StackFrameAMD64::ReturnAddress.
new_frame->instruction = new_frame->context.rip - 1;
return new_frame.release();
}
} // namespace google_breakpad

View file

@ -0,0 +1,112 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// stackwalker_amd64.h: amd64-specific stackwalker.
//
// Provides stack frames given amd64 register context and a memory region
// corresponding to a amd64 stack.
//
// Author: Mark Mentovai, Ted Mielczarek
#ifndef PROCESSOR_STACKWALKER_AMD64_H__
#define PROCESSOR_STACKWALKER_AMD64_H__
#include <vector>
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/common/minidump_format.h"
#include "google_breakpad/processor/stackwalker.h"
#include "google_breakpad/processor/stack_frame_cpu.h"
#include "processor/cfi_frame_info.h"
namespace google_breakpad {
class CodeModules;
class StackwalkerAMD64 : public Stackwalker {
public:
// context is a amd64 context object that gives access to amd64-specific
// register state corresponding to the innermost called frame to be
// included in the stack. The other arguments are passed directly through
// to the base Stackwalker constructor.
StackwalkerAMD64(const SystemInfo* system_info,
const MDRawContextAMD64* context,
MemoryRegion* memory,
const CodeModules* modules,
StackFrameSymbolizer* frame_symbolizer);
private:
// A STACK CFI-driven frame walker for the AMD64
typedef SimpleCFIWalker<uint64_t, MDRawContextAMD64> CFIWalker;
// Implementation of Stackwalker, using amd64 context (stack pointer in %rsp,
// stack base in %rbp) and stack conventions (saved stack pointer at 0(%rbp))
virtual StackFrame* GetContextFrame();
virtual StackFrame* GetCallerFrame(const CallStack* stack,
bool stack_scan_allowed);
// Use cfi_frame_info (derived from STACK CFI records) to construct
// the frame that called frames.back(). The caller takes ownership
// of the returned frame. Return NULL on failure.
StackFrameAMD64* GetCallerByCFIFrameInfo(const vector<StackFrame*>& frames,
CFIFrameInfo* cfi_frame_info);
// Assumes a traditional frame layout where the frame pointer has not been
// omitted. The expectation is that caller's %rbp is pushed to the stack
// after the return address of the callee, and that the callee's %rsp can
// be used to find the pushed %rbp.
// Caller owns the returned frame object. Returns NULL on failure.
StackFrameAMD64* GetCallerByFramePointerRecovery(
const vector<StackFrame*>& frames);
// Scan the stack for plausible return addresses. The caller takes ownership
// of the returned frame. Return NULL on failure.
StackFrameAMD64* GetCallerByStackScan(const vector<StackFrame*>& frames);
// Trying to simulate a return. The caller takes ownership of the returned
// frame. Return NULL on failure.
StackFrameAMD64* GetCallerBySimulatingReturn(
const vector<StackFrame*>& frames);
// Stores the CPU context corresponding to the innermost stack frame to
// be returned by GetContextFrame.
const MDRawContextAMD64* context_;
// Our register map, for cfi_walker_.
static const CFIWalker::RegisterSet cfi_register_map_[];
// Our CFI frame walker.
const CFIWalker cfi_walker_;
};
} // namespace google_breakpad
#endif // PROCESSOR_STACKWALKER_AMD64_H__

View file

@ -0,0 +1,937 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// Original author: Jim Blandy <jimb@mozilla.com> <jimb@red-bean.com>
// stackwalker_amd64_unittest.cc: Unit tests for StackwalkerAMD64 class.
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <string.h>
#include <string>
#include <vector>
#include "breakpad_googletest_includes.h"
#include "common/test_assembler.h"
#include "common/using_std_string.h"
#include "google_breakpad/common/minidump_format.h"
#include "google_breakpad/processor/basic_source_line_resolver.h"
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/code_module.h"
#include "google_breakpad/processor/source_line_resolver_interface.h"
#include "google_breakpad/processor/stack_frame_cpu.h"
#include "processor/stackwalker_unittest_utils.h"
#include "processor/stackwalker_amd64.h"
using google_breakpad::BasicSourceLineResolver;
using google_breakpad::CallStack;
using google_breakpad::CodeModule;
using google_breakpad::StackFrameSymbolizer;
using google_breakpad::StackFrame;
using google_breakpad::StackFrameAMD64;
using google_breakpad::Stackwalker;
using google_breakpad::StackwalkerAMD64;
using google_breakpad::SystemInfo;
using google_breakpad::test_assembler::kLittleEndian;
using google_breakpad::test_assembler::Label;
using google_breakpad::test_assembler::Section;
using std::vector;
using testing::_;
using testing::AnyNumber;
using testing::DoAll;
using testing::Return;
using testing::SetArgumentPointee;
using testing::Test;
class StackwalkerAMD64Fixture {
public:
StackwalkerAMD64Fixture()
: stack_section(kLittleEndian),
// Give the two modules reasonable standard locations and names
// for tests to play with.
module1(0x00007400c0000000ULL, 0x10000, "module1", "version1"),
module2(0x00007500b0000000ULL, 0x10000, "module2", "version2") {
// Identify the system as a Linux system.
system_info.os = "Linux";
system_info.os_short = "linux";
system_info.os_version = "Horrendous Hippo";
system_info.cpu = "x86";
system_info.cpu_info = "";
// Put distinctive values in the raw CPU context.
BrandContext(&raw_context);
// Create some modules with some stock debugging information.
modules.Add(&module1);
modules.Add(&module2);
// By default, none of the modules have symbol info; call
// SetModuleSymbols to override this.
EXPECT_CALL(supplier, GetCStringSymbolData(_, _, _, _, _))
.WillRepeatedly(Return(MockSymbolSupplier::NOT_FOUND));
// Avoid GMOCK WARNING "Uninteresting mock function call - returning
// directly" for FreeSymbolData().
EXPECT_CALL(supplier, FreeSymbolData(_)).Times(AnyNumber());
// Reset max_frames_scanned since it's static.
Stackwalker::set_max_frames_scanned(1024);
}
// Set the Breakpad symbol information that supplier should return for
// MODULE to INFO.
void SetModuleSymbols(MockCodeModule* module, const string& info) {
size_t buffer_size;
char *buffer = supplier.CopySymbolDataAndOwnTheCopy(info, &buffer_size);
EXPECT_CALL(supplier, GetCStringSymbolData(module, &system_info, _, _, _))
.WillRepeatedly(DoAll(SetArgumentPointee<3>(buffer),
SetArgumentPointee<4>(buffer_size),
Return(MockSymbolSupplier::FOUND)));
}
// Populate stack_region with the contents of stack_section. Use
// stack_section.start() as the region's starting address.
void RegionFromSection() {
string contents;
ASSERT_TRUE(stack_section.GetContents(&contents));
stack_region.Init(stack_section.start().Value(), contents);
}
// Fill RAW_CONTEXT with pseudo-random data, for round-trip checking.
void BrandContext(MDRawContextAMD64 *raw_context) {
uint8_t x = 173;
for (size_t i = 0; i < sizeof(*raw_context); i++)
reinterpret_cast<uint8_t*>(raw_context)[i] = (x += 17);
}
SystemInfo system_info;
MDRawContextAMD64 raw_context;
Section stack_section;
MockMemoryRegion stack_region;
MockCodeModule module1;
MockCodeModule module2;
MockCodeModules modules;
MockSymbolSupplier supplier;
BasicSourceLineResolver resolver;
CallStack call_stack;
const vector<StackFrame*>* frames;
};
class GetContextFrame: public StackwalkerAMD64Fixture, public Test { };
class SanityCheck: public StackwalkerAMD64Fixture, public Test { };
TEST_F(SanityCheck, NoResolver) {
// There should be no references to the stack in this walk: we don't
// provide any call frame information, so trying to reconstruct the
// context frame's caller should fail. So there's no need for us to
// provide stack contents.
raw_context.rip = 0x00007400c0000200ULL;
raw_context.rbp = 0x8000000080000000ULL;
StackFrameSymbolizer frame_symbolizer(NULL, NULL);
StackwalkerAMD64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
// This should succeed even without a resolver or supplier.
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
ASSERT_EQ(1U, modules_without_symbols.size());
ASSERT_EQ("module1", modules_without_symbols[0]->debug_file());
ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
frames = call_stack.frames();
ASSERT_GE(1U, frames->size());
StackFrameAMD64 *frame = static_cast<StackFrameAMD64*>(frames->at(0));
// Check that the values from the original raw context made it
// through to the context in the stack frame.
EXPECT_EQ(0, memcmp(&raw_context, &frame->context, sizeof(raw_context)));
}
TEST_F(GetContextFrame, Simple) {
// There should be no references to the stack in this walk: we don't
// provide any call frame information, so trying to reconstruct the
// context frame's caller should fail. So there's no need for us to
// provide stack contents.
raw_context.rip = 0x00007400c0000200ULL;
raw_context.rbp = 0x8000000080000000ULL;
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAMD64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
ASSERT_EQ(1U, modules_without_symbols.size());
ASSERT_EQ("module1", modules_without_symbols[0]->debug_file());
ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
frames = call_stack.frames();
ASSERT_GE(1U, frames->size());
StackFrameAMD64 *frame = static_cast<StackFrameAMD64*>(frames->at(0));
// Check that the values from the original raw context made it
// through to the context in the stack frame.
EXPECT_EQ(0, memcmp(&raw_context, &frame->context, sizeof(raw_context)));
}
// The stackwalker should be able to produce the context frame even
// without stack memory present.
TEST_F(GetContextFrame, NoStackMemory) {
raw_context.rip = 0x00007400c0000200ULL;
raw_context.rbp = 0x8000000080000000ULL;
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAMD64 walker(&system_info, &raw_context, NULL, &modules,
&frame_symbolizer);
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
ASSERT_EQ(1U, modules_without_symbols.size());
ASSERT_EQ("module1", modules_without_symbols[0]->debug_file());
ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
frames = call_stack.frames();
ASSERT_GE(1U, frames->size());
StackFrameAMD64 *frame = static_cast<StackFrameAMD64*>(frames->at(0));
// Check that the values from the original raw context made it
// through to the context in the stack frame.
EXPECT_EQ(0, memcmp(&raw_context, &frame->context, sizeof(raw_context)));
}
class GetCallerFrame: public StackwalkerAMD64Fixture, public Test { };
TEST_F(GetCallerFrame, ScanWithoutSymbols) {
// When the stack walker resorts to scanning the stack,
// only addresses located within loaded modules are
// considered valid return addresses.
// Force scanning through three frames to ensure that the
// stack pointer is set properly in scan-recovered frames.
stack_section.start() = 0x8000000080000000ULL;
uint64_t return_address1 = 0x00007500b0000100ULL;
uint64_t return_address2 = 0x00007500b0000900ULL;
Label frame1_sp, frame2_sp, frame1_rbp;
stack_section
// frame 0
.Append(16, 0) // space
.D64(0x00007400b0000000ULL) // junk that's not
.D64(0x00007500d0000000ULL) // a return address
.D64(return_address1) // actual return address
// frame 1
.Mark(&frame1_sp)
.Append(16, 0) // space
.D64(0x00007400b0000000ULL) // more junk
.D64(0x00007500d0000000ULL)
.Mark(&frame1_rbp)
.D64(stack_section.start()) // This is in the right place to be
// a saved rbp, but it's bogus, so
// we shouldn't report it.
.D64(return_address2) // actual return address
// frame 2
.Mark(&frame2_sp)
.Append(32, 0); // end of stack
RegionFromSection();
raw_context.rip = 0x00007400c0000200ULL;
raw_context.rbp = frame1_rbp.Value();
raw_context.rsp = stack_section.start().Value();
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAMD64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
ASSERT_EQ(2U, modules_without_symbols.size());
ASSERT_EQ("module1", modules_without_symbols[0]->debug_file());
ASSERT_EQ("module2", modules_without_symbols[1]->debug_file());
ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
frames = call_stack.frames();
ASSERT_EQ(3U, frames->size());
StackFrameAMD64 *frame0 = static_cast<StackFrameAMD64*>(frames->at(0));
EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
ASSERT_EQ(StackFrameAMD64::CONTEXT_VALID_ALL, frame0->context_validity);
EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context)));
StackFrameAMD64 *frame1 = static_cast<StackFrameAMD64*>(frames->at(1));
EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame1->trust);
ASSERT_EQ((StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP |
StackFrameAMD64::CONTEXT_VALID_RBP),
frame1->context_validity);
EXPECT_EQ(return_address1, frame1->context.rip);
EXPECT_EQ(frame1_sp.Value(), frame1->context.rsp);
EXPECT_EQ(frame1_rbp.Value(), frame1->context.rbp);
StackFrameAMD64 *frame2 = static_cast<StackFrameAMD64*>(frames->at(2));
EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame2->trust);
ASSERT_EQ((StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP),
frame2->context_validity);
EXPECT_EQ(return_address2, frame2->context.rip);
EXPECT_EQ(frame2_sp.Value(), frame2->context.rsp);
}
TEST_F(GetCallerFrame, ScanWithFunctionSymbols) {
// During stack scanning, if a potential return address
// is located within a loaded module that has symbols,
// it is only considered a valid return address if it
// lies within a function's bounds.
stack_section.start() = 0x8000000080000000ULL;
uint64_t return_address = 0x00007500b0000110ULL;
Label frame1_sp, frame1_rbp;
stack_section
// frame 0
.Append(16, 0) // space
.D64(0x00007400b0000000ULL) // junk that's not
.D64(0x00007500b0000000ULL) // a return address
.D64(0x00007400c0001000ULL) // a couple of plausible addresses
.D64(0x00007500b000aaaaULL) // that are not within functions
.D64(return_address) // actual return address
// frame 1
.Mark(&frame1_sp)
.Append(32, 0) // end of stack
.Mark(&frame1_rbp);
RegionFromSection();
raw_context.rip = 0x00007400c0000200ULL;
raw_context.rbp = frame1_rbp.Value();
raw_context.rsp = stack_section.start().Value();
SetModuleSymbols(&module1,
// The youngest frame's function.
"FUNC 100 400 10 platypus\n");
SetModuleSymbols(&module2,
// The calling frame's function.
"FUNC 100 400 10 echidna\n");
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAMD64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
ASSERT_EQ(0U, modules_without_symbols.size());
ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
frames = call_stack.frames();
ASSERT_EQ(2U, frames->size());
StackFrameAMD64 *frame0 = static_cast<StackFrameAMD64*>(frames->at(0));
EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
ASSERT_EQ(StackFrameAMD64::CONTEXT_VALID_ALL, frame0->context_validity);
EXPECT_EQ("platypus", frame0->function_name);
EXPECT_EQ(0x00007400c0000100ULL, frame0->function_base);
StackFrameAMD64 *frame1 = static_cast<StackFrameAMD64*>(frames->at(1));
EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame1->trust);
ASSERT_EQ((StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP |
StackFrameAMD64::CONTEXT_VALID_RBP),
frame1->context_validity);
EXPECT_EQ(return_address, frame1->context.rip);
EXPECT_EQ(frame1_sp.Value(), frame1->context.rsp);
EXPECT_EQ(frame1_rbp.Value(), frame1->context.rbp);
EXPECT_EQ("echidna", frame1->function_name);
EXPECT_EQ(0x00007500b0000100ULL, frame1->function_base);
}
// StackwalkerAMD64::GetCallerByFramePointerRecovery should never return an
// instruction pointer of 0 because IP of 0 is an end of stack marker and the
// stack walk may be terminated prematurely. Instead it should return NULL
// so that the stack walking code can proceed to stack scanning.
TEST_F(GetCallerFrame, GetCallerByFramePointerRecovery) {
MockCodeModule user32_dll(0x00007ff9cb8a0000ULL, 0x14E000, "user32.dll",
"version1");
SetModuleSymbols(&user32_dll, // user32.dll
"PUBLIC fa60 0 DispatchMessageWorker\n"
"PUBLIC fee0 0 UserCallWinProcCheckWow\n"
"PUBLIC 1cdb0 0 _fnHkINLPMSG\n"
"STACK CFI INIT fa60 340 .cfa: $rsp .ra: .cfa 8 - ^\n"
"STACK CFI fa60 .cfa: $rsp 128 +\n"
"STACK CFI INIT fee0 49f .cfa: $rsp .ra: .cfa 8 - ^\n"
"STACK CFI fee0 .cfa: $rsp 240 +\n"
"STACK CFI INIT 1cdb0 9f .cfa: $rsp .ra: .cfa 8 - ^\n"
"STACK CFI 1cdb0 .cfa: $rsp 80 +\n");
// Create some modules with some stock debugging information.
MockCodeModules local_modules;
local_modules.Add(&user32_dll);
Label frame0_rsp;
Label frame0_rbp;
Label frame1_rsp;
Label frame2_rsp;
stack_section.start() = 0x00000099abf0f238ULL;
stack_section
.Mark(&frame0_rsp)
.D64(0x00007ff9cb8b00dcULL)
.Mark(&frame1_rsp)
.D64(0x0000000000000000ULL)
.D64(0x0000000000000001ULL)
.D64(0x00000099abf0f308ULL)
.D64(0x00007ff9cb8bce3aULL) // Stack residue from execution of
// user32!_fnHkINLPMSG+0x8a
.D64(0x000000000000c2e0ULL)
.D64(0x00000099abf0f328ULL)
.D64(0x0000000100000001ULL)
.D64(0x0000000000000000ULL)
.D64(0x0000000000000000ULL)
.D64(0x0000000000000000ULL)
.D64(0x0000000000000000ULL)
.D64(0x0000000000000000ULL)
.D64(0x0000000000000000ULL)
.D64(0x00007ff9ccad53e4ULL)
.D64(0x0000000000000048ULL)
.D64(0x0000000000000001ULL)
.D64(0x00000099abf0f5e0ULL)
.D64(0x00000099b61f7388ULL)
.D64(0x0000000000000030ULL)
.D64(0xffffff66540f0a1fULL)
.D64(0xffffff6649e08c77ULL)
.D64(0x00007ff9cb8affb4ULL) // Return address in
// user32!UserCallWinProcCheckWow+0xd4
.D64(0x0000000000000000ULL)
.D64(0x00000099abf0f368ULL)
.D64(0x0000000000000000ULL)
.D64(0x0000000000000000ULL)
.D64(0x0000000000000000ULL)
.D64(0x00000099a8150fd8ULL)
.D64(0x00000099abf0f3e8ULL)
.D64(0x00007ff9cb8afc07ULL) // Return address in
// user32!DispatchMessageWorker+0x1a7
.Mark(&frame2_rsp)
.Append(256, 0)
.Mark(&frame0_rbp) // The following are expected by
// GetCallerByFramePointerRecovery.
.D64(0xfffffffffffffffeULL) // %caller_rbp = *(%callee_rbp)
.D64(0x0000000000000000ULL) // %caller_rip = *(%callee_rbp + 8)
.D64(0x00000099a3e31040ULL) // %caller_rsp = *(%callee_rbp + 16)
.Append(256, 0);
RegionFromSection();
raw_context.rip = 0x00000099a8150fd8ULL; // IP in context frame is guarbage
raw_context.rsp = frame0_rsp.Value();
raw_context.rbp = frame0_rbp.Value();
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAMD64 walker(&system_info, &raw_context, &stack_region,
&local_modules, &frame_symbolizer);
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
ASSERT_EQ(0U, modules_without_symbols.size());
ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
frames = call_stack.frames();
ASSERT_EQ(3U, frames->size());
{ // To avoid reusing locals by mistake
StackFrameAMD64 *frame = static_cast<StackFrameAMD64*>(frames->at(0));
EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame->trust);
ASSERT_EQ(StackFrameAMD64::CONTEXT_VALID_ALL, frame->context_validity);
EXPECT_EQ("", frame->function_name);
EXPECT_EQ(0x00000099a8150fd8ULL, frame->instruction);
EXPECT_EQ(0x00000099a8150fd8ULL, frame->context.rip);
EXPECT_EQ(frame0_rsp.Value(), frame->context.rsp);
EXPECT_EQ(frame0_rbp.Value(), frame->context.rbp);
}
{ // To avoid reusing locals by mistake
StackFrameAMD64 *frame = static_cast<StackFrameAMD64*>(frames->at(1));
EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame->trust);
ASSERT_EQ((StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP |
StackFrameAMD64::CONTEXT_VALID_RBP),
frame->context_validity);
EXPECT_EQ("UserCallWinProcCheckWow", frame->function_name);
EXPECT_EQ(140710838468828ULL, frame->instruction + 1);
EXPECT_EQ(140710838468828ULL, frame->context.rip);
EXPECT_EQ(frame1_rsp.Value(), frame->context.rsp);
EXPECT_EQ(&user32_dll, frame->module);
}
{ // To avoid reusing locals by mistake
StackFrameAMD64 *frame = static_cast<StackFrameAMD64*>(frames->at(2));
EXPECT_EQ(StackFrame::FRAME_TRUST_CFI, frame->trust);
ASSERT_EQ((StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP |
StackFrameAMD64::CONTEXT_VALID_RBP),
frame->context_validity);
EXPECT_EQ("DispatchMessageWorker", frame->function_name);
EXPECT_EQ(140710838467591ULL, frame->instruction + 1);
EXPECT_EQ(140710838467591ULL, frame->context.rip);
EXPECT_EQ(frame2_rsp.Value(), frame->context.rsp);
EXPECT_EQ(&user32_dll, frame->module);
}
}
// Don't use frame pointer recovery if %rbp is not 8-byte aligned, which
// indicates that it's not being used as a frame pointer.
TEST_F(GetCallerFrame, FramePointerNotAligned) {
stack_section.start() = 0x8000000080000000ULL;
uint64_t return_address1 = 0x00007500b0000100ULL;
Label frame0_rbp, not_frame1_rbp, frame1_sp;
stack_section
// frame 0
.Align(8, 0)
.Append(2, 0) // mis-align the frame pointer
.Mark(&frame0_rbp)
.D64(not_frame1_rbp) // not the previous frame pointer
.D64(0x00007500b0000a00ULL) // plausible but wrong return address
.Align(8, 0)
.D64(return_address1) // return address
// frame 1
.Mark(&frame1_sp)
.Mark(&not_frame1_rbp)
.Append(32, 0); // end of stack
RegionFromSection();
raw_context.rip = 0x00007400c0000200ULL;
raw_context.rbp = frame0_rbp.Value();
raw_context.rsp = stack_section.start().Value();
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAMD64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
frames = call_stack.frames();
ASSERT_EQ(2U, frames->size());
StackFrameAMD64 *frame0 = static_cast<StackFrameAMD64*>(frames->at(0));
EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
ASSERT_EQ(StackFrameAMD64::CONTEXT_VALID_ALL, frame0->context_validity);
EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context)));
StackFrameAMD64 *frame1 = static_cast<StackFrameAMD64*>(frames->at(1));
EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame1->trust);
ASSERT_EQ((StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP),
frame1->context_validity);
EXPECT_EQ(return_address1, frame1->context.rip);
EXPECT_EQ(frame1_sp.Value(), frame1->context.rsp);
}
// Don't use frame pointer recovery if the recovered %rip is not
// a canonical x86-64 address.
TEST_F(GetCallerFrame, NonCanonicalInstructionPointerFromFramePointer) {
stack_section.start() = 0x8000000080000000ULL;
uint64_t return_address1 = 0x00007500b0000100ULL;
Label frame0_rbp, frame1_sp, not_frame1_bp;
stack_section
// frame 0
.Align(8, 0)
.Mark(&frame0_rbp)
.D64(not_frame1_bp) // some junk on the stack
.D64(0xDADADADADADADADA) // not the return address
.D64(return_address1) // return address
// frame 1
.Mark(&frame1_sp)
.Append(16, 0)
.Mark(&not_frame1_bp)
.Append(32, 0); // end of stack
RegionFromSection();
raw_context.rip = 0x00007400c0000200ULL;
raw_context.rbp = frame0_rbp.Value();
raw_context.rsp = stack_section.start().Value();
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAMD64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
frames = call_stack.frames();
ASSERT_EQ(2U, frames->size());
StackFrameAMD64 *frame0 = static_cast<StackFrameAMD64*>(frames->at(0));
EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
ASSERT_EQ(StackFrameAMD64::CONTEXT_VALID_ALL, frame0->context_validity);
EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context)));
StackFrameAMD64 *frame1 = static_cast<StackFrameAMD64*>(frames->at(1));
EXPECT_EQ(StackFrame::FRAME_TRUST_SCAN, frame1->trust);
ASSERT_EQ((StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP),
frame1->context_validity);
EXPECT_EQ(return_address1, frame1->context.rip);
EXPECT_EQ(frame1_sp.Value(), frame1->context.rsp);
}
// Test that set_max_frames_scanned prevents using stack scanning
// to find caller frames.
TEST_F(GetCallerFrame, ScanningNotAllowed) {
// When the stack walker resorts to scanning the stack,
// only addresses located within loaded modules are
// considered valid return addresses.
stack_section.start() = 0x8000000080000000ULL;
uint64_t return_address1 = 0x00007500b0000100ULL;
uint64_t return_address2 = 0x00007500b0000900ULL;
Label frame1_sp, frame2_sp, frame1_rbp;
stack_section
// frame 0
.Append(16, 0) // space
.D64(0x00007400b0000000ULL) // junk that's not
.D64(0x00007500d0000000ULL) // a return address
.D64(return_address1) // actual return address
// frame 1
.Mark(&frame1_sp)
.Append(16, 0) // space
.D64(0x00007400b0000000ULL) // more junk
.D64(0x00007500d0000000ULL)
.Mark(&frame1_rbp)
.D64(stack_section.start()) // This is in the right place to be
// a saved rbp, but it's bogus, so
// we shouldn't report it.
.D64(return_address2) // actual return address
// frame 2
.Mark(&frame2_sp)
.Append(32, 0); // end of stack
RegionFromSection();
raw_context.rip = 0x00007400c0000200ULL;
raw_context.rbp = frame1_rbp.Value();
raw_context.rsp = stack_section.start().Value();
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAMD64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
Stackwalker::set_max_frames_scanned(0);
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
ASSERT_EQ(1U, modules_without_symbols.size());
ASSERT_EQ("module1", modules_without_symbols[0]->debug_file());
ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
frames = call_stack.frames();
ASSERT_EQ(1U, frames->size());
StackFrameAMD64 *frame0 = static_cast<StackFrameAMD64*>(frames->at(0));
EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
ASSERT_EQ(StackFrameAMD64::CONTEXT_VALID_ALL, frame0->context_validity);
EXPECT_EQ(0, memcmp(&raw_context, &frame0->context, sizeof(raw_context)));
}
TEST_F(GetCallerFrame, CallerPushedRBP) {
// Functions typically push their %rbp upon entry and set %rbp pointing
// there. If stackwalking finds a plausible address for the next frame's
// %rbp directly below the return address, assume that it is indeed the
// next frame's %rbp.
stack_section.start() = 0x8000000080000000ULL;
uint64_t return_address = 0x00007500b0000110ULL;
Label frame0_rbp, frame1_sp, frame1_rbp;
stack_section
// frame 0
.Append(16, 0) // space
.D64(0x00007400b0000000ULL) // junk that's not
.D64(0x00007500b0000000ULL) // a return address
.D64(0x00007400c0001000ULL) // a couple of plausible addresses
.D64(0x00007500b000aaaaULL) // that are not within functions
.Mark(&frame0_rbp)
.D64(frame1_rbp) // caller-pushed %rbp
.D64(return_address) // actual return address
// frame 1
.Mark(&frame1_sp)
.Append(32, 0) // body of frame1
.Mark(&frame1_rbp) // end of stack
.D64(0);
RegionFromSection();
raw_context.rip = 0x00007400c0000200ULL;
raw_context.rbp = frame0_rbp.Value();
raw_context.rsp = stack_section.start().Value();
SetModuleSymbols(&module1,
// The youngest frame's function.
"FUNC 100 400 10 sasquatch\n");
SetModuleSymbols(&module2,
// The calling frame's function.
"FUNC 100 400 10 yeti\n");
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAMD64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
ASSERT_EQ(0U, modules_without_symbols.size());
ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
frames = call_stack.frames();
ASSERT_EQ(2U, frames->size());
StackFrameAMD64 *frame0 = static_cast<StackFrameAMD64*>(frames->at(0));
EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
ASSERT_EQ(StackFrameAMD64::CONTEXT_VALID_ALL, frame0->context_validity);
EXPECT_EQ(frame0_rbp.Value(), frame0->context.rbp);
EXPECT_EQ("sasquatch", frame0->function_name);
EXPECT_EQ(0x00007400c0000100ULL, frame0->function_base);
StackFrameAMD64 *frame1 = static_cast<StackFrameAMD64*>(frames->at(1));
EXPECT_EQ(StackFrame::FRAME_TRUST_FP, frame1->trust);
ASSERT_EQ((StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP |
StackFrameAMD64::CONTEXT_VALID_RBP),
frame1->context_validity);
EXPECT_EQ(return_address, frame1->context.rip);
EXPECT_EQ(frame1_sp.Value(), frame1->context.rsp);
EXPECT_EQ(frame1_rbp.Value(), frame1->context.rbp);
EXPECT_EQ("yeti", frame1->function_name);
EXPECT_EQ(0x00007500b0000100ULL, frame1->function_base);
}
struct CFIFixture: public StackwalkerAMD64Fixture {
CFIFixture() {
// Provide a bunch of STACK CFI records; we'll walk to the caller
// from every point in this series, expecting to find the same set
// of register values.
SetModuleSymbols(&module1,
// The youngest frame's function.
"FUNC 4000 1000 10 enchiridion\n"
// Initially, just a return address.
"STACK CFI INIT 4000 100 .cfa: $rsp 8 + .ra: .cfa 8 - ^\n"
// Push %rbx.
"STACK CFI 4001 .cfa: $rsp 16 + $rbx: .cfa 16 - ^\n"
// Save %r12 in %rbx. Weird, but permitted.
"STACK CFI 4002 $r12: $rbx\n"
// Allocate frame space, and save %r13.
"STACK CFI 4003 .cfa: $rsp 40 + $r13: .cfa 32 - ^\n"
// Put the return address in %r13.
"STACK CFI 4005 .ra: $r13\n"
// Save %rbp, and use it as a frame pointer.
"STACK CFI 4006 .cfa: $rbp 16 + $rbp: .cfa 24 - ^\n"
// The calling function.
"FUNC 5000 1000 10 epictetus\n"
// Mark it as end of stack.
"STACK CFI INIT 5000 1000 .cfa: $rsp .ra 0\n");
// Provide some distinctive values for the caller's registers.
expected.rsp = 0x8000000080000000ULL;
expected.rip = 0x00007400c0005510ULL;
expected.rbp = 0x68995b1de4700266ULL;
expected.rbx = 0x5a5beeb38de23be8ULL;
expected.r12 = 0xed1b02e8cc0fc79cULL;
expected.r13 = 0x1d20ad8acacbe930ULL;
expected.r14 = 0xe94cffc2f7adaa28ULL;
expected.r15 = 0xb638d17d8da413b5ULL;
// By default, registers are unchanged.
raw_context = expected;
}
// Walk the stack, using stack_section as the contents of the stack
// and raw_context as the current register values. (Set
// raw_context.rsp to the stack's starting address.) Expect two
// stack frames; in the older frame, expect the callee-saves
// registers to have values matching those in 'expected'.
void CheckWalk() {
RegionFromSection();
raw_context.rsp = stack_section.start().Value();
StackFrameSymbolizer frame_symbolizer(&supplier, &resolver);
StackwalkerAMD64 walker(&system_info, &raw_context, &stack_region, &modules,
&frame_symbolizer);
vector<const CodeModule*> modules_without_symbols;
vector<const CodeModule*> modules_with_corrupt_symbols;
ASSERT_TRUE(walker.Walk(&call_stack, &modules_without_symbols,
&modules_with_corrupt_symbols));
ASSERT_EQ(0U, modules_without_symbols.size());
ASSERT_EQ(0U, modules_with_corrupt_symbols.size());
frames = call_stack.frames();
ASSERT_EQ(2U, frames->size());
StackFrameAMD64 *frame0 = static_cast<StackFrameAMD64*>(frames->at(0));
EXPECT_EQ(StackFrame::FRAME_TRUST_CONTEXT, frame0->trust);
ASSERT_EQ(StackFrameAMD64::CONTEXT_VALID_ALL, frame0->context_validity);
EXPECT_EQ("enchiridion", frame0->function_name);
EXPECT_EQ(0x00007400c0004000ULL, frame0->function_base);
StackFrameAMD64 *frame1 = static_cast<StackFrameAMD64*>(frames->at(1));
EXPECT_EQ(StackFrame::FRAME_TRUST_CFI, frame1->trust);
ASSERT_EQ((StackFrameAMD64::CONTEXT_VALID_RIP |
StackFrameAMD64::CONTEXT_VALID_RSP |
StackFrameAMD64::CONTEXT_VALID_RBP |
StackFrameAMD64::CONTEXT_VALID_RBX |
StackFrameAMD64::CONTEXT_VALID_R12 |
StackFrameAMD64::CONTEXT_VALID_R13 |
StackFrameAMD64::CONTEXT_VALID_R14 |
StackFrameAMD64::CONTEXT_VALID_R15),
frame1->context_validity);
EXPECT_EQ(expected.rip, frame1->context.rip);
EXPECT_EQ(expected.rsp, frame1->context.rsp);
EXPECT_EQ(expected.rbp, frame1->context.rbp);
EXPECT_EQ(expected.rbx, frame1->context.rbx);
EXPECT_EQ(expected.r12, frame1->context.r12);
EXPECT_EQ(expected.r13, frame1->context.r13);
EXPECT_EQ(expected.r14, frame1->context.r14);
EXPECT_EQ(expected.r15, frame1->context.r15);
EXPECT_EQ("epictetus", frame1->function_name);
}
// The values we expect to find for the caller's registers.
MDRawContextAMD64 expected;
};
class CFI: public CFIFixture, public Test { };
TEST_F(CFI, At4000) {
Label frame1_rsp = expected.rsp;
stack_section
.D64(0x00007400c0005510ULL) // return address
.Mark(&frame1_rsp); // This effectively sets stack_section.start().
raw_context.rip = 0x00007400c0004000ULL;
CheckWalk();
}
TEST_F(CFI, At4001) {
Label frame1_rsp = expected.rsp;
stack_section
.D64(0x5a5beeb38de23be8ULL) // saved %rbx
.D64(0x00007400c0005510ULL) // return address
.Mark(&frame1_rsp); // This effectively sets stack_section.start().
raw_context.rip = 0x00007400c0004001ULL;
raw_context.rbx = 0xbe0487d2f9eafe29ULL; // callee's (distinct) %rbx value
CheckWalk();
}
TEST_F(CFI, At4002) {
Label frame1_rsp = expected.rsp;
stack_section
.D64(0x5a5beeb38de23be8ULL) // saved %rbx
.D64(0x00007400c0005510ULL) // return address
.Mark(&frame1_rsp); // This effectively sets stack_section.start().
raw_context.rip = 0x00007400c0004002ULL;
raw_context.rbx = 0xed1b02e8cc0fc79cULL; // saved %r12
raw_context.r12 = 0xb0118de918a4bceaULL; // callee's (distinct) %r12 value
CheckWalk();
}
TEST_F(CFI, At4003) {
Label frame1_rsp = expected.rsp;
stack_section
.D64(0x0e023828dffd4d81ULL) // garbage
.D64(0x1d20ad8acacbe930ULL) // saved %r13
.D64(0x319e68b49e3ace0fULL) // garbage
.D64(0x5a5beeb38de23be8ULL) // saved %rbx
.D64(0x00007400c0005510ULL) // return address
.Mark(&frame1_rsp); // This effectively sets stack_section.start().
raw_context.rip = 0x00007400c0004003ULL;
raw_context.rbx = 0xed1b02e8cc0fc79cULL; // saved %r12
raw_context.r12 = 0x89d04fa804c87a43ULL; // callee's (distinct) %r12
raw_context.r13 = 0x5118e02cbdb24b03ULL; // callee's (distinct) %r13
CheckWalk();
}
// The results here should be the same as those at module offset 0x4003.
TEST_F(CFI, At4004) {
Label frame1_rsp = expected.rsp;
stack_section
.D64(0x0e023828dffd4d81ULL) // garbage
.D64(0x1d20ad8acacbe930ULL) // saved %r13
.D64(0x319e68b49e3ace0fULL) // garbage
.D64(0x5a5beeb38de23be8ULL) // saved %rbx
.D64(0x00007400c0005510ULL) // return address
.Mark(&frame1_rsp); // This effectively sets stack_section.start().
raw_context.rip = 0x00007400c0004004ULL;
raw_context.rbx = 0xed1b02e8cc0fc79cULL; // saved %r12
raw_context.r12 = 0x89d04fa804c87a43ULL; // callee's (distinct) %r12
raw_context.r13 = 0x5118e02cbdb24b03ULL; // callee's (distinct) %r13
CheckWalk();
}
TEST_F(CFI, At4005) {
Label frame1_rsp = expected.rsp;
stack_section
.D64(0x4b516dd035745953ULL) // garbage
.D64(0x1d20ad8acacbe930ULL) // saved %r13
.D64(0xa6d445e16ae3d872ULL) // garbage
.D64(0x5a5beeb38de23be8ULL) // saved %rbx
.D64(0xaa95fa054aedfbaeULL) // garbage
.Mark(&frame1_rsp); // This effectively sets stack_section.start().
raw_context.rip = 0x00007400c0004005ULL;
raw_context.rbx = 0xed1b02e8cc0fc79cULL; // saved %r12
raw_context.r12 = 0x46b1b8868891b34aULL; // callee's %r12
raw_context.r13 = 0x00007400c0005510ULL; // return address
CheckWalk();
}
TEST_F(CFI, At4006) {
Label frame0_rbp;
Label frame1_rsp = expected.rsp;
stack_section
.D64(0x043c6dfceb91aa34ULL) // garbage
.D64(0x1d20ad8acacbe930ULL) // saved %r13
.D64(0x68995b1de4700266ULL) // saved %rbp
.Mark(&frame0_rbp) // frame pointer points here
.D64(0x5a5beeb38de23be8ULL) // saved %rbx
.D64(0xf015ee516ad89eabULL) // garbage
.Mark(&frame1_rsp); // This effectively sets stack_section.start().
raw_context.rip = 0x00007400c0004006ULL;
raw_context.rbp = frame0_rbp.Value();
raw_context.rbx = 0xed1b02e8cc0fc79cULL; // saved %r12
raw_context.r12 = 0x26e007b341acfebdULL; // callee's %r12
raw_context.r13 = 0x00007400c0005510ULL; // return address
CheckWalk();
}

View file

@ -0,0 +1,302 @@
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// stackwalker_arm.cc: arm-specific stackwalker.
//
// See stackwalker_arm.h for documentation.
//
// Author: Mark Mentovai, Ted Mielczarek, Jim Blandy
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <vector>
#include "common/scoped_ptr.h"
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/memory_region.h"
#include "google_breakpad/processor/source_line_resolver_interface.h"
#include "google_breakpad/processor/stack_frame_cpu.h"
#include "google_breakpad/processor/system_info.h"
#include "processor/cfi_frame_info.h"
#include "processor/logging.h"
#include "processor/stackwalker_arm.h"
namespace google_breakpad {
StackwalkerARM::StackwalkerARM(const SystemInfo* system_info,
const MDRawContextARM* context,
int fp_register,
MemoryRegion* memory,
const CodeModules* modules,
StackFrameSymbolizer* resolver_helper)
: Stackwalker(system_info, memory, modules, resolver_helper),
context_(context), fp_register_(fp_register),
context_frame_validity_(StackFrameARM::CONTEXT_VALID_ALL) { }
StackFrame* StackwalkerARM::GetContextFrame() {
if (!context_) {
BPLOG(ERROR) << "Can't get context frame without context";
return NULL;
}
StackFrameARM* frame = new StackFrameARM();
// The instruction pointer is stored directly in a register (r15), so pull it
// straight out of the CPU context structure.
frame->context = *context_;
frame->context_validity = context_frame_validity_;
frame->trust = StackFrame::FRAME_TRUST_CONTEXT;
frame->instruction = frame->context.iregs[MD_CONTEXT_ARM_REG_PC];
return frame;
}
StackFrameARM* StackwalkerARM::GetCallerByCFIFrameInfo(
const vector<StackFrame*>& frames,
CFIFrameInfo* cfi_frame_info) {
StackFrameARM* last_frame = static_cast<StackFrameARM*>(frames.back());
static const char* register_names[] = {
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
"r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc",
"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
"fps", "cpsr",
NULL
};
// Populate a dictionary with the valid register values in last_frame.
CFIFrameInfo::RegisterValueMap<uint32_t> callee_registers;
for (int i = 0; register_names[i]; i++)
if (last_frame->context_validity & StackFrameARM::RegisterValidFlag(i))
callee_registers[register_names[i]] = last_frame->context.iregs[i];
// Use the STACK CFI data to recover the caller's register values.
CFIFrameInfo::RegisterValueMap<uint32_t> caller_registers;
if (!cfi_frame_info->FindCallerRegs(callee_registers, *memory_,
&caller_registers))
return NULL;
// Construct a new stack frame given the values the CFI recovered.
scoped_ptr<StackFrameARM> frame(new StackFrameARM());
for (int i = 0; register_names[i]; i++) {
CFIFrameInfo::RegisterValueMap<uint32_t>::iterator entry =
caller_registers.find(register_names[i]);
if (entry != caller_registers.end()) {
// We recovered the value of this register; fill the context with the
// value from caller_registers.
frame->context_validity |= StackFrameARM::RegisterValidFlag(i);
frame->context.iregs[i] = entry->second;
} else if (4 <= i && i <= 11 && (last_frame->context_validity &
StackFrameARM::RegisterValidFlag(i))) {
// If the STACK CFI data doesn't mention some callee-saves register, and
// it is valid in the callee, assume the callee has not yet changed it.
// Registers r4 through r11 are callee-saves, according to the Procedure
// Call Standard for the ARM Architecture, which the Linux ABI follows.
frame->context_validity |= StackFrameARM::RegisterValidFlag(i);
frame->context.iregs[i] = last_frame->context.iregs[i];
}
}
// If the CFI doesn't recover the PC explicitly, then use .ra.
if (!(frame->context_validity & StackFrameARM::CONTEXT_VALID_PC)) {
CFIFrameInfo::RegisterValueMap<uint32_t>::iterator entry =
caller_registers.find(".ra");
if (entry != caller_registers.end()) {
if (fp_register_ == -1) {
frame->context_validity |= StackFrameARM::CONTEXT_VALID_PC;
frame->context.iregs[MD_CONTEXT_ARM_REG_PC] = entry->second;
} else {
// The CFI updated the link register and not the program counter.
// Handle getting the program counter from the link register.
frame->context_validity |= StackFrameARM::CONTEXT_VALID_PC;
frame->context_validity |= StackFrameARM::CONTEXT_VALID_LR;
frame->context.iregs[MD_CONTEXT_ARM_REG_LR] = entry->second;
frame->context.iregs[MD_CONTEXT_ARM_REG_PC] =
last_frame->context.iregs[MD_CONTEXT_ARM_REG_LR];
}
}
}
// If the CFI doesn't recover the SP explicitly, then use .cfa.
if (!(frame->context_validity & StackFrameARM::CONTEXT_VALID_SP)) {
CFIFrameInfo::RegisterValueMap<uint32_t>::iterator entry =
caller_registers.find(".cfa");
if (entry != caller_registers.end()) {
frame->context_validity |= StackFrameARM::CONTEXT_VALID_SP;
frame->context.iregs[MD_CONTEXT_ARM_REG_SP] = entry->second;
}
}
// If we didn't recover the PC and the SP, then the frame isn't very useful.
static const int essentials = (StackFrameARM::CONTEXT_VALID_SP
| StackFrameARM::CONTEXT_VALID_PC);
if ((frame->context_validity & essentials) != essentials)
return NULL;
frame->trust = StackFrame::FRAME_TRUST_CFI;
return frame.release();
}
StackFrameARM* StackwalkerARM::GetCallerByStackScan(
const vector<StackFrame*>& frames) {
StackFrameARM* last_frame = static_cast<StackFrameARM*>(frames.back());
uint32_t last_sp = last_frame->context.iregs[MD_CONTEXT_ARM_REG_SP];
uint32_t caller_sp, caller_pc;
if (!ScanForReturnAddress(last_sp, &caller_sp, &caller_pc,
/*is_context_frame=*/last_frame->trust ==
StackFrame::FRAME_TRUST_CONTEXT)) {
// No plausible return address was found.
return NULL;
}
// ScanForReturnAddress found a reasonable return address. Advance
// %sp to the location above the one where the return address was
// found.
caller_sp += 4;
// Create a new stack frame (ownership will be transferred to the caller)
// and fill it in.
StackFrameARM* frame = new StackFrameARM();
frame->trust = StackFrame::FRAME_TRUST_SCAN;
frame->context = last_frame->context;
frame->context.iregs[MD_CONTEXT_ARM_REG_PC] = caller_pc;
frame->context.iregs[MD_CONTEXT_ARM_REG_SP] = caller_sp;
frame->context_validity = StackFrameARM::CONTEXT_VALID_PC |
StackFrameARM::CONTEXT_VALID_SP;
return frame;
}
StackFrameARM* StackwalkerARM::GetCallerByFramePointer(
const vector<StackFrame*>& frames) {
StackFrameARM* last_frame = static_cast<StackFrameARM*>(frames.back());
if (!(last_frame->context_validity &
StackFrameARM::RegisterValidFlag(fp_register_))) {
return NULL;
}
uint32_t last_fp = last_frame->context.iregs[fp_register_];
uint32_t caller_fp = 0;
if (last_fp && !memory_->GetMemoryAtAddress(last_fp, &caller_fp)) {
BPLOG(ERROR) << "Unable to read caller_fp from last_fp: 0x"
<< std::hex << last_fp;
return NULL;
}
uint32_t caller_lr = 0;
if (last_fp && !memory_->GetMemoryAtAddress(last_fp + 4, &caller_lr)) {
BPLOG(ERROR) << "Unable to read caller_lr from last_fp + 4: 0x"
<< std::hex << (last_fp + 4);
return NULL;
}
uint32_t caller_sp = last_fp ? last_fp + 8 :
last_frame->context.iregs[MD_CONTEXT_ARM_REG_SP];
// Create a new stack frame (ownership will be transferred to the caller)
// and fill it in.
StackFrameARM* frame = new StackFrameARM();
frame->trust = StackFrame::FRAME_TRUST_FP;
frame->context = last_frame->context;
frame->context.iregs[fp_register_] = caller_fp;
frame->context.iregs[MD_CONTEXT_ARM_REG_SP] = caller_sp;
frame->context.iregs[MD_CONTEXT_ARM_REG_PC] =
last_frame->context.iregs[MD_CONTEXT_ARM_REG_LR];
frame->context.iregs[MD_CONTEXT_ARM_REG_LR] = caller_lr;
frame->context_validity = StackFrameARM::CONTEXT_VALID_PC |
StackFrameARM::CONTEXT_VALID_LR |
StackFrameARM::RegisterValidFlag(fp_register_) |
StackFrameARM::CONTEXT_VALID_SP;
return frame;
}
StackFrame* StackwalkerARM::GetCallerFrame(const CallStack* stack,
bool stack_scan_allowed) {
if (!memory_ || !stack) {
BPLOG(ERROR) << "Can't get caller frame without memory or stack";
return NULL;
}
const vector<StackFrame*>& frames = *stack->frames();
StackFrameARM* last_frame = static_cast<StackFrameARM*>(frames.back());
scoped_ptr<StackFrameARM> frame;
// See if there is DWARF call frame information covering this address.
// TODO(jperaza): Ignore iOS CFI info until it is properly collected.
// https://bugs.chromium.org/p/google-breakpad/issues/detail?id=764
if (!system_info_ || system_info_->os != "iOS") {
scoped_ptr<CFIFrameInfo> cfi_frame_info(
frame_symbolizer_->FindCFIFrameInfo(last_frame));
if (cfi_frame_info.get())
frame.reset(GetCallerByCFIFrameInfo(frames, cfi_frame_info.get()));
}
// If CFI failed, or there wasn't CFI available, fall back
// to frame pointer, if this is configured.
if (fp_register_ >= 0 && !frame.get())
frame.reset(GetCallerByFramePointer(frames));
// If everuthing failed, fall back to stack scanning.
if (stack_scan_allowed && !frame.get())
frame.reset(GetCallerByStackScan(frames));
// If nothing worked, tell the caller.
if (!frame.get())
return NULL;
// Should we terminate the stack walk? (end-of-stack or broken invariant)
if (TerminateWalk(frame->context.iregs[MD_CONTEXT_ARM_REG_PC],
frame->context.iregs[MD_CONTEXT_ARM_REG_SP],
last_frame->context.iregs[MD_CONTEXT_ARM_REG_SP],
/*first_unwind=*/last_frame->trust ==
StackFrame::FRAME_TRUST_CONTEXT)) {
return NULL;
}
// The new frame's context's PC is the return address, which is one
// instruction past the instruction that caused us to arrive at the
// callee. Set new_frame->instruction to one less than the PC. This won't
// reference the beginning of the call instruction, but it's at least
// within it, which is sufficient to get the source line information to
// match up with the line that contains the function call. Callers that
// require the exact return address value may access
// frame->context.iregs[MD_CONTEXT_ARM_REG_PC].
frame->instruction = frame->context.iregs[MD_CONTEXT_ARM_REG_PC] - 2;
return frame.release();
}
} // namespace google_breakpad

View file

@ -0,0 +1,106 @@
// -*- mode: C++ -*-
// Copyright 2010 Google LLC
//
// 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 LLC 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.
// stackwalker_arm.h: arm-specific stackwalker.
//
// Provides stack frames given arm register context and a memory region
// corresponding to an arm stack.
//
// Author: Mark Mentovai, Ted Mielczarek
#ifndef PROCESSOR_STACKWALKER_ARM_H__
#define PROCESSOR_STACKWALKER_ARM_H__
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/common/minidump_format.h"
#include "google_breakpad/processor/stackwalker.h"
namespace google_breakpad {
class CodeModules;
class StackwalkerARM : public Stackwalker {
public:
// context is an arm context object that gives access to arm-specific
// register state corresponding to the innermost called frame to be
// included in the stack. The other arguments are passed directly through
// to the base Stackwalker constructor.
StackwalkerARM(const SystemInfo* system_info,
const MDRawContextARM* context,
int fp_register,
MemoryRegion* memory,
const CodeModules* modules,
StackFrameSymbolizer* frame_symbolizer);
// Change the context validity mask of the frame returned by
// GetContextFrame to VALID. This is only for use by unit tests; the
// default behavior is correct for all application code.
void SetContextFrameValidity(int valid) { context_frame_validity_ = valid; }
private:
// Implementation of Stackwalker, using arm context and stack conventions.
virtual StackFrame* GetContextFrame();
virtual StackFrame* GetCallerFrame(const CallStack* stack,
bool stack_scan_allowed);
// Use cfi_frame_info (derived from STACK CFI records) to construct
// the frame that called frames.back(). The caller takes ownership
// of the returned frame. Return NULL on failure.
StackFrameARM* GetCallerByCFIFrameInfo(const vector<StackFrame*>& frames,
CFIFrameInfo* cfi_frame_info);
// Use the frame pointer. The caller takes ownership of the returned frame.
// Return NULL on failure.
StackFrameARM* GetCallerByFramePointer(const vector<StackFrame*>& frames);
// Scan the stack for plausible return addresses. The caller takes ownership
// of the returned frame. Return NULL on failure.
StackFrameARM* GetCallerByStackScan(const vector<StackFrame*>& frames);
// Stores the CPU context corresponding to the youngest stack frame, to
// be returned by GetContextFrame.
const MDRawContextARM* context_;
// The register to use a as frame pointer. The value is -1 if frame pointer
// cannot be used.
int fp_register_;
// Validity mask for youngest stack frame. This is always
// CONTEXT_VALID_ALL in real use; it is only changeable for the sake of
// unit tests.
int context_frame_validity_;
};
} // namespace google_breakpad
#endif // PROCESSOR_STACKWALKER_ARM_H__

View file

@ -0,0 +1,357 @@
// Copyright 2013 Google LLC
//
// 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 LLC 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.
// stackwalker_arm64.cc: arm64-specific stackwalker.
//
// See stackwalker_arm64.h for documentation.
//
// Author: Mark Mentovai, Ted Mielczarek, Jim Blandy, Colin Blundell
#ifdef HAVE_CONFIG_H
#include <config.h> // Must come first
#endif
#include <vector>
#include "common/scoped_ptr.h"
#include "google_breakpad/processor/call_stack.h"
#include "google_breakpad/processor/memory_region.h"
#include "google_breakpad/processor/source_line_resolver_interface.h"
#include "google_breakpad/processor/stack_frame_cpu.h"
#include "processor/cfi_frame_info.h"
#include "processor/logging.h"
#include "processor/stackwalker_arm64.h"
namespace google_breakpad {
StackwalkerARM64::StackwalkerARM64(const SystemInfo* system_info,
const MDRawContextARM64* context,
MemoryRegion* memory,
const CodeModules* modules,
StackFrameSymbolizer* resolver_helper)
: Stackwalker(system_info, memory, modules, resolver_helper),
context_(context),
context_frame_validity_(StackFrameARM64::CONTEXT_VALID_ALL),
address_range_mask_(0xffffffffffffffff) {
if (modules && modules->module_count() > 0) {
// ARM64 supports storing pointer authentication codes in the upper bits of
// a pointer. Make a best guess at the range of valid addresses based on the
// range of loaded modules.
const CodeModule *high_module =
modules->GetModuleAtSequence(modules->module_count() - 1);
uint64_t mask = high_module->base_address() + high_module->size();
mask |= mask >> 1;
mask |= mask >> 2;
mask |= mask >> 4;
mask |= mask >> 8;
mask |= mask >> 16;
mask |= mask >> 32;
address_range_mask_ = mask;
}
}
uint64_t StackwalkerARM64::PtrauthStrip(uint64_t ptr) {
uint64_t stripped = ptr & address_range_mask_;
return modules_ && modules_->GetModuleForAddress(stripped) ? stripped : ptr;
}
StackFrame* StackwalkerARM64::GetContextFrame() {
if (!context_) {
BPLOG(ERROR) << "Can't get context frame without context";
return NULL;
}
StackFrameARM64* frame = new StackFrameARM64();
// The instruction pointer is stored directly in a register (x32), so pull it
// straight out of the CPU context structure.
frame->context = *context_;
frame->context_validity = context_frame_validity_;
frame->trust = StackFrame::FRAME_TRUST_CONTEXT;
frame->instruction = frame->context.iregs[MD_CONTEXT_ARM64_REG_PC];
frame->context.iregs[MD_CONTEXT_ARM64_REG_LR] =
PtrauthStrip(frame->context.iregs[MD_CONTEXT_ARM64_REG_LR]);
return frame;
}
StackFrameARM64* StackwalkerARM64::GetCallerByCFIFrameInfo(
const vector<StackFrame*>& frames,
CFIFrameInfo* cfi_frame_info) {
StackFrameARM64* last_frame = static_cast<StackFrameARM64*>(frames.back());
static const char* register_names[] = {
"x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
"x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
"x16", "x17", "x18", "x19", "x20", "x21", "x22", "x23",
"x24", "x25", "x26", "x27", "x28", "x29", "x30", "sp",
"pc", NULL
};
// Populate a dictionary with the valid register values in last_frame.
CFIFrameInfo::RegisterValueMap<uint64_t> callee_registers;
for (int i = 0; register_names[i]; i++) {
if (last_frame->context_validity & StackFrameARM64::RegisterValidFlag(i))
callee_registers[register_names[i]] = last_frame->context.iregs[i];
}
// Use the STACK CFI data to recover the caller's register values.
CFIFrameInfo::RegisterValueMap<uint64_t> caller_registers;
if (!cfi_frame_info->FindCallerRegs(callee_registers, *memory_,
&caller_registers)) {
return NULL;
}
// Construct a new stack frame given the values the CFI recovered.
scoped_ptr<StackFrameARM64> frame(new StackFrameARM64());
for (int i = 0; register_names[i]; i++) {
CFIFrameInfo::RegisterValueMap<uint64_t>::iterator entry =
caller_registers.find(register_names[i]);
if (entry != caller_registers.end()) {
// We recovered the value of this register; fill the context with the
// value from caller_registers.
frame->context_validity |= StackFrameARM64::RegisterValidFlag(i);
frame->context.iregs[i] = entry->second;
} else if (19 <= i && i <= 29 && (last_frame->context_validity &
StackFrameARM64::RegisterValidFlag(i))) {
// If the STACK CFI data doesn't mention some callee-saves register, and
// it is valid in the callee, assume the callee has not yet changed it.
// Registers r19 through r29 are callee-saves, according to the Procedure
// Call Standard for the ARM AARCH64 Architecture, which the Linux ABI
// follows.
frame->context_validity |= StackFrameARM64::RegisterValidFlag(i);
frame->context.iregs[i] = last_frame->context.iregs[i];
}
}
// If the CFI doesn't recover the PC explicitly, then use .ra.
if (!(frame->context_validity & StackFrameARM64::CONTEXT_VALID_PC)) {
CFIFrameInfo::RegisterValueMap<uint64_t>::iterator entry =
caller_registers.find(".ra");
if (entry != caller_registers.end()) {
frame->context_validity |= StackFrameARM64::CONTEXT_VALID_PC;
frame->context.iregs[MD_CONTEXT_ARM64_REG_PC] = entry->second;
}
}
// If the CFI doesn't recover the SP explicitly, then use .cfa.
if (!(frame->context_validity & StackFrameARM64::CONTEXT_VALID_SP)) {
CFIFrameInfo::RegisterValueMap<uint64_t>::iterator entry =
caller_registers.find(".cfa");
if (entry != caller_registers.end()) {
frame->context_validity |= StackFrameARM64::CONTEXT_VALID_SP;
frame->context.iregs[MD_CONTEXT_ARM64_REG_SP] = entry->second;
}
}
// If we didn't recover the PC and the SP, then the frame isn't very useful.
static const uint64_t essentials = (StackFrameARM64::CONTEXT_VALID_SP
| StackFrameARM64::CONTEXT_VALID_PC);
if ((frame->context_validity & essentials) != essentials)
return NULL;
frame->context.iregs[MD_CONTEXT_ARM64_REG_PC] =
PtrauthStrip(frame->context.iregs[MD_CONTEXT_ARM64_REG_PC]);
frame->trust = StackFrame::FRAME_TRUST_CFI;
return frame.release();
}
StackFrameARM64* StackwalkerARM64::GetCallerByStackScan(
const vector<StackFrame*>& frames) {
StackFrameARM64* last_frame = static_cast<StackFrameARM64*>(frames.back());
uint64_t last_sp = last_frame->context.iregs[MD_CONTEXT_ARM64_REG_SP];
uint64_t caller_sp, caller_pc;
if (!ScanForReturnAddress(last_sp, &caller_sp, &caller_pc,
/*is_context_frame=*/last_frame->trust ==
StackFrame::FRAME_TRUST_CONTEXT)) {
// No plausible return address was found.
return NULL;
}
// ScanForReturnAddress found a reasonable return address. Advance
// %sp to the location above the one where the return address was
// found.
caller_sp += 8;
// Create a new stack frame (ownership will be transferred to the caller)
// and fill it in.
StackFrameARM64* frame = new StackFrameARM64();
frame->trust = StackFrame::FRAME_TRUST_SCAN;
frame->context = last_frame->context;
frame->context.iregs[MD_CONTEXT_ARM64_REG_PC] = caller_pc;
frame->context.iregs[MD_CONTEXT_ARM64_REG_SP] = caller_sp;
frame->context_validity = StackFrameARM64::CONTEXT_VALID_PC |
StackFrameARM64::CONTEXT_VALID_SP;
return frame;
}
StackFrameARM64* StackwalkerARM64::GetCallerByFramePointer(
const vector<StackFrame*>& frames) {
StackFrameARM64* last_frame = static_cast<StackFrameARM64*>(frames.back());
if (!(last_frame->context_validity & StackFrameARM64::CONTEXT_VALID_LR)) {
CorrectRegLRByFramePointer(frames, last_frame);
}
uint64_t last_fp = last_frame->context.iregs[MD_CONTEXT_ARM64_REG_FP];
uint64_t caller_fp = 0;
if (last_fp && !memory_->GetMemoryAtAddress(last_fp, &caller_fp)) {
BPLOG(ERROR) << "Unable to read caller_fp from last_fp: 0x"
<< std::hex << last_fp;
return NULL;
}
uint64_t caller_lr = 0;
if (last_fp && !memory_->GetMemoryAtAddress(last_fp + 8, &caller_lr)) {
BPLOG(ERROR) << "Unable to read caller_lr from last_fp + 8: 0x"
<< std::hex << (last_fp + 8);
return NULL;
}
caller_lr = PtrauthStrip(caller_lr);
uint64_t caller_sp = last_fp ? last_fp + 16 :
last_frame->context.iregs[MD_CONTEXT_ARM64_REG_SP];
// Create a new stack frame (ownership will be transferred to the caller)
// and fill it in.
StackFrameARM64* frame = new StackFrameARM64();
frame->trust = StackFrame::FRAME_TRUST_FP;
frame->context = last_frame->context;
frame->context.iregs[MD_CONTEXT_ARM64_REG_FP] = caller_fp;
frame->context.iregs[MD_CONTEXT_ARM64_REG_SP] = caller_sp;
frame->context.iregs[MD_CONTEXT_ARM64_REG_PC] =
last_frame->context.iregs[MD_CONTEXT_ARM64_REG_LR];
frame->context.iregs[MD_CONTEXT_ARM64_REG_LR] = caller_lr;
frame->context_validity = StackFrameARM64::CONTEXT_VALID_PC |
StackFrameARM64::CONTEXT_VALID_LR |
StackFrameARM64::CONTEXT_VALID_FP |
StackFrameARM64::CONTEXT_VALID_SP;
return frame;
}
void StackwalkerARM64::CorrectRegLRByFramePointer(
const vector<StackFrame*>& frames,
StackFrameARM64* last_frame) {
// Need at least two frames to correct and
// register $FP should always be greater than register $SP.
if (frames.size() < 2 || !last_frame ||
last_frame->context.iregs[MD_CONTEXT_ARM64_REG_FP] <=
last_frame->context.iregs[MD_CONTEXT_ARM64_REG_SP])
return;
// Searching for a real callee frame. Skipping inline frames since they
// don't contain context (and cannot be downcasted to StackFrameARM64).
size_t last_frame_callee_id = frames.size() - 2;
while (last_frame_callee_id >= 0 && frames[last_frame_callee_id]->trust ==
StackFrame::FRAME_TRUST_INLINE) {
last_frame_callee_id--;
}
if (last_frame_callee_id < 0) return;
StackFrameARM64* last_frame_callee =
static_cast<StackFrameARM64*>(frames[last_frame_callee_id]);
uint64_t last_frame_callee_fp =
last_frame_callee->context.iregs[MD_CONTEXT_ARM64_REG_FP];
uint64_t last_fp = 0;
if (last_frame_callee_fp &&
!memory_->GetMemoryAtAddress(last_frame_callee_fp, &last_fp)) {
BPLOG(ERROR) << "Unable to read last_fp from last_last_fp: 0x" << std::hex
<< last_frame_callee_fp;
return;
}
// Give up if STACK CFI doesn't agree with frame pointer.
if (last_frame->context.iregs[MD_CONTEXT_ARM64_REG_FP] != last_fp)
return;
uint64_t last_lr = 0;
if (last_frame_callee_fp &&
!memory_->GetMemoryAtAddress(last_frame_callee_fp + 8, &last_lr)) {
BPLOG(ERROR) << "Unable to read last_lr from (last_last_fp + 8): 0x"
<< std::hex << (last_frame_callee_fp + 8);
return;
}
last_lr = PtrauthStrip(last_lr);
last_frame->context.iregs[MD_CONTEXT_ARM64_REG_LR] = last_lr;
}
StackFrame* StackwalkerARM64::GetCallerFrame(const CallStack* stack,
bool stack_scan_allowed) {
if (!memory_ || !stack) {
BPLOG(ERROR) << "Can't get caller frame without memory or stack";
return NULL;
}
const vector<StackFrame*>& frames = *stack->frames();
StackFrameARM64* last_frame = static_cast<StackFrameARM64*>(frames.back());
scoped_ptr<StackFrameARM64> frame;
// See if there is DWARF call frame information covering this address.
scoped_ptr<CFIFrameInfo> cfi_frame_info(
frame_symbolizer_->FindCFIFrameInfo(last_frame));
if (cfi_frame_info.get())
frame.reset(GetCallerByCFIFrameInfo(frames, cfi_frame_info.get()));
// If CFI failed, or there wasn't CFI available, fall back to frame pointer.
if (!frame.get())
frame.reset(GetCallerByFramePointer(frames));
// If everything failed, fall back to stack scanning.
if (stack_scan_allowed && !frame.get())
frame.reset(GetCallerByStackScan(frames));
// If nothing worked, tell the caller.
if (!frame.get())
return NULL;
// Should we terminate the stack walk? (end-of-stack or broken invariant)
if (TerminateWalk(frame->context.iregs[MD_CONTEXT_ARM64_REG_PC],
frame->context.iregs[MD_CONTEXT_ARM64_REG_SP],
last_frame->context.iregs[MD_CONTEXT_ARM64_REG_SP],
/*first_unwind=*/last_frame->trust ==
StackFrame::FRAME_TRUST_CONTEXT)) {
return NULL;
}
// The new frame's context's PC is the return address, which is one
// instruction past the instruction that caused us to arrive at the callee.
// ARM64 instructions have a uniform 4-byte encoding, so subtracting 4 off
// the return address gets back to the beginning of the call instruction.
// Callers that require the exact return address value may access
// frame->context.iregs[MD_CONTEXT_ARM64_REG_PC].
frame->instruction = frame->context.iregs[MD_CONTEXT_ARM64_REG_PC] - 4;
return frame.release();
}
} // namespace google_breakpad

View file

@ -0,0 +1,117 @@
// -*- mode: C++ -*-
// Copyright 2013 Google LLC
//
// 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 LLC 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.
// stackwalker_arm64.h: arm64-specific stackwalker.
//
// Provides stack frames given arm64 register context and a memory region
// corresponding to an arm64 stack.
//
// Author: Mark Mentovai, Ted Mielczarek, Colin Blundell
#ifndef PROCESSOR_STACKWALKER_ARM64_H__
#define PROCESSOR_STACKWALKER_ARM64_H__
#include "google_breakpad/common/breakpad_types.h"
#include "google_breakpad/common/minidump_format.h"
#include "google_breakpad/processor/stackwalker.h"
namespace google_breakpad {
class CodeModules;
class StackwalkerARM64 : public Stackwalker {
public:
// context is an arm64 context object that gives access to arm64-specific
// register state corresponding to the innermost called frame to be
// included in the stack. The other arguments are passed directly through
// to the base Stackwalker constructor.
StackwalkerARM64(const SystemInfo* system_info,
const MDRawContextARM64* context,
MemoryRegion* memory,
const CodeModules* modules,
StackFrameSymbolizer* frame_symbolizer);
// Change the context validity mask of the frame returned by
// GetContextFrame to VALID. This is only for use by unit tests; the
// default behavior is correct for all application code.
void SetContextFrameValidity(uint64_t valid) {
context_frame_validity_ = valid;
}
private:
// Strip pointer authentication codes from an address.
uint64_t PtrauthStrip(uint64_t ptr);
// Implementation of Stackwalker, using arm64 context and stack conventions.
virtual StackFrame* GetContextFrame();
virtual StackFrame* GetCallerFrame(const CallStack* stack,
bool stack_scan_allowed);
// Use cfi_frame_info (derived from STACK CFI records) to construct
// the frame that called frames.back(). The caller takes ownership
// of the returned frame. Return NULL on failure.
StackFrameARM64* GetCallerByCFIFrameInfo(const vector<StackFrame*>& frames,
CFIFrameInfo* cfi_frame_info);
// Use the frame pointer. The caller takes ownership of the returned frame.
// Return NULL on failure.
StackFrameARM64* GetCallerByFramePointer(const vector<StackFrame*>& frames);
// Scan the stack for plausible return addresses. The caller takes ownership
// of the returned frame. Return NULL on failure.
StackFrameARM64* GetCallerByStackScan(const vector<StackFrame*>& frames);
// GetCallerByFramePointer() depends on the previous frame having recovered
// x30($LR) which may not have been done when using CFI.
// This function recovers $LR in the previous frame by using the frame-pointer
// two frames back to read it from the stack.
void CorrectRegLRByFramePointer(const vector<StackFrame*>& frames,
StackFrameARM64* last_frame);
// Stores the CPU context corresponding to the youngest stack frame, to
// be returned by GetContextFrame.
const MDRawContextARM64* context_;
// Validity mask for youngest stack frame. This is always
// CONTEXT_VALID_ALL in real use; it is only changeable for the sake of
// unit tests.
uint64_t context_frame_validity_;
// A mask of the valid address bits, determined from the address range of
// modules_.
uint64_t address_range_mask_;
};
} // namespace google_breakpad
#endif // PROCESSOR_STACKWALKER_ARM64_H__

Some files were not shown because too many files have changed in this diff Show more