| // Copyright 2014 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. |
| |
| // Copied and simplified from rsc.io/arm/armasm/objdumpext_test.go. |
| |
| package ppc64asm |
| |
| import ( |
| "bytes" |
| "debug/elf" |
| "encoding/binary" |
| "fmt" |
| "io" |
| "log" |
| "os" |
| "os/exec" |
| "runtime" |
| "strconv" |
| "strings" |
| "testing" |
| ) |
| |
| var objdumpPath = "objdump" |
| |
| var objdumpCrossNames = [...]string{"powerpc64-linux-gnu-objdump", "powerpc64le-linux-gnu-objdump"} |
| |
| func testObjdump(t *testing.T, generate func(func([]byte))) { |
| if testing.Short() { |
| t.Skip("skipping objdump test in short mode") |
| } |
| if runtime.GOARCH != "ppc64le" && runtime.GOARCH != "ppc64" { |
| found := false |
| for _, c := range objdumpCrossNames { |
| if _, err := exec.LookPath(c); err == nil { |
| objdumpPath = c |
| found = true |
| break |
| } |
| } |
| if !found { |
| t.Skip("skipping; test requires host tool objdump for ppc64 or ppc64le") |
| } |
| } else if _, err := exec.LookPath(objdumpPath); err != nil { |
| t.Skip(err) |
| } |
| |
| testExtDis(t, "gnu", objdump, generate, allowedMismatchObjdump) |
| } |
| |
| func objdump(ext *ExtDis) error { |
| // File already written with instructions; add ELF header. |
| if err := writeELF64(ext.File, ext.Size); err != nil { |
| return err |
| } |
| |
| b, err := ext.Run(objdumpPath, "-d", "-z", ext.File.Name()) |
| if err != nil { |
| return err |
| } |
| |
| var ( |
| nmatch int |
| reading bool |
| next uint32 = start |
| addr uint32 |
| encbuf [8]byte |
| enc []byte |
| text string |
| ) |
| flush := func() { |
| if addr == next { |
| if m := pcrel.FindStringSubmatch(text); m != nil { |
| targ, _ := strconv.ParseUint(m[2], 16, 64) |
| text = fmt.Sprintf("%s.%+#x", m[1], int32(uint32(targ)-addr)) |
| } |
| if strings.HasPrefix(text, "stmia") { |
| text = "stm" + text[5:] |
| } |
| if strings.HasPrefix(text, "stmfd") { |
| text = "stmdb" + text[5:] |
| } |
| if strings.HasPrefix(text, "ldmfd") { |
| text = "ldm" + text[5:] |
| } |
| text = strings.Replace(text, "#0.0", "#0", -1) |
| if text == "undefined" && len(enc) == 4 { |
| text = "error: unknown instruction" |
| enc = nil |
| } |
| // Prefixed instructions may not decode as expected if |
| // they are an invalid form. Some are tested in decode.txt. |
| // objdump treats these like two instructions. |
| // |
| // Look for primary opcode 1 and advance an exta 4 bytes if |
| // this failed to decode. |
| if strings.HasPrefix(text, ".long") && enc[0]>>2 == 1 { |
| next += 4 |
| } |
| ext.Dec <- ExtInst{addr, encbuf, len(enc), text} |
| encbuf = [8]byte{} |
| next += uint32(len(enc)) |
| enc = nil |
| } |
| } |
| var textangle = []byte("<.text>:") |
| for { |
| line, err := b.ReadSlice('\n') |
| if err != nil { |
| if err == io.EOF { |
| break |
| } |
| return fmt.Errorf("reading objdump output: %v", err) |
| } |
| if bytes.Contains(line, textangle) { |
| reading = true |
| continue |
| } |
| if !reading { |
| continue |
| } |
| if debug { |
| os.Stdout.Write(line) |
| } |
| if enc1 := parseContinuation(line, encbuf[:len(enc)]); enc1 != nil { |
| enc = enc1 |
| continue |
| } |
| flush() |
| nmatch++ |
| addr, enc, text = parseLine(line, encbuf[:0]) |
| if addr > next { |
| return fmt.Errorf("address out of sync expected <= %#x at %q in:\n%s", next, line, line) |
| } |
| } |
| flush() |
| if next != start+uint32(ext.Size) { |
| return fmt.Errorf("not enough results found [%d %d]", next, start+ext.Size) |
| } |
| if err := ext.Wait(); err != nil { |
| return fmt.Errorf("exec: %v", err) |
| } |
| |
| return nil |
| } |
| |
| var ( |
| undefined = []byte("<UNDEFINED>") |
| unpredictable = []byte("<UNPREDICTABLE>") |
| illegalShifter = []byte("<illegal shifter operand>") |
| ) |
| |
| func parseLine(line []byte, encstart []byte) (addr uint32, enc []byte, text string) { |
| oline := line |
| i := index(line, ":\t") |
| if i < 0 { |
| log.Fatalf("cannot parse disassembly: %q", oline) |
| } |
| x, err := strconv.ParseUint(string(trimSpace(line[:i])), 16, 32) |
| if err != nil { |
| log.Fatalf("cannot parse disassembly: %q", oline) |
| } |
| addr = uint32(x) |
| line = line[i+2:] |
| i = bytes.IndexByte(line, '\t') |
| if i < 0 { |
| log.Fatalf("cannot parse disassembly: %q", oline) |
| } |
| enc, ok := parseHex(line[:i], encstart) |
| if !ok { |
| log.Fatalf("cannot parse disassembly: %q", oline) |
| } |
| line = trimSpace(line[i:]) |
| if bytes.Contains(line, undefined) { |
| text = "undefined" |
| return |
| } |
| if bytes.Contains(line, illegalShifter) { |
| text = "undefined" |
| return |
| } |
| if false && bytes.Contains(line, unpredictable) { |
| text = "unpredictable" |
| return |
| } |
| if i := bytes.IndexByte(line, ';'); i >= 0 { |
| line = trimSpace(line[:i]) |
| } |
| text = string(fixSpace(line)) |
| return |
| } |
| |
| func parseContinuation(line []byte, enc []byte) []byte { |
| i := index(line, ":\t") |
| if i < 0 { |
| return nil |
| } |
| line = line[i+1:] |
| enc, _ = parseHex(line, enc) |
| return enc |
| } |
| |
| // writeELF64 writes an ELF64 header to the file, |
| // describing a text segment that starts at start |
| // and extends for size bytes. |
| func writeELF64(f *os.File, size int) error { |
| f.Seek(0, io.SeekStart) |
| var hdr elf.Header64 |
| var prog elf.Prog64 |
| var sect elf.Section64 |
| var buf bytes.Buffer |
| binary.Write(&buf, binary.BigEndian, &hdr) |
| off1 := buf.Len() |
| binary.Write(&buf, binary.BigEndian, &prog) |
| off2 := buf.Len() |
| binary.Write(&buf, binary.BigEndian, §) |
| off3 := buf.Len() |
| buf.Reset() |
| data := byte(elf.ELFDATA2MSB) |
| hdr = elf.Header64{ |
| Ident: [16]byte{0x7F, 'E', 'L', 'F', 2, data, 1}, |
| Type: 2, |
| Machine: uint16(elf.EM_PPC64), |
| Version: 1, |
| Entry: start, |
| Phoff: uint64(off1), |
| Shoff: uint64(off2), |
| Flags: 0x05000002, |
| Ehsize: uint16(off1), |
| Phentsize: uint16(off2 - off1), |
| Phnum: 1, |
| Shentsize: uint16(off3 - off2), |
| Shnum: 3, |
| Shstrndx: 2, |
| } |
| binary.Write(&buf, binary.BigEndian, &hdr) |
| prog = elf.Prog64{ |
| Type: 1, |
| Off: start, |
| Vaddr: start, |
| Paddr: start, |
| Filesz: uint64(size), |
| Memsz: uint64(size), |
| Flags: 5, |
| Align: start, |
| } |
| binary.Write(&buf, binary.BigEndian, &prog) |
| binary.Write(&buf, binary.BigEndian, §) // NULL section |
| sect = elf.Section64{ |
| Name: 1, |
| Type: uint32(elf.SHT_PROGBITS), |
| Addr: start, |
| Off: start, |
| Size: uint64(size), |
| Flags: uint64(elf.SHF_ALLOC | elf.SHF_EXECINSTR), |
| Addralign: 4, |
| } |
| binary.Write(&buf, binary.BigEndian, §) // .text |
| sect = elf.Section64{ |
| Name: uint32(len("\x00.text\x00")), |
| Type: uint32(elf.SHT_STRTAB), |
| Addr: 0, |
| Off: uint64(off2 + (off3-off2)*3), |
| Size: uint64(len("\x00.text\x00.shstrtab\x00")), |
| Addralign: 1, |
| } |
| binary.Write(&buf, binary.BigEndian, §) |
| buf.WriteString("\x00.text\x00.shstrtab\x00") |
| f.Write(buf.Bytes()) |
| return nil |
| } |