diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py
index 91d75c81610..f6e1e95c238 100644
--- a/gdb/python/lib/gdb/dap/events.py
+++ b/gdb/python/lib/gdb/dap/events.py
@@ -275,6 +275,19 @@ def _on_inferior_call(event):
             _expected_pause = False
             send_event("stopped", obj)
 
+@in_gdb_thread
+def send_corefile_stop_event():
+    global inferior_running
+    inferior_running = False
+
+    obj = {
+        "threadId": gdb.selected_thread().global_num,
+        "allThreadsStopped": True,
+        "reason": "attach",
+    }
+    global _expected_pause
+    _expected_pause = False
+    send_event("stopped", obj)
 
 gdb.events.stop.connect(_on_stop)
 gdb.events.exited.connect(_on_exit)
diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py
index 6fde3396ee9..6b0cb1d5861 100644
--- a/gdb/python/lib/gdb/dap/launch.py
+++ b/gdb/python/lib/gdb/dap/launch.py
@@ -20,7 +20,7 @@ from typing import Mapping, Optional, Sequence
 
 import gdb
 
-from .events import exec_and_expect_stop, expect_process, expect_stop
+from .events import exec_and_expect_stop, expect_process, expect_stop, send_corefile_stop_event
 from .server import (
     DeferredRequest,
     call_function_later,
@@ -136,6 +136,7 @@ def attach(
     pid: Optional[int] = None,
     target: Optional[str] = None,
     adaSourceCharset: Optional[str] = None,
+    coreFile: Optional[str] = None,
     **args,
 ):
     # The actual attach is handled by this function.
@@ -149,11 +150,20 @@ def attach(
             cmd = "attach " + str(pid)
         elif target is not None:
             cmd = "target remote " + target
+        elif coreFile is not None:
+            cmd = "core-file " + coreFile
         else:
             raise DAPException("attach requires either 'pid' or 'target'")
         expect_process("attach")
         expect_stop("attach")
         exec_and_log(cmd)
+
+        # Report a stop event when connecting to a core file.
+        # Currently GDB doesn't trigger a stop event when opening a
+        # new core file, so we need to emit one now.
+        if coreFile is not None:
+            send_corefile_stop_event()
+
         # Attach response does not have a body.
         return None
 
diff --git a/gdb/python/lib/gdb/dap/server.py b/gdb/python/lib/gdb/dap/server.py
index 01c190c797e..e7d08ce662c 100644
--- a/gdb/python/lib/gdb/dap/server.py
+++ b/gdb/python/lib/gdb/dap/server.py
@@ -622,7 +622,7 @@ def terminate(**args):
 @in_gdb_thread
 def _disconnect_or_kill(terminate: Optional[bool]):
     inf = gdb.selected_inferior()
-    if inf.connection is None:
+    if inf.connection is None or inf.corefile is not None:
         # Nothing to do here.
         return
     if terminate is None:
diff --git a/gdb/testsuite/gdb.dap/corefile.c b/gdb/testsuite/gdb.dap/corefile.c
new file mode 100644
index 00000000000..ce1d43b5e51
--- /dev/null
+++ b/gdb/testsuite/gdb.dap/corefile.c
@@ -0,0 +1,45 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2026 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/>.  */
+
+#include <stdlib.h>
+
+int global_var = 0;
+
+void
+baz (void)
+{
+  abort ();
+}
+
+void
+bar (void)
+{
+  baz ();
+}
+
+void
+foo (void)
+{
+  bar ();
+}
+
+int
+main (void)
+{
+  foo ();
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.dap/corefile.exp b/gdb/testsuite/gdb.dap/corefile.exp
new file mode 100644
index 00000000000..83772f7f511
--- /dev/null
+++ b/gdb/testsuite/gdb.dap/corefile.exp
@@ -0,0 +1,92 @@
+# Copyright 2023-2026 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/>.
+
+# Test using "attach" in DAP for opening a core file.
+
+require allow_dap_tests
+
+load_lib dap-support.exp
+
+standard_testfile
+
+if {[build_executable ${testfile}.exp $testfile] == -1} {
+    return
+}
+
+set corefile [core_find $binfile {}]
+if {$corefile == ""} {
+    untested "unable to create or find corefile"
+    return
+}
+
+# Test that attaching to a core file works at all.
+set attach_id [dap_corefile $corefile $binfile]
+
+dap_check_request_and_response "configurationDone" configurationDone
+
+dap_check_response "attach response" attach $attach_id
+
+dap_wait_for_event_and_check "stopped" stopped \
+    "body reason" attach
+
+# Try 'continue', this should fail.
+set obj [dap_request_and_response continue \
+	     {o threadId [i 1]}]
+set response [lindex $obj 0]
+gdb_assert { [dict get $response success] == "false" } \
+    "continue with core file target"
+
+# Get a backtrace from the core file.
+set bt [lindex [dap_check_request_and_response "backtrace" stackTrace \
+		    {o threadId [i 1]}] 0]
+set frame_id [dict get [lindex [dict get $bt body stackFrames] 0] id]
+
+# Get all scopes for frame 0.  Search through scopes to find the
+# register scope.
+set scopes [dap_check_request_and_response "get scopes" scopes \
+		[format {o frameId [i %d]} $frame_id]]
+set scopes [dict get [lindex $scopes 0] body scopes]
+set reg_scope ""
+foreach s $scopes {
+    if {[dict get $s name] == "Registers"} {
+	set reg_scope $s
+    }
+}
+gdb_assert { $reg_scope ne "" } "found register scope"
+
+# Read all the registers from the register scope.
+set num [dict get $reg_scope variablesReference]
+lassign [dap_check_request_and_response "fetch all registers" \
+	     "variables" \
+	     [format {o variablesReference [i %d] count [i %d]} $num\
+		  [dict get $reg_scope namedVariables]]] \
+    val events
+
+set obj [dap_request_and_response setExpression \
+	     {o expression [s global_var] value [s 23]}]
+set response [lindex $obj 0]
+gdb_assert { [dict get $response success] == "false" } \
+    "set global variable fails"
+
+# The call to dap_shutdown will check the log file for exceptions.
+# The problem is that the attempt to write to a global variable above
+# will trigger an exception, which is recorded in the log file, which
+# then causes the log file check to fail.
+#
+# We really need a better way to handle this.
+proc empty_dap_check_log_file {} {}
+with_override dap_check_log_file empty_dap_check_log_file {
+    dap_shutdown true
+}
diff --git a/gdb/testsuite/lib/dap-support.exp b/gdb/testsuite/lib/dap-support.exp
index 54d9178648f..926d5891b0b 100644
--- a/gdb/testsuite/lib/dap-support.exp
+++ b/gdb/testsuite/lib/dap-support.exp
@@ -369,6 +369,22 @@ proc dap_attach {pid {prog ""}} {
     return [dap_send_request attach $args]
 }
 
+# Start gdb, send a DAP initialize request, and then an attach request
+# specifying COREFILE as the core file to attach too.  Returns the
+# empty string on failure, or the attach request sequence ID.
+proc dap_corefile {corefile {prog ""}} {
+    if {[dap_initialize "startup - initialize"] == ""} {
+	return ""
+    }
+
+    set args [format {o coreFile [s %s]} $corefile]
+    if {$prog != ""} {
+	append args [format { program [s %s]} $prog]
+    }
+
+    return [dap_send_request attach $args]
+}
+
 # Start gdb, send a DAP initialize request, and then an attach request
 # specifying TARGET as the remote target.  Returns the empty string on
 # failure, or the attach request sequence ID.
