Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

243 linhas
5.9 KiB

  1. // Copyright 2014 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package binutils
  15. import (
  16. "bufio"
  17. "fmt"
  18. "io"
  19. "os/exec"
  20. "strconv"
  21. "strings"
  22. "sync"
  23. "github.com/google/pprof/internal/plugin"
  24. )
  25. const (
  26. defaultAddr2line = "addr2line"
  27. // addr2line may produce multiple lines of output. We
  28. // use this sentinel to identify the end of the output.
  29. sentinel = ^uint64(0)
  30. )
  31. // addr2Liner is a connection to an addr2line command for obtaining
  32. // address and line number information from a binary.
  33. type addr2Liner struct {
  34. mu sync.Mutex
  35. rw lineReaderWriter
  36. base uint64
  37. // nm holds an addr2Liner using nm tool. Certain versions of addr2line
  38. // produce incomplete names due to
  39. // https://sourceware.org/bugzilla/show_bug.cgi?id=17541. As a workaround,
  40. // the names from nm are used when they look more complete. See addrInfo()
  41. // code below for the exact heuristic.
  42. nm *addr2LinerNM
  43. }
  44. // lineReaderWriter is an interface to abstract the I/O to an addr2line
  45. // process. It writes a line of input to the job, and reads its output
  46. // one line at a time.
  47. type lineReaderWriter interface {
  48. write(string) error
  49. readLine() (string, error)
  50. close()
  51. }
  52. type addr2LinerJob struct {
  53. cmd *exec.Cmd
  54. in io.WriteCloser
  55. out *bufio.Reader
  56. }
  57. func (a *addr2LinerJob) write(s string) error {
  58. _, err := fmt.Fprint(a.in, s+"\n")
  59. return err
  60. }
  61. func (a *addr2LinerJob) readLine() (string, error) {
  62. return a.out.ReadString('\n')
  63. }
  64. // close releases any resources used by the addr2liner object.
  65. func (a *addr2LinerJob) close() {
  66. a.in.Close()
  67. a.cmd.Wait()
  68. }
  69. // newAddr2liner starts the given addr2liner command reporting
  70. // information about the given executable file. If file is a shared
  71. // library, base should be the address at which it was mapped in the
  72. // program under consideration.
  73. func newAddr2Liner(cmd, file string, base uint64) (*addr2Liner, error) {
  74. if cmd == "" {
  75. cmd = defaultAddr2line
  76. }
  77. j := &addr2LinerJob{
  78. cmd: exec.Command(cmd, "-aif", "-e", file),
  79. }
  80. var err error
  81. if j.in, err = j.cmd.StdinPipe(); err != nil {
  82. return nil, err
  83. }
  84. outPipe, err := j.cmd.StdoutPipe()
  85. if err != nil {
  86. return nil, err
  87. }
  88. j.out = bufio.NewReader(outPipe)
  89. if err := j.cmd.Start(); err != nil {
  90. return nil, err
  91. }
  92. a := &addr2Liner{
  93. rw: j,
  94. base: base,
  95. }
  96. return a, nil
  97. }
  98. func (d *addr2Liner) readString() (string, error) {
  99. s, err := d.rw.readLine()
  100. if err != nil {
  101. return "", err
  102. }
  103. return strings.TrimSpace(s), nil
  104. }
  105. // readFrame parses the addr2line output for a single address. It
  106. // returns a populated plugin.Frame and whether it has reached the end of the
  107. // data.
  108. func (d *addr2Liner) readFrame() (plugin.Frame, bool) {
  109. funcname, err := d.readString()
  110. if err != nil {
  111. return plugin.Frame{}, true
  112. }
  113. if strings.HasPrefix(funcname, "0x") {
  114. // If addr2line returns a hex address we can assume it is the
  115. // sentinel. Read and ignore next two lines of output from
  116. // addr2line
  117. d.readString()
  118. d.readString()
  119. return plugin.Frame{}, true
  120. }
  121. fileline, err := d.readString()
  122. if err != nil {
  123. return plugin.Frame{}, true
  124. }
  125. linenumber := 0
  126. if funcname == "??" {
  127. funcname = ""
  128. }
  129. if fileline == "??:0" {
  130. fileline = ""
  131. } else {
  132. if i := strings.LastIndex(fileline, ":"); i >= 0 {
  133. // Remove discriminator, if present
  134. if disc := strings.Index(fileline, " (discriminator"); disc > 0 {
  135. fileline = fileline[:disc]
  136. }
  137. // If we cannot parse a number after the last ":", keep it as
  138. // part of the filename.
  139. if line, err := strconv.Atoi(fileline[i+1:]); err == nil {
  140. linenumber = line
  141. fileline = fileline[:i]
  142. }
  143. }
  144. }
  145. return plugin.Frame{
  146. Func: funcname,
  147. File: fileline,
  148. Line: linenumber}, false
  149. }
  150. func (d *addr2Liner) rawAddrInfo(addr uint64) ([]plugin.Frame, error) {
  151. d.mu.Lock()
  152. defer d.mu.Unlock()
  153. if err := d.rw.write(fmt.Sprintf("%x", addr-d.base)); err != nil {
  154. return nil, err
  155. }
  156. if err := d.rw.write(fmt.Sprintf("%x", sentinel)); err != nil {
  157. return nil, err
  158. }
  159. resp, err := d.readString()
  160. if err != nil {
  161. return nil, err
  162. }
  163. if !strings.HasPrefix(resp, "0x") {
  164. return nil, fmt.Errorf("unexpected addr2line output: %s", resp)
  165. }
  166. var stack []plugin.Frame
  167. for {
  168. frame, end := d.readFrame()
  169. if end {
  170. break
  171. }
  172. if frame != (plugin.Frame{}) {
  173. stack = append(stack, frame)
  174. }
  175. }
  176. return stack, err
  177. }
  178. // addrInfo returns the stack frame information for a specific program
  179. // address. It returns nil if the address could not be identified.
  180. func (d *addr2Liner) addrInfo(addr uint64) ([]plugin.Frame, error) {
  181. stack, err := d.rawAddrInfo(addr)
  182. if err != nil {
  183. return nil, err
  184. }
  185. // Certain versions of addr2line produce incomplete names due to
  186. // https://sourceware.org/bugzilla/show_bug.cgi?id=17541. Attempt to replace
  187. // the name with a better one from nm.
  188. if len(stack) > 0 && d.nm != nil {
  189. nm, err := d.nm.addrInfo(addr)
  190. if err == nil && len(nm) > 0 {
  191. // Last entry in frame list should match since it is non-inlined. As a
  192. // simple heuristic, we only switch to the nm-based name if it is longer
  193. // by 2 or more characters. We consider nm names that are longer by 1
  194. // character insignificant to avoid replacing foo with _foo on MacOS (for
  195. // unknown reasons read2line produces the former and nm produces the
  196. // latter on MacOS even though both tools are asked to produce mangled
  197. // names).
  198. nmName := nm[len(nm)-1].Func
  199. a2lName := stack[len(stack)-1].Func
  200. if len(nmName) > len(a2lName)+1 {
  201. stack[len(stack)-1].Func = nmName
  202. }
  203. }
  204. }
  205. return stack, nil
  206. }