| // Copyright 2017 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 ../../arm/armasm/objdumpext_test.go. |
| |
| package arm64asm |
| |
| import ( |
| "bytes" |
| "debug/elf" |
| "encoding/binary" |
| "fmt" |
| "io" |
| "log" |
| "os" |
| "os/exec" |
| "strconv" |
| "strings" |
| "testing" |
| ) |
| |
| const objdumpPath = "/usr/bin/objdump" |
| |
| func testObjdumpARM64(t *testing.T, generate func(func([]byte))) { |
| testObjdumpArch(t, generate, ModeARM64) |
| } |
| |
| func testObjdumpArch(t *testing.T, generate func(func([]byte)), arch Mode) { |
| checkObjdumpAarch64(t) |
| testExtDis(t, "gnu", arch, objdump, generate, allowedMismatchObjdump) |
| testExtDis(t, "plan9", arch, objdump, generate, allowedMismatchObjdump) |
| } |
| |
| func checkObjdumpAarch64(t *testing.T) { |
| out, err := exec.Command(objdumpPath, "-i").Output() |
| if err != nil { |
| t.Skipf("cannot run objdump: %v\n%s", err, out) |
| } |
| if !strings.Contains(string(out), "aarch64") { |
| t.Skip("objdump does not have aarch64 support") |
| } |
| } |
| |
| func objdump(ext *ExtDis) error { |
| // File already written with instructions; add ELF header. |
| if ext.Arch == ModeARM64 { |
| if err := writeELF64(ext.File, ext.Size); err != nil { |
| return err |
| } |
| } else { |
| panic("unknown arch") |
| } |
| |
| b, err := ext.Run(objdumpPath, "-d", "-z", ext.File.Name()) |
| if err != nil { |
| return err |
| } |
| |
| var ( |
| nmatch int |
| reading bool |
| next uint64 = start |
| addr uint64 |
| encbuf [4]byte |
| enc []byte |
| text string |
| ) |
| flush := func() { |
| if addr == next { |
| // PC-relative addresses are translated to absolute addresses based on PC by GNU objdump |
| // Following logical rewrites the absolute addresses back to PC-relative ones for comparing |
| // with our disassembler output which are PC-relative |
| |
| if m := pcrelprfmim.FindStringSubmatch(text); m != nil { |
| targ, _ := strconv.ParseUint(m[2], 16, 64) |
| text = fmt.Sprintf("%s .%+#x", m[1], uint64(targ-uint64(addr))) |
| } |
| if m := pcrelprfm.FindStringSubmatch(text); m != nil { |
| targ, _ := strconv.ParseUint(m[2], 16, 64) |
| text = fmt.Sprintf("%s .%+#x", m[1], uint64(targ-uint64(addr))) |
| } |
| if m := pcrelim.FindStringSubmatch(text); m != nil { |
| targ, _ := strconv.ParseUint(m[2], 16, 64) |
| text = fmt.Sprintf("%s .%+#x", m[1], uint64(targ-uint64(addr))) |
| } |
| if m := pcrelimzr.FindStringSubmatch(text); m != nil { |
| targ, _ := strconv.ParseUint(m[2], 16, 64) |
| text = fmt.Sprintf("%s .%+#x", m[1], uint64(targ-uint64(addr))) |
| } |
| if m := pcrelr.FindStringSubmatch(text); m != nil { |
| targ, _ := strconv.ParseUint(m[2], 16, 64) |
| if strings.Contains(m[1], "adrp") { |
| text = fmt.Sprintf("%s .%+#x", m[1], uint64(targ-uint64(addr&0xfffff000))) |
| } else { |
| text = fmt.Sprintf("%s .%+#x", m[1], uint64(targ-uint64(addr))) |
| } |
| } |
| if m := pcrelrzr.FindStringSubmatch(text); m != nil { |
| targ, _ := strconv.ParseUint(m[2], 16, 64) |
| if strings.Contains(m[1], "adrp") { |
| text = fmt.Sprintf("%s .%+#x", m[1], uint64(targ-uint64(addr&0xfffff000))) |
| } else { |
| text = fmt.Sprintf("%s .%+#x", m[1], uint64(targ-uint64(addr))) |
| } |
| } |
| if m := pcrel.FindStringSubmatch(text); m != nil { |
| targ, _ := strconv.ParseUint(m[2], 16, 64) |
| text = fmt.Sprintf("%s .%+#x", m[1], uint64(targ-uint64(addr))) |
| } |
| if strings.HasPrefix(text, "mov") && strings.Contains(text, "//") { |
| s := strings.Split(text, " //") |
| text = s[0] |
| } |
| text = strings.Replace(text, "#0.0", "#0", -1) |
| if text == "undefined" && len(enc) == 4 { |
| text = "error: unknown instruction" |
| enc = nil |
| } |
| if len(enc) == 4 { |
| // prints as word but we want to record bytes |
| enc[0], enc[3] = enc[3], enc[0] |
| enc[1], enc[2] = enc[2], enc[1] |
| } |
| ext.Dec <- ExtInst{addr, encbuf, len(enc), text} |
| encbuf = [4]byte{} |
| enc = nil |
| next += 4 |
| } |
| } |
| 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+uint64(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") |
| slashslash = []byte("//") |
| ) |
| |
| func parseLine(line []byte, encstart []byte) (addr uint64, enc []byte, text string) { |
| ok := false |
| oline := line |
| i := index(line, ":\t") |
| if i < 0 { |
| log.Fatalf("cannot parse disassembly: %q", oline) |
| } |
| x, err := strconv.ParseUint(string(bytes.TrimSpace(line[:i])), 16, 32) |
| if err != nil { |
| log.Fatalf("cannot parse disassembly: %q", oline) |
| } |
| addr = uint64(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 = bytes.TrimSpace(line[i:]) |
| if bytes.Contains(line, undefined) { |
| text = "undefined" |
| return |
| } |
| if false && bytes.Contains(line, unpredictable) { |
| text = "unpredictable" |
| return |
| } |
| // Strip trailing comment starting with ';' |
| // e.g: "csinv x23, x2, x19, cc ; xxx" |
| if i := bytes.IndexByte(line, ';'); i >= 0 { |
| line = bytes.TrimSpace(line[:i]) |
| } |
| // Strip trailing comment starting with "//" |
| // e.g: "fccmpe s2, s9, #0x7, ne // xxx" |
| if i := bytes.Index(line, slashslash); i >= 0 { |
| line = bytes.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 (0x8000) 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.LittleEndian, &hdr) |
| off1 := buf.Len() |
| binary.Write(&buf, binary.LittleEndian, &prog) |
| off2 := buf.Len() |
| binary.Write(&buf, binary.LittleEndian, §) |
| off3 := buf.Len() |
| buf.Reset() |
| data := byte(elf.ELFDATA2LSB) |
| hdr = elf.Header64{ |
| Ident: [16]byte{0x7F, 'E', 'L', 'F', 2, data, 1}, |
| Type: 2, |
| Machine: uint16(elf.EM_AARCH64), |
| 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.LittleEndian, &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.LittleEndian, &prog) |
| binary.Write(&buf, binary.LittleEndian, §) // 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.LittleEndian, §) // .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.LittleEndian, §) |
| buf.WriteString("\x00.text\x00.shstrtab\x00") |
| f.Write(buf.Bytes()) |
| return nil |
| } |