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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
|
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime
import (
"internal/goarch"
"unsafe"
)
// hexdumpWords prints a word-oriented hex dump of [p, p+len).
//
// If mark != nil, it will be passed to hexdumper.mark.
func hexdumpWords(p, len uintptr, mark func(uintptr, hexdumpMarker)) {
printlock()
// Provide a default annotation
symMark := func(u uintptr, hm hexdumpMarker) {
if mark != nil {
mark(u, hm)
}
// Can we symbolize this value?
val := *(*uintptr)(unsafe.Pointer(u))
fn := findfunc(val)
if fn.valid() {
hm.start()
print("<", funcname(fn), "+", hex(val-fn.entry()), ">\n")
}
}
h := hexdumper{addr: p, mark: symMark}
h.write(unsafe.Slice((*byte)(unsafe.Pointer(p)), len))
h.close()
printunlock()
}
// hexdumper is a Swiss-army knife hex dumper.
//
// To use, optionally set addr and wordBytes, then call write repeatedly,
// followed by close.
type hexdumper struct {
// addr is the address to print for the first byte of data.
addr uintptr
// addrBytes is the number of bytes of addr to print. If this is 0, it
// defaults to goarch.PtrSize.
addrBytes uint8
// wordBytes is the number of bytes in a word. If wordBytes is 1, this
// prints a byte-oriented dump. If it's > 1, this interprets the data as a
// sequence of words of the given size. If it's 0, it's treated as
// goarch.PtrSize.
wordBytes uint8
// mark is an optional function that can annotate values in the hex dump.
//
// If non-nil, it is called with the address of every complete, aligned word
// in the hex dump.
//
// If it decides to print an annotation, it must first call m.start(), then
// print the annotation, followed by a new line.
mark func(addr uintptr, m hexdumpMarker)
// Below here is state
ready int8 // 0=need to init state; 1=need to print header; 2=ready
// dataBuf accumulates a line at a time of data, in case it's split across
// buffers.
dataBuf [16]byte
dataPos uint8
dataSkip uint8 // Skip first n bytes of buf on first line
// toPos maps from byte offset in data to a visual offset in the printed line.
toPos [16]byte
}
type hexdumpMarker struct {
chars int
}
func (h *hexdumper) write(data []byte) {
if h.ready == 0 {
h.init()
}
// Handle leading data
if h.dataPos > 0 {
n := copy(h.dataBuf[h.dataPos:], data)
h.dataPos += uint8(n)
data = data[n:]
if h.dataPos < uint8(len(h.dataBuf)) {
return
}
h.flushLine(h.dataBuf[:])
h.dataPos = 0
}
// Handle full lines in data
for len(data) >= len(h.dataBuf) {
h.flushLine(data[:len(h.dataBuf)])
data = data[len(h.dataBuf):]
}
// Handle trailing data
h.dataPos = uint8(copy(h.dataBuf[:], data))
}
func (h *hexdumper) close() {
if h.dataPos > 0 {
h.flushLine(h.dataBuf[:h.dataPos])
}
}
func (h *hexdumper) init() {
const bytesPerLine = len(h.dataBuf)
if h.addrBytes == 0 {
h.addrBytes = goarch.PtrSize
} else if h.addrBytes < 0 || h.addrBytes > goarch.PtrSize {
throw("invalid addrBytes")
}
if h.wordBytes == 0 {
h.wordBytes = goarch.PtrSize
}
wb := int(h.wordBytes)
if wb < 0 || wb >= bytesPerLine || wb&(wb-1) != 0 {
throw("invalid wordBytes")
}
// Construct position mapping.
for i := range h.toPos {
// First, calculate the "field" within the line, applying byte swizzling.
field := 0
if goarch.BigEndian {
field = i
} else {
field = i ^ int(wb-1)
}
// Translate this field into a visual offset.
// "00112233 44556677 8899AABB CCDDEEFF"
h.toPos[i] = byte(field*2 + field/4 + field/8)
}
// The first line may need to skip some fields to get to alignment.
// Round down the starting address.
nAddr := h.addr &^ uintptr(bytesPerLine-1)
// Skip bytes to get to alignment.
h.dataPos = uint8(h.addr - nAddr)
h.dataSkip = uint8(h.addr - nAddr)
h.addr = nAddr
// We're ready to print the header.
h.ready = 1
}
func (h *hexdumper) flushLine(data []byte) {
const bytesPerLine = len(h.dataBuf)
const maxAddrChars = 2 * goarch.PtrSize
const addrSep = ": "
dataStart := int(2*h.addrBytes) + len(addrSep)
// dataChars uses the same formula to toPos above. We calculate it with the
// "last field", then add the size of the last field.
const dataChars = (bytesPerLine-1)*2 + (bytesPerLine-1)/4 + (bytesPerLine-1)/8 + 2
const asciiSep = " "
asciiStart := dataStart + dataChars + len(asciiSep)
const asciiChars = bytesPerLine
nlPos := asciiStart + asciiChars
var lineBuf [maxAddrChars + len(addrSep) + dataChars + len(asciiSep) + asciiChars + 1]byte
clear := func() {
for i := range lineBuf {
lineBuf[i] = ' '
}
}
clear()
if h.ready == 1 {
// Print column offsets header.
for offset, pos := range h.toPos {
h.fmtHex(lineBuf[dataStart+int(pos+1):][:1], uint64(offset))
}
// Print ASCII offsets.
for offset := range asciiChars {
h.fmtHex(lineBuf[asciiStart+offset:][:1], uint64(offset))
}
lineBuf[nlPos] = '\n'
gwrite(lineBuf[:nlPos+1])
clear()
h.ready = 2
}
// Format address.
h.fmtHex(lineBuf[:2*h.addrBytes], uint64(h.addr))
copy(lineBuf[2*h.addrBytes:], addrSep)
// Format data in hex and ASCII.
for offset, b := range data {
if offset < int(h.dataSkip) {
continue
}
pos := h.toPos[offset]
h.fmtHex(lineBuf[dataStart+int(pos):][:2], uint64(b))
copy(lineBuf[dataStart+dataChars:], asciiSep)
ascii := uint8('.')
if b >= ' ' && b <= '~' {
ascii = b
}
lineBuf[asciiStart+offset] = ascii
}
// Trim buffer.
end := asciiStart + len(data)
lineBuf[end] = '\n'
buf := lineBuf[:end+1]
// Print.
gwrite(buf)
// Print marks.
if h.mark != nil {
clear()
for offset := 0; offset+int(h.wordBytes) <= len(data); offset += int(h.wordBytes) {
if offset < int(h.dataSkip) {
continue
}
addr := h.addr + uintptr(offset)
// Find the position of the left edge of this word
caret := dataStart + int(min(h.toPos[offset], h.toPos[offset+int(h.wordBytes)-1]))
h.mark(addr, hexdumpMarker{caret})
}
}
h.addr += uintptr(bytesPerLine)
h.dataPos = 0
h.dataSkip = 0
}
// fmtHex formats v in base 16 into buf. It fills all of buf. If buf is too
// small to represent v, it the output will start with '*'.
func (h *hexdumper) fmtHex(buf []byte, v uint64) {
const dig = "0123456789abcdef"
i := len(buf) - 1
for ; i >= 0; i-- {
buf[i] = dig[v%16]
v /= 16
}
if v != 0 {
// Indicate that we couldn't fit the whole number.
buf[0] = '*'
}
}
func (m hexdumpMarker) start() {
var spaces [64]byte
for i := range spaces {
spaces[i] = ' '
}
for m.chars > len(spaces) {
gwrite(spaces[:])
m.chars -= len(spaces)
}
gwrite(spaces[:m.chars])
print("^ ")
}
|