mirror of
https://github.com/anchore/syft.git
synced 2026-02-12 10:36:45 +01:00
add gcc and clang toolchains
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
9bf4c5bdf9
commit
32946ec41f
@ -26,7 +26,7 @@ import (
|
|||||||
|
|
||||||
type SymbolCaptureScope string
|
type SymbolCaptureScope string
|
||||||
|
|
||||||
//type SymbolTypes string
|
// type SymbolTypes string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SymbolScopeAll SymbolCaptureScope = "all" // any and all binaries
|
SymbolScopeAll SymbolCaptureScope = "all" // any and all binaries
|
||||||
@ -35,8 +35,8 @@ const (
|
|||||||
SymbolScopeGolang SymbolCaptureScope = "golang" // only binaries built with the golang toolchain
|
SymbolScopeGolang SymbolCaptureScope = "golang" // only binaries built with the golang toolchain
|
||||||
SymbolScopeNone SymbolCaptureScope = "none" // do not capture any symbols
|
SymbolScopeNone SymbolCaptureScope = "none" // do not capture any symbols
|
||||||
|
|
||||||
//SymbolTypeCode SymbolTypes = "code"
|
// SymbolTypeCode SymbolTypes = "code"
|
||||||
//SymbolTypeData SymbolTypes = "data"
|
// SymbolTypeData SymbolTypes = "data"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -55,16 +55,16 @@ type SymbolConfig struct {
|
|||||||
// CaptureScope defines the scope of symbols to capture from executables (all binaries, libraries only, applications only, golang binaries only, or none).
|
// CaptureScope defines the scope of symbols to capture from executables (all binaries, libraries only, applications only, golang binaries only, or none).
|
||||||
CaptureScope []SymbolCaptureScope `json:"capture" yaml:"capture" mapstructure:"capture"`
|
CaptureScope []SymbolCaptureScope `json:"capture" yaml:"capture" mapstructure:"capture"`
|
||||||
|
|
||||||
|
// Types are the types of Go symbols to capture, relative to `go tool nm` output (e.g. T, t, R, r, D, d, B, b, C, U, etc).
|
||||||
|
// If empty, all symbol types are captured.
|
||||||
|
Types []string
|
||||||
|
|
||||||
// Go configures Go-specific symbol capturing settings.
|
// Go configures Go-specific symbol capturing settings.
|
||||||
Go GoSymbolConfig `json:"go" yaml:"go" mapstructure:"go"`
|
Go GoSymbolConfig `json:"go" yaml:"go" mapstructure:"go"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoSymbolConfig holds settings specific to capturing symbols from binaries built with the golang toolchain.
|
// GoSymbolConfig holds settings specific to capturing symbols from binaries built with the golang toolchain.
|
||||||
type GoSymbolConfig struct {
|
type GoSymbolConfig struct {
|
||||||
// Types are the types of Go symbols to capture, relative to `go tool nm` output (e.g. T, t, R, r, D, d, B, b, C, U, etc).
|
|
||||||
// If empty, all symbol types are captured.
|
|
||||||
Types []string
|
|
||||||
|
|
||||||
// StandardLibrary indicates whether to capture Go standard library symbols (e.g. "fmt", "net/http", etc).
|
// StandardLibrary indicates whether to capture Go standard library symbols (e.g. "fmt", "net/http", etc).
|
||||||
StandardLibrary bool `json:"standard-library" yaml:"standard-library" mapstructure:"standard-library"`
|
StandardLibrary bool `json:"standard-library" yaml:"standard-library" mapstructure:"standard-library"`
|
||||||
|
|
||||||
@ -104,8 +104,8 @@ func DefaultConfig() Config {
|
|||||||
CaptureScope: []SymbolCaptureScope{
|
CaptureScope: []SymbolCaptureScope{
|
||||||
SymbolScopeGolang,
|
SymbolScopeGolang,
|
||||||
},
|
},
|
||||||
|
Types: []string{"T", "t"},
|
||||||
Go: GoSymbolConfig{
|
Go: GoSymbolConfig{
|
||||||
Types: []string{"T", "t"},
|
|
||||||
StandardLibrary: true,
|
StandardLibrary: true,
|
||||||
ExtendedStandardLibrary: true,
|
ExtendedStandardLibrary: true,
|
||||||
ThirdPartyModules: true,
|
ThirdPartyModules: true,
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
package executable
|
package executable
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"debug/buildinfo"
|
|
||||||
"debug/elf"
|
"debug/elf"
|
||||||
"io"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -47,28 +45,10 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader, cfg
|
|||||||
func elfToolchains(reader unionreader.UnionReader, f *elf.File) []file.Toolchain {
|
func elfToolchains(reader unionreader.UnionReader, f *elf.File) []file.Toolchain {
|
||||||
return includeNoneNil(
|
return includeNoneNil(
|
||||||
golangToolchainEvidence(reader),
|
golangToolchainEvidence(reader),
|
||||||
|
cToolchainEvidence(f),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldCaptureSymbols(data *file.Executable, cfg SymbolConfig) bool {
|
|
||||||
// TODO: IMPLEMENT ME!
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// elfGolangToolchainEvidence attempts to extract Go toolchain information from the ELF file.
|
|
||||||
func golangToolchainEvidence(reader io.ReaderAt) *file.Toolchain {
|
|
||||||
bi, err := buildinfo.Read(reader)
|
|
||||||
if err != nil || bi == nil {
|
|
||||||
// not a golang binary
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &file.Toolchain{
|
|
||||||
Name: "go",
|
|
||||||
Version: bi.GoVersion,
|
|
||||||
Kind: file.ToolchainKindCompiler,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func includeNoneNil(evidence ...*file.Toolchain) []file.Toolchain {
|
func includeNoneNil(evidence ...*file.Toolchain) []file.Toolchain {
|
||||||
var toolchains []file.Toolchain
|
var toolchains []file.Toolchain
|
||||||
for _, e := range evidence {
|
for _, e := range evidence {
|
||||||
@ -84,8 +64,18 @@ func elfNMSymbols(f *elf.File, cfg SymbolConfig, toolchains []file.Toolchain) []
|
|||||||
return captureElfGoSymbols(f, cfg)
|
return captureElfGoSymbols(f, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: capture other symbol types (non-go) based on the scope selection (lib, app, etc)
|
// include all symbols
|
||||||
return nil
|
syms, err := f.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields("error", err).Trace("unable to read symbols from elf file")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var symbols []string
|
||||||
|
for _, sym := range syms {
|
||||||
|
symbols = append(symbols, sym.Name)
|
||||||
|
}
|
||||||
|
return symbols
|
||||||
}
|
}
|
||||||
|
|
||||||
func captureElfGoSymbols(f *elf.File, cfg SymbolConfig) []string {
|
func captureElfGoSymbols(f *elf.File, cfg SymbolConfig) []string {
|
||||||
@ -96,7 +86,7 @@ func captureElfGoSymbols(f *elf.File, cfg SymbolConfig) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var symbols []string
|
var symbols []string
|
||||||
filter := createGoSymbolFilter(cfg.Go)
|
filter := createGoSymbolFilter(cfg)
|
||||||
for _, sym := range syms {
|
for _, sym := range syms {
|
||||||
name, include := filter(sym.Name, elfSymbolType(sym, f.Sections))
|
name, include := filter(sym.Name, elfSymbolType(sym, f.Sections))
|
||||||
if include {
|
if include {
|
||||||
|
|||||||
@ -227,26 +227,6 @@ func Test_elfHasExports(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_elfNMSymbols_nonGoReturnsNil(t *testing.T) {
|
|
||||||
// for non-Go binaries, elfNMSymbols should return nil since we only support Go for now
|
|
||||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
|
||||||
t.Helper()
|
|
||||||
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
|
|
||||||
require.NoError(t, err)
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := elf.NewFile(readerForFixture(t, "bin/hello_linux"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// no Go toolchain present
|
|
||||||
toolchains := []file.Toolchain{}
|
|
||||||
cfg := SymbolConfig{}
|
|
||||||
|
|
||||||
symbols := elfNMSymbols(f, cfg, toolchains)
|
|
||||||
assert.Nil(t, symbols, "expected nil symbols for non-Go binary")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_elfGoToolchainDetection(t *testing.T) {
|
func Test_elfGoToolchainDetection(t *testing.T) {
|
||||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@ -296,19 +276,21 @@ func Test_elfGoSymbolCapture(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fixture string
|
fixture string
|
||||||
cfg GoSymbolConfig
|
cfg SymbolConfig
|
||||||
wantSymbols []string // exact symbol names that must be present
|
wantSymbols []string // exact symbol names that must be present
|
||||||
wantMinSymbolCount int
|
wantMinSymbolCount int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "capture all symbol types",
|
name: "capture all symbol types",
|
||||||
fixture: "bin/hello_linux",
|
fixture: "bin/hello_linux",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
StandardLibrary: true,
|
Go: GoSymbolConfig{
|
||||||
ExtendedStandardLibrary: true,
|
StandardLibrary: true,
|
||||||
ThirdPartyModules: true,
|
ExtendedStandardLibrary: true,
|
||||||
ExportedSymbols: true,
|
ThirdPartyModules: true,
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
|
UnexportedSymbols: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wantSymbols: []string{
|
wantSymbols: []string{
|
||||||
// stdlib - fmt package (used via fmt.Println)
|
// stdlib - fmt package (used via fmt.Println)
|
||||||
@ -331,10 +313,12 @@ func Test_elfGoSymbolCapture(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "capture only third-party symbols",
|
name: "capture only third-party symbols",
|
||||||
fixture: "bin/hello_linux",
|
fixture: "bin/hello_linux",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ThirdPartyModules: true,
|
Go: GoSymbolConfig{
|
||||||
ExportedSymbols: true,
|
ThirdPartyModules: true,
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
|
UnexportedSymbols: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wantSymbols: []string{
|
wantSymbols: []string{
|
||||||
"github.com/davecgh/go-spew/spew.(*dumpState).dump",
|
"github.com/davecgh/go-spew/spew.(*dumpState).dump",
|
||||||
@ -345,10 +329,12 @@ func Test_elfGoSymbolCapture(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "capture only extended stdlib symbols",
|
name: "capture only extended stdlib symbols",
|
||||||
fixture: "bin/hello_linux",
|
fixture: "bin/hello_linux",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExtendedStandardLibrary: true,
|
Go: GoSymbolConfig{
|
||||||
ExportedSymbols: true,
|
ExtendedStandardLibrary: true,
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
|
UnexportedSymbols: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wantSymbols: []string{
|
wantSymbols: []string{
|
||||||
"golang.org/x/text/internal/language.Tag.String",
|
"golang.org/x/text/internal/language.Tag.String",
|
||||||
@ -358,13 +344,15 @@ func Test_elfGoSymbolCapture(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "capture with text section types only",
|
name: "capture with text section types only",
|
||||||
fixture: "bin/hello_linux",
|
fixture: "bin/hello_linux",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
Types: []string{"T", "t"}, // text section (code) symbols
|
Types: []string{"T", "t"}, // text section (code) symbols
|
||||||
StandardLibrary: true,
|
Go: GoSymbolConfig{
|
||||||
ExtendedStandardLibrary: true,
|
StandardLibrary: true,
|
||||||
ThirdPartyModules: true,
|
ExtendedStandardLibrary: true,
|
||||||
ExportedSymbols: true,
|
ThirdPartyModules: true,
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
|
UnexportedSymbols: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wantSymbols: []string{
|
wantSymbols: []string{
|
||||||
"encoding/json.Marshal",
|
"encoding/json.Marshal",
|
||||||
@ -379,7 +367,7 @@ func Test_elfGoSymbolCapture(t *testing.T) {
|
|||||||
f, err := elf.NewFile(reader)
|
f, err := elf.NewFile(reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
symbols := captureElfGoSymbols(f, SymbolConfig{Go: tt.cfg})
|
symbols := captureElfGoSymbols(f, tt.cfg)
|
||||||
symbolSet := make(map[string]struct{}, len(symbols))
|
symbolSet := make(map[string]struct{}, len(symbols))
|
||||||
for _, sym := range symbols {
|
for _, sym := range symbols {
|
||||||
symbolSet[sym] = struct{}{}
|
symbolSet[sym] = struct{}{}
|
||||||
@ -415,8 +403,8 @@ func Test_elfNMSymbols_goReturnsSymbols(t *testing.T) {
|
|||||||
{Name: "go", Version: "1.24", Kind: file.ToolchainKindCompiler},
|
{Name: "go", Version: "1.24", Kind: file.ToolchainKindCompiler},
|
||||||
}
|
}
|
||||||
cfg := SymbolConfig{
|
cfg := SymbolConfig{
|
||||||
|
Types: []string{"T", "t"},
|
||||||
Go: GoSymbolConfig{
|
Go: GoSymbolConfig{
|
||||||
Types: []string{"T", "t"},
|
|
||||||
StandardLibrary: true,
|
StandardLibrary: true,
|
||||||
ExtendedStandardLibrary: true,
|
ExtendedStandardLibrary: true,
|
||||||
ThirdPartyModules: true,
|
ThirdPartyModules: true,
|
||||||
|
|||||||
@ -31,7 +31,7 @@ const (
|
|||||||
// createGoSymbolFilter creates a filter function for Go symbols based on the provided configuration. This filter function
|
// createGoSymbolFilter creates a filter function for Go symbols based on the provided configuration. This filter function
|
||||||
// returns true if a symbol should be included based on its name and type. This also allows for modification of the symbol name
|
// returns true if a symbol should be included based on its name and type. This also allows for modification of the symbol name
|
||||||
// if necessary (e.g., normalization of vendored module paths). The returned name is only valid if the boolean is true.
|
// if necessary (e.g., normalization of vendored module paths). The returned name is only valid if the boolean is true.
|
||||||
func createGoSymbolFilter(cfg GoSymbolConfig) func(string, string) (string, bool) {
|
func createGoSymbolFilter(cfg SymbolConfig) func(string, string) (string, bool) {
|
||||||
validNmTypes := buildNmTypes(cfg.Types)
|
validNmTypes := buildNmTypes(cfg.Types)
|
||||||
|
|
||||||
return func(symName, symType string) (string, bool) {
|
return func(symName, symType string) (string, bool) {
|
||||||
@ -47,13 +47,13 @@ func createGoSymbolFilter(cfg GoSymbolConfig) func(string, string) (string, bool
|
|||||||
|
|
||||||
// filter based on exported/unexported symbol configuration
|
// filter based on exported/unexported symbol configuration
|
||||||
exported := isExportedSymbol(symName)
|
exported := isExportedSymbol(symName)
|
||||||
if !shouldIncludeByExportStatus(exported, cfg.ExportedSymbols, cfg.UnexportedSymbols) {
|
if !shouldIncludeByExportStatus(exported, cfg.Go.ExportedSymbols, cfg.Go.UnexportedSymbols) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle type equality functions (e.g., type:.eq.myStruct)
|
// handle type equality functions (e.g., type:.eq.myStruct)
|
||||||
if isTypeEqualityFunction(symName) {
|
if isTypeEqualityFunction(symName) {
|
||||||
if !cfg.TypeEqualityFunctions {
|
if !cfg.Go.TypeEqualityFunctions {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return symName, true
|
return symName, true
|
||||||
@ -61,21 +61,21 @@ func createGoSymbolFilter(cfg GoSymbolConfig) func(string, string) (string, bool
|
|||||||
|
|
||||||
// handle GC shape stencil functions (e.g., go.shape.func())
|
// handle GC shape stencil functions (e.g., go.shape.func())
|
||||||
if isGCShapeStencil(symName) {
|
if isGCShapeStencil(symName) {
|
||||||
if !cfg.GCShapeStencils {
|
if !cfg.Go.GCShapeStencils {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return symName, true
|
return symName, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalize vendored module paths if configured
|
// normalize vendored module paths if configured
|
||||||
symName = normalizeVendoredPath(symName, cfg.NormalizeVendoredModules)
|
symName = normalizeVendoredPath(symName, cfg.Go.NormalizeVendoredModules)
|
||||||
|
|
||||||
// determine the package path for classification
|
// determine the package path for classification
|
||||||
pkgPath := extractPackagePath(symName)
|
pkgPath := extractPackagePath(symName)
|
||||||
|
|
||||||
// handle extended stdlib (golang.org/x/*)
|
// handle extended stdlib (golang.org/x/*)
|
||||||
if isExtendedStdlib(pkgPath) {
|
if isExtendedStdlib(pkgPath) {
|
||||||
if !cfg.ExtendedStandardLibrary {
|
if !cfg.Go.ExtendedStandardLibrary {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return symName, true
|
return symName, true
|
||||||
@ -83,14 +83,14 @@ func createGoSymbolFilter(cfg GoSymbolConfig) func(string, string) (string, bool
|
|||||||
|
|
||||||
// handle stdlib packages
|
// handle stdlib packages
|
||||||
if isStdlibPackage(pkgPath) {
|
if isStdlibPackage(pkgPath) {
|
||||||
if !cfg.StandardLibrary {
|
if !cfg.Go.StandardLibrary {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return symName, true
|
return symName, true
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is a third-party package
|
// this is a third-party package
|
||||||
if !cfg.ThirdPartyModules {
|
if !cfg.Go.ThirdPartyModules {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return symName, true
|
return symName, true
|
||||||
@ -151,7 +151,7 @@ func isGCShapeStencil(symName string) bool {
|
|||||||
|
|
||||||
// normalizeVendoredPath removes the "vendor/" prefix from vendored module paths if normalization is enabled.
|
// normalizeVendoredPath removes the "vendor/" prefix from vendored module paths if normalization is enabled.
|
||||||
func normalizeVendoredPath(symName string, normalize bool) string {
|
func normalizeVendoredPath(symName string, normalize bool) string {
|
||||||
if normalize && strings.HasPrefix(symName, vendorPrefix) {
|
if normalize && isVendoredPath(symName) {
|
||||||
return strings.TrimPrefix(symName, vendorPrefix)
|
return strings.TrimPrefix(symName, vendorPrefix)
|
||||||
}
|
}
|
||||||
return symName
|
return symName
|
||||||
|
|||||||
@ -607,7 +607,7 @@ func Test_isStdlibPackage(t *testing.T) {
|
|||||||
func Test_createGoSymbolFilter(t *testing.T) {
|
func Test_createGoSymbolFilter(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
cfg GoSymbolConfig
|
cfg SymbolConfig
|
||||||
symName string
|
symName string
|
||||||
symType string
|
symType string
|
||||||
wantName string
|
wantName string
|
||||||
@ -616,10 +616,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
// NM type filtering
|
// NM type filtering
|
||||||
{
|
{
|
||||||
name: "valid NM type with defaults",
|
name: "valid NM type with defaults",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: true,
|
UnexportedSymbols: true,
|
||||||
|
StandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "fmt.Println",
|
symName: "fmt.Println",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -628,10 +630,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid NM type with defaults",
|
name: "invalid NM type with defaults",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: true,
|
UnexportedSymbols: true,
|
||||||
|
StandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "fmt.Println",
|
symName: "fmt.Println",
|
||||||
symType: "X", // important!
|
symType: "X", // important!
|
||||||
@ -640,11 +644,13 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "custom NM types - included",
|
name: "custom NM types - included",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
Types: []string{"T"},
|
Types: []string{"T"},
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: true,
|
UnexportedSymbols: true,
|
||||||
|
StandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "fmt.Println",
|
symName: "fmt.Println",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -653,11 +659,13 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "custom NM types - excluded",
|
name: "custom NM types - excluded",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
Types: []string{"T"},
|
Types: []string{"T"},
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: true,
|
UnexportedSymbols: true,
|
||||||
|
StandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "fmt.Println",
|
symName: "fmt.Println",
|
||||||
symType: "t",
|
symType: "t",
|
||||||
@ -668,10 +676,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
// floating point literal filtering
|
// floating point literal filtering
|
||||||
{
|
{
|
||||||
name: "floating point literal filtered",
|
name: "floating point literal filtered",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: true,
|
UnexportedSymbols: true,
|
||||||
|
StandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "$f64.3fceb851eb851eb8",
|
symName: "$f64.3fceb851eb851eb8",
|
||||||
symType: "R",
|
symType: "R",
|
||||||
@ -682,10 +692,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
// export status filtering
|
// export status filtering
|
||||||
{
|
{
|
||||||
name: "exported symbol with only exported enabled",
|
name: "exported symbol with only exported enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: false,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: true,
|
UnexportedSymbols: false,
|
||||||
|
StandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "fmt.Println",
|
symName: "fmt.Println",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -694,10 +706,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unexported symbol with only exported enabled",
|
name: "unexported symbol with only exported enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: false,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: true,
|
UnexportedSymbols: false,
|
||||||
|
StandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "fmt.println",
|
symName: "fmt.println",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -708,10 +722,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
// type equality functions
|
// type equality functions
|
||||||
{
|
{
|
||||||
name: "type equality function - enabled",
|
name: "type equality function - enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
TypeEqualityFunctions: true,
|
UnexportedSymbols: true,
|
||||||
|
TypeEqualityFunctions: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "type:.eq.myStruct",
|
symName: "type:.eq.myStruct",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -720,10 +736,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "type equality function - disabled",
|
name: "type equality function - disabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
TypeEqualityFunctions: false,
|
UnexportedSymbols: true,
|
||||||
|
TypeEqualityFunctions: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "type:.eq.myStruct",
|
symName: "type:.eq.myStruct",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -734,10 +752,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
// GC shape stencils
|
// GC shape stencils
|
||||||
{
|
{
|
||||||
name: "gc shape stencil - enabled",
|
name: "gc shape stencil - enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
GCShapeStencils: true,
|
UnexportedSymbols: true,
|
||||||
|
GCShapeStencils: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "go.shape.func()",
|
symName: "go.shape.func()",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -746,10 +766,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "gc shape stencil - disabled",
|
name: "gc shape stencil - disabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
GCShapeStencils: false,
|
UnexportedSymbols: true,
|
||||||
|
GCShapeStencils: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "go.shape.func()",
|
symName: "go.shape.func()",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -758,10 +780,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "gc shape stencil embedded in generic - enabled",
|
name: "gc shape stencil embedded in generic - enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
GCShapeStencils: true,
|
UnexportedSymbols: true,
|
||||||
|
GCShapeStencils: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "slices.partitionCmpFunc[go.shape.struct { Key string; Value int }]",
|
symName: "slices.partitionCmpFunc[go.shape.struct { Key string; Value int }]",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -770,10 +794,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "gc shape stencil embedded in generic - disabled",
|
name: "gc shape stencil embedded in generic - disabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
GCShapeStencils: false,
|
UnexportedSymbols: true,
|
||||||
|
GCShapeStencils: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "slices.partitionCmpFunc[go.shape.struct { Key string; Value int }]",
|
symName: "slices.partitionCmpFunc[go.shape.struct { Key string; Value int }]",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -784,11 +810,13 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
// vendored module normalization
|
// vendored module normalization
|
||||||
{
|
{
|
||||||
name: "vendored path - normalization enabled",
|
name: "vendored path - normalization enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
ThirdPartyModules: true,
|
UnexportedSymbols: true,
|
||||||
NormalizeVendoredModules: true,
|
ThirdPartyModules: true,
|
||||||
|
NormalizeVendoredModules: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "vendor/github.com/foo/bar.Baz",
|
symName: "vendor/github.com/foo/bar.Baz",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -797,11 +825,13 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "vendored path - normalization disabled",
|
name: "vendored path - normalization disabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
ThirdPartyModules: true,
|
UnexportedSymbols: true,
|
||||||
NormalizeVendoredModules: false,
|
ThirdPartyModules: true,
|
||||||
|
NormalizeVendoredModules: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "vendor/github.com/foo/bar.Baz",
|
symName: "vendor/github.com/foo/bar.Baz",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -812,10 +842,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
// extended stdlib
|
// extended stdlib
|
||||||
{
|
{
|
||||||
name: "extended stdlib - enabled",
|
name: "extended stdlib - enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
ExtendedStandardLibrary: true,
|
UnexportedSymbols: true,
|
||||||
|
ExtendedStandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "golang.org/x/net/html.Parse",
|
symName: "golang.org/x/net/html.Parse",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -824,10 +856,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "extended stdlib - disabled",
|
name: "extended stdlib - disabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
ExtendedStandardLibrary: false,
|
UnexportedSymbols: true,
|
||||||
|
ExtendedStandardLibrary: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "golang.org/x/net/html.Parse",
|
symName: "golang.org/x/net/html.Parse",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -838,10 +872,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
// stdlib
|
// stdlib
|
||||||
{
|
{
|
||||||
name: "stdlib - enabled",
|
name: "stdlib - enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: true,
|
UnexportedSymbols: true,
|
||||||
|
StandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "fmt.Println",
|
symName: "fmt.Println",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -850,10 +886,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "stdlib - disabled",
|
name: "stdlib - disabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: false,
|
UnexportedSymbols: true,
|
||||||
|
StandardLibrary: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "fmt.Println",
|
symName: "fmt.Println",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -862,10 +900,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "nested stdlib - enabled",
|
name: "nested stdlib - enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: true,
|
UnexportedSymbols: true,
|
||||||
|
StandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "net/http.ListenAndServe",
|
symName: "net/http.ListenAndServe",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -876,10 +916,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
// third party
|
// third party
|
||||||
{
|
{
|
||||||
name: "third party - enabled",
|
name: "third party - enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
ThirdPartyModules: true,
|
UnexportedSymbols: true,
|
||||||
|
ThirdPartyModules: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "github.com/spf13/cobra.Command",
|
symName: "github.com/spf13/cobra.Command",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -888,10 +930,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "third party - disabled",
|
name: "third party - disabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
ThirdPartyModules: false,
|
UnexportedSymbols: true,
|
||||||
|
ThirdPartyModules: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "github.com/spf13/cobra.Command",
|
symName: "github.com/spf13/cobra.Command",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -902,10 +946,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
// main package (treated as stdlib)
|
// main package (treated as stdlib)
|
||||||
{
|
{
|
||||||
name: "main package - stdlib enabled",
|
name: "main package - stdlib enabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: true,
|
UnexportedSymbols: true,
|
||||||
|
StandardLibrary: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "main.main",
|
symName: "main.main",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
@ -914,10 +960,12 @@ func Test_createGoSymbolFilter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "main package - stdlib disabled",
|
name: "main package - stdlib disabled",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExportedSymbols: true,
|
Go: GoSymbolConfig{
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
StandardLibrary: false,
|
UnexportedSymbols: true,
|
||||||
|
StandardLibrary: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
symName: "main.main",
|
symName: "main.main",
|
||||||
symType: "T",
|
symType: "T",
|
||||||
|
|||||||
@ -59,7 +59,7 @@ func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader, cf
|
|||||||
data.HasExports = machoHasExports(f)
|
data.HasExports = machoHasExports(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
data.Toolchains = machoToolchains(reader, f)
|
data.Toolchains = machoToolchains(reader)
|
||||||
if shouldCaptureSymbols(data, cfg) {
|
if shouldCaptureSymbols(data, cfg) {
|
||||||
symbols = machoNMSymbols(f, cfg, data.Toolchains)
|
symbols = machoNMSymbols(f, cfg, data.Toolchains)
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader, cf
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func machoToolchains(reader unionreader.UnionReader, f *macho.File) []file.Toolchain {
|
func machoToolchains(reader unionreader.UnionReader) []file.Toolchain {
|
||||||
return includeNoneNil(
|
return includeNoneNil(
|
||||||
golangToolchainEvidence(reader),
|
golangToolchainEvidence(reader),
|
||||||
)
|
)
|
||||||
@ -83,13 +83,17 @@ func machoNMSymbols(f *macho.File, cfg SymbolConfig, toolchains []file.Toolchain
|
|||||||
return captureMachoGoSymbols(f, cfg)
|
return captureMachoGoSymbols(f, cfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: capture other symbol types (non-go) based on the scope selection (lib, app, etc)
|
// include all symbols
|
||||||
return nil
|
var symbols []string
|
||||||
|
for _, sym := range f.Symtab.Syms {
|
||||||
|
symbols = append(symbols, sym.Name)
|
||||||
|
}
|
||||||
|
return symbols
|
||||||
}
|
}
|
||||||
|
|
||||||
func captureMachoGoSymbols(f *macho.File, cfg SymbolConfig) []string {
|
func captureMachoGoSymbols(f *macho.File, cfg SymbolConfig) []string {
|
||||||
var symbols []string
|
var symbols []string
|
||||||
filter := createGoSymbolFilter(cfg.Go)
|
filter := createGoSymbolFilter(cfg)
|
||||||
for _, sym := range f.Symtab.Syms {
|
for _, sym := range f.Symtab.Syms {
|
||||||
name, include := filter(sym.Name, machoSymbolType(sym, f.Sections))
|
name, include := filter(sym.Name, machoSymbolType(sym, f.Sections))
|
||||||
if include {
|
if include {
|
||||||
@ -99,15 +103,6 @@ func captureMachoGoSymbols(f *macho.File, cfg SymbolConfig) []string {
|
|||||||
return symbols
|
return symbols
|
||||||
}
|
}
|
||||||
|
|
||||||
func isGoToolchainPresent(toolchains []file.Toolchain) bool {
|
|
||||||
for _, tc := range toolchains {
|
|
||||||
if tc.Name == "go" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func machoSymbolType(s macho.Symbol, sections []*macho.Section) string {
|
func machoSymbolType(s macho.Symbol, sections []*macho.Section) string {
|
||||||
// stab (debugging) symbols get '-'
|
// stab (debugging) symbols get '-'
|
||||||
if s.Type&machoNStab != 0 {
|
if s.Type&machoNStab != 0 {
|
||||||
@ -133,7 +128,7 @@ func machoSymbolType(s macho.Symbol, sections []*macho.Section) string {
|
|||||||
|
|
||||||
// lowercase for local symbols, uppercase for external
|
// lowercase for local symbols, uppercase for external
|
||||||
if !isExternal && typeChar != '-' && typeChar != '?' {
|
if !isExternal && typeChar != '-' && typeChar != '?' {
|
||||||
typeChar = typeChar + 32 // convert to lowercase
|
typeChar += 32 // convert to lowercase
|
||||||
}
|
}
|
||||||
|
|
||||||
return string(typeChar)
|
return string(typeChar)
|
||||||
|
|||||||
@ -121,26 +121,6 @@ func Test_machoUniversal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_machoNMSymbols_nonGoReturnsNil(t *testing.T) {
|
|
||||||
// for non-Go binaries, machoNMSymbols should return nil since we only support Go for now
|
|
||||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
|
||||||
t.Helper()
|
|
||||||
f, err := os.Open(filepath.Join("test-fixtures/shared-info", fixture))
|
|
||||||
require.NoError(t, err)
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := macho.NewFile(readerForFixture(t, "bin/hello_mac"))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// no Go toolchain present
|
|
||||||
toolchains := []file.Toolchain{}
|
|
||||||
cfg := SymbolConfig{}
|
|
||||||
|
|
||||||
symbols := machoNMSymbols(f, cfg, toolchains)
|
|
||||||
assert.Nil(t, symbols, "expected nil symbols for non-Go binary")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_machoGoToolchainDetection(t *testing.T) {
|
func Test_machoGoToolchainDetection(t *testing.T) {
|
||||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@ -163,10 +143,8 @@ func Test_machoGoToolchainDetection(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
reader := readerForFixture(t, tt.fixture)
|
reader := readerForFixture(t, tt.fixture)
|
||||||
f, err := macho.NewFile(reader)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
toolchains := machoToolchains(reader, f)
|
toolchains := machoToolchains(reader)
|
||||||
assert.Equal(t, tt.wantPresent, isGoToolchainPresent(toolchains))
|
assert.Equal(t, tt.wantPresent, isGoToolchainPresent(toolchains))
|
||||||
|
|
||||||
if tt.wantPresent {
|
if tt.wantPresent {
|
||||||
@ -190,19 +168,21 @@ func Test_machoGoSymbolCapture(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
fixture string
|
fixture string
|
||||||
cfg GoSymbolConfig
|
cfg SymbolConfig
|
||||||
wantSymbols []string // exact symbol names that must be present
|
wantSymbols []string // exact symbol names that must be present
|
||||||
wantMinSymbolCount int
|
wantMinSymbolCount int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "capture all symbol types",
|
name: "capture all symbol types",
|
||||||
fixture: "bin/hello_mac",
|
fixture: "bin/hello_mac",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
StandardLibrary: true,
|
Go: GoSymbolConfig{
|
||||||
ExtendedStandardLibrary: true,
|
StandardLibrary: true,
|
||||||
ThirdPartyModules: true,
|
ExtendedStandardLibrary: true,
|
||||||
ExportedSymbols: true,
|
ThirdPartyModules: true,
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
|
UnexportedSymbols: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wantSymbols: []string{
|
wantSymbols: []string{
|
||||||
// stdlib - fmt package (used via fmt.Println)
|
// stdlib - fmt package (used via fmt.Println)
|
||||||
@ -225,10 +205,12 @@ func Test_machoGoSymbolCapture(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "capture only third-party symbols",
|
name: "capture only third-party symbols",
|
||||||
fixture: "bin/hello_mac",
|
fixture: "bin/hello_mac",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ThirdPartyModules: true,
|
Go: GoSymbolConfig{
|
||||||
ExportedSymbols: true,
|
ThirdPartyModules: true,
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
|
UnexportedSymbols: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wantSymbols: []string{
|
wantSymbols: []string{
|
||||||
"github.com/davecgh/go-spew/spew.(*dumpState).dump",
|
"github.com/davecgh/go-spew/spew.(*dumpState).dump",
|
||||||
@ -239,10 +221,12 @@ func Test_machoGoSymbolCapture(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "capture only extended stdlib symbols",
|
name: "capture only extended stdlib symbols",
|
||||||
fixture: "bin/hello_mac",
|
fixture: "bin/hello_mac",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
ExtendedStandardLibrary: true,
|
Go: GoSymbolConfig{
|
||||||
ExportedSymbols: true,
|
ExtendedStandardLibrary: true,
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
|
UnexportedSymbols: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wantSymbols: []string{
|
wantSymbols: []string{
|
||||||
"golang.org/x/text/internal/language.Tag.String",
|
"golang.org/x/text/internal/language.Tag.String",
|
||||||
@ -252,13 +236,15 @@ func Test_machoGoSymbolCapture(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "capture with text section types only",
|
name: "capture with text section types only",
|
||||||
fixture: "bin/hello_mac",
|
fixture: "bin/hello_mac",
|
||||||
cfg: GoSymbolConfig{
|
cfg: SymbolConfig{
|
||||||
Types: []string{"T", "t"}, // text section (code) symbols
|
Types: []string{"T", "t"}, // text section (code) symbols
|
||||||
StandardLibrary: true,
|
Go: GoSymbolConfig{
|
||||||
ExtendedStandardLibrary: true,
|
StandardLibrary: true,
|
||||||
ThirdPartyModules: true,
|
ExtendedStandardLibrary: true,
|
||||||
ExportedSymbols: true,
|
ThirdPartyModules: true,
|
||||||
UnexportedSymbols: true,
|
ExportedSymbols: true,
|
||||||
|
UnexportedSymbols: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
wantSymbols: []string{
|
wantSymbols: []string{
|
||||||
"encoding/json.Marshal",
|
"encoding/json.Marshal",
|
||||||
@ -273,7 +259,7 @@ func Test_machoGoSymbolCapture(t *testing.T) {
|
|||||||
f, err := macho.NewFile(reader)
|
f, err := macho.NewFile(reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
symbols := captureMachoGoSymbols(f, SymbolConfig{Go: tt.cfg})
|
symbols := captureMachoGoSymbols(f, tt.cfg)
|
||||||
symbolSet := make(map[string]struct{}, len(symbols))
|
symbolSet := make(map[string]struct{}, len(symbols))
|
||||||
for _, sym := range symbols {
|
for _, sym := range symbols {
|
||||||
symbolSet[sym] = struct{}{}
|
symbolSet[sym] = struct{}{}
|
||||||
@ -309,8 +295,8 @@ func Test_machoNMSymbols_goReturnsSymbols(t *testing.T) {
|
|||||||
{Name: "go", Version: "1.24", Kind: file.ToolchainKindCompiler},
|
{Name: "go", Version: "1.24", Kind: file.ToolchainKindCompiler},
|
||||||
}
|
}
|
||||||
cfg := SymbolConfig{
|
cfg := SymbolConfig{
|
||||||
|
Types: []string{"T", "t"},
|
||||||
Go: GoSymbolConfig{
|
Go: GoSymbolConfig{
|
||||||
Types: []string{"T", "t"},
|
|
||||||
StandardLibrary: true,
|
StandardLibrary: true,
|
||||||
ExtendedStandardLibrary: true,
|
ExtendedStandardLibrary: true,
|
||||||
ThirdPartyModules: true,
|
ThirdPartyModules: true,
|
||||||
|
|||||||
47
syft/file/cataloger/executable/symbols.go
Normal file
47
syft/file/cataloger/executable/symbols.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package executable
|
||||||
|
|
||||||
|
import "github.com/anchore/syft/syft/file"
|
||||||
|
|
||||||
|
// shouldCaptureSymbols determines whether symbols should be captured for the given executable
|
||||||
|
// based on the configured capture scopes. If any configured scope matches the executable's
|
||||||
|
// characteristics, symbols will be captured.
|
||||||
|
func shouldCaptureSymbols(data *file.Executable, cfg SymbolConfig) bool {
|
||||||
|
if data == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, scope := range cfg.CaptureScope {
|
||||||
|
switch scope {
|
||||||
|
case SymbolScopeNone:
|
||||||
|
// explicit "none" means don't capture (but continue checking other scopes)
|
||||||
|
continue
|
||||||
|
case SymbolScopeAll:
|
||||||
|
return true
|
||||||
|
case SymbolScopeLibraries:
|
||||||
|
if data.HasExports {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case SymbolScopeApplications:
|
||||||
|
if data.HasEntrypoint {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case SymbolScopeGolang:
|
||||||
|
if hasGolangToolchain(data) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no scopes matched, do not capture symbols (empty scope means none)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// hasGolangToolchain checks if the executable was built with the Go toolchain.
|
||||||
|
func hasGolangToolchain(data *file.Executable) bool {
|
||||||
|
for _, tc := range data.Toolchains {
|
||||||
|
if tc.Name == "go" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
226
syft/file/cataloger/executable/symbols_test.go
Normal file
226
syft/file/cataloger/executable/symbols_test.go
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
package executable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestShouldCaptureSymbols(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data *file.Executable
|
||||||
|
cfg SymbolConfig
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil data returns false",
|
||||||
|
data: nil,
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeAll},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty capture scope returns false",
|
||||||
|
data: &file.Executable{},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scope none returns false",
|
||||||
|
data: &file.Executable{},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeNone},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scope all returns true",
|
||||||
|
data: &file.Executable{},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeAll},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scope libraries with exports returns true",
|
||||||
|
data: &file.Executable{
|
||||||
|
HasExports: true,
|
||||||
|
},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scope libraries without exports returns false",
|
||||||
|
data: &file.Executable{
|
||||||
|
HasExports: false,
|
||||||
|
},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scope applications with entrypoint returns true",
|
||||||
|
data: &file.Executable{
|
||||||
|
HasEntrypoint: true,
|
||||||
|
},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeApplications},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scope applications without entrypoint returns false",
|
||||||
|
data: &file.Executable{
|
||||||
|
HasEntrypoint: false,
|
||||||
|
},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeApplications},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scope golang with go toolchain returns true",
|
||||||
|
data: &file.Executable{
|
||||||
|
Toolchains: []file.Toolchain{
|
||||||
|
{Name: "go", Version: "1.21.0", Kind: file.ToolchainKindCompiler},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scope golang without go toolchain returns false",
|
||||||
|
data: &file.Executable{
|
||||||
|
Toolchains: []file.Toolchain{
|
||||||
|
{Name: "gcc", Version: "12.0.0", Kind: file.ToolchainKindCompiler},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "scope golang with empty toolchains returns false",
|
||||||
|
data: &file.Executable{},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple scopes with one match returns true",
|
||||||
|
data: &file.Executable{
|
||||||
|
HasExports: false,
|
||||||
|
HasEntrypoint: true,
|
||||||
|
},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries, SymbolScopeApplications},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple scopes with no match returns false",
|
||||||
|
data: &file.Executable{
|
||||||
|
HasExports: false,
|
||||||
|
HasEntrypoint: false,
|
||||||
|
},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeLibraries, SymbolScopeApplications},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none scope followed by matching scope returns true",
|
||||||
|
data: &file.Executable{
|
||||||
|
HasEntrypoint: true,
|
||||||
|
},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeNone, SymbolScopeApplications},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "go toolchain among multiple toolchains returns true",
|
||||||
|
data: &file.Executable{
|
||||||
|
Toolchains: []file.Toolchain{
|
||||||
|
{Name: "gcc", Version: "12.0.0", Kind: file.ToolchainKindCompiler},
|
||||||
|
{Name: "go", Version: "1.21.0", Kind: file.ToolchainKindCompiler},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cfg: SymbolConfig{
|
||||||
|
CaptureScope: []SymbolCaptureScope{SymbolScopeGolang},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := shouldCaptureSymbols(tt.data, tt.cfg)
|
||||||
|
require.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasGolangToolchain(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
data *file.Executable
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty toolchains",
|
||||||
|
data: &file.Executable{},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no go toolchain",
|
||||||
|
data: &file.Executable{
|
||||||
|
Toolchains: []file.Toolchain{
|
||||||
|
{Name: "gcc", Version: "12.0.0", Kind: file.ToolchainKindCompiler},
|
||||||
|
{Name: "clang", Version: "15.0.0", Kind: file.ToolchainKindCompiler},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "has go toolchain",
|
||||||
|
data: &file.Executable{
|
||||||
|
Toolchains: []file.Toolchain{
|
||||||
|
{Name: "go", Version: "1.21.0", Kind: file.ToolchainKindCompiler},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "go toolchain among others",
|
||||||
|
data: &file.Executable{
|
||||||
|
Toolchains: []file.Toolchain{
|
||||||
|
{Name: "gcc", Version: "12.0.0", Kind: file.ToolchainKindCompiler},
|
||||||
|
{Name: "go", Version: "1.21.0", Kind: file.ToolchainKindCompiler},
|
||||||
|
{Name: "ld", Version: "2.38", Kind: file.ToolchainKindLinker},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := hasGolangToolchain(tt.data)
|
||||||
|
require.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
3
syft/file/cataloger/executable/test-fixtures/toolchains/.gitignore
vendored
Normal file
3
syft/file/cataloger/executable/test-fixtures/toolchains/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
bin/
|
||||||
|
Dockerfile.sha256
|
||||||
|
*.fingerprint
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
# invoke all make files in subdirectories
|
||||||
|
.PHONY: all gcc clang
|
||||||
|
|
||||||
|
all: gcc clang
|
||||||
|
|
||||||
|
gcc:
|
||||||
|
$(MAKE) -C gcc
|
||||||
|
|
||||||
|
clang:
|
||||||
|
$(MAKE) -C clang
|
||||||
|
|
||||||
|
%:
|
||||||
|
@for dir in gcc clang; do \
|
||||||
|
$(MAKE) -C $$dir $@; \
|
||||||
|
done
|
||||||
@ -0,0 +1 @@
|
|||||||
|
FROM silkeh/clang:18.1.8
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
BIN=./bin
|
||||||
|
TOOL_IMAGE=localhost/syft-toolchain-clang-build-tools:latest
|
||||||
|
FINGERPRINT_FILE=$(BIN).fingerprint
|
||||||
|
|
||||||
|
ifndef BIN
|
||||||
|
$(error BIN is not set)
|
||||||
|
endif
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := fixtures
|
||||||
|
|
||||||
|
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||||
|
fixtures: build
|
||||||
|
|
||||||
|
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||||
|
fingerprint: $(FINGERPRINT_FILE)
|
||||||
|
|
||||||
|
tools-check:
|
||||||
|
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||||
|
|
||||||
|
tools:
|
||||||
|
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||||
|
|
||||||
|
build: tools
|
||||||
|
@mkdir -p $(BIN)
|
||||||
|
docker run --platform linux/amd64 -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||||
|
|
||||||
|
debug:
|
||||||
|
docker run --platform linux/amd64 -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||||
|
|
||||||
|
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||||
|
.PHONY: $(FINGERPRINT_FILE)
|
||||||
|
$(FINGERPRINT_FILE):
|
||||||
|
@find project Dockerfile Makefile -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||||
|
|
||||||
|
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||||
|
clean:
|
||||||
|
rm -rf $(BIN) Dockerfile.sha256 $(FINGERPRINT_FILE)
|
||||||
|
|
||||||
|
.PHONY: tools tools-check build debug clean
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
BIN=../bin
|
||||||
|
|
||||||
|
all: $(BIN)/hello_clang
|
||||||
|
|
||||||
|
$(BIN)/hello_clang: hello.c
|
||||||
|
clang hello.c -o $(BIN)/hello_clang
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(BIN)/hello_clang
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf("Hello, World!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
FROM gcc:13.4.0
|
||||||
@ -0,0 +1,39 @@
|
|||||||
|
BIN=./bin
|
||||||
|
TOOL_IMAGE=localhost/syft-toolchain-gcc-build-tools:latest
|
||||||
|
FINGERPRINT_FILE=$(BIN).fingerprint
|
||||||
|
|
||||||
|
ifndef BIN
|
||||||
|
$(error BIN is not set)
|
||||||
|
endif
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := fixtures
|
||||||
|
|
||||||
|
# requirement 1: 'fixtures' goal to generate any and all test fixtures
|
||||||
|
fixtures: build
|
||||||
|
|
||||||
|
# requirement 2: 'fingerprint' goal to determine if the fixture input that indicates any existing cache should be busted
|
||||||
|
fingerprint: $(FINGERPRINT_FILE)
|
||||||
|
|
||||||
|
tools-check:
|
||||||
|
@sha256sum -c Dockerfile.sha256 || (echo "Tools Dockerfile has changed" && exit 1)
|
||||||
|
|
||||||
|
tools:
|
||||||
|
@(docker inspect $(TOOL_IMAGE) > /dev/null && make tools-check) || (docker build --platform linux/amd64 -t $(TOOL_IMAGE) . && sha256sum Dockerfile > Dockerfile.sha256)
|
||||||
|
|
||||||
|
build: tools
|
||||||
|
@mkdir -p $(BIN)
|
||||||
|
docker run --platform linux/amd64 -i -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) make
|
||||||
|
|
||||||
|
debug:
|
||||||
|
docker run --platform linux/amd64 -i --rm -v $(shell pwd):/mount -w /mount/project $(TOOL_IMAGE) bash
|
||||||
|
|
||||||
|
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||||
|
.PHONY: $(FINGERPRINT_FILE)
|
||||||
|
$(FINGERPRINT_FILE):
|
||||||
|
@find project Dockerfile Makefile -type f -exec sha256sum {} \; | sort -k2 > $(FINGERPRINT_FILE)
|
||||||
|
|
||||||
|
# requirement 4: 'clean' goal to remove all generated test fixtures
|
||||||
|
clean:
|
||||||
|
rm -rf $(BIN) Dockerfile.sha256 $(FINGERPRINT_FILE)
|
||||||
|
|
||||||
|
.PHONY: tools tools-check build debug clean
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
BIN=../bin
|
||||||
|
|
||||||
|
all: $(BIN)/hello_gcc
|
||||||
|
|
||||||
|
$(BIN)/hello_gcc: hello.c
|
||||||
|
gcc hello.c -o $(BIN)/hello_gcc
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(BIN)/hello_gcc
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf("Hello, World!\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
81
syft/file/cataloger/executable/toolchains.go
Normal file
81
syft/file/cataloger/executable/toolchains.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package executable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"debug/buildinfo"
|
||||||
|
"debug/elf"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clangVersionPattern = regexp.MustCompile(`clang version (\d+\.\d+\.\d+)`)
|
||||||
|
gccVersionPattern = regexp.MustCompile(`GCC: \([^)]+\) (\d+\.\d+\.\d+)`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// elfGolangToolchainEvidence attempts to extract Go toolchain information from the ELF file.
|
||||||
|
func golangToolchainEvidence(reader io.ReaderAt) *file.Toolchain {
|
||||||
|
bi, err := buildinfo.Read(reader)
|
||||||
|
if err != nil || bi == nil {
|
||||||
|
// not a golang binary
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &file.Toolchain{
|
||||||
|
Name: "go",
|
||||||
|
Version: bi.GoVersion,
|
||||||
|
Kind: file.ToolchainKindCompiler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cToolchainEvidence attempts to extract C/C++ compiler information from the ELF .comment section.
|
||||||
|
// This detects GCC and Clang compilers based on their version strings.
|
||||||
|
func cToolchainEvidence(f *elf.File) *file.Toolchain {
|
||||||
|
commentSection := f.Section(".comment")
|
||||||
|
if commentSection == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := commentSection.Data()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// the .comment section contains null-terminated strings
|
||||||
|
comments := strings.Split(string(data), "\x00")
|
||||||
|
|
||||||
|
// check for clang first since clang binaries often have both GCC and clang entries
|
||||||
|
// (clang includes GCC compatibility info)
|
||||||
|
for _, comment := range comments {
|
||||||
|
if match := clangVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||||
|
return &file.Toolchain{
|
||||||
|
Name: "clang",
|
||||||
|
Version: match[1],
|
||||||
|
Kind: file.ToolchainKindCompiler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not clang, check for GCC
|
||||||
|
for _, comment := range comments {
|
||||||
|
if match := gccVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||||
|
return &file.Toolchain{
|
||||||
|
Name: "gcc",
|
||||||
|
Version: match[1],
|
||||||
|
Kind: file.ToolchainKindCompiler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGoToolchainPresent(toolchains []file.Toolchain) bool {
|
||||||
|
for _, tc := range toolchains {
|
||||||
|
if tc.Name == "go" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
61
syft/file/cataloger/executable/toolchains_test.go
Normal file
61
syft/file/cataloger/executable/toolchains_test.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package executable
|
||||||
|
|
||||||
|
import (
|
||||||
|
"debug/elf"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/internal/unionreader"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_cToolchainDetection(t *testing.T) {
|
||||||
|
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||||
|
t.Helper()
|
||||||
|
f, err := os.Open(filepath.Join("test-fixtures/toolchains", fixture))
|
||||||
|
require.NoError(t, err)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fixture string
|
||||||
|
want *file.Toolchain
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "gcc binary",
|
||||||
|
fixture: "gcc/bin/hello_gcc",
|
||||||
|
want: &file.Toolchain{
|
||||||
|
Name: "gcc",
|
||||||
|
Version: "13.4.0",
|
||||||
|
Kind: file.ToolchainKindCompiler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "clang binary",
|
||||||
|
fixture: "clang/bin/hello_clang",
|
||||||
|
want: &file.Toolchain{
|
||||||
|
Name: "clang",
|
||||||
|
Version: "18.1.8",
|
||||||
|
Kind: file.ToolchainKindCompiler,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
reader := readerForFixture(t, tt.fixture)
|
||||||
|
f, err := elf.NewFile(reader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got := cToolchainEvidence(f)
|
||||||
|
|
||||||
|
if d := cmp.Diff(tt.want, got); d != "" {
|
||||||
|
t.Errorf("cToolchainEvidence() mismatch (-want +got):\n%s", d)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ type (
|
|||||||
// RelocationReadOnly indicates the RELRO security protection level applied to an ELF binary.
|
// RelocationReadOnly indicates the RELRO security protection level applied to an ELF binary.
|
||||||
RelocationReadOnly string
|
RelocationReadOnly string
|
||||||
|
|
||||||
//SymbolType string
|
// SymbolType string
|
||||||
|
|
||||||
ToolchainKind string
|
ToolchainKind string
|
||||||
)
|
)
|
||||||
@ -26,11 +26,11 @@ const (
|
|||||||
RelocationReadOnlyFull RelocationReadOnly = "full" // full RELRO protection
|
RelocationReadOnlyFull RelocationReadOnly = "full" // full RELRO protection
|
||||||
|
|
||||||
//// from https://pkg.go.dev/cmd/nm
|
//// from https://pkg.go.dev/cmd/nm
|
||||||
//SymbolTypeText SymbolType = "T" // text (code) segment symbol
|
// SymbolTypeText SymbolType = "T" // text (code) segment symbol
|
||||||
//SymbolTypeTextStatic SymbolType = "t" // static text segment symbol
|
// SymbolTypeTextStatic SymbolType = "t" // static text segment symbol
|
||||||
//SymbolTypeReadOnly SymbolType = "R" // read-only data segment symbol
|
// SymbolTypeReadOnly SymbolType = "R" // read-only data segment symbol
|
||||||
//SymbolTypeReadOnlyStatic SymbolType = "r" // static read-only data segment symbol
|
// SymbolTypeReadOnlyStatic SymbolType = "r" // static read-only data segment symbol
|
||||||
//SymbolTypeData SymbolType = "D" // data segment symbol
|
// SymbolTypeData SymbolType = "D" // data segment symbol
|
||||||
//SymbolTypeDataStatic SymbolType = "d" // static data segment symbol
|
//SymbolTypeDataStatic SymbolType = "d" // static data segment symbol
|
||||||
//SymbolTypeBSS SymbolType = "B" // bss segment symbol
|
//SymbolTypeBSS SymbolType = "B" // bss segment symbol
|
||||||
//SymbolTypeBSSStatic SymbolType = "b" // static bss segment symbol
|
//SymbolTypeBSSStatic SymbolType = "b" // static bss segment symbol
|
||||||
@ -56,7 +56,7 @@ type Executable struct {
|
|||||||
ELFSecurityFeatures *ELFSecurityFeatures `json:"elfSecurityFeatures,omitempty" yaml:"elfSecurityFeatures" mapstructure:"elfSecurityFeatures"`
|
ELFSecurityFeatures *ELFSecurityFeatures `json:"elfSecurityFeatures,omitempty" yaml:"elfSecurityFeatures" mapstructure:"elfSecurityFeatures"`
|
||||||
|
|
||||||
// Symbols captures the selection from the symbol table found in the binary.
|
// Symbols captures the selection from the symbol table found in the binary.
|
||||||
//Symbols []Symbol `json:"symbols,omitempty" yaml:"symbols" mapstructure:"symbols"`
|
// Symbols []Symbol `json:"symbols,omitempty" yaml:"symbols" mapstructure:"symbols"`
|
||||||
SymbolNames []string `json:"symbolNames,omitempty" yaml:"symbolNames" mapstructure:"symbolNames"`
|
SymbolNames []string `json:"symbolNames,omitempty" yaml:"symbolNames" mapstructure:"symbolNames"`
|
||||||
|
|
||||||
// Toolchains captures information about the the compiler, linker, runtime, or other toolchains used to build (or otherwise exist within) the executable.
|
// Toolchains captures information about the the compiler, linker, runtime, or other toolchains used to build (or otherwise exist within) the executable.
|
||||||
@ -71,7 +71,7 @@ type Toolchain struct {
|
|||||||
// TODO: should we allow for aux information here? free form?
|
// TODO: should we allow for aux information here? free form?
|
||||||
}
|
}
|
||||||
|
|
||||||
//type Symbol struct {
|
// type Symbol struct {
|
||||||
// //Type SymbolType `json:"type" yaml:"type" mapstructure:"type"`
|
// //Type SymbolType `json:"type" yaml:"type" mapstructure:"type"`
|
||||||
// Type string `json:"type" yaml:"type" mapstructure:"type"`
|
// Type string `json:"type" yaml:"type" mapstructure:"type"`
|
||||||
// Name string `json:"name" yaml:"name" mapstructure:"name"`
|
// Name string `json:"name" yaml:"name" mapstructure:"name"`
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user