github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/safemem/block_unsafe.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package safemem
    16  
    17  import (
    18  	"fmt"
    19  	"unsafe"
    20  
    21  	"github.com/SagerNet/gvisor/pkg/gohacks"
    22  	"github.com/SagerNet/gvisor/pkg/safecopy"
    23  	"github.com/SagerNet/gvisor/pkg/sync"
    24  )
    25  
    26  // A Block is a range of contiguous bytes, similar to []byte but with the
    27  // following differences:
    28  //
    29  // - The memory represented by a Block may require the use of safecopy to
    30  // access.
    31  //
    32  // - Block does not carry a capacity and cannot be expanded.
    33  //
    34  // Blocks are immutable and may be copied by value. The zero value of Block
    35  // represents an empty range, analogous to a nil []byte.
    36  type Block struct {
    37  	// [start, start+length) is the represented memory.
    38  	//
    39  	// start is an unsafe.Pointer to ensure that Block prevents the represented
    40  	// memory from being garbage-collected.
    41  	start  unsafe.Pointer
    42  	length int
    43  
    44  	// needSafecopy is true if accessing the represented memory requires the
    45  	// use of safecopy.
    46  	needSafecopy bool
    47  }
    48  
    49  // BlockFromSafeSlice returns a Block equivalent to slice, which is safe to
    50  // access without safecopy.
    51  func BlockFromSafeSlice(slice []byte) Block {
    52  	return blockFromSlice(slice, false)
    53  }
    54  
    55  // BlockFromUnsafeSlice returns a Block equivalent to bs, which is not safe to
    56  // access without safecopy.
    57  func BlockFromUnsafeSlice(slice []byte) Block {
    58  	return blockFromSlice(slice, true)
    59  }
    60  
    61  func blockFromSlice(slice []byte, needSafecopy bool) Block {
    62  	if len(slice) == 0 {
    63  		return Block{}
    64  	}
    65  	return Block{
    66  		start:        unsafe.Pointer(&slice[0]),
    67  		length:       len(slice),
    68  		needSafecopy: needSafecopy,
    69  	}
    70  }
    71  
    72  // BlockFromSafePointer returns a Block equivalent to [ptr, ptr+length), which is
    73  // safe to access without safecopy.
    74  //
    75  // Preconditions: ptr+length does not overflow.
    76  func BlockFromSafePointer(ptr unsafe.Pointer, length int) Block {
    77  	return blockFromPointer(ptr, length, false)
    78  }
    79  
    80  // BlockFromUnsafePointer returns a Block equivalent to [ptr, ptr+len), which
    81  // is not safe to access without safecopy.
    82  //
    83  // Preconditions: ptr+len does not overflow.
    84  func BlockFromUnsafePointer(ptr unsafe.Pointer, length int) Block {
    85  	return blockFromPointer(ptr, length, true)
    86  }
    87  
    88  func blockFromPointer(ptr unsafe.Pointer, length int, needSafecopy bool) Block {
    89  	if uptr := uintptr(ptr); uptr+uintptr(length) < uptr {
    90  		panic(fmt.Sprintf("ptr %#x + len %#x overflows", uptr, length))
    91  	}
    92  	return Block{
    93  		start:        ptr,
    94  		length:       length,
    95  		needSafecopy: needSafecopy,
    96  	}
    97  }
    98  
    99  // DropFirst returns a Block equivalent to b, but with the first n bytes
   100  // omitted. It is analogous to the [n:] operation on a slice, except that if n
   101  // > b.Len(), DropFirst returns an empty Block instead of panicking.
   102  //
   103  // Preconditions: n >= 0.
   104  func (b Block) DropFirst(n int) Block {
   105  	if n < 0 {
   106  		panic(fmt.Sprintf("invalid n: %d", n))
   107  	}
   108  	return b.DropFirst64(uint64(n))
   109  }
   110  
   111  // DropFirst64 is equivalent to DropFirst but takes a uint64.
   112  func (b Block) DropFirst64(n uint64) Block {
   113  	if n >= uint64(b.length) {
   114  		return Block{}
   115  	}
   116  	return Block{
   117  		start:        unsafe.Pointer(uintptr(b.start) + uintptr(n)),
   118  		length:       b.length - int(n),
   119  		needSafecopy: b.needSafecopy,
   120  	}
   121  }
   122  
   123  // TakeFirst returns a Block equivalent to the first n bytes of b. It is
   124  // analogous to the [:n] operation on a slice, except that if n > b.Len(),
   125  // TakeFirst returns a copy of b instead of panicking.
   126  //
   127  // Preconditions: n >= 0.
   128  func (b Block) TakeFirst(n int) Block {
   129  	if n < 0 {
   130  		panic(fmt.Sprintf("invalid n: %d", n))
   131  	}
   132  	return b.TakeFirst64(uint64(n))
   133  }
   134  
   135  // TakeFirst64 is equivalent to TakeFirst but takes a uint64.
   136  func (b Block) TakeFirst64(n uint64) Block {
   137  	if n == 0 {
   138  		return Block{}
   139  	}
   140  	if n >= uint64(b.length) {
   141  		return b
   142  	}
   143  	return Block{
   144  		start:        b.start,
   145  		length:       int(n),
   146  		needSafecopy: b.needSafecopy,
   147  	}
   148  }
   149  
   150  // ToSlice returns a []byte equivalent to b.
   151  func (b Block) ToSlice() []byte {
   152  	return *(*[]byte)(unsafe.Pointer(&gohacks.SliceHeader{
   153  		Data: b.start,
   154  		Len:  b.length,
   155  		Cap:  b.length,
   156  	}))
   157  }
   158  
   159  // Addr returns b's start address as a uintptr. It returns uintptr instead of
   160  // unsafe.Pointer so that code using safemem cannot obtain unsafe.Pointers
   161  // without importing the unsafe package explicitly.
   162  //
   163  // Note that a uintptr is not recognized as a pointer by the garbage collector,
   164  // such that if there are no uses of b after a call to b.Addr() and the address
   165  // is to Go-managed memory, the returned uintptr does not prevent garbage
   166  // collection of the pointee.
   167  func (b Block) Addr() uintptr {
   168  	return uintptr(b.start)
   169  }
   170  
   171  // Len returns b's length in bytes.
   172  func (b Block) Len() int {
   173  	return b.length
   174  }
   175  
   176  // NeedSafecopy returns true if accessing b.ToSlice() requires the use of safecopy.
   177  func (b Block) NeedSafecopy() bool {
   178  	return b.needSafecopy
   179  }
   180  
   181  // String implements fmt.Stringer.String.
   182  func (b Block) String() string {
   183  	if uintptr(b.start) == 0 && b.length == 0 {
   184  		return "<nil>"
   185  	}
   186  	var suffix string
   187  	if b.needSafecopy {
   188  		suffix = "*"
   189  	}
   190  	return fmt.Sprintf("[%#x-%#x)%s", uintptr(b.start), uintptr(b.start)+uintptr(b.length), suffix)
   191  }
   192  
   193  // Copy copies src.Len() or dst.Len() bytes, whichever is less, from src
   194  // to dst and returns the number of bytes copied.
   195  //
   196  // If src and dst overlap, the data stored in dst is unspecified.
   197  func Copy(dst, src Block) (int, error) {
   198  	if !dst.needSafecopy && !src.needSafecopy {
   199  		return copy(dst.ToSlice(), src.ToSlice()), nil
   200  	}
   201  
   202  	n := dst.length
   203  	if n > src.length {
   204  		n = src.length
   205  	}
   206  	if n == 0 {
   207  		return 0, nil
   208  	}
   209  
   210  	switch {
   211  	case dst.needSafecopy && !src.needSafecopy:
   212  		return safecopy.CopyOut(dst.start, src.TakeFirst(n).ToSlice())
   213  	case !dst.needSafecopy && src.needSafecopy:
   214  		return safecopy.CopyIn(dst.TakeFirst(n).ToSlice(), src.start)
   215  	case dst.needSafecopy && src.needSafecopy:
   216  		n64, err := safecopy.Copy(dst.start, src.start, uintptr(n))
   217  		return int(n64), err
   218  	default:
   219  		panic("unreachable")
   220  	}
   221  }
   222  
   223  // Zero sets all bytes in dst to 0 and returns the number of bytes zeroed.
   224  func Zero(dst Block) (int, error) {
   225  	if !dst.needSafecopy {
   226  		bs := dst.ToSlice()
   227  		if !sync.RaceEnabled {
   228  			// If the race detector isn't enabled, the golang
   229  			// compiler replaces the next loop with memclr
   230  			// (https://github.com/golang/go/issues/5373).
   231  			for i := range bs {
   232  				bs[i] = 0
   233  			}
   234  		} else {
   235  			bsLen := len(bs)
   236  			if bsLen == 0 {
   237  				return 0, nil
   238  			}
   239  			bs[0] = 0
   240  			for i := 1; i < bsLen; i *= 2 {
   241  				copy(bs[i:], bs[:i])
   242  			}
   243  		}
   244  		return len(bs), nil
   245  	}
   246  
   247  	n64, err := safecopy.ZeroOut(dst.start, uintptr(dst.length))
   248  	return int(n64), err
   249  }
   250  
   251  // Safecopy atomics are no slower than non-safecopy atomics, so use the former
   252  // even when !b.needSafecopy to get consistent alignment checking.
   253  
   254  // SwapUint32 invokes safecopy.SwapUint32 on the first 4 bytes of b.
   255  //
   256  // Preconditions: b.Len() >= 4.
   257  func SwapUint32(b Block, new uint32) (uint32, error) {
   258  	if b.length < 4 {
   259  		panic(fmt.Sprintf("insufficient length: %d", b.length))
   260  	}
   261  	return safecopy.SwapUint32(b.start, new)
   262  }
   263  
   264  // SwapUint64 invokes safecopy.SwapUint64 on the first 8 bytes of b.
   265  //
   266  // Preconditions: b.Len() >= 8.
   267  func SwapUint64(b Block, new uint64) (uint64, error) {
   268  	if b.length < 8 {
   269  		panic(fmt.Sprintf("insufficient length: %d", b.length))
   270  	}
   271  	return safecopy.SwapUint64(b.start, new)
   272  }
   273  
   274  // CompareAndSwapUint32 invokes safecopy.CompareAndSwapUint32 on the first 4
   275  // bytes of b.
   276  //
   277  // Preconditions: b.Len() >= 4.
   278  func CompareAndSwapUint32(b Block, old, new uint32) (uint32, error) {
   279  	if b.length < 4 {
   280  		panic(fmt.Sprintf("insufficient length: %d", b.length))
   281  	}
   282  	return safecopy.CompareAndSwapUint32(b.start, old, new)
   283  }
   284  
   285  // LoadUint32 invokes safecopy.LoadUint32 on the first 4 bytes of b.
   286  //
   287  // Preconditions: b.Len() >= 4.
   288  func LoadUint32(b Block) (uint32, error) {
   289  	if b.length < 4 {
   290  		panic(fmt.Sprintf("insufficient length: %d", b.length))
   291  	}
   292  	return safecopy.LoadUint32(b.start)
   293  }