github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/proc/gdbserial/rr.go (about) 1 package gdbserial 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "strconv" 12 "strings" 13 "syscall" 14 15 "github.com/go-delve/delve/pkg/config" 16 "github.com/go-delve/delve/pkg/proc" 17 ) 18 19 // RecordAsync configures rr to record the execution of the specified 20 // program. Returns a run function which will actually record the program, a 21 // stop function which will prematurely terminate the recording of the 22 // program. 23 func RecordAsync(cmd []string, wd string, quiet bool, stdin string, stdout proc.OutputRedirect, stderr proc.OutputRedirect) (run func() (string, error), stop func() error, err error) { 24 if err := checkRRAvailable(); err != nil { 25 return nil, nil, err 26 } 27 28 rfd, wfd, err := os.Pipe() 29 if err != nil { 30 return nil, nil, err 31 } 32 33 args := make([]string, 0, len(cmd)+2) 34 args = append(args, "record", "--print-trace-dir=3") 35 args = append(args, cmd...) 36 rrcmd := exec.Command("rr", args...) 37 var closefn func() 38 rrcmd.Stdin, rrcmd.Stdout, rrcmd.Stderr, closefn, err = openRedirects(stdin, stdout, stderr, quiet) 39 if err != nil { 40 return nil, nil, err 41 } 42 rrcmd.ExtraFiles = []*os.File{wfd} 43 rrcmd.Dir = wd 44 45 tracedirChan := make(chan string) 46 go func() { 47 bs, _ := ioutil.ReadAll(rfd) 48 tracedirChan <- strings.TrimSpace(string(bs)) 49 }() 50 51 run = func() (string, error) { 52 err := rrcmd.Run() 53 closefn() 54 _ = wfd.Close() 55 tracedir := <-tracedirChan 56 return tracedir, err 57 } 58 59 stop = func() error { 60 return rrcmd.Process.Signal(syscall.SIGTERM) 61 } 62 63 return run, stop, nil 64 } 65 66 func openRedirects(stdinPath string, stdoutOR proc.OutputRedirect, stderrOR proc.OutputRedirect, quiet bool) (stdin, stdout, stderr *os.File, closefn func(), err error) { 67 toclose := []*os.File{} 68 69 if stdinPath != "" { 70 stdin, err = os.Open(stdinPath) 71 if err != nil { 72 return nil, nil, nil, nil, err 73 } 74 toclose = append(toclose, stdin) 75 } else { 76 stdin = os.Stdin 77 } 78 79 create := func(redirect proc.OutputRedirect, dflt *os.File) (f *os.File) { 80 if redirect.Path != "" { 81 f, err = os.Create(redirect.Path) 82 if f != nil { 83 toclose = append(toclose, f) 84 } 85 86 return f 87 } else if redirect.File != nil { 88 toclose = append(toclose, redirect.File) 89 90 return redirect.File 91 } 92 93 if quiet { 94 return nil 95 } 96 97 return dflt 98 } 99 100 stdout = create(stdoutOR, os.Stdout) 101 if err != nil { 102 return nil, nil, nil, nil, err 103 } 104 105 stderr = create(stderrOR, os.Stderr) 106 if err != nil { 107 return nil, nil, nil, nil, err 108 } 109 110 closefn = func() { 111 for _, f := range toclose { 112 _ = f.Close() 113 } 114 } 115 116 return stdin, stdout, stderr, closefn, nil 117 } 118 119 // Record uses rr to record the execution of the specified program and 120 // returns the trace directory's path. 121 func Record(cmd []string, wd string, quiet bool, stdin string, stdout proc.OutputRedirect, stderr proc.OutputRedirect) (tracedir string, err error) { 122 run, _, err := RecordAsync(cmd, wd, quiet, stdin, stdout, stderr) 123 if err != nil { 124 return "", err 125 } 126 127 // ignore run errors, it could be the program crashing 128 return run() 129 } 130 131 // Replay starts an instance of rr in replay mode, with the specified trace 132 // directory, and connects to it. 133 func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string, rrOnProcessPid int, cmdline string) (*proc.TargetGroup, error) { 134 if err := checkRRAvailable(); err != nil { 135 return nil, err 136 } 137 138 args := []string{ 139 "replay", 140 "--dbgport=0", 141 } 142 if rrOnProcessPid != 0 { 143 args = append(args, fmt.Sprintf("--onprocess=%d", rrOnProcessPid)) 144 } 145 args = append(args, tracedir) 146 147 rrcmd := exec.Command("rr", args...) 148 rrcmd.Stdout = os.Stdout 149 stderr, err := rrcmd.StderrPipe() 150 if err != nil { 151 return nil, err 152 } 153 rrcmd.SysProcAttr = sysProcAttr(false) 154 155 initch := make(chan rrInit) 156 go rrStderrParser(stderr, initch, quiet) 157 158 err = rrcmd.Start() 159 if err != nil { 160 return nil, err 161 } 162 163 init := <-initch 164 if init.err != nil { 165 rrcmd.Process.Kill() 166 return nil, init.err 167 } 168 169 p := newProcess(rrcmd.Process) 170 p.tracedir = tracedir 171 p.conn.useXcmd = true // 'rr' does not support the 'M' command which is what we would usually use to write memory, this is only important during function calls, in any other situation writing memory will fail anyway. 172 if deleteOnDetach { 173 p.onDetach = func() { 174 safeRemoveAll(p.tracedir) 175 } 176 } 177 tgt, err := p.Dial(init.port, init.exe, cmdline, 0, debugInfoDirs, proc.StopLaunched) 178 if err != nil { 179 rrcmd.Process.Kill() 180 return nil, err 181 } 182 183 return tgt, nil 184 } 185 186 // ErrPerfEventParanoid is the error returned by Reply and Record if 187 // /proc/sys/kernel/perf_event_paranoid is greater than 1. 188 type ErrPerfEventParanoid struct { 189 actual int 190 } 191 192 func (err ErrPerfEventParanoid) Error() string { 193 return fmt.Sprintf("rr needs /proc/sys/kernel/perf_event_paranoid <= 1, but it is %d", err.actual) 194 } 195 196 func checkRRAvailable() error { 197 if _, err := exec.LookPath("rr"); err != nil { 198 return &ErrBackendUnavailable{} 199 } 200 201 // Check that /proc/sys/kernel/perf_event_paranoid doesn't exist or is <= 1. 202 buf, err := ioutil.ReadFile("/proc/sys/kernel/perf_event_paranoid") 203 if err == nil { 204 perfEventParanoid, _ := strconv.Atoi(strings.TrimSpace(string(buf))) 205 if perfEventParanoid > 1 { 206 return ErrPerfEventParanoid{perfEventParanoid} 207 } 208 } 209 210 return nil 211 } 212 213 type rrInit struct { 214 port string 215 exe string 216 err error 217 } 218 219 const ( 220 rrGdbCommandPrefix = " gdb " 221 rrGdbLaunchPrefix = "Launch gdb with" 222 targetCmd = "target extended-remote " 223 ) 224 225 func rrStderrParser(stderr io.ReadCloser, initch chan<- rrInit, quiet bool) { 226 rd := bufio.NewReader(stderr) 227 defer stderr.Close() 228 229 for { 230 line, err := rd.ReadString('\n') 231 if err != nil { 232 initch <- rrInit{"", "", err} 233 close(initch) 234 return 235 } 236 237 if strings.HasPrefix(line, rrGdbCommandPrefix) { 238 initch <- rrParseGdbCommand(line[len(rrGdbCommandPrefix):]) 239 close(initch) 240 break 241 } 242 243 if strings.HasPrefix(line, rrGdbLaunchPrefix) { 244 continue 245 } 246 247 if !quiet { 248 os.Stderr.Write([]byte(line)) 249 } 250 } 251 252 io.Copy(os.Stderr, rd) 253 } 254 255 type ErrMalformedRRGdbCommand struct { 256 line, reason string 257 } 258 259 func (err *ErrMalformedRRGdbCommand) Error() string { 260 return fmt.Sprintf("malformed gdb command %q: %s", err.line, err.reason) 261 } 262 263 func rrParseGdbCommand(line string) rrInit { 264 port := "" 265 fields := config.SplitQuotedFields(line, '\'') 266 for i := 0; i < len(fields); i++ { 267 switch fields[i] { 268 case "-ex": 269 if i+1 >= len(fields) { 270 return rrInit{err: &ErrMalformedRRGdbCommand{line, "-ex not followed by an argument"}} 271 } 272 arg := fields[i+1] 273 274 if !strings.HasPrefix(arg, targetCmd) { 275 continue 276 } 277 278 port = arg[len(targetCmd):] 279 i++ 280 281 case "-l": 282 // skip argument 283 i++ 284 } 285 } 286 287 if port == "" { 288 return rrInit{err: &ErrMalformedRRGdbCommand{line, "could not find -ex argument"}} 289 } 290 291 exe := fields[len(fields)-1] 292 293 return rrInit{port: port, exe: exe} 294 } 295 296 // RecordAndReplay acts like calling Record and then Replay. 297 func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, stdin string, stdout proc.OutputRedirect, stderr proc.OutputRedirect) (*proc.TargetGroup, string, error) { 298 tracedir, err := Record(cmd, wd, quiet, stdin, stdout, stderr) 299 if tracedir == "" { 300 return nil, "", err 301 } 302 t, err := Replay(tracedir, quiet, true, debugInfoDirs, 0, strings.Join(cmd, " ")) 303 return t, tracedir, err 304 } 305 306 // safeRemoveAll removes dir and its contents but only as long as dir does 307 // not contain directories. 308 func safeRemoveAll(dir string) { 309 fis, err := os.ReadDir(dir) 310 if err != nil { 311 return 312 } 313 for _, fi := range fis { 314 if fi.IsDir() { 315 return 316 } 317 } 318 for _, fi := range fis { 319 if err := os.Remove(filepath.Join(dir, fi.Name())); err != nil { 320 return 321 } 322 } 323 os.Remove(dir) 324 }