// 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. 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 } // 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 != "" { if strings.Contains(url.Path, "/debug/pprof/") { url.Path = path.Clean(url.Path + "/../symbol") } else { 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 := int64(l.Address) + offset if addr < 0 { return fmt.Errorf("unexpected negative adjusted address, mapping %v source %d, offset %d", l.Mapping, l.Address, offset) } 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 { addr, err := strconv.ParseInt(symbol[1], 0, 64) if err != nil { return fmt.Errorf("unexpected parse failure %s: %v", symbol[1], err) } if addr < 0 { return fmt.Errorf("unexpected negative adjusted address, source %s, offset %d", symbol[1], offset) } // Reapply offset expected by the profile. addr -= 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[uint64(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 }