// 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_test import ( "fmt" "internal/abi" "internal/goarch" "runtime" "slices" "strings" "testing" "unsafe" ) func TestHexdumper(t *testing.T) { check := func(label, got, want string) { got = strings.TrimRight(got, "\n") want = strings.TrimPrefix(want, "\n") want = strings.TrimRight(want, "\n") if got != want { t.Errorf("%s: got\n%s\nwant\n%s", label, got, want) } } data := make([]byte, 32) for i := range data { data[i] = 0x10 + byte(i) } check("basic", runtime.Hexdumper(0, 1, nil, data), ` 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 00000000: 10111213 14151617 18191a1b 1c1d1e1f ................ 00000010: 20212223 24252627 28292a2b 2c2d2e2f !"#$%&'()*+,-./`) if !goarch.BigEndian { // Different word sizes check("word=4", runtime.Hexdumper(0, 4, nil, data), ` 3 2 1 0 7 6 5 4 b a 9 8 f e d c 0123456789abcdef 00000000: 13121110 17161514 1b1a1918 1f1e1d1c ................ 00000010: 23222120 27262524 2b2a2928 2f2e2d2c !"#$%&'()*+,-./`) check("word=8", runtime.Hexdumper(0, 8, nil, data), ` 7 6 5 4 3 2 1 0 f e d c b a 9 8 0123456789abcdef 00000000: 17161514 13121110 1f1e1d1c 1b1a1918 ................ 00000010: 27262524 23222120 2f2e2d2c 2b2a2928 !"#$%&'()*+,-./`) } // Starting offset check("offset=1", runtime.Hexdumper(1, 1, nil, data), ` 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 00000000: 101112 13141516 1718191a 1b1c1d1e ............... 00000010: 1f202122 23242526 2728292a 2b2c2d2e . !"#$%&'()*+,-. 00000020: 2f /`) if !goarch.BigEndian { // ... combined with a word size check("offset=1 and word=4", runtime.Hexdumper(1, 4, nil, data), ` 3 2 1 0 7 6 5 4 b a 9 8 f e d c 0123456789abcdef 00000000: 121110 16151413 1a191817 1e1d1c1b ............... 00000010: 2221201f 26252423 2a292827 2e2d2c2b . !"#$%&'()*+,-. 00000020: 2f /`) } // Partial data full of annoying boundaries. partials := make([][]byte, 0) for i := 0; i < len(data); i += 2 { partials = append(partials, data[i:i+2]) } check("partials", runtime.Hexdumper(1, 1, nil, partials...), ` 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 00000000: 101112 13141516 1718191a 1b1c1d1e ............... 00000010: 1f202122 23242526 2728292a 2b2c2d2e . !"#$%&'()*+,-. 00000020: 2f /`) // Marks. check("marks", runtime.Hexdumper(0, 1, func(addr uintptr, start func()) { if addr%7 == 0 { start() println("mark") } }, data), ` 0 1 2 3 4 5 6 7 8 9 a b c d e f 0123456789abcdef 00000000: 10111213 14151617 18191a1b 1c1d1e1f ................ ^ mark ^ mark ^ mark 00000010: 20212223 24252627 28292a2b 2c2d2e2f !"#$%&'()*+,-./ ^ mark ^ mark`) if !goarch.BigEndian { check("marks and word=4", runtime.Hexdumper(0, 4, func(addr uintptr, start func()) { if addr%7 == 0 { start() println("mark") } }, data), ` 3 2 1 0 7 6 5 4 b a 9 8 f e d c 0123456789abcdef 00000000: 13121110 17161514 1b1a1918 1f1e1d1c ................ ^ mark 00000010: 23222120 27262524 2b2a2928 2f2e2d2c !"#$%&'()*+,-./ ^ mark`) } } func TestHexdumpWords(t *testing.T) { if goarch.BigEndian || goarch.PtrSize != 8 { // We could support these, but it's kind of a pain. t.Skip("requires 64-bit little endian") } // Most of this is in hexdumper. Here we just test the symbolizer. pc := abi.FuncPCABIInternal(TestHexdumpWords) pcs := slices.Repeat([]uintptr{pc}, 3) // Make sure pcs doesn't move around on us. var p runtime.Pinner defer p.Unpin() p.Pin(&pcs[0]) // Get a 16 byte, 16-byte-aligned chunk of pcs so the hexdump is simple. start := uintptr(unsafe.Pointer(&pcs[0])) start = (start + 15) &^ uintptr(15) // Do the hex dump. got := runtime.HexdumpWords(start, 16) // Construct the expected output. pcStr := fmt.Sprintf("%016x", pc) pcStr = pcStr[:8] + " " + pcStr[8:] // Add middle space ascii := make([]byte, 8) for i := range ascii { b := byte(pc >> (8 * i)) if b >= ' ' && b <= '~' { ascii[i] = b } else { ascii[i] = '.' } } want := fmt.Sprintf(` 7 6 5 4 3 2 1 0 f e d c b a 9 8 0123456789abcdef %016x: %s %s %s%s ^ ^ `, start, pcStr, pcStr, ascii, ascii) want = strings.TrimPrefix(want, "\n") if got != want { t.Errorf("got\n%s\nwant\n%s", got, want) } }