mirror of
https://github.com/anchore/syft.git
synced 2026-08-19 16:48:27 +02:00
decode golang symbols (#5089)
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
29fd7d0dec
commit
c890e7f17f
@ -52,10 +52,7 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
|
||||
continue
|
||||
}
|
||||
seen[fn.Name] = struct{}{}
|
||||
syms = append(syms, binarySymbol{
|
||||
packagePath: fn.PackageName(),
|
||||
name: fn.Name,
|
||||
})
|
||||
syms = append(syms, makeBinarySymbol(fn.Name, fn.PackageName()))
|
||||
}
|
||||
|
||||
// debug/gosym only exposes top-level functions; functions that the compiler inlined into their
|
||||
@ -71,7 +68,7 @@ func getSymbols(r io.ReaderAt) (syms []binarySymbol, err error) {
|
||||
continue
|
||||
}
|
||||
seen[name] = struct{}{}
|
||||
syms = append(syms, binarySymbol{packagePath: pkgPath, name: name})
|
||||
syms = append(syms, makeBinarySymbol(name, pkgPath))
|
||||
}
|
||||
|
||||
return syms, nil
|
||||
@ -94,6 +91,55 @@ func packagePathFromSymbolName(name string) string {
|
||||
return name[:slash+1+dot]
|
||||
}
|
||||
|
||||
// makeBinarySymbol builds a binarySymbol, unescaping the %xx sequences the go linker introduces in the
|
||||
// import-path portion of a symbol name (see unescapePackagePath). The local-symbol suffix (method and
|
||||
// function names) is left untouched, so only the path prefix shared by packagePath and name is rewritten.
|
||||
func makeBinarySymbol(name, pkgPath string) binarySymbol {
|
||||
unescaped := unescapePackagePath(pkgPath)
|
||||
if unescaped != pkgPath && strings.HasPrefix(name, pkgPath) {
|
||||
name = unescaped + name[len(pkgPath):]
|
||||
}
|
||||
return binarySymbol{packagePath: unescaped, name: name}
|
||||
}
|
||||
|
||||
// unescapePackagePath reverses the escaping cmd/internal/objabi.PathToPrefix applies to import paths in
|
||||
// symbol names: bytes like '.' (at or after the final '/'), '%', '"', control bytes, and high bytes are
|
||||
// written as lowercase "%xx". For example "gopkg.in/yaml.v2" is stored as "gopkg.in/yaml%2ev2", so this
|
||||
// restores it before matching against the (unescaped) module paths from build info. A lone or malformed
|
||||
// '%' sequence is left as-is.
|
||||
func unescapePackagePath(path string) string {
|
||||
if !strings.Contains(path, "%") {
|
||||
return path
|
||||
}
|
||||
var b strings.Builder
|
||||
b.Grow(len(path))
|
||||
for i := 0; i < len(path); i++ {
|
||||
if path[i] == '%' && i+2 < len(path) {
|
||||
if hi, ok1 := unhex(path[i+1]); ok1 {
|
||||
if lo, ok2 := unhex(path[i+2]); ok2 {
|
||||
b.WriteByte(hi<<4 | lo)
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
b.WriteByte(path[i])
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func unhex(c byte) (byte, bool) {
|
||||
switch {
|
||||
case c >= '0' && c <= '9':
|
||||
return c - '0', true
|
||||
case c >= 'a' && c <= 'f':
|
||||
return c - 'a' + 10, true
|
||||
case c >= 'A' && c <= 'F':
|
||||
return c - 'A' + 10, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// nameWithoutTypeArgs strips the type-argument portion from an instantiated generic symbol name, e.g.
|
||||
// "foo/bar.Do[net/url.Values]" -> "foo/bar.Do". The slashes and dots inside the brackets would otherwise
|
||||
// corrupt package-path derivation (yielding "foo/bar.Do[net" for the example above). Mirrors
|
||||
|
||||
@ -97,6 +97,58 @@ func Test_moduleSymbols(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_escapedImportPathAttribution(t *testing.T) {
|
||||
// the go linker escapes the '.' in the last path element, so "gopkg.in/yaml.v2" appears in the symbol
|
||||
// table as "gopkg.in/yaml%2ev2". attribution must still land these under the unescaped module path.
|
||||
mainModule := &debug.Module{Path: "github.com/someorg/somecli"}
|
||||
deps := []*debug.Module{{Path: "gopkg.in/yaml.v2"}}
|
||||
|
||||
symbols := []binarySymbol{
|
||||
makeBinarySymbol("gopkg.in/yaml%2ev2.(*decoder).alias", "gopkg.in/yaml%2ev2"),
|
||||
makeBinarySymbol("gopkg.in/yaml%2ev2.(*TypeError).Error", "gopkg.in/yaml%2ev2"),
|
||||
}
|
||||
|
||||
gotByModule, gotStdlib := moduleSymbols(symbols, mainModule, deps)
|
||||
assert.Nil(t, gotStdlib)
|
||||
assert.Equal(t, map[string]map[string][]string{
|
||||
"gopkg.in/yaml.v2": {
|
||||
"gopkg.in/yaml.v2": {"(*TypeError).Error", "(*decoder).alias"},
|
||||
},
|
||||
}, gotByModule)
|
||||
}
|
||||
|
||||
func Test_unescapePackagePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{"gopkg.in/yaml%2ev2", "gopkg.in/yaml.v2"},
|
||||
{"foo%25bar", "foo%bar"},
|
||||
{"foo%22bar", "foo\"bar"},
|
||||
// no escapes: unchanged
|
||||
{"github.com/foo/bar", "github.com/foo/bar"},
|
||||
{"", ""},
|
||||
// malformed sequences are left as-is
|
||||
{"foo%2", "foo%2"},
|
||||
{"foo%zz", "foo%zz"},
|
||||
{"foo%", "foo%"},
|
||||
// uppercase hex is accepted too
|
||||
{"foo%2Ebar", "foo.bar"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, unescapePackagePath(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_makeBinarySymbol(t *testing.T) {
|
||||
// only the import-path prefix is unescaped; the local-symbol suffix is preserved verbatim
|
||||
sym := makeBinarySymbol("gopkg.in/yaml%2ev2.(*decoder).alias", "gopkg.in/yaml%2ev2")
|
||||
assert.Equal(t, "gopkg.in/yaml.v2", sym.packagePath)
|
||||
assert.Equal(t, "gopkg.in/yaml.v2.(*decoder).alias", sym.name)
|
||||
}
|
||||
|
||||
func Test_localSymbolName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user