| // Copyright 2020 The Go Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style | |
| // license that can be found in the LICENSE file. | |
| // Package pkgpath determines the package path used by gccgo/GoLLVM symbols. | |
| // This package is not used for the gc compiler. | |
| package pkgpath | |
| import ( | |
| "bytes" | |
| "errors" | |
| "fmt" | |
| "os" | |
| "os/exec" | |
| "strings" | |
| ) | |
| // ToSymbolFunc returns a function that may be used to convert a | |
| // package path into a string suitable for use as a symbol. | |
| // cmd is the gccgo/GoLLVM compiler in use, and tmpdir is a temporary | |
| // directory to pass to os.CreateTemp(). | |
| // For example, this returns a function that converts "net/http" | |
| // into a string like "net..z2fhttp". The actual string varies for | |
| // different gccgo/GoLLVM versions, which is why this returns a function | |
| // that does the conversion appropriate for the compiler in use. | |
| func ToSymbolFunc(cmd, tmpdir string) (func(string) string, error) { | |
| // To determine the scheme used by cmd, we compile a small | |
| // file and examine the assembly code. Older versions of gccgo | |
| // use a simple mangling scheme where there can be collisions | |
| // between packages whose paths are different but mangle to | |
| // the same string. More recent versions use a new mangler | |
| // that avoids these collisions. | |
| const filepat = "*_gccgo_manglechck.go" | |
| f, err := os.CreateTemp(tmpdir, filepat) | |
| if err != nil { | |
| return nil, err | |
| } | |
| gofilename := f.Name() | |
| f.Close() | |
| defer os.Remove(gofilename) | |
| if err := os.WriteFile(gofilename, []byte(mangleCheckCode), 0644); err != nil { | |
| return nil, err | |
| } | |
| command := exec.Command(cmd, "-S", "-o", "-", gofilename) | |
| buf, err := command.Output() | |
| if err != nil { | |
| return nil, err | |
| } | |
| // Original mangling: go.l__ufer.Run | |
| // Mangling v2: go.l..u00e4ufer.Run | |
| // Mangling v3: go_0l_u00e4ufer.Run | |
| if bytes.Contains(buf, []byte("go_0l_u00e4ufer.Run")) { | |
| return toSymbolV3, nil | |
| } else if bytes.Contains(buf, []byte("go.l..u00e4ufer.Run")) { | |
| return toSymbolV2, nil | |
| } else if bytes.Contains(buf, []byte("go.l__ufer.Run")) { | |
| return toSymbolV1, nil | |
| } else { | |
| return nil, errors.New(cmd + ": unrecognized mangling scheme") | |
| } | |
| } | |
| // mangleCheckCode is the package we compile to determine the mangling scheme. | |
| const mangleCheckCode = ` | |
| package läufer | |
| func Run(x int) int { | |
| return 1 | |
| } | |
| ` | |
| // toSymbolV1 converts a package path using the original mangling scheme. | |
| func toSymbolV1(ppath string) string { | |
| clean := func(r rune) rune { | |
| switch { | |
| case 'A' <= r && r <= 'Z', 'a' <= r && r <= 'z', | |
| '0' <= r && r <= '9': | |
| return r | |
| } | |
| return '_' | |
| } | |
| return strings.Map(clean, ppath) | |
| } | |
| // toSymbolV2 converts a package path using the second mangling scheme. | |
| func toSymbolV2(ppath string) string { | |
| var bsl strings.Builder | |
| changed := false | |
| for _, c := range ppath { | |
| if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '_' { | |
| bsl.WriteByte(byte(c)) | |
| continue | |
| } | |
| var enc string | |
| switch { | |
| case c == '.': | |
| enc = ".x2e" | |
| case c < 0x80: | |
| enc = fmt.Sprintf("..z%02x", c) | |
| case c < 0x10000: | |
| enc = fmt.Sprintf("..u%04x", c) | |
| default: | |
| enc = fmt.Sprintf("..U%08x", c) | |
| } | |
| bsl.WriteString(enc) | |
| changed = true | |
| } | |
| if !changed { | |
| return ppath | |
| } | |
| return bsl.String() | |
| } | |
| // v3UnderscoreCodes maps from a character that supports an underscore | |
| // encoding to the underscore encoding character. | |
| var v3UnderscoreCodes = map[byte]byte{ | |
| '_': '_', | |
| '.': '0', | |
| '/': '1', | |
| '*': '2', | |
| ',': '3', | |
| '{': '4', | |
| '}': '5', | |
| '[': '6', | |
| ']': '7', | |
| '(': '8', | |
| ')': '9', | |
| '"': 'a', | |
| ' ': 'b', | |
| ';': 'c', | |
| } | |
| // toSymbolV3 converts a package path using the third mangling scheme. | |
| func toSymbolV3(ppath string) string { | |
| var bsl strings.Builder | |
| changed := false | |
| for _, c := range ppath { | |
| if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') { | |
| bsl.WriteByte(byte(c)) | |
| continue | |
| } | |
| if c < 0x80 { | |
| if u, ok := v3UnderscoreCodes[byte(c)]; ok { | |
| bsl.WriteByte('_') | |
| bsl.WriteByte(u) | |
| changed = true | |
| continue | |
| } | |
| } | |
| var enc string | |
| switch { | |
| case c < 0x80: | |
| enc = fmt.Sprintf("_x%02x", c) | |
| case c < 0x10000: | |
| enc = fmt.Sprintf("_u%04x", c) | |
| default: | |
| enc = fmt.Sprintf("_U%08x", c) | |
| } | |
| bsl.WriteString(enc) | |
| changed = true | |
| } | |
| if !changed { | |
| return ppath | |
| } | |
| return bsl.String() | |
| } |