You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

362 lines
9.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 symbolizer provides a routine to populate a profile with
  15. // symbol, file and line number information. It relies on the
  16. // addr2liner and demangle packages to do the actual work.
  17. package symbolizer
  18. import (
  19. "fmt"
  20. "io/ioutil"
  21. "net/http"
  22. "net/url"
  23. "path/filepath"
  24. "strings"
  25. "github.com/google/pprof/internal/binutils"
  26. "github.com/google/pprof/internal/plugin"
  27. "github.com/google/pprof/internal/symbolz"
  28. "github.com/google/pprof/profile"
  29. "github.com/ianlancetaylor/demangle"
  30. )
  31. // Symbolizer implements the plugin.Symbolize interface.
  32. type Symbolizer struct {
  33. Obj plugin.ObjTool
  34. UI plugin.UI
  35. Transport http.RoundTripper
  36. }
  37. // test taps for dependency injection
  38. var symbolzSymbolize = symbolz.Symbolize
  39. var localSymbolize = doLocalSymbolize
  40. var demangleFunction = Demangle
  41. // Symbolize attempts to symbolize profile p. First uses binutils on
  42. // local binaries; if the source is a URL it attempts to get any
  43. // missed entries using symbolz.
  44. func (s *Symbolizer) Symbolize(mode string, sources plugin.MappingSources, p *profile.Profile) error {
  45. remote, local, fast, force, demanglerMode := true, true, false, false, ""
  46. for _, o := range strings.Split(strings.ToLower(mode), ":") {
  47. switch o {
  48. case "":
  49. continue
  50. case "none", "no":
  51. return nil
  52. case "local":
  53. remote, local = false, true
  54. case "fastlocal":
  55. remote, local, fast = false, true, true
  56. case "remote":
  57. remote, local = true, false
  58. case "force":
  59. force = true
  60. default:
  61. switch d := strings.TrimPrefix(o, "demangle="); d {
  62. case "full", "none", "templates":
  63. demanglerMode = d
  64. force = true
  65. continue
  66. case "default":
  67. continue
  68. }
  69. s.UI.PrintErr("ignoring unrecognized symbolization option: " + mode)
  70. s.UI.PrintErr("expecting -symbolize=[local|fastlocal|remote|none][:force][:demangle=[none|full|templates|default]")
  71. }
  72. }
  73. var err error
  74. if local {
  75. // Symbolize locally using binutils.
  76. if err = localSymbolize(p, fast, force, s.Obj, s.UI); err != nil {
  77. s.UI.PrintErr("local symbolization: " + err.Error())
  78. }
  79. }
  80. if remote {
  81. post := func(source, post string) ([]byte, error) {
  82. return postURL(source, post, s.Transport)
  83. }
  84. if err = symbolzSymbolize(p, force, sources, post, s.UI); err != nil {
  85. return err // Ran out of options.
  86. }
  87. }
  88. demangleFunction(p, force, demanglerMode)
  89. return nil
  90. }
  91. // postURL issues a POST to a URL over HTTP.
  92. func postURL(source, post string, tr http.RoundTripper) ([]byte, error) {
  93. client := &http.Client{
  94. Transport: tr,
  95. }
  96. resp, err := client.Post(source, "application/octet-stream", strings.NewReader(post))
  97. if err != nil {
  98. return nil, fmt.Errorf("http post %s: %v", source, err)
  99. }
  100. defer resp.Body.Close()
  101. if resp.StatusCode != http.StatusOK {
  102. return nil, fmt.Errorf("http post %s: %v", source, statusCodeError(resp))
  103. }
  104. return ioutil.ReadAll(resp.Body)
  105. }
  106. func statusCodeError(resp *http.Response) error {
  107. if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
  108. // error is from pprof endpoint
  109. if body, err := ioutil.ReadAll(resp.Body); err == nil {
  110. return fmt.Errorf("server response: %s - %s", resp.Status, body)
  111. }
  112. }
  113. return fmt.Errorf("server response: %s", resp.Status)
  114. }
  115. // doLocalSymbolize adds symbol and line number information to all locations
  116. // in a profile. mode enables some options to control
  117. // symbolization.
  118. func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error {
  119. if fast {
  120. if bu, ok := obj.(*binutils.Binutils); ok {
  121. bu.SetFastSymbolization(true)
  122. }
  123. }
  124. mt, err := newMapping(prof, obj, ui, force)
  125. if err != nil {
  126. return err
  127. }
  128. defer mt.close()
  129. functions := make(map[profile.Function]*profile.Function)
  130. for _, l := range mt.prof.Location {
  131. m := l.Mapping
  132. segment := mt.segments[m]
  133. if segment == nil {
  134. // Nothing to do.
  135. continue
  136. }
  137. stack, err := segment.SourceLine(l.Address)
  138. if err != nil || len(stack) == 0 {
  139. // No answers from addr2line.
  140. continue
  141. }
  142. l.Line = make([]profile.Line, len(stack))
  143. l.IsFolded = false
  144. for i, frame := range stack {
  145. if frame.Func != "" {
  146. m.HasFunctions = true
  147. }
  148. if frame.File != "" {
  149. m.HasFilenames = true
  150. }
  151. if frame.Line != 0 {
  152. m.HasLineNumbers = true
  153. }
  154. f := &profile.Function{
  155. Name: frame.Func,
  156. SystemName: frame.Func,
  157. Filename: frame.File,
  158. }
  159. if fp := functions[*f]; fp != nil {
  160. f = fp
  161. } else {
  162. functions[*f] = f
  163. f.ID = uint64(len(mt.prof.Function)) + 1
  164. mt.prof.Function = append(mt.prof.Function, f)
  165. }
  166. l.Line[i] = profile.Line{
  167. Function: f,
  168. Line: int64(frame.Line),
  169. }
  170. }
  171. if len(stack) > 0 {
  172. m.HasInlineFrames = true
  173. }
  174. }
  175. return nil
  176. }
  177. // Demangle updates the function names in a profile with demangled C++
  178. // names, simplified according to demanglerMode. If force is set,
  179. // overwrite any names that appear already demangled.
  180. func Demangle(prof *profile.Profile, force bool, demanglerMode string) {
  181. if force {
  182. // Remove the current demangled names to force demangling
  183. for _, f := range prof.Function {
  184. if f.Name != "" && f.SystemName != "" {
  185. f.Name = f.SystemName
  186. }
  187. }
  188. }
  189. var options []demangle.Option
  190. switch demanglerMode {
  191. case "": // demangled, simplified: no parameters, no templates, no return type
  192. options = []demangle.Option{demangle.NoParams, demangle.NoTemplateParams}
  193. case "templates": // demangled, simplified: no parameters, no return type
  194. options = []demangle.Option{demangle.NoParams}
  195. case "full":
  196. options = []demangle.Option{demangle.NoClones}
  197. case "none": // no demangling
  198. return
  199. }
  200. // Copy the options because they may be updated by the call.
  201. o := make([]demangle.Option, len(options))
  202. for _, fn := range prof.Function {
  203. if fn.Name != "" && fn.SystemName != fn.Name {
  204. continue // Already demangled.
  205. }
  206. copy(o, options)
  207. if demangled := demangle.Filter(fn.SystemName, o...); demangled != fn.SystemName {
  208. fn.Name = demangled
  209. continue
  210. }
  211. // Could not demangle. Apply heuristics in case the name is
  212. // already demangled.
  213. name := fn.SystemName
  214. if looksLikeDemangledCPlusPlus(name) {
  215. if demanglerMode == "" || demanglerMode == "templates" {
  216. name = removeMatching(name, '(', ')')
  217. }
  218. if demanglerMode == "" {
  219. name = removeMatching(name, '<', '>')
  220. }
  221. }
  222. fn.Name = name
  223. }
  224. }
  225. // looksLikeDemangledCPlusPlus is a heuristic to decide if a name is
  226. // the result of demangling C++. If so, further heuristics will be
  227. // applied to simplify the name.
  228. func looksLikeDemangledCPlusPlus(demangled string) bool {
  229. if strings.Contains(demangled, ".<") { // Skip java names of the form "class.<init>"
  230. return false
  231. }
  232. return strings.ContainsAny(demangled, "<>[]") || strings.Contains(demangled, "::")
  233. }
  234. // removeMatching removes nested instances of start..end from name.
  235. func removeMatching(name string, start, end byte) string {
  236. s := string(start) + string(end)
  237. var nesting, first, current int
  238. for index := strings.IndexAny(name[current:], s); index != -1; index = strings.IndexAny(name[current:], s) {
  239. switch current += index; name[current] {
  240. case start:
  241. nesting++
  242. if nesting == 1 {
  243. first = current
  244. }
  245. case end:
  246. nesting--
  247. switch {
  248. case nesting < 0:
  249. return name // Mismatch, abort
  250. case nesting == 0:
  251. name = name[:first] + name[current+1:]
  252. current = first - 1
  253. }
  254. }
  255. current++
  256. }
  257. return name
  258. }
  259. // newMapping creates a mappingTable for a profile.
  260. func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
  261. mt := &mappingTable{
  262. prof: prof,
  263. segments: make(map[*profile.Mapping]plugin.ObjFile),
  264. }
  265. // Identify used mappings
  266. mappings := make(map[*profile.Mapping]bool)
  267. for _, l := range prof.Location {
  268. mappings[l.Mapping] = true
  269. }
  270. missingBinaries := false
  271. for midx, m := range prof.Mapping {
  272. if !mappings[m] {
  273. continue
  274. }
  275. // Do not attempt to re-symbolize a mapping that has already been symbolized.
  276. if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
  277. continue
  278. }
  279. if m.File == "" {
  280. if midx == 0 {
  281. ui.PrintErr("Main binary filename not available.")
  282. continue
  283. }
  284. missingBinaries = true
  285. continue
  286. }
  287. // Skip well-known system mappings
  288. if m.Unsymbolizable() {
  289. continue
  290. }
  291. // Skip mappings pointing to a source URL
  292. if m.BuildID == "" {
  293. if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") {
  294. continue
  295. }
  296. }
  297. name := filepath.Base(m.File)
  298. f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset)
  299. if err != nil {
  300. ui.PrintErr("Local symbolization failed for ", name, ": ", err)
  301. missingBinaries = true
  302. continue
  303. }
  304. if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
  305. ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch")
  306. f.Close()
  307. continue
  308. }
  309. mt.segments[m] = f
  310. }
  311. if missingBinaries {
  312. ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" +
  313. "Try setting PPROF_BINARY_PATH to the search path for local binaries.")
  314. }
  315. return mt, nil
  316. }
  317. // mappingTable contains the mechanisms for symbolization of a
  318. // profile.
  319. type mappingTable struct {
  320. prof *profile.Profile
  321. segments map[*profile.Mapping]plugin.ObjFile
  322. }
  323. // Close releases any external processes being used for the mapping.
  324. func (mt *mappingTable) close() {
  325. for _, segment := range mt.segments {
  326. segment.Close()
  327. }
  328. }