Files
monibuca/plugin/debug/pkg/internal/binutils/addr2liner_llvm.go
2024-12-16 20:06:39 +08:00

185 lines
4.8 KiB
Go

// 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"
"encoding/json"
"fmt"
"io"
"os/exec"
"strconv"
"strings"
"sync"
"m7s.live/v5/plugin/debug/pkg/internal/plugin"
)
const (
defaultLLVMSymbolizer = "llvm-symbolizer"
)
// llvmSymbolizer is a connection to an llvm-symbolizer command for
// obtaining address and line number information from a binary.
type llvmSymbolizer struct {
sync.Mutex
filename string
rw lineReaderWriter
base uint64
isData bool
}
type llvmSymbolizerJob struct {
cmd *exec.Cmd
in io.WriteCloser
out *bufio.Reader
// llvm-symbolizer requires the symbol type, CODE or DATA, for symbolization.
symType string
}
func (a *llvmSymbolizerJob) write(s string) error {
_, err := fmt.Fprintln(a.in, a.symType, s)
return err
}
func (a *llvmSymbolizerJob) readLine() (string, error) {
s, err := a.out.ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(s), nil
}
// close releases any resources used by the llvmSymbolizer object.
func (a *llvmSymbolizerJob) close() {
a.in.Close()
a.cmd.Wait()
}
// newLLVMSymbolizer starts the given llvmSymbolizer 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 newLLVMSymbolizer(cmd, file string, base uint64, isData bool) (*llvmSymbolizer, error) {
if cmd == "" {
cmd = defaultLLVMSymbolizer
}
j := &llvmSymbolizerJob{
cmd: exec.Command(cmd, "--inlining", "-demangle=false", "--output-style=JSON"),
symType: "CODE",
}
if isData {
j.symType = "DATA"
}
var err error
if j.in, err = j.cmd.StdinPipe(); err != nil {
return nil, err
}
outPipe, err := j.cmd.StdoutPipe()
if err != nil {
return nil, err
}
j.out = bufio.NewReader(outPipe)
if err := j.cmd.Start(); err != nil {
return nil, err
}
a := &llvmSymbolizer{
filename: file,
rw: j,
base: base,
isData: isData,
}
return a, nil
}
// readDataFrames parses the llvm-symbolizer DATA output for a single address. It
// returns a populated plugin.Frame array with a single entry.
func (d *llvmSymbolizer) readDataFrames() ([]plugin.Frame, error) {
line, err := d.rw.readLine()
if err != nil {
return nil, err
}
var frame struct {
Address string `json:"Address"`
ModuleName string `json:"ModuleName"`
Data struct {
Start string `json:"Start"`
Size string `json:"Size"`
Name string `json:"Name"`
} `json:"Data"`
}
if err := json.Unmarshal([]byte(line), &frame); err != nil {
return nil, err
}
// Match non-JSON output behaviour of stuffing the start/size into the filename of a single frame,
// with the size being a decimal value.
size, err := strconv.ParseInt(frame.Data.Size, 0, 0)
if err != nil {
return nil, err
}
var stack []plugin.Frame
stack = append(stack, plugin.Frame{Func: frame.Data.Name, File: fmt.Sprintf("%s %d", frame.Data.Start, size)})
return stack, nil
}
// readCodeFrames parses the llvm-symbolizer CODE output for a single address. It
// returns a populated plugin.Frame array.
func (d *llvmSymbolizer) readCodeFrames() ([]plugin.Frame, error) {
line, err := d.rw.readLine()
if err != nil {
return nil, err
}
var frame struct {
Address string `json:"Address"`
ModuleName string `json:"ModuleName"`
Symbol []struct {
Line int `json:"Line"`
Column int `json:"Column"`
FunctionName string `json:"FunctionName"`
FileName string `json:"FileName"`
StartLine int `json:"StartLine"`
} `json:"Symbol"`
}
if err := json.Unmarshal([]byte(line), &frame); err != nil {
return nil, err
}
var stack []plugin.Frame
for _, s := range frame.Symbol {
stack = append(stack, plugin.Frame{Func: s.FunctionName, File: s.FileName, Line: s.Line, Column: s.Column, StartLine: s.StartLine})
}
return stack, nil
}
// addrInfo returns the stack frame information for a specific program
// address. It returns nil if the address could not be identified.
func (d *llvmSymbolizer) addrInfo(addr uint64) ([]plugin.Frame, error) {
d.Lock()
defer d.Unlock()
if err := d.rw.write(fmt.Sprintf("%s 0x%x", d.filename, addr-d.base)); err != nil {
return nil, err
}
if d.isData {
return d.readDataFrames()
}
return d.readCodeFrames()
}