forked from alexflint/go-memdump
-
Notifications
You must be signed in to change notification settings - Fork 0
/
homogenous.go
171 lines (147 loc) · 4.02 KB
/
homogenous.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package memdump
import (
"bytes"
"encoding/gob"
"fmt"
"io"
"reflect"
)
// header is gob-encoded in the first segment
type header struct {
Protocol int32
Descriptor descriptor
}
// Encoder writes memdumps to the provided writer
type Encoder struct {
w io.Writer
t reflect.Type
}
// NewEncoder creates an Encoder that writes memdumps to the provided writer
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{
w: w,
}
}
// Encode writes a memdump of the provided object to output. You must pass a
// pointer to the object you wish to encode. (To encode a pointer, pass a
// pointer to a pointer.)
func (e *Encoder) Encode(obj interface{}) error {
t := reflect.TypeOf(obj)
if t.Kind() != reflect.Ptr {
panic(fmt.Sprintf("expected a pointer but got %T", obj))
}
if e.t != nil && e.t != t {
panic(fmt.Sprintf("each call to Encode should pass the same type, but got %v then %v", e.t, t))
}
if e.t == nil {
// write the header
gob := gob.NewEncoder(e.w)
err := gob.Encode(header{
Protocol: homogeneousProtocol,
Descriptor: describe(t.Elem()),
})
if err != nil {
return fmt.Errorf("error writing footer: %v", err)
}
e.t = t
_, err = e.w.Write(delim)
}
// first segment: write the object data
mem := newMemEncoder(e.w)
ptrs, err := mem.Encode(obj)
if err != nil {
return fmt.Errorf("error writing data segment: %v", err)
}
// write delimiter
_, err = e.w.Write(delim)
if err != nil {
return fmt.Errorf("error writing delimiter: %v", err)
}
// second segment: write the footer
err = encodeLocations(e.w, &locations{Pointers: ptrs})
if err != nil {
return fmt.Errorf("error writing footer: %v", err)
}
// write delimiter
_, err = e.w.Write(delim)
if err != nil {
return fmt.Errorf("error writing delimiter: %v", err)
}
return nil
}
// Decoder reads memdumps from the provided reader
type Decoder struct {
dr *DelimitedReader
t reflect.Type
}
// NewDecoder creates a Decoder that reads memdumps
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{
dr: NewDelimitedReader(r),
}
}
// Decode reads an object of the specified type from the input.
// The object passed to Decode must be a pointer to the type
// was originally passed to Encode().
func (d *Decoder) Decode(dest interface{}) error {
t := reflect.TypeOf(dest)
if t.Kind() != reflect.Ptr {
panic(fmt.Sprintf("expected a pointer but got %T", dest))
}
ptr, err := d.DecodePtr(t.Elem())
if err != nil {
return err
}
reflect.ValueOf(dest).Elem().Set(reflect.ValueOf(ptr).Elem())
return nil
}
// DecodePtr reads an object of the specified type from the input
// and returns a pointer to it. The provided type must be the result
// of calling reflect.TypeOf(x) where x is the object originally
// passed to Encode(). The return valoue will be of type *x
func (d *Decoder) DecodePtr(t reflect.Type) (interface{}, error) {
if d.t != nil && d.t != t {
panic(fmt.Sprintf("each call to Encode should pass the same type, but got %v then %v", d.t, t))
}
// read the header
if d.t == nil {
// decode the descriptor
seg, err := d.dr.Next()
if err != nil {
return nil, fmt.Errorf("error reading header segment: %v", err)
}
var header header
dec := gob.NewDecoder(bytes.NewBuffer(seg))
err = dec.Decode(&header)
if err != nil {
return nil, fmt.Errorf("error decoding header: %v", err)
}
// compare descriptors
expectedDescr := describe(t)
if !descriptorsEqual(expectedDescr, header.Descriptor) {
return nil, ErrIncompatibleLayout
}
d.t = t
}
// read the data
dataseg, err := d.dr.Next()
if len(dataseg) == 0 && err == io.EOF {
return nil, io.EOF
}
if err != nil {
return nil, fmt.Errorf("error reading data segment: %v", err)
}
// read the footer
footerseg, err := d.dr.Next()
if err != nil {
return nil, fmt.Errorf("error decoding footer: %v", err)
}
// decode footer
var f locations
err = decodeLocations(bytes.NewBuffer(footerseg), &f)
if err != nil {
return nil, fmt.Errorf("error decoding footer: %v", err)
}
// relocate the data
return relocate(dataseg, f.Pointers, f.Main, t)
}