| // 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 |
| ^ <runtime_test.TestHexdumpWords+0x0> |
| ^ <runtime_test.TestHexdumpWords+0x0> |
| `, start, pcStr, pcStr, ascii, ascii) |
| want = strings.TrimPrefix(want, "\n") |
| |
| if got != want { |
| t.Errorf("got\n%s\nwant\n%s", got, want) |
| } |
| } |