| // 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() { |
| if (supports_call_command()) { |
| 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(" !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(" Setup of the script"); |
| print("--------------------------------------------------------------------"); |
| print(" !set_module(\"module_name_no_extension\")"); |
| print(" we'll try the usual suspects for where v8's code might have"); |
| print(" been linked into, but you can also set it manually,"); |
| print(" e.g. !set_module(\"v8_for_testing\")"); |
| print(" !set_iso(isolate_address)"); |
| print(" call this function before using !mem or other heap routines"); |
| print(""); |
| |
| print("--------------------------------------------------------------------"); |
| print(" Managed heap"); |
| print("--------------------------------------------------------------------"); |
| 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(" !rs(chunk_address, set_id = 0)"); |
| print(" prints slots from the remembered set in the MemoryChunk. If"); |
| print(" 'chunk_address' isn't specified, prints for all chunks in the"); |
| print(" old space; 'set_id' should match RememberedSetType enum,"); |
| print(" e.g. !rs, !rs 0x2fb14780000, !rs(0x2fb14780000, 1)"); |
| print(""); |
| |
| print("--------------------------------------------------------------------"); |
| print(" Managed objects"); |
| print("--------------------------------------------------------------------"); |
| print(" !jot(tagged_addr, depth)"); |
| print(" dumps the tree of objects using 'tagged_addr' as a root,"); |
| print(" assumes that pointer fields are aligned at ptr_size boundary,"); |
| print(" unspecified depth means 'unlimited',"); |
| print(" e.g. !jot(0x235cb869f9, 2), !jot 0x235cb869f9"); |
| print(" !jo_in_range(start_addr, end_addr)"); |
| print(" prints address/map pointers of objects found inside the range"); |
| print(" specified by 'start_addr' and 'end_addr', assumes the object"); |
| print(" pointers to be aligned at ptr_size boundary,"); |
| print(" e.g. !jo_in_range(0x235cb869f8 - 0x100, 0x235cb869f8 + 0x1a0"); |
| print(" !jo_prev(address, max_slots = 100)"); |
| print(" prints address and map pointer of the nearest object within"); |
| print(" 'max_slots' before the given 'address', assumes the object"); |
| print(" pointers to be aligned at ptr_size boundary,"); |
| print(" e.g. !jo_prev 0x235cb869f8, !jo_prev(0x235cb869f9, 16)"); |
| print(" !jo_next(address, max_slots = 100)"); |
| print(" prints address and map pointer of the nearest object within"); |
| print(" 'max_slots' following the given 'address', assumes the object"); |
| print(" pointers to be aligned at ptr_size boundary,"); |
| print(" e.g. !jo_next 0x235cb869f8, !jo_next(0x235cb869f9, 20)"); |
| print(""); |
| |
| print("--------------------------------------------------------------------"); |
| print(" Miscellaneous"); |
| print("--------------------------------------------------------------------"); |
| print(" !dp(address, count = 10)"); |
| print(" similar to the built-in 'dp' command but augments output with"); |
| print(" more data for values that are managed pointers, note that it"); |
| print(" aligns the given 'address' at ptr_sized boundary,"); |
| print(" e.g. !dp 0x235cb869f9, !dp(0x235cb869f9, 500), !dp @rsp"); |
| print(" !handles(print_handles = false)"); |
| print(" prints stats for handles, if 'print_handles' is true will"); |
| print(" output all handles as well,"); |
| print(" e.g. !handles, !handles(), !handles(true)"); |
| 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.is_map(0x235cb869f9)"); |
| print("--------------------------------------------------------------------"); |
| } |
| |
| /*============================================================================= |
| On scrip load |
| =============================================================================*/ |
| |
| /*============================================================================= |
| Output |
| =============================================================================*/ |
| function print(s) { |
| host.diagnostics.debugLog(s + "\n"); |
| } |
| |
| function inspect(s) { |
| for (let k of Reflect.ownKeys(s)) { |
| // Attempting to print either of: |
| // 'Reflect.get(s, k)', 'typeof Reflect.get(s, k)', 's[k]' |
| // might throw: "Error: Object does not have a size", |
| // while 'typeof s[k]' returns 'undefined' and prints the full list of |
| // properties. Oh well... |
| print(`${k} => ${typeof s[k]}`); |
| } |
| } |
| |
| function hex(number) { |
| return `0x${number.toString(16)}`; |
| } |
| |
| /*============================================================================= |
| Utils (postmortem and live) |
| =============================================================================*/ |
| // WinDbg wraps large integers (0x80000000+) into an object of library type that |
| // fails isInteger test (and, consequently fail isSafeInteger test even if the |
| // original value was a safe integer). |
| // However, that library type does have a set of methods on it which you can use |
| // to force conversion: |
| // .asNumber() / .valueOf(): Performs conversion to JavaScript number. |
| // Throws if the ordinal part of the 64-bit number does not pack into JavaScript |
| // number without loss of precision. |
| // .convertToNumber(): Performs conversion to JavaScript number. |
| // Does NOT throw if the ordinal part of the 64-bit number does not pack into |
| // JavaScript number. This will simply result in loss of precision. |
| // The library will also add these methods to the prototype for the standard |
| // number prototype. Meaning you can always .asNumber() / .convertToNumber() to |
| // get either JavaScript number or the private Int64 type into a JavaScript |
| // number. |
| // We could use the conversion functions but it seems that doing the conversion |
| // via toString is just as good and slightly more generic... |
| function int(val) { |
| if (typeof val === 'number') { |
| return Number.isInteger(val) ? val : undefined; |
| } |
| if (typeof val === 'object') { |
| let n = parseInt(val.toString()); |
| return isNaN(n) ? undefined : n; |
| } |
| return undefined; |
| } |
| |
| function is_live_session() { |
| // Assume that there is a single session (not sure how to get multiple ones |
| // going, maybe, in kernel debugging?). |
| return (host.namespace.Debugger.Sessions[0].Attributes.Target.IsLiveTarget); |
| } |
| |
| function is_TTD_session() { |
| // Assume that there is a single session (not sure how to get multiple ones |
| // going, maybe, in kernel debugging?). |
| return (host.namespace.Debugger.Sessions[0].Attributes.Target.IsTTDTarget); |
| } |
| |
| function supports_call_command() { |
| return is_live_session() && !is_TTD_session(); |
| } |
| |
| function cast(address, type_name) { |
| return host.createTypedObject(address, module_name(), type_name); |
| } |
| |
| function pointer_size() { |
| return host.namespace.Debugger.Sessions[0].Attributes.Machine.PointerSize; |
| } |
| |
| 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]; |
| } |
| |
| // JS doesn't do bitwise operations on large integers, so let's do it ourselves |
| // using hex string representation. |
| function bitwise_and(l, r) { |
| l = hex(l); |
| let l_length = l.length; |
| r = hex(r); |
| let r_length = r.length; |
| let res = ""; |
| let length = Math.min(l_length, r_length) - 2; // to account for "0x" |
| for (let i = 1; i <= length; i++) { |
| res = (parseInt(l[l_length - i], 16) & parseInt(r[r_length - i], 16)) |
| .toString(16) + res; |
| } |
| return parseInt(res, 16); |
| } |
| |
| |
| /*============================================================================= |
| Script setup |
| =============================================================================*/ |
| // 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, run !set_module 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; |
| }); |
| |
| let v8_test = host.namespace.Debugger.State.DebuggerVariables.curprocess |
| .Modules.Where( |
| function(m) { |
| return m.Name.indexOf("\\v8_for_testing.dll") !== -1; |
| }); |
| |
| if (v8.Count() > 0) { |
| module_name_cache = "v8"; |
| } |
| else if (v8_test.Count() > 0) { |
| module_name_cache = "v8_for_testing"; |
| } |
| 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.Count() > 0) { |
| module_name_cache = exe_name; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (!module_name_cache) { |
| print(`ERROR. Couldn't determine module name for v8's symbols.`); |
| print(`Please run !set_module (e.g. "!set_module \"v8_for_testing\"")`); |
| } |
| return module_name_cache; |
| }; |
| |
| let using_ptr_compr = false; |
| let isolate_address = 0; |
| function set_isolate_address(addr, ptr_compr) { |
| isolate_address = addr; |
| |
| if (typeof ptr_compr === 'undefined') { |
| ptr_compr = (bitwise_and(isolate_address, 0xffffffff) == 0); |
| } |
| using_ptr_compr = ptr_compr; |
| |
| if (using_ptr_compr) { |
| print("The target is using pointer compression."); |
| } |
| } |
| |
| |
| /*============================================================================= |
| Wrappers around V8's printing functions and other utils for live-debugging |
| =============================================================================*/ |
| function make_call(fn) { |
| if (!supports_call_command()) { |
| print("ERROR: This command is supported in live sessions only!"); |
| return; |
| } |
| |
| // .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; |
| } |
| |
| function print_object(address) { |
| let output = make_call(`_v8_internal_Print_Object(${decomp(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; |
| } |
| } |
| } |
| |
| function print_object_from_handle(handle_to_object) { |
| let handle = host.evaluateExpression(handle_to_object); |
| let location = handle.location_; |
| let pobj = poi(location.address); // handles use uncompressed pointers |
| print_object(pobj); |
| } |
| |
| 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) |
| =============================================================================*/ |
| /*----------------------------------------------------------------------------- |
| Pointer compression |
| -----------------------------------------------------------------------------*/ |
| function tagged_size() { |
| return using_ptr_compr ? 4 : pointer_size(); |
| } |
| |
| function get_compressed_ptr_base() { |
| if (!using_ptr_compr) return 0; |
| |
| return isolate_address; |
| } |
| |
| function decomp(value) { |
| if (value > 0xffffffff) return value; |
| return get_compressed_ptr_base() + value; |
| } |
| |
| // Adjust for possible pointer compression ('address' is assumed to be on the |
| // managed heap). |
| function poim(address) { |
| try { |
| // readMemoryValues throws if cannot read from 'address'. |
| return host.memory.readMemoryValues(decomp(address), 1, tagged_size())[0]; |
| } |
| catch (e){} |
| } |
| |
| /*----------------------------------------------------------------------------- |
| Exploring objects |
| -----------------------------------------------------------------------------*/ |
| function is_map(addr) { |
| let address = int(addr); |
| if (!Number.isSafeInteger(address) || address % 2 == 0) return false; |
| |
| // the first field in all objects, including maps, is a map pointer, but for |
| // maps the pointer is always the same - the meta map that points to itself. |
| const map_addr = int(poim(address - 1)); |
| if (!Number.isSafeInteger(map_addr)) return false; |
| |
| const map_map_addr = int(poim(map_addr - 1)); |
| if (!Number.isSafeInteger(map_map_addr)) return false; |
| |
| return (map_addr === map_map_addr); |
| } |
| |
| function is_likely_object(addr) { |
| let address = int(addr); |
| if (!Number.isSafeInteger(address) || address % 2 == 0) return false; |
| |
| // the first field in all objects must be a map pointer |
| return is_map(poim(address - 1)); |
| } |
| |
| function find_object_near(aligned_addr, max_distance, step_op) { |
| if (!step_op) { |
| const step = tagged_size(); |
| const prev = |
| find_object_near(aligned_addr, max_distance, x => x - step); |
| const next = |
| find_object_near(aligned_addr, max_distance, x => x + step); |
| |
| if (!prev) return next; |
| if (!next) return prev; |
| return (addr - prev <= next - addr) ? prev : next; |
| } |
| |
| let maybe_map_addr = poim(aligned_addr); |
| let iters = 0; |
| while (maybe_map_addr && iters < max_distance) { |
| if (is_map(maybe_map_addr)) { |
| return aligned_addr; |
| } |
| aligned_addr = step_op(aligned_addr); |
| maybe_map_addr = poim(aligned_addr); |
| iters++; |
| } |
| } |
| |
| function find_object_prev(addr, max_distance) { |
| if (!Number.isSafeInteger(int(addr))) return; |
| |
| const ptr_size = tagged_size(); |
| const aligned_addr = addr - (addr % ptr_size); |
| return find_object_near(aligned_addr, max_distance, x => x - ptr_size); |
| } |
| |
| function find_object_next(addr, max_distance) { |
| if (!Number.isSafeInteger(int(addr))) return; |
| |
| const ptr_size = tagged_size(); |
| const aligned_addr = addr - (addr % ptr_size) + ptr_size; |
| return find_object_near(aligned_addr, max_distance, x => x + ptr_size); |
| } |
| |
| function print_object_prev(addr, max_slots = 100) { |
| let obj_addr = find_object_prev(addr, max_slots); |
| if (!obj_addr) { |
| print( |
| `No object found within ${max_slots} slots prior to ${hex(addr)}`); |
| } |
| else { |
| print( |
| `found object: ${hex(obj_addr + 1)} : ${hex(poim(obj_addr))}`); |
| } |
| } |
| |
| function print_object_next(addr, max_slots = 100) { |
| let obj_addr = find_object_next(addr, max_slots); |
| if (!obj_addr) { |
| print( |
| `No object found within ${max_slots} slots following ${hex(addr)}`); |
| } |
| else { |
| print( |
| `found object: ${hex(obj_addr + 1)} : ${hex(poim(obj_addr))}`); |
| } |
| } |
| |
| // This function assumes that pointers to objects are stored at ptr-size aligned |
| // boundaries. |
| function print_objects_in_range(start, end){ |
| if (!Number.isSafeInteger(int(start)) || !Number.isSafeInteger(int(end))) { |
| return; |
| } |
| const ptr_size = pointer_size(); |
| if (start < ptr_size || end <= start) return; |
| |
| let iters = (end - start) / ptr_size; |
| let cur = start - ptr_size; |
| print(`===============================================`); |
| print(`objects in range ${hex(start)} - ${hex(end)}`); |
| print(`===============================================`); |
| let count = 0; |
| while (cur && cur < end) { |
| let obj = find_object_next(cur, iters); |
| if (obj) { |
| count++; |
| print(`${hex(obj + 1)} : ${hex(poim(obj))}`); |
| iters = (end - cur) / ptr_size; |
| } |
| cur = obj + ptr_size; |
| } |
| print(`===============================================`); |
| print(`found ${count} objects in range ${hex(start)} - ${hex(end)}`) |
| print(`===============================================`); |
| } |
| |
| // This function assumes the pointer fields to be ptr-size aligned. |
| function print_objects_tree(root, depth_limit) { |
| if(!is_likely_object(root)) { |
| print(`${hex(root)} doesn't look like an object`); |
| return; |
| } |
| |
| let path = []; |
| |
| function impl(obj, depth, depth_limit) { |
| const ptr_size = tagged_size(); |
| // print the current object and its map pointer |
| const this_obj = |
| `${" ".repeat(2 * depth)}${hex(obj)} : ${hex(poim(obj - 1))}`; |
| const cutoff = depth_limit && depth == depth_limit - 1; |
| print(`${this_obj}${cutoff ? " (...)" : ""}`); |
| if (cutoff) return; |
| |
| path[depth] = obj; |
| path.length = depth + 1; |
| let cur = obj - 1 + ptr_size; |
| |
| // Scan downwards until an address that is likely to be at the start of |
| // another object, in which case it's time to pop out from the recursion. |
| let iter = 0; // an arbitrary guard to avoid hanging the debugger |
| let seen = new Set(path); |
| while (!is_likely_object(cur + 1) && iter < 100) { |
| iter++; |
| let field = poim(cur); |
| if (is_likely_object(field)) { |
| if (seen.has(field)) { |
| print( |
| `${" ".repeat(2 * depth + 2)}cycle: ${hex(cur)}->${hex(field)}`); |
| } |
| else { |
| impl(field, depth + 1, depth_limit); |
| } |
| } |
| cur += ptr_size; |
| } |
| } |
| print(`===============================================`); |
| impl(root, 0, depth_limit); |
| print(`===============================================`); |
| } |
| |
| /*----------------------------------------------------------------------------- |
| Memory spaces |
| -----------------------------------------------------------------------------*/ |
| 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 = hex(cur.address); |
| let area = `${hex(cur.area_start_)} - ${hex(cur.area_end_)}`; |
| let dt = `dt ${addr} ${module_name()}!v8::internal::MemoryChunk`; |
| print(`${imm} ${addr}:\t ${area} (${hex(cur.size_)}) : ${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 (!Number.isSafeInteger(int(address))) return undefined; |
| |
| 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; |
| } |
| } |
| |
| return undefined; |
| } |
| |
| 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("OldLargeObjectSpace", |
| 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_); |
| } |
| } |
| |
| function print_owning_space(address) { |
| if (isolate_address == 0) { |
| print("Please call !set_iso(isolate_address) first."); |
| return; |
| } |
| |
| address = decomp(address); |
| let c = find_chunk(address); |
| if (c) { |
| print(`${hex(address)} is in ${c.space} (chunk: ${hex(c.address)})`); |
| } |
| else { |
| print(`Address ${hex(address)} is not in managed heap`); |
| } |
| } |
| |
| /*----------------------------------------------------------------------------- |
| Handles |
| -----------------------------------------------------------------------------*/ |
| function print_handles_data(print_handles = false) { |
| if (isolate_address == 0) { |
| print("Please call !set_iso(isolate_address) first."); |
| return; |
| } |
| |
| let iso = cast(isolate_address, "v8::internal::Isolate"); |
| let hsd = iso.handle_scope_data_; |
| let hsimpl = iso.handle_scope_implementer_; |
| |
| // depth level |
| print(`Nested depth level: ${hsd.level}`); |
| |
| // count of handles |
| const ptr_size = pointer_size(); |
| let blocks = hsimpl.blocks_; |
| const block_size = 1022; // v8::internal::KB - 2 |
| const first_block = blocks.data_.address; |
| const last_block = (blocks.size_ == 0) |
| ? first_block |
| : first_block + ptr_size * (blocks.size_ - 1); |
| |
| const count = (blocks.size_ == 0) |
| ? 0 |
| : (blocks.size_ - 1) * block_size + |
| (hsd.next.address - poi(last_block))/ptr_size; |
| print(`Currently tracking ${count} local handles`); |
| |
| // print the handles |
| if (print_handles && count > 0) { |
| for (let block = first_block; block < last_block; |
| block += block_size * ptr_size) { |
| print(`Handles in block at ${hex(block)}`); |
| for (let i = 0; i < block_size; i++) { |
| const location = poi(block + i * ptr_size); |
| print(` ${hex(location)}->${hex(poi(location))}`); |
| } |
| } |
| |
| let location = poi(last_block); |
| print(`Handles in block at ${hex(last_block)}`); |
| for (let location = poi(last_block); location < hsd.next.address; |
| location += ptr_size) { |
| print(` ${hex(location)}->${hex(poi(location))}`); |
| } |
| } |
| |
| // where will the next handle allocate at? |
| const prefix = "Next handle's location will be"; |
| if (hsd.next.address < hsd.limit.address) { |
| print(`${prefix} at ${hex(hsd.next.address)}`); |
| } |
| else if (hsimpl.spare_) { |
| const location = hsimpl.spare_.address; |
| print(`${prefix} from the spare block at ${hex(location)}`); |
| } |
| else { |
| print(`${prefix} from a new block to be allocated`); |
| } |
| } |
| |
| /*----------------------------------------------------------------------------- |
| dp |
| -----------------------------------------------------------------------------*/ |
| function pad_right(addr) { |
| let addr_hex = hex(addr); |
| return `${addr_hex}${" ".repeat(pointer_size() * 2 + 2 - addr_hex.length)}`; |
| } |
| |
| // TODO irinayat: would be nice to identify handles and smi as well |
| function dp(addr, count = 10) { |
| if (isolate_address == 0) { |
| print(`To see where objects are located, run !set_iso.`); |
| } |
| |
| if (!Number.isSafeInteger(int(addr))) { |
| print(`${hex(addr)} doesn't look like a valid address`); |
| return; |
| } |
| |
| const ptr_size = tagged_size(); |
| let aligned_addr = addr - (addr % ptr_size); |
| let val = poim(aligned_addr); |
| let iter = 0; |
| while (val && iter < count) { |
| const map = is_map(val); |
| const obj = is_likely_object(val) && !map; |
| |
| const augm_map = map ? "map" : ""; |
| const augm_obj = obj ? "obj" : ""; |
| const augm_other = !map && !obj ? "val" : ""; |
| |
| let c = find_chunk(decomp(val)); |
| const augm_space = c ? ` in ${c.space}` : ""; |
| const augm = `${augm_map}${augm_obj}${augm_other}${augm_space}`; |
| |
| const full_ptr = using_ptr_compr ? |
| pad_right((map || obj) ? decomp(val) : val) : ""; |
| print(`${pad_right(aligned_addr)} ${pad_right(val)} ${full_ptr} ${augm}`); |
| |
| aligned_addr += ptr_size; |
| val = poim(aligned_addr); |
| iter++; |
| } |
| } |
| |
| /*----------------------------------------------------------------------------- |
| Remembered Sets |
| -----------------------------------------------------------------------------*/ |
| // set ids: 0 = OLD_TO_NEW, 1 = 0 = OLD_TO_OLD |
| function print_remembered_set(chunk_addr, set_id = 0) { |
| if (!chunk_addr) { |
| if (isolate_address == 0) { |
| print("Please call !set_iso(isolate_address) or provide chunk address."); |
| return; |
| } |
| |
| 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('lo', h.lo_space_.memory_chunk_list_.front_, chunks); |
| for (let c of chunks) { |
| try { |
| print_remembered_set(c.address); |
| } |
| catch (e) { |
| print(`failed to process chunk ${hex(c.address)} due to ${e.message}`); |
| } |
| } |
| return; |
| } |
| |
| print(`Remembered set in chunk ${hex(chunk_addr)}`); |
| let chunk = cast(chunk_addr, "v8::internal::MemoryChunk"); |
| |
| // chunk.slot_set_ is an array of SlotSet's. For standard pages there is 0 or |
| // 1 item in the array, but for large pages there will be more. |
| const page_size = 256 * 1024; |
| const sets_count = Math.floor((chunk.size_ + page_size - 1) / page_size); |
| let rs = chunk.slot_set_[set_id]; |
| if (rs.isNull) { |
| print(` <empty>`); |
| return; |
| } |
| if (rs[0].page_start_ != chunk_addr) { |
| print(`page_start_ [${hex(rs.page_start_)}] doesn't match chunk_addr!`); |
| return; |
| } |
| |
| const ptr_size = tagged_size(); |
| let count = 0; |
| for (let s = 0; s < sets_count; s++){ |
| const buckets_count = rs[s].buckets_.Count(); |
| for (let b = 0; b < buckets_count; b++) { |
| let bucket = rs[s].buckets_[b]; |
| if (bucket.isNull) continue; |
| // there are 32 cells in each bucket, cell's size is 32 bits |
| print(` bucket ${hex(bucket.address.asNumber())}:`); |
| const first_cell = bucket.address.asNumber(); |
| for (let c = 0; c < 32; c++) { |
| let cell = host.memory.readMemoryValues( |
| first_cell + c * 4, 1, 4 /*size to read*/)[0]; |
| if (cell == 0) continue; |
| let mask = 1; |
| for (let bit = 0; bit < 32; bit++){ |
| if (cell & mask) { |
| count++; |
| const slot_offset = (b * 32 * 32 + c * 32 + bit) * ptr_size; |
| const slot = rs[s].page_start_ + slot_offset; |
| print(` ${hex(slot)} -> ${hex(poim(slot))}`); |
| } |
| mask = mask << 1; |
| } |
| } |
| } |
| } |
| |
| if (count == 0) print(` <empty>`); |
| else print(` ${count} remembered pointers in chunk ${hex(chunk_addr)}`); |
| } |
| |
| |
| /*============================================================================= |
| 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_js_stack, "jst"), |
| |
| new host.functionAlias(set_isolate_address, "set_iso"), |
| new host.functionAlias(module_name, "set_module"), |
| new host.functionAlias(print_memory, "mem"), |
| new host.functionAlias(print_owning_space, "where"), |
| new host.functionAlias(print_handles_data, "handles"), |
| new host.functionAlias(print_remembered_set, "rs"), |
| |
| new host.functionAlias(print_object_prev, "jo_prev"), |
| new host.functionAlias(print_object_next, "jo_next"), |
| new host.functionAlias(print_objects_in_range, "jo_in_range"), |
| new host.functionAlias(print_objects_tree, "jot"), |
| |
| new host.functionAlias(dp, "dp"), |
| |
| new host.functionAlias(set_user_js_bp, "jsbp"), |
| ] |
| } |