github.com/alexflint/go-memdump@v1.1.0/serialize.go (about) 1 package memdump 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "io" 7 "reflect" 8 "sort" 9 "sync" 10 "unsafe" 11 ) 12 13 // uintptrSize is the size in bytes of uintptr 14 const uintptrSize = unsafe.Sizeof(uintptr(0)) 15 16 // byteType is the reflect.Type of byte 17 var ( 18 byteType = reflect.TypeOf(byte(0)) 19 typeCache = make(map[reflect.Type]*typeInfo) 20 typeCacheLock sync.Mutex 21 ) 22 23 // block represents a value to be written to the stream 24 type block struct { 25 src reflect.Value 26 dest uintptr 27 } 28 29 // pointer represents the location of a pointer in a type 30 type pointer struct { 31 offset uintptr 32 typ reflect.Type 33 } 34 35 // typeInfo represents the location of the pointers in a type 36 type typeInfo struct { 37 pointers []pointer 38 } 39 40 // asBytes gets a byte slice with data pointer set to the address of the 41 // assigned value and length set to the sizeof the value. 42 func asBytes(v reflect.Value) []byte { 43 size := int(v.Type().Size()) 44 hdr := reflect.SliceHeader{ 45 Data: v.Addr().Pointer(), 46 Len: size, 47 Cap: size, 48 } 49 return *(*[]byte)(unsafe.Pointer(&hdr)) 50 } 51 52 // isNil determines whether the pointer contained within v is nil. 53 // This is equivalent to checking x==nil, except for strings, where 54 // this method checks the data pointer inside the string header. 55 func isNil(v reflect.Value) bool { 56 if v.Kind() == reflect.String { 57 hdr := (*reflect.StringHeader)(unsafe.Pointer(v.Addr().Pointer())) 58 return hdr.Data == 0 59 } 60 return v.IsNil() 61 } 62 63 func readPointer(v reflect.Value) uintptr { 64 if v.Kind() == reflect.String { 65 hdr := (*reflect.StringHeader)(unsafe.Pointer(v.Addr().Pointer())) 66 return hdr.Data 67 } 68 return v.Pointer() 69 } 70 71 type byOffset []pointer 72 73 func (xs byOffset) Len() int { return len(xs) } 74 func (xs byOffset) Swap(i, j int) { xs[i], xs[j] = xs[j], xs[i] } 75 func (xs byOffset) Less(i, j int) bool { return xs[i].offset < xs[j].offset } 76 77 type countingWriter struct { 78 w io.Writer 79 offset int 80 } 81 82 func (w *countingWriter) Write(buf []byte) (int, error) { 83 n, err := w.w.Write(buf) 84 w.offset += n 85 return n, err 86 } 87 88 // memEncoder writes the in-memory representation of an object, together 89 // with all referenced objects. 90 type memEncoder struct { 91 w countingWriter 92 } 93 94 func newMemEncoder(w io.Writer) *memEncoder { 95 return &memEncoder{ 96 w: countingWriter{w: w}, 97 } 98 } 99 100 // memEncoderState contains the state that is local to a single Encode() call. 101 type memEncoderState struct { 102 ptrLocs []int64 103 next uintptr 104 } 105 106 // alloc makes room for N objects of the specified type, and returns the 107 // base offset for that object. It deals correctly with alignment. 108 func (e *memEncoderState) alloc(t reflect.Type, n int) uintptr { 109 align := uintptr(t.Align()) 110 if e.next%align != 0 { 111 e.next += align - (e.next % align) 112 } 113 cur := e.next 114 e.next += t.Size() * uintptr(n) 115 return cur 116 } 117 118 // arrayFromString gets a fixed-size array representing the bytes pointed to 119 // by a string. 120 func arrayFromString(strval reflect.Value) reflect.Value { 121 if strval.Kind() != reflect.String { 122 panic(fmt.Sprintf("expected string type but got %s", strval.Type())) 123 } 124 125 hdr := (*reflect.StringHeader)(unsafe.Pointer(strval.Addr().Pointer())) 126 typ := reflect.ArrayOf(hdr.Len, byteType) 127 return reflect.NewAt(typ, unsafe.Pointer(hdr.Data)).Elem() 128 } 129 130 // arrayFromSlice gets a fixed-size array representing the data pointed to by 131 // a slice 132 func arrayFromSlice(sliceval reflect.Value) reflect.Value { 133 if sliceval.Kind() != reflect.Slice { 134 panic(fmt.Sprintf("expected string type but got %s", sliceval.Type())) 135 } 136 137 hdr := (*reflect.SliceHeader)(unsafe.Pointer(sliceval.Addr().Pointer())) 138 typ := reflect.ArrayOf(hdr.Len, sliceval.Type().Elem()) 139 return reflect.NewAt(typ, unsafe.Pointer(hdr.Data)).Elem() 140 } 141 142 // Encode writes the in-memory representation of the object pointed to by ptr. It 143 // returns the offset of each pointer and an error. 144 func (e *memEncoder) Encode(ptr interface{}) ([]int64, error) { 145 var state memEncoderState 146 147 ptrval := reflect.ValueOf(ptr) 148 objval := ptrval.Elem() 149 cache := make(map[uintptr]uintptr) 150 queue := []block{{ 151 src: objval, 152 dest: state.alloc(objval.Type(), 1), 153 }} 154 155 for len(queue) > 0 { 156 cur := queue[0] 157 queue = queue[1:] 158 159 blockaddr := cur.src.Addr() 160 blockbytes := asBytes(cur.src) 161 162 // check the position of the writer 163 if cur.dest < uintptr(e.w.offset) { 164 panic(fmt.Sprintf("block.dest=%d but writer is at %d", cur.dest, e.w.offset)) 165 } 166 167 // for byte-alignment purposes we may need to fill some bytes 168 if fill := cur.dest - uintptr(e.w.offset); fill > 0 { 169 _, err := e.w.Write(make([]byte, fill)) 170 if err != nil { 171 return nil, err 172 } 173 } 174 175 // look up info about this type 176 info := lookupType(cur.src.Type()) 177 178 // add each referenced object to the queue 179 var blockpos uintptr 180 for _, ptr := range info.pointers { 181 _, err := e.w.Write(blockbytes[blockpos:ptr.offset]) 182 if err != nil { 183 return nil, err 184 } 185 186 ptrdata := unsafe.Pointer(blockaddr.Pointer() + ptr.offset) 187 ptrval := reflect.NewAt(ptr.typ, ptrdata).Elem() 188 189 var dest uintptr 190 if !isNil(ptrval) { 191 state.ptrLocs = append(state.ptrLocs, int64(cur.dest+ptr.offset)) 192 193 var found bool 194 dest, found = cache[readPointer(ptrval)] 195 if !found { 196 switch ptr.typ.Kind() { 197 case reflect.Ptr: 198 dest = state.alloc(ptr.typ.Elem(), 1) 199 queue = append(queue, block{ 200 src: ptrval.Elem(), 201 dest: dest, 202 }) 203 case reflect.Slice: 204 dest = state.alloc(ptr.typ.Elem(), ptrval.Len()) 205 arr := arrayFromSlice(ptrval) 206 queue = append(queue, block{ 207 src: arr, 208 dest: dest, 209 }) 210 case reflect.String: 211 dest = state.alloc(byteType, ptrval.Len()) 212 arr := arrayFromString(ptrval) 213 queue = append(queue, block{ 214 src: arr, 215 dest: dest, 216 }) 217 } 218 cache[readPointer(ptrval)] = dest 219 } 220 } 221 222 err = binary.Write(&e.w, binary.LittleEndian, uint64(dest)) 223 if err != nil { 224 return nil, err 225 } 226 blockpos = ptr.offset + uintptrSize 227 } 228 229 _, err := e.w.Write(blockbytes[blockpos:]) 230 if err != nil { 231 return nil, err 232 } 233 } 234 235 return state.ptrLocs, nil 236 } 237 238 // pointerFinder gets the byte offset of each pointer in an object. It 239 // only considers the immediate value of an object (i.e. the bytes that 240 // would be copied in a simple assignment). It does not follow pointers 241 // to other objects. 242 type pointerFinder struct { 243 pointers []pointer 244 } 245 246 func (f *pointerFinder) visit(t reflect.Type, base uintptr) { 247 switch t.Kind() { 248 case reflect.Ptr, reflect.String, reflect.Slice: 249 // these four types all store one pointer at offset zero 250 f.pointers = append(f.pointers, pointer{ 251 offset: base, 252 typ: t, 253 }) 254 case reflect.Struct: 255 for i := 0; i < t.NumField(); i++ { 256 field := t.Field(i) 257 f.visit(field.Type, base+field.Offset) 258 } 259 case reflect.Array: 260 elemSize := t.Elem().Size() 261 elemPtrs := lookupType(t.Elem()).pointers 262 for _, elemPtr := range elemPtrs { 263 for i := 0; i < t.Len(); i++ { 264 f.pointers = append(f.pointers, pointer{ 265 offset: base + uintptr(i)*elemSize + elemPtr.offset, 266 typ: elemPtr.typ, 267 }) 268 } 269 } 270 case reflect.Map, reflect.Chan, reflect.Interface, reflect.UnsafePointer, reflect.Func: 271 panic(fmt.Sprintf("cannot serialize objects of %v kind (got %v)", t.Kind(), t)) 272 } 273 } 274 275 // lookupType gets the type info for t. 276 func lookupType(t reflect.Type) *typeInfo { 277 typeCacheLock.Lock() 278 info, found := typeCache[t] 279 typeCacheLock.Unlock() 280 281 if !found { 282 var f pointerFinder 283 f.visit(t, 0) 284 info = &typeInfo{pointers: f.pointers} 285 sort.Sort(byOffset(info.pointers)) 286 287 typeCacheLock.Lock() 288 typeCache[t] = info 289 typeCacheLock.Unlock() 290 } 291 292 return info 293 }