// Copyright 2018 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 prog
import (
"fmt"
)
// memAlloc keeps track of allocated objects in a program
// and decides where to allocate new objects.
// It has 2 main methods: noteAlloc which is called for existing allocations
// in a program as we analyze it; and alloc which decides where to allocate
// a new object.
// The implementation is based on a 2-level bitmap where each bit represents
// 64 bytes (memAllocGranule) of program memory.
type memAlloc struct {
size uint64
mem [memAllocL1Size]*[memAllocL0Size]uint64
buf [memAllocL0Size]uint64
}
const (
memAllocGranule = 64 // 1 bit per that many bytes (all allocations are rounded to this size)
memAllocMaxMem = 16 << 20
memAllocL0Size = 64
bitsPerUint64 = 8 * 8
memAllocL0Mem = memAllocL0Size * memAllocGranule * bitsPerUint64
memAllocL1Size = memAllocMaxMem / memAllocL0Mem
)
func newMemAlloc(totalMemSize uint64) *memAlloc {
if totalMemSize > memAllocMaxMem {
panic(fmt.Sprintf("newMemAlloc: too much mem %v (max: %v)", totalMemSize, memAllocMaxMem))
}
if totalMemSize%memAllocL0Mem != 0 {
panic(fmt.Sprintf("newMemAlloc: unaligned size %v (align: %v)", totalMemSize, memAllocL0Mem))
}
ma := &memAlloc{
size: totalMemSize / memAllocGranule,
}
ma.mem[0] = &ma.buf
return ma
}
func (ma *memAlloc) noteAlloc(addr0, size0 uint64) {
addr := addr0 / memAllocGranule
size := (addr0+size0+memAllocGranule-1)/memAllocGranule - addr
for i := uint64(0); i < size; i++ {
ma.set(addr + i)
}
}
func (ma *memAlloc) alloc(r *randGen, size0 uint64) uint64 {
if size0 == 0 {
size0 = 1
}
size := (size0 + memAllocGranule - 1) / memAllocGranule
end := ma.size - size
for start := uint64(0); start < end; start++ {
empty := true
for i := uint64(0); i < size; i++ {
if ma.get(start + i) {
empty = false
break
}
}
if empty {
start0 := start * memAllocGranule
ma.noteAlloc(start0, size0)
return start0
}
}
ma.bankruptcy()
return ma.alloc(r, size0)
}
func (ma *memAlloc) bankruptcy() {
for i1 := uint64(0); i1 < ma.size/(memAllocL0Size*bitsPerUint64); i1++ {
if ma.mem[i1] == nil {
continue
}
for i0 := range ma.mem[i1] {
ma.mem[i1][i0] = 0
}
}
}
func (ma *memAlloc) pos(idx uint64) (i1, i0, bit uint64) {
i1 = idx / (memAllocL0Size * bitsPerUint64)
r1 := idx % (memAllocL0Size * bitsPerUint64)
i0 = r1 / bitsPerUint64
bit = 1 << (r1 % bitsPerUint64)
return
}
func (ma *memAlloc) set(idx uint64) {
i1, i0, bit := ma.pos(idx)
if ma.mem[i1] == nil {
ma.mem[i1] = new([memAllocL0Size]uint64)
}
ma.mem[i1][i0] |= bit
}
func (ma *memAlloc) get(idx uint64) bool {
i1, i0, bit := ma.pos(idx)
if ma.mem[i1] == nil {
return false
}
return ma.mem[i1][i0]&bit != 0
}
type vmaAlloc struct {
numPages uint64
used []uint64
m map[uint64]struct{}
}
func newVmaAlloc(totalPages uint64) *vmaAlloc {
return &vmaAlloc{
numPages: totalPages,
m: make(map[uint64]struct{}),
}
}
func (va *vmaAlloc) noteAlloc(page, size uint64) {
for i := page; i < page+size; i++ {
if _, ok := va.m[i]; ok {
continue
}
va.m[i] = struct{}{}
va.used = append(va.used, i)
}
}
func (va *vmaAlloc) alloc(r *randGen, size uint64) uint64 {
if size > va.numPages {
panic(fmt.Sprintf("vmaAlloc: bad size=%v numPages=%v", size, va.numPages))
}
var page uint64
if len(va.used) == 0 || r.oneOf(5) {
page = r.rand(4)
if !r.oneOf(100) {
page = va.numPages - page - size
}
} else {
page = va.used[r.rand(len(va.used))]
if size > 1 && r.bin() {
off := r.rand(int(size))
if off > page {
off = page
}
page -= off
}
if page+size > va.numPages {
page = va.numPages - size
}
}
if page >= va.numPages || size > va.numPages || page+size > va.numPages {
panic(fmt.Sprintf("vmaAlloc: bad page=%v size=%v numPages=%v", page, size, va.numPages))
}
va.noteAlloc(page, size)
return page
}