blob: 0d7dbb540b6daf3e2388e966bb7ab524b2ea329f [file] [log] [blame]
// 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("^ ")
}