mirror of
https://github.com/taigrr/gopher-os
synced 2025-01-18 04:43:13 -08:00
1063 lines
30 KiB
Go
1063 lines
30 KiB
Go
package aml
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"gopheros/device/acpi/table"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
"unsafe"
|
|
)
|
|
|
|
var (
|
|
regenExpFiles = flag.Bool("aml-regenerate-parser-exp-files", false, "Regenerate the expected output files for AML parser tests against real AML files")
|
|
replayCrashersFrom = flag.String("aml-replay-crashers-from", "", "Replay go-fuzz generated crasher files from this folder")
|
|
)
|
|
|
|
// TestParserCrashers scans through the crasher corpus generated by go-fuzz and
|
|
// pipes each corpus through the AML parser.
|
|
func TestParserCrashers(t *testing.T) {
|
|
if *replayCrashersFrom == "" {
|
|
t.Skip("-aml-replay-crashers-from not specified; skipping")
|
|
return
|
|
}
|
|
|
|
fuzzFiles, err := filepath.Glob(filepath.Join(*replayCrashersFrom, "*"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, fuzzFile := range fuzzFiles {
|
|
// corpus files lack an extension
|
|
if filepath.Ext(fuzzFile) != "" {
|
|
continue
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(fuzzFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
t.Logf("trying to parse crash corpus: %q", fuzzFile)
|
|
p, resolver := parserForMockPayload(t, data)
|
|
_ = p.ParseAML(0, "DSDT", resolver.LookupTable("DSDT"))
|
|
}
|
|
|
|
}
|
|
|
|
func TestParser(t *testing.T) {
|
|
flag.Parse()
|
|
|
|
pathToDumps := pkgDir() + "/../table/tabletest/"
|
|
|
|
specs := []struct {
|
|
expTreeContentFile string
|
|
tableFiles []string
|
|
}{
|
|
{
|
|
"DSDT-SSDT.exp",
|
|
[]string{"DSDT.aml", "SSDT.aml"},
|
|
},
|
|
{
|
|
"parser-testsuite-DSDT.exp",
|
|
[]string{"parser-testsuite-DSDT.aml"},
|
|
},
|
|
}
|
|
|
|
for _, spec := range specs {
|
|
t.Run(fmt.Sprintf("parse [%s]", strings.Join(spec.tableFiles, ", ")), func(t *testing.T) {
|
|
var resolver = mockResolver{
|
|
pathToDumps: pathToDumps,
|
|
tableFiles: spec.tableFiles,
|
|
}
|
|
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(42)
|
|
|
|
p := NewParser(&testWriter{t: t}, tree)
|
|
for tableIndex, tableFile := range spec.tableFiles {
|
|
tableName := strings.Replace(tableFile, ".aml", "", -1)
|
|
if err := p.ParseAML(uint8(tableIndex), tableName, resolver.LookupTable(tableName)); err != nil {
|
|
t.Errorf("[%s]: %v", tableName, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Pretty-print tree
|
|
var treeDump bytes.Buffer
|
|
tree.PrettyPrint(&treeDump)
|
|
|
|
// Check if we need to rebuild the exp files
|
|
pathToExpFile := filepath.Join(pathToDumps, spec.expTreeContentFile)
|
|
if *regenExpFiles {
|
|
f, err := os.Create(pathToExpFile)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer func() { _ = f.Close() }()
|
|
|
|
if _, err = treeDump.WriteTo(f); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Logf("regenerated exp file contents: %s", pathToExpFile)
|
|
return
|
|
}
|
|
|
|
// Read the exp file and compare its contents to the generated dump
|
|
expDump, err := ioutil.ReadFile(pathToExpFile)
|
|
if err != nil {
|
|
t.Fatalf("error opening exp file: %s; try running tests with -aml-regenerate-parser-exp-files", pathToExpFile)
|
|
}
|
|
|
|
if !reflect.DeepEqual(expDump, treeDump.Bytes()) {
|
|
t.Fatal("parsed tree content does not match expected content")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseAMLErrors(t *testing.T) {
|
|
t.Run("parseObjectList failed", func(t *testing.T) {
|
|
p, resolver := parserForMockPayload(t, []byte{uint8(pOpBuffer)})
|
|
if err := p.ParseAML(0, "DSDT", resolver.LookupTable("DSDT")); err != errParsingAML {
|
|
t.Fatalf("expected to get errParsingAML; got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("connectNamedObjArgs failed", func(t *testing.T) {
|
|
p, resolver := parserForMockPayload(t, []byte{})
|
|
|
|
namedObj := p.objTree.newNamedObject(pOpName, 0, [amlNameLen]byte{'F', 'O', 'O', 'F'})
|
|
p.objTree.append(namedObj, p.objTree.newObject(pOpDwordPrefix, 0))
|
|
p.objTree.append(p.objTree.ObjectAt(1), namedObj) // Attach to first child of root scope
|
|
|
|
if err := p.ParseAML(0, "DSDT", resolver.LookupTable("DSDT")); err != errParsingAML {
|
|
t.Fatalf("expected to get errParsingAML; got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("mergeScopeDirectives failed", func(t *testing.T) {
|
|
p, resolver := parserForMockPayload(t, []byte{})
|
|
|
|
scopeDirective := p.objTree.newObject(pOpScope, 0)
|
|
p.objTree.append(p.objTree.ObjectAt(1), scopeDirective) // Attach to first child of root scope
|
|
|
|
if err := p.ParseAML(0, "DSDT", resolver.LookupTable("DSDT")); err != errParsingAML {
|
|
t.Fatalf("expected to get errParsingAML; got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("relocateNamedObjects failed", func(t *testing.T) {
|
|
p, resolver := parserForMockPayload(t, []byte{})
|
|
|
|
namedObj := p.objTree.newNamedObject(pOpName, 0, [amlNameLen]byte{'F', 'O', 'O', 'F'})
|
|
namepath := p.objTree.newObject(pOpIntNamePath, 0)
|
|
namepath.value = []byte{'^', '^', 'F', 'O', 'O', 'F'}
|
|
target := p.objTree.newObject(pOpOnes, 0)
|
|
p.objTree.append(namedObj, namepath)
|
|
p.objTree.append(namedObj, target)
|
|
p.objTree.append(p.objTree.ObjectAt(0), namedObj)
|
|
|
|
if err := p.ParseAML(0, "DSDT", resolver.LookupTable("DSDT")); err != errParsingAML {
|
|
t.Fatalf("expected to get errParsingAML; got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("parseDeferredBlocks failed", func(t *testing.T) {
|
|
p, resolver := parserForMockPayload(t, []byte{})
|
|
|
|
// Attach a deferred block to the first child of the root scope
|
|
def := p.objTree.newObject(pOpBankField, 0)
|
|
def.pkgEnd = 1
|
|
p.objTree.append(p.objTree.ObjectAt(1), def)
|
|
|
|
if err := p.ParseAML(0, "DSDT", resolver.LookupTable("DSDT")); err != errParsingAML {
|
|
t.Fatalf("expected to get errParsingAML; got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("resolveMethodCalls failed", func(t *testing.T) {
|
|
p, resolver := parserForMockPayload(t, []byte{})
|
|
|
|
method := p.objTree.newNamedObject(pOpMethod, 0, [amlNameLen]byte{'M', 'T', 'H', 'D'})
|
|
namepath := p.objTree.newObject(pOpIntNamePath, 0)
|
|
namepath.value = []byte{'M', 'T', 'H', 'D'}
|
|
p.objTree.append(method, namepath)
|
|
p.objTree.append(p.objTree.ObjectAt(0), method)
|
|
|
|
inv := p.objTree.newObject(pOpIntNamePathOrMethodCall, 0)
|
|
inv.value = []byte{'M', 'T', 'H', 'D'}
|
|
p.objTree.append(p.objTree.ObjectAt(0), inv)
|
|
|
|
if err := p.ParseAML(0, "DSDT", resolver.LookupTable("DSDT")); err != errParsingAML {
|
|
t.Fatalf("expected to get errParsingAML; got: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("connectNonNamedObjArgs failed", func(t *testing.T) {
|
|
p, resolver := parserForMockPayload(t, []byte{})
|
|
|
|
// Use a named object whose args contain a TermArg
|
|
obj := p.objTree.newObject(pOpMatch, 0)
|
|
p.objTree.append(p.objTree.ObjectAt(0), obj)
|
|
|
|
if err := p.ParseAML(0, "DSDT", resolver.LookupTable("DSDT")); err != errParsingAML {
|
|
t.Fatalf("expected to get errParsingAML; got: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParseObjectListErrors(t *testing.T) {
|
|
p, _ := parserForMockPayload(t, []byte{uint8(pOpBuffer)})
|
|
p.scopeEnter(0)
|
|
if res := p.parseObjectList(); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
}
|
|
|
|
func TestParseArgErrors(t *testing.T) {
|
|
info := new(pOpcodeInfo)
|
|
obj := new(Object)
|
|
|
|
t.Run("parsePkgLength error", func(t *testing.T) {
|
|
// Incomplete pkg length
|
|
p, _ := parserForMockPayload(t, []byte{0xff})
|
|
if _, res := p.parseArg(info, obj, pArgTypePkgLen); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid pkgLen", func(t *testing.T) {
|
|
p, _ := parserForMockPayload(t, []byte{0x4})
|
|
if _, res := p.parseArg(info, obj, pArgTypePkgLen); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("parseTermList error", func(t *testing.T) {
|
|
p, _ := parserForMockPayload(t, []byte{extOpPrefix})
|
|
p.mode = parseModeAllBlocks
|
|
if _, res := p.parseArg(info, obj, pArgTypeTermList); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParseNameOrMethodCallErrors(t *testing.T) {
|
|
payload := []byte{
|
|
// Namestring
|
|
'F', 'O', 'O', 'F',
|
|
}
|
|
|
|
t.Run("path not resolving to entity", func(t *testing.T) {
|
|
p, _ := parserForMockPayload(t, payload)
|
|
p.scopeEnter(0)
|
|
|
|
p.mode = parseModeAllBlocks
|
|
if res := p.parseNamePathOrMethodCall(); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("incomplete method call", func(t *testing.T) {
|
|
p, _ := parserForMockPayload(t, payload)
|
|
p.mode = parseModeAllBlocks
|
|
|
|
// Add a method FOOF object as a child of the root scope and set it up
|
|
// so that it requires 1 arg.
|
|
method := p.objTree.newNamedObject(pOpMethod, 0, [amlNameLen]byte{'F', 'O', 'O', 'F'})
|
|
p.objTree.append(p.objTree.ObjectAt(0), method)
|
|
|
|
p.objTree.append(method, p.objTree.newObject(pOpIntNamePath, 0))
|
|
argCountObj := p.objTree.newObject(pOpBytePrefix, 0)
|
|
argCountObj.value = uint64(1)
|
|
p.objTree.append(method, argCountObj)
|
|
|
|
// Enter one of the default child scopes of the root scope
|
|
// so the lookup for FOOF yields the object we just appended.
|
|
p.scopeEnter(1)
|
|
|
|
p.mode = parseModeAllBlocks
|
|
if res := p.parseNamePathOrMethodCall(); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParseStrictTermArgErrors(t *testing.T) {
|
|
// Set up the stream to include a non-Type2/arg opcode
|
|
p, _ := parserForMockPayload(t, []byte{uint8(pOpMethod)})
|
|
p.scopeEnter(0)
|
|
|
|
p.mode = parseModeAllBlocks
|
|
if _, res := p.parseStrictTermArg(new(Object)); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
}
|
|
|
|
func TestParseSimpleArg(t *testing.T) {
|
|
specs := []struct {
|
|
payload []byte
|
|
argType pArgType
|
|
expRes parseResult
|
|
expVal interface{}
|
|
}{
|
|
{
|
|
[]byte{0x32},
|
|
pArgTypeByteData,
|
|
parseResultOk,
|
|
uint64(0x32),
|
|
},
|
|
{
|
|
[]byte{0x32, 0x33},
|
|
pArgTypeWordData,
|
|
parseResultOk,
|
|
uint64(0x3332),
|
|
},
|
|
{
|
|
[]byte{0x32, 0x33, 0x34, 0x35},
|
|
pArgTypeDwordData,
|
|
parseResultOk,
|
|
uint64(0x35343332),
|
|
},
|
|
{
|
|
[]byte{0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39},
|
|
pArgTypeQwordData,
|
|
parseResultOk,
|
|
uint64(0x3938373635343332),
|
|
},
|
|
{
|
|
[]byte{'F', 'O', 'O', 0x00},
|
|
pArgTypeString,
|
|
parseResultOk,
|
|
[]byte{'F', 'O', 'O'},
|
|
},
|
|
{
|
|
[]byte{'^', '^', 'F', 'O', 'O', 'F'},
|
|
pArgTypeNameString,
|
|
parseResultOk,
|
|
[]byte{'^', '^', 'F', 'O', 'O', 'F'},
|
|
},
|
|
// unsupported arg type
|
|
{
|
|
[]byte{},
|
|
pArgTypeFieldList,
|
|
parseResultFailed,
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for specIndex, spec := range specs {
|
|
p, _ := parserForMockPayload(t, spec.payload)
|
|
obj, res := p.parseSimpleArg(spec.argType)
|
|
if res != spec.expRes {
|
|
t.Errorf("[spec %d] expected to get parse result %d; got %d", specIndex, spec.expRes, res)
|
|
continue
|
|
}
|
|
|
|
if obj != nil && !reflect.DeepEqual(obj.value, spec.expVal) {
|
|
t.Errorf("[spec %d] expected to get value \"%v\"; got \"%v\"", specIndex, spec.expVal, obj.value)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseTargetErrors(t *testing.T) {
|
|
// Set up the stream to include an un-expected opcode
|
|
p, _ := parserForMockPayload(t, []byte{uint8(pOpMethod)})
|
|
p.scopeEnter(0)
|
|
|
|
if _, res := p.parseTarget(); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
}
|
|
|
|
func TestParseFieldElementsErrors(t *testing.T) {
|
|
genFieldObj := func(tree *ObjectTree) *Object {
|
|
field := tree.newObject(pOpField, 0)
|
|
flags := tree.newObject(pOpBytePrefix, 0)
|
|
flags.value = uint64(0xfe)
|
|
tree.append(field, flags)
|
|
tree.append(tree.ObjectAt(0), field)
|
|
return field
|
|
}
|
|
|
|
specs := []struct {
|
|
descr string
|
|
payload []byte
|
|
}{
|
|
{
|
|
"reserved field parsePkgLength error",
|
|
[]byte{
|
|
0x00, // ReservedField
|
|
},
|
|
},
|
|
{
|
|
"access field access type parse error",
|
|
[]byte{
|
|
0x01, // AccessField
|
|
},
|
|
},
|
|
{
|
|
"access field access attrib parse error",
|
|
[]byte{
|
|
0x01, // AccessField
|
|
0x01, // AccessType
|
|
},
|
|
},
|
|
{
|
|
"ext. access field access type parse error",
|
|
[]byte{
|
|
0x03, // ExtendedAccessField
|
|
},
|
|
},
|
|
{
|
|
"ext. access field access attrib parse error",
|
|
[]byte{
|
|
0x03, // ExtendedAccessField
|
|
0x01, // AccessType
|
|
},
|
|
},
|
|
{
|
|
"ext. access field access length parse error",
|
|
[]byte{
|
|
0x03, // ExtendedAccessField
|
|
0x01, // AccessType
|
|
0x02, // AccessAttrib
|
|
},
|
|
},
|
|
{
|
|
"EOF parsing connection",
|
|
[]byte{
|
|
0x02, // Connection
|
|
},
|
|
},
|
|
{
|
|
"connection buffer data parsePkgLength error",
|
|
[]byte{
|
|
0x02, // Connection
|
|
uint8(pOpBuffer),
|
|
},
|
|
},
|
|
{
|
|
"connection buffer length exceeds stream length",
|
|
[]byte{
|
|
0x02, // Connection
|
|
uint8(pOpBuffer),
|
|
0x5, // pkgLen
|
|
},
|
|
},
|
|
{
|
|
"connection bad opcode type for buffer size",
|
|
[]byte{
|
|
0x02, // Connection
|
|
uint8(pOpBuffer),
|
|
0x1, // pkgLen
|
|
extOpPrefix,
|
|
},
|
|
},
|
|
{
|
|
"connection bad word buffer size",
|
|
[]byte{
|
|
0x02, // Connection
|
|
uint8(pOpBuffer),
|
|
0x2, // pkgLen
|
|
uint8(pOpWordPrefix),
|
|
0x1, // missing next byte
|
|
},
|
|
},
|
|
{
|
|
"connection bad dword buffer size",
|
|
[]byte{
|
|
0x02, // Connection
|
|
uint8(pOpBuffer),
|
|
0x3, // pkgLen
|
|
uint8(pOpDwordPrefix),
|
|
0x1, 0x2, 0x3, // missing next byte
|
|
},
|
|
},
|
|
{
|
|
"connection parse namestring error",
|
|
[]byte{
|
|
0x02, // Connection
|
|
'^',
|
|
},
|
|
},
|
|
{
|
|
"incomplete named field name",
|
|
[]byte{
|
|
'F', 'O', 'O', // missing last character
|
|
},
|
|
},
|
|
{
|
|
"error parsing named field pkgLength",
|
|
[]byte{
|
|
'F', 'O', 'O', 'F',
|
|
0xff, // incomplete pkg length
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, spec := range specs {
|
|
t.Run(spec.descr, func(t *testing.T) {
|
|
p, _ := parserForMockPayload(t, spec.payload)
|
|
if res := p.parseFieldElements(genFieldObj(p.objTree)); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParsePkgLength(t *testing.T) {
|
|
specs := []struct {
|
|
payload []byte
|
|
exp uint32
|
|
}{
|
|
// lead byte bits (6:7) indicate 1 extra byte for the len. The
|
|
// parsed length will use bits 0:3 from the lead byte plus
|
|
// the full 8 bits of the following byte.
|
|
{
|
|
[]byte{1<<6 | 7, 255},
|
|
4087,
|
|
},
|
|
// lead byte bits (6:7) indicate 2 extra bytes for the len. The
|
|
// parsed length will use bits 0:3 from the lead byte plus
|
|
// the full 8 bits of the following bytes.
|
|
{
|
|
[]byte{2<<6 | 8, 255, 128},
|
|
528376,
|
|
},
|
|
// lead byte bits (6:7) indicate 3 extra bytes for the len. The
|
|
// parsed length will use bits 0:3 from the lead byte plus
|
|
// the full 8 bits of the following bytes.
|
|
{
|
|
[]byte{3<<6 | 6, 255, 128, 42},
|
|
44568566,
|
|
},
|
|
}
|
|
|
|
for specIndex, spec := range specs {
|
|
p, _ := parserForMockPayload(t, spec.payload)
|
|
got, res := p.parsePkgLength()
|
|
|
|
if res != parseResultOk {
|
|
t.Errorf("[spec %d] expected to get parseResultOk(%d); got %d", specIndex, parseResultOk, res)
|
|
continue
|
|
}
|
|
|
|
if got != spec.exp {
|
|
t.Errorf("[spec %d] expected parsePkgLength to return %d; got %d", specIndex, spec.exp, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParsePkgLengthErrors(t *testing.T) {
|
|
specs := [][]byte{
|
|
// lead byte bits (6:7) indicate 1 extra byte that is missing
|
|
[]byte{1 << 6},
|
|
// lead byte bits (6:7) indicate 2 extra bytes with the 1st and then 2nd missing
|
|
[]byte{2 << 6},
|
|
[]byte{2 << 6, 0x1},
|
|
// lead byte bits (6:7) indicate 3 extra bytes with the 1st and then 2nd and then 3rd missing
|
|
[]byte{3 << 6},
|
|
[]byte{3 << 6, 0x1},
|
|
[]byte{3 << 6, 0x1, 0x2},
|
|
}
|
|
|
|
for specIndex, spec := range specs {
|
|
p, _ := parserForMockPayload(t, spec)
|
|
if _, res := p.parsePkgLength(); res != parseResultFailed {
|
|
t.Errorf("[spec %d] expected to get parseResultFailed(%d); got %d", specIndex, parseResultFailed, res)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseStringErrors(t *testing.T) {
|
|
specs := [][]byte{
|
|
// Unexpected EOF before terminating null byte
|
|
[]byte{'A'},
|
|
// Characters outside the allowed [0x01, 0x7f] range
|
|
[]byte{'A', 0xba, 0xdf, 0x00},
|
|
}
|
|
|
|
for specIndex, spec := range specs {
|
|
p, _ := parserForMockPayload(t, spec)
|
|
|
|
if _, res := p.parseString(); res != parseResultFailed {
|
|
t.Errorf("[spec %d] expected to get parseResultFailed(%d); got %d", specIndex, parseResultFailed, res)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseNamestring(t *testing.T) {
|
|
specs := []struct {
|
|
payload []byte
|
|
expRes parseResult
|
|
expVal []byte
|
|
}{
|
|
{
|
|
[]byte{0x00},
|
|
parseResultOk,
|
|
[]byte{},
|
|
},
|
|
{
|
|
// NameSeg with root prefix
|
|
[]byte{'\\', '_', 'F', 'O', 'O'},
|
|
parseResultOk,
|
|
[]byte{'\\', '_', 'F', 'O', 'O'},
|
|
},
|
|
{
|
|
// DualNamePath
|
|
[]byte{0x2e, 'F', 'O', 'O', 'F', 'B', 'A', 'R', 'B'},
|
|
parseResultOk,
|
|
[]byte{0x2e, 'F', 'O', 'O', 'F', 'B', 'A', 'R', 'B'},
|
|
},
|
|
{
|
|
// MultiNamePath with caret prefix
|
|
[]byte{'^', '^',
|
|
0x2f,
|
|
0x3, // 3 segments
|
|
'F', 'O', 'O', 'F',
|
|
'F', 'O', 'O', 'F',
|
|
'F', 'O', 'O', 'F',
|
|
},
|
|
parseResultOk,
|
|
[]byte{'^', '^',
|
|
0x2f,
|
|
0x3, // 3 segments
|
|
'F', 'O', 'O', 'F',
|
|
'F', 'O', 'O', 'F',
|
|
'F', 'O', 'O', 'F',
|
|
},
|
|
},
|
|
{
|
|
// Unexpected EOF after prefix
|
|
[]byte{'^'},
|
|
parseResultFailed,
|
|
nil,
|
|
},
|
|
{
|
|
// Unexpected EOF in DualNamePath
|
|
[]byte{0x2e, 'F', 'O', 'O', 'F', 'B', 'A', 'R'}, // missing last char
|
|
parseResultFailed,
|
|
nil,
|
|
},
|
|
{
|
|
// Unexpected EOF reading SegCount in MultiNamePath
|
|
[]byte{'^', '^',
|
|
0x2f,
|
|
// missing segment count
|
|
},
|
|
parseResultFailed,
|
|
nil,
|
|
},
|
|
{
|
|
// Unexpected EOF in MultiNamePath
|
|
[]byte{'^', '^',
|
|
0x2f,
|
|
0x3, // 3 segments
|
|
'F', 'O', 'O', 'F',
|
|
'F', 'O', 'O', 'F',
|
|
// missing third segment
|
|
},
|
|
parseResultFailed,
|
|
nil,
|
|
},
|
|
{
|
|
// Unexpected EOF in NameSeg
|
|
[]byte{'F', 'O', 'O'}, // missing last char
|
|
parseResultFailed,
|
|
nil,
|
|
},
|
|
{
|
|
// Invalid lead char for NameSeg
|
|
[]byte{'0', 'F', 'O', 'O'},
|
|
parseResultFailed,
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for specIndex, spec := range specs {
|
|
p, _ := parserForMockPayload(t, spec.payload)
|
|
obj, res := p.parseNameString()
|
|
if res != spec.expRes {
|
|
t.Errorf("[spec %d] expected to get parse result %d; got %d", specIndex, spec.expRes, res)
|
|
continue
|
|
}
|
|
|
|
if obj != nil && !reflect.DeepEqual(obj, spec.expVal) {
|
|
t.Errorf("[spec %d] expected to get value \"%v\"; got \"%v\"", specIndex, spec.expVal, obj)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConnectNamedObjectsErrors(t *testing.T) {
|
|
t.Run("first arg is not a namepath", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
namedObj := tree.newNamedObject(pOpName, 0, [amlNameLen]byte{'F', 'O', 'O', 'F'})
|
|
tree.append(namedObj, tree.newObject(pOpDwordPrefix, 0))
|
|
tree.append(tree.ObjectAt(1), namedObj) // Attach to first child of root scope
|
|
|
|
p := NewParser(ioutil.Discard, tree)
|
|
if res := p.connectNamedObjArgs(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("named object arg count mismatch", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
// Use a named object whose args contain a TermArg
|
|
namedObj := tree.newNamedObject(pOpBankField, 0, [amlNameLen]byte{'F', 'O', 'O', 'F'})
|
|
namepathObj := tree.newObject(pOpIntNamePath, 0)
|
|
namepathObj.value = []byte{'F', 'O', 'O', 'F'}
|
|
tree.append(namedObj, namepathObj)
|
|
tree.append(tree.ObjectAt(0), namedObj)
|
|
|
|
p := NewParser(ioutil.Discard, tree)
|
|
if res := p.connectNamedObjArgs(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("incomplete namepath", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
// Use a named object whose args contain a TermArg
|
|
namedObj := tree.newNamedObject(pOpBankField, 0, [amlNameLen]byte{'F', 'O', 'O', 'F'})
|
|
namepathObj := tree.newObject(pOpIntNamePath, 0)
|
|
namepathObj.value = []byte{'F', 'O'} // namepath len < amlNameLen
|
|
tree.append(namedObj, namepathObj)
|
|
tree.append(tree.ObjectAt(0), namedObj)
|
|
|
|
p := NewParser(ioutil.Discard, tree)
|
|
if res := p.connectNamedObjArgs(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
}
|
|
|
|
func TestMergeScopeDirectivesErrors(t *testing.T) {
|
|
t.Run("malformed scope object", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
scopeDirective := tree.newObject(pOpScope, 0)
|
|
tree.append(tree.ObjectAt(1), scopeDirective) // Attach to first child of root scope
|
|
|
|
p := NewParser(ioutil.Discard, tree)
|
|
if res := p.mergeScopeDirectives(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("unable to resolve scope reference", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
scopeDirective := tree.newObject(pOpScope, 0)
|
|
namepathObj := tree.newObject(pOpIntNamePath, 0)
|
|
namepathObj.value = []byte{'F', 'O', 'O', 'F'}
|
|
tree.append(scopeDirective, namepathObj)
|
|
tree.append(tree.ObjectAt(1), scopeDirective) // Attach to first child of root scope
|
|
|
|
// Simulate second mergeScopes attempt
|
|
p := NewParser(ioutil.Discard, tree)
|
|
p.resolvePasses = 2
|
|
|
|
if res := p.mergeScopeDirectives(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("scope target resolves to obj without IntScopeBlock child", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
scopeDirective := tree.newObject(pOpScope, 0)
|
|
namepathObj := tree.newObject(pOpIntNamePath, 0)
|
|
namepathObj.value = []byte{'D', 'E', 'V', '0'}
|
|
tree.append(scopeDirective, namepathObj)
|
|
tree.append(tree.ObjectAt(1), scopeDirective) // Attach to first child of root scope
|
|
|
|
tree.append(tree.ObjectAt(0), tree.newNamedObject(pOpDevice, 0, [amlNameLen]byte{'D', 'E', 'V', '0'}))
|
|
|
|
// Simulate second mergeScopes attempt
|
|
p := NewParser(ioutil.Discard, tree)
|
|
p.resolvePasses = 2
|
|
|
|
if res := p.mergeScopeDirectives(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRelocateNamedObjectsErrors(t *testing.T) {
|
|
t.Run("first arg is not a namepath", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
namedObj := tree.newNamedObject(pOpName, 0, [amlNameLen]byte{'F', 'O', 'O', 'F'})
|
|
tree.append(namedObj, tree.newObject(pOpDwordPrefix, 0))
|
|
tree.append(tree.ObjectAt(1), namedObj) // Attach to first child of root scope
|
|
|
|
p := NewParser(ioutil.Discard, tree)
|
|
if res := p.relocateNamedObjects(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("unresolved relocation target", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
scope := tree.newNamedObject(pOpIntScopeBlock, 0, [amlNameLen]byte{'S', 'C', 'O', 'P'})
|
|
tree.append(tree.ObjectAt(1), scope)
|
|
|
|
namedObj := tree.newNamedObject(pOpName, 0, [amlNameLen]byte{'F', 'O', 'O', 'F'})
|
|
namepathObj := tree.newObject(pOpIntNamePath, 0)
|
|
namepathObj.value = []byte{'^', '^', '^', '^', 'F', 'O', 'O', 'F'}
|
|
tree.append(namedObj, namepathObj)
|
|
tree.append(scope, namedObj)
|
|
|
|
p := NewParser(ioutil.Discard, tree)
|
|
if res := p.relocateNamedObjects(0); res != parseResultRequireExtraPass {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultRequireExtraPass, res)
|
|
}
|
|
})
|
|
|
|
t.Run("unresolved relocation target after maxResolvePasses", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
namedObj := tree.newNamedObject(pOpName, 0, [amlNameLen]byte{'F', 'O', 'O', 'F'})
|
|
namepathObj := tree.newObject(pOpIntNamePath, 0)
|
|
namepathObj.value = []byte{'^', '^', 'F', 'O', 'O', 'F'}
|
|
tree.append(namedObj, namepathObj)
|
|
|
|
// call relocateNamedObjects on detached nameObj and simulate maxResolvePasses relocateNamedObjects calls
|
|
p := NewParser(ioutil.Discard, tree)
|
|
p.resolvePasses = maxResolvePasses + 1
|
|
|
|
if res := p.relocateNamedObjects(namedObj.index); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("resolve target resolves to obj without IntScopeBlock child", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
dev0 := tree.newNamedObject(pOpDevice, 0, [amlNameLen]byte{'D', 'E', 'V', '0'})
|
|
namepathObj := tree.newObject(pOpIntNamePath, 0)
|
|
namepathObj.value = []byte{'D', 'E', 'V', '0'}
|
|
tree.append(dev0, namepathObj)
|
|
tree.append(tree.ObjectAt(0), dev0)
|
|
|
|
dev1 := tree.newNamedObject(pOpDevice, 0, [amlNameLen]byte{'D', 'E', 'V', '1'})
|
|
namepathObj = tree.newObject(pOpIntNamePath, 0)
|
|
namepathObj.value = []byte{'^', 'D', 'E', 'V', '1'}
|
|
tree.append(dev1, namepathObj)
|
|
|
|
// place another named obj between dev0 and dev1 so that the caret in
|
|
// dev1 namepath resolves to dev0
|
|
cpu0 := tree.newNamedObject(pOpProcessor, 0, [amlNameLen]byte{'C', 'P', 'U', '0'})
|
|
namepathObj = tree.newObject(pOpIntNamePath, 0)
|
|
namepathObj.value = []byte{'C', 'P', 'U', '0'}
|
|
tree.append(cpu0, namepathObj)
|
|
tree.append(dev0, cpu0)
|
|
tree.append(cpu0, dev1)
|
|
|
|
p := NewParser(ioutil.Discard, tree)
|
|
if res := p.relocateNamedObjects(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestParseDeferredBlocksErrors(t *testing.T) {
|
|
p, _ := parserForMockPayload(t, []byte{extOpPrefix})
|
|
|
|
// Attach a deferred block to the first child of the root scope
|
|
def := p.objTree.newObject(pOpBankField, 0)
|
|
def.pkgEnd = 1
|
|
p.objTree.append(p.objTree.ObjectAt(1), def)
|
|
|
|
if res := p.parseDeferredBlocks(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
}
|
|
|
|
func TestConnectNonNamedObjectsErrors(t *testing.T) {
|
|
// Allocate a tree but don't create default scopes to make sure that we lack
|
|
// the number of args (siblings OR parent's siblings) required for the pOpAdd opcode
|
|
tree := NewObjectTree()
|
|
root := tree.newObject(pOpIntScopeBlock, 0)
|
|
scope := tree.newObject(pOpIntScopeBlock, 0)
|
|
tree.append(root, scope)
|
|
|
|
// Use a named object whose args contain a TermArg
|
|
obj := tree.newObject(pOpAdd, 0)
|
|
tree.append(scope, obj)
|
|
|
|
p := NewParser(os.Stdout, tree)
|
|
if res := p.connectNonNamedObjArgs(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
}
|
|
|
|
func TestResolveMethodCallsErrors(t *testing.T) {
|
|
t.Run("method missing flag object", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
method := tree.newNamedObject(pOpMethod, 0, [amlNameLen]byte{'M', 'T', 'H', 'D'})
|
|
namepath := tree.newObject(pOpIntNamePath, 0)
|
|
namepath.value = []byte{'M', 'T', 'H', 'D'}
|
|
tree.append(method, namepath)
|
|
tree.append(tree.ObjectAt(0), method)
|
|
|
|
inv := tree.newObject(pOpIntNamePathOrMethodCall, 0)
|
|
inv.value = []byte{'M', 'T', 'H', 'D'}
|
|
tree.append(tree.ObjectAt(0), inv)
|
|
|
|
p := NewParser(os.Stdout, tree)
|
|
if res := p.resolveMethodCalls(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("method contains malformed flag object", func(t *testing.T) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(0)
|
|
|
|
method := tree.newNamedObject(pOpMethod, 0, [amlNameLen]byte{'M', 'T', 'H', 'D'})
|
|
namepath := tree.newObject(pOpIntNamePath, 0)
|
|
namepath.value = []byte{'M', 'T', 'H', 'D'}
|
|
flags := tree.newObject(pOpStringPrefix, 0)
|
|
flags.value = []byte{'F', 'O', 'O'} // malformed flags: this should be a uint64
|
|
tree.append(method, namepath)
|
|
tree.append(method, flags)
|
|
tree.append(tree.ObjectAt(0), method)
|
|
|
|
inv := tree.newObject(pOpIntNamePathOrMethodCall, 0)
|
|
inv.value = []byte{'M', 'T', 'H', 'D'}
|
|
tree.append(tree.ObjectAt(0), inv)
|
|
|
|
p := NewParser(os.Stdout, tree)
|
|
if res := p.resolveMethodCalls(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
|
|
t.Run("method call arg count mismatch", func(t *testing.T) {
|
|
// Allocate a tree but don't create default scopes to make sure that we lack
|
|
// the number of args (siblings OR parent's siblings) required for the method
|
|
// invocation
|
|
tree := NewObjectTree()
|
|
root := tree.newObject(pOpIntScopeBlock, 0)
|
|
scope := tree.newObject(pOpIntScopeBlock, 0)
|
|
tree.append(root, scope)
|
|
|
|
// MTHD calls expect 6 args
|
|
method := tree.newNamedObject(pOpMethod, 0, [amlNameLen]byte{'M', 'T', 'H', 'D'})
|
|
namepath := tree.newObject(pOpIntNamePath, 0)
|
|
namepath.value = []byte{'M', 'T', 'H', 'D'}
|
|
flags := tree.newObject(pOpBytePrefix, 0)
|
|
flags.value = uint64(0x6)
|
|
tree.append(method, namepath)
|
|
tree.append(method, flags)
|
|
tree.append(scope, method)
|
|
|
|
inv := tree.newObject(pOpIntNamePathOrMethodCall, 0)
|
|
inv.value = []byte{'M', 'T', 'H', 'D'}
|
|
tree.append(scope, inv)
|
|
|
|
p := NewParser(os.Stdout, tree)
|
|
if res := p.resolveMethodCalls(0); res != parseResultFailed {
|
|
t.Fatalf("expected to get parseResultFailed(%d); got %d", parseResultFailed, res)
|
|
}
|
|
})
|
|
}
|
|
|
|
func parserForMockPayload(t *testing.T, payload []byte) (*Parser, table.Resolver) {
|
|
tree := NewObjectTree()
|
|
tree.CreateDefaultScopes(42)
|
|
p := NewParser(&testWriter{t: t}, tree)
|
|
|
|
resolver := mockByteDataResolver(payload)
|
|
|
|
p.init(0, "DSDT", resolver.LookupTable("DSDT"))
|
|
return p, resolver
|
|
}
|
|
|
|
type testWriter struct {
|
|
t *testing.T
|
|
buf bytes.Buffer
|
|
}
|
|
|
|
func (t *testWriter) Write(data []byte) (int, error) {
|
|
for _, b := range data {
|
|
if b == '\n' {
|
|
t.t.Log(t.buf.String())
|
|
t.buf.Reset()
|
|
continue
|
|
}
|
|
_ = t.buf.WriteByte(b)
|
|
}
|
|
|
|
return len(data), nil
|
|
}
|
|
|
|
type mockByteDataResolver []byte
|
|
|
|
func (m mockByteDataResolver) LookupTable(string) *table.SDTHeader {
|
|
headerLen := unsafe.Sizeof(table.SDTHeader{})
|
|
stream := make([]byte, int(headerLen)+len(m))
|
|
copy(stream[headerLen:], m)
|
|
|
|
header := (*table.SDTHeader)(unsafe.Pointer(&stream[0]))
|
|
header.Signature = [4]byte{'D', 'S', 'D', 'T'}
|
|
header.Length = uint32(len(stream))
|
|
header.Revision = 2
|
|
|
|
return header
|
|
}
|
|
|
|
func pkgDir() string {
|
|
_, f, _, _ := runtime.Caller(1)
|
|
return filepath.Dir(f)
|
|
}
|
|
|
|
type mockResolver struct {
|
|
pathToDumps string
|
|
tableFiles []string
|
|
}
|
|
|
|
func (m mockResolver) LookupTable(name string) *table.SDTHeader {
|
|
for _, f := range m.tableFiles {
|
|
if !strings.Contains(f, name) {
|
|
continue
|
|
}
|
|
|
|
data, err := ioutil.ReadFile(filepath.Join(m.pathToDumps, f))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return (*table.SDTHeader)(unsafe.Pointer(&data[0]))
|
|
}
|
|
|
|
return nil
|
|
}
|