github.com/bookerzzz/grok@v0.0.0/stack.go (about) 1 package grok 2 3 import ( 4 "runtime/debug" 5 "bytes" 6 "bufio" 7 "os" 8 "strings" 9 "io/ioutil" 10 "io" 11 "sync" 12 "fmt" 13 "regexp" 14 "strconv" 15 "os/exec" 16 "unsafe" 17 "reflect" 18 ) 19 20 const ( 21 newline = '\n' 22 tab = '\t' 23 help = `Interactive stack: 24 25 > help (h) prints this help menu 26 > exit (x) exits the interactive stack 27 > show (?) repeats the current position 28 > quick (q) summary of the entire stack 29 > next (n) next item in the stack 30 > prev (p) previous item in the stack 31 > top (t) go to the top of the stack 32 > jump (j) {index} jumps to the entry at the given index 33 > dump (d) {index} prints a dump of the value corresponding to {index} of the current stack entry 34 35 ` 36 ) 37 var ( 38 stackMutex = sync.Mutex{} 39 rxPointA = regexp.MustCompile(`^(?P<Package>.+)\.(\(?P<Receiver>([^\)]+)\)\.)(?P<Func>[^\(]+)\((?P<Args>[^\)]*)\)\s*\t(?P<File>[^:]+):(?P<Line>[0-9]+)\s(?P<Identifier>\+0x[0-9a-f]+)$`) 40 rxPointB = regexp.MustCompile(`^(?P<Package>.+)\.(?P<Func>[^\(]+)\((?P<Args>[^\)]*)\)\s*\t(?P<File>[^:]+):(?P<Line>[0-9]+) (?P<Identifier>\+0x[0-9a-f]+)$`) 41 rxPointC = regexp.MustCompile(`^(?P<Package>.+)\.(\(?P<Receiver>([^\)]+)\)\.)(?P<Func>[^\(]+)\s*\t(?P<File>[^:]+):(?P<Line>[0-9]+)\s(?P<Identifier>\+0x[0-9a-f]+)$`) 42 rxPointD = regexp.MustCompile(`^(?P<Package>.+)\.(?P<Func>[^\(]+)\s*\t(?P<File>[^:]+):(?P<Line>[0-9]+)\s(?P<Identifier>\+0x[0-9a-f]+)$`) 43 ) 44 45 type point struct { 46 Position string 47 Package string 48 Receiver string 49 Func string 50 File string 51 Line string 52 Args []arg 53 Identifier string 54 } 55 56 type arg struct { 57 Hex string 58 Pointer uintptr 59 Value interface{} 60 Err error 61 } 62 63 func Stack(options ...Option) { 64 c := defaults 65 for _, o := range options { 66 o(&c) 67 } 68 69 // only one stack at a time 70 stackMutex.Lock() 71 defer stackMutex.Unlock() 72 73 colour := colourer(c) 74 out := outer(c) 75 points := stack(c) 76 77 for _, p := range points { 78 args := "" 79 for i, a := range p.Args { 80 if i > 0 { 81 args = args + ", " 82 } 83 args = args + colour(a.Hex, colourRed) 84 } 85 out("%s(%s) %s:%s\n", colour(p.Func, colourGreen), args, colour(p.File, colourBlue), colour(p.Line, colourRed)) 86 } 87 } 88 89 func stack(c Conf) []point { 90 points := []point{} 91 stack := debug.Stack() 92 r := bytes.NewReader(stack) 93 94 lines := [][]byte{} 95 line := []byte{} 96 for { 97 b, err := r.ReadByte() 98 if err != nil { 99 lines = append(lines, line) 100 break; 101 } 102 if b == newline { 103 lines = append(lines, line) 104 line = []byte{} 105 continue; 106 } 107 line = append(line, b) 108 } 109 110 for i := 1; i < len(lines); i = i+2 { 111 // Skip stack creation lines 112 if i > 0 && i <= 6 { 113 continue 114 } 115 // skip empty lines 116 if len(lines[i]) == 0 { 117 continue 118 } 119 p := point{ 120 Position: fmt.Sprintf("%d", len(points)), 121 } 122 l := append(lines[i], lines[i+1]...) 123 for _, rx := range []*regexp.Regexp{rxPointA, rxPointB, rxPointC, rxPointD} { 124 if rx.Match(l) { 125 matches := rx.FindSubmatch(l) 126 names := rx.SubexpNames() 127 for k, m := range matches { 128 n := names[k] 129 switch n { 130 case "Package": 131 p.Package = string(m) 132 case "Receiver": 133 p.Receiver = string(m) 134 case "Func": 135 p.Func = string(m) 136 case "Args": 137 p.Args = []arg{} 138 args := bytes.Split(m, []byte(", ")) 139 for _, h := range args { 140 if len(h) == 0 { 141 continue 142 } 143 a := arg{ 144 Hex: string(h), 145 } 146 i, err := strconv.ParseUint(string(h[2:]), 16, 64) 147 if err != nil { 148 a.Err = err 149 p.Args = append(p.Args, a) 150 continue 151 } 152 a.Pointer = uintptr(i) 153 //v := reflect.Indirect(reflect.ValueOf((*interface{})(unsafe.Pointer(a.Pointer)))) 154 v := unsafe.Pointer(a.Pointer) 155 if v != nil { 156 a.Value = v 157 } 158 p.Args = append(p.Args, a) 159 } 160 case "File": 161 p.File = string(m) 162 case "Line": 163 p.Line = string(m) 164 case "Identifier": 165 p.Identifier = string(m) 166 } 167 } 168 } 169 } 170 points = append(points, p) 171 } 172 return points 173 } 174 175 func hijackStdOutput() (original io.Writer, done func()) { 176 stdout := *os.Stdout 177 stderr := *os.Stderr 178 r, w, _ := os.Pipe() 179 *os.Stdout = *w 180 *os.Stderr = *w 181 182 return &stdout, func() { 183 w.Close() 184 captured, _ := ioutil.ReadAll(r) 185 *os.Stdout = stdout 186 *os.Stderr = stderr 187 os.Stdout.Write(captured) 188 } 189 } 190 191 func InteractiveStack(options ...Option) { 192 stackMutex.Lock() 193 defer stackMutex.Unlock() 194 195 stdout, done := hijackStdOutput() 196 defer done() 197 198 c := defaults 199 WithWriter(stdout)(&c) 200 for _, o := range options { 201 o(&c) 202 } 203 colour := colourer(c) 204 out := outer(c) 205 index := 0 206 207 points := stack(c) 208 reader := bufio.NewReader(os.Stdin) 209 out(colour(help, colourGreen)) 210 points[index].out(c) 211 for { 212 out(colour("> ", colourRed)) 213 input, err := reader.ReadString(newline) 214 if err != nil { 215 out(colour("Sorry, couldn't pick up what you just put down.\n", colourRed)) 216 continue 217 } 218 219 command := "" 220 parts := []string{} 221 for _, p := range strings.Split(input, " ") { 222 p = strings.TrimSpace(p) 223 if len(p) == 0 { 224 continue 225 } 226 parts = append(parts, p) 227 } 228 if len(parts) > 0 { 229 command = parts[0] 230 } 231 232 if command == "exit" || command == "x" { 233 out(colour("Thank you. Goodbye.\n", colourGreen)) 234 break; 235 } 236 237 switch command { 238 case "show", "?": 239 points[index].out(c) 240 case "top", "t": 241 index = 0 242 points[index].out(c) 243 case "next", "n": 244 if index == len(points) - 1 { 245 out(colour("You've reached the end!\n", colourRed)) 246 } else { 247 index = index + 1 248 } 249 points[index].out(c) 250 case "prev", "p": 251 if index == 0 { 252 out(colour("You've reached the top!\n", colourRed)) 253 } else { 254 index = index - 1 255 } 256 points[index].out(c) 257 case "dump", "d": 258 if len(parts) <= 0 { 259 out(colour("Not quite sure which arg to dump?!\n", colourRed)) 260 out(colour(help, colourGreen)) 261 continue 262 } 263 i, err := strconv.ParseUint(parts[1], 10, 64) 264 if err != nil { 265 out(colour("I don't understand %q. It's not a number.\n", colourRed), i) 266 continue 267 } 268 if int(i) < 0 || int(i) > len(points[index].Args) - 1 { 269 out(colour("I can't dump it. It's out of range.\n", colourRed)) 270 continue 271 } 272 a := points[index].Args[int(i)] 273 dump(a.Hex, reflect.ValueOf(a.Value), c) 274 case "quick", "q": 275 for pi, p := range points { 276 args := "" 277 for i, a := range p.Args { 278 if i > 0 { 279 args = args + ", " 280 } 281 args = args + colour(a.Hex, colourGreen) 282 } 283 out("%s %s(%s) %s%s\n",colour(fmt.Sprintf("[%d]", pi), colourRed), colour(p.Func, colourBlue), args, colour(p.File, colourBlue), colour(":" + p.Line, colourRed)) 284 } 285 case "jump", "j": 286 if len(parts) <= 0 { 287 out(colour("Not quite sure what I need to jump to?!\n", colourRed)) 288 out(colour(help, colourGreen)) 289 continue 290 } 291 i, err := strconv.ParseUint(parts[1], 10, 64) 292 if err != nil { 293 out(colour("I don't understand %q. It's not a number.\n", colourRed), i) 294 continue 295 } 296 if int(i) < 0 || int(i) > len(points) - 1 { 297 out(colour("I can't jump that far. It's out of range.\n", colourRed)) 298 continue 299 } 300 index = int(i) 301 points[index].out(c) 302 case "open", "o": 303 editor := os.Getenv("EDITOR") 304 cmd := exec.Command(editor, points[index].File, "+" + points[index].Line) 305 cmd.Stdout = stdout 306 cmd.Stderr = stdout 307 cmd.Stdin = os.Stdin 308 err := cmd.Run() 309 if err != nil { 310 out(colour(err.Error()+"\n", colourRed)) 311 } 312 default: 313 out(colour(help, colourGreen)) 314 } 315 } 316 } 317 318 func (p *point) out(c Conf) { 319 colour := colourer(c) 320 out := outer(c) 321 322 out("position: %s\n", colour(p.Position, colourRed)) 323 out("identifier: %s\n", colour(p.Identifier, colourRed)) 324 out("package: %s\n", colour(p.Package, colourGreen)) 325 out("receiver: %s\n", colour(p.Receiver, colourGreen)) 326 out("function: %s\n", colour(p.Func, colourGreen)) 327 out("args: \n") 328 for i, a := range p.Args { 329 out(colour(" [%d] %s %+v\n", colourRed), i, colour(a.Hex, colourGreen), a.Value) 330 } 331 out("file: %s%s\n", colour(p.File, colourBlue), colour(":" + p.Line, colourRed)) 332 }