blob: da701a7fdf16332298a7b0a6815680de0699166d [file] [log] [blame] [edit]
// Copyright 2017 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package targets
import (
"encoding/binary"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"sync"
"time"
)
type Target struct {
osCommon
OS string
Arch string
VMArch string // e.g. amd64 for 386, or arm64 for arm
PtrSize uint64
PageSize uint64
NumPages uint64
DataOffset uint64
Int64Alignment uint64
LittleEndian bool
CFlags []string
Triple string
CCompiler string
Objdump string // name of objdump executable
KernelCompiler string // override CC when running kernel make
KernelLinker string // override LD when running kernel make
KernelArch string
KernelHeaderArch string
BrokenCompiler string
// NeedSyscallDefine is used by csource package to decide when to emit __NR_* defines.
NeedSyscallDefine func(nr uint64) bool
HostEndian binary.ByteOrder
SyscallTrampolines map[string]string
init *sync.Once
initOther *sync.Once
// Target for the other compiler. If SYZ_CLANG says to use gcc, this will be clang. Or the other way around.
other *Target
timeouts Timeouts
}
type osCommon struct {
// What OS can build native binaries for this OS.
// If not set, defaults to itself (i.e. native build).
// Later we can extend this to be a list, but so far we don't have more than one OS.
BuildOS string
// Does the OS use syscall numbers (e.g. Linux) or has interface based on functions (e.g. fuchsia).
SyscallNumbers bool
// Syscalls accept int64 arguments (>sizeof(void*)).
Int64SyscallArgs bool
// E.g. "__NR_" or "SYS_".
SyscallPrefix string
// ipc<->executor communication tuning.
// If ExecutorUsesShmem, programs and coverage are passed through shmem, otherwise via pipes.
ExecutorUsesShmem bool
// If ExecutorUsesForkServer, executor uses extended protocol with handshake.
ExecutorUsesForkServer bool
// Special mode for OSes that do not have support for building Go binaries.
// In this mode we run Go binaries on the host machine, only executor runs on target.
HostFuzzer bool
// How to run syz-executor directly.
// Some systems build syz-executor into their images.
// If this flag is not empty, syz-executor will not be copied to the machine, and will be run using
// this command instead.
ExecutorBin string
// Extension of executable files (notably, .exe for windows).
ExeExtension string
// Name of the kernel object file.
KernelObject string
// Name of cpp(1) executable.
CPP string
// Syscalls on which pseudo syscalls depend. Syzkaller will make sure that __NR* or SYS* definitions
// for those syscalls are enabled.
PseudoSyscallDeps map[string][]string
// Common CFLAGS for this OS.
cflags []string
}
// Timeouts structure parametrizes timeouts throughout the system.
// It allows to support different operating system, architectures and execution environments
// (emulation, models, etc) without scattering and duplicating knowledge about their execution
// performance everywhere.
// Timeouts calculation consists of 2 parts: base values and scaling.
// Base timeout values consist of a single syscall timeout, program timeout and "no output" timeout
// and are specified by the target (OS/arch), or defaults are used.
// Scaling part is calculated from the execution environment in pkg/mgrconfig based on VM type,
// kernel build type, emulation, etc. Scaling is specifically converged to a single number so that
// it can be specified/overridden for command line tools (e.g. syz-execprog -slowdown=10).
type Timeouts struct {
// Base scaling factor, used only for a single syscall timeout.
Slowdown int
// Capped scaling factor used for timeouts other than syscall timeout.
// It's already applied to all values in this struct, but can be used for one-off timeout values
// in the system. This should also be applied to syscall/program timeout attributes in syscall descriptions.
// Derived from Slowdown and should not be greater than Slowdown.
// The idea behind capping is that slowdown can be large (10-20) and most timeouts already
// include some safety margin. If we just multiply them we will get too large timeouts,
// e.g. program timeout can become 5s*20 = 100s, or "no output" timeout: 5m*20 = 100m.
Scale time.Duration
// Timeout for a single syscall, after this time the syscall is considered "blocked".
Syscall time.Duration
// Timeout for a single program execution.
Program time.Duration
// Timeout for "no output" detection.
NoOutput time.Duration
// Limit on a single VM running time, after this time a VM is restarted.
VMRunningTime time.Duration
// How long we should test to get "no output" error (derivative of NoOutput, here to avoid duplication).
NoOutputRunningTime time.Duration
}
const (
Akaros = "akaros"
FreeBSD = "freebsd"
Darwin = "darwin"
Fuchsia = "fuchsia"
Linux = "linux"
NetBSD = "netbsd"
OpenBSD = "openbsd"
TestOS = "test"
Trusty = "trusty"
Windows = "windows"
AMD64 = "amd64"
ARM64 = "arm64"
ARM = "arm"
I386 = "386"
MIPS64LE = "mips64le"
PPC64LE = "ppc64le"
S390x = "s390x"
RiscV64 = "riscv64"
TestArch64 = "64"
TestArch64Fork = "64_fork"
TestArch32Shmem = "32_shmem"
TestArch32ForkShmem = "32_fork_shmem"
)
func Get(OS, arch string) *Target {
return GetEx(OS, arch, useClang)
}
func GetEx(OS, arch string, clang bool) *Target {
target := List[OS][arch]
if target == nil {
return nil
}
target.init.Do(target.lazyInit)
if clang == useClang {
return target
}
target.initOther.Do(func() {
other := new(Target)
*other = *target
other.setCompiler(clang)
other.lazyInit()
target.other = other
})
return target.other
}
// nolint: lll
var List = map[string]map[string]*Target{
TestOS: {
TestArch64: {
PtrSize: 8,
PageSize: 4 << 10,
// Compile with -no-pie due to issues with ASan + ASLR on ppc64le.
CFlags: []string{"-fsanitize=address", "-no-pie"},
osCommon: osCommon{
SyscallNumbers: true,
SyscallPrefix: "SYS_",
ExecutorUsesShmem: false,
ExecutorUsesForkServer: false,
},
},
TestArch64Fork: {
PtrSize: 8,
PageSize: 8 << 10,
// Compile with -no-pie due to issues with ASan + ASLR on ppc64le.
CFlags: []string{"-fsanitize=address", "-no-pie"},
osCommon: osCommon{
SyscallNumbers: true,
SyscallPrefix: "SYS_",
ExecutorUsesShmem: false,
ExecutorUsesForkServer: true,
},
},
TestArch32Shmem: {
PtrSize: 4,
PageSize: 8 << 10,
Int64Alignment: 4,
CFlags: []string{"-static"},
osCommon: osCommon{
SyscallNumbers: true,
Int64SyscallArgs: true,
SyscallPrefix: "SYS_",
ExecutorUsesShmem: true,
ExecutorUsesForkServer: false,
},
},
TestArch32ForkShmem: {
PtrSize: 4,
PageSize: 4 << 10,
CFlags: []string{"-static-pie"},
osCommon: osCommon{
SyscallNumbers: true,
Int64SyscallArgs: true,
SyscallPrefix: "SYS_",
ExecutorUsesShmem: true,
ExecutorUsesForkServer: true,
HostFuzzer: true,
},
},
},
Linux: {
AMD64: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
CFlags: []string{"-m64"},
Triple: "x86_64-linux-gnu",
KernelArch: "x86_64",
KernelHeaderArch: "x86",
NeedSyscallDefine: func(nr uint64) bool {
// Only generate defines for new syscalls
// (added after commit 8a1ab3155c2ac on 2012-10-04).
return nr >= 313
},
},
I386: {
VMArch: AMD64,
PtrSize: 4,
PageSize: 4 << 10,
Int64Alignment: 4,
LittleEndian: true,
CFlags: []string{"-m32"},
Triple: "x86_64-linux-gnu",
KernelArch: "i386",
KernelHeaderArch: "x86",
},
ARM64: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
Triple: "aarch64-linux-gnu",
KernelArch: "arm64",
KernelHeaderArch: "arm64",
},
ARM: {
VMArch: ARM64,
PtrSize: 4,
PageSize: 4 << 10,
LittleEndian: true,
CFlags: []string{"-D__LINUX_ARM_ARCH__=6", "-march=armv6"},
Triple: "arm-linux-gnueabi",
KernelArch: "arm",
KernelHeaderArch: "arm",
},
MIPS64LE: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
CFlags: []string{"-march=mips64r2", "-mabi=64", "-EL"},
Triple: "mips64el-linux-gnuabi64",
KernelArch: "mips",
KernelHeaderArch: "mips",
},
PPC64LE: {
PtrSize: 8,
PageSize: 64 << 10,
LittleEndian: true,
CFlags: []string{"-D__powerpc64__"},
Triple: "powerpc64le-linux-gnu",
KernelArch: "powerpc",
KernelHeaderArch: "powerpc",
},
S390x: {
PtrSize: 8,
PageSize: 4 << 10,
DataOffset: 0xfffff000,
CFlags: []string{"-fPIE"},
LittleEndian: false,
Triple: "s390x-linux-gnu",
KernelArch: "s390",
KernelHeaderArch: "s390",
SyscallTrampolines: map[string]string{
// The s390x Linux syscall ABI allows for upto 5 input parameters passed in registers, and this is not enough
// for the mmap syscall. Therefore, all input parameters for the mmap syscall are packed into a struct
// on user stack and the pointer to the struct is passed as an input parameter to the syscall.
// To work around this problem we therefore reroute the mmap syscall to the glibc mmap wrapper.
"mmap": "mmap",
},
},
RiscV64: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
Triple: "riscv64-linux-gnu",
KernelArch: "riscv",
KernelHeaderArch: "riscv",
},
},
FreeBSD: {
AMD64: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
CCompiler: "clang",
CFlags: []string{"-m64"},
NeedSyscallDefine: func(nr uint64) bool {
// freebsd_12_shm_open, shm_open2, shm_rename, __realpathat, close_range, copy_file_range
return nr == 482 || nr >= 569
},
},
I386: {
VMArch: AMD64,
PtrSize: 4,
PageSize: 4 << 10,
// The default DataOffset doesn't work with 32-bit
// FreeBSD and using ld.lld due to collisions.
DataOffset: 256 << 20,
Int64Alignment: 4,
LittleEndian: true,
CCompiler: "clang",
CFlags: []string{"-m32"},
NeedSyscallDefine: func(nr uint64) bool {
// freebsd_12_shm_open, shm_open2, shm_rename, __realpathat, close_range, copy_file_range
return nr == 482 || nr >= 569
},
},
RiscV64: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
CCompiler: "clang",
CFlags: []string{"-m64"},
NeedSyscallDefine: func(nr uint64) bool {
// freebsd_12_shm_open, shm_open2, shm_rename, __realpathat, close_range, copy_file_range
return nr == 482 || nr >= 569
},
},
},
Darwin: {
AMD64: {
PtrSize: 8,
PageSize: 4 << 10,
DataOffset: 512 << 24,
LittleEndian: true,
CCompiler: "clang",
CFlags: []string{
"-m64",
"-I", sourceDirVar + "/san",
// FIXME(HerrSpace): syscall was marked as deprecated on macos
"-Wno-deprecated-declarations",
},
NeedSyscallDefine: dontNeedSyscallDefine,
},
},
NetBSD: {
AMD64: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
CFlags: []string{
"-m64",
"-static-pie",
"--sysroot", sourceDirVar + "/dest/",
},
CCompiler: sourceDirVar + "/tools/bin/x86_64--netbsd-g++",
},
},
OpenBSD: {
AMD64: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
CCompiler: "c++",
// PIE is enabled on OpenBSD by default, so no need for -static-pie.
CFlags: []string{"-m64", "-static", "-lutil"},
NeedSyscallDefine: func(nr uint64) bool {
switch nr {
case 8: // SYS___tfork
return true
case 94: // SYS___thrsleep
return true
case 198: // SYS___syscall
return true
case 295: // SYS___semctl
return true
case 301: // SYS___thrwakeup
return true
case 302: // SYS___threxit
return true
case 303: // SYS___thrsigdivert
return true
case 304: // SYS___getcwd
return true
case 329: // SYS___set_tcb
return true
case 330: // SYS___get_tcb
return true
}
return false
},
},
},
Fuchsia: {
AMD64: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
KernelHeaderArch: "x64",
CCompiler: sourceDirVar + "/prebuilt/third_party/clang/linux-x64/bin/clang",
Objdump: sourceDirVar + "/prebuilt/third_party/clang/linux-x64/bin/llvm-objdump",
CFlags: fuchsiaCFlags("x64", "x86_64"),
},
ARM64: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
KernelHeaderArch: ARM64,
CCompiler: sourceDirVar + "/prebuilt/third_party/clang/linux-x64/bin/clang",
Objdump: sourceDirVar + "/prebuilt/third_party/clang/linux-x64/bin/llvm-objdump",
CFlags: fuchsiaCFlags(ARM64, "aarch64"),
},
},
Windows: {
AMD64: {
PtrSize: 8,
// TODO(dvyukov): what should we do about 4k vs 64k?
PageSize: 4 << 10,
LittleEndian: true,
},
},
Akaros: {
AMD64: {
PtrSize: 8,
PageSize: 4 << 10,
LittleEndian: true,
KernelHeaderArch: "x86",
NeedSyscallDefine: dontNeedSyscallDefine,
CCompiler: sourceDirVar + "/toolchain/x86_64-ucb-akaros-gcc/bin/x86_64-ucb-akaros-g++",
CFlags: []string{
"-static",
},
},
},
Trusty: {
ARM: {
PtrSize: 4,
PageSize: 4 << 10,
LittleEndian: true,
NeedSyscallDefine: dontNeedSyscallDefine,
},
},
}
var oses = map[string]osCommon{
Linux: {
SyscallNumbers: true,
SyscallPrefix: "__NR_",
ExecutorUsesShmem: true,
ExecutorUsesForkServer: true,
KernelObject: "vmlinux",
PseudoSyscallDeps: map[string][]string{
"syz_read_part_table": {"memfd_create"},
"syz_mount_image": {"memfd_create"},
"syz_io_uring_setup": {"io_uring_setup"},
"syz_clone3": {"clone3", "exit"},
"syz_clone": {"clone", "exit"},
},
cflags: []string{"-static-pie"},
},
FreeBSD: {
SyscallNumbers: true,
Int64SyscallArgs: true,
SyscallPrefix: "SYS_",
ExecutorUsesShmem: true,
ExecutorUsesForkServer: true,
KernelObject: "kernel.full",
CPP: "g++",
// FreeBSD is missing toolchain support for static PIEs.
cflags: []string{
"-static",
"-lc++",
// For some configurations -no-pie is passed to the compiler,
// which is not used by clang.
// Ensure clang does not complain about it.
"-Wno-unused-command-line-argument",
},
},
Darwin: {
SyscallNumbers: true,
Int64SyscallArgs: true,
SyscallPrefix: "SYS_",
ExecutorUsesShmem: true,
// FIXME(HerrSpace): ForkServer is b0rked in a peculiar way. I did some
// printf debugging in parseOutput in ipc.go. It usually works for a
// few executions. Eventually the reported ncmd stops making sense and
// the resulting replies are partially garbage. I also looked at the
// executor side of things, but that's harder to track as we are just
// banging bytes in the shmem there and don't use structs like on the
// go side.
ExecutorUsesForkServer: false,
KernelObject: "kernel.kasan",
// Note: We need a real g++ here, not the symlink to clang++ common on
// macOS systems. Homebrews gcc package suffixes these with the gcc
// version to avoid conflicting with the macOS symlink. Currently -11.
CPP: "g++-11",
cflags: []string{"-lc++"},
},
NetBSD: {
BuildOS: Linux,
SyscallNumbers: true,
SyscallPrefix: "SYS_",
ExecutorUsesShmem: true,
ExecutorUsesForkServer: true,
KernelObject: "netbsd.gdb",
},
OpenBSD: {
SyscallNumbers: true,
SyscallPrefix: "SYS_",
ExecutorUsesShmem: true,
ExecutorUsesForkServer: true,
KernelObject: "bsd.gdb",
CPP: "ecpp",
},
Fuchsia: {
BuildOS: Linux,
SyscallNumbers: false,
ExecutorUsesShmem: false,
ExecutorUsesForkServer: false,
HostFuzzer: true,
ExecutorBin: "syz-executor",
KernelObject: "zircon.elf",
},
Windows: {
SyscallNumbers: false,
ExecutorUsesShmem: false,
ExecutorUsesForkServer: false,
ExeExtension: ".exe",
KernelObject: "vmlinux",
},
Akaros: {
BuildOS: Linux,
SyscallNumbers: true,
SyscallPrefix: "SYS_",
ExecutorUsesShmem: false,
ExecutorUsesForkServer: true,
HostFuzzer: true,
KernelObject: "akaros-kernel-64b",
},
Trusty: {
SyscallNumbers: true,
Int64SyscallArgs: true,
SyscallPrefix: "__NR_",
},
}
var (
commonCFlags = []string{
"-O2",
"-pthread",
"-Wall",
"-Werror",
"-Wparentheses",
"-Wunused-const-variable",
"-Wframe-larger-than=16384", // executor uses stacks of limited size, so no jumbo frames
"-Wno-stringop-overflow",
"-Wno-array-bounds",
"-Wno-format-overflow",
"-Wno-unused-but-set-variable",
"-Wno-unused-command-line-argument",
}
optionalCFlags = map[string]bool{
"-static": true, // some distributions don't have static libraries
"-static-pie": true, // this flag is also not supported everywhere
"-Wunused-const-variable": true, // gcc 5 does not support this flag
"-fsanitize=address": true, // some OSes don't have ASAN
"-Wno-stringop-overflow": true,
"-Wno-array-bounds": true,
"-Wno-format-overflow": true,
"-Wno-unused-but-set-variable": true,
"-Wno-unused-command-line-argument": true,
}
fallbackCFlags = map[string]string{
"-static-pie": "-static", // if an ASLR static binary is impossible, build just a static one
}
)
func fuchsiaCFlags(arch, clangArch string) []string {
out := sourceDirVar + "/out/" + arch
return []string{
"-Wno-deprecated",
"-target", clangArch + "-fuchsia",
"-ldriver",
"-lfdio",
"-lzircon",
"--sysroot", out + "/zircon_toolchain/obj/zircon/public/sysroot/sysroot",
"-I", sourceDirVar + "/sdk/lib/fdio/include",
"-I", sourceDirVar + "/zircon/system/ulib/fidl/include",
"-I", sourceDirVar + "/src/lib/ddk/include",
"-I", out + "/fidling/gen/sdk/fidl/fuchsia.device",
"-I", out + "/fidling/gen/sdk/fidl/fuchsia.device.manager",
"-I", out + "/fidling/gen/sdk/fidl/fuchsia.hardware.nand",
"-I", out + "/fidling/gen/sdk/fidl/fuchsia.hardware.power.statecontrol",
"-I", out + "/fidling/gen/sdk/fidl/fuchsia.hardware.usb.peripheral",
"-I", out + "/fidling/gen/zircon/vdso/zx",
"-L", out + "/" + arch + "-shared",
}
}
func init() {
for OS, archs := range List {
for arch, target := range archs {
initTarget(target, OS, arch)
}
}
arch32, arch64 := splitArch(runtime.GOARCH)
goos := runtime.GOOS
if goos == "android" {
goos = Linux
}
for _, target := range List[TestOS] {
if List[goos] == nil {
continue
}
arch := arch64
if target.PtrSize == 4 {
arch = arch32
}
host := List[goos][arch]
if host == nil {
continue
}
target.CCompiler = host.CCompiler
target.CPP = host.CPP
target.CFlags = append(append([]string{}, host.CFlags...), target.CFlags...)
target.CFlags = processMergedFlags(target.CFlags)
// At least FreeBSD/386 requires a non-default DataOffset value.
target.DataOffset = host.DataOffset
// In ESA/390 mode, the CPU is able to address only 31bit of memory but
// arithmetic operations are still 32bit
// Fix cflags by replacing compiler's -m32 option with -m31
if arch == S390x {
for i := range target.CFlags {
target.CFlags[i] = strings.Replace(target.CFlags[i], "-m32", "-m31", -1)
}
}
target.BuildOS = goos
}
}
func initTarget(target *Target, OS, arch string) {
if common, ok := oses[OS]; ok {
target.osCommon = common
}
target.init = new(sync.Once)
target.initOther = new(sync.Once)
target.OS = OS
target.Arch = arch
if target.KernelArch == "" {
target.KernelArch = target.Arch
}
if target.NeedSyscallDefine == nil {
target.NeedSyscallDefine = needSyscallDefine
}
if target.DataOffset == 0 {
target.DataOffset = 512 << 20
}
target.NumPages = (16 << 20) / target.PageSize
sourceDir := getSourceDir(target)
for sourceDir != "" && sourceDir[len(sourceDir)-1] == '/' {
sourceDir = sourceDir[:len(sourceDir)-1]
}
target.replaceSourceDir(&target.CCompiler, sourceDir)
target.replaceSourceDir(&target.Objdump, sourceDir)
for i := range target.CFlags {
target.replaceSourceDir(&target.CFlags[i], sourceDir)
}
if OS == Linux && arch == runtime.GOARCH {
// Don't use cross-compiler for native compilation, there are cases when this does not work:
// https://github.com/google/syzkaller/pull/619
// https://github.com/google/syzkaller/issues/387
// https://github.com/google/syzkaller/commit/06db3cec94c54e1cf720cdd5db72761514569d56
target.Triple = ""
}
if target.CCompiler == "" {
target.setCompiler(useClang)
}
if target.CPP == "" {
target.CPP = "cpp"
}
if target.Objdump == "" {
target.Objdump = "objdump"
if target.Triple != "" {
target.Objdump = target.Triple + "-objdump"
}
}
if target.BuildOS == "" {
target.BuildOS = OS
}
if runtime.GOOS != target.BuildOS {
// Spoil native binaries if they are not usable, so that nobody tries to use them later.
target.CCompiler = fmt.Sprintf("cant-build-%v-on-%v", target.OS, runtime.GOOS)
target.CPP = target.CCompiler
}
for _, flags := range [][]string{commonCFlags, target.osCommon.cflags} {
target.CFlags = append(target.CFlags, flags...)
}
if OS == TestOS {
if runtime.GOARCH != S390x {
target.LittleEndian = true
} else {
target.LittleEndian = false
}
}
if target.LittleEndian {
target.HostEndian = binary.LittleEndian
} else {
target.HostEndian = binary.BigEndian
}
// Temporal hack.
if OS == Linux && os.Getenv("SYZ_STARNIX_HACK") != "" {
target.ExecutorUsesShmem = false
target.ExecutorUsesForkServer = false
target.HostFuzzer = true
}
}
func (target *Target) Timeouts(slowdown int) Timeouts {
if slowdown <= 0 {
panic(fmt.Sprintf("bad slowdown %v", slowdown))
}
timeouts := target.timeouts
timeouts.Slowdown = slowdown
timeouts.Scale = time.Duration(slowdown)
if timeouts.Scale > 3 {
timeouts.Scale = 3
}
if timeouts.Syscall == 0 {
timeouts.Syscall = 50 * time.Millisecond
}
if timeouts.Program == 0 {
timeouts.Program = 5 * time.Second
}
if timeouts.NoOutput == 0 {
// The timeout used to be 3 mins for a long time.
// But (1) we were seeing flakes on linux where net namespace
// destruction can be really slow, and (2) gVisor watchdog timeout
// is 3 mins + 1/4 of that for checking period = 3m45s.
// Current linux max timeout is CONFIG_DEFAULT_HUNG_TASK_TIMEOUT=140
// and workqueue.watchdog_thresh=140 which both actually result
// in 140-280s detection delay.
// So the current timeout is 5 mins (300s).
// We don't want it to be too long too because it will waste time on real hangs.
timeouts.NoOutput = 5 * time.Minute
}
if timeouts.VMRunningTime == 0 {
timeouts.VMRunningTime = time.Hour
}
timeouts.Syscall *= time.Duration(slowdown)
timeouts.Program *= timeouts.Scale
timeouts.NoOutput *= timeouts.Scale
timeouts.VMRunningTime *= timeouts.Scale
timeouts.NoOutputRunningTime = timeouts.NoOutput + time.Minute
return timeouts
}
func (target *Target) setCompiler(clang bool) {
// setCompiler may be called effectively twice for target.other,
// so first we remove flags the previous call may have added.
pos := 0
for _, flag := range target.CFlags {
if flag == "-ferror-limit=0" ||
strings.HasPrefix(flag, "--target=") {
continue
}
target.CFlags[pos] = flag
pos++
}
target.CFlags = target.CFlags[:pos]
if clang {
target.CCompiler = "clang"
target.KernelCompiler = "clang"
target.KernelLinker = "ld.lld"
if target.Triple != "" {
target.CFlags = append(target.CFlags, "--target="+target.Triple)
}
target.CFlags = append(target.CFlags, "-ferror-limit=0")
} else {
target.CCompiler = "gcc"
target.KernelCompiler = ""
target.KernelLinker = ""
if target.Triple != "" {
target.CCompiler = target.Triple + "-" + target.CCompiler
}
}
}
func (target *Target) replaceSourceDir(param *string, sourceDir string) {
if !strings.Contains(*param, sourceDirVar) {
return
}
if sourceDir == "" {
target.BrokenCompiler = "SOURCEDIR is not set"
return
}
*param = strings.Replace(*param, sourceDirVar, sourceDir, -1)
}
func (target *Target) lazyInit() {
if runtime.GOOS != target.BuildOS || target.BrokenCompiler != "" {
return
}
// Only fail on CI for native build.
// On CI we want to fail loudly if cross-compilation breaks.
// Also fail if SOURCEDIR_GOOS is set b/c in that case user probably assumes it will work.
if (target.OS != runtime.GOOS || !runningOnCI) && getSourceDir(target) == "" {
if _, err := exec.LookPath(target.CCompiler); err != nil {
target.BrokenCompiler = fmt.Sprintf("%v is missing (%v)", target.CCompiler, err)
return
}
}
flagsToCheck := append([]string{}, target.CFlags...)
for _, value := range fallbackCFlags {
flagsToCheck = append(flagsToCheck, value)
}
flags := make(map[string]*bool)
commonCFlags := []string{}
uncommonCFlags := []string{}
var wg sync.WaitGroup
for _, flag := range flagsToCheck {
if !optionalCFlags[flag] {
commonCFlags = append(commonCFlags, flag)
continue
}
uncommonCFlags = append(uncommonCFlags, flag)
}
for _, flag := range uncommonCFlags {
_, exists := flags[flag]
if exists {
continue
}
res := new(bool)
flags[flag] = res
wg.Add(1)
go func(flag string) {
defer wg.Done()
*res = checkFlagSupported(target, commonCFlags, flag)
}(flag)
}
wg.Wait()
newCFlags := []string{}
for _, flag := range target.CFlags {
for {
if res := flags[flag]; res == nil || *res {
// The flag is either verified to be supported or must be supported.
newCFlags = append(newCFlags, flag)
} else if fallback := fallbackCFlags[flag]; fallback != "" {
// The flag is not supported, but probably we can replace it by another one.
flag = fallback
continue
}
break
}
}
target.CFlags = newCFlags
// Check that the compiler is actually functioning. It may be present, but still broken.
// Common for Linux distros, over time we've seen:
// Error: alignment too large: 15 assumed
// fatal error: asm/unistd.h: No such file or directory
// fatal error: asm/errno.h: No such file or directory
// collect2: error: ld terminated with signal 11 [Segmentation fault]
if runningOnCI || getSourceDir(target) != "" {
return // On CI all compilers are expected to work, so we don't do the following check.
}
args := []string{"-x", "c++", "-", "-o", "/dev/null"}
args = append(args, target.CFlags...)
cmd := exec.Command(target.CCompiler, args...)
cmd.Stdin = strings.NewReader(simpleProg)
if out, err := cmd.CombinedOutput(); err != nil {
target.BrokenCompiler = string(out)
return
}
}
func checkFlagSupported(target *Target, targetCFlags []string, flag string) bool {
args := []string{"-x", "c++", "-", "-o", "/dev/null", "-Werror", flag}
args = append(args, targetCFlags...)
cmd := exec.Command(target.CCompiler, args...)
cmd.Stdin = strings.NewReader(simpleProg)
return cmd.Run() == nil
}
// Split an arch into a pair of related 32 and 64 bit arch names.
// If the arch is unknown, we assume that the arch is 64 bit.
func splitArch(arch string) (string, string) {
type pair struct {
name32 string
name64 string
}
pairs := []pair{
{I386, AMD64},
{ARM, ARM64},
}
for _, p := range pairs {
if p.name32 == arch || p.name64 == arch {
return p.name32, p.name64
}
}
return "", arch
}
func processMergedFlags(flags []string) []string {
mutuallyExclusive := [][]string{
// For GCC, "-static-pie -static" is not equal to "-static".
// And since we do it anyway, also clean up those that do get overridden -
// this will improve the flags list readability.
{"-static", "-static-pie", "-no-pie", "-pie"},
}
// For mutually exclusive groups, keep only the last flag.
for _, group := range mutuallyExclusive {
m := map[string]bool{}
for _, s := range group {
m[s] = true
}
keep := ""
for i := len(flags) - 1; i >= 0; i-- {
if m[flags[i]] {
keep = flags[i]
break
}
}
if keep != "" {
newFlags := []string{}
for _, s := range flags {
if s == keep || !m[s] {
newFlags = append(newFlags, s)
}
}
flags = newFlags
}
}
// Clean up duplicates.
dup := map[string]bool{}
newFlags := []string{}
for _, s := range flags {
if dup[s] {
continue
}
newFlags = append(newFlags, s)
dup[s] = true
}
return newFlags
}
func getSourceDir(target *Target) string {
// First try the most granular env option.
name := fmt.Sprintf("SOURCEDIR_%s_%s_%s_%s",
strings.ToUpper(target.OS), strings.ToUpper(target.Arch),
strings.ToUpper(runtime.GOOS), strings.ToUpper(runtime.GOARCH),
)
if ret := os.Getenv(name); ret != "" {
return ret
}
// .. then the older one.
name = fmt.Sprintf("SOURCEDIR_%s", strings.ToUpper(target.OS))
if ret := os.Getenv(name); ret != "" {
return ret
}
return os.Getenv("SOURCEDIR")
}
func needSyscallDefine(nr uint64) bool { return true }
func dontNeedSyscallDefine(nr uint64) bool { return false }
var (
runningOnCI = os.Getenv("CI") != ""
useClang = os.Getenv("SYZ_CLANG") != ""
)
const (
sourceDirVar = "${SOURCEDIR}"
simpleProg = `
#include <stdio.h>
#include <dirent.h> // ensures that system headers are installed
#include <algorithm> // ensures that C++ headers are installed
int main() { printf("Hello, World!\n"); }
`
)