PR-URL: https://github.com/nodejs/node/pull/61898 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
814 lines
25 KiB
Text
814 lines
25 KiB
Text
# Copyright 2014 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.
|
|
|
|
# Print tagged object.
|
|
define job
|
|
call (void) _v8_internal_Print_Object((void*)($arg0))
|
|
end
|
|
document job
|
|
Print a v8 JavaScript object
|
|
Usage: job tagged_ptr
|
|
end
|
|
|
|
# Print content of V8 handles.
|
|
python
|
|
import gdb
|
|
import gdb.printing
|
|
import gdb.types
|
|
|
|
class PrintV8HandleCommand(gdb.Command):
|
|
"""Print content of a V8 object, accessed through a handle."""
|
|
|
|
def __init__(self, command_name):
|
|
self.command_name = command_name
|
|
super(PrintV8HandleCommand, self).__init__(command_name, gdb.COMMAND_DATA)
|
|
|
|
@staticmethod
|
|
def print_direct(value, from_tty):
|
|
CMD = "call (void) _v8_internal_Print_Object((v8::internal::Address*)({}))"
|
|
gdb.execute(CMD.format(value.format_string()), from_tty)
|
|
return True
|
|
|
|
@staticmethod
|
|
def print_indirect(value, from_tty):
|
|
CMD = "call (void) _v8_internal_Print_Object(*(v8::internal::Address**)({}))"
|
|
gdb.execute(CMD.format(value.format_string()), from_tty)
|
|
return True
|
|
|
|
def invoke(self, arg, from_tty):
|
|
argv = gdb.string_to_argv(arg)
|
|
if len(argv) != 1:
|
|
raise gdb.GdbError("{} takes exactly one argument.".format(
|
|
self.command_name))
|
|
value = gdb.parse_and_eval(argv[0])
|
|
if not self.print_handle(value, from_tty):
|
|
raise gdb.GdbError("{} cannot print a value of type {}".format(
|
|
self.command_name, value.type))
|
|
|
|
|
|
class PrintV8LocalCommand(PrintV8HandleCommand):
|
|
"""Print content of v8::(Maybe)?Local."""
|
|
|
|
def __init__(self):
|
|
super(PrintV8LocalCommand, self).__init__("print-v8-local")
|
|
|
|
def print_handle(self, value, from_tty):
|
|
if gdb.types.get_basic_type(value.type).code != gdb.TYPE_CODE_STRUCT:
|
|
return False
|
|
# After https://crrev.com/c/4335544, v8::MaybeLocal contains a local_.
|
|
if gdb.types.has_field(value.type, "local_"):
|
|
value = value["local_"]
|
|
# After https://crrev.com/c/4335544, v8::Local contains a location_.
|
|
if gdb.types.has_field(value.type, "location_"):
|
|
return self.print_indirect(value["location_"], from_tty)
|
|
# Before https://crrev.com/c/4335544, v8::Local contained a val_.
|
|
if gdb.types.has_field(value.type, "val_"):
|
|
return self.print_indirect(value["val_"], from_tty)
|
|
# With v8_enable_direct_handle=true, v8::Local contains a ptr_.
|
|
if gdb.types.has_field(value.type, "ptr_"):
|
|
return self.print_direct(value["ptr_"], from_tty)
|
|
# We don't know how to print this...
|
|
return False
|
|
|
|
class PrintV8InternalHandleCommand(PrintV8HandleCommand):
|
|
"""Print content of v8::internal::(Maybe)?(Direct|Indirect)?Handle."""
|
|
|
|
def __init__(self):
|
|
super(PrintV8InternalHandleCommand, self).__init__("print-v8-internal-handle")
|
|
|
|
def print_handle(self, value, from_tty):
|
|
if gdb.types.get_basic_type(value.type).code != gdb.TYPE_CODE_STRUCT:
|
|
return False
|
|
# Indirect handles contain a location_.
|
|
if gdb.types.has_field(value.type, "location_"):
|
|
return self.print_indirect(value["location_"], from_tty)
|
|
# With v8_enable_direct_handle=true, direct handles contain a obj_.
|
|
if gdb.types.has_field(value.type, "obj_"):
|
|
return self.print_direct(value["obj_"], from_tty)
|
|
# Without v8_enable_direct_handle=true, direct handles contain a handle_.
|
|
if gdb.types.has_field(value.type, "handle_"):
|
|
return self.print_handle(value["handle_"], from_tty)
|
|
# We don't know how to print this...
|
|
return False
|
|
|
|
PrintV8LocalCommand()
|
|
PrintV8InternalHandleCommand()
|
|
end
|
|
|
|
python
|
|
class TryAliasCommand(gdb.Command):
|
|
"""Create an alias, but don't error if it already exists."""
|
|
|
|
def __init__ (self):
|
|
super (TryAliasCommand, self).__init__ ("try-alias", gdb.COMMAND_FILES)
|
|
|
|
def invoke (self, arg, from_tty):
|
|
try:
|
|
gdb.execute('alias ' + arg)
|
|
except:
|
|
pass
|
|
|
|
TryAliasCommand()
|
|
end
|
|
|
|
try-alias jlh = print-v8-local
|
|
try-alias jl = print-v8-local
|
|
try-alias jh = print-v8-internal-handle
|
|
|
|
# Print Code objects containing given PC.
|
|
define jco
|
|
if $argc == 0
|
|
call (void) _v8_internal_Print_Code((void*)($pc))
|
|
else
|
|
call (void) _v8_internal_Print_Code((void*)($arg0))
|
|
end
|
|
end
|
|
document jco
|
|
Print a v8 Code object from an internal code address
|
|
Usage: jco pc
|
|
end
|
|
|
|
# Print JSDispatchEntry object in the JSDispatchTable with the given dispatch handle
|
|
define jdh
|
|
call (void) _v8_internal_Print_Dispatch_Handle((uint32_t)($arg0))
|
|
end
|
|
document jdh
|
|
Print JSDispatchEntry object in the JSDispatchTable with the given dispatch handle
|
|
Usage: jdh dispatch_handle
|
|
end
|
|
|
|
# Print Code objects assembly code surrounding given PC.
|
|
define jca
|
|
if $argc == 0
|
|
call (void) _v8_internal_Print_OnlyCode((void*)($pc), 30)
|
|
else
|
|
if $argc == 1
|
|
call (void) _v8_internal_Print_OnlyCode((void*)($arg0), 30)
|
|
else
|
|
call (void) _v8_internal_Print_OnlyCode((void*)($arg0), (size_t)($arg1))
|
|
end
|
|
end
|
|
end
|
|
document jca
|
|
Print a v8 Code object assembly code from an internal code address
|
|
Usage: jca pc
|
|
end
|
|
|
|
# Print TransitionTree.
|
|
define jtt
|
|
call (void) _v8_internal_Print_TransitionTree((void*)($arg0), false)
|
|
end
|
|
document jtt
|
|
Print the complete transition tree starting at the given v8 map.
|
|
Usage: jtt tagged_ptr
|
|
end
|
|
|
|
# Print TransitionTree starting from the root map.
|
|
define jttr
|
|
call (void) _v8_internal_Print_TransitionTree((void*)($arg0), true)
|
|
end
|
|
document jttr
|
|
Print the complete transition tree starting at the root map of the given v8 map.
|
|
Usage: jttr tagged_ptr
|
|
end
|
|
|
|
# Print JavaScript stack trace.
|
|
define jst
|
|
call (void) _v8_internal_Print_StackTrace()
|
|
end
|
|
document jst
|
|
Print the current JavaScript stack trace
|
|
Usage: jst
|
|
end
|
|
|
|
# Print JavaScript stack trace in concise format
|
|
define jstc
|
|
call (void) _v8_internal_Print_StackTraceConcise()
|
|
end
|
|
document jstc
|
|
Print the current JavaScript stack trace in concise format
|
|
Usage: jstc
|
|
end
|
|
|
|
# Print TurboFan graph node.
|
|
define pn
|
|
call _v8_internal_Node_Print((void*)($arg0))
|
|
end
|
|
document pn
|
|
Print a v8 TurboFan graph node
|
|
Usage: pn node_address
|
|
end
|
|
|
|
# Skip the JavaScript stack.
|
|
define jss
|
|
set $js_entry_sp=v8::internal::Isolate::Current()->thread_local_top()->js_entry_sp_
|
|
set $rbp=*(void**)$js_entry_sp
|
|
set $rsp=$js_entry_sp + 2*sizeof(void*)
|
|
set $pc=*(void**)($js_entry_sp+sizeof(void*))
|
|
end
|
|
document jss
|
|
Skip the jitted stack on x64 to where we entered JS last.
|
|
Usage: jss
|
|
end
|
|
|
|
# Print v8::FunctionCallbackInfo<T>& info.
|
|
define jfci
|
|
call _v8_internal_Print_FunctionCallbackInfo((void*)(&$arg0))
|
|
end
|
|
document jfci
|
|
Print v8::FunctionCallbackInfo<T>& info.
|
|
Usage: jfci info
|
|
end
|
|
|
|
# Print v8::PropertyCallbackInfo<T>& info.
|
|
define jpci
|
|
call _v8_internal_Print_PropertyCallbackInfo((void*)(&$arg0))
|
|
end
|
|
document jpci
|
|
Print v8::PropertyCallbackInfo<T>& info.
|
|
Usage: jpci info
|
|
end
|
|
|
|
# Print whether the object is marked, the mark-bit cell and index. The address
|
|
# of the cell is handy for reverse debugging to check when the object was
|
|
# marked/unmarked.
|
|
define jomb
|
|
call _v8_internal_Print_Object_MarkBit((void*)($arg0))
|
|
end
|
|
document jomb
|
|
Print whether the object is marked, the markbit cell and index.
|
|
Usage: jomb tagged_ptr
|
|
end
|
|
|
|
# Execute a simulator command.
|
|
python
|
|
import gdb
|
|
|
|
class SimCommand(gdb.Command):
|
|
"""Sim the current program."""
|
|
|
|
def __init__ (self):
|
|
super (SimCommand, self).__init__ ("sim", gdb.COMMAND_SUPPORT)
|
|
|
|
def invoke (self, arg, from_tty):
|
|
arg_bytes = arg.encode("utf-8") + b'\0'
|
|
arg_c_string = gdb.Value(arg_bytes, gdb.lookup_type('char').array(len(arg_bytes) - 1))
|
|
cmd_func = gdb.selected_frame().read_var("_v8_internal_Simulator_ExecDebugCommand")
|
|
cmd_func(arg_c_string)
|
|
|
|
SimCommand()
|
|
end
|
|
|
|
# Print stack trace with assertion scopes.
|
|
define bta
|
|
python
|
|
import re
|
|
frame_re = re.compile("^#(\d+)\s*(?:0x[a-f\d]+ in )?(.+) \(.+ at (.+)")
|
|
assert_re = re.compile("^\s*(\S+) = .+<v8::internal::Per\w+AssertScope<v8::internal::(\S*), (false|true)>")
|
|
btl = gdb.execute("backtrace full", to_string = True).splitlines()
|
|
for l in btl:
|
|
match = frame_re.match(l)
|
|
if match:
|
|
print("[%-2s] %-60s %-40s" % (match.group(1), match.group(2), match.group(3)))
|
|
match = assert_re.match(l)
|
|
if match:
|
|
if match.group(3) == "false":
|
|
prefix = "Disallow"
|
|
color = "\033[91m"
|
|
else:
|
|
prefix = "Allow"
|
|
color = "\033[92m"
|
|
print("%s -> %s %s (%s)\033[0m" % (color, prefix, match.group(2), match.group(1)))
|
|
end
|
|
end
|
|
document bta
|
|
Print stack trace with assertion scopes
|
|
Usage: bta
|
|
end
|
|
|
|
# Search for a pointer inside all valid pages.
|
|
define space_find
|
|
set $space = $arg0
|
|
set $current_page = $space->first_page()
|
|
while ($current_page != 0)
|
|
printf "# Searching in %p - %p\n", $current_page->area_start(), $current_page->area_end()-1
|
|
find $current_page->area_start(), $current_page->area_end()-1, $arg1
|
|
set $current_page = $current_page->next_page()
|
|
end
|
|
end
|
|
|
|
define heap_find
|
|
set $heap = v8::internal::Isolate::Current()->heap()
|
|
printf "# Searching for %p in old_space ===============================\n", $arg0
|
|
space_find $heap->old_space() ($arg0)
|
|
printf "# Searching for %p in map_space ===============================\n", $arg0
|
|
space_find $heap->map_space() $arg0
|
|
printf "# Searching for %p in code_space ===============================\n", $arg0
|
|
space_find $heap->code_space() $arg0
|
|
end
|
|
document heap_find
|
|
Find the location of a given address in V8 pages.
|
|
Usage: heap_find address
|
|
end
|
|
|
|
# The 'disassembly-flavor' command is only available on i386 and x84_64.
|
|
python
|
|
try:
|
|
gdb.execute("set disassembly-flavor intel")
|
|
except gdb.error:
|
|
pass
|
|
end
|
|
|
|
# Configuring ASLR may not be possible on some platforms, such running via the
|
|
# `rr` debugger.
|
|
python
|
|
try:
|
|
gdb.execute("set disable-randomization off")
|
|
except gdb.error:
|
|
pass
|
|
end
|
|
|
|
# Install a handler whenever the debugger stops due to a signal. It walks up the
|
|
# stack looking for V8_Dcheck / V8_Fatal / OS::DebugBreak frame and moves the
|
|
# frame to the one above it so it's immediately at the line of code that
|
|
# triggered the stop condition.
|
|
python
|
|
def v8_stop_handler(event):
|
|
frame = gdb.selected_frame()
|
|
select_frame = None
|
|
message = None
|
|
count = 0
|
|
# Limit stack scanning since the frames we look for are near the top anyway,
|
|
# and otherwise stack overflows can be very slow.
|
|
while frame is not None and count < 10:
|
|
count += 1
|
|
# If we are in a frame created by gdb (e.g. for `(gdb) call foo()`), gdb
|
|
# emits a dummy frame between its stack and the program's stack. Abort the
|
|
# walk if we see this frame.
|
|
if frame.type() == gdb.DUMMY_FRAME: break
|
|
|
|
frame_name = frame.name()
|
|
if frame_name is None:
|
|
frame = frame.older()
|
|
continue
|
|
if frame_name == 'V8_Dcheck' or frame_name.endswith('::DefaultDcheckHandler'):
|
|
try:
|
|
frame_message = gdb.lookup_symbol('message', frame.block())[0]
|
|
if frame_message:
|
|
message = frame_message.value(frame).string()
|
|
except:
|
|
pass
|
|
select_frame = frame.older()
|
|
if any([
|
|
frame_name.startswith('V8_Fatal'),
|
|
frame_name == 'v8::base::OS::DebugBreak',
|
|
frame_name == '__GI_abort',
|
|
frame_name == '__GI_raise',
|
|
frame_name == 'v8::base::OS::Abort',
|
|
frame_name == 'v8::Utils::ApiCheck',
|
|
]):
|
|
select_frame = frame.older()
|
|
frame = frame.older()
|
|
|
|
if select_frame is not None:
|
|
select_frame.select()
|
|
gdb.execute('frame')
|
|
if message:
|
|
print('==> DCHECK error: {}'.format(message))
|
|
|
|
gdb.events.stop.connect(v8_stop_handler)
|
|
end
|
|
|
|
# Code imported from chromium/src/tools/gdb/gdbinit
|
|
python
|
|
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
compile_dirs = set()
|
|
src_dir = None
|
|
|
|
def get_current_debug_file_directories():
|
|
dir = gdb.execute("show debug-file-directory", to_string=True)
|
|
dir = dir[
|
|
len('The directory where separate debug symbols are searched for is "'
|
|
):-len('".') - 1]
|
|
return set(dir.split(":"))
|
|
|
|
|
|
def add_debug_file_directory(dir):
|
|
# gdb has no function to add debug-file-directory, simulates that by using
|
|
# `show debug-file-directory` and `set debug-file-directory <directories>`.
|
|
current_dirs = get_current_debug_file_directories()
|
|
current_dirs.add(dir)
|
|
gdb.execute(
|
|
"set debug-file-directory %s" % ":".join(current_dirs), to_string=True)
|
|
|
|
|
|
def load_libcxx_pretty_printers(src_dir):
|
|
libcxx_pretty_printers = os.path.join(src_dir, 'third_party', 'libc++', 'src',
|
|
'utils', 'gdb', 'libcxx')
|
|
if not os.path.isdir(libcxx_pretty_printers):
|
|
return
|
|
sys.path.insert(1, libcxx_pretty_printers)
|
|
from printers import register_libcxx_printer_loader
|
|
register_libcxx_printer_loader()
|
|
|
|
|
|
def set_src_dir(compile_dir):
|
|
global src_dir
|
|
git = subprocess.Popen(
|
|
['git', '-C', compile_dir, 'rev-parse', '--show-toplevel'],
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
src_dir, _ = git.communicate()
|
|
if git.returncode:
|
|
return
|
|
if isinstance(src_dir, str):
|
|
src_dir = src_dir.rstrip()
|
|
else:
|
|
src_dir = src_dir.decode('utf-8').rstrip()
|
|
|
|
# If there's no LICENSE.v8 file in the repo root, we got the wrong git repo.
|
|
# The most common way to have this happen is to be in a git checkout of
|
|
# another project that vendors chromium in its own third_party directory. In
|
|
# that case, try falling back to the current working directory.
|
|
if not os.path.isfile(os.path.join(src_dir, 'LICENSE.v8')):
|
|
src_dir = os.path.abspath(os.getcwd())
|
|
|
|
load_libcxx_pretty_printers(src_dir)
|
|
|
|
|
|
def newobj_handler(event):
|
|
global compile_dirs
|
|
compile_dir = os.path.dirname(event.new_objfile.filename)
|
|
if not compile_dir:
|
|
return
|
|
if compile_dir in compile_dirs:
|
|
return
|
|
compile_dirs.add(compile_dir)
|
|
|
|
# Add source path
|
|
gdb.execute("dir %s" % compile_dir)
|
|
|
|
# Need to tell the location of .dwo files.
|
|
# https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
|
|
# https://crbug.com/603286#c35
|
|
add_debug_file_directory(compile_dir)
|
|
|
|
global src_dir
|
|
if not src_dir:
|
|
set_src_dir(compile_dir)
|
|
|
|
# Event hook for newly loaded objfiles.
|
|
# https://sourceware.org/gdb/onlinedocs/gdb/Events-In-Python.html
|
|
gdb.events.new_objfile.connect(newobj_handler)
|
|
|
|
gdb.execute("set environment V8_GDBINIT_SOURCED=1")
|
|
|
|
end
|
|
|
|
### CppGC helpers.
|
|
|
|
# Pretty printer for cppgc::Member.
|
|
python
|
|
|
|
import re
|
|
|
|
|
|
class CppGCMemberPrinter(object):
|
|
"""Print cppgc Member types."""
|
|
|
|
def __init__(self, val, category, pointee_type):
|
|
self.val = val
|
|
self.category = category
|
|
self.pointee_type = pointee_type
|
|
|
|
def to_string(self):
|
|
pointer = gdb.parse_and_eval(
|
|
"_cppgc_internal_Uncompress_Member((void*){})".format(
|
|
self.val.address))
|
|
return "{}Member<{}> pointing to {}".format(
|
|
'' if self.category is None else self.category, self.pointee_type,
|
|
pointer)
|
|
|
|
def display_hint(self):
|
|
return "{}Member<{}>".format('' if self.category is None else self.category,
|
|
self.pointee_type)
|
|
|
|
|
|
def cppgc_pretty_printers(val):
|
|
typename = val.type.name or val.type.tag or str(val.type)
|
|
regex = re.compile("^(cppgc|blink)::(Weak|Untraced)?Member<(.*)>$")
|
|
match = regex.match(typename)
|
|
if match is not None:
|
|
return CppGCMemberPrinter(
|
|
val, category=match.group(2), pointee_type=match.group(3))
|
|
|
|
# The member type might have been obscured by typedefs and template
|
|
# arguments. Fallback to using the underlying type.
|
|
real_type = val.type.strip_typedefs()
|
|
if (real_type.name is not None and
|
|
real_type.name.startswith('cppgc::internal::BasicMember')):
|
|
inner_type = real_type.template_argument(0)
|
|
return CppGCMemberPrinter(
|
|
val, category='Basic', pointee_type=inner_type.name)
|
|
|
|
return None
|
|
|
|
|
|
gdb.pretty_printers.append(cppgc_pretty_printers)
|
|
|
|
class TaggedPrintCommand(gdb.Command):
|
|
"""
|
|
The equivalent of the print command that deals with tagged objects
|
|
|
|
Usage: tp <expression>
|
|
Example: tp Isolate::Current()->context()->get(3)
|
|
"""
|
|
|
|
def __init__(self):
|
|
super(TaggedPrintCommand, self).__init__("tp", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION)
|
|
|
|
def invoke(self, arg, from_tty):
|
|
"""Called when the 'tp' command is invoked."""
|
|
try:
|
|
# Split the expression by '->' to handle chained calls.
|
|
parts = arg.split("->")
|
|
if not parts:
|
|
print("Error: No expression provided.")
|
|
return
|
|
|
|
if len(parts) == 1:
|
|
gdb.execute(f"print {parts[0]}")
|
|
else:
|
|
current_val = gdb.parse_and_eval(parts[0])
|
|
for part in parts[1:-1]:
|
|
current_val = self._evaluate_part(current_val, part, False)
|
|
if current_val is None: # Early exit if an evaluation fails.
|
|
return
|
|
|
|
self._evaluate_part(current_val, parts[-1], True)
|
|
|
|
except gdb.error as e:
|
|
print(f"Error: {e}")
|
|
|
|
def _evaluate_part(self, current_val, part, last):
|
|
"""Evaluates a single part of the expression (method call or member access)."""
|
|
|
|
storage = None
|
|
target = f"(({current_val.type}){current_val})"
|
|
|
|
type = current_val.type
|
|
if type.tag and type.tag.startswith("v8::internal::Tagged<"):
|
|
storage = gdb.parse_and_eval(f"malloc({type.sizeof})").cast(type.pointer())
|
|
command = f"(({storage.type}){storage})->ptr_ = {current_val['ptr_']}"
|
|
gdb.parse_and_eval(command)
|
|
target = f"(({storage.type}){storage})->ToRawPtr()"
|
|
|
|
# Check for a method call (ends with parentheses).
|
|
command = f"{target}->{part}"
|
|
result = None
|
|
if last:
|
|
gdb.execute(f"print {command}")
|
|
else:
|
|
result = gdb.parse_and_eval(command)
|
|
if storage:
|
|
gdb.parse_and_eval(f"free({storage})")
|
|
return result
|
|
|
|
TaggedPrintCommand()
|
|
|
|
|
|
static_roots_cache = None
|
|
def GetStaticRoot(ptr):
|
|
global static_roots_cache
|
|
if static_roots_cache is None:
|
|
static_roots_cache = {}
|
|
try:
|
|
for field in gdb.lookup_type("v8::internal::StaticReadOnlyRoot").fields():
|
|
value = gdb.parse_and_eval("v8::internal::StaticReadOnlyRoot::"+field.name)
|
|
if int(value) in static_roots_cache:
|
|
static_roots_cache[int(value)] += "," + field.name
|
|
else:
|
|
static_roots_cache[int(value)] = field.name
|
|
except Exception as e:
|
|
print("Warning, couldn't find static roots: ", e)
|
|
ptr = ptr & 0xFFFFFFFF
|
|
return static_roots_cache.get(ptr, None)
|
|
|
|
|
|
def InterpretTaggedPointer(pointer):
|
|
if (pointer & 0x1) == 0:
|
|
return f"Smi: {pointer >> 1}"
|
|
|
|
static_root = GetStaticRoot(pointer)
|
|
if static_root is not None:
|
|
return f"{static_root}"
|
|
|
|
return None
|
|
|
|
|
|
def OptimizedOutToNone(val):
|
|
if val is None:
|
|
return None
|
|
if val.is_optimized_out:
|
|
return None
|
|
return val
|
|
|
|
|
|
class TaggedPrinter(object):
|
|
"""Print Tagged<Foo> types."""
|
|
|
|
def __init__(self, val, pointee_type):
|
|
self.val = OptimizedOutToNone(val)
|
|
self.pointee_type = pointee_type.lstrip('v8::internal::')
|
|
self.ptr = self.val["ptr_"] if self.val is not None else None
|
|
|
|
def to_string(self):
|
|
if self.ptr is None:
|
|
return f"Tagged<{self.pointee_type}>{{<optimized out>}}"
|
|
pointer = int(self.ptr)
|
|
fmt = f"Tagged<{self.pointee_type}>{{0x{pointer:02x}}}"
|
|
if (label := InterpretTaggedPointer(pointer)) is not None:
|
|
fmt += f" ({label})"
|
|
return fmt
|
|
|
|
|
|
def tagged_pretty_printers(val):
|
|
# The member type might have been obscured by typedefs and template
|
|
# arguments. Fallback to using the underlying type.
|
|
real_type = val.type.strip_typedefs()
|
|
typename = real_type.name or real_type.tag or str(real_type)
|
|
regex = re.compile("^v8::internal::Tagged<(.*)>$")
|
|
match = regex.match(typename)
|
|
if match is not None:
|
|
return TaggedPrinter(val, pointee_type=match.group(1))
|
|
|
|
return None
|
|
|
|
|
|
gdb.pretty_printers.append(tagged_pretty_printers)
|
|
|
|
|
|
class HandlePrinter(object):
|
|
"""Print Handle<Foo> types."""
|
|
|
|
def __init__(self, val, pointee_type):
|
|
self.val = OptimizedOutToNone(val)
|
|
self.pointee_type = pointee_type.lstrip('v8::internal::')
|
|
self.location = OptimizedOutToNone(self.val["location_"] if self.val is not None else None)
|
|
self.ptr = self.location.dereference() if self.location is not None else None
|
|
|
|
def to_string(self):
|
|
if self.location is None:
|
|
return f"Handle<{self.pointee_type}>{{<optimized out>}}"
|
|
location = int(self.location)
|
|
fmt = f"Handle<{self.pointee_type}>{{0x{location:02x}}}: "
|
|
if self.ptr is None:
|
|
fmt += f"<optimized out>"
|
|
else:
|
|
pointer = None
|
|
try:
|
|
pointer = int(self.ptr)
|
|
except Exception as e:
|
|
fmt += f"[{e}]"
|
|
else:
|
|
fmt += f"0x{pointer:02x}"
|
|
if (label := InterpretTaggedPointer(pointer)) is not None:
|
|
fmt += f" ({label})"
|
|
|
|
return fmt
|
|
|
|
|
|
def handle_pretty_printers(val):
|
|
# The member type might have been obscured by typedefs and template
|
|
# arguments. Fallback to using the underlying type.
|
|
real_type = val.type.strip_typedefs()
|
|
typename = real_type.name or real_type.tag or str(real_type)
|
|
regex = re.compile("^v8::internal::(?:Indirect)?Handle<(.*)>$")
|
|
match = regex.match(typename)
|
|
if match is not None:
|
|
return HandlePrinter(val, pointee_type=match.group(1))
|
|
|
|
return None
|
|
|
|
|
|
gdb.pretty_printers.append(handle_pretty_printers)
|
|
|
|
|
|
class DirectHandlePrinter(object):
|
|
"""Print DirectHandle<Foo> types."""
|
|
|
|
def __init__(self, val, pointee_type):
|
|
self.val = val
|
|
self.pointee_type = pointee_type.lstrip('v8::internal::')
|
|
self.inner = None
|
|
if gdb.types.has_field(self.val.type, "handle_"):
|
|
self.inner = HandlePrinter(val['handle_'], pointee_type)
|
|
elif gdb.types.has_field(self.val.type, "obj_"):
|
|
self.inner = TaggedPrinter(val['obj_'], pointee_type)
|
|
else:
|
|
print("Warning: No recognised DirectHandle field")
|
|
|
|
def to_string(self):
|
|
if self.inner is not None:
|
|
return self.inner.to_string()
|
|
|
|
|
|
def direct_handle_pretty_printers(val):
|
|
# The member type might have been obscured by typedefs and template
|
|
# arguments. Fallback to using the underlying type.
|
|
real_type = val.type.strip_typedefs()
|
|
typename = real_type.name or real_type.tag or str(real_type)
|
|
regex = re.compile("^v8::internal::DirectHandle<(.*)>$")
|
|
match = regex.match(typename)
|
|
if match is not None:
|
|
if gdb.types.has_field(real_type, "handle_"):
|
|
handle = None
|
|
if not val.is_optimized_out:
|
|
handle = val['handle_']
|
|
return HandlePrinter(handle, match.group(1))
|
|
elif gdb.types.has_field(real_type, "obj_"):
|
|
obj = None
|
|
if not val.is_optimized_out:
|
|
obj = val['obj_']
|
|
return TaggedPrinter(obj, match.group(1))
|
|
|
|
return None
|
|
|
|
gdb.pretty_printers.append(direct_handle_pretty_printers)
|
|
|
|
end
|
|
|
|
python
|
|
|
|
# V8 JIT-compiled frame unwinder for x86-64.
|
|
# See https://sourceware.org/gdb/current/onlinedocs/gdb.html/Unwinding-Frames-in-Python.html
|
|
|
|
from gdb.unwinder import Unwinder, FrameId
|
|
|
|
class V8Unwinder(Unwinder):
|
|
"""Unwinder for V8 JIT-compiled frames by following the RBP chain on x86-64."""
|
|
|
|
PTR_SIZE = 8 # x86-64
|
|
|
|
@staticmethod
|
|
def read_u64(addr):
|
|
"""Return little-endian uint64 from inferior memory, or raise gdb.MemoryError."""
|
|
inferior = gdb.selected_inferior()
|
|
data = inferior.read_memory(addr, V8Unwinder.PTR_SIZE)
|
|
return int.from_bytes(data.tobytes(), byteorder="little", signed=False)
|
|
|
|
def __init__(self):
|
|
super(V8Unwinder, self).__init__("V8Unwinder")
|
|
self.enabled = True
|
|
|
|
def __call__(self, pending_frame):
|
|
try:
|
|
arch = pending_frame.architecture()
|
|
if arch.name() != "i386:x86-64":
|
|
return None # Don't use this unwinder if not on x86-64.
|
|
if pending_frame.name() is not None:
|
|
return None # gdb can already symbolicate this, likely not a JIT frame.
|
|
|
|
fp = int(pending_frame.read_register("rbp"))
|
|
|
|
if fp == 0 or (fp & 0x7) != 0: # Chain ends or misaligned.
|
|
return None
|
|
|
|
# Now we know we are likely in a V8 JIT-compiled frame.
|
|
# Stack layout:
|
|
# rbp+16 <-- caller's rsp before `call`
|
|
# rbp+8 [ return address (saved rip) ]
|
|
# rbp+0 [ saved rbp ] <-- current frame pointer
|
|
prev_rsp = fp + self.PTR_SIZE * 2
|
|
ret_addr = self.read_u64(fp + self.PTR_SIZE)
|
|
prev_rbp = self.read_u64(fp)
|
|
|
|
if ret_addr == 0 or prev_rbp == 0:
|
|
return None # Invalid frame.
|
|
|
|
# GDB needs a Frame ID to identify the frame.
|
|
# 1. A stack address that is stable across the lifetime of a frame.
|
|
# We use the previous frame's rsp.
|
|
# 2. A code address that is stable across the lifetime of a frame.
|
|
# Ideally, the function's start address. We don't have symbols here,
|
|
# so it's not easy to find out. Use the return address as an
|
|
# approximation.
|
|
# See https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/frame-id.h
|
|
frame_id = FrameId(prev_rsp, ret_addr)
|
|
unwind_info = pending_frame.create_unwind_info(frame_id)
|
|
unwind_info.add_saved_register("rsp", gdb.Value(prev_rsp))
|
|
unwind_info.add_saved_register("rip", gdb.Value(ret_addr))
|
|
unwind_info.add_saved_register("rbp", gdb.Value(prev_rbp))
|
|
return unwind_info
|
|
|
|
except Exception:
|
|
return None # Give up unwinding on errors.
|
|
|
|
gdb.unwinder.register_unwinder(None, V8Unwinder(), replace=True)
|
|
|
|
end
|