blob: fc1890c5a43d645d18da586c614ec8d30cf51071 [file] [log] [blame]
# Copyright 2022-2024 Free Software Foundation, Inc.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
# These are deprecated in 3.9, but required in older versions.
from typing import Mapping, Optional, Sequence
import gdb
from .events import exec_and_expect_stop, expect_process, expect_stop
from .server import (
DeferredRequest,
call_function_later,
capability,
request,
send_gdb,
send_gdb_with_response,
)
from .startup import DAPException, exec_and_log, in_dap_thread, in_gdb_thread
# A launch or attach promise that that will be fulfilled after a
# configurationDone request has been processed.
_launch_or_attach_promise = None
# A DeferredRequest that handles either a "launch" or "attach"
# request.
class _LaunchOrAttachDeferredRequest(DeferredRequest):
def __init__(self, callback):
self._callback = callback
global _launch_or_attach_promise
if _launch_or_attach_promise is not None:
raise DAPException("launch or attach already specified")
_launch_or_attach_promise = self
# Invoke the callback and return the result.
@in_dap_thread
def invoke(self):
return self._callback()
# Override this so we can clear the global when rescheduling.
@in_dap_thread
def reschedule(self):
global _launch_or_attach_promise
_launch_or_attach_promise = None
super().reschedule()
# A wrapper for the 'file' command that correctly quotes its argument.
@in_gdb_thread
def file_command(program):
# Handle whitespace, quotes, and backslashes here. Exactly what
# to quote depends on libiberty's buildargv and safe-ctype.
program = re.sub("[ \t\n\r\f\v\\\\'\"]", "\\\\\\g<0>", program)
exec_and_log("file " + program)
# Any parameters here are necessarily extensions -- DAP requires this
# from implementations. Any additions or changes here should be
# documented in the gdb manual.
@request("launch", on_dap_thread=True)
def launch(
*,
program: Optional[str] = None,
cwd: Optional[str] = None,
args: Sequence[str] = (),
env: Optional[Mapping[str, str]] = None,
stopAtBeginningOfMainSubprogram: bool = False,
stopOnEntry: bool = False,
**extra,
):
# Launch setup is handled here. This is done synchronously so
# that errors can be reported in a natural way.
@in_gdb_thread
def _setup_launch():
if cwd is not None:
exec_and_log("cd " + cwd)
if program is not None:
file_command(program)
inf = gdb.selected_inferior()
inf.arguments = args
if env is not None:
inf.clear_env()
for name, value in env.items():
inf.set_env(name, value)
# Actual launching done here. See below for more info.
@in_gdb_thread
def _do_launch():
expect_process("process")
if stopAtBeginningOfMainSubprogram:
cmd = "start"
elif stopOnEntry:
cmd = "starti"
else:
cmd = "run"
exec_and_expect_stop(cmd)
@in_dap_thread
def _launch_impl():
send_gdb_with_response(_setup_launch)
# We do not wait for the result here. It might be a little
# nicer if we did -- perhaps the various thread events would
# occur in a more logical sequence -- but if the inferior does
# not stop, then the launch response will not be seen either,
# which seems worse.
send_gdb(_do_launch)
# Launch response does not have a body.
return None
# The launch itself is deferred until the configurationDone
# request.
return _LaunchOrAttachDeferredRequest(_launch_impl)
@request("attach", on_dap_thread=True)
def attach(
*,
program: Optional[str] = None,
pid: Optional[int] = None,
target: Optional[str] = None,
**args,
):
# The actual attach is handled by this function.
@in_gdb_thread
def _do_attach():
if program is not None:
file_command(program)
if pid is not None:
cmd = "attach " + str(pid)
elif target is not None:
cmd = "target remote " + target
else:
raise DAPException("attach requires either 'pid' or 'target'")
expect_process("attach")
expect_stop("attach")
exec_and_log(cmd)
# Attach response does not have a body.
return None
@in_dap_thread
def _attach_impl():
return send_gdb_with_response(_do_attach)
# The attach itself is deferred until the configurationDone
# request.
return _LaunchOrAttachDeferredRequest(_attach_impl)
@capability("supportsConfigurationDoneRequest")
@request("configurationDone", on_dap_thread=True)
def config_done(**args):
# Handle the launch or attach.
global _launch_or_attach_promise
if _launch_or_attach_promise is None:
raise DAPException("launch or attach not specified")
# Resolve the launch or attach, but only after the
# configurationDone response has been sent.
call_function_later(_launch_or_attach_promise.reschedule)