// 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 pointers"); print(" 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 (begin user's script)"); 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_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`) } /*============================================================================= 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_user_js_bp, "jsbp"), ] }