Files
Joyee Cheung 8bda32a987 [tools] recover parameter names from ScopeInfo in debug helper plugins
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}
2026-11-04 08:52:16 -08:00

220 lines
8.0 KiB
Makefile

PYTHON ?= python3
GDB ?= gdb
LLDB ?= lldb
# Default to the most common build output directories generated by
# gm.py for convenience, but allow overriding for custom setups.
ifeq ($(shell uname -s),Darwin)
OUT_DIR ?= ../../../out/arm64.release
DEBUG_HELPER_LIB ?= $(OUT_DIR)/libv8_debug_helper.dylib
else
OUT_DIR ?= ../../../out/x64.release
DEBUG_HELPER_LIB ?= $(OUT_DIR)/libv8_debug_helper.so
endif
D8 ?= $(dir $(DEBUG_HELPER_LIB))d8
CORRUPTION_BIN ?= $(dir $(DEBUG_HELPER_LIB))corruption_harness
TEST_DIR ?= ./test
FIXTURES_DIR ?= $(TEST_DIR)/fixtures
BACKTRACE_SCRIPT ?= $(FIXTURES_DIR)/throw.js
INSPECT_SCRIPT ?= $(FIXTURES_DIR)/inspect-fixture.js
ARGS_MISMATCH_SCRIPT ?= $(FIXTURES_DIR)/args-mismatch.js
CAPTURED_ARGS_SCRIPT ?= $(FIXTURES_DIR)/captured-args.js
CORRUPTION_SCRIPT ?= $(FIXTURES_DIR)/invalid-script-source.js
# d8 enables --sandbox-prohibit-insecure-mode on macOS/Linux, which makes
# sandbox initialization abort with a fatal OOM when launched under a debugger
# that disables ASLR by default.
D8_ARGS ?= --no-sandbox-prohibit-insecure-mode
GDBINIT ?= ../../gdbinit
GDB_PLUGIN ?= ./gdb_plugin.py
LLDB_PLUGIN ?= ./lldb_plugin.py
TEST_PACKAGE ?= test
CORE_DIR ?= $(OUT_DIR)/debug_helper.cores
BACKTRACE_CORE ?= $(CORE_DIR)/throw.core
INSPECT_CORE ?= $(CORE_DIR)/inspect-fixture.core
ARGS_MISMATCH_CORE ?= $(CORE_DIR)/args-mismatch.core
CAPTURED_ARGS_CORE ?= $(CORE_DIR)/captured-args.core
CORRUPTION_CORE ?= $(CORE_DIR)/$(basename $(notdir $(CORRUPTION_SCRIPT))).core
CORRUPTION_CORE_CASES := invalid-script-source invalid-shared-function-info corrupted-map
CORRUPTION_CORES := $(foreach case,$(CORRUPTION_CORE_CASES),$(CORE_DIR)/$(case).core)
# Without pointer compression d8 remaps the embedded builtins to a second,
# file-backed mapping of the binary at a high address. Those pages are clean and
# file-backed, so the default coredump_filter (0x33) leaves them out of the core
# (they are recorded only as an NT_FILE note), and lldb cannot read them back to
# unwind the JS frames running in those builtins. Adding bit 2 (file-backed
# private, 0x3f = 0x33 | file-backed) allows gdb to capture those
# pages. The value is read when the core is written, so setting it on the
# stopped inferior after `run` and before `generate-core-file` is enough.
define generate_core_gdb
mkdir -p "$(dir $(3))"
rm -f "$(3)"
V8_DEBUG_HELPER_LIB_PATH="$(abspath $(DEBUG_HELPER_LIB))" \
$(GDB) -nx -q -batch \
-iex 'set debuginfod enabled off' \
-ex 'file "$(1)"' \
-ex 'run $(2)' \
-ex 'python open("/proc/%d/coredump_filter" % gdb.selected_inferior().pid, "w").write("0x3f")' \
-ex 'generate-core-file $(3)' \
-ex 'quit'
endef
define generate_core_lldb
mkdir -p "$(dir $(3))"
rm -f "$(3)"
V8_DEBUG_HELPER_LIB_PATH="$(abspath $(DEBUG_HELPER_LIB))" \
$(LLDB) -b \
-O 'target create "$(1)"' \
-O 'settings set -- target.run-args $(2)' \
-O 'run' \
-k 'process save-core --style full "$(3)"' \
-k 'quit'
endef
ifeq ($(shell uname -s),Darwin)
define generate_core
$(call generate_core_lldb,$(1),$(2),$(3))
endef
else
define generate_core
$(call generate_core_gdb,$(1),$(2),$(3))
endef
endif
define run_gdb_live
V8_DEBUG_HELPER_LIB_PATH="$(abspath $(DEBUG_HELPER_LIB))" \
$(GDB) -nx -q -batch \
-iex 'set debuginfod enabled off' \
-iex 'source $(abspath $(GDBINIT))' \
-iex 'source $(abspath $(GDB_PLUGIN))' \
-ex 'file "$(1)"' \
-ex 'run $(2)' \
-ex 'bt' \
-ex 'quit'
endef
define run_lldb_live
V8_DEBUG_HELPER_LIB_PATH="$(abspath $(DEBUG_HELPER_LIB))" \
$(LLDB) -b \
-O 'command script import "$(abspath $(LLDB_PLUGIN))"' \
-O 'target create "$(1)"' \
-O 'settings set -- target.run-args $(2)' \
-O 'run' \
-k 'bt' \
-k 'quit'
endef
define run_gdb_core
V8_DEBUG_HELPER_LIB_PATH="$(abspath $(DEBUG_HELPER_LIB))" \
$(GDB) -nx -q -batch \
-iex 'set debuginfod enabled off' \
-iex 'source $(abspath $(GDBINIT))' \
-iex 'source $(abspath $(GDB_PLUGIN))' \
-ex 'file "$(1)"' \
-ex 'core-file $(2)' \
-ex 'bt' \
-ex 'quit'
endef
define run_lldb_core
V8_DEBUG_HELPER_LIB_PATH="$(abspath $(DEBUG_HELPER_LIB))" \
$(LLDB) -b \
-O 'command script import "$(abspath $(LLDB_PLUGIN))"' \
-O 'target create --core "$(2)" "$(1)"' \
-O 'bt' \
-O 'quit'
endef
# Runs one unittest spec ($(1) = module or module.Class) with the full environment.
define run_test
GDB='$(GDB)' \
GDB_PLUGIN='$(abspath $(GDB_PLUGIN))' \
GDBINIT='$(abspath $(GDBINIT))' \
LLDB='$(LLDB)' \
LLDB_PLUGIN='$(abspath $(LLDB_PLUGIN))' \
D8='$(abspath $(D8))' \
D8_ARGS='$(D8_ARGS)' \
CORRUPTION_BIN='$(abspath $(CORRUPTION_BIN))' \
DEBUG_HELPER_LIB='$(abspath $(DEBUG_HELPER_LIB))' \
CORE_DIR='$(abspath $(CORE_DIR))' \
$(PYTHON) -m unittest -v $(TEST_PACKAGE).$(1)
endef
.PHONY: run-live-gdb run-live-backtrace-gdb run-live-corruption-gdb run-live-lldb run-live-backtrace-lldb run-live-corruption-lldb run-live run-core-gdb run-core-backtrace-gdb run-core-corruption-gdb run-core-lldb run-core-backtrace-lldb run-core-corruption-lldb run-core run prepare-cores clean-cores clean-pyc test-live test-core test test-gdb test-lldb
$(BACKTRACE_CORE): $(BACKTRACE_SCRIPT) $(D8)
$(call generate_core,$(abspath $(D8)),$(D8_ARGS) --abort-on-uncaught-exception "$(abspath $(BACKTRACE_SCRIPT))",$(abspath $@))
$(INSPECT_CORE): $(INSPECT_SCRIPT) $(D8)
$(call generate_core,$(abspath $(D8)),$(D8_ARGS) --abort-on-uncaught-exception "$(abspath $(INSPECT_SCRIPT))",$(abspath $@))
$(ARGS_MISMATCH_CORE): $(ARGS_MISMATCH_SCRIPT) $(D8)
$(call generate_core,$(abspath $(D8)),$(D8_ARGS) --abort-on-uncaught-exception "$(abspath $(ARGS_MISMATCH_SCRIPT))",$(abspath $@))
$(CAPTURED_ARGS_CORE): $(CAPTURED_ARGS_SCRIPT) $(D8)
$(call generate_core,$(abspath $(D8)),$(D8_ARGS) --abort-on-uncaught-exception "$(abspath $(CAPTURED_ARGS_SCRIPT))",$(abspath $@))
# The corruption cores depend on the harness binary too, so they get
# regenerated whenever the harness changes.
$(CORE_DIR)/%.core: $(FIXTURES_DIR)/%.js $(CORRUPTION_BIN)
$(call generate_core,$(abspath $(CORRUPTION_BIN)),"$(abspath $(FIXTURES_DIR))/$*.js",$(abspath $@))
run-live-gdb: run-live-backtrace-gdb run-live-corruption-gdb
run-live-backtrace-gdb:
$(call run_gdb_live,$(abspath $(D8)),$(D8_ARGS) --abort-on-uncaught-exception "$(abspath $(BACKTRACE_SCRIPT))")
run-live-lldb: run-live-backtrace-lldb run-live-corruption-lldb
run-live-backtrace-lldb:
$(call run_lldb_live,$(abspath $(D8)),$(D8_ARGS) --abort-on-uncaught-exception "$(abspath $(BACKTRACE_SCRIPT))")
run-live-corruption-gdb:
$(call run_gdb_live,$(abspath $(CORRUPTION_BIN)),"$(abspath $(CORRUPTION_SCRIPT))")
run-live-corruption-lldb:
$(call run_lldb_live,$(abspath $(CORRUPTION_BIN)),"$(abspath $(CORRUPTION_SCRIPT))")
run-core-gdb: run-core-backtrace-gdb run-core-corruption-gdb
run-core-backtrace-gdb: $(BACKTRACE_CORE)
$(call run_gdb_core,$(abspath $(D8)),$(abspath $(BACKTRACE_CORE)))
run-core-corruption-gdb: $(CORRUPTION_CORE)
$(call run_gdb_core,$(abspath $(CORRUPTION_BIN)),$(abspath $(CORRUPTION_CORE)))
run-core-lldb: run-core-backtrace-lldb run-core-corruption-lldb
run-core-backtrace-lldb: $(BACKTRACE_CORE)
$(call run_lldb_core,$(abspath $(D8)),$(abspath $(BACKTRACE_CORE)))
run-core-corruption-lldb: $(CORRUPTION_CORE)
$(call run_lldb_core,$(abspath $(CORRUPTION_BIN)),$(abspath $(CORRUPTION_CORE)))
run-core: run-core-gdb run-core-lldb
prepare-cores: $(BACKTRACE_CORE) $(INSPECT_CORE) $(ARGS_MISMATCH_CORE) $(CAPTURED_ARGS_CORE) $(CORRUPTION_CORES)
clean-cores:
rm -rf "$(CORE_DIR)"
clean-pyc:
find . -type d -name __pycache__ -prune -exec rm -rf {} +
.SECONDEXPANSION:
# Run any unittest spec directly. The target name is the spec, e.g.
# make test_gdb_live
# make test_gdb_live.GdbBacktraceTest
# make test_gdb_core.GdbInspectCoreTest
# Specs for the coredump tests (name contains "core") depend on prepared cores.
test_%: $$(if $$(findstring core,$$*),prepare-cores)
$(call run_test,$@)
# Convenience helpers for common test groups
test-live: test_gdb_live test_lldb_live
test-core: test_gdb_core test_lldb_core
test-gdb: test_gdb_live test_gdb_core
test-lldb: test_lldb_live test_lldb_core
test: test-live test-core