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