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