From a604cf376325b5f4d5ead8c2ca50da91330c72c8 Mon Sep 17 00:00:00 2001 From: Alexander Potapenko Date: Tue, 16 Apr 2024 15:11:22 +0200 Subject: pkg/ifuzz/arm64: add arm64 support This patch adds instruction generator for ARM64 based on the descriptions provided as part of Go's arm64asm package. It also implements support for pseudo-instructions for calling ARM64 hypercalls. --- pkg/ifuzz/arm64/arm64.go | 116 + pkg/ifuzz/arm64/gen/gen.go | 133 + pkg/ifuzz/arm64/gen/json/LICENSE | 28 + pkg/ifuzz/arm64/gen/json/README.md | 5 + pkg/ifuzz/arm64/gen/json/arm64.json | 1219 ++++++ pkg/ifuzz/arm64/generated/empty.go | 6 + pkg/ifuzz/arm64/generated/insns.go | 7053 +++++++++++++++++++++++++++++++++++ pkg/ifuzz/arm64/pseudo.go | 73 + pkg/ifuzz/arm64/util.go | 9 + pkg/ifuzz/arm64/util_test.go | 26 + pkg/ifuzz/arm64_test.go | 94 + pkg/ifuzz/ifuzz.go | 2 + pkg/ifuzz/ifuzz_test.go | 2 +- pkg/ifuzz/iset/iset.go | 1 + 14 files changed, 8766 insertions(+), 1 deletion(-) create mode 100644 pkg/ifuzz/arm64/arm64.go create mode 100644 pkg/ifuzz/arm64/gen/gen.go create mode 100644 pkg/ifuzz/arm64/gen/json/LICENSE create mode 100644 pkg/ifuzz/arm64/gen/json/README.md create mode 100644 pkg/ifuzz/arm64/gen/json/arm64.json create mode 100644 pkg/ifuzz/arm64/generated/empty.go create mode 100644 pkg/ifuzz/arm64/generated/insns.go create mode 100644 pkg/ifuzz/arm64/pseudo.go create mode 100644 pkg/ifuzz/arm64/util.go create mode 100644 pkg/ifuzz/arm64/util_test.go create mode 100644 pkg/ifuzz/arm64_test.go (limited to 'pkg/ifuzz') diff --git a/pkg/ifuzz/arm64/arm64.go b/pkg/ifuzz/arm64/arm64.go new file mode 100644 index 000000000..3b835566f --- /dev/null +++ b/pkg/ifuzz/arm64/arm64.go @@ -0,0 +1,116 @@ +// Copyright 2024 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. + +//go:generate bash -c "go run gen/gen.go gen/json/arm64.json | gofmt > generated/insns.go" + +// Package arm64 allows to generate and mutate arm64 machine code. +package arm64 + +import ( + "encoding/binary" + "fmt" + "math/rand" + + "github.com/google/syzkaller/pkg/ifuzz/iset" +) + +type InsnField struct { + Name string + Start uint // Little endian bit order. + Length uint +} + +type Insn struct { + Name string + OpcodeMask uint32 + Opcode uint32 + Fields []InsnField + AsUInt32 uint32 + Operands []uint32 + Pseudo bool + Priv bool + Generator func(cfg *iset.Config, r *rand.Rand) []byte // for pseudo instructions +} + +type InsnSet struct { + modeInsns iset.ModeInsns + Insns []*Insn +} + +func Register(insns []*Insn) { + if len(insns) == 0 { + panic("no instructions") + } + insnset := &InsnSet{ + Insns: append(insns, pseudo...), + } + for _, insn := range insnset.Insns { + insnset.modeInsns.Add(insn) + } + iset.Arches[iset.ArchArm64] = insnset + templates = insns +} + +func (insnset *InsnSet) GetInsns(mode iset.Mode, typ iset.Type) []iset.Insn { + return insnset.modeInsns[mode][typ] +} + +func (insn *Insn) Info() (string, iset.Mode, bool, bool) { + return insn.Name, 1 << iset.ModeLong64, insn.Pseudo, insn.Priv +} + +func (insn *Insn) Encode(cfg *iset.Config, r *rand.Rand) []byte { + if insn.Pseudo { + return insn.Generator(cfg, r) + } + ret := make([]byte, 4) + binary.LittleEndian.PutUint32(ret, insn.AsUInt32) + return ret +} + +func (insnset *InsnSet) Decode(mode iset.Mode, text []byte) (int, error) { + if len(text) < 4 { + return 0, fmt.Errorf("must be at least 4 bytes") + } + opcode := binary.LittleEndian.Uint32(text[:4]) + _, err := ParseInsn(opcode) + if err != nil { + return 0, fmt.Errorf("failed to decode %x", opcode) + } + return 4, nil +} + +func (insnset *InsnSet) DecodeExt(mode iset.Mode, text []byte) (int, error) { + return 0, fmt.Errorf("no external decoder") +} + +var templates []*Insn + +func (insn *Insn) initFromValue(val uint32) { + operands := []uint32{} + for _, field := range insn.Fields { + extracted := extractBits(val, field.Start, field.Length) + operands = append(operands, extracted) + } + insn.Operands = operands + insn.AsUInt32 = val +} + +func (insn *Insn) matchesValue(val uint32) bool { + opcode := val & insn.OpcodeMask + return opcode == insn.Opcode +} + +func ParseInsn(val uint32) (Insn, error) { + for _, tmpl := range templates { + if tmpl.matchesValue(val) { + newInsn := *tmpl + newInsn.initFromValue(val) + return newInsn, nil + } + } + unknown := Insn{ + Name: "unknown", + } + return unknown, fmt.Errorf("unrecognized instruction: %08x", val) +} diff --git a/pkg/ifuzz/arm64/gen/gen.go b/pkg/ifuzz/arm64/gen/gen.go new file mode 100644 index 000000000..659fea202 --- /dev/null +++ b/pkg/ifuzz/arm64/gen/gen.go @@ -0,0 +1,133 @@ +// Copyright 2024 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. + +// gen generates instruction tables (ifuzz_types/insns.go) from ARM64 JSON. +package main + +import ( + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + + "github.com/google/syzkaller/pkg/ifuzz/arm64" + "github.com/google/syzkaller/pkg/serializer" + "github.com/google/syzkaller/pkg/tool" +) + +func main() { + if len(os.Args) != 2 { + tool.Failf("usage: gen arm64.json") + } + jsonStr, err := os.ReadFile(os.Args[1]) + if err != nil { + tool.Failf("failed to open input file: %v", err) + } + insns := JSONToInsns(jsonStr) + + fmt.Printf(`// Code generated by pkg/ifuzz/gen. DO NOT EDIT. + +// go:build !codeanalysis + +package generated + +import ( + . "github.com/google/syzkaller/pkg/ifuzz/arm64" +) + +func init() { + Register(insns_arm64) +} + +var insns_arm64 = +`) + serializer.Write(os.Stdout, insns) + + fmt.Fprintf(os.Stderr, "handled %v\n", len(insns)) +} + +type insnDesc struct { + Name string + Bits string + Arch string + Syntax string + Code string + Alias string +} + +func isPrivateInsn(insn arm64.Insn) bool { + switch insn.Name { + case "AT", "DC", "IC", "SYS", "SYSL", "TLBI": + return true + } + return false +} + +func JSONToInsns(jsonStr []byte) []*arm64.Insn { + var insnDescriptions []insnDesc + err := json.Unmarshal(jsonStr, &insnDescriptions) + if err != nil { + return nil + } + ret := []*arm64.Insn{} + for _, desc := range insnDescriptions { + mask := uint32(0) + opcode := uint32(0) + curBit := uint(31) + fields := []arm64.InsnField{} + pieces := strings.Split(desc.Bits, "|") + for _, piece := range pieces { + size := uint(1) + pair := strings.Split(piece, ":") + var pattern = piece + if len(pair) == 2 { + size64, err := strconv.ParseUint(pair[1], 10, 0) + if err != nil { + return nil + } + size = uint(size64) + pattern = pair[0] + } + updateOpcode := true + opPart := uint32(0) + maskPart := uint32(0) + if pattern[0:1] != "(" { + number, err := strconv.ParseUint(pattern, 2, 32) + if err != nil { + // This is a named region. + field := arm64.InsnField{ + Name: pattern, + Start: curBit, + Length: size, + } + fields = append(fields, field) + updateOpcode = false + } else { + // This is a binary mask. + opPart = uint32(number) + maskPart = ((1 << size) - 1) + } + } + opcode <<= size + mask <<= size + curBit -= size + if updateOpcode { + opcode |= opPart + mask |= maskPart + } + } + templ := arm64.Insn{ + Name: desc.Name, + OpcodeMask: mask, + Opcode: opcode, + Fields: fields, + AsUInt32: opcode, + } + templ.Priv = isPrivateInsn(templ) + insn := new(arm64.Insn) + *insn = templ + ret = append(ret, insn) + } + return ret +} diff --git a/pkg/ifuzz/arm64/gen/json/LICENSE b/pkg/ifuzz/arm64/gen/json/LICENSE new file mode 100644 index 000000000..931520b99 --- /dev/null +++ b/pkg/ifuzz/arm64/gen/json/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2015 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/pkg/ifuzz/arm64/gen/json/README.md b/pkg/ifuzz/arm64/gen/json/README.md new file mode 100644 index 000000000..4f44ea79f --- /dev/null +++ b/pkg/ifuzz/arm64/gen/json/README.md @@ -0,0 +1,5 @@ +arm64.json is taken from the Go Language repository: +https://tip.golang.org/src/cmd/vendor/golang.org/x/arch/arm64/arm64asm/inst.json + +Please see the LICENSE file (taken from https://tip.golang.org/src/cmd/vendor/golang.org/x/arch/LICENSE) +for the relevant copyright notice and disclaimers. diff --git a/pkg/ifuzz/arm64/gen/json/arm64.json b/pkg/ifuzz/arm64/gen/json/arm64.json new file mode 100644 index 000000000..2d25c944a --- /dev/null +++ b/pkg/ifuzz/arm64/gen/json/arm64.json @@ -0,0 +1,1219 @@ +[{"Name":"ADC","Bits":"0|0|0|1|1|0|1|0|0|0|0|Rm:5|0|0|0|0|0|0|Rn:5|Rd:5","Arch":"32-bit variant","Syntax":"ADC , , ","Code":"","Alias":""}, +{"Name":"ADC","Bits":"1|0|0|1|1|0|1|0|0|0|0|Rm:5|0|0|0|0|0|0|Rn:5|Rd:5","Arch":"64-bit variant","Syntax":"ADC , , ","Code":"","Alias":""}, +{"Name":"ADCS","Bits":"0|0|1|1|1|0|1|0|0|0|0|Rm:5|0|0|0|0|0|0|Rn:5|Rd:5","Arch":"32-bit variant","Syntax":"ADCS , , ","Code":"","Alias":""}, +{"Name":"ADCS","Bits":"1|0|1|1|1|0|1|0|0|0|0|Rm:5|0|0|0|0|0|0|Rn:5|Rd:5","Arch":"64-bit variant","Syntax":"ADCS , , ","Code":"","Alias":""}, +{"Name":"ADD (extended register)","Bits":"0|0|0|0|1|0|1|1|0|0|1|Rm:5|option:3|imm3:3|Rn:5|Rd:5","Arch":"32-bit variant","Syntax":"ADD , , {, {#}}","Code":"","Alias":""}, +{"Name":"ADD (extended register)","Bits":"1|0|0|0|1|0|1|1|0|0|1|Rm:5|option:3|imm3:3|Rn:5|Rd:5","Arch":"64-bit variant","Syntax":"ADD , , {, {#}}","Code":"","Alias":""}, +{"Name":"ADD (immediate)","Bits":"0|0|0|1|0|0|0|1|shift:2|imm12:12|Rn:5|Rd:5","Arch":"32-bit variant","Syntax":"ADD , , #{, }","Code":"","Alias":"This instruction is used by the alias MOV (to/from SP)."}, +{"Name":"ADD (immediate)","Bits":"1|0|0|1|0|0|0|1|shift:2|imm12:12|Rn:5|Rd:5","Arch":"64-bit variant","Syntax":"ADD , , #{, }","Code":"","Alias":"This instruction is used by the alias MOV (to/from SP)."}, +{"Name":"ADD (shifted register)","Bits":"0|0|0|0|1|0|1|1|shift:2|0|Rm:5|imm6:6|Rn:5|Rd:5","Arch":"32-bit variant","Syntax":"ADD , , {, #}","Code":"","Alias":""}, +{"Name":"ADD (shifted register)","Bits":"1|0|0|0|1|0|1|1|shift:2|0|Rm:5|imm6:6|Rn:5|Rd:5","Arch":"64-bit variant","Syntax":"ADD , , {, #}","Code":"","Alias":""}, +{"Name":"ADDS (extended register)","Bits":"0|0|1|0|1|0|1|1|0|0|1|Rm:5|option:3|imm3:3|Rn:5|Rd:5","Arch":"32-bit variant","Syntax":"ADDS , , {, {#}}","Code":"","Alias":"This instruction is used by the alias CMN (extended register)."}, +{"Name":"ADDS (extended register)","Bits":"1|0|1|0|1|0|1|1|0|0|1|Rm:5|option:3|imm3:3|Rn:5|Rd:5","Arch":"64-bit variant","Syntax":"ADDS , , {, {#}}","Code":"","Alias":"This instruction is used by the alias CMN (extended register)."}, +{"Name":"ADDS (immediate)","Bits":"0|0|1|1|0|0|0|1|shift:2|imm12:12|Rn:5|Rd:5","Arch":"32-bit variant","Syntax":"ADDS , , #{, }","Code":"","Alias":"This instruction is used by the alias CMN (immediate)."}, +{"Name":"ADDS (immediate)","Bits":"1|0|1|1|0|0|0|1|shift:2|imm12:12|Rn:5|Rd:5","Arch":"64-bit variant","Syntax":"ADDS , , #{, }","Code":"","Alias":"This instruction is used by the alias CMN (immediate)."}, +{"Name":"ADDS (shifted register)","Bits":"0|0|1|0|1|0|1|1|shift:2|0|Rm:5|imm6:6|Rn:5|Rd:5","Arch":"32-bit variant","Syntax":"ADDS , , {, #}","Code":"","Alias":"This instruction is used by the alias CMN (shifted register)."}, +{"Name":"ADDS (shifted register)","Bits":"1|0|1|0|1|0|1|1|shift:2|0|Rm:5|imm6:6|Rn:5|Rd:5","Arch":"64-bit variant","Syntax":"ADDS , , {, #}","Code":"","Alias":"This instruction is used by the alias CMN (shifted register)."}, +{"Name":"ADR","Bits":"0|immlo:2|1|0|0|0|0|immhi:19|Rd:5","Arch":"Literal variant","Syntax":"ADR ,