github.com/decomp/exp@v0.0.0-20210624183419-6d058f5e1da6/cmd/bin2asm/dump.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  	"text/tabwriter"
    10  	"text/template"
    11  	"unicode"
    12  
    13  	"github.com/mewrev/pe"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  // dumpMainAsm dumps the main.asm file of the executable.
    18  func dumpMainAsm(file *pe.File, hasOverlay bool) error {
    19  	t, err := parseTemplate("main.asm.tmpl")
    20  	if err != nil {
    21  		return errors.WithStack(err)
    22  	}
    23  	sectHdrs, err := file.SectHeaders()
    24  	if err != nil {
    25  		return errors.WithStack(err)
    26  	}
    27  	var data []string
    28  	for _, sectHdr := range sectHdrs {
    29  		sectName := underline(sectHdr.Name)
    30  		data = append(data, sectName)
    31  	}
    32  	if hasOverlay {
    33  		data = append(data, "overlay")
    34  	}
    35  	// Store output.
    36  	if err := writeFile(t, "main.asm", data); err != nil {
    37  		return errors.WithStack(err)
    38  	}
    39  	return nil
    40  }
    41  
    42  // dumpPEHeaderAsm dumps the pe-hdr.asm file of the executable.
    43  func dumpPEHeaderAsm(file *pe.File) error {
    44  	t, err := parseTemplate("pe-hdr.asm.tmpl")
    45  	if err != nil {
    46  		return errors.WithStack(err)
    47  	}
    48  	optHdr, err := file.OptHeader()
    49  	if err != nil {
    50  		return errors.WithStack(err)
    51  	}
    52  	dosHdr, err := file.DOSHeader()
    53  	if err != nil {
    54  		return errors.WithStack(err)
    55  	}
    56  	dosStub, err := file.DOSStub()
    57  	if err != nil {
    58  		return errors.WithStack(err)
    59  	}
    60  	fileHdr, err := file.FileHeader()
    61  	if err != nil {
    62  		return errors.WithStack(err)
    63  	}
    64  	sectAlignKB := optHdr.SectAlign / 1024
    65  	sectHdrs, err := file.SectHeaders()
    66  	if err != nil {
    67  		return errors.WithStack(err)
    68  	}
    69  	// Calculate size of sections containing data.
    70  	var dataSizes []string
    71  	for _, sectHdr := range sectHdrs {
    72  		if sectHdr.Flags&pe.SectFlagData == 0 {
    73  			// Ignore non-data sections.
    74  			continue
    75  		}
    76  		if sectHdr.Size < sectHdr.VirtSize {
    77  			// Section contains uninitialized data.
    78  			dataSize := fmt.Sprintf("%s_vsize", underline(sectHdr.Name))
    79  			dataSizes = append(dataSizes, dataSize)
    80  		} else {
    81  			// Section is padded.
    82  			dataSize := fmt.Sprintf("%s_size", underline(sectHdr.Name))
    83  			dataSizes = append(dataSizes, dataSize)
    84  		}
    85  	}
    86  	// Store output.
    87  	data := map[string]interface{}{
    88  		"OptHdr":      optHdr,
    89  		"DosHdr":      dosHdr,
    90  		"DOSStub":     dosStub,
    91  		"FileHdr":     fileHdr,
    92  		"SectAlignKB": sectAlignKB,
    93  		"SectHdrs":    sectHdrs,
    94  		"DataSizes":   strings.Join(dataSizes, " + "),
    95  		"DataDirs":    optHdr.DataDirs,
    96  	}
    97  	if err := writeFile(t, "pe-hdr.asm", data); err != nil {
    98  		return errors.WithStack(err)
    99  	}
   100  	return nil
   101  }
   102  
   103  // ### [ Helper functions ] ####################################################
   104  
   105  // writeFile applies a parsed template to the specified data object, writing the
   106  // output to the specified file.
   107  func writeFile(t *template.Template, filename string, data interface{}) error {
   108  	path := filepath.Join(outDir, filename)
   109  	dbg.Printf("creating %q\n", path)
   110  	f, err := os.Create(path)
   111  	if err != nil {
   112  		return errors.WithStack(err)
   113  	}
   114  	defer f.Close()
   115  	bw := bufio.NewWriter(f)
   116  	defer bw.Flush()
   117  	tw := tabwriter.NewWriter(bw, 1, 8, 1, ' ', 0)
   118  	if err := t.Execute(tw, data); err != nil {
   119  		return errors.WithStack(err)
   120  	}
   121  	return nil
   122  }
   123  
   124  // parseTemplate parses and returns the given template.
   125  func parseTemplate(filename string) (*template.Template, error) {
   126  	funcMap := map[string]interface{}{
   127  		"isprint":   isPrint,
   128  		"underline": underline,
   129  		"nameArray": nameArray,
   130  		"ui16":      ui16,
   131  		"ui32":      ui32,
   132  	}
   133  	path := filepath.Join(bin2asmDir, filename)
   134  	t, err := template.New(filename).Funcs(funcMap).ParseFiles(path)
   135  	if err != nil {
   136  		return nil, errors.WithStack(err)
   137  	}
   138  	return t, nil
   139  }
   140  
   141  // isPrint reports if the given byte is printable.
   142  func isPrint(b byte) bool {
   143  	if b >= 0x7F {
   144  		return false
   145  	}
   146  	return unicode.IsPrint(rune(b))
   147  }
   148  
   149  // underline replaces dot characters in the given string with underscore
   150  // characters.
   151  func underline(s string) string {
   152  	return strings.Replace(s, ".", "_", -1)
   153  }
   154  
   155  // nameArray converts the given string to a byte array of 8 characters, pretty-
   156  // printed in NASM syntax.
   157  func nameArray(s string) string {
   158  	out := fmt.Sprintf("%q", s)
   159  	for i := len(s); i < 8; i++ {
   160  		out += ", 0x00"
   161  	}
   162  	return out
   163  }
   164  
   165  // ui16 converts x to a 16-bit unsigned integer.
   166  func ui16(x interface{}) uint16 {
   167  	switch x := x.(type) {
   168  	case pe.Arch:
   169  		return uint16(x)
   170  	case pe.DLLFlag:
   171  		return uint16(x)
   172  	case pe.Flag:
   173  		return uint16(x)
   174  	case pe.OptState:
   175  		return uint16(x)
   176  	case pe.Subsystem:
   177  		return uint16(x)
   178  	default:
   179  		panic(fmt.Errorf("main.ui16: support for type %T not yet implemented", x))
   180  	}
   181  }
   182  
   183  // ui32 converts x to a 32-bit unsigned integer.
   184  func ui32(x interface{}) uint32 {
   185  	switch x := x.(type) {
   186  	case pe.SectFlag:
   187  		return uint32(x)
   188  	case pe.Time:
   189  		return uint32(x)
   190  	default:
   191  		panic(fmt.Errorf("main.ui32: support for type %T not yet implemented", x))
   192  	}
   193  }