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, §) 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, §) // 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, §) // .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, §) 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, §) 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 }