175 lines
4.5 KiB
Go
175 lines
4.5 KiB
Go
|
// 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"
|
||
|
"io/ioutil"
|
||
|
"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 ioutil.TempFile.
|
||
|
// 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 := ioutil.TempFile(tmpdir, filepat)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
gofilename := f.Name()
|
||
|
f.Close()
|
||
|
defer os.Remove(gofilename)
|
||
|
|
||
|
if err := ioutil.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 {
|
||
|
// This has to build at boostrap time, so it has to build
|
||
|
// with Go 1.4, so we don't use strings.Builder.
|
||
|
bsl := make([]byte, 0, len(ppath))
|
||
|
changed := false
|
||
|
for _, c := range ppath {
|
||
|
if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') || c == '_' {
|
||
|
bsl = append(bsl, 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 = append(bsl, enc...)
|
||
|
changed = true
|
||
|
}
|
||
|
if !changed {
|
||
|
return ppath
|
||
|
}
|
||
|
return string(bsl)
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
// This has to build at boostrap time, so it has to build
|
||
|
// with Go 1.4, so we don't use strings.Builder.
|
||
|
bsl := make([]byte, 0, len(ppath))
|
||
|
changed := false
|
||
|
for _, c := range ppath {
|
||
|
if ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || ('0' <= c && c <= '9') {
|
||
|
bsl = append(bsl, byte(c))
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if c < 0x80 {
|
||
|
if u, ok := v3UnderscoreCodes[byte(c)]; ok {
|
||
|
bsl = append(bsl, '_', 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 = append(bsl, enc...)
|
||
|
changed = true
|
||
|
}
|
||
|
if !changed {
|
||
|
return ppath
|
||
|
}
|
||
|
return string(bsl)
|
||
|
}
|