c3ac338bb9
Add a simple unwinder for gdb which, on x64, walks frame pointers whenever there is no source information available. Ideally we would only do this for V8 PCs but this appears hard to do in an Unwinder without messing with gdb's internal assumptions. Change-Id: Iba1e62a3768340ee912e81d691237c1920a8ae91 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3608628 Reviewed-by: Camillo Bruni <cbruni@chromium.org> Commit-Queue: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/main@{#80204}
314 lines
8.6 KiB
Plaintext
314 lines
8.6 KiB
Plaintext
# 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::internal::Handle.
|
|
define jh
|
|
call (void) _v8_internal_Print_Object(*((v8::internal::Object**)($arg0).location_))
|
|
end
|
|
document jh
|
|
Print content of a v8::internal::Handle
|
|
Usage: jh internal_handle
|
|
end
|
|
|
|
# Print content of v8::Local handle.
|
|
define jlh
|
|
call (void) _v8_internal_Print_Object(*((v8::internal::Object**)($arg0).val_))
|
|
end
|
|
document jlh
|
|
Print content of a v8::Local handle
|
|
Usage: jlh local_handle
|
|
end
|
|
|
|
# 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 TransitionTree.
|
|
define jtt
|
|
call (void) _v8_internal_Print_TransitionTree((void*)($arg0))
|
|
end
|
|
document jtt
|
|
Print the complete transition tree of the given v8 Map.
|
|
Usage: jtt 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 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
|
|
|
|
# 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_c_string = gdb.Value(arg)
|
|
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` debuggger.
|
|
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 < 7:
|
|
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
|
|
|
|
if frame.name() == 'V8_Dcheck':
|
|
frame_message = gdb.lookup_symbol('message', frame.block())[0]
|
|
if frame_message:
|
|
message = frame_message.value(frame).string()
|
|
select_frame = frame.older()
|
|
break
|
|
if frame.name() is not None and frame.name().startswith('V8_Fatal'):
|
|
select_frame = frame.older()
|
|
if frame.name() == 'v8::base::OS::DebugBreak':
|
|
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()
|
|
|
|
|
|
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 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)
|
|
|
|
# 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
|
|
|
|
# Add a simple unwinder which, on x64, walks frame pointers when there
|
|
# is no source information available.
|
|
python
|
|
|
|
from gdb.unwinder import Unwinder
|
|
|
|
class V8UnwinderFrameId(object):
|
|
def __init__(self, sp, pc):
|
|
self.sp = sp
|
|
self.pc = pc
|
|
|
|
class V8Unwinder(Unwinder):
|
|
def __init__(self):
|
|
super(V8Unwinder, self).__init__("V8Unwinder")
|
|
self.enabled = True
|
|
|
|
def __call__(self, pending_frame):
|
|
try:
|
|
# Only supported on x64.
|
|
if gdb.selected_inferior().architecture().name() != "i386:x86-64":
|
|
return None
|
|
|
|
pc = pending_frame.read_register("rip")
|
|
sym_and_line = gdb.current_progspace().find_pc_line(int(pc))
|
|
|
|
if sym_and_line.symtab is not None:
|
|
return None
|
|
fp = pending_frame.read_register("rbp").reinterpret_cast(
|
|
gdb.lookup_type("void").pointer().pointer())
|
|
|
|
next_sp = fp
|
|
next_fp = fp.dereference()
|
|
next_pc = (fp+1).dereference()
|
|
|
|
frame_info = V8UnwinderFrameId(next_sp, next_pc)
|
|
|
|
# create_unwind_info seems to sometimes have issues accessing
|
|
# the frame_info if it's not first accessed in Python.
|
|
_lol_gdb_workaround = frame_info.pc + 1
|
|
|
|
unwind_info = pending_frame.create_unwind_info(frame_info)
|
|
unwind_info.add_saved_register("rsp", next_sp)
|
|
unwind_info.add_saved_register("rip", next_pc)
|
|
unwind_info.add_saved_register("rbp", next_fp)
|
|
return unwind_info
|
|
except Exception as e:
|
|
return None
|
|
|
|
gdb.unwinder.register_unwinder(None, V8Unwinder(), replace=True)
|
|
|
|
end
|