| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/extras/importer/etw/eventtrace_parser.html"> |
| <link rel="import" href="/extras/importer/etw/process_parser.html"> |
| <link rel="import" href="/extras/importer/etw/thread_parser.html"> |
| <link rel="import" href="/importer/importer.html"> |
| <link rel="import" href="/base/base64.html"> |
| <link rel="import" href="/model/model.html"> |
| |
| <script> |
| /** |
| * @fileoverview Imports JSON file with the raw payloads from a Windows event |
| * trace into the Model. This format is outputted by Chrome running |
| * on a Windows system. |
| * |
| * This importer assumes the events arrived as a JSON file and the payloads are |
| * undecoded sequence of bytes in hex format string. The unit tests provide |
| * examples of the trace format. |
| * |
| * The format of the system trace is |
| * { |
| * name: 'ETW', |
| * content: [ <events> ] |
| * } |
| * |
| * where the <events> are dictionary values with fields. |
| * |
| * { |
| * guid: "1234-...", // The unique GUID for the event. |
| * op: 12, // The opcode of the event. |
| * ver: 1, // The encoding version of the event. |
| * cpu: 0, // The cpu id on which the event was captured. |
| * ts: 1092, // The thread id on which the event was captured. |
| * payload: "aaaa" // A base64 encoded string of the raw payload. |
| * } |
| * |
| * The payload is an undecoded version of the raw event sent by ETW. |
| * This importer uses specific parsers to decode recognized events. |
| * A parser need to register the recognized event by calling |
| * registerEventHandler(guid, opcode, handler). The parser is responsible to |
| * decode the payload and update the Model. |
| * |
| * The payload formats are described there: |
| * http://msdn.microsoft.com/en-us/library/windows/desktop/aa364085(v=vs.85).aspx |
| * |
| */ |
| 'use strict'; |
| |
| tr.exportTo('tr.e.importer.etw', function() { |
| var Importer = tr.importer.Importer; |
| |
| // GUID and opcode of a Thread DCStart event, as defined at the link above. |
| var kThreadGuid = '3D6FA8D1-FE05-11D0-9DDA-00C04FD7BA7C'; |
| var kThreadDCStartOpcode = 3; |
| |
| /** |
| * Represents the raw bytes payload decoder. |
| * @constructor |
| */ |
| function Decoder() { |
| this.payload_ = new DataView(new ArrayBuffer(256)); |
| }; |
| |
| Decoder.prototype = { |
| __proto__: Object.prototype, |
| |
| reset: function(base64_payload) { |
| var decoded_size = tr.b.Base64.getDecodedBufferLength(base64_payload); |
| if (decoded_size > this.payload_.byteLength) |
| this.payload_ = new DataView(new ArrayBuffer(decoded_size)); |
| |
| tr.b.Base64.DecodeToTypedArray(base64_payload, this.payload_); |
| this.position_ = 0; |
| }, |
| |
| skip: function(length) { |
| this.position_ += length; |
| }, |
| |
| decodeUInt8: function() { |
| var result = this.payload_.getUint8(this.position_, true); |
| this.position_ += 1; |
| return result; |
| }, |
| |
| decodeUInt16: function() { |
| var result = this.payload_.getUint16(this.position_, true); |
| this.position_ += 2; |
| return result; |
| }, |
| |
| decodeUInt32: function() { |
| var result = this.payload_.getUint32(this.position_, true); |
| this.position_ += 4; |
| return result; |
| }, |
| |
| decodeUInt64ToString: function() { |
| // Javascript isn't able to manage 64-bit numeric values. |
| var low = this.decodeUInt32(); |
| var high = this.decodeUInt32(); |
| var low_str = ('0000000' + low.toString(16)).substr(-8); |
| var high_str = ('0000000' + high.toString(16)).substr(-8); |
| var result = high_str + low_str; |
| return result; |
| }, |
| |
| decodeInt8: function() { |
| var result = this.payload_.getInt8(this.position_, true); |
| this.position_ += 1; |
| return result; |
| }, |
| |
| decodeInt16: function() { |
| var result = this.payload_.getInt16(this.position_, true); |
| this.position_ += 2; |
| return result; |
| }, |
| |
| decodeInt32: function() { |
| var result = this.payload_.getInt32(this.position_, true); |
| this.position_ += 4; |
| return result; |
| }, |
| |
| decodeInt64ToString: function() { |
| // Javascript isn't able to manage 64-bit numeric values. |
| // Fallback to unsigned 64-bit hexa value. |
| return this.decodeUInt64ToString(); |
| }, |
| |
| decodeUInteger: function(is64) { |
| if (is64) |
| return this.decodeUInt64ToString(); |
| return this.decodeUInt32(); |
| }, |
| |
| decodeString: function() { |
| var str = ''; |
| while (true) { |
| var c = this.decodeUInt8(); |
| if (!c) |
| return str; |
| str = str + String.fromCharCode(c); |
| } |
| }, |
| |
| decodeW16String: function() { |
| var str = ''; |
| while (true) { |
| var c = this.decodeUInt16(); |
| if (!c) |
| return str; |
| str = str + String.fromCharCode(c); |
| } |
| }, |
| |
| decodeFixedW16String: function(length) { |
| var old_position = this.position_; |
| var str = ''; |
| for (var i = 0; i < length; i++) { |
| var c = this.decodeUInt16(); |
| if (!c) |
| break; |
| str = str + String.fromCharCode(c); |
| } |
| |
| // Move the position after the fixed buffer (i.e. wchar[length]). |
| this.position_ = old_position + 2 * length; |
| return str; |
| }, |
| |
| decodeBytes: function(length) { |
| var bytes = []; |
| for (var i = 0; i < length; ++i) { |
| var c = this.decodeUInt8(); |
| bytes.push(c); |
| } |
| return bytes; |
| }, |
| |
| decodeSID: function(is64) { |
| // Decode the TOKEN_USER structure. |
| var pSid = this.decodeUInteger(is64); |
| var attributes = this.decodeUInt32(); |
| |
| // Skip padding. |
| if (is64) |
| this.decodeUInt32(); |
| |
| // Decode the SID structure. |
| var revision = this.decodeUInt8(); |
| var subAuthorityCount = this.decodeUInt8(); |
| this.decodeUInt16(); |
| this.decodeUInt32(); |
| |
| if (revision != 1) |
| throw 'Invalid SID revision: could not decode the SID structure.'; |
| |
| var sid = this.decodeBytes(4 * subAuthorityCount); |
| |
| return { |
| pSid: pSid, |
| attributes: attributes, |
| sid: sid |
| }; |
| }, |
| |
| decodeSystemTime: function() { |
| // Decode the SystemTime structure. |
| var wYear = this.decodeInt16(); |
| var wMonth = this.decodeInt16(); |
| var wDayOfWeek = this.decodeInt16(); |
| var wDay = this.decodeInt16(); |
| var wHour = this.decodeInt16(); |
| var wMinute = this.decodeInt16(); |
| var wSecond = this.decodeInt16(); |
| var wMilliseconds = this.decodeInt16(); |
| return { |
| wYear: wYear, |
| wMonth: wMonth, |
| wDayOfWeek: wDayOfWeek, |
| wDay: wDay, |
| wHour: wHour, |
| wMinute: wMinute, |
| wSecond: wSecond, |
| wMilliseconds: wMilliseconds |
| }; |
| }, |
| |
| decodeTimeZoneInformation: function() { |
| // Decode the TimeZoneInformation structure. |
| var bias = this.decodeUInt32(); |
| var standardName = this.decodeFixedW16String(32); |
| var standardDate = this.decodeSystemTime(); |
| var standardBias = this.decodeUInt32(); |
| var daylightName = this.decodeFixedW16String(32); |
| var daylightDate = this.decodeSystemTime(); |
| var daylightBias = this.decodeUInt32(); |
| return { |
| bias: bias, |
| standardName: standardName, |
| standardDate: standardDate, |
| standardBias: standardBias, |
| daylightName: daylightName, |
| daylightDate: daylightDate, |
| daylightBias: daylightBias |
| }; |
| } |
| |
| }; |
| |
| /** |
| * Imports Windows ETW kernel events into a specified model. |
| * @constructor |
| */ |
| function EtwImporter(model, events) { |
| this.importPriority = 3; |
| this.model_ = model; |
| this.events_ = events; |
| this.handlers_ = {}; |
| this.decoder_ = new Decoder(); |
| this.walltime_ = undefined; |
| this.ticks_ = undefined; |
| this.is64bit_ = undefined; |
| |
| // A map of tids to their process pid. On Windows, the tid is global to |
| // the system and doesn't need to belong to a process. As many events |
| // only provide tid, this map allows to retrieve the parent process. |
| this.tidsToPid_ = {}; |
| |
| // Instantiate the parsers; this will register handlers for known events. |
| var allTypeInfos = tr.e.importer.etw.Parser.getAllRegisteredTypeInfos(); |
| this.parsers_ = allTypeInfos.map( |
| function(typeInfo) { |
| return new typeInfo.constructor(this); |
| }, this); |
| } |
| |
| /** |
| * Guesses whether the provided events is from a Windows ETW trace. |
| * The object must has a property named 'name' with the value 'ETW' and |
| * a property 'content' with all the undecoded events. |
| * |
| * @return {boolean} True when events is a Windows ETW array. |
| */ |
| EtwImporter.canImport = function(events) { |
| if (!events.hasOwnProperty('name') || |
| !events.hasOwnProperty('content') || |
| events.name !== 'ETW') { |
| return false; |
| } |
| |
| return true; |
| }; |
| |
| EtwImporter.prototype = { |
| __proto__: Importer.prototype, |
| |
| get model() { |
| return this.model_; |
| }, |
| |
| createThreadIfNeeded: function(pid, tid) { |
| this.tidsToPid_[tid] = pid; |
| }, |
| |
| removeThreadIfPresent: function(tid) { |
| this.tidsToPid_[tid] = undefined; |
| }, |
| |
| getPidFromWindowsTid: function(tid) { |
| if (tid == 0) |
| return 0; |
| var pid = this.tidsToPid_[tid]; |
| if (pid == undefined) { |
| // Kernel threads are not defined. |
| return 0; |
| } |
| return pid; |
| }, |
| |
| getThreadFromWindowsTid: function(tid) { |
| var pid = this.getPidFromWindowsTid(tid); |
| var process = this.model_.getProcess(pid); |
| if (!process) |
| return undefined; |
| return process.getThread(tid); |
| }, |
| |
| /* |
| * Retrieve the Cpu for a given cpuNumber. |
| * @return {Cpu} A Cpu corresponding to the given cpuNumber. |
| */ |
| getOrCreateCpu: function(cpuNumber) { |
| var cpu = this.model_.kernel.getOrCreateCpu(cpuNumber); |
| return cpu; |
| }, |
| |
| /** |
| * Imports the data in this.events_ into this.model_. |
| */ |
| importEvents: function(isSecondaryImport) { |
| this.events_.content.forEach(this.parseInfo.bind(this)); |
| |
| if (this.walltime_ == undefined || this.ticks_ == undefined) |
| throw Error('Cannot find clock sync information in the system trace.'); |
| |
| if (this.is64bit_ == undefined) |
| throw Error('Cannot determine pointer size of the system trace.'); |
| |
| this.events_.content.forEach(this.parseEvent.bind(this)); |
| }, |
| |
| importTimestamp: function(timestamp) { |
| var ts = parseInt(timestamp, 16); |
| return (ts - this.walltime_ + this.ticks_) / 1000.; |
| }, |
| |
| parseInfo: function(event) { |
| // Retrieve clock sync information. |
| if (event.hasOwnProperty('guid') && |
| event.hasOwnProperty('walltime') && |
| event.hasOwnProperty('tick') && |
| event.guid === 'ClockSync') { |
| this.walltime_ = parseInt(event.walltime, 16); |
| this.ticks_ = parseInt(event.tick, 16); |
| } |
| |
| // Retrieve pointer size information from a Thread.DCStart event. |
| if (this.is64bit_ == undefined && |
| event.hasOwnProperty('guid') && |
| event.hasOwnProperty('op') && |
| event.hasOwnProperty('ver') && |
| event.hasOwnProperty('payload') && |
| event.guid === kThreadGuid && |
| event.op == kThreadDCStartOpcode) { |
| var decoded_size = tr.b.Base64.getDecodedBufferLength(event.payload); |
| |
| if (event.ver == 1) { |
| if (decoded_size >= 52) |
| this.is64bit_ = true; |
| else |
| this.is64bit_ = false; |
| } else if (event.ver == 2) { |
| if (decoded_size >= 64) |
| this.is64bit_ = true; |
| else |
| this.is64bit_ = false; |
| } else if (event.ver == 3) { |
| if (decoded_size >= 60) |
| this.is64bit_ = true; |
| else |
| this.is64bit_ = false; |
| } |
| } |
| |
| return true; |
| }, |
| |
| parseEvent: function(event) { |
| if (!event.hasOwnProperty('guid') || |
| !event.hasOwnProperty('op') || |
| !event.hasOwnProperty('ver') || |
| !event.hasOwnProperty('cpu') || |
| !event.hasOwnProperty('ts') || |
| !event.hasOwnProperty('payload')) { |
| return false; |
| } |
| |
| var timestamp = this.importTimestamp(event.ts); |
| |
| // Create the event header. |
| var header = { |
| guid: event.guid, |
| opcode: event.op, |
| version: event.ver, |
| cpu: event.cpu, |
| timestamp: timestamp, |
| is64: this.is64bit_ |
| }; |
| |
| // Set the payload to decode. |
| var decoder = this.decoder_; |
| decoder.reset(event.payload); |
| |
| // Retrieve the handler to decode the payload. |
| var handler = this.getEventHandler(header.guid, header.opcode); |
| if (!handler) |
| return false; |
| |
| if (!handler(header, decoder)) { |
| this.model_.importWarning({ |
| type: 'parse_error', |
| message: 'Malformed ' + header.guid + ' event (' + text + ')' |
| }); |
| return false; |
| } |
| |
| return true; |
| }, |
| |
| /** |
| * Registers a windows ETW event handler used by parseEvent(). |
| */ |
| registerEventHandler: function(guid, opcode, handler) { |
| if (this.handlers_[guid] == undefined) |
| this.handlers_[guid] = []; |
| this.handlers_[guid][opcode] = handler; |
| }, |
| |
| /** |
| * Retrieves a registered event handler. |
| */ |
| getEventHandler: function(guid, opcode) { |
| if (this.handlers_[guid] == undefined) |
| return undefined; |
| return this.handlers_[guid][opcode]; |
| } |
| |
| }; |
| |
| // Register the EtwImporter to the Importer. |
| tr.importer.Importer.register(EtwImporter); |
| |
| return { |
| EtwImporter: EtwImporter |
| }; |
| }); |
| </script> |