aboutsummaryrefslogtreecommitdiffstats
path: root/prog
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2017-12-13 20:12:13 +0100
committerDmitry Vyukov <dvyukov@google.com>2017-12-17 11:39:14 +0100
commit8ef00507063baf3fa681bb53113cb33adda5e4d7 (patch)
tree90f5be39889c5e064c92f16d9649627e84933820 /prog
parenteaeccee1d7f7a3f22e842309f21e3b118bd95254 (diff)
prog: don't serialize output data args
Fixes #188 We now will write just ""/1000 to denote a 1000-byte output buffer. Also we now don't store 1000-byte buffer in memory just to denote size. Old format is still parsed.
Diffstat (limited to 'prog')
-rw-r--r--prog/analysis.go6
-rw-r--r--prog/clone.go2
-rw-r--r--prog/encoding.go40
-rw-r--r--prog/encoding_test.go8
-rw-r--r--prog/encodingexec.go12
-rw-r--r--prog/hints.go20
-rw-r--r--prog/hints_test.go36
-rw-r--r--prog/mutation.go26
-rw-r--r--prog/prog.go40
-rw-r--r--prog/rand.go28
-rw-r--r--prog/validation.go16
11 files changed, 148 insertions, 86 deletions
diff --git a/prog/analysis.go b/prog/analysis.go
index 80a127108..fcb00350b 100644
--- a/prog/analysis.go
+++ b/prog/analysis.go
@@ -58,12 +58,12 @@ func (s *state) analyze(c *Call) {
}
case *BufferType:
a := arg.(*DataArg)
- if typ.Dir() != DirOut && len(a.Data) != 0 {
+ if typ.Dir() != DirOut && len(a.Data()) != 0 {
switch typ.Kind {
case BufferString:
- s.strings[string(a.Data)] = true
+ s.strings[string(a.Data())] = true
case BufferFilename:
- s.files[string(a.Data)] = true
+ s.files[string(a.Data())] = true
}
}
}
diff --git a/prog/clone.go b/prog/clone.go
index 1ff42f60f..bdb9b5074 100644
--- a/prog/clone.go
+++ b/prog/clone.go
@@ -44,7 +44,7 @@ func clone(arg Arg, newargs map[Arg]Arg) Arg {
case *DataArg:
a1 := new(DataArg)
*a1 = *a
- a1.Data = append([]byte{}, a.Data...)
+ a1.data = append([]byte{}, a.data...)
arg1 = a1
case *GroupArg:
a1 := new(GroupArg)
diff --git a/prog/encoding.go b/prog/encoding.go
index 4d8150959..2b7999efc 100644
--- a/prog/encoding.go
+++ b/prog/encoding.go
@@ -74,15 +74,19 @@ func serialize(arg Arg, buf *bytes.Buffer, vars map[Arg]int, varSeq *int) {
fmt.Fprintf(buf, "&%v=", serializeAddr(arg))
serialize(a.Res, buf, vars, varSeq)
case *DataArg:
- data := a.Data
- if !arg.Type().Varlen() {
- // Statically typed data will be padded with 0s during
- // deserialization, so we can strip them here for readability.
- for len(data) >= 2 && data[len(data)-1] == 0 && data[len(data)-2] == 0 {
- data = data[:len(data)-1]
+ if a.Type().Dir() == DirOut {
+ fmt.Fprintf(buf, "\"\"/%v", a.Size())
+ } else {
+ data := a.Data()
+ if !arg.Type().Varlen() {
+ // Statically typed data will be padded with 0s during
+ // deserialization, so we can strip them here for readability.
+ for len(data) >= 2 && data[len(data)-1] == 0 && data[len(data)-2] == 0 {
+ data = data[:len(data)-1]
+ }
}
+ serializeData(buf, data)
}
- serializeData(buf, data)
case *GroupArg:
var delims []byte
switch arg.Type().(type) {
@@ -287,13 +291,29 @@ func (target *Target) parseArg(typ Type, p *parser, vars map[string]Arg) (Arg, e
if err != nil {
return nil, err
}
+ size := ^uint64(0)
+ if p.Char() == '/' {
+ p.Parse('/')
+ sizeStr := p.Ident()
+ size, err = strconv.ParseUint(sizeStr, 0, 64)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse buffer size: %q", sizeStr)
+ }
+ }
if !typ.Varlen() {
- if diff := int(typ.Size()) - len(data); diff > 0 {
+ size = typ.Size()
+ } else if size == ^uint64(0) {
+ size = uint64(len(data))
+ }
+ if typ.Dir() == DirOut {
+ arg = MakeOutDataArg(typ, size)
+ } else {
+ if diff := int(size) - len(data); diff > 0 {
data = append(data, make([]byte, diff)...)
}
- data = data[:typ.Size()]
+ data = data[:size]
+ arg = MakeDataArg(typ, data)
}
- arg = MakeDataArg(typ, data)
case '{':
t1, ok := typ.(*StructType)
if !ok {
diff --git a/prog/encoding_test.go b/prog/encoding_test.go
index 39063a055..5fce30142 100644
--- a/prog/encoding_test.go
+++ b/prog/encoding_test.go
@@ -168,8 +168,12 @@ func TestSerializeDeserialize(t *testing.T) {
target := initTargetTest(t, "test", "64")
tests := [][2]string{
{
- `serialize(&(0x7f0000408000)={"6861736800000000000000000000", "4849000000"})`,
- `serialize(&(0x7f0000408000)={'hash\x00', 'HI\x00'})`,
+ `serialize0(&(0x7f0000408000)={"6861736800000000000000000000", "4849000000"})`,
+ `serialize0(&(0x7f0000408000)={'hash\x00', 'HI\x00'})`,
+ },
+ {
+ `serialize1(&(0x7f0000000000)="0000000000000000", 0x8)`,
+ `serialize1(&(0x7f0000000000)=""/8, 0x8)`,
},
}
for _, test := range tests {
diff --git a/prog/encodingexec.go b/prog/encodingexec.go
index e45ba1192..937f3c322 100644
--- a/prog/encodingexec.go
+++ b/prog/encodingexec.go
@@ -104,7 +104,8 @@ func (p *Prog) SerializeForExec(buffer []byte, pid int) (int, error) {
if _, ok := arg1.(*UnionArg); ok {
return
}
- if a1, ok := arg1.(*DataArg); ok && len(a1.Data) == 0 {
+ if a1, ok := arg1.(*DataArg); ok &&
+ (a1.Type().Dir() == DirOut || len(a1.Data()) == 0) {
return
}
if !IsPad(arg1.Type()) && arg1.Type().Dir() != DirOut {
@@ -270,16 +271,17 @@ func (w *execContext) writeArg(arg Arg, pid int, csumMap map[Arg]CsumInfo) {
w.write(0) // bit field offset
w.write(0) // bit field length
case *DataArg:
+ data := a.Data()
w.write(ExecArgData)
- w.write(uint64(len(a.Data)))
- padded := len(a.Data)
- if pad := 8 - len(a.Data)%8; pad != 8 {
+ w.write(uint64(len(data)))
+ padded := len(data)
+ if pad := 8 - len(data)%8; pad != 8 {
padded += pad
}
if len(w.buf) < padded {
w.eof = true
} else {
- copy(w.buf, a.Data)
+ copy(w.buf, data)
w.buf = w.buf[padded:]
}
default:
diff --git a/prog/hints.go b/prog/hints.go
index e9f6f8a62..fb3100c3f 100644
--- a/prog/hints.go
+++ b/prog/hints.go
@@ -120,16 +120,21 @@ func checkConstArg(arg *ConstArg, compMap CompMap, exec func()) {
func checkDataArg(arg *DataArg, compMap CompMap, exec func()) {
bytes := make([]byte, 8)
- for i := 0; i < min(len(arg.Data), maxDataLength); i++ {
+ data := arg.Data()
+ size := len(data)
+ if size > maxDataLength {
+ size = maxDataLength
+ }
+ for i := 0; i < size; i++ {
original := make([]byte, 8)
- copy(original, arg.Data[i:])
+ copy(original, data[i:])
val := binary.LittleEndian.Uint64(original)
for replacer := range shrinkExpand(val, compMap) {
binary.LittleEndian.PutUint64(bytes, replacer)
- copy(arg.Data[i:], bytes)
+ copy(data[i:], bytes)
exec()
}
- copy(arg.Data[i:], original)
+ copy(data[i:], original)
}
}
@@ -205,10 +210,3 @@ func init() {
specialIntsSet[v] = true
}
}
-
-func min(a, b int) int {
- if a <= b {
- return a
- }
- return b
-}
diff --git a/prog/hints_test.go b/prog/hints_test.go
index 904d36500..eef2758b6 100644
--- a/prog/hints_test.go
+++ b/prog/hints_test.go
@@ -161,10 +161,10 @@ func TestHintsCheckDataArg(t *testing.T) {
res := make(map[string]bool)
// Whatever type here. It's just needed to pass the
// dataArg.Type().Dir() == DirIn check.
- typ := ArrayType{TypeCommon{"", "", 0, DirIn, false}, nil, 0, 0, 0}
- dataArg := &DataArg{ArgCommon{&typ}, []byte(test.in)}
+ typ := &ArrayType{TypeCommon{"", "", 0, DirIn, false}, nil, 0, 0, 0}
+ dataArg := MakeDataArg(typ, []byte(test.in)).(*DataArg)
checkDataArg(dataArg, test.comps, func() {
- res[string(dataArg.Data)] = true
+ res[string(dataArg.Data())] = true
})
if !reflect.DeepEqual(res, test.res) {
s := "\ngot: ["
@@ -365,26 +365,30 @@ func TestHintsRandom(t *testing.T) {
func extractValues(c *Call) map[uint64]bool {
vals := make(map[uint64]bool)
foreachArg(c, func(arg, _ Arg, _ *[]Arg) {
+ if arg.Type().Dir() == DirOut {
+ return
+ }
switch a := arg.(type) {
case *ConstArg:
vals[a.Val] = true
case *DataArg:
- for i := range a.Data {
- vals[uint64(a.Data[i])] = true
- if i < len(a.Data)-1 {
- v := uint64(a.Data[i]) | uint64(a.Data[i+1])<<8
+ data := a.Data()
+ for i := range data {
+ vals[uint64(data[i])] = true
+ if i < len(data)-1 {
+ v := uint64(data[i]) | uint64(data[i+1])<<8
vals[v] = true
}
- if i < len(a.Data)-3 {
- v := uint64(a.Data[i]) | uint64(a.Data[i+1])<<8 |
- uint64(a.Data[i+2])<<16 | uint64(a.Data[i+3])<<24
+ if i < len(data)-3 {
+ v := uint64(data[i]) | uint64(data[i+1])<<8 |
+ uint64(data[i+2])<<16 | uint64(data[i+3])<<24
vals[v] = true
}
- if i < len(a.Data)-7 {
- v := uint64(a.Data[i]) | uint64(a.Data[i+1])<<8 |
- uint64(a.Data[i+2])<<16 | uint64(a.Data[i+3])<<24 |
- uint64(a.Data[i+4])<<32 | uint64(a.Data[i+5])<<40 |
- uint64(a.Data[i+6])<<48 | uint64(a.Data[i+7])<<56
+ if i < len(data)-7 {
+ v := uint64(data[i]) | uint64(data[i+1])<<8 |
+ uint64(data[i+2])<<16 | uint64(data[i+3])<<24 |
+ uint64(data[i+4])<<32 | uint64(data[i+5])<<40 |
+ uint64(data[i+6])<<48 | uint64(data[i+7])<<56
vals[v] = true
}
}
@@ -428,7 +432,7 @@ func TestHintsData(t *testing.T) {
var got []string
p.MutateWithHints(0, test.comps, func(newP *Prog) {
got = append(got, hex.EncodeToString(
- newP.Calls[0].Args[0].(*PointerArg).Res.(*DataArg).Data))
+ newP.Calls[0].Args[0].(*PointerArg).Res.(*DataArg).Data()))
})
sort.Strings(test.out)
sort.Strings(got)
diff --git a/prog/mutation.go b/prog/mutation.go
index 63c58ad7f..1aeef4fa4 100644
--- a/prog/mutation.go
+++ b/prog/mutation.go
@@ -101,27 +101,28 @@ func (p *Prog) Mutate(rs rand.Source, ncalls int, ct *ChoiceTable, corpus []*Pro
a := arg.(*DataArg)
switch t.Kind {
case BufferBlobRand, BufferBlobRange:
- var data []byte
- data = append([]byte{}, a.Data...)
+ data := append([]byte{}, a.Data()...)
minLen, maxLen := uint64(0), maxBlobLen
if t.Kind == BufferBlobRange {
minLen, maxLen = t.RangeBegin, t.RangeEnd
}
- a.Data = mutateData(r, data, minLen, maxLen)
+ a.data = mutateData(r, data, minLen, maxLen)
case BufferString:
+ data := append([]byte{}, a.Data()...)
if r.bin() {
minLen, maxLen := uint64(0), maxBlobLen
if t.TypeSize != 0 {
minLen, maxLen = t.TypeSize, t.TypeSize
}
- a.Data = mutateData(r, append([]byte{}, a.Data...), minLen, maxLen)
+ a.data = mutateData(r, data, minLen, maxLen)
} else {
- a.Data = r.randString(s, t.Values, t.Dir())
+ a.data = r.randString(s, t.Values, t.Dir())
}
case BufferFilename:
- a.Data = []byte(r.filename(s))
+ a.data = []byte(r.filename(s))
case BufferText:
- a.Data = r.mutateText(t.Text, a.Data)
+ data := append([]byte{}, a.Data()...)
+ a.data = r.mutateText(t.Text, data)
default:
panic("unknown buffer kind")
}
@@ -430,19 +431,20 @@ func Minimize(p0 *Prog, callIndex0 int, pred0 func(*Prog, int) bool, crash bool)
return false
}
triedPaths[path] = true
- if typ.Kind != BufferBlobRand && typ.Kind != BufferBlobRange {
+ if typ.Kind != BufferBlobRand && typ.Kind != BufferBlobRange ||
+ typ.Dir() == DirOut {
return false
}
a := arg.(*DataArg)
minLen := int(typ.RangeBegin)
- for step := len(a.Data) - minLen; len(a.Data) > minLen && step > 0; {
- if len(a.Data)-step >= minLen {
- a.Data = a.Data[:len(a.Data)-step]
+ for step := len(a.Data()) - minLen; len(a.Data()) > minLen && step > 0; {
+ if len(a.Data())-step >= minLen {
+ a.data = a.Data()[:len(a.Data())-step]
p.Target.assignSizesCall(call)
if pred(p, callIndex0) {
continue
}
- a.Data = a.Data[:len(a.Data)+step]
+ a.data = a.Data()[:len(a.Data())+step]
p.Target.assignSizesCall(call)
}
step /= 2
diff --git a/prog/prog.go b/prog/prog.go
index 0fcf6b66e..66de938b3 100644
--- a/prog/prog.go
+++ b/prog/prog.go
@@ -86,11 +86,36 @@ func (arg *PointerArg) Size() uint64 {
// Used for BufferType.
type DataArg struct {
ArgCommon
- Data []byte
+ data []byte // for in/inout args
+ size uint64 // for out Args
+}
+
+func MakeDataArg(t Type, data []byte) Arg {
+ if t.Dir() == DirOut {
+ panic("non-empty output data arg")
+ }
+ return &DataArg{ArgCommon: ArgCommon{typ: t}, data: append([]byte{}, data...)}
+}
+
+func MakeOutDataArg(t Type, size uint64) Arg {
+ if t.Dir() != DirOut {
+ panic("empty input data arg")
+ }
+ return &DataArg{ArgCommon: ArgCommon{typ: t}, size: size}
}
func (arg *DataArg) Size() uint64 {
- return uint64(len(arg.Data))
+ if len(arg.data) != 0 {
+ return uint64(len(arg.data))
+ }
+ return arg.size
+}
+
+func (arg *DataArg) Data() []byte {
+ if arg.Type().Dir() == DirOut {
+ panic("getting data of output data arg")
+ }
+ return arg.data
}
// Used for StructType and ArrayType.
@@ -240,10 +265,6 @@ func MakeResultArg(t Type, r Arg, v uint64) Arg {
return arg
}
-func MakeDataArg(t Type, data []byte) Arg {
- return &DataArg{ArgCommon: ArgCommon{typ: t}, Data: append([]byte{}, data...)}
-}
-
func MakePointerArg(t Type, page uint64, off int, npages uint64, obj Arg) Arg {
return &PointerArg{ArgCommon: ArgCommon{typ: t}, PageIndex: page, PageOffset: off, PagesNum: npages, Res: obj}
}
@@ -267,6 +288,13 @@ func defaultArg(t Type) Arg {
case *ResourceType:
return MakeResultArg(t, nil, typ.Desc.Type.Default())
case *BufferType:
+ if t.Dir() == DirOut {
+ var sz uint64
+ if !typ.Varlen() {
+ sz = typ.Size()
+ }
+ return MakeOutDataArg(t, sz)
+ }
var data []byte
if !typ.Varlen() {
data = make([]byte, typ.Size())
diff --git a/prog/rand.go b/prog/rand.go
index b99399c9d..173248599 100644
--- a/prog/rand.go
+++ b/prog/rand.go
@@ -593,32 +593,38 @@ func (r *randGen) generateArg(s *state, typ Type) (arg Arg, calls []*Call) {
if a.Kind == BufferBlobRange {
sz = r.randRange(a.RangeBegin, a.RangeEnd)
}
+ if a.Dir() == DirOut {
+ return MakeOutDataArg(a, sz), nil
+ }
data := make([]byte, sz)
- if a.Dir() != DirOut {
- for i := range data {
- data[i] = byte(r.Intn(256))
- }
+ for i := range data {
+ data[i] = byte(r.Intn(256))
}
return MakeDataArg(a, data), nil
case BufferString:
data := r.randString(s, a.Values, a.Dir())
+ if a.Dir() == DirOut {
+ return MakeOutDataArg(a, uint64(len(data))), nil
+ }
return MakeDataArg(a, data), nil
case BufferFilename:
- var data []byte
if a.Dir() == DirOut {
+ sz := 0
switch {
case r.nOutOf(1, 3):
- data = make([]byte, r.Intn(100))
+ sz = r.Intn(100)
case r.nOutOf(1, 2):
- data = make([]byte, 108) // UNIX_PATH_MAX
+ sz = 108 // UNIX_PATH_MAX
default:
- data = make([]byte, 4096) // PATH_MAX
+ sz = 4096 // PATH_MAX
}
- } else {
- data = []byte(r.filename(s))
+ return MakeOutDataArg(a, uint64(sz)), nil
}
- return MakeDataArg(a, data), nil
+ return MakeDataArg(a, []byte(r.filename(s))), nil
case BufferText:
+ if a.Dir() == DirOut {
+ return MakeOutDataArg(a, uint64(r.Intn(100))), nil
+ }
return MakeDataArg(a, r.generateText(a.Text)), nil
default:
panic("unknown buffer kind")
diff --git a/prog/validation.go b/prog/validation.go
index e1f083b2a..d0f6d0bd2 100644
--- a/prog/validation.go
+++ b/prog/validation.go
@@ -68,10 +68,9 @@ func (c *Call) validate(ctx *validCtx) error {
return fmt.Errorf("syscall %v: output arg '%v'/'%v' has non default value '%+v'", c.Meta.Name, a.Type().FieldName(), a.Type().Name(), a)
}
case *DataArg:
- for _, v := range a.Data {
- if v != 0 {
- return fmt.Errorf("syscall %v: output arg '%v' has data", c.Meta.Name, a.Type().Name())
- }
+ if len(a.data) != 0 {
+ return fmt.Errorf("syscall %v: output arg '%v' has data",
+ c.Meta.Name, a.Type().Name())
}
}
}
@@ -123,9 +122,9 @@ func (c *Call) validate(ctx *validCtx) error {
case *DataArg:
switch typ1.Kind {
case BufferString:
- if typ1.TypeSize != 0 && uint64(len(a.Data)) != typ1.TypeSize {
+ if typ1.TypeSize != 0 && a.Size() != typ1.TypeSize {
return fmt.Errorf("syscall %v: string arg '%v' has size %v, which should be %v",
- c.Meta.Name, a.Type().Name(), len(a.Data), typ1.TypeSize)
+ c.Meta.Name, a.Type().Name(), a.Size(), typ1.TypeSize)
}
}
default:
@@ -178,10 +177,9 @@ func (c *Call) validate(ctx *validCtx) error {
}
case *DataArg:
typ1 := a.Type()
- if !typ1.Varlen() && typ1.Size() != uint64(len(a.Data)) {
+ if !typ1.Varlen() && typ1.Size() != a.Size() {
return fmt.Errorf("syscall %v: data arg %v has wrong size %v, want %v",
- c.Meta.Name, arg.Type().Name(),
- len(a.Data), typ1.Size())
+ c.Meta.Name, arg.Type().Name(), a.Size(), typ1.Size())
}
switch typ1 := a.Type().(type) {
case *ArrayType: