blob: d0bc5803a121579a9b119a47c4e16faa5d2aa8e5 [file] [edit]
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import argparse
import sys
from typing import Any, get_args
from cli.commands.base import BaseCommand
from shared.protocol.watch_request import COMMAND_NAME, WatchAccessType
class Command(BaseCommand):
COMMAND_NAME = COMMAND_NAME
ALIASES = [
"watchpoint",
"data-breakpoint",
"setDataBreakpoints",
"set-data-breakpoints",
"set_data_breakpoints",
]
@staticmethod
def register_cli(subparsers: Any) -> None:
watch_parser = subparsers.add_parser(
Command.COMMAND_NAME,
aliases=Command.ALIASES,
help="Set or delete a data breakpoint (watchpoint)",
description=(
"Set or delete a hardware watchpoint on a variable or expression. "
"Passing -t/--thread-id and -a/--access-type (and optionally "
"--frame-index) is required when setting a watchpoint. To watch a "
"memory address, pass a typed dereference expression "
"(e.g. '*(uint32_t*)0x1234'). The expression is evaluated once "
"and the watchpoint stays on that fixed address for the life of "
"the process; a watchpoint on a local variable keeps firing after "
"its function returns and the stack slot is reused. Remove it "
"with 'watch -d <data_id>'; 'get-state' lists each active "
"watchpoint's data_id and expression."
),
)
watch_parser.add_argument(
"-t",
"--thread-id",
type=int,
default=None,
help="Thread ID whose stack frame and process should be used (required when setting a watchpoint)",
)
watch_parser.add_argument(
"--frame-index",
type=int,
default=0,
help="Frame index within the stacktrace (defaults to 0)",
)
watch_parser.add_argument(
"-a",
"--access-type",
choices=list(get_args(WatchAccessType)),
default=None,
help="Access type to break on ('write' or 'readWrite'; required when setting a watchpoint)",
)
watch_parser.add_argument(
"-c",
"--condition",
type=str,
default=None,
help="Optional conditional expression for the watchpoint",
)
watch_parser.add_argument(
"--hit-condition",
type=str,
default=None,
help="Optional hit count condition for the watchpoint",
)
watch_parser.add_argument(
"-d",
"--delete",
action="store_true",
help="Delete the watchpoint matching the given data_id instead of adding it",
)
watch_parser.add_argument(
"--clear",
action="store_true",
help="Clear all active watchpoints",
)
watch_parser.add_argument(
"name",
nargs="*",
default=[],
help=(
"Variable or expression to watch; with -d/--delete, the "
"data_id of the watchpoint to remove"
),
)
@staticmethod
async def execute(args: argparse.Namespace) -> int | None:
if isinstance(args.name, list):
args.name = " ".join(args.name).strip()
if args.clear:
return None
if args.delete:
if not args.name:
print(
"Error: data_id is required when deleting a watchpoint.",
file=sys.stderr,
)
return 2
return None
if not args.name:
print(
"Error: expression is required unless --clear is specified.",
file=sys.stderr,
)
return 2
missing = []
if args.thread_id is None:
missing.append("-t/--thread-id")
if args.access_type is None:
missing.append("-a/--access-type")
if missing:
print(
f"Error: {' and '.join(missing)} required when setting a watchpoint.",
file=sys.stderr,
)
return 2
return None