eb66765125
Both LO_SPACE and NEW_LO_SPACE use the basic page management system of LargeObjectSpace, but implement different AllocateRaw methods (with the NEW_LO_SPACE version shadowing the LO_SPACE version). To clean this up, and allow other future LargeObjectSpace implementations (in particular, an off-thread variant), refactored the current LargeObjectSpace into a base class, and make both LargeObjectSpace (renamed to OldLargeObjectSpace) and NewLargeObjectSpace extend this class. Bug: chromium:1011762 Change-Id: I41b45b97f2611611dcfde677213131396df03a5e Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1876824 Commit-Queue: Leszek Swirski <leszeks@chromium.org> Auto-Submit: Leszek Swirski <leszeks@chromium.org> Reviewed-by: Peter Marshall <petermarshall@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Cr-Commit-Position: refs/heads/master@{#64560}
892 lines
31 KiB
JavaScript
892 lines
31 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() {
|
|
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"),
|
|
]
|
|
}
|