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