4965a34e0f
The extensions require isolate address to be set but don't rely on calling any runtime functions, which makes them viable for post-mortem debugging, if the corresponding memory is included into the dump !set_iso(isolate_address) call this function before using !mem or other heap routines !mem or !mem(\"space1[ space2 ...]\") prints memory chunks from the 'space' owned by the heap in the isolate set by !set_iso; valid values for 'space' are: new, old, map, code, lo [large], nlo [newlarge], ro [readonly] if no 'space' specified prints memory chunks for all spaces, e.g. !mem(\"code\"), !mem(\"ro new old\") !where(address) prints name of the space and address of the MemoryChunk the 'address' is from, e.g. !where(0x235cb869f9) Output from !mem would look something like this: 0:000> !mem("old") Heap at 0x210652b8838 Im address: object area start - end (size) OldSpace (allocating at: 0x1703dae7a20): * 0x33d9a8c0000: 0x33d9a8c0138 - 0x33d9a8f1000 (0x31000) 0x1703dac0000: 0x1703dac0138 - 0x1703db00000 (0x40000) Change-Id: Iae1a217bbc5c5a88e2cf742db88ead9bb6fc904c Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1669744 Commit-Queue: Irina Yatsenko <irinayat@microsoft.com> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Reviewed-by: Sergiy Belozorov <sergiyb@chromium.org> Cr-Commit-Position: refs/heads/master@{#62316}
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"),
|
|
]
|
|
}
|