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