diff --git a/chore/nmdump/nmdump.go b/chore/nmdump/nmdump.go index c0a68042..4a0e2a8b 100644 --- a/chore/nmdump/nmdump.go +++ b/chore/nmdump/nmdump.go @@ -22,25 +22,41 @@ import ( "os" "github.com/goplus/llgo/xtool/env/llvm" + nmtool "github.com/goplus/llgo/xtool/nm" ) func main() { - if len(os.Args) != 2 { - fmt.Fprintln(os.Stderr, "Usage: nmdump libfile") + if len(os.Args) < 2 { + fmt.Fprintln(os.Stderr, "Usage: nmdump [flags] libfile") return } nm := llvm.New("").Nm() - items, err := nm.List(os.Args[1]) + + var flags []string + libfile := os.Args[len(os.Args)-1] + if len(os.Args) > 2 { + flags = os.Args[1 : len(os.Args)-1] + } + + items, err := nm.List(libfile, flags...) + for _, item := range items { if item.File != "" { fmt.Printf("\n%s:\n", item.File) } for _, sym := range item.Symbols { + var versionInfo string + switch sym.VersionType { + case nmtool.VersionSpecific: + versionInfo = fmt.Sprintf("@%s", sym.Version) + case nmtool.VersionDefault: + versionInfo = fmt.Sprintf("@@%s", sym.Version) + } if sym.FAddr { - fmt.Printf("%016x %c %s\n", sym.Addr, sym.Type, sym.Name) + fmt.Printf("%016x %c %s%s\n", sym.Addr, sym.Type, sym.Name, versionInfo) } else { - fmt.Printf("%16s %c %s\n", "", sym.Type, sym.Name) + fmt.Printf("%16s %c %s%s\n", "", sym.Type, sym.Name, versionInfo) } } } diff --git a/xtool/nm/nm.go b/xtool/nm/nm.go index 23ff57db..a1c44e0d 100644 --- a/xtool/nm/nm.go +++ b/xtool/nm/nm.go @@ -62,12 +62,26 @@ const ( LocalASym = SymbolType('s') // Local symbol in an assembler source file ) +// VersionType represents the version type of a symbol. +// This is specific to Linux systems. +// On macOS , this will always be VersionNone. +// https://sourceware.org/binutils/docs/binutils/nm.html +type VersionType int + +const ( + VersionNone VersionType = iota // No version information + VersionSpecific // Specific version (@) + VersionDefault // Default version (@@) +) + // Symbol represents a symbol in an object file. type Symbol struct { - Name string // symbol name - Addr uint64 // symbol address - Type SymbolType // symbol type - FAddr bool // address is valid + Name string // symbol name + Addr uint64 // symbol address + Type SymbolType // symbol type + FAddr bool // address is valid + VersionType VersionType // version type of the symbol + Version string // version information of the symbol } // ObjectFile represents an object file. @@ -76,11 +90,20 @@ type ObjectFile struct { Symbols []*Symbol // symbols } -// List lists symbols in an archive file. -func (p *Cmd) List(arfile string) (items []*ObjectFile, err error) { +// List lists symbols in an archive file +// accepts optional nm command flags. +// Note: The available flags may vary depending on the operating system. +// On Linux, the -D flag is used to display dynamic symbols from the dynamic symbol table. +// On macOS, there's no -D flag. The nm command displays all symbols (including dynamic ones) by default. +// This difference is due to the distinct ways Linux (using ELF format) and macOS (using Mach-O format) +// When working with dynamic libraries: +// On Linux: Use 'nm -D /path/to/library.so' +// On macOS: Simply use 'nm /path/to/library.dylib' +func (p *Cmd) List(arfile string, options ...string) (items []*ObjectFile, err error) { var stdout bytes.Buffer var stderr bytes.Buffer - cmd := exec.Command(p.app, arfile) + args := append(options, arfile) + cmd := exec.Command(p.app, args...) cmd.Stdout = &stdout cmd.Stderr = &stderr e := cmd.Run() @@ -134,28 +157,48 @@ func listOutput(data []byte) (items []*ObjectFile, err error) { return } var sym *Symbol + var fullSymName string if is64bits(line) { + fullSymName = string(line[19:]) sym = &Symbol{ - Name: string(line[19:]), Type: SymbolType(line[17]), } if sym.FAddr = hasAddr(line); sym.FAddr { sym.Addr = hexUint64(line) } } else { + fullSymName = string(line[11:]) sym = &Symbol{ - Name: string(line[11:]), Type: SymbolType(line[9]), } if sym.FAddr = hasAddr(line); sym.FAddr { sym.Addr = uint64(hexUint32(line)) } } + + sym.Name, sym.VersionType, sym.Version = parseSymName(fullSymName) item.Symbols = append(item.Symbols, sym) } return } +func parseSymName(symName string) (name string, versionType VersionType, version string) { + if idx := strings.LastIndex(symName, "@"); idx != -1 { + name = symName[:idx] + version = symName[idx+1:] + if idx > 0 && symName[idx-1] == '@' { + versionType = VersionDefault + name = symName[:idx-1] + } else { + versionType = VersionSpecific + } + } else { + name = symName + versionType = VersionNone + } + return +} + func hasAddr(line []byte) bool { c := line[0] return c != ' ' && c != '-'