github.com/goplus/llgo@v0.8.3/xtool/nm/nm.go (about)

     1  /*
     2   * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package nm
    18  
    19  import (
    20  	"bytes"
    21  	"errors"
    22  	"fmt"
    23  	"os"
    24  	"os/exec"
    25  	"strings"
    26  )
    27  
    28  var (
    29  	errInvalidOutput = errors.New("invalid nm output")
    30  )
    31  
    32  // -----------------------------------------------------------------------------
    33  
    34  // Cmd represents a nm command.
    35  type Cmd struct {
    36  	app string
    37  }
    38  
    39  // New creates a new nm command.
    40  func New(app string) *Cmd {
    41  	if app == "" {
    42  		app = "nm"
    43  	}
    44  	return &Cmd{app}
    45  }
    46  
    47  // -----------------------------------------------------------------------------
    48  
    49  // SymbolType represents a symbol type.
    50  type SymbolType uint8
    51  
    52  const (
    53  	Undefined = SymbolType('U') // Undefined
    54  	Text      = SymbolType('T') // Text (code) section symbol
    55  	Data      = SymbolType('D') // Data (global var) section symbol
    56  	Rodata    = SymbolType('R') // Read-only data (rodata) section symbol
    57  	BSS       = SymbolType('B') // BSS (uninitialized global var) section symbol
    58  
    59  	LocalText = SymbolType('t') // Local text (code) section symbol
    60  	LocalData = SymbolType('d') // Local data (local var) section symbol
    61  	LocalBSS  = SymbolType('b') // Local BSS (uninitialized local var) section symbol
    62  	LocalASym = SymbolType('s') // Local symbol in an assembler source file
    63  )
    64  
    65  // Symbol represents a symbol in an object file.
    66  type Symbol struct {
    67  	Name  string     // symbol name
    68  	Addr  uint64     // symbol address
    69  	Type  SymbolType // symbol type
    70  	FAddr bool       // address is valid
    71  }
    72  
    73  // ObjectFile represents an object file.
    74  type ObjectFile struct {
    75  	File    string    // file name
    76  	Symbols []*Symbol // symbols
    77  }
    78  
    79  // List lists symbols in an archive file.
    80  func (p *Cmd) List(arfile string) (items []*ObjectFile, err error) {
    81  	var stdout bytes.Buffer
    82  	var stderr bytes.Buffer
    83  	cmd := exec.Command(p.app, arfile)
    84  	cmd.Stdout = &stdout
    85  	cmd.Stderr = &stderr
    86  	e := cmd.Run()
    87  	if stderr.Len() > 0 {
    88  		listError(stderr.Bytes())
    89  	}
    90  	items, err = listOutput(stdout.Bytes())
    91  	if err == nil {
    92  		err = e
    93  	}
    94  	return
    95  }
    96  
    97  func listError(data []byte) {
    98  	sep := []byte{'\n'}
    99  	nosym := []byte(": no symbols")
   100  	lines := bytes.Split(data, sep)
   101  	for _, line := range lines {
   102  		if len(line) == 0 || bytes.HasSuffix(line, nosym) {
   103  			continue
   104  		}
   105  		os.Stderr.Write(line)
   106  		os.Stderr.Write(sep)
   107  	}
   108  }
   109  
   110  func listOutput(data []byte) (items []*ObjectFile, err error) {
   111  	sep := []byte{'\n'}
   112  	item := &ObjectFile{}
   113  	lines := bytes.Split(data, sep)
   114  	for _, line := range lines {
   115  		if len(line) == 0 {
   116  			if item.File == "" && len(item.Symbols) > 0 {
   117  				items = append(items, item)
   118  			}
   119  			item = nil
   120  			continue
   121  		}
   122  		if item == nil {
   123  			s := string(line)
   124  			if strings.HasSuffix(s, ":") {
   125  				item = &ObjectFile{File: s[:len(s)-1]}
   126  				items = append(items, item)
   127  				continue
   128  			}
   129  			err = errInvalidOutput
   130  			return
   131  		}
   132  		if len(line) < 10 {
   133  			err = errInvalidOutput
   134  			return
   135  		}
   136  		var sym *Symbol
   137  		if is64bits(line) {
   138  			sym = &Symbol{
   139  				Name: string(line[19:]),
   140  				Type: SymbolType(line[17]),
   141  			}
   142  			if sym.FAddr = hasAddr(line); sym.FAddr {
   143  				sym.Addr = hexUint64(line)
   144  			}
   145  		} else {
   146  			sym = &Symbol{
   147  				Name: string(line[11:]),
   148  				Type: SymbolType(line[9]),
   149  			}
   150  			if sym.FAddr = hasAddr(line); sym.FAddr {
   151  				sym.Addr = uint64(hexUint32(line))
   152  			}
   153  		}
   154  		item.Symbols = append(item.Symbols, sym)
   155  	}
   156  	return
   157  }
   158  
   159  func hasAddr(line []byte) bool {
   160  	c := line[0]
   161  	return c != ' ' && c != '-'
   162  }
   163  
   164  func is64bits(line []byte) bool {
   165  	if line[0] != ' ' {
   166  		return line[8] != ' '
   167  	}
   168  	return line[9] == ' '
   169  }
   170  
   171  func hexUint64(b []byte) uint64 {
   172  	defer func() {
   173  		if e := recover(); e != nil {
   174  			fmt.Fprintln(os.Stderr, "-->", string(b))
   175  			panic(e)
   176  		}
   177  	}()
   178  	_ = b[15] // bounds check hint to compiler; see golang.org/issue/14808
   179  	return hex(b[15]) | hex(b[14])<<4 | hex(b[13])<<8 | hex(b[12])<<12 |
   180  		hex(b[11])<<16 | hex(b[10])<<20 | hex(b[9])<<24 | hex(b[8])<<28 |
   181  		hex(b[7])<<32 | hex(b[6])<<36 | hex(b[5])<<40 | hex(b[4])<<44 |
   182  		hex(b[3])<<48 | hex(b[2])<<52 | hex(b[1])<<56 | hex(b[0])<<60
   183  }
   184  
   185  func hexUint32(b []byte) uint64 {
   186  	defer func() {
   187  		if e := recover(); e != nil {
   188  			fmt.Fprintln(os.Stderr, "-->", string(b))
   189  			panic(e)
   190  		}
   191  	}()
   192  	_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
   193  	return hex(b[7]) | hex(b[6])<<4 | hex(b[5])<<8 | hex(b[4])<<12 |
   194  		hex(b[3])<<16 | hex(b[2])<<20 | hex(b[1])<<24 | hex(b[0])<<28
   195  }
   196  
   197  func hex(b byte) uint64 {
   198  	return hexTable[b]
   199  }
   200  
   201  var hexTable = []uint64{
   202  	'0': 0, '1': 1, '2': 2, '3': 3, '4': 4,
   203  	'5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
   204  	'a': 10, 'b': 11, 'c': 12, 'd': 13, 'e': 14, 'f': 15,
   205  	'A': 10, 'B': 11, 'C': 12, 'D': 13, 'E': 14, 'F': 15,
   206  }
   207  
   208  // -----------------------------------------------------------------------------