• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/*=============================================================================
6  This is a convenience script for debugging with WinDbg (akin to gdbinit)
7  It can be loaded into WinDbg with: .scriptload full_path\windbg.js
8
9  To printout the help message below into the debugger's command window:
10  !help
11=============================================================================*/
12
13function help() {
14  if (supports_call_command()) {
15  print("--------------------------------------------------------------------");
16  print("  LIVE debugging only");
17  print("--------------------------------------------------------------------");
18  print("  !jlh(\"local_handle_var_name\")");
19  print("      prints object held by the handle");
20  print("      e.g. !jlh(\"key\") or !jlh(\"this->receiver_\")");
21  print("  !job(address_or_taggedint)");
22  print("      prints object at the address, e.g. !job(0x235cb869f9)");
23  print("  !jst() or !jst");
24  print("      prints javascript stack (output goes into the console)");
25  print("  !jsbp() or !jsbp");
26  print("      sets bp in v8::internal::Execution::Call");
27  print("");
28  }
29
30  print("--------------------------------------------------------------------");
31  print("  Setup of the script");
32  print("--------------------------------------------------------------------");
33  print("  !set_module(\"module_name_no_extension\")");
34  print("      we'll try the usual suspects for where v8's code might have");
35  print("      been linked into, but you can also set it manually,");
36  print("      e.g. !set_module(\"v8_for_testing\")");
37  print("  !set_iso(isolate_address)");
38  print("      call this function before using !mem or other heap routines");
39  print("");
40
41  print("--------------------------------------------------------------------");
42  print("  Managed heap");
43  print("--------------------------------------------------------------------");
44  print("  !mem or !mem(\"space1[ space2 ...]\")");
45  print("      prints memory chunks from the 'space' owned by the heap in the");
46  print("      isolate set by !set_iso; valid values for 'space' are:");
47  print("      new, old, map, code, lo [large], nlo [newlarge], ro [readonly]");
48  print("      if no 'space' specified prints memory chunks for all spaces,");
49  print("      e.g. !mem(\"code\"), !mem(\"ro new old\")");
50  print("  !where(address)");
51  print("      prints name of the space and address of the MemoryChunk the");
52  print("      'address' is from, e.g. !where(0x235cb869f9)");
53  print("  !rs(chunk_address, set_id = 0)");
54  print("      prints slots from the remembered set in the MemoryChunk. If");
55  print("      'chunk_address' isn't specified, prints for all chunks in the");
56  print("      old space; 'set_id' should match RememberedSetType enum,");
57  print("      e.g. !rs, !rs 0x2fb14780000, !rs(0x2fb14780000, 1)");
58  print("");
59
60  print("--------------------------------------------------------------------");
61  print("  Managed objects");
62  print("--------------------------------------------------------------------");
63  print("  !jot(tagged_addr, depth)");
64  print("      dumps the tree of objects using 'tagged_addr' as a root,");
65  print("      assumes that pointer fields are aligned at ptr_size boundary,");
66  print("      unspecified depth means 'unlimited',");
67  print("      e.g. !jot(0x235cb869f9, 2), !jot 0x235cb869f9");
68  print("  !jo_in_range(start_addr, end_addr)");
69  print("      prints address/map pointers of objects found inside the range");
70  print("      specified by 'start_addr' and 'end_addr', assumes the object");
71  print("      pointers to be aligned at ptr_size boundary,");
72  print("      e.g. !jo_in_range(0x235cb869f8 - 0x100, 0x235cb869f8 + 0x1a0");
73  print("  !jo_prev(address, max_slots = 100)");
74  print("      prints address and map pointer of the nearest object within");
75  print("      'max_slots' before the given 'address', assumes the object");
76  print("      pointers to be aligned at ptr_size boundary,");
77  print("      e.g. !jo_prev 0x235cb869f8, !jo_prev(0x235cb869f9, 16)");
78  print("  !jo_next(address, max_slots = 100)");
79  print("      prints address and map pointer of the nearest object within");
80  print("      'max_slots' following the given 'address', assumes the object");
81  print("      pointers to be aligned at ptr_size boundary,");
82  print("      e.g. !jo_next 0x235cb869f8, !jo_next(0x235cb869f9, 20)");
83  print("");
84
85  print("--------------------------------------------------------------------");
86  print("  Miscellaneous");
87  print("--------------------------------------------------------------------");
88  print("  !dp(address, count = 10)");
89  print("      similar to the built-in 'dp' command but augments output with");
90  print("      more data for values that are managed pointers, note that it");
91  print("      aligns the given 'address' at ptr_sized boundary,");
92  print("      e.g. !dp 0x235cb869f9, !dp(0x235cb869f9, 500), !dp @rsp");
93  print("  !handles(print_handles = false)");
94  print("      prints stats for handles, if 'print_handles' is true will");
95  print("      output all handles as well,");
96  print("      e.g. !handles, !handles(), !handles(true)");
97  print("");
98
99  print("--------------------------------------------------------------------");
100  print("  To run any function from this script (live or postmortem):");
101  print("");
102  print("  dx @$scriptContents.function_name(args)");
103  print("      e.g. dx @$scriptContents.pointer_size()");
104  print("      e.g. dx @$scriptContents.is_map(0x235cb869f9)");
105  print("--------------------------------------------------------------------");
106}
107
108/*=============================================================================
109  On scrip load
110=============================================================================*/
111
112/*=============================================================================
113  Output
114=============================================================================*/
115function print(s) {
116  host.diagnostics.debugLog(s + "\n");
117}
118
119function inspect(s) {
120  for (let k of Reflect.ownKeys(s)) {
121    // Attempting to print either of:
122    // 'Reflect.get(s, k)', 'typeof Reflect.get(s, k)', 's[k]'
123    // might throw: "Error: Object does not have a size",
124    // while 'typeof s[k]' returns 'undefined' and prints the full list of
125    // properties. Oh well...
126    print(`${k} => ${typeof s[k]}`);
127  }
128}
129
130function hex(number) {
131  return `0x${number.toString(16)}`;
132}
133
134/*=============================================================================
135  Utils (postmortem and live)
136=============================================================================*/
137// WinDbg wraps large integers (0x80000000+) into an object of library type that
138// fails isInteger test (and, consequently fail isSafeInteger test even if the
139// original value was a safe integer).
140// However, that library type does have a set of methods on it which you can use
141// to force conversion:
142// .asNumber() / .valueOf(): Performs conversion to JavaScript number.
143// Throws if the ordinal part of the 64-bit number does not pack into JavaScript
144// number without loss of precision.
145// .convertToNumber(): Performs conversion to JavaScript number.
146// Does NOT throw if the ordinal part of the 64-bit number does not pack into
147// JavaScript number. This will simply result in loss of precision.
148// The library will also add these methods to the prototype for the standard
149// number prototype. Meaning you can always .asNumber() / .convertToNumber() to
150// get either JavaScript number or the private Int64 type into a JavaScript
151// number.
152// We could use the conversion functions but it seems that doing the conversion
153// via toString is just as good and slightly more generic...
154function int(val) {
155  if (typeof val === 'number') {
156    return Number.isInteger(val) ? val : undefined;
157  }
158  if (typeof val === 'object') {
159    let n = parseInt(val.toString());
160    return isNaN(n) ? undefined : n;
161  }
162  return undefined;
163}
164
165function is_live_session() {
166  // Assume that there is a single session (not sure how to get multiple ones
167  // going, maybe, in kernel debugging?).
168  return (host.namespace.Debugger.Sessions[0].Attributes.Target.IsLiveTarget);
169}
170
171function is_TTD_session() {
172  // Assume that there is a single session (not sure how to get multiple ones
173  // going, maybe, in kernel debugging?).
174  return (host.namespace.Debugger.Sessions[0].Attributes.Target.IsTTDTarget);
175}
176
177function supports_call_command() {
178  return is_live_session() && !is_TTD_session();
179}
180
181function cast(address, type_name) {
182  return host.createTypedObject(address, module_name(), type_name);
183}
184
185function pointer_size() {
186  return host.namespace.Debugger.Sessions[0].Attributes.Machine.PointerSize;
187}
188
189function poi(address) {
190  try {
191    // readMemoryValues throws if cannot read from 'address'.
192    return host.memory.readMemoryValues(address, 1, pointer_size())[0];
193  }
194  catch (e){}
195}
196
197function get_register(name) {
198  return host.namespace.Debugger.State.DebuggerVariables.curthread
199         .Registers.User[name];
200}
201
202// JS doesn't do bitwise operations on large integers, so let's do it ourselves
203// using hex string representation.
204function bitwise_and(l, r) {
205  l = hex(l);
206  let l_length = l.length;
207  r = hex(r);
208  let r_length = r.length;
209  let res = "";
210  let length = Math.min(l_length, r_length) - 2;  // to account for "0x"
211  for (let i = 1; i <= length; i++) {
212    res = (parseInt(l[l_length - i], 16) & parseInt(r[r_length - i], 16))
213          .toString(16) + res;
214  }
215  return parseInt(res, 16);
216}
217
218
219/*=============================================================================
220  Script setup
221=============================================================================*/
222// In debug builds v8 code is compiled into v8.dll, and in release builds
223// the code is compiled directly into the executable. If you are debugging some
224// other embedder, run !set_module and provide the module name to use.
225const known_exes = ["d8", "unittests", "mksnapshot", "chrome", "chromium"];
226let module_name_cache;
227function module_name(use_this_module) {
228  if (use_this_module) {
229    module_name_cache = use_this_module;
230  }
231
232  if (!module_name_cache) {
233    let v8 = host.namespace.Debugger.State.DebuggerVariables.curprocess
234             .Modules.Where(
235                function(m) {
236                 return m.Name.indexOf("\\v8.dll") !== -1;
237                });
238
239    let v8_test = host.namespace.Debugger.State.DebuggerVariables.curprocess
240                  .Modules.Where(
241                      function(m) {
242                      return m.Name.indexOf("\\v8_for_testing.dll") !== -1;
243                      });
244
245    if (v8.Count() > 0) {
246      module_name_cache = "v8";
247    }
248    else if (v8_test.Count() > 0) {
249      module_name_cache = "v8_for_testing";
250    }
251    else {
252      for (let exe_name in known_exes) {
253        let exe = host.namespace.Debugger.State.DebuggerVariables.curprocess
254                  .Modules.Where(
255                    function(m) {
256                      return m.Name.indexOf(`\\${exe_name}.exe`) !== -1;
257                    });
258        if (exe.Count() > 0) {
259            module_name_cache = exe_name;
260            break;
261        }
262      }
263    }
264  }
265
266  if (!module_name_cache) {
267    print(`ERROR. Couldn't determine module name for v8's symbols.`);
268    print(`Please run !set_module (e.g. "!set_module \"v8_for_testing\"")`);
269  }
270  return module_name_cache;
271};
272
273let using_ptr_compr = false;
274let isolate_address = 0;
275function set_isolate_address(addr, ptr_compr) {
276  isolate_address = addr;
277
278  if (typeof ptr_compr === 'undefined') {
279    ptr_compr = (bitwise_and(isolate_address, 0xffffffff) == 0);
280  }
281  using_ptr_compr = ptr_compr;
282
283  if (using_ptr_compr) {
284    print("The target is using pointer compression.");
285  }
286}
287
288
289/*=============================================================================
290  Wrappers around V8's printing functions and other utils for live-debugging
291=============================================================================*/
292function make_call(fn) {
293  if (!supports_call_command()) {
294    print("ERROR: This command is supported in live sessions only!");
295    return;
296  }
297
298  // .call resets current frame to the top one, so have to manually remember
299  // and restore it after making the call.
300  let curframe = host.namespace.Debugger.State.DebuggerVariables.curframe;
301  let ctl = host.namespace.Debugger.Utility.Control;
302  let output = ctl.ExecuteCommand(`.call ${fn};g`);
303  curframe.SwitchTo();
304  return output;
305}
306
307function print_object(address) {
308  let output = make_call(`_v8_internal_Print_Object(${decomp(address)})`);
309
310  // skip the first few lines with meta info of .call command
311  let skip_line = true;
312  for (let line of output) {
313    if (!skip_line) {
314      print(line);
315      continue;
316    }
317    if (line.includes("deadlocks and corruption of the debuggee")) {
318      skip_line = false;
319    }
320  }
321}
322
323function print_object_from_handle(handle_to_object) {
324  let handle = host.evaluateExpression(handle_to_object);
325  let location = handle.location_;
326  let pobj = poi(location.address);  // handles use uncompressed pointers
327  print_object(pobj);
328}
329
330function print_js_stack() {
331  make_call("_v8_internal_Print_StackTrace()");
332}
333
334function set_user_js_bp() {
335  let ctl = host.namespace.Debugger.Utility.Control;
336  ctl.ExecuteCommand(`bp ${module_name()}!v8::internal::Execution::Call`)
337}
338
339
340/*=============================================================================
341  Managed heap related functions (live and post-mortem debugging)
342=============================================================================*/
343/*-----------------------------------------------------------------------------
344    Pointer compression
345-----------------------------------------------------------------------------*/
346function tagged_size() {
347  return using_ptr_compr ? 4 : pointer_size();
348}
349
350function get_compressed_ptr_base() {
351  if (!using_ptr_compr) return 0;
352
353  return isolate_address;
354}
355
356function decomp(value) {
357  if (value > 0xffffffff) return value;
358  return get_compressed_ptr_base() + value;
359}
360
361// Adjust for possible pointer compression ('address' is assumed to be on the
362// managed heap).
363function poim(address) {
364  try {
365    // readMemoryValues throws if cannot read from 'address'.
366    return host.memory.readMemoryValues(decomp(address), 1, tagged_size())[0];
367  }
368  catch (e){}
369}
370
371/*-----------------------------------------------------------------------------
372    Exploring objects
373-----------------------------------------------------------------------------*/
374function is_map(addr) {
375  let address = int(addr);
376  if (!Number.isSafeInteger(address) || address % 2 == 0) return false;
377
378  // the first field in all objects, including maps, is a map pointer, but for
379  // maps the pointer is always the same - the meta map that points to itself.
380  const map_addr = int(poim(address - 1));
381  if (!Number.isSafeInteger(map_addr)) return false;
382
383  const map_map_addr = int(poim(map_addr - 1));
384  if (!Number.isSafeInteger(map_map_addr)) return false;
385
386  return (map_addr === map_map_addr);
387}
388
389function is_likely_object(addr) {
390  let address = int(addr);
391  if (!Number.isSafeInteger(address) || address % 2 == 0) return false;
392
393  // the first field in all objects must be a map pointer
394  return is_map(poim(address - 1));
395}
396
397function find_object_near(aligned_addr, max_distance, step_op) {
398  if (!step_op) {
399    const step = tagged_size();
400    const prev =
401      find_object_near(aligned_addr, max_distance, x => x - step);
402    const next =
403      find_object_near(aligned_addr, max_distance, x => x + step);
404
405    if (!prev) return next;
406    if (!next) return prev;
407    return (addr - prev <= next - addr) ? prev : next;
408  }
409
410  let maybe_map_addr = poim(aligned_addr);
411  let iters = 0;
412  while (maybe_map_addr && iters < max_distance) {
413    if (is_map(maybe_map_addr)) {
414      return aligned_addr;
415    }
416    aligned_addr = step_op(aligned_addr);
417    maybe_map_addr = poim(aligned_addr);
418    iters++;
419  }
420}
421
422function find_object_prev(addr, max_distance) {
423  if (!Number.isSafeInteger(int(addr))) return;
424
425  const ptr_size = tagged_size();
426  const aligned_addr = addr - (addr % ptr_size);
427  return find_object_near(aligned_addr, max_distance, x => x - ptr_size);
428}
429
430function find_object_next(addr, max_distance) {
431  if (!Number.isSafeInteger(int(addr))) return;
432
433  const ptr_size = tagged_size();
434  const aligned_addr = addr - (addr % ptr_size) + ptr_size;
435  return find_object_near(aligned_addr, max_distance, x => x + ptr_size);
436}
437
438function print_object_prev(addr, max_slots = 100) {
439  let obj_addr = find_object_prev(addr, max_slots);
440  if (!obj_addr) {
441    print(
442      `No object found within ${max_slots} slots prior to ${hex(addr)}`);
443  }
444  else {
445    print(
446      `found object: ${hex(obj_addr + 1)} : ${hex(poim(obj_addr))}`);
447  }
448}
449
450function print_object_next(addr, max_slots = 100) {
451  let obj_addr = find_object_next(addr, max_slots);
452  if (!obj_addr) {
453    print(
454      `No object found within ${max_slots} slots following ${hex(addr)}`);
455  }
456  else {
457    print(
458      `found object: ${hex(obj_addr + 1)} : ${hex(poim(obj_addr))}`);
459  }
460}
461
462// This function assumes that pointers to objects are stored at ptr-size aligned
463// boundaries.
464function print_objects_in_range(start, end){
465  if (!Number.isSafeInteger(int(start)) || !Number.isSafeInteger(int(end))) {
466    return;
467  }
468  const ptr_size = pointer_size();
469  if (start < ptr_size || end <= start) return;
470
471  let iters = (end - start) / ptr_size;
472  let cur = start - ptr_size;
473  print(`===============================================`);
474  print(`objects in range ${hex(start)} - ${hex(end)}`);
475  print(`===============================================`);
476  let count = 0;
477  while (cur && cur < end) {
478    let obj = find_object_next(cur, iters);
479    if (obj) {
480      count++;
481      print(`${hex(obj + 1)} : ${hex(poim(obj))}`);
482      iters  = (end - cur) / ptr_size;
483    }
484    cur = obj + ptr_size;
485  }
486  print(`===============================================`);
487  print(`found ${count} objects in range ${hex(start)} - ${hex(end)}`)
488  print(`===============================================`);
489}
490
491// This function assumes the pointer fields to be ptr-size aligned.
492function print_objects_tree(root, depth_limit) {
493  if(!is_likely_object(root)) {
494    print(`${hex(root)} doesn't look like an object`);
495    return;
496  }
497
498  let path = [];
499
500  function impl(obj, depth, depth_limit) {
501    const ptr_size = tagged_size();
502    // print the current object and its map pointer
503    const this_obj =
504      `${" ".repeat(2 * depth)}${hex(obj)} : ${hex(poim(obj - 1))}`;
505    const cutoff = depth_limit && depth == depth_limit - 1;
506    print(`${this_obj}${cutoff ? " (...)" : ""}`);
507    if (cutoff) return;
508
509    path[depth] = obj;
510    path.length = depth + 1;
511    let cur = obj - 1 + ptr_size;
512
513    // Scan downwards until an address that is likely to be at the start of
514    // another object, in which case it's time to pop out from the recursion.
515    let iter = 0; // an arbitrary guard to avoid hanging the debugger
516    let seen = new Set(path);
517    while (!is_likely_object(cur + 1) && iter < 100) {
518      iter++;
519      let field = poim(cur);
520      if (is_likely_object(field)) {
521        if (seen.has(field)) {
522          print(
523            `${" ".repeat(2 * depth + 2)}cycle: ${hex(cur)}->${hex(field)}`);
524        }
525        else {
526          impl(field, depth + 1, depth_limit);
527        }
528      }
529      cur += ptr_size;
530    }
531  }
532  print(`===============================================`);
533  impl(root, 0, depth_limit);
534  print(`===============================================`);
535}
536
537/*-----------------------------------------------------------------------------
538    Memory spaces
539-----------------------------------------------------------------------------*/
540const NEVER_EVACUATE = 1 << 7; // see src\heap\spaces.h
541
542function print_memory_chunk_list(space_type, front, top, age_mark) {
543  let alloc_pos = top ? ` (allocating at: ${top})` : "";
544  let age_mark_pos = age_mark ? ` (age_mark at: ${top})` : "";
545  print(`${space_type}${alloc_pos}${age_mark_pos}:`);
546  if (front.isNull) {
547    print("<empty>\n");
548    return;
549  }
550
551  let cur = front;
552  while (!cur.isNull) {
553    let imm = cur.flags_ & NEVER_EVACUATE ? "*" : " ";
554    let addr = hex(cur.address);
555    let area = `${hex(cur.area_start_)} - ${hex(cur.area_end_)}`;
556    let dt = `dt ${addr} ${module_name()}!v8::internal::MemoryChunk`;
557    print(`${imm}    ${addr}:\t ${area} (${hex(cur.size_)}) : ${dt}`);
558    cur = cur.list_node_.next_;
559  }
560  print("");
561}
562
563const space_tags =
564  ['old', 'new_to', 'new_from', 'ro', 'map', 'code', 'lo', 'nlo'];
565
566function get_chunks_space(space_tag, front, chunks) {
567    let cur = front;
568    while (!cur.isNull) {
569        chunks.push({
570          'address':cur.address,
571          'area_start_':cur.area_start_,
572          'area_end_':cur.area_end_,
573          'space':space_tag});
574        cur = cur.list_node_.next_;
575    }
576}
577
578function get_chunks() {
579  let iso = cast(isolate_address, "v8::internal::Isolate");
580  let h = iso.heap_;
581
582  let chunks = [];
583  get_chunks_space('old', h.old_space_.memory_chunk_list_.front_, chunks);
584  get_chunks_space('new_to',
585    h.new_space_.to_space_.memory_chunk_list_.front_, chunks);
586  get_chunks_space('new_from',
587    h.new_space_.from_space_.memory_chunk_list_.front_, chunks);
588  get_chunks_space('ro', h.read_only_space_.memory_chunk_list_.front_, chunks);
589  get_chunks_space('map', h.map_space_.memory_chunk_list_.front_, chunks);
590  get_chunks_space('code', h.code_space_.memory_chunk_list_.front_, chunks);
591  get_chunks_space('lo', h.lo_space_.memory_chunk_list_.front_, chunks);
592  get_chunks_space('nlo', h.new_lo_space_.memory_chunk_list_.front_, chunks);
593
594  return chunks;
595}
596
597function find_chunk(address) {
598  if (!Number.isSafeInteger(int(address))) return undefined;
599
600  let chunks = get_chunks(isolate_address);
601  for (let c of chunks) {
602    let chunk = cast(c.address, "v8::internal::MemoryChunk");
603    if (address >= chunk.area_start_ && address < chunk.area_end_) {
604      return c;
605    }
606  }
607
608  return undefined;
609}
610
611function print_memory(space = "all") {
612  if (isolate_address == 0) {
613    print("Please call !set_iso(isolate_address) first.");
614    return;
615  }
616
617  let iso = cast(isolate_address, "v8::internal::Isolate");
618  let h = iso.heap_;
619  print(`Heap at ${h.targetLocation}`);
620
621  let st = space.toLowerCase().split(" ");
622
623  print("Im   address:\t object area start - end (size)");
624  if (st.includes("all") || st.includes("old")) {
625    print_memory_chunk_list("OldSpace",
626      h.old_space_.memory_chunk_list_.front_,
627      h.old_space_.allocation_info_.top_);
628  }
629  if (st.includes("all") || st.includes("new")) {
630    // new space doesn't use the chunk list from its base class but from
631    // the to/from semi-spaces it points to
632    print_memory_chunk_list("NewSpace_To",
633      h.new_space_.to_space_.memory_chunk_list_.front_,
634      h.new_space_.allocation_info_.top_,
635      h.new_space_.to_space_.age_mark_);
636    print_memory_chunk_list("NewSpace_From",
637      h.new_space_.from_space_.memory_chunk_list_.front_);
638  }
639  if (st.includes("all") || st.includes("map")) {
640    print_memory_chunk_list("MapSpace",
641      h.map_space_.memory_chunk_list_.front_,
642      h.map_space_.allocation_info_.top_);
643  }
644  if (st.includes("all") || st.includes("code")) {
645    print_memory_chunk_list("CodeSpace",
646      h.code_space_.memory_chunk_list_.front_,
647      h.code_space_.allocation_info_.top_);
648  }
649  if (st.includes("all") || st.includes("large") || st.includes("lo")) {
650    print_memory_chunk_list("OldLargeObjectSpace",
651      h.lo_space_.memory_chunk_list_.front_);
652  }
653  if (st.includes("all") || st.includes("newlarge") || st.includes("nlo")) {
654    print_memory_chunk_list("NewLargeObjectSpace",
655      h.new_lo_space_.memory_chunk_list_.front_);
656  }
657  if (st.includes("all") || st.includes("readonly") || st.includes("ro")) {
658    print_memory_chunk_list("ReadOnlySpace",
659      h.read_only_space_.memory_chunk_list_.front_);
660  }
661}
662
663function print_owning_space(address) {
664  if (isolate_address == 0) {
665    print("Please call !set_iso(isolate_address) first.");
666    return;
667  }
668
669  address = decomp(address);
670  let c = find_chunk(address);
671  if (c) {
672      print(`${hex(address)} is in ${c.space} (chunk: ${hex(c.address)})`);
673  }
674  else {
675      print(`Address ${hex(address)} is not in managed heap`);
676  }
677}
678
679/*-----------------------------------------------------------------------------
680    Handles
681-----------------------------------------------------------------------------*/
682function print_handles_data(print_handles = false) {
683  if (isolate_address == 0) {
684    print("Please call !set_iso(isolate_address) first.");
685    return;
686  }
687
688  let iso = cast(isolate_address, "v8::internal::Isolate");
689  let hsd = iso.handle_scope_data_;
690  let hsimpl = iso.handle_scope_implementer_;
691
692  // depth level
693  print(`Nested depth level: ${hsd.level}`);
694
695  // count of handles
696  const ptr_size = pointer_size();
697  let blocks = hsimpl.blocks_;
698  const block_size = 1022; // v8::internal::KB - 2
699  const first_block = blocks.data_.address;
700  const last_block = (blocks.size_ == 0)
701                     ? first_block
702                     : first_block + ptr_size * (blocks.size_ - 1);
703
704  const count = (blocks.size_ == 0)
705              ? 0
706              : (blocks.size_ - 1) * block_size +
707                (hsd.next.address - poi(last_block))/ptr_size;
708  print(`Currently tracking ${count} local handles`);
709
710  // print the handles
711  if (print_handles && count > 0) {
712    for (let block = first_block; block < last_block;
713         block += block_size * ptr_size) {
714      print(`Handles in block at ${hex(block)}`);
715      for (let i = 0; i < block_size; i++) {
716        const location = poi(block + i * ptr_size);
717        print(`  ${hex(location)}->${hex(poi(location))}`);
718      }
719    }
720
721    let location = poi(last_block);
722    print(`Handles in block at ${hex(last_block)}`);
723    for (let location = poi(last_block); location < hsd.next.address;
724         location += ptr_size) {
725      print(`  ${hex(location)}->${hex(poi(location))}`);
726    }
727  }
728
729  // where will the next handle allocate at?
730  const prefix = "Next handle's location will be";
731  if (hsd.next.address < hsd.limit.address) {
732    print(`${prefix} at ${hex(hsd.next.address)}`);
733  }
734  else if (hsimpl.spare_) {
735    const location = hsimpl.spare_.address;
736    print(`${prefix} from the spare block at ${hex(location)}`);
737  }
738  else {
739    print(`${prefix} from a new block to be allocated`);
740  }
741}
742
743/*-----------------------------------------------------------------------------
744    dp
745-----------------------------------------------------------------------------*/
746function pad_right(addr) {
747  let addr_hex = hex(addr);
748  return `${addr_hex}${" ".repeat(pointer_size() * 2 + 2 - addr_hex.length)}`;
749}
750
751// TODO irinayat: would be nice to identify handles and smi as well
752function dp(addr, count = 10) {
753  if (isolate_address == 0) {
754    print(`To see where objects are located, run !set_iso.`);
755  }
756
757  if (!Number.isSafeInteger(int(addr))) {
758    print(`${hex(addr)} doesn't look like a valid address`);
759    return;
760  }
761
762  const ptr_size = tagged_size();
763  let aligned_addr = addr - (addr % ptr_size);
764  let val = poim(aligned_addr);
765  let iter = 0;
766  while (val && iter < count) {
767    const map = is_map(val);
768    const obj = is_likely_object(val) && !map;
769
770    const augm_map = map ? "map" : "";
771    const augm_obj = obj ? "obj" : "";
772    const augm_other = !map && !obj ? "val" : "";
773
774    let c = find_chunk(decomp(val));
775    const augm_space = c ? ` in ${c.space}` : "";
776    const augm = `${augm_map}${augm_obj}${augm_other}${augm_space}`;
777
778    const full_ptr = using_ptr_compr ?
779        pad_right((map || obj) ? decomp(val) : val) : "";
780    print(`${pad_right(aligned_addr)} ${pad_right(val)} ${full_ptr}   ${augm}`);
781
782    aligned_addr += ptr_size;
783    val = poim(aligned_addr);
784    iter++;
785  }
786}
787
788/*-----------------------------------------------------------------------------
789    Remembered Sets
790-----------------------------------------------------------------------------*/
791// set ids: 0 = OLD_TO_NEW, 1 = 0 = OLD_TO_OLD
792function print_remembered_set(chunk_addr, set_id = 0) {
793  if (!chunk_addr) {
794    if (isolate_address == 0) {
795      print("Please call !set_iso(isolate_address) or provide chunk address.");
796      return;
797    }
798
799    let iso = cast(isolate_address, "v8::internal::Isolate");
800    let h = iso.heap_;
801    let chunks = [];
802    get_chunks_space('old', h.old_space_.memory_chunk_list_.front_, chunks);
803    get_chunks_space('lo', h.lo_space_.memory_chunk_list_.front_, chunks);
804    for (let c of chunks) {
805      try {
806        print_remembered_set(c.address);
807      }
808      catch (e) {
809        print(`failed to process chunk ${hex(c.address)} due to ${e.message}`);
810      }
811    }
812    return;
813  }
814
815  print(`Remembered set in chunk ${hex(chunk_addr)}`);
816  let chunk = cast(chunk_addr, "v8::internal::MemoryChunk");
817
818  // chunk.slot_set_ is an array of SlotSet's. For standard pages there is 0 or
819  // 1 item in the array, but for large pages there will be more.
820  const page_size = 256 * 1024;
821  const sets_count = Math.floor((chunk.size_ + page_size - 1) / page_size);
822  let rs = chunk.slot_set_[set_id];
823  if (rs.isNull) {
824    print(`  <empty>`);
825    return;
826  }
827  if (rs[0].page_start_ != chunk_addr) {
828    print(`page_start_ [${hex(rs.page_start_)}] doesn't match chunk_addr!`);
829    return;
830  }
831
832  const ptr_size = tagged_size();
833  let count = 0;
834  for (let s = 0; s < sets_count; s++){
835    const buckets_count = rs[s].buckets_.Count();
836    for (let b = 0; b < buckets_count; b++) {
837      let bucket = rs[s].buckets_[b];
838      if (bucket.isNull) continue;
839      // there are 32 cells in each bucket, cell's size is 32 bits
840      print(`  bucket ${hex(bucket.address.asNumber())}:`);
841      const first_cell = bucket.address.asNumber();
842      for (let c = 0; c < 32; c++) {
843        let cell = host.memory.readMemoryValues(
844          first_cell + c * 4, 1, 4 /*size to read*/)[0];
845        if (cell == 0) continue;
846        let mask = 1;
847        for (let bit = 0; bit < 32; bit++){
848          if (cell & mask) {
849            count++;
850            const slot_offset = (b * 32 * 32 + c * 32 + bit) * ptr_size;
851            const slot = rs[s].page_start_ + slot_offset;
852            print(`    ${hex(slot)} -> ${hex(poim(slot))}`);
853          }
854          mask = mask << 1;
855        }
856      }
857    }
858  }
859
860  if (count == 0) print(`  <empty>`);
861  else print(`  ${count} remembered pointers in chunk ${hex(chunk_addr)}`);
862}
863
864
865/*=============================================================================
866  Initialize short aliased names for the most common commands
867=============================================================================*/
868function initializeScript() {
869  return [
870      new host.functionAlias(help, "help"),
871      new host.functionAlias(print_object_from_handle, "jlh"),
872      new host.functionAlias(print_object, "job"),
873      new host.functionAlias(print_js_stack, "jst"),
874
875      new host.functionAlias(set_isolate_address, "set_iso"),
876      new host.functionAlias(module_name, "set_module"),
877      new host.functionAlias(print_memory, "mem"),
878      new host.functionAlias(print_owning_space, "where"),
879      new host.functionAlias(print_handles_data, "handles"),
880      new host.functionAlias(print_remembered_set, "rs"),
881
882      new host.functionAlias(print_object_prev, "jo_prev"),
883      new host.functionAlias(print_object_next, "jo_next"),
884      new host.functionAlias(print_objects_in_range, "jo_in_range"),
885      new host.functionAlias(print_objects_tree, "jot"),
886
887      new host.functionAlias(dp, "dp"),
888
889      new host.functionAlias(set_user_js_bp, "jsbp"),
890  ]
891}
892