// Copyright 2014 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package symbolz symbolizes a profile using the output from the symbolz // service. package symbolz import ( "bytes" "fmt" "io" "net/url" "path" "regexp" "strconv" "strings" "github.com/google/pprof/internal/plugin" "github.com/google/pprof/profile" ) var ( symbolzRE = regexp.MustCompile(`(0x[[:xdigit:]]+)\s+(.*)`) ) // Symbolize symbolizes profile p by parsing data returned by a symbolz // handler. syms receives the symbolz query (hex addresses separated by '+') // and returns the symbolz output in a string. If force is false, it will only // symbolize locations from mappings not already marked as HasFunctions. Does // not skip unsymbolizable files since the symbolz handler can be flexible // enough to handle some of those cases such as JIT locations in //anon. func Symbolize(p *profile.Profile, force bool, sources plugin.MappingSources, syms func(string, string) ([]byte, error), ui plugin.UI) error { for _, m := range p.Mapping { if !force && m.HasFunctions { // Only check for HasFunctions as symbolz only populates function names. continue } mappingSources := sources[m.File] if m.BuildID != "" { mappingSources = append(mappingSources, sources[m.BuildID]...) } for _, source := range mappingSources { if symz := symbolz(source.Source); symz != "" { if err := symbolizeMapping(symz, int64(source.Start)-int64(m.Start), syms, m, p); err != nil { return err } m.HasFunctions = true break } } } return nil } // hasGperftoolsSuffix checks whether path ends with one of the suffixes listed in // pprof_remote_servers.html from the gperftools distribution func hasGperftoolsSuffix(path string) bool { suffixes := []string{ "/pprof/heap", "/pprof/growth", "/pprof/profile", "/pprof/pmuprofile", "/pprof/contention", } for _, s := range suffixes { if strings.HasSuffix(path, s) { return true } } return false } // symbolz returns the corresponding symbolz source for a profile URL. func symbolz(source string) string { if url, err := url.Parse(source); err == nil && url.Host != "" { // All paths in the net/http/pprof Go package contain /debug/pprof/ if strings.Contains(url.Path, "/debug/pprof/") || hasGperftoolsSuffix(url.Path) { url.Path = path.Clean(url.Path + "/../symbol") } else { url.Path = path.Clean(url.Path + "/../symbolz") } url.RawQuery = "" return url.String() } return "" } // symbolizeMapping symbolizes locations belonging to a Mapping by querying // a symbolz handler. An offset is applied to all addresses to take care of // normalization occurred for merged Mappings. func symbolizeMapping(source string, offset int64, syms func(string, string) ([]byte, error), m *profile.Mapping, p *profile.Profile) error { // Construct query of addresses to symbolize. var a []string for _, l := range p.Location { if l.Mapping == m && l.Address != 0 && len(l.Line) == 0 { // Compensate for normalization. addr, overflow := adjust(l.Address, offset) if overflow { return fmt.Errorf("cannot adjust address %d by %d, it would overflow (mapping %v)", l.Address, offset, l.Mapping) } a = append(a, fmt.Sprintf("%#x", addr)) } } if len(a) == 0 { // No addresses to symbolize. return nil } lines := make(map[uint64]profile.Line) functions := make(map[string]*profile.Function) b, err := syms(source, strings.Join(a, "+")) if err != nil { return err } buf := bytes.NewBuffer(b) for { l, err := buf.ReadString('\n') if err != nil { if err == io.EOF { break } return err } if symbol := symbolzRE.FindStringSubmatch(l); len(symbol) == 3 { origAddr, err := strconv.ParseUint(symbol[1], 0, 64) if err != nil { return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err) } // Reapply offset expected by the profile. addr, overflow := adjust(origAddr, -offset) if overflow { return fmt.Errorf("cannot adjust symbolz address %d by %d, it would overflow", origAddr, -offset) } name := symbol[2] fn := functions[name] if fn == nil { fn = &profile.Function{ ID: uint64(len(p.Function) + 1), Name: name, SystemName: name, } functions[name] = fn p.Function = append(p.Function, fn) } lines[addr] = profile.Line{Function: fn} } } for _, l := range p.Location { if l.Mapping != m { continue } if line, ok := lines[l.Address]; ok { l.Line = []profile.Line{line} } } return nil } // adjust shifts the specified address by the signed offset. It returns the // adjusted address. It signals that the address cannot be adjusted without an // overflow by returning true in the second return value. func adjust(addr uint64, offset int64) (uint64, bool) { adj := uint64(int64(addr) + offset) if offset < 0 { if adj >= addr { return 0, true } } else { if adj < addr { return 0, true } } return adj, false }