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