blob: f6c438510ec1e7f15ecf2a97a3c2b723295129f1 [file] [log] [blame]
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Runs a particular analyzer over a minidump or set of minidumps.
#include <windows.h> // NOLINT
#include <dbghelp.h>
#include <set>
#include <unordered_map>
#include <vector>
#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "base/files/file_path.h"
#include "base/strings/string16.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "syzygy/application/application.h"
#include "syzygy/minidump/minidump.h"
#include "syzygy/refinery/analyzers/analysis_runner.h"
#include "syzygy/refinery/analyzers/analyzer_factory.h"
#include "syzygy/refinery/analyzers/analyzer_util.h"
#include "syzygy/refinery/process_state/process_state.h"
#include "syzygy/refinery/symbols/dia_symbol_provider.h"
#include "syzygy/refinery/symbols/symbol_provider.h"
namespace {
using AnalyzerName = std::string;
using AnalyzerNames = std::vector<std::string>;
using AnalyzerSet = std::set<AnalyzerName>;
using AnalyzerGraph = std::unordered_map<AnalyzerName, AnalyzerSet>;
using LayerNames = std::vector<std::string>;
class RunAnalyzerApplication : public application::AppImplBase {
public:
RunAnalyzerApplication();
bool ParseCommandLine(const base::CommandLine* command_line);
int Run();
private:
bool AddLayerPrerequisiteAnalyzers(const refinery::AnalyzerFactory& factory);
bool OrderAnalyzers(const refinery::AnalyzerFactory& factory);
template <typename LayerPtrType>
void PrintLayer(const char* layer_name,
refinery::ProcessState* process_state);
void PrintProcessState(refinery::ProcessState* process_state);
void PrintUsage(const base::FilePath& program,
const base::StringPiece& message);
bool AddAnalyzers(const refinery::AnalyzerFactory& factory,
refinery::AnalysisRunner* runner);
bool Analyze(const minidump::Minidump& minidump,
const refinery::AnalyzerFactory& factory,
const refinery::Analyzer::ProcessAnalysis& process_analysis);
std::vector<base::FilePath> mindump_paths_;
std::string analyzer_names_;
bool resolve_dependencies_;
std::string output_layers_;
DISALLOW_COPY_AND_ASSIGN(RunAnalyzerApplication);
};
// A worker class that knows how to order analyzers topologically by their
// layer dependencies.
class AnalyzerOrderer {
public:
explicit AnalyzerOrderer(const refinery::AnalyzerFactory& factory);
bool CreateGraph(const std::string& names);
std::string Order();
private:
void Visit(const AnalyzerName& name);
const refinery::AnalyzerFactory& factory_;
AnalyzerGraph graph_;
AnalyzerSet visited_;
AnalyzerSet used_;
AnalyzerNames ordering_;
};
std::vector<std::string> SplitStringList(const std::string& name_list) {
return base::SplitString(name_list, ",", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
}
std::string JoinAnalyzerSet(const AnalyzerSet& analyzer_set) {
std::string ret;
for (const auto& analyzer_name : analyzer_set) {
if (!ret.empty())
ret.append(",");
ret.append(analyzer_name);
}
return ret;
}
const char* kLayerNames[] = {
#define LAYER_NAME(layer_name) #layer_name "Layer",
PROCESS_STATE_LAYERS(LAYER_NAME)
#undef LAYER_NAME
};
bool IsValidLayerName(const std::string& candidate) {
for (const auto name : kLayerNames) {
if (candidate == name)
return true;
}
return false;
}
std::string GetValidLayerNames() {
std::string ret;
for (const auto name : kLayerNames) {
if (!ret.empty())
ret.append(", ");
ret.append(name);
}
return ret;
}
bool IsValidAnalyzerName(const std::string& candidate) {
refinery::AnalyzerFactory::AnalyzerNames names;
refinery::StaticAnalyzerFactory factory;
factory.GetAnalyzerNames(&names);
for (const auto name : names) {
if (candidate == name)
return true;
}
return false;
}
std::string GetValidAnalyzerNames() {
refinery::AnalyzerFactory::AnalyzerNames names;
refinery::StaticAnalyzerFactory factory;
factory.GetAnalyzerNames(&names);
return base::JoinString(names, ", ");
}
const char kUsageFormatStr[] =
"Usage: %ls [options] <dump files or patterns>\n"
"\n"
" --analyzers=<comma-seperated list of analyzer names>\n"
" Configures the set of analyzers to run on each of the dump\n"
" files.\n"
" Default value: %s\n"
" --output-layers=<comma-seperated list of layer names>\n"
" The list of layers to output. If no list of analyzer is provided,\n"
" this option will configure all analyzers that output the requested\n"
" layer or layers.\n"
" Default value: %s\n"
" --no-dependencies\n"
" If provided, the layer dependencies of the requested analyzers\n"
" won't be used to supplement the analyzer list.\n";
const char kDefaultAnalyzers[] = "HeapAnalyzer,StackFrameAnalyzer,TebAnalyzer";
const char kDefaultOutputLayers[] = "TypedDataLayer";
bool RunAnalyzerApplication::AddLayerPrerequisiteAnalyzers(
const refinery::AnalyzerFactory& factory) {
// Build the transitive closure of all the analyzers we need.
// The analyzers we've yet to process, initialized to the entire list.
AnalyzerNames to_process = SplitStringList(analyzer_names_);
AnalyzerSet selected_analyzers;
for (const auto& name : to_process)
selected_analyzers.insert(name);
while (!to_process.empty()) {
// Pop one analyzer name off the list to process.
std::string analyzer_name = to_process.back();
to_process.pop_back();
// Get the input layers this analyzer depends on.
refinery::AnalyzerFactory::Layers input_layers;
if (!factory.GetInputLayers(analyzer_name, &input_layers))
return false;
// Now retrieve all the analyzers that produce these layers, and see about
// adding them to the mix.
for (const auto& input_layer : input_layers) {
refinery::AnalyzerFactory::AnalyzerNames outputting_names;
factory.GetAnalyzersOutputting(input_layer, &outputting_names);
for (const auto& outputting_name : outputting_names) {
bool inserted = selected_analyzers.insert(outputting_name).second;
if (inserted) {
// This analyzer was not already in all names, add it to the queue
// of names to process.
to_process.push_back(outputting_name);
}
}
}
}
analyzer_names_ = JoinAnalyzerSet(selected_analyzers);
return true;
}
bool RunAnalyzerApplication::OrderAnalyzers(
const refinery::AnalyzerFactory& factory) {
// Topologically order the analyzers.
// Start by building the graph of analyzer dependencies.
AnalyzerOrderer orderer(factory);
if (!orderer.CreateGraph(analyzer_names_))
return false;
analyzer_names_ = orderer.Order();
return true;
}
template <typename LayerPtrType>
void RunAnalyzerApplication::PrintLayer(const char* layer_name,
refinery::ProcessState* process_state) {
DCHECK(process_state);
LayerPtrType layer;
if (!process_state->FindLayer(&layer)) {
LOG(INFO) << "No " << layer_name << " layer";
return;
}
for (const auto& record : *layer) {
std::string str = record->data().DebugString();
::fprintf(out(), "0x%08llX(0x%04X){\n%s}\n", record->range().start(),
record->range().size(), str.c_str());
}
}
void RunAnalyzerApplication::PrintProcessState(
refinery::ProcessState* process_state) {
DCHECK(process_state);
LayerNames layer_names = SplitStringList(output_layers_);
#define PRINT_LAYER(layer_name) \
if (std::find(layer_names.begin(), layer_names.end(), \
#layer_name "Layer") != layer_names.end()) \
PrintLayer<refinery::layer_name##LayerPtr>(#layer_name, process_state);
PROCESS_STATE_LAYERS(PRINT_LAYER)
#undef PRINT_LAYER
}
void RunAnalyzerApplication::PrintUsage(const base::FilePath& program,
const base::StringPiece& message) {
if (!message.empty()) {
::fwrite(message.data(), 1, message.length(), out());
::fprintf(out(), "\n\n");
}
::fprintf(out(), kUsageFormatStr, program.BaseName().value().c_str(),
kDefaultAnalyzers, kDefaultOutputLayers);
}
RunAnalyzerApplication::RunAnalyzerApplication()
: AppImplBase("RunAnalyzerApplication"), resolve_dependencies_(true) {
}
bool RunAnalyzerApplication::ParseCommandLine(
const base::CommandLine* cmd_line) {
if (cmd_line->HasSwitch("help")) {
PrintUsage(cmd_line->GetProgram(), "");
return false;
}
if (cmd_line->HasSwitch("no-dependencies"))
resolve_dependencies_ = false;
static const char kAnalyzers[] = "analyzers";
if (cmd_line->HasSwitch(kAnalyzers)) {
analyzer_names_ = cmd_line->GetSwitchValueASCII(kAnalyzers);
if (analyzer_names_.empty()) {
PrintUsage(cmd_line->GetProgram(),
"Must provide a non-empty analyzer list with this flag.");
return false;
}
for (const auto& analyzer_name : SplitStringList(analyzer_names_)) {
if (!IsValidAnalyzerName(analyzer_name)) {
PrintUsage(cmd_line->GetProgram(),
base::StringPrintf(
"Analyzer \"%s\" doesn't exist, must be one of \"%s\"",
analyzer_name.c_str(), GetValidAnalyzerNames().c_str()));
return false;
}
}
}
static const char kOutputLayers[] = "output-layers";
if (cmd_line->HasSwitch(kOutputLayers)) {
output_layers_ = cmd_line->GetSwitchValueASCII(kOutputLayers);
if (output_layers_.empty()) {
PrintUsage(cmd_line->GetProgram(),
"Must provide a non-empty output layer list with this flag.");
return false;
}
for (const auto& layer_name : SplitStringList(output_layers_)) {
if (!IsValidLayerName(layer_name)) {
PrintUsage(cmd_line->GetProgram(),
base::StringPrintf(
"Layer \"%s\" doesn't exist, must be one of \"%s\"",
layer_name.c_str(), GetValidLayerNames().c_str()));
return false;
}
}
}
for (const auto& arg : cmd_line->GetArgs()) {
if (!AppendMatchingPaths(base::FilePath(arg), &mindump_paths_)) {
PrintUsage(
cmd_line->GetProgram(),
base::StringPrintf("Can't find file or pattern \"%s\"", arg.c_str()));
return false;
}
}
if (mindump_paths_.empty()) {
PrintUsage(cmd_line->GetProgram(),
"You must provide at least one dump file.");
return false;
}
return true;
}
int RunAnalyzerApplication::Run() {
// If no analyzers are specified, but output layers are, we pick analyzers
// from the requested layers.
refinery::StaticAnalyzerFactory analyzer_factory;
if (!output_layers_.empty() && analyzer_names_.empty()) {
AnalyzerSet selected_analyzers;
for (const auto& layer_name : SplitStringList(output_layers_)) {
refinery::ProcessState::LayerEnum layer =
refinery::ProcessState::LayerFromName(layer_name);
if (layer == refinery::ProcessState::UnknownLayer) {
LOG(ERROR) << "Unknown layer: " << layer_name;
return 1;
}
AnalyzerNames analyzer_names;
analyzer_factory.GetAnalyzersOutputting(layer, &analyzer_names);
for (const auto& analyzer_name : analyzer_names)
selected_analyzers.insert(analyzer_name);
}
analyzer_names_ = JoinAnalyzerSet(selected_analyzers);
}
if (output_layers_.empty())
output_layers_ = kDefaultOutputLayers;
if (analyzer_names_.empty())
analyzer_names_ = kDefaultAnalyzers;
if (resolve_dependencies_ &&
!AddLayerPrerequisiteAnalyzers(analyzer_factory)) {
LOG(ERROR) << "Unable to add dependent analyzers.";
return 1;
}
if (!OrderAnalyzers(analyzer_factory)) {
LOG(ERROR) << "Unable to order analyzers.";
return 1;
} else {
LOG(INFO) << "Using analyzer list: " << analyzer_names_;
LOG(INFO) << "Outputting layers: " << output_layers_;
}
scoped_refptr<refinery::SymbolProvider> symbol_provider(
new refinery::SymbolProvider());
scoped_refptr<refinery::DiaSymbolProvider> dia_symbol_provider(
new refinery::DiaSymbolProvider());
for (const auto& minidump_path : mindump_paths_) {
::fprintf(out(), "Processing \"%ls\"\n", minidump_path.value().c_str());
minidump::FileMinidump minidump;
if (!minidump.Open(minidump_path)) {
LOG(ERROR) << "Unable to open dump file.";
return 1;
}
refinery::ProcessState process_state;
refinery::SimpleProcessAnalysis analysis(
&process_state, dia_symbol_provider, symbol_provider);
if (Analyze(minidump, analyzer_factory, analysis)) {
PrintProcessState(&process_state);
} else {
LOG(ERROR) << "Failure processing minidump " << minidump_path.value();
}
}
return 0;
}
bool RunAnalyzerApplication::AddAnalyzers(
const refinery::AnalyzerFactory& factory,
refinery::AnalysisRunner* runner) {
AnalyzerNames analyzers = SplitStringList(analyzer_names_);
for (const auto& analyzer_name : analyzers) {
std::unique_ptr<refinery::Analyzer> analyzer(
factory.CreateAnalyzer(analyzer_name));
if (analyzer) {
runner->AddAnalyzer(std::move(analyzer));
} else {
LOG(ERROR) << "No such analyzer " << analyzer_name;
return false;
}
}
return true;
}
bool RunAnalyzerApplication::Analyze(
const minidump::Minidump& minidump,
const refinery::AnalyzerFactory& factory,
const refinery::Analyzer::ProcessAnalysis& process_analysis) {
DCHECK(process_analysis.process_state());
minidump::Minidump::Stream sys_info_stream =
minidump.FindNextStream(nullptr, SystemInfoStream);
MINIDUMP_SYSTEM_INFO system_info = {};
if (!sys_info_stream.ReadAndAdvanceElement(&system_info)) {
LOG(ERROR) << "Unable to read system info stream.";
return false;
}
VLOG(1) << base::StringPrintf("Systeminformation");
VLOG(1) << base::StringPrintf(" ProcessorArchitecture 0x%04X",
system_info.ProcessorArchitecture);
VLOG(1) << base::StringPrintf(" ProcessorLevel 0x%04X",
system_info.ProcessorLevel);
VLOG(1) << base::StringPrintf(" ProcessorRevision 0x%04X",
system_info.ProcessorRevision);
VLOG(1) << base::StringPrintf(" NumberOfProcessors %d",
system_info.NumberOfProcessors);
VLOG(1) << base::StringPrintf(" ProductType %d", system_info.ProductType);
VLOG(1) << base::StringPrintf(" MajorVersion 0x%08X",
system_info.MajorVersion);
VLOG(1) << base::StringPrintf(" MinorVersion 0x%08X",
system_info.MinorVersion);
VLOG(1) << base::StringPrintf(" BuildNumber 0x%08X",
system_info.BuildNumber);
VLOG(1) << base::StringPrintf(" PlatformId 0x%08X", system_info.PlatformId);
VLOG(1) << base::StringPrintf(" CSDVersionRva 0x%08X",
system_info.CSDVersionRva);
VLOG(1) << base::StringPrintf(" SuiteMask 0x%04X", system_info.SuiteMask);
VLOG(1) << " CPU information:";
VLOG(1) << base::StringPrintf(" VendorId 0x%08X:0x%08X:0x%08X",
system_info.Cpu.X86CpuInfo.VendorId[0],
system_info.Cpu.X86CpuInfo.VendorId[1],
system_info.Cpu.X86CpuInfo.VendorId[1]);
VLOG(1) << base::StringPrintf(" VersionInformation 0x%08X",
system_info.Cpu.X86CpuInfo.VersionInformation);
VLOG(1) << base::StringPrintf(" FeatureInformation 0x%08X",
system_info.Cpu.X86CpuInfo.FeatureInformation);
VLOG(1) << base::StringPrintf(
" AMDExtendedCpuFeatures 0x%08X",
system_info.Cpu.X86CpuInfo.AMDExtendedCpuFeatures);
refinery::AnalysisRunner runner;
if (!AddAnalyzers(factory, &runner))
return false;
return runner.Analyze(minidump, process_analysis) ==
refinery::Analyzer::ANALYSIS_COMPLETE;
}
AnalyzerOrderer::AnalyzerOrderer(const refinery::AnalyzerFactory& factory)
: factory_(factory) {
}
bool AnalyzerOrderer::CreateGraph(const std::string& analyzer_names) {
AnalyzerNames analyzers = SplitStringList(analyzer_names);
AnalyzerSet all_analyzers;
for (const AnalyzerName& name : analyzers)
all_analyzers.insert(name);
// For each requested analyser, find the layers it inputs. From each of those
// layers, find the analyzers that output those layers - intersected with
// the analyzers we care about.
for (const AnalyzerName& analyzer_name : all_analyzers) {
refinery::AnalyzerFactory::Layers input_layers;
if (!factory_.GetInputLayers(analyzer_name, &input_layers))
return false;
AnalyzerSet& dependencies = graph_[analyzer_name];
for (auto input_layer : input_layers) {
refinery::AnalyzerFactory::AnalyzerNames outputting_names;
factory_.GetAnalyzersOutputting(input_layer, &outputting_names);
for (const AnalyzerName& outputting_name : outputting_names) {
if (all_analyzers.find(outputting_name) != all_analyzers.end()) {
// Note that the graph may be circular, and it's in particular
// acceptable for analyzers to consume and produce the same layer.
// This is the case for e.g. type propagation, which propagates
// the types of pointers.
dependencies.insert(outputting_name);
}
}
}
}
return true;
}
std::string AnalyzerOrderer::Order() {
DCHECK(visited_.empty());
DCHECK(used_.empty());
DCHECK(ordering_.empty());
for (const auto& node : graph_)
Visit(node.first);
DCHECK(visited_.empty());
DCHECK_EQ(graph_.size(), used_.size());
DCHECK_EQ(graph_.size(), ordering_.size());
return base::JoinString(ordering_, ",");
}
void AnalyzerOrderer::Visit(const AnalyzerName& name) {
DCHECK(graph_.find(name) != graph_.end());
if (visited_.find(name) != visited_.end())
return;
visited_.insert(name);
for (const AnalyzerName& dep : graph_[name])
Visit(dep);
visited_.erase(name);
if (used_.find(name) == used_.end()) {
used_.insert(name);
ordering_.push_back(name);
}
}
} // namespace
int main(int argc, const char* const* argv) {
base::AtExitManager at_exit_manager;
base::CommandLine::Init(argc, argv);
return application::Application<RunAnalyzerApplication>().Run();
}