github.com/undoio/delve@v1.9.0/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/undoio/delve/pkg/config" 16 "github.com/undoio/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, redirects [3]string) (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(redirects, 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(redirects [3]string, quiet bool) (stdin, stdout, stderr *os.File, closefn func(), err error) { 67 toclose := []*os.File{} 68 69 if redirects[0] != "" { 70 stdin, err = os.Open(redirects[0]) 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(path string, dflt *os.File) *os.File { 80 if path == "" { 81 if quiet { 82 return nil 83 } 84 return dflt 85 } 86 var f *os.File 87 f, err = os.Create(path) 88 if f != nil { 89 toclose = append(toclose, f) 90 } 91 return f 92 } 93 94 stdout = create(redirects[1], os.Stdout) 95 if err != nil { 96 return nil, nil, nil, nil, err 97 } 98 99 stderr = create(redirects[2], os.Stderr) 100 if err != nil { 101 return nil, nil, nil, nil, err 102 } 103 104 closefn = func() { 105 for _, f := range toclose { 106 _ = f.Close() 107 } 108 } 109 110 return stdin, stdout, stderr, closefn, nil 111 } 112 113 // Record uses rr to record the execution of the specified program and 114 // returns the trace directory's path. 115 func Record(cmd []string, wd string, quiet bool, redirects [3]string) (tracedir string, err error) { 116 run, _, err := RecordAsync(cmd, wd, quiet, redirects) 117 if err != nil { 118 return "", err 119 } 120 121 // ignore run errors, it could be the program crashing 122 return run() 123 } 124 125 // Replay starts an instance of rr in replay mode, with the specified trace 126 // directory, and connects to it. 127 func Replay(tracedir string, quiet, deleteOnDetach bool, debugInfoDirs []string) (*proc.Target, error) { 128 if err := checkRRAvailable(); err != nil { 129 return nil, err 130 } 131 132 rrcmd := exec.Command("rr", "replay", "--dbgport=0", tracedir) 133 rrcmd.Stdout = os.Stdout 134 stderr, err := rrcmd.StderrPipe() 135 if err != nil { 136 return nil, err 137 } 138 rrcmd.SysProcAttr = sysProcAttr(false) 139 140 initch := make(chan rrInit) 141 go rrStderrParser(stderr, initch, quiet) 142 143 err = rrcmd.Start() 144 if err != nil { 145 return nil, err 146 } 147 148 init := <-initch 149 if init.err != nil { 150 rrcmd.Process.Kill() 151 return nil, init.err 152 } 153 154 p := newProcess(rrcmd.Process) 155 p.tracedir = tracedir 156 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. 157 if deleteOnDetach { 158 p.onDetach = func() { 159 safeRemoveAll(p.tracedir) 160 } 161 } 162 tgt, err := p.Dial(init.port, init.exe, 0, debugInfoDirs, proc.StopLaunched) 163 if err != nil { 164 rrcmd.Process.Kill() 165 return nil, err 166 } 167 168 return tgt, nil 169 } 170 171 // ErrPerfEventParanoid is the error returned by Reply and Record if 172 // /proc/sys/kernel/perf_event_paranoid is greater than 1. 173 type ErrPerfEventParanoid struct { 174 actual int 175 } 176 177 func (err ErrPerfEventParanoid) Error() string { 178 return fmt.Sprintf("rr needs /proc/sys/kernel/perf_event_paranoid <= 1, but it is %d", err.actual) 179 } 180 181 func checkRRAvailable() error { 182 if _, err := exec.LookPath("rr"); err != nil { 183 return &ErrBackendUnavailable{} 184 } 185 186 // Check that /proc/sys/kernel/perf_event_paranoid doesn't exist or is <= 1. 187 buf, err := ioutil.ReadFile("/proc/sys/kernel/perf_event_paranoid") 188 if err == nil { 189 perfEventParanoid, _ := strconv.Atoi(strings.TrimSpace(string(buf))) 190 if perfEventParanoid > 1 { 191 return ErrPerfEventParanoid{perfEventParanoid} 192 } 193 } 194 195 return nil 196 } 197 198 type rrInit struct { 199 port string 200 exe string 201 err error 202 } 203 204 const ( 205 rrGdbCommandPrefix = " gdb " 206 rrGdbLaunchPrefix = "Launch gdb with" 207 targetCmd = "target extended-remote " 208 ) 209 210 func rrStderrParser(stderr io.ReadCloser, initch chan<- rrInit, quiet bool) { 211 rd := bufio.NewReader(stderr) 212 defer stderr.Close() 213 214 for { 215 line, err := rd.ReadString('\n') 216 if err != nil { 217 initch <- rrInit{"", "", err} 218 close(initch) 219 return 220 } 221 222 if strings.HasPrefix(line, rrGdbCommandPrefix) { 223 initch <- rrParseGdbCommand(line[len(rrGdbCommandPrefix):]) 224 close(initch) 225 break 226 } 227 228 if strings.HasPrefix(line, rrGdbLaunchPrefix) { 229 continue 230 } 231 232 if !quiet { 233 os.Stderr.Write([]byte(line)) 234 } 235 } 236 237 io.Copy(os.Stderr, rd) 238 } 239 240 type ErrMalformedRRGdbCommand struct { 241 line, reason string 242 } 243 244 func (err *ErrMalformedRRGdbCommand) Error() string { 245 return fmt.Sprintf("malformed gdb command %q: %s", err.line, err.reason) 246 } 247 248 func rrParseGdbCommand(line string) rrInit { 249 port := "" 250 fields := config.SplitQuotedFields(line, '\'') 251 for i := 0; i < len(fields); i++ { 252 switch fields[i] { 253 case "-ex": 254 if i+1 >= len(fields) { 255 return rrInit{err: &ErrMalformedRRGdbCommand{line, "-ex not followed by an argument"}} 256 } 257 arg := fields[i+1] 258 259 if !strings.HasPrefix(arg, targetCmd) { 260 continue 261 } 262 263 port = arg[len(targetCmd):] 264 i++ 265 266 case "-l": 267 // skip argument 268 i++ 269 } 270 } 271 272 if port == "" { 273 return rrInit{err: &ErrMalformedRRGdbCommand{line, "could not find -ex argument"}} 274 } 275 276 exe := fields[len(fields)-1] 277 278 return rrInit{port: port, exe: exe} 279 } 280 281 // RecordAndReplay acts like calling Record and then Replay. 282 func RecordAndReplay(cmd []string, wd string, quiet bool, debugInfoDirs []string, redirects [3]string) (*proc.Target, string, error) { 283 tracedir, err := Record(cmd, wd, quiet, redirects) 284 if tracedir == "" { 285 return nil, "", err 286 } 287 t, err := Replay(tracedir, quiet, true, debugInfoDirs) 288 return t, tracedir, err 289 } 290 291 // safeRemoveAll removes dir and its contents but only as long as dir does 292 // not contain directories. 293 func safeRemoveAll(dir string) { 294 dh, err := os.Open(dir) 295 if err != nil { 296 return 297 } 298 defer dh.Close() 299 fis, err := dh.Readdir(-1) 300 if err != nil { 301 return 302 } 303 for _, fi := range fis { 304 if fi.IsDir() { 305 return 306 } 307 } 308 for _, fi := range fis { 309 if err := os.Remove(filepath.Join(dir, fi.Name())); err != nil { 310 return 311 } 312 } 313 os.Remove(dir) 314 }