1package gdb 2 3import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "fmt" 8 "strconv" 9 "strings" 10) 11 12// NotificationCallback is a callback used to report the notifications that GDB 13// send asynchronously through the MI2 interface. Responses to the commands are 14// not part of these notifications. The notification generic object contains the 15// notification sent by GDB. 16type NotificationCallback func(notification map[string]interface{}) 17 18// Send issues a command to GDB. Operation is the name of the MI2 command to 19// execute without the leading "-" (this means that it is impossible send a CLI 20// command), arguments is an optional list of arguments, in GDB parlance the can 21// be: options, parameters or "--". It returns a generic object which represents 22// the reply of GDB or an error in case the command cannot be delivered to GDB. 23func (gdb *Gdb) Send(operation string, arguments ...string) (map[string]interface{}, error) { 24 // atomically increase the sequence number and queue a pending command 25 pending := make(chan map[string]interface{}) 26 gdb.mutex.Lock() 27 sequence := strconv.FormatInt(gdb.sequence, 10) 28 gdb.pending[sequence] = pending 29 gdb.sequence++ 30 gdb.mutex.Unlock() 31 32 // prepare the command 33 buffer := bytes.NewBufferString(fmt.Sprintf("%s-%s", sequence, operation)) 34 for _, argument := range arguments { 35 buffer.WriteByte(' ') 36 // quote the argument only if needed because GDB interprets un/quoted 37 // values differently in some contexts, e.g., when the value is a 38 // number('1' vs '"1"') or an option ('--thread' vs '"--thread"') 39 if strings.ContainsAny(argument, "\a\b\f\n\r\t\v\\'\" ") { 40 argument = strconv.Quote(argument) 41 } 42 buffer.WriteString(argument) 43 } 44 buffer.WriteByte('\n') 45 46 // send the command 47 if _, err := gdb.stdin.Write(buffer.Bytes()); err != nil { 48 return nil, err 49 } 50 51 // wait for a response 52 result := <-pending 53 gdb.mutex.Lock() 54 delete(gdb.pending, sequence) 55 gdb.mutex.Unlock() 56 return result, nil 57} 58 59// CheckedSend works like Send, except that if the result returned by 60// gdb has class=error, CheckedSend returns a non-nil error value 61// (containing the gdb error message) 62func (gdb *Gdb) CheckedSend(operation string, arguments ...string) (map[string]interface{}, error) { 63 result, err := gdb.Send(operation, arguments...) 64 65 if err != nil { 66 return nil, err 67 } 68 69 if result["class"] == "error" { 70 if payload, isMap := result["payload"].(map[string]interface{}); isMap { 71 if msg, isString := payload["msg"].(string); isString { 72 return nil, errors.New(msg) 73 } 74 } 75 // Class is error, but no message? Stringify the entire 76 // result as the error message then 77 return nil, errors.New("Unknown gdb error: " + fmt.Sprint(result["payload"])) 78 } 79 80 return result, nil 81} 82 83func (gdb *Gdb) recordReader() { 84 scanner := bufio.NewScanner(gdb.stdout) 85 for scanner.Scan() { 86 // scan the GDB output one line at a time skipping the GDB terminator 87 line := scanner.Text() 88 if line == terminator { 89 continue 90 } 91 92 // parse the line and distinguish between command result and 93 // notification 94 record := parseRecord(line) 95 sequence, isResult := record[sequenceKey] 96 if isResult { 97 // if it is a result record remove the sequence field and complete 98 // the pending command 99 delete(record, sequenceKey) 100 gdb.mutex.RLock() 101 pending := gdb.pending[sequence.(string)] 102 gdb.mutex.RUnlock() 103 pending <- record 104 } else { 105 if gdb.onNotification != nil { 106 gdb.onNotification(record) 107 } 108 } 109 } 110 if err := scanner.Err(); err != nil { 111 panic(err) 112 } 113 gdb.recordReaderDone <- true 114} 115