1<!DOCTYPE html> 2<!-- 3Copyright (c) 2014 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/base/base64.html"> 9<link rel="import" href="/tracing/extras/importer/etw/eventtrace_parser.html"> 10<link rel="import" href="/tracing/extras/importer/etw/process_parser.html"> 11<link rel="import" href="/tracing/extras/importer/etw/thread_parser.html"> 12<link rel="import" href="/tracing/importer/importer.html"> 13<link rel="import" href="/tracing/model/model.html"> 14 15<script> 16/** 17 * @fileoverview Imports JSON file with the raw payloads from a Windows event 18 * trace into the Model. This format is outputted by Chrome running 19 * on a Windows system. 20 * 21 * This importer assumes the events arrived as a JSON file and the payloads are 22 * undecoded sequence of bytes in hex format string. The unit tests provide 23 * examples of the trace format. 24 * 25 * The format of the system trace is 26 * { 27 * name: 'ETW', 28 * content: [ <events> ] 29 * } 30 * 31 * where the <events> are dictionary values with fields. 32 * 33 * { 34 * guid: "1234-...", // The unique GUID for the event. 35 * op: 12, // The opcode of the event. 36 * ver: 1, // The encoding version of the event. 37 * cpu: 0, // The cpu id on which the event was captured. 38 * ts: 1092, // The thread id on which the event was captured. 39 * payload: "aaaa" // A base64 encoded string of the raw payload. 40 * } 41 * 42 * The payload is an undecoded version of the raw event sent by ETW. 43 * This importer uses specific parsers to decode recognized events. 44 * A parser need to register the recognized event by calling 45 * registerEventHandler(guid, opcode, handler). The parser is responsible to 46 * decode the payload and update the Model. 47 * 48 * The payload formats are described there: 49 * http://msdn.microsoft.com/en-us/library/windows/desktop/aa364085(v=vs.85).aspx 50 * 51 */ 52'use strict'; 53 54tr.exportTo('tr.e.importer.etw', function() { 55 // GUID and opcode of a Thread DCStart event, as defined at the link above. 56 var kThreadGuid = '3D6FA8D1-FE05-11D0-9DDA-00C04FD7BA7C'; 57 var kThreadDCStartOpcode = 3; 58 59 /** 60 * Represents the raw bytes payload decoder. 61 * @constructor 62 */ 63 function Decoder() { 64 this.payload_ = new DataView(new ArrayBuffer(256)); 65 }; 66 67 Decoder.prototype = { 68 __proto__: Object.prototype, 69 70 reset: function(base64_payload) { 71 var decoded_size = tr.b.Base64.getDecodedBufferLength(base64_payload); 72 if (decoded_size > this.payload_.byteLength) 73 this.payload_ = new DataView(new ArrayBuffer(decoded_size)); 74 75 tr.b.Base64.DecodeToTypedArray(base64_payload, this.payload_); 76 this.position_ = 0; 77 }, 78 79 skip: function(length) { 80 this.position_ += length; 81 }, 82 83 decodeUInt8: function() { 84 var result = this.payload_.getUint8(this.position_, true); 85 this.position_ += 1; 86 return result; 87 }, 88 89 decodeUInt16: function() { 90 var result = this.payload_.getUint16(this.position_, true); 91 this.position_ += 2; 92 return result; 93 }, 94 95 decodeUInt32: function() { 96 var result = this.payload_.getUint32(this.position_, true); 97 this.position_ += 4; 98 return result; 99 }, 100 101 decodeUInt64ToString: function() { 102 // Javascript isn't able to manage 64-bit numeric values. 103 var low = this.decodeUInt32(); 104 var high = this.decodeUInt32(); 105 var low_str = ('0000000' + low.toString(16)).substr(-8); 106 var high_str = ('0000000' + high.toString(16)).substr(-8); 107 var result = high_str + low_str; 108 return result; 109 }, 110 111 decodeInt8: function() { 112 var result = this.payload_.getInt8(this.position_, true); 113 this.position_ += 1; 114 return result; 115 }, 116 117 decodeInt16: function() { 118 var result = this.payload_.getInt16(this.position_, true); 119 this.position_ += 2; 120 return result; 121 }, 122 123 decodeInt32: function() { 124 var result = this.payload_.getInt32(this.position_, true); 125 this.position_ += 4; 126 return result; 127 }, 128 129 decodeInt64ToString: function() { 130 // Javascript isn't able to manage 64-bit numeric values. 131 // Fallback to unsigned 64-bit hexa value. 132 return this.decodeUInt64ToString(); 133 }, 134 135 decodeUInteger: function(is64) { 136 if (is64) 137 return this.decodeUInt64ToString(); 138 return this.decodeUInt32(); 139 }, 140 141 decodeString: function() { 142 var str = ''; 143 while (true) { 144 var c = this.decodeUInt8(); 145 if (!c) 146 return str; 147 str = str + String.fromCharCode(c); 148 } 149 }, 150 151 decodeW16String: function() { 152 var str = ''; 153 while (true) { 154 var c = this.decodeUInt16(); 155 if (!c) 156 return str; 157 str = str + String.fromCharCode(c); 158 } 159 }, 160 161 decodeFixedW16String: function(length) { 162 var old_position = this.position_; 163 var str = ''; 164 for (var i = 0; i < length; i++) { 165 var c = this.decodeUInt16(); 166 if (!c) 167 break; 168 str = str + String.fromCharCode(c); 169 } 170 171 // Move the position after the fixed buffer (i.e. wchar[length]). 172 this.position_ = old_position + 2 * length; 173 return str; 174 }, 175 176 decodeBytes: function(length) { 177 var bytes = []; 178 for (var i = 0; i < length; ++i) { 179 var c = this.decodeUInt8(); 180 bytes.push(c); 181 } 182 return bytes; 183 }, 184 185 decodeSID: function(is64) { 186 // Decode the TOKEN_USER structure. 187 var pSid = this.decodeUInteger(is64); 188 var attributes = this.decodeUInt32(); 189 190 // Skip padding. 191 if (is64) 192 this.decodeUInt32(); 193 194 // Decode the SID structure. 195 var revision = this.decodeUInt8(); 196 var subAuthorityCount = this.decodeUInt8(); 197 this.decodeUInt16(); 198 this.decodeUInt32(); 199 200 if (revision != 1) 201 throw 'Invalid SID revision: could not decode the SID structure.'; 202 203 var sid = this.decodeBytes(4 * subAuthorityCount); 204 205 return { 206 pSid: pSid, 207 attributes: attributes, 208 sid: sid 209 }; 210 }, 211 212 decodeSystemTime: function() { 213 // Decode the SystemTime structure. 214 var wYear = this.decodeInt16(); 215 var wMonth = this.decodeInt16(); 216 var wDayOfWeek = this.decodeInt16(); 217 var wDay = this.decodeInt16(); 218 var wHour = this.decodeInt16(); 219 var wMinute = this.decodeInt16(); 220 var wSecond = this.decodeInt16(); 221 var wMilliseconds = this.decodeInt16(); 222 return { 223 wYear: wYear, 224 wMonth: wMonth, 225 wDayOfWeek: wDayOfWeek, 226 wDay: wDay, 227 wHour: wHour, 228 wMinute: wMinute, 229 wSecond: wSecond, 230 wMilliseconds: wMilliseconds 231 }; 232 }, 233 234 decodeTimeZoneInformation: function() { 235 // Decode the TimeZoneInformation structure. 236 var bias = this.decodeUInt32(); 237 var standardName = this.decodeFixedW16String(32); 238 var standardDate = this.decodeSystemTime(); 239 var standardBias = this.decodeUInt32(); 240 var daylightName = this.decodeFixedW16String(32); 241 var daylightDate = this.decodeSystemTime(); 242 var daylightBias = this.decodeUInt32(); 243 return { 244 bias: bias, 245 standardName: standardName, 246 standardDate: standardDate, 247 standardBias: standardBias, 248 daylightName: daylightName, 249 daylightDate: daylightDate, 250 daylightBias: daylightBias 251 }; 252 } 253 254 }; 255 256 /** 257 * Imports Windows ETW kernel events into a specified model. 258 * @constructor 259 */ 260 function EtwImporter(model, events) { 261 this.importPriority = 3; 262 this.model_ = model; 263 this.events_ = events; 264 this.handlers_ = {}; 265 this.decoder_ = new Decoder(); 266 this.walltime_ = undefined; 267 this.ticks_ = undefined; 268 this.is64bit_ = undefined; 269 270 // A map of tids to their process pid. On Windows, the tid is global to 271 // the system and doesn't need to belong to a process. As many events 272 // only provide tid, this map allows to retrieve the parent process. 273 this.tidsToPid_ = {}; 274 275 // Instantiate the parsers; this will register handlers for known events. 276 var allTypeInfos = tr.e.importer.etw.Parser.getAllRegisteredTypeInfos(); 277 this.parsers_ = allTypeInfos.map( 278 function(typeInfo) { 279 return new typeInfo.constructor(this); 280 }, this); 281 } 282 283 /** 284 * Guesses whether the provided events is from a Windows ETW trace. 285 * The object must has a property named 'name' with the value 'ETW' and 286 * a property 'content' with all the undecoded events. 287 * 288 * @return {boolean} True when events is a Windows ETW array. 289 */ 290 EtwImporter.canImport = function(events) { 291 if (!events.hasOwnProperty('name') || 292 !events.hasOwnProperty('content') || 293 events.name !== 'ETW') { 294 return false; 295 } 296 297 return true; 298 }; 299 300 EtwImporter.prototype = { 301 __proto__: tr.importer.Importer.prototype, 302 303 get importerName() { 304 return 'EtwImporter'; 305 }, 306 307 get model() { 308 return this.model_; 309 }, 310 311 createThreadIfNeeded: function(pid, tid) { 312 this.tidsToPid_[tid] = pid; 313 }, 314 315 removeThreadIfPresent: function(tid) { 316 this.tidsToPid_[tid] = undefined; 317 }, 318 319 getPidFromWindowsTid: function(tid) { 320 if (tid == 0) 321 return 0; 322 var pid = this.tidsToPid_[tid]; 323 if (pid == undefined) { 324 // Kernel threads are not defined. 325 return 0; 326 } 327 return pid; 328 }, 329 330 getThreadFromWindowsTid: function(tid) { 331 var pid = this.getPidFromWindowsTid(tid); 332 var process = this.model_.getProcess(pid); 333 if (!process) 334 return undefined; 335 return process.getThread(tid); 336 }, 337 338 /* 339 * Retrieve the Cpu for a given cpuNumber. 340 * @return {Cpu} A Cpu corresponding to the given cpuNumber. 341 */ 342 getOrCreateCpu: function(cpuNumber) { 343 var cpu = this.model_.kernel.getOrCreateCpu(cpuNumber); 344 return cpu; 345 }, 346 347 /** 348 * Imports the data in this.events_ into this.model_. 349 */ 350 importEvents: function() { 351 this.events_.content.forEach(this.parseInfo.bind(this)); 352 353 if (this.walltime_ == undefined || this.ticks_ == undefined) 354 throw Error('Cannot find clock sync information in the system trace.'); 355 356 if (this.is64bit_ == undefined) 357 throw Error('Cannot determine pointer size of the system trace.'); 358 359 this.events_.content.forEach(this.parseEvent.bind(this)); 360 }, 361 362 importTimestamp: function(timestamp) { 363 var ts = parseInt(timestamp, 16); 364 return (ts - this.walltime_ + this.ticks_) / 1000.; 365 }, 366 367 parseInfo: function(event) { 368 // Retrieve clock sync information. 369 if (event.hasOwnProperty('guid') && 370 event.hasOwnProperty('walltime') && 371 event.hasOwnProperty('tick') && 372 event.guid === 'ClockSync') { 373 this.walltime_ = parseInt(event.walltime, 16); 374 this.ticks_ = parseInt(event.tick, 16); 375 } 376 377 // Retrieve pointer size information from a Thread.DCStart event. 378 if (this.is64bit_ == undefined && 379 event.hasOwnProperty('guid') && 380 event.hasOwnProperty('op') && 381 event.hasOwnProperty('ver') && 382 event.hasOwnProperty('payload') && 383 event.guid === kThreadGuid && 384 event.op == kThreadDCStartOpcode) { 385 var decoded_size = tr.b.Base64.getDecodedBufferLength(event.payload); 386 387 if (event.ver == 1) { 388 if (decoded_size >= 52) 389 this.is64bit_ = true; 390 else 391 this.is64bit_ = false; 392 } else if (event.ver == 2) { 393 if (decoded_size >= 64) 394 this.is64bit_ = true; 395 else 396 this.is64bit_ = false; 397 } else if (event.ver == 3) { 398 if (decoded_size >= 60) 399 this.is64bit_ = true; 400 else 401 this.is64bit_ = false; 402 } 403 } 404 405 return true; 406 }, 407 408 parseEvent: function(event) { 409 if (!event.hasOwnProperty('guid') || 410 !event.hasOwnProperty('op') || 411 !event.hasOwnProperty('ver') || 412 !event.hasOwnProperty('cpu') || 413 !event.hasOwnProperty('ts') || 414 !event.hasOwnProperty('payload')) { 415 return false; 416 } 417 418 var timestamp = this.importTimestamp(event.ts); 419 420 // Create the event header. 421 var header = { 422 guid: event.guid, 423 opcode: event.op, 424 version: event.ver, 425 cpu: event.cpu, 426 timestamp: timestamp, 427 is64: this.is64bit_ 428 }; 429 430 // Set the payload to decode. 431 var decoder = this.decoder_; 432 decoder.reset(event.payload); 433 434 // Retrieve the handler to decode the payload. 435 var handler = this.getEventHandler(header.guid, header.opcode); 436 if (!handler) 437 return false; 438 439 if (!handler(header, decoder)) { 440 this.model_.importWarning({ 441 type: 'parse_error', 442 message: 'Malformed ' + header.guid + ' event (' + text + ')' 443 }); 444 return false; 445 } 446 447 return true; 448 }, 449 450 /** 451 * Registers a windows ETW event handler used by parseEvent(). 452 */ 453 registerEventHandler: function(guid, opcode, handler) { 454 if (this.handlers_[guid] == undefined) 455 this.handlers_[guid] = []; 456 this.handlers_[guid][opcode] = handler; 457 }, 458 459 /** 460 * Retrieves a registered event handler. 461 */ 462 getEventHandler: function(guid, opcode) { 463 if (this.handlers_[guid] == undefined) 464 return undefined; 465 return this.handlers_[guid][opcode]; 466 } 467 468 }; 469 470 // Register the EtwImporter to the Importer. 471 tr.importer.Importer.register(EtwImporter); 472 473 return { 474 EtwImporter: EtwImporter 475 }; 476}); 477</script> 478