// Copyright 2019 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /*============================================================================= This is a convenience script for debugging with WinDbg (akin to gdbinit) It can be loaded into WinDbg with: .scriptload full_path\windbg.js To printout the help message below into the debugger's command window: !help =============================================================================*/ function help() { print("--------------------------------------------------------------------"); print(" LIVE debugging only"); print("--------------------------------------------------------------------"); print(" !jlh(\"local_handle_var_name\")"); print(" prints object held by the handle"); print(" e.g. !jlh(\"key\") or !jlh(\"this->receiver_\")"); print(" !job(address_or_taggedint)"); print(" prints object at the address, e.g. !job(0x235cb869f9)"); print(" !jobs(start_address, count)"); print(" prints 'count' objects from a continuous range of Object"); print(" pointers, e.g. !jobs(0x5f7270, 42)"); print(" !jst() or !jst"); print(" prints javascript stack (output goes into the console)"); print(" !jsbp() or !jsbp"); print(" sets bp in v8::internal::Execution::Call"); print(""); print("--------------------------------------------------------------------"); print(" Managed heap"); print("--------------------------------------------------------------------"); print(" !set_iso(isolate_address)"); print(" call this function before using !mem or other heap routines"); print(" !mem or !mem(\"space1[ space2 ...]\")"); print(" prints memory chunks from the 'space' owned by the heap in the"); print(" isolate set by !set_iso; valid values for 'space' are:"); print(" new, old, map, code, lo [large], nlo [newlarge], ro [readonly]"); print(" if no 'space' specified prints memory chunks for all spaces,"); print(" e.g. !mem(\"code\"), !mem(\"ro new old\")"); print(" !where(address)"); print(" prints name of the space and address of the MemoryChunk the"); print(" 'address' is from, e.g. !where(0x235cb869f9)"); print(""); print("--------------------------------------------------------------------"); print(" To run any function from this script (live or postmortem):"); print(""); print(" dx @$scriptContents.function_name(args)"); print(" e.g. dx @$scriptContents.pointer_size()"); print(" e.g. dx @$scriptContents.module_name(\"v8_for_test\")"); print("--------------------------------------------------------------------"); } /*============================================================================= Output =============================================================================*/ function print(s) { host.diagnostics.debugLog(s + "\n"); } function print_filtered(obj, filter) { for (let line of obj) { if (!filter || line.indexOf(filter) != -1) { print(line); } } } function inspect(s) { for (var k of Reflect.ownKeys(s)) { print(k + " => " + Reflect.get(s, k)); } } /*============================================================================= Utils (postmortem and live) =============================================================================*/ function cast(address, type_name) { return host.createTypedObject(address, module_name(), type_name); } // Failed to figure out how to get pointer size from the debugger's data model, // so we parse it out from sizeof(void*) output. function pointer_size() { let ctl = host.namespace.Debugger.Utility.Control; let sizeof = ctl.ExecuteCommand("?? sizeof(void*)"); let output = ""; for (output of sizeof) {} // unsigned int64 8 return parseInt(output.trim().split(" ").pop()); } function poi(address) { try { // readMemoryValues throws if cannot read from 'address'. return host.memory.readMemoryValues(address, 1, pointer_size())[0]; } catch (e){} } function get_register(name) { return host.namespace.Debugger.State.DebuggerVariables.curthread .Registers.User[name]; } // In debug builds v8 code is compiled into v8.dll, and in release builds // the code is compiled directly into the executable. If you are debugging some // other embedder, invoke module_name explicitly from the debugger and provide // the module name to use. const known_exes = ["d8", "unittests", "mksnapshot", "chrome", "chromium"]; let module_name_cache; function module_name(use_this_module) { if (use_this_module) { module_name_cache = use_this_module; } if (!module_name_cache) { let v8 = host.namespace.Debugger.State.DebuggerVariables.curprocess .Modules.Where( function(m) { return m.Name.indexOf("\\v8.dll") !== -1; }); if (v8) { module_name_cache = "v8"; } else { for (let exe_name in known_exes) { let exe = host.namespace.Debugger.State.DebuggerVariables.curprocess .Modules.Where( function(m) { return m.Name.indexOf(`\\${exe_name}.exe`) !== -1; }); if (exe) { module_name_cache = exe_name; break; } } } } return module_name_cache; }; function make_call(fn) { // .call resets current frame to the top one, so have to manually remember // and restore it after making the call. let curframe = host.namespace.Debugger.State.DebuggerVariables.curframe; let ctl = host.namespace.Debugger.Utility.Control; let output = ctl.ExecuteCommand(`.call ${fn};g`); curframe.SwitchTo(); return output; } // Skips the meta output about the .call invocation. function make_call_and_print_return(fn) { let output = make_call(fn); let print_line = false; for (let line of output) { if (print_line) { print(line); break; } if (line.includes(".call returns")) { print_line = true; } } } /*============================================================================= Wrappers around V8's printing functions and other utils for live-debugging =============================================================================*/ /*----------------------------------------------------------------------------- 'address' should be an int (so in hex must include '0x' prefix). -----------------------------------------------------------------------------*/ function print_object(address) { let output = make_call(`_v8_internal_Print_Object(${address})`); // skip the first few lines with meta info of .call command let skip_line = true; for (let line of output) { if (!skip_line) { print(line); continue; } if (line.includes("deadlocks and corruption of the debuggee")) { skip_line = false; } } } /*----------------------------------------------------------------------------- 'handle_to_object' should be a name of a Handle which can be a local variable or it can be a member variable like "this->receiver_". -----------------------------------------------------------------------------*/ function print_object_from_handle(handle_to_object) { let handle = host.evaluateExpression(handle_to_object); let location = handle.location_; let pobj = poi(location.address); print_object(pobj); } /*----------------------------------------------------------------------------- 'start_address' should be an int (so in hex must include '0x' prefix), it can point at any continuous memory that contains Object pointers. -----------------------------------------------------------------------------*/ function print_objects_array(start_address, count) { let ctl = host.namespace.Debugger.Utility.Control; let psize = pointer_size(); let addr_int = start_address; for (let i = 0; i < count; i++) { const addr_hex = `0x${addr_int.toString(16)}`; // TODO: Tried using createPointerObject but it throws unknown exception // from ChakraCore. Why? //let obj = host.createPointerObject(addr_hex, module, "void*"); let output = ctl.ExecuteCommand(`dp ${addr_hex} l1`); let item = ""; for (item of output) {} // 005f7270 34604101 let deref = `0x${item.split(" ").pop()}`; print(`${addr_hex} -> ${deref}`); print_object(deref); addr_int += psize; } } function print_js_stack() { make_call("_v8_internal_Print_StackTrace()"); } function set_user_js_bp() { let ctl = host.namespace.Debugger.Utility.Control; ctl.ExecuteCommand(`bp ${module_name()}!v8::internal::Execution::Call`) } /*============================================================================= Managed heap related functions (live and post-mortem debugging) =============================================================================*/ let isolate_address = 0; function set_isolate_address(addr) { isolate_address = addr; } /*----------------------------------------------------------------------------- Memory in each Space is organized into a linked list of memory chunks -----------------------------------------------------------------------------*/ const NEVER_EVACUATE = 1 << 7; // see src\heap\spaces.h function print_memory_chunk_list(space_type, front, top, age_mark) { let alloc_pos = top ? ` (allocating at: ${top})` : ""; let age_mark_pos = age_mark ? ` (age_mark at: ${top})` : ""; print(`${space_type}${alloc_pos}${age_mark_pos}:`); if (front.isNull) { print("\n"); return; } let cur = front; while (!cur.isNull) { let imm = cur.flags_ & NEVER_EVACUATE ? "*" : " "; let addr = `0x${cur.address.toString(16)}`; let area = `0x${cur.area_start_.toString(16)} - 0x${cur.area_end_.toString(16)}`; let dt = `dt ${addr} ${module_name()}!v8::internal::MemoryChunk`; print(`${imm} ${addr}:\t ${area} (0x${cur.size_.toString(16)}) : ${dt}`); cur = cur.list_node_.next_; } print(""); } const space_tags = ['old', 'new_to', 'new_from', 'ro', 'map', 'code', 'lo', 'nlo']; function get_chunks_space(space_tag, front, chunks) { let cur = front; while (!cur.isNull) { chunks.push({ 'address':cur.address, 'area_start_':cur.area_start_, 'area_end_':cur.area_end_, 'space':space_tag}); cur = cur.list_node_.next_; } } function get_chunks() { let iso = cast(isolate_address, "v8::internal::Isolate"); let h = iso.heap_; let chunks = []; get_chunks_space('old', h.old_space_.memory_chunk_list_.front_, chunks); get_chunks_space('new_to', h.new_space_.to_space_.memory_chunk_list_.front_, chunks); get_chunks_space('new_from', h.new_space_.from_space_.memory_chunk_list_.front_, chunks); get_chunks_space('ro', h.read_only_space_.memory_chunk_list_.front_, chunks); get_chunks_space('map', h.map_space_.memory_chunk_list_.front_, chunks); get_chunks_space('code', h.code_space_.memory_chunk_list_.front_, chunks); get_chunks_space('lo', h.lo_space_.memory_chunk_list_.front_, chunks); get_chunks_space('nlo', h.new_lo_space_.memory_chunk_list_.front_, chunks); return chunks; } function find_chunk(address) { // if 'address' is greater than Number.MAX_SAFE_INTEGER, comparison ops on it // throw "Error: 64 bit value loses precision on conversion to number" try { let chunks = get_chunks(isolate_address); for (let c of chunks) { let chunk = cast(c.address, "v8::internal::MemoryChunk"); if (address >= chunk.area_start_ && address < chunk.area_end_) { return c; } } } catch (e) { } return undefined; } /*----------------------------------------------------------------------------- Print memory chunks from spaces in the current Heap 'isolate_address' should be an int (so in hex must include '0x' prefix). 'space': space separated string containing "all", "old", "new", "map", "code", "ro [readonly]", "lo [large]", "nlo [newlarge]" -----------------------------------------------------------------------------*/ function print_memory(space = "all") { if (isolate_address == 0) { print("Please call !set_iso(isolate_address) first."); return; } let iso = cast(isolate_address, "v8::internal::Isolate"); let h = iso.heap_; print(`Heap at ${h.targetLocation}`); let st = space.toLowerCase().split(" "); print("Im address:\t object area start - end (size)"); if (st.includes("all") || st.includes("old")) { print_memory_chunk_list("OldSpace", h.old_space_.memory_chunk_list_.front_, h.old_space_.allocation_info_.top_); } if (st.includes("all") || st.includes("new")) { // new space doesn't use the chunk list from its base class but from // the to/from semi-spaces it points to print_memory_chunk_list("NewSpace_To", h.new_space_.to_space_.memory_chunk_list_.front_, h.new_space_.allocation_info_.top_, h.new_space_.to_space_.age_mark_); print_memory_chunk_list("NewSpace_From", h.new_space_.from_space_.memory_chunk_list_.front_); } if (st.includes("all") || st.includes("map")) { print_memory_chunk_list("MapSpace", h.map_space_.memory_chunk_list_.front_, h.map_space_.allocation_info_.top_); } if (st.includes("all") || st.includes("code")) { print_memory_chunk_list("CodeSpace", h.code_space_.memory_chunk_list_.front_, h.code_space_.allocation_info_.top_); } if (st.includes("all") || st.includes("large") || st.includes("lo")) { print_memory_chunk_list("LargeObjectSpace", h.lo_space_.memory_chunk_list_.front_); } if (st.includes("all") || st.includes("newlarge") || st.includes("nlo")) { print_memory_chunk_list("NewLargeObjectSpace", h.new_lo_space_.memory_chunk_list_.front_); } if (st.includes("all") || st.includes("readonly") || st.includes("ro")) { print_memory_chunk_list("ReadOnlySpace", h.read_only_space_.memory_chunk_list_.front_); } } /*----------------------------------------------------------------------------- 'isolate_address' and 'address' should be ints (so in hex must include '0x' prefix). -----------------------------------------------------------------------------*/ function print_owning_space(address) { if (isolate_address == 0) { print("Please call !set_iso(isolate_address) first."); return; } let c = find_chunk(address); let addr = `0x${address.toString(16)}`; if (c) { print(`${addr} is in ${c.space} (chunk: 0x${c.address.toString(16)})`); } else { print(`Address ${addr} is not in managed heap`); } } /*============================================================================= Initialize short aliased names for the most common commands =============================================================================*/ function initializeScript() { return [ new host.functionAlias(help, "help"), new host.functionAlias(print_object_from_handle, "jlh"), new host.functionAlias(print_object, "job"), new host.functionAlias(print_objects_array, "jobs"), new host.functionAlias(print_js_stack, "jst"), new host.functionAlias(set_isolate_address, "set_iso"), new host.functionAlias(print_memory, "mem"), new host.functionAlias(print_owning_space, "where"), new host.functionAlias(set_user_js_bp, "jsbp"), ] }