Repository: go-delve/delve
Pull request #2367 (2021-03-04 04:41:33)

service/dap: Support for replay and core modes
This PR aims to add support for rr replay and core actions from the DAP layer. This basically encloses the following:

## New launch modes: replay and core

The following modes are added:

- **replay**: Replays an rr trace, allowing backwards flows (reverse continue and stepback). Requires a `traceDirPath` property on `launch.json` pointing to a valid rr trace directory. 
Equivalent to `dlv replay <tracedir>` command.
- **core**: Replays a core dump file, showing its callstack and the file matching the callsite. Requires a `coreFilePath` property on `launch.json` pointing to a valid coredump file.
Equivalent to `dlv core <exe> <corefile>` command.

## Dependencies

To achieve this the following additional changes were made:

- Implement the `onStepBackRequest` and `onReverseContinueRequest` methods on service/dap
- Adapt `onLaunchRequest` with the requried validations and logic for these new modes
- Use `CapabilitiesEvent` responses to enable the StepBack controls on the supported scenarios (see [dicussion here](https://github.com/go-delve/delve/pull/2367#discussion_r590975026))
- Add the corresponding launch.json support on vs code: https://github.com/golang/vscode-go/pull/1268 

lggomez: For the relatively small work it took without reworking any of the existing support in the dap service it looks very promising already @aarzilli @polinasok (sans the broken tests and the rest of the details I still must take care of)

Here's a preview of an unoptimized binary (compiled with gcflags '-N -l'). The debug flow along with the stacktrace and symbols seem to work just fine:

https://user-images.githubusercontent.com/6326271/110226918-ceef8380-7ed1-11eb-9386-09d00181d71d.mp4

Here's an unoptimized binary (the common use case, as people will not usually compile with optimizations turn off except for edge cases). As expected, symbols that were optimized away due to inlining and further heurists are broken, but no errors are triggered on the backend:

https://user-images.githubusercontent.com/6326271/110226958-15dd7900-7ed2-11eb-9bc1-587bd71c517c.mp4

I still have to test the core dumps, but I have already some questions/TODOs in which I could use some help:
- Variable handling: how should be handle the broken variables? assuming we do not have a way to tell apart optimized apps from debug builds 
- CPU identification: rr is only compatible with linux and [certain CPU families] (https://github.com/rr-debugger/rr#system-requirements). There is an official intel package which might be worth looking at for that end: https://github.com/intel-go/cpuid, but since this involves adding a third party dependency into the project (and still a TODO for AMD cpus) I bring this into the conversation first



aarzilli: > Variable handling: how should be handle the broken variables? assuming we do not have a way to tell apart optimized apps from debug builds

I'm not sure why this is a problem specific to this. It can already happen with the "exec" launch mode. You say that this is:

> the common use case, as people will not usually compile with optimizations turn off except for edge cases

but why would that be the case? Why wouldn't the rr backend be supported with the "debug" and "test" launch modes?

> CPU identification: rr is only compatible with linux and certain CPU families. There is an official intel package which might be worth looking at for that end: https://github.com/intel-go/cpuid

Setting aside the cpuid discussion for a moment, you have a chicken and egg problem. You are trying to tell the client if `SupportStepBack` is true in the initialization response but you can't know that until you know the launch mode in the launch request. I think that's the wrong place to do it, either you do it with a capabilities event (assuming it works for this in VSCode), after you get the launch request, or with some magic in the javascript extension.
lggomez: > I'm not sure why this is a problem specific to this. It can already happen with the "exec" launch mode. You say that this is:
> 
> > the common use case, as people will not usually compile with optimizations turn off except for edge cases

What I meant with this is that people replaying traces will see a lot of bogus entries on the variables pane unless the trace itself comes from a deoptimized build, which is not the default case. I aimed at the possibility of maybe trying to sanitize the invalid symbols from the debugger state but I guess that won't be a worthwhile approach 

> Setting aside the cpuid discussion for a moment, you have a chicken and egg problem. You are trying to tell the client if `SupportStepBack` is true in the initialization response but you can't know that until you know the launch mode in the launch request. I think that's the wrong place to do it, either you do it with a capabilities event (assuming it works for this in VSCode), after you get the launch request, or with some magic in the javascript extension.

Indeed, I handled this with the nodejs API on the old dap implementation I'm porting https://github.com/golang/vscode-go/pull/89/files#diff-42370209a717d9c657a14f0dcfc980d8321fff898c864c7be23b315deb977394R822-R827. I need to check with @hyangah if this is still feasible with the new design (I'm adding the VS code parameters on https://github.com/golang/vscode-go/pull/1268)
aarzilli: > I aimed at the possibility of maybe trying to sanitize the invalid symbols from the debugger state but I guess that won't be a worthwhile approach

AFAICT there's nothing useful we can do about it.
hyangah: > > I'm not sure why this is a problem specific to this. It can already happen with the "exec" launch mode. You say that this is:
> > > the common use case, as people will not usually compile with optimizations turn off except for edge cases
> 
> What I meant with this is that people replaying traces will see a lot of bogus entries on the variables pane unless the trace itself comes from a deoptimized build, which is not the default case. I aimed at the possibility of maybe trying to sanitize the invalid symbols from the debugger state but I guess that won't be a worthwhile approach
> 
> > Setting aside the cpuid discussion for a moment, you have a chicken and egg problem. You are trying to tell the client if `SupportStepBack` is true in the initialization response but you can't know that until you know the launch mode in the launch request. I think that's the wrong place to do it, either you do it with a capabilities event (assuming it works for this in VSCode), after you get the launch request, or with some magic in the javascript extension.
> 
> Indeed, I handled this with the nodejs API on the old dap implementation I'm porting https://github.com/golang/vscode-go/pull/89/files#diff-42370209a717d9c657a14f0dcfc980d8321fff898c864c7be23b315deb977394R822-R827. I need to check with @hyangah if this is still feasible with the new design (I'm adding the VS code parameters to this dap service on [golang/vscode-go#1268](https://github.com/golang/vscode-go/pull/1268))

I don't see a way for the Go extension to step in during initialization between the editor & the dap server using the DAP protocol. (unless we have a flag to supply when starting dlv dap). Any capabilities change has to be done through the `CapabilitiesEvent`. Not sure whether it will be too late if dlv dap tells the change when receiving the launch request that specifies rr mode, but I think it's worth trying.

polinasok: > > > Setting aside the cpuid discussion for a moment, you have a chicken and egg problem. You are trying to tell the client if `SupportStepBack` is true in the initialization response but you can't know that until you know the launch mode in the launch request. I think that's the wrong place to do it, either you do it with a capabilities event (assuming it works for this in VSCode), after you get the launch request, or with some magic in the javascript extension.
> > 
> > 
> > Indeed, I handled this with the nodejs API on the old dap implementation I'm porting https://github.com/golang/vscode-go/pull/89/files#diff-42370209a717d9c657a14f0dcfc980d8321fff898c864c7be23b315deb977394R822-R827. I need to check with @hyangah if this is still feasible with the new design (I'm adding the VS code parameters to this dap service on [golang/vscode-go#1268](https://github.com/golang/vscode-go/pull/1268))
> 
> I don't see a way for the Go extension to step in during initialization between the editor & the dap server using the DAP protocol. (unless we have a flag to supply when starting dlv dap). Any capabilities change has to be done through the `CapabilitiesEvent`. Not sure whether it will be too late if dlv dap tells the change when receiving the launch request that specifies rr mode, but I think it's worth trying.

If we were to change things after the initialize response, the Capabilities event would be the only way to go, but the editor gives no guarantees. We would need to try this to know for sure. That said, not all launch configuration attributes are meant to become launch/attach args. Some should stay as command-line arguments the dlv command. For example, the extension already parses and passes `--log` when starting dlv-dap, and it is ignored by the launch/attach request handler. I intended for backend to be handled the same way. You can access the value via `s.config.Debugger.Backend` in `onInitializeRequest`. You should be able to test this already if you just use the port in your launch config to connect to a server that you started manually, but we would need to make a change on the extension side to support `--backend` explicitly (although the shortcut for now would be to use the free-form `dlvFlags` attribute).
polinasok: > > Variable handling: how should be handle the broken variables? assuming we do not have a way to tell apart optimized apps from debug builds
> 
> I'm not sure why this is a problem specific to this. It can already happen with the "exec" launch mode. You say that this is:
> 
> > the common use case, as people will not usually compile with optimizations turn off except for edge cases
> 
> but why would that be the case? Why wouldn't the rr backend be supported with the "debug" and "test" launch modes?

Agree. Whatever happens in other modes makes sense here. I don't think we should do anything special with the variables as opposed to just displaying whatever we get from Debugger backend.

I also remember seeing at some point a warning with dlv cli that the program being debugged was compiled with optimizations. I guess it could be nice to display a pop-message warning the user that that's what they are about to do and that it will impact variables, and if that's what they want, they should use so and so flags.
lggomez: > > > Variable handling: how should be handle the broken variables? assuming we do not have a way to tell apart optimized apps from debug builds
> > 
> > 
> > I'm not sure why this is a problem specific to this. It can already happen with the "exec" launch mode. You say that this is:
> > > the common use case, as people will not usually compile with optimizations turn off except for edge cases
> > 
> > 
> > but why would that be the case? Why wouldn't the rr backend be supported with the "debug" and "test" launch modes?
> 
> Agree. Whatever happens in other modes makes sense here. I don't think we should do anything special with the variables as opposed to just displaying whatever we get from Debugger backend.
> 
> I also remember seeing at some point a warning with dlv cli that the program being debugged was compiled with optimizations. I guess it could be nice to display a pop-message warning the user that that's what they are about to do and that it will impact variables, and if that's what they want, they should use so and so flags.

I haven't gone deep enough into the debugger but if that info is available it should be inferred in some way because AFAIK the go compiler does not embed its compile flags into the ELF file or executable (please correct me if I'm wrong as I'm still not well versed in this territory). But either way, having that would be a plus to tell to the user as it would save them diagnosis time (and it would save you folks probably some time on non-issues here) 
aarzilli: > I haven't gone deep enough into the debugger but if that info is available it should be inferred in some way because AFAIK the go compiler does not embed its compile flags into the ELF file or executable

It actually does since 1.10. We use it to compute `proc.(*Function).Optimized`, which is where the optimized warning comes from.
polinasok: Meta comment. We have quite a bit of back-and-forth and from the commits, I can see that things keep evolving, stuffs gets added, removed, reshuffled. Would it be possible to keep the top overview comment up-to-date on what the PR provides with some high-level summary for each bullet as to how that feature is provided (using which mode/backend attribute and/or cli flag). Thank you.
aarzilli: Do you want a code review or is this still WIP?
lggomez: It's still a WIP but it's halfway there. With most of the design concerns resolved (at least up to this point) I have work to do on testing and fixing any remaining issues on the core dump replay and rr trace record modes, and then implement the tests. rr traces work fine for the cases I've tested, although testing on other platforms and programs is more than welcome
lggomez: @aarzilli @polinasok @hyangah well, I can finally say that the main implementation is done, with https://github.com/golang/vscode-go/pull/1268/files I can record, and replay traces from vs code, along with core dumps

Now some review is in order and I should ask how should I start on testing this. As I understand currently the continue requests are blocking, thus not addressable on the tests but I wonder if we could emulate at least a threadsRequest for a fixture with a core file, which should return a stack trace on the supported scenario
polinasok: > @aarzilli @polinasok @hyangah well, I can finally say that the main implementation is done, with https://github.com/golang/vscode-go/pull/1268/files I can record, and replay traces from vs code, along with core dumps
> 
> Now some review is in order and I should ask how should I start on testing this. As I understand currently the continue requests are blocking, thus not addressable on the tests but I wonder if we could emulate at least a threadsRequest for a fixture with a core file, which should return a stack trace on the supported scenario

Glad to see so much progress! I am currently trying to finish my code that will make the server support asynchronous requests and make continue not blocking (so it possible to pause, disconnect and set breakpoints while the program is running). I will be trying to get as much as I can done this week before being on vacation next week. If you could wait a bit for the dust to settle, we would be in a better place both code-wise and time-wise to give your work the attention that it deserves. 
polinasok: I think this PR
Updates https://github.com/golang/vscode-go/issues/110
Updates https://github.com/golang/vscode-go/issues/1243
Updates https://github.com/golang/vscode-go/issues/168
lggomez: I've updated to the latest master commit here and on the vs-code side (I had a really hard time with the rebases but I think we're even and there are no conflicts on sight). But now I'm getting the following error on the server whenever I'm trying to connect:

```shell
~/DEV/helloworld_modules$ dlv debug main.go --headless=true --listen=:54321 --api-version=2 --log
API server listening at: [::]:54321
2021-05-05T23:48:57-03:00 info layer=debugger launching process with args: [/home/razael/DEV/helloworld_modules/__debug_bin]
2021-05-05T23:49:29-03:00 error layer=rpc rpc:invalid character 'C' looking for beginning of value
2021-05-05T23:49:29-03:00 debug layer=debugger detaching
```

Tests seem to pass, so it doesn't immediately look like a regression on the server API and this is the first time I get this error, after rebasing the changes from this past month. Does it remind you of any recent commit @polinasok?
polinasok: > I've updated to the latest master commit here and on the vs-code side (I had a really hard time with the rebases but I think we're even and there are no conflicts on sight). But now I'm getting the following error on the server whenever I'm trying to connect:
> 
> ```shell
> ~/DEV/helloworld_modules$ dlv debug main.go --headless=true --listen=:54321 --api-version=2 --log
> API server listening at: [::]:54321
> 2021-05-05T23:48:57-03:00 info layer=debugger launching process with args: [/home/razael/DEV/helloworld_modules/__debug_bin]
> 2021-05-05T23:49:29-03:00 error layer=rpc rpc:invalid character 'C' looking for beginning of value
> 2021-05-05T23:49:29-03:00 debug layer=debugger detaching
> ```
> 
> Tests seem to pass, so it doesn't immediately look like a regression on the server API and this is the first time I get this error, after rebasing the changes from this past month. Does it remind you of any recent commit @polinasok?

I have never seen that error before. Are you running rpc (not dap) server on purpose?
lggomez: Mainly to see the logs on a console, since I want to see the debug output and so far I've not been able to do so from the VS Code UI
hyangah: @lggomez dlv DAP needs to start a different command at this moment. The usual delve commands don't use DAP protocol - the error message indicates the RPC server received DAP protocol, not the expected delve JSON-RPC .

https://github.com/golang/vscode-go/blob/master/docs/dlv-dap.md#developing describes how to start the dap server outside VSCode.

Re: VS Code not showing logs - VS Code Go extension currently starts the dlv dap server before the debug session starts. VS Code does not activate the debug console until a session starts, so, logs or crash info generated before an actual debug starts are dropped on the floor. Once the dlv dap server starts, the logs will appear in the debug console - in the [Go Nightly extension](https://github.com/golang/vscode-go/blob/master/docs/nightly.md). We are planning to add a workaround (i.e. start the `dlv dap` command AFTER :-( a debug session starts) soon.
derekparker: @lggomez is this ready for another round of reviews? If so please rebase. I'll look over the rest of the comments and add my own review within the next few days.
lggomez: > @lggomez is this ready for another round of reviews? If so please rebase. I'll look over the rest of the comments and add my own review within the next few days.

Done! I updated my changeset to the current implementation and rebase up to today's changes
lggomez: Sorry, I fixed the tests and updated up to today's changes

There is one test that passes but its panic is not caught correctly though, making it hang up indefinetely:

```shell
=== RUN   TestSetVariableWithCall
DAP server listening at: [::]:50924
Connecting to server at: [::]:50924
DAP server listening at: [::]:50926
Connecting to server at: [::]:50926
about to panic
about to break--- PASS: TestSetVariableWithCall (0.59s)

Process finished with the exit code 0
```

lggomez: In reference to this https://github.com/go-delve/delve/pull/2367#discussion_r638172679

It'd be great if we can simplify the interaction between vs code and the dap server, on the API level I just couldn't see the support baked off yet so that's why it is included under an explicit new mode. We could add further semantics to the `traceDirectory` and `coreDumpPath` parameters to disregard the need for the record mode, or maybe just a new configuration parameter, but I'd like to hear any ideas on what do you folks think would be the best form to implement this (since the main client of interest -vs code- will be interacting via the JSON code on launch.json, so the way I see it the more ergonomic or self-explanatory it is the better)
aarzilli: > It'd be great if we can simplify the interaction between vs code and the dap server, on the API level I just couldn't see the support baked off yet so that's why it is included under an explicit new mode

What does 'baked off' mean?
lggomez: > > It'd be great if we can simplify the interaction between vs code and the dap server, on the API level I just couldn't see the support baked off yet so that's why it is included under an explicit new mode
> 
> What does 'baked off' mean?

Sorry, the expression I meant was 'baked in', and with that I was referring to the need of implementing the server side code for the dap apicall. As you folks mention the support the record and replay is already present in the delve backend code but some code to glue this support and present it to the dap frontend is needed, thus this pr
aarzilli: @lggomez 
Ok. In my opinion the way to do this would be to add a backend option to launch.json, which gets passed directly to `debugger.New`.
If you see the combination of `rr+debug`, `rr+test` or `rr+exec` in onConfigurationDoneRequest you ignore stopOnEntry and unconditionally send a continued event.
The user will be able to stop the recording early by pressing the pause button (you don't have to change anything for this to work). 

This is more or less how it works on GoLand, as far as I understand, and I think this would be a good way to do it in vscode as well.
aarzilli: Isn't it weird that DAP has StepBack as the reverse of Next and StepIn and StepOut do not have a reverse counterpart?
polinasok: Is this the summary of the new state of things?

* [in this PR] `dlv core <exec> <corefile>` is supported via  `mode=core, program=..., coreFilePath=...`
* [in this PR] `dlv replay <tracedir>` is supported via `mode=replay, traceDirPath=...`
* [not in this PR] `dlv <cmd> --backend=rr` - to be revisited in another PR

polinasok: Also, I think the title of the PR, not just the description need updating. It should say something like "support replay and core modes"
polinasok: Please update
https://github.com/go-delve/delve/blob/4fd6c483e1efe349e0ed98d61693984f764a23fb/cmd/dlv/cmds/commands.go#L181
derekparker: Looks like the dust is finally settling on this! I'll take a final review pass on Monday.

Patch:
diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts
index 995e435f..198849c9 100644
--- a/.teamcity/settings.kts
+++ b/.teamcity/settings.kts
@@ -68,6 +68,9 @@ project {
         }))
     }
     buildType(AggregatorBuild(tests))
+    params {
+        param("teamcity.ui.settings.readOnly", "true")
+    }
 }
 
 class AggregatorBuild(tests: Collection<BuildType>) : BuildType({
@@ -105,7 +108,7 @@ class AggregatorBuild(tests: Collection<BuildType>) : BuildType({
             publisher = github {
                 githubUrl = "https://api.github.com"
                 authType = personalToken {
-                    token = "credentialsJSON:a7e7526c-7195-4790-bbb6-9fb4692f92d0"
+                    token = "credentialsJSON:1312c856-0e13-4b04-8c40-ac26d4a5f700"
                 }
             }
             param("github_oauth_user", "")
diff --git a/Documentation/usage/dlv_dap.md b/Documentation/usage/dlv_dap.md
index 8fc171f3..e72717ba 100644
--- a/Documentation/usage/dlv_dap.md
+++ b/Documentation/usage/dlv_dap.md
@@ -12,6 +12,8 @@ to be launched or process to be attached to. The following modes are supported:
 - launch + exec (executes precompiled binary, like 'dlv exec')
 - launch + debug (builds and launches, like 'dlv debug')
 - launch + test (builds and tests, like 'dlv test')
+- launch + replay (replays an rr trace, like 'dlv replay')
+- launch + core (replays a core dump file, like 'dlv core')
 - attach + local (attaches to a running process, like 'dlv attach')
 The server does not yet accept multiple client connections (--accept-multiclient).
 While --continue is not supported, stopOnEntry launch/attach attribute can be used to control if
diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go
index e11bdd12..c5523694 100644
--- a/cmd/dlv/cmds/commands.go
+++ b/cmd/dlv/cmds/commands.go
@@ -182,6 +182,8 @@ to be launched or process to be attached to. The following modes are supported:
 - launch + exec (executes precompiled binary, like 'dlv exec')
 - launch + debug (builds and launches, like 'dlv debug')
 - launch + test (builds and tests, like 'dlv test')
+- launch + replay (replays an rr trace, like 'dlv replay')
+- launch + core (replays a core dump file, like 'dlv core')
 - attach + local (attaches to a running process, like 'dlv attach')
 The server does not yet accept multiple client connections (--accept-multiclient).
 While --continue is not supported, stopOnEntry launch/attach attribute can be used to control if
diff --git a/pkg/proc/gdbserial/rr.go b/pkg/proc/gdbserial/rr.go
index 60ef4629..42891839 100644
--- a/pkg/proc/gdbserial/rr.go
+++ b/pkg/proc/gdbserial/rr.go
@@ -22,7 +22,7 @@ import (
 // stop function which will prematurely terminate the recording of the
 // program.
 func RecordAsync(cmd []string, wd string, quiet bool, redirects [3]string) (run func() (string, error), stop func() error, err error) {
-	if err := checkRRAvailabe(); err != nil {
+	if err := checkRRAvailable(); err != nil {
 		return nil, nil, err
 	}
 
@@ -126,7 +126,7 @@ func Record(cmd []string, wd string, quiet bool, redirects [3]string) (tracedir
 // Replay starts an instance of rr in replay mode, with the specified trace
 // directory, and connects to it.
 func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*proc.Target, error) {
-	if err := checkRRAvailabe(); err != nil {
+	if err := checkRRAvailable(); err != nil {
 		return nil, err
 	}
 
@@ -178,7 +178,7 @@ func (err ErrPerfEventParanoid) Error() string {
 	return fmt.Sprintf("rr needs /proc/sys/kernel/perf_event_paranoid <= 1, but it is %d", err.actual)
 }
 
-func checkRRAvailabe() error {
+func checkRRAvailable() error {
 	if _, err := exec.LookPath("rr"); err != nil {
 		return &ErrBackendUnavailable{}
 	}
diff --git a/service/dap/server.go b/service/dap/server.go
index 1bb02e62..f122aa21 100644
--- a/service/dap/server.go
+++ b/service/dap/server.go
@@ -33,11 +33,13 @@ import (
 	"github.com/go-delve/delve/pkg/locspec"
 	"github.com/go-delve/delve/pkg/logflags"
 	"github.com/go-delve/delve/pkg/proc"
+
 	"github.com/go-delve/delve/service"
 	"github.com/go-delve/delve/service/api"
 	"github.com/go-delve/delve/service/debugger"
 	"github.com/go-delve/delve/service/internal/sameuser"
 	"github.com/google/go-dap"
+
 	"github.com/sirupsen/logrus"
 )
 
@@ -547,12 +549,18 @@ func (s *Server) handleRequest(request dap.Message) {
 		<-resumeRequestLoop
 	case *dap.StepBackRequest:
 		// Optional (capability ‘supportsStepBack’)
-		// TODO: implement this request in V1
-		s.onStepBackRequest(request)
+		go func() {
+			defer s.recoverPanic(request)
+			s.onStepBackRequest(request, resumeRequestLoop)
+		}()
+		<-resumeRequestLoop
 	case *dap.ReverseContinueRequest:
 		// Optional (capability ‘supportsStepBack’)
-		// TODO: implement this request in V1
-		s.onReverseContinueRequest(request)
+		go func() {
+			defer s.recoverPanic(request)
+			s.onReverseContinueRequest(request, resumeRequestLoop)
+		}()
+		<-resumeRequestLoop
 	//--- Synchronous requests ---
 	case *dap.InitializeRequest:
 		// Required
@@ -712,7 +720,7 @@ func (s *Server) onInitializeRequest(request *dap.InitializeRequest) {
 	// TODO(polina): support these requests in addition to vscode-go feature parity
 	response.Body.SupportsTerminateRequest = false
 	response.Body.SupportsRestartRequest = false
-	response.Body.SupportsStepBack = false
+	response.Body.SupportsStepBack = false // To be enabled by CapabilitiesEvent based on configuration
 	response.Body.SupportsSetExpression = false
 	response.Body.SupportsLoadedSourcesRequest = false
 	response.Body.SupportsReadMemoryRequest = false
@@ -755,13 +763,58 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 
 	// TODO(polina): Respond with an error if debug session is in progress?
 	program, ok := request.Arguments["program"].(string)
-	if !ok || program == "" {
+	if (!ok || program == "") && mode != "replay" { // Only fail on modes requiring a program
 		s.sendErrorResponse(request.Request,
 			FailedToLaunch, "Failed to launch",
 			"The program attribute is missing in debug configuration.")
 		return
 	}
 
+
+	if mode == "replay" {
+		traceDirPath, _ := request.Arguments["traceDirPath"].(string)
+
+
+		// Validate trace directory
+		if traceDirPath == "" {
+			s.sendErrorResponse(request.Request,
+				FailedToLaunch, "Failed to launch",
+				"The 'traceDirPath' attribute is missing in debug configuration.")
+			return
+		}
+
+
+
+
+
+		// Assign the rr trace directory path to debugger configuration
+		s.config.Debugger.CoreFile = traceDirPath
+		s.config.Debugger.Backend = "rr"
+	}
+
+	if mode == "core" {
+		coreFilePath, _ := request.Arguments["coreFilePath"].(string)
+
+
+		// Validate core dump path
+		if coreFilePath == "" {
+
+			s.sendErrorResponse(request.Request,
+				FailedToLaunch, "Failed to launch",
+				"The 'coreFilePath' attribute is missing in debug configuration.")
+			return
+		}
+
+
+		// Assign the non-empty core file path to debugger configuration. This will
+		// trigger a native core file replay instead of an rr trace replay
+		s.config.Debugger.CoreFile = coreFilePath
+		s.config.Debugger.Backend = "core"
+	}
+
+	s.log.Debugf("debug backend is '%s'", s.config.Debugger.Backend)
+	
+	// Prepare the debug executable filename, build flags and build it
 	if mode == "debug" || mode == "test" {
 		output, ok := request.Arguments["output"].(string)
 		if !ok || output == "" {
@@ -786,9 +839,10 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 			}
 		}
 
-		s.log.Debugf("building binary at %s", debugbinary)
 		var cmd string
 		var out []byte
+		// Log debug binary build
+		s.log.Debugf("building binary '%s' from '%s' with flags '%v'", debugbinary, program, buildFlags)
 		switch mode {
 		case "debug":
 			cmd, out, err = gobuild.GoBuildCombinedOutput(debugbinary, []string{program}, buildFlags)
@@ -912,6 +966,10 @@ func (s *Server) onLaunchRequest(request *dap.LaunchRequest) {
 		s.sendErrorResponse(request.Request, FailedToLaunch, "Failed to launch", err.Error())
 		return
 	}
+	// Enable StepBack controls on supported backends
+	if s.config.Debugger.Backend == "rr" {
+		s.send(&dap.CapabilitiesEvent{ Event: *newEvent("capabilities"), Body: dap.CapabilitiesEventBody{Capabilities: dap.Capabilities{ SupportsStepBack: true }}})
+	}
 
 	// Notify the client that the debugger is ready to start accepting
 	// configuration requests for setting breakpoints, etc. The client
@@ -963,10 +1021,15 @@ func (s *Server) stopNoDebugProcess() {
 //      Required args: program
 //      Optional args with default: cwd, noDebug
 //      Optional args: args
-// TODO(pull/2367): add "replay", "core"
+// -- replay: skips program build and sets the Debugger.CoreFile property based on the
+//		Required args: coreFilePath
+//      Optional args: program, args
+// -- core: skips program build and sets the Debugger.CoreFile property based on the
+//      Required args: program, traceDirPath
+//      Optional args: args
 func isValidLaunchMode(mode interface{}) bool {
 	switch mode {
-	case "exec", "debug", "test":
+	case "exec", "debug", "test", "replay", "core":
 		return true
 	}
 	return false
@@ -2407,16 +2470,21 @@ func (s *Server) onRestartRequest(request *dap.RestartRequest) {
 	s.sendNotYetImplementedErrorResponse(request.Request)
 }
 
-// onStepBackRequest sends a not-yet-implemented error response.
-// Capability 'supportsStepBack' is not set 'initialize' response.
-func (s *Server) onStepBackRequest(request *dap.StepBackRequest) {
-	s.sendNotYetImplementedErrorResponse(request.Request)
+// onStepBackRequest handles 'stepBack' request.
+// This is an optional request enabled by capability ‘supportsStepBackRequest’.
+func (s *Server) onStepBackRequest(request *dap.StepBackRequest, asyncSetupDone chan struct{}) {
+	s.sendStepResponse(request.Arguments.ThreadId, &dap.StepBackResponse{Response: *newResponse(request.Request)})
+	s.doStepCommand(api.ReverseNext, request.Arguments.ThreadId, asyncSetupDone)
 }
 
-// onReverseContinueRequest sends a not-yet-implemented error response.
-// Capability 'supportsStepBack' is not set 'initialize' response.
-func (s *Server) onReverseContinueRequest(request *dap.ReverseContinueRequest) {
-	s.sendNotYetImplementedErrorResponse(request.Request)
+// onReverseContinueRequest performs a rewind command call up to the previous
+// breakpoint or the start of the process
+// This is an optional request enabled by capability ‘supportsStepBackRequest’.
+func (s *Server) onReverseContinueRequest(request *dap.ReverseContinueRequest, asyncSetupDone chan struct{}) {
+	s.send(&dap.ReverseContinueResponse{
+		Response: *newResponse(request.Request),
+	})
+	s.doRunCommand(api.Rewind, asyncSetupDone)
 }
 
 // computeEvaluateName finds the named child, and computes its evaluate name.
@@ -2855,4 +2923,4 @@ func (s *Server) toServerPath(path string) string {
 		s.log.Debugf("client path=%s converted to server path=%s\n", path, serverPath)
 	}
 	return serverPath
-}
+}
\ No newline at end of file


Test patch:
diff --git a/service/dap/server_test.go b/service/dap/server_test.go
index 28358e06..e1c0d93b 100644
--- a/service/dap/server_test.go
+++ b/service/dap/server_test.go
@@ -4571,11 +4571,6 @@ func TestOptionalNotYetImplementedResponses(t *testing.T) {
 		client.RestartRequest()
 		expectNotYetImplemented("restart")
 
-		client.StepBackRequest()
-		expectNotYetImplemented("stepBack")
-
-		client.ReverseContinueRequest()
-		expectNotYetImplemented("reverseContinue")
 
 		client.SetExpressionRequest()
 		expectNotYetImplemented("setExpression")
@@ -4752,6 +4747,20 @@ func TestBadLaunchRequests(t *testing.T) {
 		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "debug", "program": fixture.Source, "noDebug": true, "cwd": "dir/invalid"})
 		checkFailedToLaunch(client.ExpectErrorResponse(t)) // invalid directory, the error message is system-dependent.
 
+		// Bad parameters on "replay" and "core" modes
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "traceDirPath": ""})
+		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+			"Failed to launch: The 'traceDirPath' attribute is missing in debug configuration.")
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "coreFilePath": ""})
+		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+			"Failed to launch: The program attribute is missing in debug configuration.")
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "replay", "program": fixture.Source, "traceDirPath": ""})
+		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+			"Failed to launch: The 'traceDirPath' attribute is missing in debug configuration.")
+		client.LaunchRequestWithArgs(map[string]interface{}{"mode": "core", "program": fixture.Source, "coreFilePath": ""})
+		checkFailedToLaunchWithMessage(client.ExpectInvisibleErrorResponse(t),
+			"Failed to launch: The 'coreFilePath' attribute is missing in debug configuration.")
+
 		// We failed to launch the program. Make sure shutdown still works.
 		client.DisconnectRequest()
 		dresp := client.ExpectDisconnectResponse(t)