blob: fa796a5ace956cbcab2d83accdb28a24f0a75b25 [file] [log] [blame]
// Copyright 2012 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.
//
// Base class for common trace parsing infrastructure.
#include "syzygy/trace/parse/parse_engine.h"
#include <windows.h> // NOLINT
#include <wmistr.h> // NOLINT
#include <evntrace.h>
#include "base/logging.h"
#include "syzygy/common/buffer_parser.h"
#include "syzygy/common/com_utils.h"
#include "syzygy/trace/parse/parser.h"
namespace trace {
namespace parser {
using ::common::BinaryBufferReader;
ParseEngine::ParseEngine(const char* name, bool fail_on_module_conflict)
: event_handler_(NULL),
error_occurred_(false),
fail_on_module_conflict_(fail_on_module_conflict) {
DCHECK(name != NULL);
DCHECK(name[0] != '\0');
name_ = name;
}
ParseEngine::~ParseEngine() {
}
const char* ParseEngine::name() const {
return name_.c_str();
}
bool ParseEngine::error_occurred() const {
return error_occurred_;
}
void ParseEngine::set_error_occurred(bool value) {
error_occurred_ = value;
}
void ParseEngine::set_event_handler(ParseEventHandler* event_handler) {
DCHECK(event_handler_ == NULL);
DCHECK(event_handler != NULL);
event_handler_ = event_handler;
}
const ModuleInformation* ParseEngine::GetModuleInformation(
uint32 process_id, AbsoluteAddress64 addr) const {
ProcessMap::const_iterator processes_it = processes_.find(process_id);
if (processes_it == processes_.end())
return NULL;
const ModuleSpace& module_space = processes_it->second;
ModuleSpace::Range range(addr, 1);
ModuleSpace::RangeMapConstIter module_it =
module_space.FindFirstIntersection(range);
if (module_it == module_space.end())
return NULL;
return &module_it->second;
}
bool ParseEngine::AddModuleInformation(DWORD process_id,
const ModuleInformation& module_info) {
// Avoid doing needless work.
if (module_info.module_size == 0)
return true;
// This happens in Windows XP ETW traces for some reason. They contain
// conflicting information, so we ignore them.
if (module_info.path.empty())
return true;
ModuleSpace& module_space = processes_[process_id];
AbsoluteAddress64 addr(module_info.base_address.value());
ModuleSpace::Range range(addr, module_info.module_size);
AnnotatedModuleInformation new_module_info(module_info);
ModuleSpace::RangeMapIter iter;
if (module_space.FindOrInsert(range, new_module_info, &iter)) {
return true;
}
// Perhaps this is a case of conflicting paths for the same module. We often
// get paths reported to us in \Device\HarddiskVolumeN\... notation, and
// othertimes in C:\... notation. In this case we're happy if everything
// matches except the path. For a little bit of extra sanity checking we
// also check the basename of the paths.
if (module_info.base_address == iter->second.base_address &&
module_info.module_checksum == iter->second.module_checksum &&
module_info.module_size == iter->second.module_size &&
module_info.module_time_date_stamp ==
iter->second.module_time_date_stamp) {
base::FilePath path1(module_info.path);
base::FilePath path2(iter->second.path);
if (path1.BaseName() == path2.BaseName()) {
return true;
}
}
// Perhaps this is a case of process id reuse. In that case, we should have
// previously seen a module unload event and marked the module information
// as dirty.
while (iter->second.is_dirty) {
module_space.Remove(iter->first);
if (module_space.FindOrInsert(range, new_module_info, &iter)) {
return true;
}
}
LOG(ERROR) << "Conflicting module info for pid=" << process_id << ": "
<< module_info.path
<< " (base=0x" << module_info.base_address
<< ", size=" << module_info.module_size << ") and "
<< iter->second.path
<< " (base=0x" << iter->second.base_address
<< ", size=" << iter->second.module_size << ").";
return fail_on_module_conflict_ ? false : true;
}
bool ParseEngine::RemoveModuleInformation(
DWORD process_id, const ModuleInformation& module_info) {
// Avoid doing needless work.
if (module_info.module_size == 0)
return true;
// This happens in Windows XP traces for some reason. They contain conflicing
// information, so we ignore them.
if (module_info.path.empty())
return true;
ModuleSpace& module_space = processes_[process_id];
AbsoluteAddress64 addr(module_info.base_address.value());
ModuleSpace::Range range(addr, module_info.module_size);
ModuleSpace::RangeMapIter it = module_space.FindFirstIntersection(range);
if (it == module_space.end()) {
// We occasionally see this, as certain modules fire off multiple Unload
// events, so we don't log an error. I'm looking at you, logman.exe.
return true;
}
if (it->first != range) {
LOG(ERROR) << "Trying to remove module with mismatching range: "
<< module_info.path
<< " (base=0x" << module_info.base_address
<< ", size=" << module_info.module_size << ").";
if (fail_on_module_conflict_)
return false;
}
// We only remove modules from a given process if a conflicting module is
// loaded after the module has been marked as dirty. This is because (1) we
// don't guarantee temporal order of all events in a process, so you
// might parse a function event after seeing the module get unloaded
// if the buffers are flushed in that order; and (2) because process ids may
// be reused (but not concurrently) so we do want to drop stale module info
// when the process has been replaced.
it->second.is_dirty = true;
return true;
}
bool ParseEngine::RemoveProcessInformation(DWORD process_id) {
ProcessMap::iterator proc_iter = processes_.find(process_id);
if (proc_iter == processes_.end()) {
LOG(ERROR) << "Unknown process id: " << process_id << ".";
return false;
}
ModuleSpace& process_info = proc_iter->second;
ModuleSpace::iterator module_iter = process_info.begin();
for (; module_iter != process_info.end(); ++module_iter) {
module_iter->second.is_dirty = true;
}
return true;
}
bool ParseEngine::DispatchEvent(EVENT_TRACE* event) {
DCHECK(event != NULL);
DCHECK(event_handler_ != NULL);
DCHECK(error_occurred_ == false);
if (kCallTraceEventClass != event->Header.Guid)
return false;
bool success = false;
TraceEventType type = static_cast<TraceEventType>(event->Header.Class.Type);
switch (type) {
case TRACE_ENTER_EVENT:
case TRACE_EXIT_EVENT:
success = DispatchEntryExitEvent(event, type);
break;
case TRACE_BATCH_ENTER:
success = DispatchBatchEnterEvent(event);
break;
case TRACE_PROCESS_ATTACH_EVENT:
case TRACE_PROCESS_DETACH_EVENT:
case TRACE_THREAD_ATTACH_EVENT:
case TRACE_THREAD_DETACH_EVENT:
success = DispatchModuleEvent(event, type);
break;
case TRACE_PROCESS_ENDED:
success = DispatchProcessEndedEvent(event);
break;
case TRACE_MODULE_EVENT:
LOG(ERROR) << "Parsing for TRACE_MODULE_EVENT not yet implemented.";
break;
case TRACE_BATCH_INVOCATION:
success = DispatchBatchInvocationEvent(event);
break;
case TRACE_THREAD_NAME:
success = DispatchThreadNameEvent(event);
break;
case TRACE_INDEXED_FREQUENCY:
success = DispatchIndexedFrequencyEvent(event);
break;
case TRACE_DYNAMIC_SYMBOL:
success = DispatchDynamicSymbolEvent(event);
break;
case TRACE_SAMPLE_DATA:
success = DispatchSampleDataEvent(event);
break;
default:
LOG(ERROR) << "Unknown event type encountered.";
break;
}
if (!success) {
error_occurred_ = true;
}
return true;
}
bool ParseEngine::DispatchEntryExitEvent(EVENT_TRACE* event,
TraceEventType type) {
DCHECK(event != NULL);
DCHECK(type == TRACE_ENTER_EVENT || type == TRACE_EXIT_EVENT);
DCHECK(event_handler_ != NULL);
DCHECK(error_occurred_ == false);
BinaryBufferReader reader(event->MofData, event->MofLength);
const TraceEnterExitEventData* data = NULL;
if (!reader.Read(sizeof(TraceEnterExitEventData), &data)) {
LOG(ERROR) << "Short entry exit event.";
return false;
}
base::Time time(base::Time::FromFileTime(
reinterpret_cast<FILETIME&>(event->Header.TimeStamp)));
DWORD process_id = event->Header.ProcessId;
DWORD thread_id = event->Header.ThreadId;
switch (type) {
case TRACE_ENTER_EVENT:
event_handler_->OnFunctionEntry(time, process_id, thread_id, data);
break;
case TRACE_EXIT_EVENT:
event_handler_->OnFunctionExit(time, process_id, thread_id, data);
break;
default:
NOTREACHED() << "Impossible event type.";
return false;
}
return true;
}
bool ParseEngine::DispatchBatchEnterEvent(EVENT_TRACE* event) {
DCHECK(event != NULL);
DCHECK(event_handler_ != NULL);
DCHECK(error_occurred_ == false);
BinaryBufferReader reader(event->MofData, event->MofLength);
const TraceBatchEnterData* data = NULL;
size_t offset_to_calls = FIELD_OFFSET(TraceBatchEnterData, calls);
if (!reader.Read(offset_to_calls, &data)) {
LOG(ERROR) << "Short or empty batch event.";
return false;
}
size_t bytes_needed = data->num_calls * sizeof(data->calls[0]);
if (!reader.Consume(bytes_needed)) {
LOG(ERROR) << "Short batch event data. Expected " << data->num_calls
<< " entries (" << (offset_to_calls + bytes_needed)
<< " bytes) but batch record was only " << event->MofLength
<< " bytes.";
return false;
}
// Trim the batch entries if the last one is NULL, indicating that the
// reporting thread was interrupted mid-write.
if (data->num_calls != 0 &&
data->calls[data->num_calls - 1].function == NULL) {
// Yuck! Cast away constness because the BinaryBufferReader only likes
// to deal with const output pointers.
const_cast<TraceBatchEnterData*>(data)->num_calls -= 1;
}
DCHECK(data->num_calls == 0 ||
data->calls[data->num_calls - 1].function != NULL);
base::Time time(base::Time::FromFileTime(
reinterpret_cast<FILETIME&>(event->Header.TimeStamp)));
DWORD process_id = event->Header.ProcessId;
DWORD thread_id = data->thread_id;
event_handler_->OnBatchFunctionEntry(time, process_id, thread_id, data);
return true;
}
bool ParseEngine::DispatchProcessEndedEvent(EVENT_TRACE* event) {
DCHECK(event != NULL);
DCHECK(event_handler_ != NULL);
DCHECK(error_occurred_ == false);
base::Time time(base::Time::FromFileTime(
reinterpret_cast<FILETIME&>(event->Header.TimeStamp)));
event_handler_->OnProcessEnded(time, event->Header.ProcessId);
if (!RemoveProcessInformation(event->Header.ProcessId))
return false;
return true;
}
bool ParseEngine::DispatchBatchInvocationEvent(EVENT_TRACE* event) {
DCHECK(event != NULL);
DCHECK(event_handler_ != NULL);
DCHECK(error_occurred_ == false);
BinaryBufferReader reader(event->MofData, event->MofLength);
if (event->MofLength % sizeof(InvocationInfo) != 0) {
LOG(ERROR) << "Invocation batch length off.";
return false;
}
const TraceBatchInvocationInfo* data = NULL;
if (!reader.Read(event->MofLength, &data)) {
LOG(ERROR) << "Short or empty batch event.";
return false;
}
// TODO(rogerm): Ensure this is robust in the presence of incomplete write.
size_t num_invocations = event->MofLength / sizeof(InvocationInfo);
base::Time time(base::Time::FromFileTime(
reinterpret_cast<FILETIME&>(event->Header.TimeStamp)));
DWORD process_id = event->Header.ProcessId;
DWORD thread_id = event->Header.ThreadId;
event_handler_->OnInvocationBatch(time,
process_id,
thread_id,
num_invocations,
data);
return true;
}
bool ParseEngine::DispatchThreadNameEvent(EVENT_TRACE* event) {
DCHECK(event != NULL);
DCHECK(event_handler_ != NULL);
DCHECK(error_occurred_ == false);
BinaryBufferReader reader(event->MofData, event->MofLength);
const char* thread_name = NULL;
size_t thread_name_len = 0;
if (!reader.ReadString(&thread_name, &thread_name_len)) {
LOG(ERROR) << "Unable to read string.";
return false;
}
base::Time time(base::Time::FromFileTime(
reinterpret_cast<FILETIME&>(event->Header.TimeStamp)));
DWORD process_id = event->Header.ProcessId;
DWORD thread_id = event->Header.ThreadId;
event_handler_->OnThreadName(time,
process_id,
thread_id,
base::StringPiece(thread_name, thread_name_len));
return true;
}
bool ParseEngine::DispatchIndexedFrequencyEvent(EVENT_TRACE* event) {
DCHECK(event != NULL);
DCHECK(event_handler_ != NULL);
DCHECK(error_occurred_ == false);
if (event->MofLength < sizeof(TraceIndexedFrequencyData)) {
LOG(ERROR) << "Data too small for TraceIndexedFrequency struct.";
return false;
}
BinaryBufferReader reader(event->MofData, event->MofLength);
const TraceIndexedFrequencyData* data = NULL;
if (!reader.Read(&data)) {
LOG(ERROR) << "Short or empty coverage data event.";
return false;
}
DCHECK(data != NULL);
// Calculate the expected size of the entire payload, headers included.
size_t expected_length = data->frequency_size * data->num_entries +
sizeof(TraceIndexedFrequencyData) - 1;
if (event->MofLength < expected_length) {
LOG(ERROR) << "Payload smaller than size implied by "
<< "TraceIndexedFrequencyData header.";
return false;
}
base::Time time(base::Time::FromFileTime(
reinterpret_cast<FILETIME&>(event->Header.TimeStamp)));
DWORD process_id = event->Header.ProcessId;
DWORD thread_id = event->Header.ThreadId;
event_handler_->OnIndexedFrequency(time, process_id, thread_id, data);
return true;
}
bool ParseEngine::DispatchDynamicSymbolEvent(EVENT_TRACE* event) {
DCHECK(event != NULL);
DCHECK(event_handler_ != NULL);
DCHECK(error_occurred_ == false);
BinaryBufferReader reader(event->MofData, event->MofLength);
const TraceDynamicSymbol* symbol = NULL;
const char* symbol_name = NULL;
size_t symbol_name_len = 0;
if (!reader.Read(FIELD_OFFSET(TraceDynamicSymbol, symbol_name), &symbol) ||
!reader.ReadString(&symbol_name, &symbol_name_len)) {
LOG(ERROR) << "Short or empty coverage data event.";
return false;
}
DWORD process_id = event->Header.ProcessId;
event_handler_->OnDynamicSymbol(
process_id, symbol->symbol_id,
base::StringPiece(symbol_name, symbol_name_len));
return true;
}
bool ParseEngine::DispatchSampleDataEvent(EVENT_TRACE* event) {
DCHECK(event != NULL);
DCHECK(event_handler_ != NULL);
DCHECK(error_occurred_ == false);
BinaryBufferReader reader(event->MofData, event->MofLength);
const TraceSampleData* data = NULL;
if (!reader.Read(&data)) {
LOG(ERROR) << "Short or empty TraceSampleData event.";
return false;
}
DCHECK(data != NULL);
// Calculate the expected size of the entire payload, headers included.
size_t expected_length = FIELD_OFFSET(TraceSampleData, buckets) +
sizeof(data->buckets[0]) * data->bucket_count;
if (event->MofLength < expected_length) {
LOG(ERROR) << "Payload smaller than size implied by TraceSampleData "
<< "header.";
return false;
}
base::Time time(base::Time::FromFileTime(
reinterpret_cast<FILETIME&>(event->Header.TimeStamp)));
DWORD process_id = event->Header.ProcessId;
event_handler_->OnSampleData(time, process_id, data);
return true;
}
namespace {
void ModuleTraceDataToModuleInformation(
const TraceModuleData& module_data,
ModuleInformation* module_info) {
DCHECK_NE(reinterpret_cast<ModuleInformation*>(NULL), module_info);
module_info->base_address.set_value(
reinterpret_cast<uint32>(module_data.module_base_addr));
module_info->module_size = module_data.module_base_size;
module_info->path = module_data.module_name;
module_info->module_checksum = module_data.module_checksum;
module_info->module_time_date_stamp = module_data.module_time_date_stamp;
}
} // namespace
bool ParseEngine::DispatchModuleEvent(EVENT_TRACE* event,
TraceEventType type) {
DCHECK(event != NULL);
DCHECK(type == TRACE_PROCESS_ATTACH_EVENT ||
type == TRACE_PROCESS_DETACH_EVENT ||
type == TRACE_THREAD_ATTACH_EVENT ||
type == TRACE_THREAD_DETACH_EVENT);
DCHECK(event_handler_ != NULL);
DCHECK(error_occurred_ == false);
BinaryBufferReader reader(event->MofData, event->MofLength);
const TraceModuleData* data = NULL;
if (!reader.Read(&data)) {
LOG(ERROR) << "Short or empty module event.";
return false;
}
if (data->module_base_addr == NULL) {
LOG(INFO) << "Encountered incompletely written module event record.";
return true;
}
base::Time time(base::Time::FromFileTime(
reinterpret_cast<FILETIME&>(event->Header.TimeStamp)));
DWORD process_id = event->Header.ProcessId;
DWORD thread_id = event->Header.ThreadId;
switch (type) {
case TRACE_PROCESS_ATTACH_EVENT: {
ModuleInformation module_info;
ModuleTraceDataToModuleInformation(*data, &module_info);
AddModuleInformation(process_id, module_info);
event_handler_->OnProcessAttach(time, process_id, thread_id, data);
break;
}
case TRACE_PROCESS_DETACH_EVENT: {
event_handler_->OnProcessDetach(time, process_id, thread_id, data);
ModuleInformation module_info;
ModuleTraceDataToModuleInformation(*data, &module_info);
RemoveModuleInformation(process_id, module_info);
break;
}
case TRACE_THREAD_ATTACH_EVENT: {
event_handler_->OnThreadAttach(time, process_id, thread_id, data);
break;
}
case TRACE_THREAD_DETACH_EVENT: {
event_handler_->OnThreadDetach(time, process_id, thread_id, data);
break;
}
default: {
LOG(ERROR) << "Unexpected module event type " << type << ".";
return false;
}
}
return true;
}
} // namespace parser
} // namespace trace