Previously `v8 args` only shows arguments of the functions in the stack
frames. This patch makes GetStackFrame in the debug helper forward the
context local information stored in ScopeInfo in the returned
properties, so the plugins can recover the parameter names in `bt` and
allow `v8 inspect` to resolve by parameter names.
The previews show the arguments as passed at the call site. Only the
names captured in the closures can be recovered, the rest still gets
positional [N] names. For example:
function outer(a, b, c) {
function capture() {
return a + c; // captures a and c, not b
}
}
(gdb) bt
#6 ... [outer @ captured-args.js:9:15]
(this=0x..., a=<Smi: 1>, [1]=<Smi: 2>, c=<Smi: 3>)
(gdb) v8 args 6
#6 outer @ captured-args.js:9:15
this = 0x... <JSGlobalProxy>
a = <Smi: 1>
[1] = <Smi: 2>
c = <Smi: 3>
(gdb) v8 inspect c
<Smi: 3>
To implement this, we expose the ScopeInfo::VariableProperties bitfield
aliases for the helper.
In order to support builds without pointer compression/cages, this also
includes isolate pointer in HeapAddresses in order to fill the
read_only_space_first_page and old_space_first_page of a HeapAddress
from the isolate root table, since without pointer compression, the
helper cannot derive these pages by itself.
Change-Id: Ie12b1e4e159836f30071cb009aadb46117064865
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/8545535
Reviewed-by: Paolo Severini <paolosev@microsoft.com>
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Cr-Commit-Position: refs/heads/main@{#111626}
622 lines
22 KiB
Python
622 lines
22 KiB
Python
# Copyright 2026 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.
|
|
"""LLDB integration for the V8 debugger bridge."""
|
|
|
|
import os
|
|
import shlex
|
|
import struct
|
|
import traceback
|
|
|
|
import lldb
|
|
|
|
from v8dbg.commands import debug_helper_lib_warning
|
|
from v8dbg.commands import dispatch_v8_command
|
|
from v8dbg.hints import resolve_heap_hints
|
|
from v8dbg.shared_bridge import get_bridge
|
|
|
|
_VERBOSE = os.environ.get("V8_DEBUG_HELPER_VERBOSE", "") != ""
|
|
|
|
|
|
def _log(message):
|
|
if _VERBOSE:
|
|
print(f"v8dbg: {message}")
|
|
|
|
|
|
_DEFAULT_FRAME_FORMAT = ("frame #${frame.index}:{ ${frame.no-debug}${frame.pc}}"
|
|
"{ ${module.file.basename}{`${function.name-with-args}"
|
|
"{${frame.no-debug}${function.pc-offset}}}}"
|
|
"{ at ${line.file.basename}:${line.number}}"
|
|
"{${function.is-optimized} [opt]}")
|
|
|
|
|
|
def _make_read_memory(process):
|
|
"""Return a memory-read callback bound to one lldb process."""
|
|
|
|
def read_memory(address, byte_count):
|
|
error = lldb.SBError()
|
|
data = process.ReadMemory(address, byte_count, error)
|
|
if not error.Success():
|
|
raise RuntimeError(error.GetCString() or "unable to read memory")
|
|
return data
|
|
|
|
return read_memory
|
|
|
|
|
|
class _LldbResolver:
|
|
"""Resolves symbols, memory, and type offsets through LLDB."""
|
|
|
|
def __init__(self, target, process, thread=None):
|
|
self._target = target
|
|
self._process = process
|
|
# Thread-local lookups use `thread`, or the selected thread.
|
|
self._thread = thread
|
|
self._ptr_size = target.GetAddressByteSize() if target else 8
|
|
|
|
def read_pointer(self, addr):
|
|
data = self._read_bytes(addr, self._ptr_size)
|
|
if data is None:
|
|
return None
|
|
return int.from_bytes(data, "little")
|
|
|
|
def _read_bytes(self, addr, byte_count):
|
|
if not self._process:
|
|
return None
|
|
error = lldb.SBError()
|
|
data = self._process.ReadMemory(addr, byte_count, error)
|
|
if not error.Success() or not data or len(data) != byte_count:
|
|
return None
|
|
return data
|
|
|
|
def debug_symbol_address(self, name):
|
|
"""Resolve the address of the variable `name` using debug info.
|
|
|
|
For a TLS variable this is the address of the resolver thread's copy.
|
|
"""
|
|
if not self._target:
|
|
return None
|
|
# Scope the lookup to a frame so that TLS variables resolve against the
|
|
# resolver's thread.
|
|
frame = self._thread_frame()
|
|
if frame is None:
|
|
return None
|
|
try:
|
|
value = frame.GetValueForVariablePath(name)
|
|
except Exception:
|
|
return None
|
|
if not value or not value.IsValid():
|
|
return None
|
|
error = value.GetError()
|
|
if error and not error.Success():
|
|
return None
|
|
load_addr = value.GetLoadAddress()
|
|
if load_addr == lldb.LLDB_INVALID_ADDRESS:
|
|
return None
|
|
return int(load_addr)
|
|
|
|
def symbol_address(self, name):
|
|
"""Resolve the address of the variable `name` for the resolver's thread.
|
|
|
|
For a TLS variable this is the address of the resolver thread's copy.
|
|
"""
|
|
addr = self.debug_symbol_address(name)
|
|
if addr is not None:
|
|
return addr
|
|
if not self._target:
|
|
return None
|
|
|
|
# Without debug info, fall back to the minimal symbol tables.
|
|
try:
|
|
contexts = self._target.FindSymbols(name)
|
|
except Exception:
|
|
if _VERBOSE:
|
|
traceback.print_exc()
|
|
return None
|
|
|
|
if not contexts:
|
|
_log(f"no symbol table entry for {name}")
|
|
return None
|
|
|
|
for context in contexts:
|
|
symbol = context.GetSymbol()
|
|
if not symbol or not symbol.IsValid():
|
|
continue
|
|
start = symbol.GetStartAddress()
|
|
section = start.GetSection()
|
|
section_name = section.GetName() if section and section.IsValid() else ""
|
|
|
|
if section_name in (".tdata", ".tbss"):
|
|
addr = self._elf_tls_symbol_address(context.GetModule(),
|
|
start.GetFileAddress())
|
|
if addr:
|
|
return addr
|
|
_log(f"cannot resolve the ELF TLS address of {name}")
|
|
continue
|
|
if section_name == "__thread_vars":
|
|
addr = self._macho_tls_symbol_address(
|
|
context.GetModule(), start.GetLoadAddress(self._target))
|
|
if addr:
|
|
return addr
|
|
_log(f"cannot resolve the Mach-O TLS address of {name}")
|
|
continue
|
|
|
|
# Not a TLS symbol, the load address is the variable's address.
|
|
load_addr = start.GetLoadAddress(self._target)
|
|
if load_addr != lldb.LLDB_INVALID_ADDRESS:
|
|
return int(load_addr)
|
|
|
|
_log(f"cannot resolve the address of {name}")
|
|
return None
|
|
|
|
def _thread_frame(self):
|
|
if not self._process:
|
|
return None
|
|
thread = self._thread or self._process.GetSelectedThread()
|
|
# Registers such as the thread pointer are only available from frame 0.
|
|
frame = thread.GetFrameAtIndex(0) if thread else None
|
|
if not frame or not frame.IsValid():
|
|
return None
|
|
return frame
|
|
|
|
def _symbol_load_address(self, name, module_name=None):
|
|
# Scope the lookup to `module_name` when given to avoid conflicts.
|
|
source = self._target
|
|
if module_name:
|
|
try:
|
|
module = self._target.FindModule(lldb.SBFileSpec(module_name))
|
|
except Exception:
|
|
module = None
|
|
if module and module.IsValid():
|
|
source = module
|
|
try:
|
|
contexts = source.FindSymbols(name)
|
|
except Exception:
|
|
return None
|
|
if not contexts:
|
|
return None
|
|
start = contexts[0].GetSymbol().GetStartAddress()
|
|
addr = start.GetLoadAddress(self._target)
|
|
return None if addr == lldb.LLDB_INVALID_ADDRESS else int(addr)
|
|
|
|
def _elf_tls_symbol_address(self, module, tls_offset):
|
|
"""Compute the address of an ELF TLS symbol in the resolver's thread.
|
|
|
|
lldb does not resolve TLS minimal symbols itself, so derive the address
|
|
from the thread pointer register and the module's TLS segment layout.
|
|
This currently only works with the local-exec model on Linux x86_64 and
|
|
arm64.
|
|
"""
|
|
triple = (self._target.GetTriple() or "") if self._target else ""
|
|
if "linux" not in triple:
|
|
_log(f"ELF TLS symbols are only resolved on Linux, triple is {triple}")
|
|
return None
|
|
|
|
# A shared library's TLS block is at a dynamic offset, skip it
|
|
# to avoid computing a plausible-looking wrong address.
|
|
# TODO(joyee): if we want to support libv8.so, we need to walk the DTV.
|
|
exe_module = self._target.FindModule(self._target.GetExecutable())
|
|
if not exe_module or not exe_module.IsValid() or module != exe_module:
|
|
_log("TLS symbol is not in the main executable, skipping")
|
|
return None
|
|
frame = self._thread_frame()
|
|
if frame is None:
|
|
_log("no frame to read the thread pointer from")
|
|
return None
|
|
|
|
# Reconstruct the PT_TLS segment size and alignment from the sections.
|
|
sections = [module.FindSection(name) for name in (".tdata", ".tbss")]
|
|
sections = [s for s in sections if s and s.IsValid()]
|
|
if not sections:
|
|
_log("the main executable has no .tdata/.tbss sections")
|
|
return None
|
|
segment_start = min(s.GetFileAddress() for s in sections)
|
|
segment_end = max(s.GetFileAddress() + s.GetByteSize() for s in sections)
|
|
segment_size = segment_end - segment_start
|
|
|
|
# TLS symbol values are normally plain offsets within the TLS segment,
|
|
# but on some targets they are virtual addresses inside PT_TLS, so
|
|
# normalize them to offsets. See
|
|
# https://github.com/llvm/llvm-project/blob/9d2000c24/lldb/source/Plugins/DynamicLoader/POSIX-DYLD/DynamicLoaderPOSIXDYLD.cpp
|
|
if segment_start <= tls_offset < segment_end:
|
|
tls_offset -= segment_start
|
|
|
|
align = 1
|
|
for section in sections:
|
|
try:
|
|
align = max(align, int(section.GetAlignment()))
|
|
except Exception:
|
|
pass
|
|
|
|
def align_up(value, alignment):
|
|
return (value + alignment - 1) & ~(alignment - 1)
|
|
|
|
if triple.startswith("x86_64"):
|
|
thread_pointer = self._read_register(frame, ("fs_base", "fsbase"))
|
|
if not thread_pointer:
|
|
_log("cannot read the fs_base register")
|
|
return None
|
|
# For x86-64 TLS, the main module's TLS block sits immediately
|
|
# below the thread pointer.
|
|
return thread_pointer - align_up(segment_size, align) + tls_offset
|
|
|
|
if triple.startswith("aarch64"):
|
|
thread_pointer = self._read_register(frame, ("tpidr", "tpidr_el0"))
|
|
if not thread_pointer:
|
|
_log("cannot read the tpidr register")
|
|
return None
|
|
# For AArch64 TLS, the main module's TLS block sits above the
|
|
# 16-byte TCB the thread pointer points to.
|
|
return thread_pointer + align_up(16, align) + tls_offset
|
|
_log(f"unsupported architecture for TLS resolution: {triple}")
|
|
return None
|
|
|
|
# Scan window when calibrating field offsets inside `struct pthread`.
|
|
_PTHREAD_SCAN_BYTES = 0x400
|
|
# Backstop when walking the pthread list, in case of corrupted links.
|
|
_MAX_PTHREADS = 4096
|
|
|
|
def _macho_tls_symbol_address(self, module, descriptor_addr):
|
|
"""Compute the address of a Mach-O TLS symbol in the resolver's thread.
|
|
|
|
lldb does not resolve TLS minimal symbols itself, so derive the address
|
|
from the TSD area scanned from libpthread's global thread list and
|
|
the TLV descriptor. In a live process, fall back to calling thunk.
|
|
"""
|
|
if descriptor_addr == lldb.LLDB_INVALID_ADDRESS:
|
|
return None
|
|
raw = self._read_bytes(descriptor_addr, 24)
|
|
if raw is not None:
|
|
# Try parsing the TLV descriptor.
|
|
# See https://github.com/apple-oss-distributions/dyld/blob/e9da5ae/libdyld/ThreadLocalVariables.h
|
|
#
|
|
# The high dword of a legacy offset is zero for any real offset while
|
|
# initialContentSize is always nonzero, which disambiguates the two.
|
|
size = struct.unpack_from("<L", raw, 20)[0]
|
|
if size != 0:
|
|
key, offset = struct.unpack_from("<LL", raw, 8)
|
|
else:
|
|
_, key, offset = struct.unpack("<QQQ", raw)
|
|
# 768 is the likely maximum TSD slot count, see
|
|
# https://github.com/apple-oss-distributions/libpthread/blob/42d026d/src/types_internal.h
|
|
# Key 0 is pthread_self and can't be assigned to a TLV.
|
|
# An implausible key or offset means we likely failed to decode the
|
|
# descriptor, in which case we fall back to calling thunk.
|
|
if not 0 < key < 768 or (size and offset >= size):
|
|
return self._macho_tls_from_thunk_call(descriptor_addr)
|
|
|
|
tsd = self._macho_selected_pthread_tsd()
|
|
block = self.read_pointer(tsd + key * 8) if tsd is not None else None
|
|
if block:
|
|
return block + offset
|
|
|
|
# The thread never touched this image's TLS, so the variable still has
|
|
# its initial value. In this case, point at the image's TLS template.
|
|
if block == 0 and module and module.IsValid():
|
|
# The blocks are instantiated from a template laid out as
|
|
# `[__thread_data][__thread_bss]`, both mapped with the image.
|
|
addresses = [
|
|
subsection.GetLoadAddress(self._target)
|
|
for section in module.sections
|
|
for subsection in section
|
|
if subsection.GetName() in ("__thread_data", "__thread_bss")
|
|
]
|
|
base = min((a for a in addresses if a != lldb.LLDB_INVALID_ADDRESS),
|
|
default=None)
|
|
if base is not None:
|
|
return base + offset
|
|
|
|
# Fall back to calling the thunk in the inferior, which is only safe
|
|
# in a live process.
|
|
return self._macho_tls_from_thunk_call(descriptor_addr)
|
|
|
|
def _macho_selected_pthread_tsd(self):
|
|
"""Find the resolver thread's TSD area by scanning libpthread's global
|
|
thread list.
|
|
"""
|
|
# https://github.com/apple-oss-distributions/libpthread/blob/42d026d/src/types_internal.h
|
|
# https://github.com/apple-oss-distributions/libpthread/blob/42d026d/src/pthread.c
|
|
if not self._process:
|
|
return None
|
|
thread = self._process.GetSelectedThread()
|
|
if not thread or not thread.IsValid():
|
|
return None
|
|
tid = thread.GetThreadID()
|
|
head = self._symbol_load_address("__pthread_head",
|
|
"libsystem_pthread.dylib")
|
|
if head is None:
|
|
return None
|
|
first = self.read_pointer(head)
|
|
if not first:
|
|
return None
|
|
word_count = self._PTHREAD_SCAN_BYTES // 8
|
|
|
|
def read_words(addr):
|
|
raw = self._read_bytes(addr, self._PTHREAD_SCAN_BYTES)
|
|
return struct.unpack(f"<{word_count}Q", raw) if raw else None
|
|
|
|
first_words = read_words(first)
|
|
if first_words is None:
|
|
return None
|
|
# The first element's TAILQ prev pointer points back at the list head, so
|
|
# scanning for it recovers the link field offset.
|
|
prev_index = next(
|
|
(i for i, word in enumerate(first_words) if i > 0 and word == head),
|
|
None)
|
|
if prev_index is None:
|
|
return None
|
|
link_index = prev_index - 1
|
|
pthreads = [(first, first_words)]
|
|
node = first_words[link_index]
|
|
while node and len(pthreads) < self._MAX_PTHREADS:
|
|
words = read_words(node)
|
|
if words is None:
|
|
# If the core is truncated, the list may be incomplete.
|
|
break
|
|
pthreads.append((node, words))
|
|
node = words[link_index]
|
|
|
|
# We want most structs to agree so that e.g. a coincidental value in a
|
|
# struct caught mid-creation can be ignored.
|
|
majority = max(1, len(pthreads) // 2)
|
|
# Prefer the TSD offset from `pthread_layout_offsets` which contains
|
|
# layout description for debuggers, see
|
|
# https://github.com/llvm/llvm-project/blob/9d2000c24/lldb/source/Plugins/SystemRuntime/MacOSX/SystemRuntimeMacOSX.cpp
|
|
tsd_index = None
|
|
spi = self._symbol_load_address("pthread_layout_offsets",
|
|
"libsystem_pthread.dylib")
|
|
raw = self._read_bytes(spi, 8) if spi is not None else None
|
|
if raw is not None:
|
|
version, base, _, entry_size = struct.unpack("<HHHH", raw)
|
|
if (version >= 1 and entry_size == 8 and base and base % 8 == 0 and
|
|
base < self._PTHREAD_SCAN_BYTES):
|
|
tsd_index = base // 8
|
|
if tsd_index is None:
|
|
# The TSD area starts with a pthread_self() slot. Other pthread_t
|
|
# fields can also point to themselves in some structs, so take the
|
|
# field where the most structs agree.
|
|
counts = [
|
|
sum(words[i] == pthread
|
|
for pthread, words in pthreads)
|
|
for i in range(word_count)
|
|
]
|
|
tsd_index = max(range(word_count), key=counts.__getitem__)
|
|
if counts[tsd_index] < majority:
|
|
return None
|
|
|
|
# The thread id field is where the list's values line up with the ids
|
|
# lldb reports.
|
|
all_tids = {t.GetThreadID() for t in self._process}
|
|
for i in range(word_count):
|
|
matches = [pthread for pthread, words in pthreads if words[i] in all_tids]
|
|
if len(matches) < majority:
|
|
continue
|
|
selected = [pthread for pthread, words in pthreads if words[i] == tid]
|
|
if len(selected) == 1:
|
|
return selected[0] + tsd_index * 8
|
|
return None
|
|
|
|
def _macho_tls_from_thunk_call(self, descriptor_addr):
|
|
"""Call the descriptor's dyld thunk in the inferior, pinned to the
|
|
resolver's thread so the result belongs to it rather than whichever thread
|
|
lldb picks. This only works in live processes.
|
|
"""
|
|
frame = self._thread_frame()
|
|
if frame is None:
|
|
return None
|
|
if not self.read_pointer(descriptor_addr):
|
|
return None
|
|
# `thunk(&descriptor)` returns `&tls_storage` for the calling thread.
|
|
expr = (f"(unsigned long long)((void*(*)(void*))(*(void**)"
|
|
f"{descriptor_addr:#x}))((void*){descriptor_addr:#x})")
|
|
options = lldb.SBExpressionOptions()
|
|
options.SetTryAllThreads(False)
|
|
options.SetStopOthers(True)
|
|
options.SetIgnoreBreakpoints(True)
|
|
options.SetUnwindOnError(True)
|
|
try:
|
|
value = frame.EvaluateExpression(expr, options)
|
|
except Exception:
|
|
return None
|
|
if not value or not value.IsValid():
|
|
return None
|
|
error = value.GetError()
|
|
if error and not error.Success():
|
|
return None
|
|
return int(value.GetValueAsUnsigned(0)) or None
|
|
|
|
def _read_register(self, frame, names):
|
|
for name in names:
|
|
try:
|
|
register = frame.FindRegister(name)
|
|
except Exception:
|
|
continue
|
|
if register and register.IsValid():
|
|
value = register.GetValueAsUnsigned(0)
|
|
if value:
|
|
return value
|
|
return None
|
|
|
|
def field_offset(self, type_name, field_name):
|
|
"""Return the byte offset of `field_name` inside `type_name`."""
|
|
if not self._target:
|
|
return None
|
|
try:
|
|
sb_type = self._target.FindFirstType(type_name)
|
|
except Exception:
|
|
return None
|
|
if not sb_type or not sb_type.IsValid():
|
|
return None
|
|
for i in range(sb_type.GetNumberOfFields()):
|
|
field = sb_type.GetFieldAtIndex(i)
|
|
if field.GetName() == field_name:
|
|
return int(field.GetOffsetInBytes())
|
|
return None
|
|
|
|
|
|
def _evaluate_address_in_lldb(target, frame, text):
|
|
"""Evaluate a debugger expression as an integer address, or None on failure."""
|
|
if frame and frame.IsValid():
|
|
value = frame.EvaluateExpression(text)
|
|
elif target and target.IsValid():
|
|
value = target.EvaluateExpression(text)
|
|
else:
|
|
return None
|
|
if value is None or not value.IsValid():
|
|
return None
|
|
err = value.GetError()
|
|
if not err or not err.Success():
|
|
return None
|
|
# Sentinel distinguishes a real Smi 0 from a silent conversion failure.
|
|
fail_value = (1 << 64) - 1
|
|
try:
|
|
raw = value.GetValueAsUnsigned(fail_value)
|
|
except Exception:
|
|
return None
|
|
if raw == fail_value:
|
|
return None
|
|
return int(raw)
|
|
|
|
|
|
# LLDB formats frames one at a time. Cache the hints of the current stop
|
|
# and thread instead of resolving them for every frame.
|
|
_last_hints = (None, None)
|
|
|
|
|
|
def _heap_hints_for_frame(frame, target, process):
|
|
global _last_hints
|
|
thread = frame.GetThread()
|
|
key = (process.GetUniqueID(), thread.GetThreadID(), process.GetStopID())
|
|
if _last_hints[0] != key:
|
|
resolver = _LldbResolver(target, process, thread)
|
|
_last_hints = (key, resolve_heap_hints(resolver))
|
|
return _last_hints[1]
|
|
|
|
|
|
def frame_annotation(frame, _unused):
|
|
"""Return the V8 JS suffix for one LLDB frame, or an empty string."""
|
|
try:
|
|
function_name = frame.GetFunctionName()
|
|
if not function_name:
|
|
symbol = frame.GetSymbol()
|
|
if symbol and symbol.IsValid():
|
|
function_name = symbol.GetName() or ""
|
|
if function_name and "Builtin" not in function_name:
|
|
return ""
|
|
|
|
process = frame.GetThread().GetProcess()
|
|
target = process.GetTarget()
|
|
read_memory = _make_read_memory(process)
|
|
|
|
fp = frame.GetFP()
|
|
if fp == lldb.LLDB_INVALID_ADDRESS:
|
|
return ""
|
|
bridge = get_bridge(target.GetAddressByteSize())
|
|
hints = _heap_hints_for_frame(frame, target, process)
|
|
return bridge.frame_suffix(fp, read_memory, hints)
|
|
except Exception:
|
|
if _VERBOSE:
|
|
traceback.print_exc()
|
|
return ""
|
|
|
|
|
|
def cmd_v8(debugger, command, result, _internal_dict):
|
|
"""V8 debug helpers.
|
|
Syntax: v8 <subcommand> [options]
|
|
|
|
Available subcommands:
|
|
|
|
- inspect -- Inspect the heap object at <addr> with the V8 inspector.
|
|
|
|
v8 inspect <addr>|this|[N]|<param-name> [--type T] [--depth N] [--array-length N]
|
|
|
|
* this / [N] / <param-name>: inspect the receiver or an argument of
|
|
the selected JS frame instead of a raw address.
|
|
* --type T: treat the object as if it has type T (e.g. `v8::internal::JSArray`).
|
|
* --depth N: limit recursive inspection depth to N (default: 1).
|
|
* --array-length N: for arrays, inspect up to N elements (default: 16).
|
|
|
|
- args -- Print the receiver and the arguments of a JS frame.
|
|
|
|
v8 args [frame#]
|
|
|
|
* frame#: the frame to show (default: the selected frame).
|
|
|
|
- isolate -- Print the current Isolate address of the selected thread.
|
|
|
|
v8 isolate
|
|
|
|
- source -- Print the source span of the function a JS frame is running.
|
|
|
|
v8 source [frame#] [--max-lines N]
|
|
|
|
* frame#: the frame to show (default: the selected frame).
|
|
* --max-lines N: cap the number of span lines shown (default: 10).
|
|
|
|
The span covers the whole function; the exact executing line is not
|
|
recovered.
|
|
"""
|
|
try:
|
|
argv = shlex.split(command or "")
|
|
except ValueError:
|
|
argv = (command or "").split()
|
|
target = debugger.GetSelectedTarget()
|
|
process = target.GetProcess() if target else None
|
|
thread = None
|
|
frame = None
|
|
if process:
|
|
thread = process.GetSelectedThread()
|
|
if thread:
|
|
frame = thread.GetSelectedFrame()
|
|
|
|
if not process or not process.IsValid():
|
|
result.SetError("v8: no running process / loaded core to inspect")
|
|
return
|
|
|
|
bridge = get_bridge(target.GetAddressByteSize())
|
|
read_memory = _make_read_memory(process)
|
|
|
|
def eval_address(text):
|
|
return _evaluate_address_in_lldb(target, frame, text)
|
|
|
|
def frame_fp(index):
|
|
if not thread or not thread.IsValid():
|
|
return None
|
|
if index is None:
|
|
selected = thread.GetSelectedFrame()
|
|
elif index < thread.GetNumFrames():
|
|
selected = thread.GetFrameAtIndex(index)
|
|
else:
|
|
return None
|
|
if not selected or not selected.IsValid():
|
|
return None
|
|
fp = selected.GetFP()
|
|
if not fp or fp == lldb.LLDB_INVALID_ADDRESS:
|
|
return None
|
|
return (selected.GetFrameID(), fp)
|
|
|
|
ok, text = dispatch_v8_command(
|
|
bridge,
|
|
argv,
|
|
read_memory=read_memory,
|
|
eval_address=eval_address,
|
|
resolver=_LldbResolver(target, process),
|
|
verbose=_VERBOSE,
|
|
frame_fp=frame_fp,
|
|
)
|
|
if not ok:
|
|
result.SetError(text)
|
|
elif text:
|
|
result.AppendMessage(text.rstrip("\n"))
|
|
|
|
|
|
def __lldb_init_module(debugger, _internal_dict):
|
|
"""Hook the LLDB frame format and register the `v8` command."""
|
|
warning = debug_helper_lib_warning()
|
|
if warning is not None:
|
|
print(warning)
|
|
return
|
|
callback = f"${{script.frame:{__name__}.frame_annotation}}"
|
|
debugger.HandleCommand(
|
|
f"settings set frame-format '{_DEFAULT_FRAME_FORMAT}{callback}\\n'")
|
|
debugger.HandleCommand(f"command script add -f {__name__}.cmd_v8 v8")
|