aboutsummaryrefslogtreecommitdiff
path: root/deps/v8/tools/windbg.js
blob: 3df14f4a2e812f252bb710b05b68323dcc4fc73b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/*=============================================================================
  This is a convenience script for debugging with WinDbg (akin to gdbinit)
  It can be loaded into WinDbg with: .scriptload full_path\windbg.js

  To printout the help message below into the debugger's command window:
  !help
=============================================================================*/

function help() {
  print("--------------------------------------------------------------------");
  print("  LIVE debugging only");
  print("--------------------------------------------------------------------");
  print("  !jlh(\"local_handle_var_name\")");
  print("      prints object held by the handle");
  print("      e.g. !jlh(\"key\") or !jlh(\"this->receiver_\")");
  print("  !job(address_or_taggedint)");
  print("      prints object at the address, e.g. !job(0x235cb869f9)");
  print("  !jobs(start_address, count)");
  print("      prints 'count' objects from a continuous range of Object");
  print("      pointers, e.g. !jobs(0x5f7270, 42)");
  print("  !jst() or !jst");
  print("      prints javascript stack (output goes into the console)");
  print("  !jsbp() or !jsbp");
  print("      sets bp in v8::internal::Execution::Call");
  print("");
  print("--------------------------------------------------------------------");
  print("  Managed heap");
  print("--------------------------------------------------------------------");
  print("  !set_iso(isolate_address)");
  print("      call this function before using !mem or other heap routines");
  print("  !mem or !mem(\"space1[ space2 ...]\")");
  print("      prints memory chunks from the 'space' owned by the heap in the");
  print("      isolate set by !set_iso; valid values for 'space' are:");
  print("      new, old, map, code, lo [large], nlo [newlarge], ro [readonly]");
  print("      if no 'space' specified prints memory chunks for all spaces,");
  print("      e.g. !mem(\"code\"), !mem(\"ro new old\")");
  print("  !where(address)");
  print("      prints name of the space and address of the MemoryChunk the");
  print("      'address' is from, e.g. !where(0x235cb869f9)");
  print("");
  print("--------------------------------------------------------------------");
  print("  To run any function from this script (live or postmortem):");
  print("");
  print("  dx @$scriptContents.function_name(args)");
  print("      e.g. dx @$scriptContents.pointer_size()");
  print("      e.g. dx @$scriptContents.module_name(\"v8_for_test\")");
  print("--------------------------------------------------------------------");
}

/*=============================================================================
  Output
=============================================================================*/
function print(s) {
  host.diagnostics.debugLog(s + "\n");
}

function print_filtered(obj, filter) {
  for (let line of obj) {
    if (!filter || line.indexOf(filter) != -1) {
      print(line);
    }
  }
}

function inspect(s) {
  for (var k of Reflect.ownKeys(s)) {
    print(k + " => " + Reflect.get(s, k));
  }
}


/*=============================================================================
  Utils (postmortem and live)
=============================================================================*/
function cast(address, type_name) {
  return host.createTypedObject(address, module_name(), type_name);
}

// Failed to figure out how to get pointer size from the debugger's data model,
// so we parse it out from sizeof(void*) output.
function pointer_size() {
  let ctl = host.namespace.Debugger.Utility.Control;
  let sizeof = ctl.ExecuteCommand("?? sizeof(void*)");
  let output = "";
  for (output of sizeof) {} // unsigned int64 8
  return parseInt(output.trim().split(" ").pop());
}

function poi(address) {
  try {
    // readMemoryValues throws if cannot read from 'address'.
    return host.memory.readMemoryValues(address, 1, pointer_size())[0];
  }
  catch (e){}
}

function get_register(name) {
  return host.namespace.Debugger.State.DebuggerVariables.curthread
         .Registers.User[name];
}

// In debug builds v8 code is compiled into v8.dll, and in release builds
// the code is compiled directly into the executable. If you are debugging some
// other embedder, invoke module_name explicitly from the debugger and provide
// the module name to use.
const known_exes = ["d8", "unittests", "mksnapshot", "chrome", "chromium"];
let module_name_cache;
function module_name(use_this_module) {
  if (use_this_module) {
    module_name_cache = use_this_module;
  }

  if (!module_name_cache) {
    let v8 = host.namespace.Debugger.State.DebuggerVariables.curprocess
             .Modules.Where(
                function(m) {
                 return m.Name.indexOf("\\v8.dll") !== -1;
                });

    if (v8)  {
      module_name_cache = "v8";
    }
    else {
      for (let exe_name in known_exes) {
        let exe = host.namespace.Debugger.State.DebuggerVariables.curprocess
                  .Modules.Where(
                    function(m) {
                      return m.Name.indexOf(`\\${exe_name}.exe`) !== -1;
                    });
        if (exe) {
            module_name_cache = exe_name;
            break;
        }
      }
    }
  }
  return module_name_cache;
};

function make_call(fn) {
  // .call resets current frame to the top one, so have to manually remember
  // and restore it after making the call.
  let curframe = host.namespace.Debugger.State.DebuggerVariables.curframe;
  let ctl = host.namespace.Debugger.Utility.Control;
  let output = ctl.ExecuteCommand(`.call ${fn};g`);
  curframe.SwitchTo();
  return output;
}

// Skips the meta output about the .call invocation.
function make_call_and_print_return(fn) {
  let output = make_call(fn);
  let print_line = false;
  for (let line of output) {
    if (print_line) {
      print(line);
      break;
    }
    if (line.includes(".call returns")) {
      print_line = true;
    }
  }
}


/*=============================================================================
  Wrappers around V8's printing functions and other utils for live-debugging
=============================================================================*/

/*-----------------------------------------------------------------------------
  'address' should be an int (so in hex must include '0x' prefix).
-----------------------------------------------------------------------------*/
function print_object(address) {
  let output = make_call(`_v8_internal_Print_Object(${address})`);

  // skip the first few lines with meta info of .call command
  let skip_line = true;
  for (let line of output) {
    if (!skip_line) {
      print(line);
      continue;
    }
    if (line.includes("deadlocks and corruption of the debuggee")) {
      skip_line = false;
    }
  }
}

/*-----------------------------------------------------------------------------
  'handle_to_object' should be a name of a Handle which can be a local
  variable or it can be a member variable like "this->receiver_".
-----------------------------------------------------------------------------*/
function print_object_from_handle(handle_to_object) {
  let handle = host.evaluateExpression(handle_to_object);
  let location = handle.location_;
  let pobj = poi(location.address);
  print_object(pobj);
}

/*-----------------------------------------------------------------------------
  'start_address' should be an int (so in hex must include '0x' prefix), it can
  point at any continuous memory that contains Object pointers.
-----------------------------------------------------------------------------*/
function print_objects_array(start_address, count) {
  let ctl = host.namespace.Debugger.Utility.Control;
  let psize = pointer_size();
  let addr_int = start_address;
  for (let i = 0; i < count; i++) {
    const addr_hex = `0x${addr_int.toString(16)}`;

    // TODO: Tried using createPointerObject but it throws unknown exception
    // from ChakraCore. Why?
    //let obj = host.createPointerObject(addr_hex, module, "void*");

    let output = ctl.ExecuteCommand(`dp ${addr_hex} l1`);
    let item = "";
    for (item of output) {} // 005f7270  34604101
    let deref = `0x${item.split(" ").pop()}`;
    print(`${addr_hex} -> ${deref}`);
    print_object(deref);

    addr_int += psize;
  }
}

function print_js_stack() {
  make_call("_v8_internal_Print_StackTrace()");
}

function set_user_js_bp() {
  let ctl = host.namespace.Debugger.Utility.Control;
  ctl.ExecuteCommand(`bp ${module_name()}!v8::internal::Execution::Call`)
}


/*=============================================================================
  Managed heap related functions (live and post-mortem debugging)
=============================================================================*/
let isolate_address = 0;
function set_isolate_address(addr) {
  isolate_address = addr;
}

/*-----------------------------------------------------------------------------
    Memory in each Space is organized into a linked list of memory chunks
-----------------------------------------------------------------------------*/
const NEVER_EVACUATE = 1 << 7; // see src\heap\spaces.h

function print_memory_chunk_list(space_type, front, top, age_mark) {
  let alloc_pos = top ? ` (allocating at: ${top})` : "";
  let age_mark_pos = age_mark ? ` (age_mark at: ${top})` : "";
  print(`${space_type}${alloc_pos}${age_mark_pos}:`);
  if (front.isNull) {
    print("<empty>\n");
    return;
  }

  let cur = front;
  while (!cur.isNull) {
    let imm = cur.flags_ & NEVER_EVACUATE ? "*" : " ";
    let addr = `0x${cur.address.toString(16)}`;
    let area =
      `0x${cur.area_start_.toString(16)} - 0x${cur.area_end_.toString(16)}`;
    let dt = `dt ${addr} ${module_name()}!v8::internal::MemoryChunk`;
    print(`${imm}    ${addr}:\t ${area} (0x${cur.size_.toString(16)}) : ${dt}`);
    cur = cur.list_node_.next_;
  }
  print("");
}

const space_tags =
  ['old', 'new_to', 'new_from', 'ro', 'map', 'code', 'lo', 'nlo'];

function get_chunks_space(space_tag, front, chunks) {
    let cur = front;
    while (!cur.isNull) {
        chunks.push({
          'address':cur.address,
          'area_start_':cur.area_start_,
          'area_end_':cur.area_end_,
          'space':space_tag});
        cur = cur.list_node_.next_;
    }
}

function get_chunks() {
  let iso = cast(isolate_address, "v8::internal::Isolate");
  let h = iso.heap_;

  let chunks = [];
  get_chunks_space('old', h.old_space_.memory_chunk_list_.front_, chunks);
  get_chunks_space('new_to',
    h.new_space_.to_space_.memory_chunk_list_.front_, chunks);
  get_chunks_space('new_from',
    h.new_space_.from_space_.memory_chunk_list_.front_, chunks);
  get_chunks_space('ro', h.read_only_space_.memory_chunk_list_.front_, chunks);
  get_chunks_space('map', h.map_space_.memory_chunk_list_.front_, chunks);
  get_chunks_space('code', h.code_space_.memory_chunk_list_.front_, chunks);
  get_chunks_space('lo', h.lo_space_.memory_chunk_list_.front_, chunks);
  get_chunks_space('nlo', h.new_lo_space_.memory_chunk_list_.front_, chunks);

  return chunks;
}

function find_chunk(address) {
  // if 'address' is greater than Number.MAX_SAFE_INTEGER, comparison ops on it
  // throw  "Error: 64 bit value loses precision on conversion to number"
  try {
    let chunks = get_chunks(isolate_address);
    for (let c of chunks) {
      let chunk = cast(c.address, "v8::internal::MemoryChunk");
      if (address >= chunk.area_start_ && address < chunk.area_end_) {
        return c;
      }
    }
  }
  catch (e) { }
  return undefined;
}

/*-----------------------------------------------------------------------------
    Print memory chunks from spaces in the current Heap
      'isolate_address' should be an int (so in hex must include '0x' prefix).
      'space': space separated string containing "all", "old", "new", "map",
               "code", "ro [readonly]", "lo [large]", "nlo [newlarge]"
-----------------------------------------------------------------------------*/
function print_memory(space = "all") {
  if (isolate_address == 0) {
    print("Please call !set_iso(isolate_address) first.");
    return;
  }

  let iso = cast(isolate_address, "v8::internal::Isolate");
  let h = iso.heap_;
  print(`Heap at ${h.targetLocation}`);

  let st = space.toLowerCase().split(" ");

  print("Im   address:\t object area start - end (size)");
  if (st.includes("all") || st.includes("old")) {
    print_memory_chunk_list("OldSpace",
      h.old_space_.memory_chunk_list_.front_,
      h.old_space_.allocation_info_.top_);
  }
  if (st.includes("all") || st.includes("new")) {
    // new space doesn't use the chunk list from its base class but from
    // the to/from semi-spaces it points to
    print_memory_chunk_list("NewSpace_To",
      h.new_space_.to_space_.memory_chunk_list_.front_,
      h.new_space_.allocation_info_.top_,
      h.new_space_.to_space_.age_mark_);
    print_memory_chunk_list("NewSpace_From",
      h.new_space_.from_space_.memory_chunk_list_.front_);
  }
  if (st.includes("all") || st.includes("map")) {
    print_memory_chunk_list("MapSpace",
      h.map_space_.memory_chunk_list_.front_,
      h.map_space_.allocation_info_.top_);
  }
  if (st.includes("all") || st.includes("code")) {
    print_memory_chunk_list("CodeSpace",
      h.code_space_.memory_chunk_list_.front_,
      h.code_space_.allocation_info_.top_);
  }
  if (st.includes("all") || st.includes("large") || st.includes("lo")) {
    print_memory_chunk_list("LargeObjectSpace",
      h.lo_space_.memory_chunk_list_.front_);
  }
  if (st.includes("all") || st.includes("newlarge") || st.includes("nlo")) {
    print_memory_chunk_list("NewLargeObjectSpace",
      h.new_lo_space_.memory_chunk_list_.front_);
  }
  if (st.includes("all") || st.includes("readonly") || st.includes("ro")) {
    print_memory_chunk_list("ReadOnlySpace",
      h.read_only_space_.memory_chunk_list_.front_);
  }
}

/*-----------------------------------------------------------------------------
    'isolate_address' and 'address' should be ints (so in hex must include '0x'
    prefix).
-----------------------------------------------------------------------------*/
function print_owning_space(address) {
  if (isolate_address == 0) {
    print("Please call !set_iso(isolate_address) first.");
    return;
  }

  let c = find_chunk(address);
  let addr = `0x${address.toString(16)}`;
  if (c) {
      print(`${addr} is in ${c.space} (chunk: 0x${c.address.toString(16)})`);
  }
  else {
      print(`Address ${addr} is not in managed heap`);
  }
}

/*=============================================================================
  Initialize short aliased names for the most common commands
=============================================================================*/
function initializeScript() {
  return [
      new host.functionAlias(help, "help"),
      new host.functionAlias(print_object_from_handle, "jlh"),
      new host.functionAlias(print_object, "job"),
      new host.functionAlias(print_objects_array, "jobs"),
      new host.functionAlias(print_js_stack, "jst"),

      new host.functionAlias(set_isolate_address, "set_iso"),
      new host.functionAlias(print_memory, "mem"),
      new host.functionAlias(print_owning_space, "where"),

      new host.functionAlias(set_user_js_bp, "jsbp"),
  ]
}