golang.org/x/arch@v0.17.0/riscv64/riscv64asm/objdumpext_test.go (about)

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package riscv64asm
     6  
     7  import (
     8  	"bytes"
     9  	"debug/elf"
    10  	"encoding/binary"
    11  	"fmt"
    12  	"io"
    13  	"log"
    14  	"os"
    15  	"os/exec"
    16  	"strconv"
    17  	"strings"
    18  	"testing"
    19  )
    20  
    21  var objdumpPath = "riscv64-linux-gnu-objdump"
    22  
    23  func testObjdumpRISCV64(t *testing.T, generate func(func([]byte))) {
    24  	testObjdumpArch(t, generate)
    25  }
    26  
    27  func testObjdumpArch(t *testing.T, generate func(func([]byte))) {
    28  	checkObjdumpRISCV64(t)
    29  	testExtDis(t, "gnu", objdump, generate, allowedMismatchObjdump)
    30  	testExtDis(t, "plan9", objdump, generate, allowedMismatchObjdump)
    31  }
    32  
    33  func checkObjdumpRISCV64(t *testing.T) {
    34  	objdumpPath, err := exec.LookPath(objdumpPath)
    35  	if err != nil {
    36  		objdumpPath = "objdump"
    37  	}
    38  	out, err := exec.Command(objdumpPath, "-i").Output()
    39  	if err != nil {
    40  		t.Skipf("cannot run objdump: %v\n%s", err, out)
    41  	}
    42  	if !strings.Contains(string(out), "riscv") {
    43  		t.Skip("objdump does not have RISC-V support")
    44  	}
    45  }
    46  
    47  func objdump(ext *ExtDis) error {
    48  	// File already written with instructions; add ELF header.
    49  	if err := writeELF64(ext.File, ext.Size); err != nil {
    50  		return err
    51  	}
    52  
    53  	b, err := ext.Run(objdumpPath, "-M numeric", "-d", "-z", ext.File.Name())
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	var (
    59  		nmatch  int
    60  		reading bool
    61  		next    uint64 = start
    62  		addr    uint64
    63  		encbuf  [4]byte
    64  		enc     []byte
    65  		text    string
    66  	)
    67  	flush := func() {
    68  		if addr == next {
    69  			// PC-relative addresses are translated to absolute addresses based on PC by GNU objdump
    70  			// Following logical rewrites the absolute addresses back to PC-relative ones for comparing
    71  			// with our disassembler output which are PC-relative
    72  			if text == "undefined" && len(enc) == 4 {
    73  				text = "error: unknown instruction"
    74  				enc = nil
    75  			}
    76  			if len(enc) == 4 {
    77  				// prints as word but we want to record bytes
    78  				enc[0], enc[3] = enc[3], enc[0]
    79  				enc[1], enc[2] = enc[2], enc[1]
    80  			}
    81  			ext.Dec <- ExtInst{addr, encbuf, len(enc), text}
    82  			encbuf = [4]byte{}
    83  			enc = nil
    84  			next += 4
    85  		}
    86  	}
    87  	var textangle = []byte("<.text>:")
    88  	for {
    89  		line, err := b.ReadSlice('\n')
    90  		if err != nil {
    91  			if err == io.EOF {
    92  				break
    93  			}
    94  			return fmt.Errorf("reading objdump output: %v", err)
    95  		}
    96  		if bytes.Contains(line, textangle) {
    97  			reading = true
    98  			continue
    99  		}
   100  		if !reading {
   101  			continue
   102  		}
   103  		if debug {
   104  			os.Stdout.Write(line)
   105  		}
   106  		if enc1 := parseContinuation(line, encbuf[:len(enc)]); enc1 != nil {
   107  			enc = enc1
   108  			continue
   109  		}
   110  		flush()
   111  		nmatch++
   112  		addr, enc, text = parseLine(line, encbuf[:0])
   113  		if addr > next {
   114  			return fmt.Errorf("address out of sync expected <= %#x at %q in:\n%s", next, line, line)
   115  		}
   116  	}
   117  	flush()
   118  	if next != start+uint64(ext.Size) {
   119  		return fmt.Errorf("not enough results found [%d %d]", next, start+ext.Size)
   120  	}
   121  	if err := ext.Wait(); err != nil {
   122  		return fmt.Errorf("exec: %v", err)
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  var (
   129  	undefined     = []byte("undefined")
   130  	unpredictable = []byte("unpredictable")
   131  	slashslash    = []byte("//")
   132  )
   133  
   134  func parseLine(line []byte, encstart []byte) (addr uint64, enc []byte, text string) {
   135  	ok := false
   136  	oline := line
   137  	i := bytes.Index(line, []byte(":\t"))
   138  	if i < 0 {
   139  		log.Fatalf("cannot parse disassembly: %q", oline)
   140  	}
   141  	x, err := strconv.ParseUint(string(bytes.TrimSpace(line[:i])), 16, 32)
   142  	if err != nil {
   143  		log.Fatalf("cannot parse disassembly: %q", oline)
   144  	}
   145  	addr = uint64(x)
   146  	line = line[i+2:]
   147  	i = bytes.IndexByte(line, '\t')
   148  	if i < 0 {
   149  		log.Fatalf("cannot parse disassembly: %q", oline)
   150  	}
   151  	enc, ok = parseHex(line[:i], encstart)
   152  	if !ok {
   153  		log.Fatalf("cannot parse disassembly: %q", oline)
   154  	}
   155  	line = bytes.TrimSpace(line[i:])
   156  	if bytes.Contains(line, undefined) {
   157  		text = "undefined"
   158  		return
   159  	}
   160  	if false && bytes.Contains(line, unpredictable) {
   161  		text = "unpredictable"
   162  		return
   163  	}
   164  	// Strip trailing comment starting with '#'
   165  	if i := bytes.IndexByte(line, '#'); i >= 0 {
   166  		line = bytes.TrimSpace(line[:i])
   167  	}
   168  	// Strip trailing comment starting with "//"
   169  	if i := bytes.Index(line, slashslash); i >= 0 {
   170  		line = bytes.TrimSpace(line[:i])
   171  	}
   172  	text = string(fixSpace(line))
   173  	return
   174  }
   175  
   176  // fixSpace rewrites runs of spaces, tabs, and newline characters into single spaces in s.
   177  // If s must be rewritten, it is rewritten in place.
   178  func fixSpace(s []byte) []byte {
   179  	s = bytes.TrimSpace(s)
   180  	for i := 0; i < len(s); i++ {
   181  		if s[i] == '\t' || s[i] == '\n' || i > 0 && s[i] == ' ' && s[i-1] == ' ' {
   182  			goto Fix
   183  		}
   184  	}
   185  	return s
   186  
   187  Fix:
   188  	b := s
   189  	w := 0
   190  	for i := 0; i < len(s); i++ {
   191  		c := s[i]
   192  		if c == '\t' || c == '\n' {
   193  			c = ' '
   194  		}
   195  		if c == ' ' && w > 0 && b[w-1] == ' ' {
   196  			continue
   197  		}
   198  		b[w] = c
   199  		w++
   200  	}
   201  	if w > 0 && b[w-1] == ' ' {
   202  		w--
   203  	}
   204  	return b[:w]
   205  }
   206  
   207  func parseContinuation(line []byte, enc []byte) []byte {
   208  	i := bytes.Index(line, []byte(":\t"))
   209  	if i < 0 {
   210  		return nil
   211  	}
   212  	line = line[i+1:]
   213  	enc, _ = parseHex(line, enc)
   214  	return enc
   215  }
   216  
   217  // writeELF64 writes an ELF64 header to the file, describing a text
   218  // segment that starts at start (0x8000) and extends for size bytes.
   219  func writeELF64(f *os.File, size int) error {
   220  	f.Seek(0, io.SeekStart)
   221  	var hdr elf.Header64
   222  	var prog elf.Prog64
   223  	var sect elf.Section64
   224  	var buf bytes.Buffer
   225  	binary.Write(&buf, binary.LittleEndian, &hdr)
   226  	off1 := buf.Len()
   227  	binary.Write(&buf, binary.LittleEndian, &prog)
   228  	off2 := buf.Len()
   229  	binary.Write(&buf, binary.LittleEndian, &sect)
   230  	off3 := buf.Len()
   231  	buf.Reset()
   232  	data := byte(elf.ELFDATA2LSB)
   233  	hdr = elf.Header64{
   234  		Ident:     [16]byte{0x7F, 'E', 'L', 'F', 2, data, 1},
   235  		Type:      2,
   236  		Machine:   uint16(elf.EM_RISCV),
   237  		Version:   1,
   238  		Entry:     start,
   239  		Phoff:     uint64(off1),
   240  		Shoff:     uint64(off2),
   241  		Flags:     0x5,
   242  		Ehsize:    uint16(off1),
   243  		Phentsize: uint16(off2 - off1),
   244  		Phnum:     1,
   245  		Shentsize: uint16(off3 - off2),
   246  		Shnum:     4,
   247  		Shstrndx:  3,
   248  	}
   249  	binary.Write(&buf, binary.LittleEndian, &hdr)
   250  	prog = elf.Prog64{
   251  		Type:   1,
   252  		Off:    start,
   253  		Vaddr:  start,
   254  		Paddr:  start,
   255  		Filesz: uint64(size),
   256  		Memsz:  uint64(size),
   257  		Flags:  5,
   258  		Align:  start,
   259  	}
   260  	binary.Write(&buf, binary.LittleEndian, &prog)
   261  	binary.Write(&buf, binary.LittleEndian, &sect) // NULL section
   262  	sect = elf.Section64{
   263  		Name:      1,
   264  		Type:      uint32(elf.SHT_PROGBITS),
   265  		Addr:      start,
   266  		Off:       start,
   267  		Size:      uint64(size),
   268  		Flags:     uint64(elf.SHF_ALLOC | elf.SHF_EXECINSTR),
   269  		Addralign: 4,
   270  	}
   271  	binary.Write(&buf, binary.LittleEndian, &sect) // .text
   272  	strtabsize := len("\x00.text\x00.riscv.attributes\x00.shstrtab\x00")
   273  	// RISC-V objdump needs the .riscv.attributes section to identify
   274  	// the RV64G (not include compressed) extensions.
   275  	sect = elf.Section64{
   276  		Name:      uint32(len("\x00.text\x00")),
   277  		Type:      uint32(0x70000003), // SHT_RISCV_ATTRIBUTES
   278  		Addr:      0,
   279  		Off:       uint64(off2 + (off3-off2)*4 + strtabsize),
   280  		Size:      114,
   281  		Addralign: 1,
   282  	}
   283  	binary.Write(&buf, binary.LittleEndian, &sect)
   284  	sect = elf.Section64{
   285  		Name:      uint32(len("\x00.text\x00.riscv.attributes\x00")),
   286  		Type:      uint32(elf.SHT_STRTAB),
   287  		Addr:      0,
   288  		Off:       uint64(off2 + (off3-off2)*4),
   289  		Size:      uint64(strtabsize),
   290  		Addralign: 1,
   291  	}
   292  	binary.Write(&buf, binary.LittleEndian, &sect)
   293  	buf.WriteString("\x00.text\x00.riscv.attributes\x00.shstrtab\x00")
   294  	// Contents of .riscv.attributes section
   295  	// which specify the extension and priv spec version. (1.11)
   296  	buf.WriteString("Aq\x00\x00\x00riscv\x00\x01g\x00\x00\x00\x05rv64i2p0_m2p0_a2p0_f2p0_d2p0_q2p0_c2p0_zmmul1p0_zfh1p0_zfhmin1p0_zba1p0_zbb1p0_zbc1p0_zbs1p0\x00\x08\x01\x0a\x0b")
   297  	f.Write(buf.Bytes())
   298  	return nil
   299  }