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 15// Package symbolz symbolizes a profile using the output from the symbolz 16// service. 17package symbolz 18 19import ( 20 "bytes" 21 "fmt" 22 "io" 23 "net/url" 24 "path" 25 "regexp" 26 "strconv" 27 "strings" 28 29 "github.com/google/pprof/internal/plugin" 30 "github.com/google/pprof/profile" 31) 32 33var ( 34 symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`) 35) 36 37// Symbolize symbolizes profile p by parsing data returned by a symbolz 38// handler. syms receives the symbolz query (hex addresses separated by '+') 39// and returns the symbolz output in a string. If force is false, it will only 40// symbolize locations from mappings not already marked as HasFunctions. Never 41// attempts symbolization of addresses from unsymbolizable system 42// mappings as those may look negative - e.g. "[vsyscall]". 43func Symbolize(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error { 44 for _, m := range p.Mapping { 45 if !force && m.HasFunctions { 46 // Only check for HasFunctions as symbolz only populates function names. 47 continue 48 } 49 // Skip well-known system mappings. 50 if m.Unsymbolizable() { 51 continue 52 } 53 mappingSources := sources[m.File] 54 if m.BuildID != "" { 55 mappingSources = append(mappingSources, sources[m.BuildID]...) 56 } 57 for _, source := range mappingSources { 58 if symz := symbolz(source.Source); symz != "" { 59 if err := symbolizeMapping(symz, int64(source.Start)-int64(m.Start), syms, m, p); err != nil { 60 return err 61 } 62 m.HasFunctions = true 63 break 64 } 65 } 66 } 67 68 return nil 69} 70 71// hasGperftoolsSuffix checks whether path ends with one of the suffixes listed in 72// pprof_remote_servers.html from the gperftools distribution 73func hasGperftoolsSuffix(path string) bool { 74 suffixes := []string{ 75 "/pprof/heap", 76 "/pprof/growth", 77 "/pprof/profile", 78 "/pprof/pmuprofile", 79 "/pprof/contention", 80 } 81 for _, s := range suffixes { 82 if strings.HasSuffix(path, s) { 83 return true 84 } 85 } 86 return false 87} 88 89// symbolz returns the corresponding symbolz source for a profile URL. 90func symbolz(source string) string { 91 if url, err := url.Parse(source); err == nil && url.Host != "" { 92 // All paths in the net/http/pprof Go package contain /debug/pprof/ 93 if strings.Contains(url.Path, "/debug/pprof/") || hasGperftoolsSuffix(url.Path) { 94 url.Path = path.Clean(url.Path + "/../symbol") 95 } else { 96 url.Path = "/symbolz" 97 } 98 url.RawQuery = "" 99 return url.String() 100 } 101 102 return "" 103} 104 105// symbolizeMapping symbolizes locations belonging to a Mapping by querying 106// a symbolz handler. An offset is applied to all addresses to take care of 107// normalization occurred for merged Mappings. 108func symbolizeMapping(source string, offset int64, syms func(string, string) ([]byte, error), m *profile.Mapping, p *profile.Profile) error { 109 // Construct query of addresses to symbolize. 110 var a []string 111 for _, l := range p.Location { 112 if l.Mapping == m && l.Address != 0 && len(l.Line) == 0 { 113 // Compensate for normalization. 114 addr, overflow := adjust(l.Address, offset) 115 if overflow { 116 return fmt.Errorf("cannot adjust address %d by %d, it would overflow (mapping %v)", l.Address, offset, l.Mapping) 117 } 118 a = append(a, fmt.Sprintf("%#x", addr)) 119 } 120 } 121 122 if len(a) == 0 { 123 // No addresses to symbolize. 124 return nil 125 } 126 127 lines := make(map[uint64]profile.Line) 128 functions := make(map[string]*profile.Function) 129 130 b, err := syms(source, strings.Join(a, "+")) 131 if err != nil { 132 return err 133 } 134 135 buf := bytes.NewBuffer(b) 136 for { 137 l, err := buf.ReadString('\n') 138 139 if err != nil { 140 if err == io.EOF { 141 break 142 } 143 return err 144 } 145 146 if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 { 147 origAddr, err := strconv.ParseUint(symbol[1], 0, 64) 148 if err != nil { 149 return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err) 150 } 151 // Reapply offset expected by the profile. 152 addr, overflow := adjust(origAddr, -offset) 153 if overflow { 154 return fmt.Errorf("cannot adjust symbolz address %d by %d, it would overflow", origAddr, -offset) 155 } 156 157 name := symbol[2] 158 fn := functions[name] 159 if fn == nil { 160 fn = &profile.Function{ 161 ID: uint64(len(p.Function) + 1), 162 Name: name, 163 SystemName: name, 164 } 165 functions[name] = fn 166 p.Function = append(p.Function, fn) 167 } 168 169 lines[addr] = profile.Line{Function: fn} 170 } 171 } 172 173 for _, l := range p.Location { 174 if l.Mapping != m { 175 continue 176 } 177 if line, ok := lines[l.Address]; ok { 178 l.Line = []profile.Line{line} 179 } 180 } 181 182 return nil 183} 184 185// adjust shifts the specified address by the signed offset. It returns the 186// adjusted address. It signals that the address cannot be adjusted without an 187// overflow by returning true in the second return value. 188func adjust(addr uint64, offset int64) (uint64, bool) { 189 adj := uint64(int64(addr) + offset) 190 if offset < 0 { 191 if adj >= addr { 192 return 0, true 193 } 194 } else { 195 if adj < addr { 196 return 0, true 197 } 198 } 199 return adj, false 200} 201