Home | History | Annotate | Download | only in x86asm
      1 // Copyright 2014 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 x86asm
      6 
      7 import (
      8 	"bytes"
      9 	"debug/elf"
     10 	"encoding/binary"
     11 	"fmt"
     12 	"io"
     13 	"log"
     14 	"os"
     15 	"strconv"
     16 	"strings"
     17 	"testing"
     18 )
     19 
     20 // Apologies for the proprietary path, but we need objdump 2.24 + some committed patches that will land in 2.25.
     21 const objdumpPath = "/Users/rsc/bin/objdump2"
     22 
     23 func testObjdump32(t *testing.T, generate func(func([]byte))) {
     24 	testObjdumpArch(t, generate, 32)
     25 }
     26 
     27 func testObjdump64(t *testing.T, generate func(func([]byte))) {
     28 	testObjdumpArch(t, generate, 64)
     29 }
     30 
     31 func testObjdumpArch(t *testing.T, generate func(func([]byte)), arch int) {
     32 	if testing.Short() {
     33 		t.Skip("skipping objdump test in short mode")
     34 	}
     35 
     36 	if _, err := os.Stat(objdumpPath); err != nil {
     37 		t.Fatal(err)
     38 	}
     39 
     40 	testExtDis(t, "gnu", arch, objdump, generate, allowedMismatchObjdump)
     41 }
     42 
     43 func objdump(ext *ExtDis) error {
     44 	// File already written with instructions; add ELF header.
     45 	if ext.Arch == 32 {
     46 		if err := writeELF32(ext.File, ext.Size); err != nil {
     47 			return err
     48 		}
     49 	} else {
     50 		if err := writeELF64(ext.File, ext.Size); err != nil {
     51 			return err
     52 		}
     53 	}
     54 
     55 	b, err := ext.Run(objdumpPath, "-d", "-z", ext.File.Name())
     56 	if err != nil {
     57 		return err
     58 	}
     59 
     60 	var (
     61 		nmatch  int
     62 		reading bool
     63 		next    uint32 = start
     64 		addr    uint32
     65 		encbuf  [32]byte
     66 		enc     []byte
     67 		text    string
     68 	)
     69 	flush := func() {
     70 		if addr == next {
     71 			switch text {
     72 			case "repz":
     73 				text = "rep"
     74 			case "repnz":
     75 				text = "repn"
     76 			default:
     77 				text = strings.Replace(text, "repz ", "rep ", -1)
     78 				text = strings.Replace(text, "repnz ", "repn ", -1)
     79 			}
     80 			if m := pcrelw.FindStringSubmatch(text); m != nil {
     81 				targ, _ := strconv.ParseUint(m[2], 16, 64)
     82 				text = fmt.Sprintf("%s .%+#x", m[1], int16(uint32(targ)-uint32(uint16(addr))-uint32(len(enc))))
     83 			}
     84 			if m := pcrel.FindStringSubmatch(text); m != nil {
     85 				targ, _ := strconv.ParseUint(m[2], 16, 64)
     86 				text = fmt.Sprintf("%s .%+#x", m[1], int32(uint32(targ)-addr-uint32(len(enc))))
     87 			}
     88 			text = strings.Replace(text, "0x0(", "(", -1)
     89 			text = strings.Replace(text, "%st(0)", "%st", -1)
     90 
     91 			ext.Dec <- ExtInst{addr, encbuf, len(enc), text}
     92 			encbuf = [32]byte{}
     93 			enc = nil
     94 			next += 32
     95 		}
     96 	}
     97 	var textangle = []byte("<.text>:")
     98 	for {
     99 		line, err := b.ReadSlice('\n')
    100 		if err != nil {
    101 			if err == io.EOF {
    102 				break
    103 			}
    104 			return fmt.Errorf("reading objdump output: %v", err)
    105 		}
    106 		if bytes.Contains(line, textangle) {
    107 			reading = true
    108 			continue
    109 		}
    110 		if !reading {
    111 			continue
    112 		}
    113 		if debug {
    114 			os.Stdout.Write(line)
    115 		}
    116 		if enc1 := parseContinuation(line, encbuf[:len(enc)]); enc1 != nil {
    117 			enc = enc1
    118 			continue
    119 		}
    120 		flush()
    121 		nmatch++
    122 		addr, enc, text = parseLine(line, encbuf[:0])
    123 		if addr > next {
    124 			return fmt.Errorf("address out of sync expected <= %#x at %q in:\n%s", next, line, line)
    125 		}
    126 	}
    127 	flush()
    128 	if next != start+uint32(ext.Size) {
    129 		return fmt.Errorf("not enough results found [%d %d]", next, start+ext.Size)
    130 	}
    131 	if err := ext.Wait(); err != nil {
    132 		return fmt.Errorf("exec: %v", err)
    133 	}
    134 
    135 	return nil
    136 }
    137 
    138 func parseLine(line []byte, encstart []byte) (addr uint32, enc []byte, text string) {
    139 	oline := line
    140 	i := index(line, ":\t")
    141 	if i < 0 {
    142 		log.Fatalf("cannot parse disassembly: %q", oline)
    143 	}
    144 	x, err := strconv.ParseUint(string(trimSpace(line[:i])), 16, 32)
    145 	if err != nil {
    146 		log.Fatalf("cannot parse disassembly: %q", oline)
    147 	}
    148 	addr = uint32(x)
    149 	line = line[i+2:]
    150 	i = bytes.IndexByte(line, '\t')
    151 	if i < 0 {
    152 		log.Fatalf("cannot parse disassembly: %q", oline)
    153 	}
    154 	enc, ok := parseHex(line[:i], encstart)
    155 	if !ok {
    156 		log.Fatalf("cannot parse disassembly: %q", oline)
    157 	}
    158 	line = trimSpace(line[i:])
    159 	if i := bytes.IndexByte(line, '#'); i >= 0 {
    160 		line = trimSpace(line[:i])
    161 	}
    162 	text = string(fixSpace(line))
    163 	return
    164 }
    165 
    166 func parseContinuation(line []byte, enc []byte) []byte {
    167 	i := index(line, ":\t")
    168 	if i < 0 {
    169 		return nil
    170 	}
    171 	line = line[i+1:]
    172 	enc, _ = parseHex(line, enc)
    173 	return enc
    174 }
    175 
    176 // writeELF32 writes an ELF32 header to the file,
    177 // describing a text segment that starts at start
    178 // and extends for size bytes.
    179 func writeELF32(f *os.File, size int) error {
    180 	f.Seek(0, 0)
    181 	var hdr elf.Header32
    182 	var prog elf.Prog32
    183 	var sect elf.Section32
    184 	var buf bytes.Buffer
    185 	binary.Write(&buf, binary.LittleEndian, &hdr)
    186 	off1 := buf.Len()
    187 	binary.Write(&buf, binary.LittleEndian, &prog)
    188 	off2 := buf.Len()
    189 	binary.Write(&buf, binary.LittleEndian, &sect)
    190 	off3 := buf.Len()
    191 	buf.Reset()
    192 	data := byte(elf.ELFDATA2LSB)
    193 	hdr = elf.Header32{
    194 		Ident:     [16]byte{0x7F, 'E', 'L', 'F', 1, data, 1},
    195 		Type:      2,
    196 		Machine:   uint16(elf.EM_386),
    197 		Version:   1,
    198 		Entry:     start,
    199 		Phoff:     uint32(off1),
    200 		Shoff:     uint32(off2),
    201 		Flags:     0x05000002,
    202 		Ehsize:    uint16(off1),
    203 		Phentsize: uint16(off2 - off1),
    204 		Phnum:     1,
    205 		Shentsize: uint16(off3 - off2),
    206 		Shnum:     3,
    207 		Shstrndx:  2,
    208 	}
    209 	binary.Write(&buf, binary.LittleEndian, &hdr)
    210 	prog = elf.Prog32{
    211 		Type:   1,
    212 		Off:    start,
    213 		Vaddr:  start,
    214 		Paddr:  start,
    215 		Filesz: uint32(size),
    216 		Memsz:  uint32(size),
    217 		Flags:  5,
    218 		Align:  start,
    219 	}
    220 	binary.Write(&buf, binary.LittleEndian, &prog)
    221 	binary.Write(&buf, binary.LittleEndian, &sect) // NULL section
    222 	sect = elf.Section32{
    223 		Name:      1,
    224 		Type:      uint32(elf.SHT_PROGBITS),
    225 		Addr:      start,
    226 		Off:       start,
    227 		Size:      uint32(size),
    228 		Flags:     uint32(elf.SHF_ALLOC | elf.SHF_EXECINSTR),
    229 		Addralign: 4,
    230 	}
    231 	binary.Write(&buf, binary.LittleEndian, &sect) // .text
    232 	sect = elf.Section32{
    233 		Name:      uint32(len("\x00.text\x00")),
    234 		Type:      uint32(elf.SHT_STRTAB),
    235 		Addr:      0,
    236 		Off:       uint32(off2 + (off3-off2)*3),
    237 		Size:      uint32(len("\x00.text\x00.shstrtab\x00")),
    238 		Addralign: 1,
    239 	}
    240 	binary.Write(&buf, binary.LittleEndian, &sect)
    241 	buf.WriteString("\x00.text\x00.shstrtab\x00")
    242 	f.Write(buf.Bytes())
    243 	return nil
    244 }
    245 
    246 // writeELF64 writes an ELF64 header to the file,
    247 // describing a text segment that starts at start
    248 // and extends for size bytes.
    249 func writeELF64(f *os.File, size int) error {
    250 	f.Seek(0, 0)
    251 	var hdr elf.Header64
    252 	var prog elf.Prog64
    253 	var sect elf.Section64
    254 	var buf bytes.Buffer
    255 	binary.Write(&buf, binary.LittleEndian, &hdr)
    256 	off1 := buf.Len()
    257 	binary.Write(&buf, binary.LittleEndian, &prog)
    258 	off2 := buf.Len()
    259 	binary.Write(&buf, binary.LittleEndian, &sect)
    260 	off3 := buf.Len()
    261 	buf.Reset()
    262 	data := byte(elf.ELFDATA2LSB)
    263 	hdr = elf.Header64{
    264 		Ident:     [16]byte{0x7F, 'E', 'L', 'F', 2, data, 1},
    265 		Type:      2,
    266 		Machine:   uint16(elf.EM_X86_64),
    267 		Version:   1,
    268 		Entry:     start,
    269 		Phoff:     uint64(off1),
    270 		Shoff:     uint64(off2),
    271 		Flags:     0x05000002,
    272 		Ehsize:    uint16(off1),
    273 		Phentsize: uint16(off2 - off1),
    274 		Phnum:     1,
    275 		Shentsize: uint16(off3 - off2),
    276 		Shnum:     3,
    277 		Shstrndx:  2,
    278 	}
    279 	binary.Write(&buf, binary.LittleEndian, &hdr)
    280 	prog = elf.Prog64{
    281 		Type:   1,
    282 		Off:    start,
    283 		Vaddr:  start,
    284 		Paddr:  start,
    285 		Filesz: uint64(size),
    286 		Memsz:  uint64(size),
    287 		Flags:  5,
    288 		Align:  start,
    289 	}
    290 	binary.Write(&buf, binary.LittleEndian, &prog)
    291 	binary.Write(&buf, binary.LittleEndian, &sect) // NULL section
    292 	sect = elf.Section64{
    293 		Name:      1,
    294 		Type:      uint32(elf.SHT_PROGBITS),
    295 		Addr:      start,
    296 		Off:       start,
    297 		Size:      uint64(size),
    298 		Flags:     uint64(elf.SHF_ALLOC | elf.SHF_EXECINSTR),
    299 		Addralign: 4,
    300 	}
    301 	binary.Write(&buf, binary.LittleEndian, &sect) // .text
    302 	sect = elf.Section64{
    303 		Name:      uint32(len("\x00.text\x00")),
    304 		Type:      uint32(elf.SHT_STRTAB),
    305 		Addr:      0,
    306 		Off:       uint64(off2 + (off3-off2)*3),
    307 		Size:      uint64(len("\x00.text\x00.shstrtab\x00")),
    308 		Addralign: 1,
    309 	}
    310 	binary.Write(&buf, binary.LittleEndian, &sect)
    311 	buf.WriteString("\x00.text\x00.shstrtab\x00")
    312 	f.Write(buf.Bytes())
    313 	return nil
    314 }
    315