diff --git a/chore/nmindex/nmindex.go b/chore/nmindex/nmindex.go index afeffef5..b20bc53e 100644 --- a/chore/nmindex/nmindex.go +++ b/chore/nmindex/nmindex.go @@ -43,7 +43,11 @@ The commands are: case "mk": makeIndex() case "q": - query() + if len(os.Args) < 3 { + fmt.Fprint(os.Stderr, "Usage: nmindex q \n") + return + } + query(os.Args[2]) default: fmt.Fprintf(os.Stderr, "unknown command: %s\n", cmd) } @@ -51,10 +55,7 @@ The commands are: func makeIndex() { env := llvm.New() - - home, err := os.UserHomeDir() - check(err) - idxDir := home + "/.llgo/nmindex" + idxDir := indexDir() os.MkdirAll(idxDir, 0755) b := nm.NewIndexBuilder(env.Nm()) @@ -64,14 +65,27 @@ func makeIndex() { stdLib("LLGO_STDROOT"), stdLib("LLGO_USRROOT"), } - err = b.Index(libDirs, idxDir, func(path string) { + err := b.Index(libDirs, idxDir, func(path string) { fmt.Println("==>", path) }) check(err) } -func query() { - panic("todo") +func query(q string) { + files, err := nm.Query(indexDir(), q) + check(err) + for _, f := range files { + fmt.Printf("%s:\n", f.ArFile) + for _, item := range f.Items { + fmt.Printf(" %c %s %s\n", item.Type, item.Symbol, item.ObjFile) + } + } +} + +func indexDir() string { + home, err := os.UserHomeDir() + check(err) + return home + "/.llgo/nmindex" } func stdLib(where string) string { diff --git a/x/nm/index.go b/x/nm/index.go index f5af7f50..b53ade38 100644 --- a/x/nm/index.go +++ b/x/nm/index.go @@ -81,11 +81,16 @@ func (p *IndexBuilder) IndexFile(arFile, outFile string) (err error) { return } var b bytes.Buffer - b.WriteString("file ") + b.WriteString("nm ") b.WriteString(arFile) b.WriteByte('\n') nbase := b.Len() for _, item := range items { + if item.File != "" { + b.WriteString("file ") + b.WriteString(item.File) + b.WriteByte('\n') + } for _, sym := range item.Symbols { switch sym.Type { case Text, Data, BSS, Rodata, 'S', 'C', 'W', 'A': diff --git a/x/nm/query.go b/x/nm/query.go new file mode 100644 index 00000000..f39c19bc --- /dev/null +++ b/x/nm/query.go @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). 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 nm + +import ( + "bufio" + "os" + "strings" +) + +// MatchedItem represents a matched item +type MatchedItem struct { + ObjFile string + Symbol string + Type SymbolType +} + +// MatchedFile represents a matched file +type MatchedFile struct { + ArFile string + Items []*MatchedItem +} + +// Query queries symbol in index files (allow wildcard). +func Query(dir string, query string) (files []*MatchedFile, err error) { + fis, err := os.ReadDir(dir) + if err != nil { + return + } + dir += "/" + for _, fi := range fis { + if fi.IsDir() { + continue + } + idxFile := fi.Name() + if !strings.HasSuffix(idxFile, ".pub") { + continue + } + files = queryIndex(files, dir+fi.Name(), query) + } + return +} + +func queryIndex(files []*MatchedFile, idxFile, query string) []*MatchedFile { + f, err := os.Open(idxFile) + if err != nil { + return files + } + defer f.Close() + + r := bufio.NewReader(f) + line, err := r.ReadString('\n') + if err != nil || !strings.HasPrefix(line, "nm ") { + return files + } + var items []*MatchedItem + arFile := line[3 : len(line)-1] + objFile := "" + query, flags := parseQuery(query) + for { + line, err = r.ReadString('\n') + if err != nil { + break + } + if strings.HasPrefix(line, "file ") { + objFile = line[5 : len(line)-1] + continue + } + typ := line[0] + sym := line[2 : len(line)-1] + if !match(sym, query, flags) { + continue + } + items = append(items, &MatchedItem{ + ObjFile: objFile, + Symbol: sym, + Type: SymbolType(typ), + }) + } + if len(items) > 0 { + files = append(files, &MatchedFile{ArFile: arFile, Items: items}) + } + return files +} + +const ( + flagSuffix = 1 << iota + flagPrefix +) + +func parseQuery(query string) (text string, flags int) { + if strings.HasSuffix(query, "*") { + query = query[:len(query)-1] + flags = flagPrefix + } + if strings.HasPrefix(query, "*") { + query = query[1:] + flags |= flagSuffix + } + text = query + return +} + +func match(s, query string, flags int) bool { + switch flags { + case 0: + return s == query + case flagPrefix: + return strings.HasPrefix(s, query) + case flagSuffix: + return strings.HasSuffix(s, query) + default: + return strings.Contains(s, query) + } +}