mirror of
https://github.com/anchore/syft.git
synced 2026-10-11 21:57:22 +02:00
add file-based toolchain detection
Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -17,8 +17,6 @@ import (
|
||||
"github.com/anchore/syft/syft/cataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/file/cataloger/executable"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/binary"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/cpp"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger/dotnet"
|
||||
@@ -147,18 +145,14 @@ func (cfg Catalog) ToFilesConfig() filecataloging.Config {
|
||||
log.WithFields("error", err).Warn("unable to configure file hashers")
|
||||
}
|
||||
|
||||
return filecataloging.Config{
|
||||
Selection: cfg.File.Metadata.Selection,
|
||||
Hashers: hashers,
|
||||
Content: filecontent.Config{
|
||||
Globs: cfg.File.Content.Globs,
|
||||
SkipFilesAboveSize: cfg.File.Content.SkipFilesAboveSize,
|
||||
},
|
||||
Executable: executable.Config{
|
||||
MIMETypes: executable.DefaultConfig().MIMETypes,
|
||||
Globs: cfg.File.Executable.Globs,
|
||||
},
|
||||
}
|
||||
c := filecataloging.DefaultConfig()
|
||||
c.Selection = cfg.File.Metadata.Selection
|
||||
c.Hashers = hashers
|
||||
c.Content.Globs = cfg.File.Content.Globs
|
||||
c.Content.SkipFilesAboveSize = cfg.File.Content.SkipFilesAboveSize
|
||||
c.Executable.Globs = cfg.File.Executable.Globs
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (cfg Catalog) ToLicenseConfig() cataloging.LicenseConfig {
|
||||
|
||||
@@ -64,7 +64,7 @@ func (c *fileConfig) PostLoad() error {
|
||||
}
|
||||
|
||||
func (c *fileConfig) DescribeFields(descriptions clio.FieldDescriptionSet) {
|
||||
descriptions.Add(&c.Metadata.Selection, `select which files should be captured by the file-metadata cataloger and included in the SBOM.
|
||||
descriptions.Add(&c.Metadata.Selection, `select which files should be captured by the file-metadata cataloger and included in the SBOM.
|
||||
Options include:
|
||||
- "all": capture all files from the search space
|
||||
- "owned-by-package": capture only files owned by packages
|
||||
|
||||
@@ -3,7 +3,7 @@ package internal
|
||||
const (
|
||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||
JSONSchemaVersion = "16.1.10"
|
||||
JSONSchemaVersion = "16.1.11"
|
||||
|
||||
// Changelog
|
||||
// 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field).
|
||||
@@ -17,4 +17,5 @@ const (
|
||||
// 16.1.8 - add VcpkgManifest metadata type for vcpkg manifest support
|
||||
// 16.1.9 - add Symbols (grouped by owning package import path) to GolangBinaryBuildinfoEntry metadata
|
||||
// 16.1.10 - add SafeTensorsModelInfo metadata type for the safetensors AI model cataloger
|
||||
// 16.1.11 - add file executable toolchain information
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "anchore.io/schema/syft/json/16.1.10/document",
|
||||
"$id": "anchore.io/schema/syft/json/16.1.11/document",
|
||||
"$ref": "#/$defs/Document",
|
||||
"$defs": {
|
||||
"AlpmDbEntry": {
|
||||
@@ -1398,6 +1398,13 @@
|
||||
"elfSecurityFeatures": {
|
||||
"$ref": "#/$defs/ELFSecurityFeatures",
|
||||
"description": "ELFSecurityFeatures contains ELF-specific security hardening information when Format is ELF."
|
||||
},
|
||||
"toolchains": {
|
||||
"items": {
|
||||
"$ref": "#/$defs/Toolchain"
|
||||
},
|
||||
"type": "array",
|
||||
"description": "Toolchains captures information about the compiler, linker, runtime, or other toolchains used to build (or otherwise exist within) the executable."
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
@@ -4450,6 +4457,27 @@
|
||||
],
|
||||
"description": "TerraformLockProviderEntry represents a single provider entry in a Terraform dependency lock file (.terraform.lock.hcl)."
|
||||
},
|
||||
"Toolchain": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Name is the name of the toolchain (e.g., \"gcc\", \"clang\", \"rust\", \"lld\", \"GNU AS\", etc.)."
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"description": "Version is the version of the toolchain."
|
||||
},
|
||||
"component": {
|
||||
"type": "string",
|
||||
"description": "Component indicates which part of the toolchain this represents (e.g., compiler, linker, assembler)."
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"component"
|
||||
]
|
||||
},
|
||||
"VcpkgManifest": {
|
||||
"properties": {
|
||||
"description": {
|
||||
|
||||
@@ -26,8 +26,11 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// MIMETypes are the MIME types that will be considered for executable cataloging.
|
||||
MIMETypes []string `json:"mime-types" yaml:"mime-types" mapstructure:"mime-types"`
|
||||
Globs []string `json:"globs" yaml:"globs" mapstructure:"globs"`
|
||||
|
||||
// Globs are the glob patterns that will be used to filter which files are cataloged.
|
||||
Globs []string `json:"globs" yaml:"globs" mapstructure:"globs"`
|
||||
}
|
||||
|
||||
type Cataloger struct {
|
||||
@@ -116,6 +119,51 @@ func processExecutableLocation(loc file.Location, resolver file.Resolver) (exec
|
||||
return processExecutable(loc, uReader)
|
||||
}
|
||||
|
||||
func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file.Executable, error) {
|
||||
data := file.Executable{}
|
||||
|
||||
// determine the executable format
|
||||
|
||||
format, err := findExecutableFormat(reader)
|
||||
if err != nil {
|
||||
log.Debugf("unable to determine executable kind for %v: %v", loc.RealPath, err)
|
||||
return nil, fmt.Errorf("unable to determine executable kind: %w", err)
|
||||
}
|
||||
|
||||
if format == "" {
|
||||
// this is not an "unknown", so just log -- this binary does not have parseable data in it
|
||||
log.Debugf("unable to determine executable format for %q", loc.RealPath)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data.Format = format
|
||||
|
||||
switch format {
|
||||
case file.ELF:
|
||||
if err = findELFFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine ELF features")
|
||||
err = fmt.Errorf("unable to determine ELF features: %w", err)
|
||||
}
|
||||
case file.PE:
|
||||
if err = findPEFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine PE features")
|
||||
err = fmt.Errorf("unable to determine PE features: %w", err)
|
||||
}
|
||||
case file.MachO:
|
||||
if err = findMachoFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine Macho features")
|
||||
err = fmt.Errorf("unable to determine Macho features: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// always allocate collections for presentation
|
||||
if data.ImportedLibraries == nil {
|
||||
data.ImportedLibraries = []string{}
|
||||
}
|
||||
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func catalogingProgress(locations int64) *monitor.TaskProgress {
|
||||
info := monitor.GenericTask{
|
||||
Title: monitor.Title{
|
||||
@@ -162,51 +210,6 @@ func locationMatchesGlob(loc file.Location, globs []string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func processExecutable(loc file.Location, reader unionreader.UnionReader) (*file.Executable, error) {
|
||||
data := file.Executable{}
|
||||
|
||||
// determine the executable format
|
||||
|
||||
format, err := findExecutableFormat(reader)
|
||||
if err != nil {
|
||||
log.Debugf("unable to determine executable kind for %v: %v", loc.RealPath, err)
|
||||
return nil, fmt.Errorf("unable to determine executable kind: %w", err)
|
||||
}
|
||||
|
||||
if format == "" {
|
||||
// this is not an "unknown", so just log -- this binary does not have parseable data in it
|
||||
log.Debugf("unable to determine executable format for %q", loc.RealPath)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
data.Format = format
|
||||
|
||||
switch format {
|
||||
case file.ELF:
|
||||
if err = findELFFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine ELF features")
|
||||
err = fmt.Errorf("unable to determine ELF features: %w", err)
|
||||
}
|
||||
case file.PE:
|
||||
if err = findPEFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine PE features")
|
||||
err = fmt.Errorf("unable to determine PE features: %w", err)
|
||||
}
|
||||
case file.MachO:
|
||||
if err = findMachoFeatures(&data, reader); err != nil {
|
||||
log.WithFields("error", err, "path", loc.RealPath).Trace("unable to determine Macho features")
|
||||
err = fmt.Errorf("unable to determine Macho features: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// always allocate collections for presentation
|
||||
if data.ImportedLibraries == nil {
|
||||
data.ImportedLibraries = []string{}
|
||||
}
|
||||
|
||||
return &data, err
|
||||
}
|
||||
|
||||
func findExecutableFormat(reader unionreader.UnionReader) (file.ExecutableFormat, error) {
|
||||
// read the first sector of the file
|
||||
buf := make([]byte, 512)
|
||||
|
||||
@@ -35,10 +35,33 @@ func findELFFeatures(data *file.Executable, reader unionreader.UnionReader) erro
|
||||
data.ELFSecurityFeatures = findELFSecurityFeatures(f)
|
||||
data.HasEntrypoint = elfHasEntrypoint(f)
|
||||
data.HasExports = elfHasExports(f)
|
||||
data.Toolchains = elfToolchains(reader, f)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func elfToolchains(reader unionreader.UnionReader, f *elf.File) []file.Toolchain {
|
||||
// parse the .comment section and symbol tables once and share them across the detectors
|
||||
comments := elfComments(f)
|
||||
symbols := elfSymbolSet(f)
|
||||
return includeNoneNil(
|
||||
golangToolchainEvidence(reader),
|
||||
cToolchainEvidence(comments, symbols),
|
||||
rustToolchainEvidence(comments),
|
||||
linkerToolchainEvidence(f, comments),
|
||||
)
|
||||
}
|
||||
|
||||
func includeNoneNil(evidence ...*file.Toolchain) []file.Toolchain {
|
||||
var toolchains []file.Toolchain
|
||||
for _, e := range evidence {
|
||||
if e != nil {
|
||||
toolchains = append(toolchains, *e)
|
||||
}
|
||||
}
|
||||
return toolchains
|
||||
}
|
||||
|
||||
func findELFSecurityFeatures(f *elf.File) *file.ELFSecurityFeatures {
|
||||
return &file.ELFSecurityFeatures{
|
||||
SymbolTableStripped: isElfSymbolTableStripped(f),
|
||||
@@ -82,6 +105,28 @@ func boolRef(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// elfSymbolSet collects the names from the static and dynamic symbol tables into a single set, so
|
||||
// detectors can test for runtime markers without re-reading and re-iterating the symbol tables each
|
||||
// time. It covers both tables since some evidence (e.g. the Fortran MAIN__ entry) lives only in the
|
||||
// static symbol table.
|
||||
func elfSymbolSet(f *elf.File) *strset.Set {
|
||||
set := strset.New()
|
||||
|
||||
if syms, err := f.Symbols(); err == nil {
|
||||
for _, sym := range syms {
|
||||
set.Add(sym.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if dynSyms, err := f.DynamicSymbols(); err == nil {
|
||||
for _, sym := range dynSyms {
|
||||
set.Add(sym.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func checkElfNXProtection(file *elf.File) bool {
|
||||
// find the program headers until you find the GNU_STACK segment
|
||||
for _, prog := range file.Progs {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -314,3 +315,73 @@ func elfWithDynamicSection(t *testing.T, class elf.Class, dynamic []byte) []byte
|
||||
buf.Write(dynamic)
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func Test_elfGoToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("testdata/golang", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
wantPresent bool
|
||||
}{
|
||||
{
|
||||
name: "go binary has toolchain",
|
||||
fixture: "bin/hello_linux",
|
||||
wantPresent: true,
|
||||
},
|
||||
}
|
||||
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)
|
||||
|
||||
toolchains := elfToolchains(reader, f)
|
||||
assert.Equal(t, tt.wantPresent, hasGoToolchain(toolchains))
|
||||
|
||||
if tt.wantPresent {
|
||||
require.NotEmpty(t, toolchains)
|
||||
assert.Equal(t, "go", toolchains[0].Name)
|
||||
assert.NotEmpty(t, toolchains[0].Version)
|
||||
assert.Equal(t, file.ToolchainComponentCompiler, toolchains[0].Component)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_elfCgoToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("testdata/golang", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
t.Run("cgo binary has both go and c toolchains", func(t *testing.T) {
|
||||
reader := readerForFixture(t, "bin/hello_linux_cgo")
|
||||
f, err := elf.NewFile(reader)
|
||||
require.NoError(t, err)
|
||||
|
||||
toolchains := elfToolchains(reader, f)
|
||||
|
||||
// versions are dynamic based on Docker image, so we ignore them in comparison
|
||||
want := []file.Toolchain{
|
||||
{Name: "go", Component: file.ToolchainComponentCompiler},
|
||||
{Name: "gcc", Component: file.ToolchainComponentCompiler},
|
||||
}
|
||||
|
||||
if d := cmp.Diff(want, toolchains, cmpopts.IgnoreFields(file.Toolchain{}, "Version")); d != "" {
|
||||
t.Errorf("elfToolchains() mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
|
||||
// verify versions are populated
|
||||
for _, tc := range toolchains {
|
||||
assert.NotEmpty(t, tc.Version, "expected version to be set for %s toolchain", tc.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader) er
|
||||
if !data.HasExports {
|
||||
data.HasExports = machoHasExports(f)
|
||||
}
|
||||
|
||||
data.Toolchains = machoToolchains(reader)
|
||||
}
|
||||
|
||||
// de-duplicate libraries
|
||||
@@ -56,6 +58,12 @@ func findMachoFeatures(data *file.Executable, reader unionreader.UnionReader) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func machoToolchains(reader unionreader.UnionReader) []file.Toolchain {
|
||||
return includeNoneNil(
|
||||
golangToolchainEvidence(reader),
|
||||
)
|
||||
}
|
||||
|
||||
func machoHasEntrypoint(f *macho.File) bool {
|
||||
// derived from struct entry_point_command found from which explicitly calls out LC_MAIN:
|
||||
// https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h
|
||||
|
||||
@@ -120,3 +120,39 @@ func Test_machoUniversal(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_machoGoToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("testdata/golang", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
wantPresent bool
|
||||
}{
|
||||
{
|
||||
name: "go binary has toolchain",
|
||||
fixture: "bin/hello_mac",
|
||||
wantPresent: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reader := readerForFixture(t, tt.fixture)
|
||||
|
||||
toolchains := machoToolchains(reader)
|
||||
assert.Equal(t, tt.wantPresent, hasGoToolchain(toolchains))
|
||||
|
||||
if tt.wantPresent {
|
||||
require.NotEmpty(t, toolchains)
|
||||
assert.Equal(t, "go", toolchains[0].Name)
|
||||
assert.NotEmpty(t, toolchains[0].Version)
|
||||
assert.Equal(t, file.ToolchainComponentCompiler, toolchains[0].Component)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ func findPEFeatures(data *file.Executable, reader unionreader.UnionReader) error
|
||||
data.ImportedLibraries = libs
|
||||
data.HasEntrypoint = peHasEntrypoint(f)
|
||||
data.HasExports = peHasExports(f)
|
||||
data.Toolchains = peToolchains(reader)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -82,3 +83,9 @@ func peHasExports(f *pe.File) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func peToolchains(reader unionreader.UnionReader) []file.Toolchain {
|
||||
return includeNoneNil(
|
||||
golangToolchainEvidence(reader),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
@@ -78,3 +79,39 @@ func Test_peHasExports(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_peGoToolchainDetection(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("testdata/golang", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
wantPresent bool
|
||||
}{
|
||||
{
|
||||
name: "go binary has toolchain",
|
||||
fixture: "bin/hello.exe",
|
||||
wantPresent: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reader := readerForFixture(t, tt.fixture)
|
||||
|
||||
toolchains := peToolchains(reader)
|
||||
assert.Equal(t, tt.wantPresent, hasGoToolchain(toolchains))
|
||||
|
||||
if tt.wantPresent {
|
||||
require.NotEmpty(t, toolchains)
|
||||
assert.Equal(t, "go", toolchains[0].Name)
|
||||
assert.NotEmpty(t, toolchains[0].Version)
|
||||
assert.Equal(t, file.ToolchainComponentCompiler, toolchains[0].Component)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
bin
|
||||
actual_verify
|
||||
Dockerfile.sha256
|
||||
Dockerfile.sha256
|
||||
*.fingerprint
|
||||
@@ -0,0 +1,18 @@
|
||||
FROM golang:1.24
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY main.go cgo_main.go ./
|
||||
|
||||
# pure Go builds (no CGO)
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /hello_linux .
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o /hello_mac .
|
||||
|
||||
RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o /hello.exe .
|
||||
|
||||
# CGO-enabled build (Linux only, uses gcc)
|
||||
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o /hello_linux_cgo ./cgo_main.go
|
||||
@@ -0,0 +1,42 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-golang-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 -i -v $(shell pwd)/$(BIN):/out $(TOOL_IMAGE) sh -c \
|
||||
"cp /hello_linux /hello_mac /hello.exe /hello_linux_cgo /out/"
|
||||
|
||||
debug:
|
||||
docker run -it --rm -v $(shell pwd):/mount -w /mount $(TOOL_IMAGE) sh
|
||||
|
||||
# requirement 3: we always need to recalculate the fingerprint based on source regardless of any existing fingerprint
|
||||
.PHONY: $(FINGERPRINT_FILE)
|
||||
$(FINGERPRINT_FILE):
|
||||
@find . -maxdepth 1 -type f \( -name "*.go" -o -name "go.*" -o -name "Dockerfile" -o -name "Makefile" \) \
|
||||
-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 fixtures fingerprint
|
||||
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int get_length(const char* s) {
|
||||
return strlen(s);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
msg := C.CString("Hello from CGO!")
|
||||
length := C.get_length(msg)
|
||||
fmt.Printf("String length: %d\n", int(length))
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module x
|
||||
|
||||
go 1.24.4
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
golang.org/x/text v0.21.0
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// use stdlib packages
|
||||
fmt.Println("Hello from Go!")
|
||||
fmt.Println(strings.ToUpper("test"))
|
||||
|
||||
// use golang.org/x package
|
||||
tag := language.English
|
||||
fmt.Println(tag.String())
|
||||
|
||||
// use third-party package
|
||||
spew.Dump(os.Args)
|
||||
|
||||
// use encoding/json
|
||||
data, _ := json.Marshal(map[string]string{"hello": "world"})
|
||||
fmt.Println(string(data))
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
bin/
|
||||
Dockerfile.sha256
|
||||
*.fingerprint
|
||||
@@ -0,0 +1,39 @@
|
||||
# invoke all make files in subdirectories
|
||||
.PHONY: all gcc clang lld mold gold rust fortran gdc gccgo gnat
|
||||
|
||||
all: gcc clang lld mold gold rust fortran gdc gccgo gnat
|
||||
|
||||
gcc:
|
||||
$(MAKE) -C gcc
|
||||
|
||||
clang:
|
||||
$(MAKE) -C clang
|
||||
|
||||
lld:
|
||||
$(MAKE) -C lld
|
||||
|
||||
mold:
|
||||
$(MAKE) -C mold
|
||||
|
||||
gold:
|
||||
$(MAKE) -C gold
|
||||
|
||||
rust:
|
||||
$(MAKE) -C rust
|
||||
|
||||
fortran:
|
||||
$(MAKE) -C fortran
|
||||
|
||||
gdc:
|
||||
$(MAKE) -C gdc
|
||||
|
||||
gccgo:
|
||||
$(MAKE) -C gccgo
|
||||
|
||||
gnat:
|
||||
$(MAKE) -C gnat
|
||||
|
||||
%:
|
||||
@for dir in gcc clang lld mold gold rust fortran gdc gccgo gnat; do \
|
||||
$(MAKE) -C $$dir $@; \
|
||||
done
|
||||
@@ -0,0 +1,6 @@
|
||||
FROM alpine:3.21.7@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d
|
||||
|
||||
RUN apk add --no-cache clang18 musl-dev make
|
||||
|
||||
# create symlink so 'clang' command works (Alpine installs as clang-18)
|
||||
RUN ln -s /usr/bin/clang-18 /usr/bin/clang
|
||||
@@ -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@sha256:d49302cbe2faf1a2dece312e258325e0d39e32874ea4dec122b4ac86f45d5b50
|
||||
@@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-fortran-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_fortran
|
||||
|
||||
$(BIN)/hello_fortran: hello.f90
|
||||
gfortran hello.f90 -o $(BIN)/hello_fortran
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_fortran
|
||||
@@ -0,0 +1,3 @@
|
||||
program hello
|
||||
print *, "Hello, World!"
|
||||
end program hello
|
||||
@@ -0,0 +1 @@
|
||||
FROM gcc:13.4.0@sha256:d49302cbe2faf1a2dece312e258325e0d39e32874ea4dec122b4ac86f45d5b50
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM debian:12.14@sha256:49ba348354a28e39c70beffd6cf43bdb8d55d81ce4b746b0428717d054b8bbc4
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gccgo make && rm -rf /var/lib/apt/lists/*
|
||||
@@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-gccgo-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_gccgo
|
||||
|
||||
$(BIN)/hello_gccgo: hello.go
|
||||
gccgo hello.go -o $(BIN)/hello_gccgo
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_gccgo
|
||||
@@ -0,0 +1,5 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
println("Hello, World!")
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM debian:12.14@sha256:49ba348354a28e39c70beffd6cf43bdb8d55d81ce4b746b0428717d054b8bbc4
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gdc make && rm -rf /var/lib/apt/lists/*
|
||||
@@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-gdc-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_gdc
|
||||
|
||||
$(BIN)/hello_gdc: hello.d
|
||||
gdc hello.d -o $(BIN)/hello_gdc
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_gdc
|
||||
@@ -0,0 +1,5 @@
|
||||
import std.stdio;
|
||||
|
||||
void main() {
|
||||
writeln("Hello, World!");
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM debian:12.14@sha256:49ba348354a28e39c70beffd6cf43bdb8d55d81ce4b746b0428717d054b8bbc4
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends gnat make && rm -rf /var/lib/apt/lists/*
|
||||
@@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-gnat-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_gnat
|
||||
|
||||
$(BIN)/hello_gnat: hello.adb
|
||||
gnatmake hello.adb -o $(BIN)/hello_gnat
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_gnat *.o *.ali
|
||||
@@ -0,0 +1,6 @@
|
||||
with Ada.Text_IO;
|
||||
|
||||
procedure Hello is
|
||||
begin
|
||||
Ada.Text_IO.Put_Line ("Hello, World!");
|
||||
end Hello;
|
||||
@@ -0,0 +1,51 @@
|
||||
V "GNAT Lib v12"
|
||||
M P W=b
|
||||
P ZX
|
||||
|
||||
RN
|
||||
RV NO_IO
|
||||
|
||||
U hello%b hello.adb b6dbb388 NE OO SU
|
||||
W ada%s ada.ads ada.ali
|
||||
Z ada.strings.text_buffers%s a-sttebu.adb a-sttebu.ali
|
||||
W ada.text_io%s a-textio.adb a-textio.ali
|
||||
|
||||
D ada.ads 20220819080952 76789da1 ada%s
|
||||
D a-except.ads 20220819080952 2a7d8d70 ada.exceptions%s
|
||||
D a-ioexce.ads 20220819080952 40018c65 ada.io_exceptions%s
|
||||
D a-stream.ads 20220819080952 119b8fb3 ada.streams%s
|
||||
D a-string.ads 20220819080952 90ac6797 ada.strings%s
|
||||
D a-sttebu.ads 20220819080952 18209c64 ada.strings.text_buffers%s
|
||||
D a-stuten.ads 20220819080952 33c54060 ada.strings.utf_encoding%s
|
||||
D a-tags.ads 20220819080952 9eaa38c6 ada.tags%s
|
||||
D a-textio.ads 20220819080952 deada98a ada.text_io%s
|
||||
D a-unccon.ads 20220819080952 0e9b276f ada.unchecked_conversion%s
|
||||
D hello.adb 20260618132941 68761a02 hello%b
|
||||
D interfac.ads 20220819080952 edec285f interfaces%s
|
||||
D i-cstrea.ads 20220819080952 e53d8b8e interfaces.c_streams%s
|
||||
D system.ads 20220819080952 a2da961d system%s
|
||||
D s-crtl.ads 20220819080952 8a0692f8 system.crtl%s
|
||||
D s-exctab.ads 20220819080952 91bef6ef system.exception_table%s
|
||||
D s-ficobl.ads 20220819080952 dc5161d4 system.file_control_block%s
|
||||
D s-parame.ads 20220819080952 d494a4a6 system.parameters%s
|
||||
D s-putima.ads 20220819080952 4e095881 system.put_images%s
|
||||
D s-secsta.ads 20220819080952 62cd9202 system.secondary_stack%s
|
||||
D s-soflin.ads 20220819080952 df77073e system.soft_links%s
|
||||
D s-stache.ads 20220819080952 0b81c1fe system.stack_checking%s
|
||||
D s-stalib.ads 20220819080952 1c9580f6 system.standard_library%s
|
||||
D s-stoele.ads 20220819080952 6d8e1ff4 system.storage_elements%s
|
||||
D s-stoele.adb 20220819080952 8f5a9db6 system.storage_elements%b
|
||||
D s-traent.ads 20220819080952 c81cbf8c system.traceback_entries%s
|
||||
D s-unstyp.ads 20220819080952 fa2a7f59 system.unsigned_types%s
|
||||
D s-wchcon.ads 20220819080952 d9032363 system.wch_con%s
|
||||
D unchconv.ads 20220819080952 ca2f9e18 unchecked_conversion%s
|
||||
G a e
|
||||
G c Z b b [hello standard 3 11 none]
|
||||
X 1 ada.ads
|
||||
16K9*Ada 20e8 11|1r6 5r4
|
||||
X 9 a-textio.ads
|
||||
58K13*Text_IO 727e16 11|1w10 5r8
|
||||
507U14*Put_Line 11|5s16
|
||||
X 11 hello.adb
|
||||
3U11*Hello 3b11 6l5 6t10
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
FROM alpine:3.21.7@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d
|
||||
|
||||
RUN apk add --no-cache gcc binutils-gold musl-dev make
|
||||
@@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-gold-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_gold
|
||||
|
||||
$(BIN)/hello_gold: hello.c
|
||||
gcc hello.c -fuse-ld=gold -o $(BIN)/hello_gold
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_gold
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
FROM alpine:3.21.7@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d
|
||||
|
||||
RUN apk add --no-cache clang18 lld musl-dev make
|
||||
|
||||
# create symlink so 'clang' command works (Alpine installs as clang-18)
|
||||
RUN ln -s /usr/bin/clang-18 /usr/bin/clang
|
||||
@@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-lld-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_lld
|
||||
|
||||
$(BIN)/hello_lld: hello.c
|
||||
clang hello.c -fuse-ld=lld -o $(BIN)/hello_lld
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_lld
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
FROM alpine:3.21.7@sha256:48b0309ca019d89d40f670aa1bc06e426dc0931948452e8491e3d65087abc07d
|
||||
|
||||
RUN apk add --no-cache gcc mold musl-dev make
|
||||
@@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-mold-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_mold
|
||||
|
||||
$(BIN)/hello_mold: hello.c
|
||||
gcc hello.c -fuse-ld=mold -o $(BIN)/hello_mold
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_mold
|
||||
@@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
FROM rust:1.83.0@sha256:a45bf1f5d9af0a23b26703b3500d70af1abff7f984a7abef5a104b42c02a292b
|
||||
@@ -0,0 +1,39 @@
|
||||
BIN=./bin
|
||||
TOOL_IMAGE=localhost/syft-toolchain-rust-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_rust
|
||||
|
||||
$(BIN)/hello_rust: hello.rs
|
||||
rustc hello.rs -o $(BIN)/hello_rust
|
||||
|
||||
clean:
|
||||
rm -f $(BIN)/hello_rust
|
||||
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, World!");
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/buildinfo"
|
||||
"debug/elf"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/scylladb/go-set/strset"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
)
|
||||
|
||||
// TODO: additional toolchain detectors we'd like to add. Each entry notes the signal location and the
|
||||
// component it yields.
|
||||
//
|
||||
// ELF (.comment / notes / symbols — same mechanism as the detectors below):
|
||||
// 1. rustc commit hash: scan rodata for "/rustc/<40-hex>/library/" panic paths; resolves rust provenance
|
||||
// for stripped or pre-1.73 binaries (where the .comment producer string is absent). needs a
|
||||
// commit->version lookup table to turn the hash into a semver. component: compiler.
|
||||
// 2. more clang forks: AMD AOCC and Arm Compiler. their producer strings are vendor-prefixed clang
|
||||
// versions (e.g. "AMD clang version 14.0.6 ...") that clangVersionPattern already matches, so today
|
||||
// they are mislabeled as plain "clang"; each needs a vendor pattern matched before clangVersionPattern
|
||||
// (the same ordering used for icx). not yet added because neither ships a freely buildable linux/amd64
|
||||
// image to anchor a real fixture (AOCC is proprietary; Arm Compiler is AArch64-only). component: compiler.
|
||||
// 3. Swift (Linux): presence of "swift5_*" sections marks Swift; version only when a "swiftlang-"
|
||||
// producer string is present (debug builds). component: compiler.
|
||||
// 4. Haskell GHC: RTS symbols (hs_init, stg_*) for identity; derive an approximate version from the
|
||||
// embedded "base_<x.y.z>" package symbol. component: compiler.
|
||||
// 5. identity-only (no reliable version) via runtime symbol prefixes: OCaml (caml_*), Nim (NimMain),
|
||||
// Crystal (__crystal_main), FreePascal (FPC_*), and the LLVM-based D compilers ldc/dmd (_Dmain). the
|
||||
// GCC D frontend (gdc) is already handled via the shared GCC .comment path. component: compiler.
|
||||
// 6. GNU assembler (GNU AS): only present in DWARF .debug_info producer strings, NOT .comment, so it
|
||||
// requires DWARF parsing and is absent in stripped builds. component: assembler.
|
||||
// 7. GNU ld (BFD): leaves no self-identifying marker; can only be inferred by elimination (ELF with a
|
||||
// compiler .comment but no lld/mold/gold evidence) at low confidence and with no version.
|
||||
//
|
||||
// not feasible via this mechanism (recorded so they aren't re-investigated):
|
||||
// - TinyCC (tcc): emits no .comment section, no producer string, and no identifying symbols (stripped
|
||||
// by default), so a tcc-built ELF carries no usable signal here.
|
||||
// - Intel classic icc/ifort, IBM XL, NVIDIA/PGI: distinct producer strings exist but the toolchains are
|
||||
// proprietary/deprecated and not freely dockerizable on linux/amd64 to anchor a real fixture.
|
||||
//
|
||||
// PE (Windows — needs debug/pe, not covered by this ELF-only file):
|
||||
// 8. MSVC link.exe + cl.exe: parse the Rich Header (the "DanS".."Rich" block, XOR-decoded into
|
||||
// ProdID/build pairs); map build numbers to Visual Studio/linker versions. components: linker + compiler.
|
||||
// 9. .NET / CLR: COR20 header (optional-header DataDirectory[14]) marks a managed assembly; read the
|
||||
// "TargetFrameworkAttribute" string (e.g. ".NETCoreApp,Version=v8.0") for the runtime version.
|
||||
// component: runtime. (NativeAOT .NET is a native binary with runtime symbols only and no clean version.)
|
||||
//
|
||||
// Mach-O (macOS — needs debug/macho):
|
||||
// 10. Swift: presence of "__swift5_*" sections; version via the "swiftlang-" producer when present.
|
||||
// component: compiler.
|
||||
|
||||
var (
|
||||
clangVersionPattern = regexp.MustCompile(`clang version (\d+\.\d+\.\d+)`)
|
||||
gccVersionPattern = regexp.MustCompile(`GCC: \([^)]+\) (\d+\.\d+\.\d+)`)
|
||||
// the Intel oneAPI DPC++/C++ compiler (icx/icpx) is an LLVM/clang fork that writes its own producer
|
||||
// string into .comment, e.g. "Intel(R) oneAPI DPC++/C++ Compiler 2024.0.2 (2024.0.2.20231223)" (see
|
||||
// https://briancallahan.net/blog/20240306.html for a real `readelf .comment` dump). since it is a clang
|
||||
// fork it may also emit a plain "clang version" entry, so it is matched before clangVersionPattern to
|
||||
// avoid mislabeling as upstream clang. evidence for the producer string and version scheme:
|
||||
// - https://briancallahan.net/blog/20240306.html
|
||||
// - https://www.intel.com/content/www/us/en/developer/articles/release-notes/oneapi-dpcpp-cpp-compiler-release-notes.html
|
||||
intelVersionPattern = regexp.MustCompile(`Intel\(R\) oneAPI[^0-9]*(\d+\.\d+\.\d+)`)
|
||||
// rustc embeds its own producer string in .comment for binaries built with rust >= 1.73 (e.g.
|
||||
// "rustc version 1.83.0 (90b35a623 2024-11-26)"). The shipped libstd objects instead carry the
|
||||
// "clang LLVM (rustc version ...)" form, so accept both.
|
||||
rustcVersionPattern = regexp.MustCompile(`rustc version (\d+\.\d+\.\d+)`)
|
||||
// LLVM lld writes "Linker: LLD <version>" into .comment (see https://lld.llvm.org/). distros often
|
||||
// inject a vendor prefix (e.g. Debian/Ubuntu emit "Linker: Ubuntu LLD 18.1.3"), so allow one.
|
||||
lldVersionPattern = regexp.MustCompile(`Linker: (?:.*? )?LLD (\d+\.\d+\.\d+)`)
|
||||
// mold writes "mold <version> (...; compatible with GNU ld)" into .comment.
|
||||
moldVersionPattern = regexp.MustCompile(`mold (\d+\.\d+\.\d+)`)
|
||||
// GNU gold writes a "gold <version>" descriptor into the .note.gnu.gold-version note section.
|
||||
goldVersionPattern = regexp.MustCompile(`gold (\d+\.\d+)`)
|
||||
)
|
||||
|
||||
// golangToolchainEvidence attempts to extract Go toolchain information from the binary build info.
|
||||
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,
|
||||
Component: file.ToolchainComponentCompiler,
|
||||
}
|
||||
}
|
||||
|
||||
// cToolchainEvidence attempts to extract compiler information from the ELF .comment section for the
|
||||
// GCC and Clang families. This covers upstream GCC/Clang, GCC frontends that share the GCC producer
|
||||
// string but pull in distinct runtime symbols (gfortran, gdc, gccgo, gnat), and the Intel oneAPI
|
||||
// (icx/icpx) clang fork.
|
||||
func cToolchainEvidence(comments []string, symbols *strset.Set) *file.Toolchain {
|
||||
for _, comment := range comments {
|
||||
// Intel oneAPI (icx) is a clang fork whose binaries carry both an "Intel(R) oneAPI ..." producer
|
||||
// string and a plain "clang version" entry, so it must be checked before the generic clang match.
|
||||
if match := intelVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "icx",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentCompiler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, comment := range comments {
|
||||
// check for clang first since clang binaries often have both GCC and clang entries
|
||||
// (clang includes GCC compatibility info)
|
||||
if match := clangVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "clang",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentCompiler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, comment := range comments {
|
||||
if match := gccVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: gccFrontendName(symbols),
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentCompiler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// gccFrontendName disambiguates which GCC frontend produced a binary. gfortran, gdc, gccgo, and gnat
|
||||
// all share the same "GCC: (...) <version>" string in .comment, so the only way to tell them apart from
|
||||
// a plain C/C++ build is by the language-specific runtime symbols they pull in. defaults to "gcc".
|
||||
func gccFrontendName(symbols *strset.Set) string {
|
||||
switch {
|
||||
case hasFortranEvidence(symbols):
|
||||
return "gfortran"
|
||||
case hasDEvidence(symbols):
|
||||
return "gdc"
|
||||
case hasGccgoEvidence(symbols):
|
||||
return "gccgo"
|
||||
case hasAdaEvidence(symbols):
|
||||
return "gnat"
|
||||
default:
|
||||
return "gcc"
|
||||
}
|
||||
}
|
||||
|
||||
// rustToolchainEvidence attempts to extract Rust compiler information from the ELF .comment section.
|
||||
func rustToolchainEvidence(comments []string) *file.Toolchain {
|
||||
for _, comment := range comments {
|
||||
if match := rustcVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "rust",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentCompiler,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// linkerToolchainEvidence attempts to extract linker information from the binary. lld and mold leave a
|
||||
// version string in the ELF .comment section, while gold writes a dedicated .note.gnu.gold-version note.
|
||||
// GNU ld (BFD) leaves no self-identifying marker, so it cannot be detected here.
|
||||
func linkerToolchainEvidence(f *elf.File, comments []string) *file.Toolchain {
|
||||
for _, comment := range comments {
|
||||
if match := lldVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "lld",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentLinker,
|
||||
}
|
||||
}
|
||||
if match := moldVersionPattern.FindStringSubmatch(comment); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "mold",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentLinker,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if section := f.Section(".note.gnu.gold-version"); section != nil {
|
||||
data, err := section.Data()
|
||||
if err == nil {
|
||||
if match := goldVersionPattern.FindStringSubmatch(string(data)); match != nil {
|
||||
return &file.Toolchain{
|
||||
Name: "gold",
|
||||
Version: match[1],
|
||||
Component: file.ToolchainComponentLinker,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// elfComments returns the null-delimited strings within the ELF .comment section, which may contain
|
||||
// multiple producer entries (compiler, assembler, linker, etc.).
|
||||
func elfComments(f *elf.File) []string {
|
||||
section := f.Section(".comment")
|
||||
if section == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := section.Data()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// the .comment section contains null-terminated strings
|
||||
return strings.Split(string(data), "\x00")
|
||||
}
|
||||
|
||||
// hasFortranEvidence reports whether the binary references the libgfortran runtime, which indicates a
|
||||
// gfortran build (MAIN__ is the real Fortran program entry, and _gfortran_* are runtime calls).
|
||||
func hasFortranEvidence(symbols *strset.Set) bool {
|
||||
return symbols.HasAny("MAIN__", "_gfortran_set_args", "_gfortran_set_options", "_gfortran_st_write")
|
||||
}
|
||||
|
||||
// hasDEvidence reports whether the binary references the gdc (GNU D) runtime. _Dmain is the D program
|
||||
// entry the compiler emits, and __gdc_personality_v0 is gdc's exception-handling personality routine.
|
||||
// see the gdc frontend in gcc: https://github.com/gcc-mirror/gcc/tree/master/gcc/d
|
||||
func hasDEvidence(symbols *strset.Set) bool {
|
||||
return symbols.HasAny("_Dmain", "__gdc_personality_v0")
|
||||
}
|
||||
|
||||
// hasGccgoEvidence reports whether the binary was built by gccgo. the libgo runtime exports __go_*
|
||||
// helpers (e.g. __go_go to start a goroutine) that the gc toolchain does not emit, so these
|
||||
// distinguish gccgo both from a plain gcc C build and from a cgo-enabled gc binary (which also carries
|
||||
// a gcc .comment). see libgo: https://github.com/gcc-mirror/gcc/tree/master/libgo/runtime
|
||||
func hasGccgoEvidence(symbols *strset.Set) bool {
|
||||
return symbols.HasAny("__go_go", "__go_init_main", "__go_register_gc_roots")
|
||||
}
|
||||
|
||||
// hasAdaEvidence reports whether the binary references the GNAT Ada runtime. adainit/adafinal are the
|
||||
// elaboration entry points and __gnat_* are runtime support calls.
|
||||
// see the GNAT runtime (libgnat): https://github.com/gcc-mirror/gcc/tree/master/gcc/ada
|
||||
func hasAdaEvidence(symbols *strset.Set) bool {
|
||||
return symbols.HasAny("__gnat_initialize", "__gnat_runtime_initialize", "adainit", "adafinal")
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
package executable
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/scylladb/go-set/strset"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/internal/unionreader"
|
||||
)
|
||||
|
||||
// hasGoToolchain is a test helper to check whether the go toolchain was detected.
|
||||
func hasGoToolchain(toolchains []file.Toolchain) bool {
|
||||
for _, tc := range toolchains {
|
||||
if tc.Name == "go" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func Test_elfToolchains(t *testing.T) {
|
||||
readerForFixture := func(t *testing.T, fixture string) unionreader.UnionReader {
|
||||
t.Helper()
|
||||
f, err := os.Open(filepath.Join("testdata/toolchains", fixture))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
compiler := file.ToolchainComponentCompiler
|
||||
linker := file.ToolchainComponentLinker
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fixture string
|
||||
want []file.Toolchain
|
||||
}{
|
||||
{
|
||||
name: "gcc: compiler only",
|
||||
fixture: "gcc/bin/hello_gcc",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gcc", Version: "13.4.0", Component: compiler},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "clang: compiler only",
|
||||
fixture: "clang/bin/hello_clang",
|
||||
want: []file.Toolchain{
|
||||
{Name: "clang", Version: "18.1.8", Component: compiler},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "lld: clang compiler + lld linker",
|
||||
fixture: "lld/bin/hello_lld",
|
||||
want: []file.Toolchain{
|
||||
{Name: "clang", Version: "18.1.8", Component: compiler},
|
||||
{Name: "lld", Version: "19.1.4", Component: linker},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mold: gcc compiler + mold linker",
|
||||
fixture: "mold/bin/hello_mold",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gcc", Version: "14.2.0", Component: compiler},
|
||||
{Name: "mold", Version: "2.34.1", Component: linker},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "gold: gcc compiler + gold linker",
|
||||
fixture: "gold/bin/hello_gold",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gcc", Version: "14.2.0", Component: compiler},
|
||||
{Name: "gold", Version: "1.16", Component: linker},
|
||||
},
|
||||
},
|
||||
{
|
||||
// rust binaries also carry a GCC producer string from the gcc-compiled C runtime glue that is
|
||||
// linked in, so both compilers are reported (akin to how cgo binaries report go and gcc).
|
||||
name: "rust: gcc glue + rustc",
|
||||
fixture: "rust/bin/hello_rust",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gcc", Version: "12.2.0", Component: compiler},
|
||||
{Name: "rust", Version: "1.83.0", Component: compiler},
|
||||
},
|
||||
},
|
||||
{
|
||||
// gfortran shares the GCC version string in .comment, so it is only distinguishable from a
|
||||
// C build by the presence of libgfortran runtime symbols (gcc gets relabeled to gfortran).
|
||||
name: "fortran: gfortran compiler only",
|
||||
fixture: "fortran/bin/hello_fortran",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gfortran", Version: "13.4.0", Component: compiler},
|
||||
},
|
||||
},
|
||||
{
|
||||
// gdc is a GCC frontend that shares the GCC version string, so it is distinguished from a C
|
||||
// build only by the D runtime symbols (_Dmain, __gdc_personality_v0).
|
||||
name: "gdc: GNU D compiler only",
|
||||
fixture: "gdc/bin/hello_gdc",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gdc", Version: "12.2.0", Component: compiler},
|
||||
},
|
||||
},
|
||||
{
|
||||
// gccgo is a GCC frontend (not the gc toolchain, so it has no Go build info); it shares the
|
||||
// GCC version string and is distinguished only by the libgo __go_* runtime symbols.
|
||||
name: "gccgo: GNU Go compiler only",
|
||||
fixture: "gccgo/bin/hello_gccgo",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gccgo", Version: "12.2.0", Component: compiler},
|
||||
},
|
||||
},
|
||||
{
|
||||
// gnat is the GCC Ada frontend; it shares the GCC version string and is distinguished only by
|
||||
// the GNAT runtime symbols (adainit, __gnat_*).
|
||||
name: "gnat: GNU Ada compiler only",
|
||||
fixture: "gnat/bin/hello_gnat",
|
||||
want: []file.Toolchain{
|
||||
{Name: "gnat", Version: "12.2.0", Component: compiler},
|
||||
},
|
||||
},
|
||||
}
|
||||
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 := elfToolchains(reader, f)
|
||||
|
||||
if d := cmp.Diff(tt.want, got); d != "" {
|
||||
t.Errorf("elfToolchains() mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_cToolchainEvidence(t *testing.T) {
|
||||
compiler := file.ToolchainComponentCompiler
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
comments []string
|
||||
symbols []string
|
||||
want *file.Toolchain
|
||||
}{
|
||||
{
|
||||
// icx (Intel oneAPI DPC++/C++) is an LLVM/clang fork. there is no freely buildable linux/amd64
|
||||
// image to anchor a real fixture, so the literal .comment producer string is asserted here. the
|
||||
// exact form is confirmed by a real `readelf -x .comment` dump (which yielded
|
||||
// "Intel(R) oneAPI DPC++/C++ Compiler 2024.0.2 (2024.0.2.20231223)"):
|
||||
// - https://briancallahan.net/blog/20240306.html
|
||||
// - https://www.intel.com/content/www/us/en/developer/articles/release-notes/oneapi-dpcpp/2025.html
|
||||
// because it is a clang fork, the Intel string must be matched before the generic clang pattern;
|
||||
// the second "clang version" entry below guards that ordering (a generic clang match must not win).
|
||||
name: "intel oneAPI icx is matched before clang",
|
||||
comments: []string{
|
||||
"Intel(R) oneAPI DPC++/C++ Compiler 2024.0.2 (2024.0.2.20231223)",
|
||||
"clang version 18.1.0",
|
||||
},
|
||||
want: &file.Toolchain{Name: "icx", Version: "2024.0.2", Component: compiler},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := cToolchainEvidence(tt.comments, strset.New(tt.symbols...))
|
||||
if d := cmp.Diff(tt.want, got); d != "" {
|
||||
t.Errorf("cToolchainEvidence() mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_linkerToolchainEvidence(t *testing.T) {
|
||||
linker := file.ToolchainComponentLinker
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
comments []string
|
||||
want *file.Toolchain
|
||||
}{
|
||||
{
|
||||
// lld writes "Linker: " + getLLDVersion() into .comment. docs and source:
|
||||
// - https://lld.llvm.org/ ("If the string \"Linker: LLD\" is included ... you are using LLD")
|
||||
// - https://github.com/llvm/llvm-project/blob/main/lld/ELF/SyntheticSections.cpp (Twine("Linker: ") + getLLDVersion())
|
||||
name: "vanilla lld",
|
||||
comments: []string{"Linker: LLD 19.1.4"},
|
||||
want: &file.Toolchain{Name: "lld", Version: "19.1.4", Component: linker},
|
||||
},
|
||||
{
|
||||
// debian/ubuntu prepend a vendor to getLLDVersion() (the same packaging mechanism behind clang's
|
||||
// "Ubuntu clang version ..."), so .comment reads "Linker: Ubuntu LLD <version>". reproduced on
|
||||
// ubuntu:24.04: `readelf -p .comment <bin>` => "Linker: Ubuntu LLD 18.1.3".
|
||||
name: "vendor-prefixed lld",
|
||||
comments: []string{"Linker: Ubuntu LLD 18.1.3"},
|
||||
want: &file.Toolchain{Name: "lld", Version: "18.1.3", Component: linker},
|
||||
},
|
||||
{
|
||||
// mold writes "mold <version> (<commit>; compatible with GNU ld)" into .comment. this format is
|
||||
// documented by mold and is also exercised end-to-end by the real fixture in Test_elfToolchains:
|
||||
// - https://github.com/rui314/mold (README: "mold leaves its identification string in .comment")
|
||||
name: "mold",
|
||||
comments: []string{"mold 2.34.1 (compatible with GNU ld)"},
|
||||
want: &file.Toolchain{Name: "mold", Version: "2.34.1", Component: linker},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// f is only consulted for the gold note, which these comment-based cases match before reaching.
|
||||
got := linkerToolchainEvidence(nil, tt.comments)
|
||||
if d := cmp.Diff(tt.want, got); d != "" {
|
||||
t.Errorf("linkerToolchainEvidence() mismatch (-want +got):\n%s", d)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,16 @@ type (
|
||||
|
||||
// RelocationReadOnly indicates the RELRO security protection level applied to an ELF binary.
|
||||
RelocationReadOnly string
|
||||
|
||||
// ToolchainComponent represents which part of the toolchain an entry describes (e.g. compiler, linker).
|
||||
// This can be expanded in the future to include assemblers, runtimes, and other toolchain components.
|
||||
ToolchainComponent string
|
||||
)
|
||||
|
||||
const (
|
||||
ToolchainComponentCompiler ToolchainComponent = "compiler"
|
||||
ToolchainComponentLinker ToolchainComponent = "linker"
|
||||
|
||||
ELF ExecutableFormat = "elf" // Executable and Linkable Format used on Unix-like systems
|
||||
MachO ExecutableFormat = "macho" // Mach object file format used on macOS and iOS
|
||||
PE ExecutableFormat = "pe" // Portable Executable format used on Windows
|
||||
@@ -34,6 +41,20 @@ type Executable struct {
|
||||
|
||||
// ELFSecurityFeatures contains ELF-specific security hardening information when Format is ELF.
|
||||
ELFSecurityFeatures *ELFSecurityFeatures `json:"elfSecurityFeatures,omitempty" yaml:"elfSecurityFeatures" mapstructure:"elfSecurityFeatures"`
|
||||
|
||||
// Toolchains captures information about the compiler, linker, runtime, or other toolchains used to build (or otherwise exist within) the executable.
|
||||
Toolchains []Toolchain `json:"toolchains,omitempty" yaml:"toolchains" mapstructure:"toolchains"`
|
||||
}
|
||||
|
||||
type Toolchain struct {
|
||||
// Name is the name of the toolchain (e.g., "gcc", "clang", "rust", "lld", "GNU AS", etc.).
|
||||
Name string `json:"name" yaml:"name" mapstructure:"name"`
|
||||
|
||||
// Version is the version of the toolchain.
|
||||
Version string `json:"version,omitempty" yaml:"version,omitempty" mapstructure:"version"`
|
||||
|
||||
// Component indicates which part of the toolchain this represents (e.g., compiler, linker, assembler).
|
||||
Component ToolchainComponent `json:"component" yaml:"component" mapstructure:"component"`
|
||||
}
|
||||
|
||||
// ELFSecurityFeatures captures security hardening and protection mechanisms in ELF binaries.
|
||||
|
||||
Reference in New Issue
Block a user