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 // -----------------------------------------------------------------------------