| // 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 binutils |
| |
| import ( |
| "bufio" |
| "bytes" |
| "io" |
| "os/exec" |
| "strconv" |
| "strings" |
| |
| "github.com/google/pprof/internal/plugin" |
| ) |
| |
| const ( |
| defaultNM = "nm" |
| ) |
| |
| // addr2LinerNM is a connection to an nm command for obtaining address |
| // information from a binary. |
| type addr2LinerNM struct { |
| m []symbolInfo // Sorted list of addresses from binary. |
| } |
| |
| type symbolInfo struct { |
| address uint64 |
| name string |
| } |
| |
| // newAddr2LinerNM starts the given nm command reporting information about the |
| // given executable file. If file is a shared library, base should be |
| // the address at which it was mapped in the program under |
| // consideration. |
| func newAddr2LinerNM(cmd, file string, base uint64) (*addr2LinerNM, error) { |
| if cmd == "" { |
| cmd = defaultNM |
| } |
| var b bytes.Buffer |
| c := exec.Command(cmd, "-n", file) |
| c.Stdout = &b |
| if err := c.Run(); err != nil { |
| return nil, err |
| } |
| return parseAddr2LinerNM(base, &b) |
| } |
| |
| func parseAddr2LinerNM(base uint64, nm io.Reader) (*addr2LinerNM, error) { |
| a := &addr2LinerNM{ |
| m: []symbolInfo{}, |
| } |
| |
| // Parse nm output and populate symbol map. |
| // Skip lines we fail to parse. |
| buf := bufio.NewReader(nm) |
| for { |
| line, err := buf.ReadString('\n') |
| if line == "" && err != nil { |
| if err == io.EOF { |
| break |
| } |
| return nil, err |
| } |
| line = strings.TrimSpace(line) |
| fields := strings.SplitN(line, " ", 3) |
| if len(fields) != 3 { |
| continue |
| } |
| address, err := strconv.ParseUint(fields[0], 16, 64) |
| if err != nil { |
| continue |
| } |
| a.m = append(a.m, symbolInfo{ |
| address: address + base, |
| name: fields[2], |
| }) |
| } |
| |
| return a, nil |
| } |
| |
| // addrInfo returns the stack frame information for a specific program |
| // address. It returns nil if the address could not be identified. |
| func (a *addr2LinerNM) addrInfo(addr uint64) ([]plugin.Frame, error) { |
| if len(a.m) == 0 || addr < a.m[0].address || addr > a.m[len(a.m)-1].address { |
| return nil, nil |
| } |
| |
| // Binary search. Search until low, high are separated by 1. |
| low, high := 0, len(a.m) |
| for low+1 < high { |
| mid := (low + high) / 2 |
| v := a.m[mid].address |
| if addr == v { |
| low = mid |
| break |
| } else if addr > v { |
| low = mid |
| } else { |
| high = mid |
| } |
| } |
| |
| // Address is between a.m[low] and a.m[high]. |
| // Pick low, as it represents [low, high). |
| f := []plugin.Frame{ |
| { |
| Func: a.m[low].name, |
| }, |
| } |
| return f, nil |
| } |