vm/adb: support multiple adb devices

Device IDs are specified in "devices" config param.
This commit is contained in:
Dmitry Vyukov 2016-08-30 14:33:39 +02:00
parent 26a5cf9efa
commit bc9b349bd7
5 changed files with 119 additions and 58 deletions

View File

@ -34,10 +34,11 @@ type Config struct {
Debug bool // dump all VM output to console
Output string // one of stdout/dmesg/file (useful only for local VM)
Syzkaller string // path to syzkaller checkout (syz-manager will look for binaries in bin subdir)
Type string // VM type (qemu, kvm, local)
Count int // number of VMs
Procs int // number of parallel processes inside of every VM
Syzkaller string // path to syzkaller checkout (syz-manager will look for binaries in bin subdir)
Type string // VM type (qemu, kvm, local)
Count int // number of VMs (don't secify for adb, instead specify devices)
Devices []string // device IDs for adb
Procs int // number of parallel processes inside of every VM
Sandbox string // type of sandbox to use during fuzzing:
// "none": don't do anything special (has false positives, e.g. due to killing init)
@ -48,8 +49,6 @@ type Config struct {
Cover bool // use kcov coverage (default: true)
Leak bool // do memory leak checking
ConsoleDev string // console device for adb vm
Enable_Syscalls []string
Disable_Syscalls []string
Suppressions []string
@ -98,21 +97,36 @@ func parse(data []byte) (*Config, map[int]bool, []*regexp.Regexp, error) {
if cfg.Type == "" {
return nil, nil, nil, fmt.Errorf("config param type is empty")
}
if cfg.Type == "none" {
switch cfg.Type {
case "none":
if cfg.Count != 0 {
return nil, nil, nil, fmt.Errorf("invalid config param count: %v, type \"none\" does not support param count", cfg.Count)
}
if cfg.Rpc == "" {
return nil, nil, nil, fmt.Errorf("config param rpc is empty (required for type \"none\")")
}
} else {
if len(cfg.Devices) != 0 {
return nil, nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type)
}
case "adb":
if cfg.Count != 0 {
return nil, nil, nil, fmt.Errorf("don't specify count for adb, instead specify devices")
}
if len(cfg.Devices) == 0 {
return nil, nil, nil, fmt.Errorf("specify at least 1 adb device")
}
cfg.Count = len(cfg.Devices)
default:
if cfg.Count <= 0 || cfg.Count > 1000 {
return nil, nil, nil, fmt.Errorf("invalid config param count: %v, want (1, 1000]", cfg.Count)
}
if cfg.Rpc == "" {
cfg.Rpc = "localhost:0"
if len(cfg.Devices) != 0 {
return nil, nil, nil, fmt.Errorf("type %v does not support devices param", cfg.Type)
}
}
if cfg.Rpc == "" {
cfg.Rpc = "localhost:0"
}
if cfg.Procs <= 0 {
cfg.Procs = 1
}
@ -219,26 +233,31 @@ func parseSuppressions(cfg *Config) ([]*regexp.Regexp, error) {
return suppressions, nil
}
func CreateVMConfig(cfg *Config) (*vm.Config, error) {
func CreateVMConfig(cfg *Config, index int) (*vm.Config, error) {
if index < 0 || index >= cfg.Count {
return nil, fmt.Errorf("invalid VM index %v (count %v)", index, cfg.Count)
}
workdir, index, err := fileutil.ProcessTempDir(cfg.Workdir)
if err != nil {
return nil, fmt.Errorf("failed to create instance temp dir: %v", err)
}
vmCfg := &vm.Config{
Name: fmt.Sprintf("%v-%v", cfg.Type, index),
Index: index,
Workdir: workdir,
Bin: cfg.Bin,
Kernel: cfg.Kernel,
Cmdline: cfg.Cmdline,
Image: cfg.Image,
Initrd: cfg.Initrd,
Sshkey: cfg.Sshkey,
Executor: filepath.Join(cfg.Syzkaller, "bin", "syz-executor"),
ConsoleDev: cfg.ConsoleDev,
Cpu: cfg.Cpu,
Mem: cfg.Mem,
Debug: cfg.Debug,
Name: fmt.Sprintf("%v-%v", cfg.Type, index),
Index: index,
Workdir: workdir,
Bin: cfg.Bin,
Kernel: cfg.Kernel,
Cmdline: cfg.Cmdline,
Image: cfg.Image,
Initrd: cfg.Initrd,
Sshkey: cfg.Sshkey,
Executor: filepath.Join(cfg.Syzkaller, "bin", "syz-executor"),
Cpu: cfg.Cpu,
Mem: cfg.Mem,
Debug: cfg.Debug,
}
if len(cfg.Devices) != 0 {
vmCfg.Device = cfg.Devices[index]
}
return vmCfg, nil
}
@ -264,11 +283,11 @@ func checkUnknownFields(data []byte) (string, error) {
"Syzkaller",
"Type",
"Count",
"Devices",
"Procs",
"Cover",
"Sandbox",
"Leak",
"ConsoleDev",
"Enable_Syscalls",
"Disable_Syscalls",
"Suppressions",

View File

@ -166,18 +166,18 @@ func RunManager(cfg *config.Config, syscalls map[int]bool, suppressions []*regex
var wg sync.WaitGroup
wg.Add(cfg.Count + 1)
for i := 0; i < cfg.Count; i++ {
first := i == 0
i := i
go func() {
defer wg.Done()
for {
vmCfg, err := config.CreateVMConfig(cfg)
vmCfg, err := config.CreateVMConfig(cfg, i)
if atomic.LoadUint32(&shutdown) != 0 {
break
}
if err != nil {
fatalf("failed to create VM config: %v", err)
}
ok := mgr.runInstance(vmCfg, first)
ok := mgr.runInstance(vmCfg, i == 0)
if atomic.LoadUint32(&shutdown) != 0 {
break
}

View File

@ -30,12 +30,13 @@ var (
flagCount = flag.Int("count", 0, "number of VMs to use (overrides config count param)")
instances chan VM
bootRequests chan bool
bootRequests chan int
shutdown = make(chan struct{})
)
type VM struct {
vm.Instance
index int
execprogBin string
executorBin string
}
@ -70,12 +71,12 @@ func main() {
log.Printf("target crash: '%s'", crashDesc)
instances = make(chan VM, cfg.Count)
bootRequests = make(chan bool, cfg.Count)
bootRequests = make(chan int, cfg.Count)
for i := 0; i < cfg.Count; i++ {
bootRequests <- true
bootRequests <- i
go func() {
for range bootRequests {
vmCfg, err := config.CreateVMConfig(cfg)
for index := range bootRequests {
vmCfg, err := config.CreateVMConfig(cfg, index)
if err != nil {
log.Fatalf("failed to create VM config: %v", err)
}
@ -91,7 +92,7 @@ func main() {
if err != nil {
log.Fatalf("failed to copy to VM: %v", err)
}
instances <- VM{inst, execprogBin, executorBin}
instances <- VM{inst, index, execprogBin, executorBin}
}
}()
}
@ -197,7 +198,7 @@ func repro(cfg *config.Config, entries []*prog.LogEntry, crashStart int) {
func returnInstance(inst VM, res bool) {
if res {
// The test crashed, discard the VM and issue another boot request.
bootRequests <- true
bootRequests <- inst.index
inst.Close()
} else {
// The test did not crash, reuse the same VM in future.

View File

@ -11,6 +11,9 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"sync"
"time"
"github.com/google/syzkaller/vm"
@ -21,8 +24,9 @@ func init() {
}
type instance struct {
cfg *vm.Config
closed chan bool
cfg *vm.Config
console string
closed chan bool
}
func ctor(cfg *vm.Config) (vm.Instance, error) {
@ -39,6 +43,9 @@ func ctor(cfg *vm.Config) (vm.Instance, error) {
if err := validateConfig(cfg); err != nil {
return nil, err
}
if err := inst.findConsole(); err != nil {
return nil, err
}
if err := inst.repair(); err != nil {
return nil, err
}
@ -52,12 +59,46 @@ func validateConfig(cfg *vm.Config) error {
if cfg.Bin == "" {
cfg.Bin = "adb"
}
if _, err := os.Stat(cfg.ConsoleDev); err != nil {
return fmt.Errorf("console device '%v' is missing: %v", cfg.ConsoleDev, err)
if !regexp.MustCompile("[0-9A-F]+").MatchString(cfg.Device) {
return fmt.Errorf("invalid adb device id '%v'", cfg.Device)
}
return nil
}
var (
consoleCacheMu sync.Mutex
consoleCache = make(map[string]string)
)
func (inst *instance) findConsole() error {
// Case Closed Debugging using Suzy-Q:
// https://chromium.googlesource.com/chromiumos/platform/ec/+/master/docs/case_closed_debugging.md
consoleCacheMu.Lock()
defer consoleCacheMu.Unlock()
if inst.console = consoleCache[inst.cfg.Device]; inst.console != "" {
return nil
}
out, err := exec.Command(inst.cfg.Bin, "devices", "-l").CombinedOutput()
if err != nil {
return fmt.Errorf("failed to execute 'adb devices -l': %v\n%v\n", err, string(out))
}
re := regexp.MustCompile(fmt.Sprintf("%v +device usb:([0-9]+)-([0-9]+)\\.([0-9]+) ", inst.cfg.Device))
match := re.FindAllStringSubmatch(string(out), 1)
if match == nil {
return fmt.Errorf("can't find adb device '%v' in 'adb devices' output:\n%v\n", inst.cfg.Device, string(out))
}
bus, _ := strconv.ParseUint(match[0][1], 10, 64)
port, _ := strconv.ParseUint(match[0][2], 10, 64)
files, err := filepath.Glob(fmt.Sprintf("/sys/bus/usb/devices/%v-%v.2:1.1/ttyUSB*", bus, port))
if err != nil || len(files) == 0 {
return fmt.Errorf("can't find any ttyUDB devices for adb device '%v' on bus %v-%v", inst.cfg.Device, bus, port)
}
inst.console = "/dev/" + filepath.Base(files[0])
consoleCache[inst.cfg.Device] = inst.console
log.Printf("associating adb device %v with console %v", inst.cfg.Device, inst.console)
return nil
}
func (inst *instance) Forward(port int) (string, error) {
// If 35099 turns out to be busy, try to forward random ports several times.
devicePort := 35099
@ -77,7 +118,7 @@ func (inst *instance) adb(args ...string) error {
}
defer wpipe.Close()
defer rpipe.Close()
cmd := exec.Command(inst.cfg.Bin, args...)
cmd := exec.Command(inst.cfg.Bin, append([]string{"-s", inst.cfg.Device}, args...)...)
cmd.Stdout = wpipe
cmd.Stderr = wpipe
if err := cmd.Start(); err != nil {
@ -158,13 +199,13 @@ func (inst *instance) Run(timeout time.Duration, command string) (<-chan []byte,
return nil, nil, err
}
cat := exec.Command("cat", inst.cfg.ConsoleDev)
cat := exec.Command("cat", inst.console)
cat.Stdout = catWpipe
cat.Stderr = catWpipe
if err := cat.Start(); err != nil {
catRpipe.Close()
catWpipe.Close()
return nil, nil, fmt.Errorf("failed to start cat %v: %v", inst.cfg.ConsoleDev, err)
return nil, nil, fmt.Errorf("failed to start cat %v: %v", inst.console, err)
}
catWpipe.Close()
@ -186,7 +227,7 @@ func (inst *instance) Run(timeout time.Duration, command string) (<-chan []byte,
if inst.cfg.Debug {
log.Printf("starting: adb shell %v", command)
}
adb := exec.Command(inst.cfg.Bin, "shell", "cd /data; "+command)
adb := exec.Command(inst.cfg.Bin, "-s", inst.cfg.Device, "shell", "cd /data; "+command)
adb.Stdout = adbWpipe
adb.Stderr = adbWpipe
if err := adb.Start(); err != nil {

View File

@ -32,20 +32,20 @@ type Instance interface {
}
type Config struct {
Name string
Index int
Workdir string
Bin string
Initrd string
Kernel string
Cmdline string
Image string
Sshkey string
Executor string
ConsoleDev string
Cpu int
Mem int
Debug bool
Name string
Index int
Workdir string
Bin string
Initrd string
Kernel string
Cmdline string
Image string
Sshkey string
Executor string
Device string
Cpu int
Mem int
Debug bool
}
type ctorFunc func(cfg *Config) (Instance, error)