| // 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("^ ") |
| } |