github.com/decomp/exp@v0.0.0-20210624183419-6d058f5e1da6/lift/x86/func.go (about)

     1  package x86
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/decomp/exp/bin"
     8  	"github.com/decomp/exp/disasm/x86"
     9  	"github.com/llir/llvm/ir"
    10  	"github.com/llir/llvm/ir/constant"
    11  	"github.com/llir/llvm/ir/enum"
    12  	"github.com/llir/llvm/ir/metadata"
    13  	"github.com/llir/llvm/ir/types"
    14  	"golang.org/x/arch/x86/x86asm"
    15  )
    16  
    17  // A Func is a function lifter.
    18  type Func struct {
    19  	// Output LLVM IR of the function.
    20  	*ir.Func
    21  	// Input assembly of the function.
    22  	AsmFunc *x86.Func
    23  	// Current basic block being generated.
    24  	cur *ir.Block
    25  	// LLVM IR basic blocks of the function.
    26  	blocks map[bin.Address]*ir.Block
    27  	// Registers used within the function.
    28  	regs map[x86asm.Reg]*ir.InstAlloca
    29  	// Status flags used within the function.
    30  	statusFlags map[StatusFlag]*ir.InstAlloca
    31  	// FPU status flags used within the function.
    32  	fstatusFlags map[FStatusFlag]*ir.InstAlloca
    33  	// Local varialbes used within the function.
    34  	locals map[string]*ir.InstAlloca
    35  	// usesEDX_EAX specifies whether any instruction of the function uses
    36  	// EDX:EAX.
    37  	usesEDX_EAX bool
    38  	// usesFPU specifies whether any instruction of the function uses the FPU.
    39  	usesFPU bool
    40  
    41  	// TODO: Move espDisp from Func to BasicBlock, and propagate symbolic
    42  	// execution information through context.json.
    43  
    44  	// ESP disposition; used for shadow stack.
    45  	espDisp int64
    46  
    47  	// FPU register stack top; integer value in range [0, 7].
    48  	st *ir.InstAlloca
    49  
    50  	// Read-only global lifter state.
    51  	l *Lifter
    52  }
    53  
    54  // NewFunc returns a new function lifter based on the input assembly of the
    55  // function.
    56  func (l *Lifter) NewFunc(asmFunc *x86.Func) *Func {
    57  	entry := asmFunc.Addr
    58  	f, ok := l.Funcs[entry]
    59  	if !ok {
    60  		// TODO: Add proper support for type signatures once type analysis has
    61  		// been conducted.
    62  		name := fmt.Sprintf("f_%06X", uint64(entry))
    63  		sig := types.NewFunc(types.Void)
    64  		typ := types.NewPointer(sig)
    65  		f = &Func{
    66  			Func: &ir.Func{
    67  				Typ: typ,
    68  				Sig: sig,
    69  			},
    70  		}
    71  		f.SetName(name)
    72  		md := &metadata.Attachment{
    73  			Name: "addr",
    74  			Node: &metadata.Tuple{
    75  				Fields: []metadata.Field{&metadata.String{Value: entry.String()}},
    76  			},
    77  		}
    78  		f.Metadata = append(f.Metadata, md)
    79  	}
    80  	f.AsmFunc = asmFunc
    81  	f.blocks = make(map[bin.Address]*ir.Block)
    82  	f.regs = make(map[x86asm.Reg]*ir.InstAlloca)
    83  	f.statusFlags = make(map[StatusFlag]*ir.InstAlloca)
    84  	f.fstatusFlags = make(map[FStatusFlag]*ir.InstAlloca)
    85  	f.locals = make(map[string]*ir.InstAlloca)
    86  	f.l = l
    87  	// Prepare output LLVM IR basic blocks.
    88  	for addr := range asmFunc.Blocks {
    89  		label := fmt.Sprintf("block_%06X", uint64(addr))
    90  		block := ir.NewBlock(label)
    91  		f.blocks[addr] = block
    92  	}
    93  	// Preprocess the function to assess if any instruction makes use of EDX:EAX
    94  	// (e.g. IDIV).
    95  	for _, bb := range asmFunc.Blocks {
    96  		for _, inst := range bb.Insts {
    97  			switch inst.Op {
    98  			// TODO: Identify more instructions which makes use of the FPU register
    99  			// stack.
   100  			case x86asm.F2XM1, x86asm.FABS, x86asm.FADD, x86asm.FADDP, x86asm.FBLD,
   101  				x86asm.FBSTP, x86asm.FCHS, x86asm.FCMOVB, x86asm.FCMOVBE,
   102  				x86asm.FCMOVE, x86asm.FCMOVNB, x86asm.FCMOVNBE, x86asm.FCMOVNE,
   103  				x86asm.FCMOVNU, x86asm.FCMOVU, x86asm.FCOM, x86asm.FCOMI,
   104  				x86asm.FCOMIP, x86asm.FCOMP, x86asm.FCOMPP, x86asm.FCOS,
   105  				x86asm.FDECSTP, x86asm.FDIV, x86asm.FDIVP, x86asm.FDIVR, x86asm.FDIVRP,
   106  				x86asm.FFREE, x86asm.FFREEP, x86asm.FIADD, x86asm.FICOM, x86asm.FICOMP,
   107  				x86asm.FIDIV, x86asm.FIDIVR, x86asm.FILD, x86asm.FIMUL, x86asm.FINCSTP,
   108  				x86asm.FIST, x86asm.FISTP, x86asm.FISTTP, x86asm.FISUB, x86asm.FISUBR,
   109  				x86asm.FLD, x86asm.FLD1, x86asm.FLDCW, x86asm.FLDENV, x86asm.FLDL2E,
   110  				x86asm.FLDL2T, x86asm.FLDLG2, x86asm.FLDLN2, x86asm.FLDPI, x86asm.FLDZ,
   111  				x86asm.FMUL, x86asm.FMULP, x86asm.FNCLEX, x86asm.FNINIT, x86asm.FNOP,
   112  				x86asm.FNSAVE, x86asm.FNSTCW, x86asm.FNSTENV, x86asm.FNSTSW,
   113  				x86asm.FPATAN, x86asm.FPREM, x86asm.FPREM1, x86asm.FPTAN,
   114  				x86asm.FRNDINT, x86asm.FRSTOR, x86asm.FSCALE, x86asm.FSIN,
   115  				x86asm.FSINCOS, x86asm.FSQRT, x86asm.FST, x86asm.FSTP, x86asm.FSUB,
   116  				x86asm.FSUBP, x86asm.FSUBR, x86asm.FSUBRP, x86asm.FTST, x86asm.FUCOM,
   117  				x86asm.FUCOMI, x86asm.FUCOMIP, x86asm.FUCOMP, x86asm.FUCOMPP,
   118  				x86asm.FWAIT, x86asm.FXAM, x86asm.FXCH, x86asm.FXRSTOR,
   119  				x86asm.FXRSTOR64, x86asm.FXSAVE, x86asm.FXSAVE64, x86asm.FXTRACT,
   120  				x86asm.FYL2X, x86asm.FYL2XP1:
   121  				f.usesFPU = true
   122  			// TODO: Identify more instructions which makes use of EDX:EAX.
   123  			case x86asm.IDIV:
   124  				f.usesEDX_EAX = true
   125  			}
   126  		}
   127  	}
   128  	return f
   129  }
   130  
   131  // Lift lifts the function from input assembly to LLVM IR.
   132  func (f *Func) Lift() {
   133  	dbg.Printf("lifting function %q at %v", f.Ident(), f.AsmFunc.Addr)
   134  	// Allocate a local variable for the FPU stack top used within the function.
   135  	if f.usesFPU {
   136  		v := ir.NewAlloca(types.I8)
   137  		v.SetName("st")
   138  		f.st = v
   139  	}
   140  	var blockAddrs bin.Addresses
   141  	for blockAddr := range f.AsmFunc.Blocks {
   142  		blockAddrs = append(blockAddrs, blockAddr)
   143  	}
   144  	sort.Sort(blockAddrs)
   145  	if len(blockAddrs) == 0 {
   146  		panic(fmt.Errorf("invalid function definition at %v; missing function body", f.AsmFunc.Addr))
   147  	}
   148  	for _, blockAddr := range blockAddrs {
   149  		bb := f.AsmFunc.Blocks[blockAddr]
   150  		f.liftBlock(bb)
   151  	}
   152  	// Add new entry basic block to define registers, status flags, and local
   153  	// variables (allocated on the stack) used within the function.
   154  	if len(f.regs) > 0 || len(f.statusFlags) > 0 || len(f.fstatusFlags) > 0 || f.usesFPU || len(f.locals) > 0 {
   155  		entry := &ir.Block{}
   156  		// Allocate local variables for each register used within the function.
   157  		for reg := x86.FirstReg; reg <= x86.LastReg; reg++ {
   158  			if inst, ok := f.regs[reg]; ok {
   159  				entry.Insts = append(entry.Insts, inst)
   160  			}
   161  		}
   162  		// Allocate local variables for the FPU register stack used within the
   163  		// function.
   164  		if f.usesFPU {
   165  			entry.Insts = append(entry.Insts, f.st)
   166  			seven := constant.NewInt(types.I8, 7)
   167  			entry.NewStore(seven, f.st)
   168  		}
   169  		// Allocate local variables for each status flag used within the function.
   170  		for status := firstStatusFlag; status <= lastStatusFlag; status++ {
   171  			if inst, ok := f.statusFlags[status]; ok {
   172  				entry.Insts = append(entry.Insts, inst)
   173  			}
   174  		}
   175  		// Allocate local variables for each FPU status flag used within the
   176  		// function.
   177  		for fstatus := fpuFirstStatusFlag; fstatus <= fpuLastStatusFlag; fstatus++ {
   178  			if inst, ok := f.fstatusFlags[fstatus]; ok {
   179  				entry.Insts = append(entry.Insts, inst)
   180  			}
   181  		}
   182  		// Allocate local variables for each local variable used within the
   183  		// function.
   184  		var names []string
   185  		for name := range f.locals {
   186  			names = append(names, name)
   187  		}
   188  		sort.Strings(names)
   189  		for _, name := range names {
   190  			inst := f.locals[name]
   191  			entry.Insts = append(entry.Insts, inst)
   192  		}
   193  		// Handle calling conventions.
   194  		f.cur = entry
   195  
   196  		f.espDisp = 8
   197  		for i := range f.Params {
   198  			// Use parameter in register.
   199  			switch f.CallingConv {
   200  			case enum.CallingConvX86FastCall:
   201  				switch i {
   202  				case 0:
   203  					continue
   204  				case 1:
   205  					continue
   206  				}
   207  			default:
   208  				// TODO: Add support for more calling conventions.
   209  			}
   210  			name := fmt.Sprintf("esp_%d", f.espDisp)
   211  			if _, ok := f.locals[name]; !ok {
   212  				inst := ir.NewAlloca(types.I32)
   213  				inst.SetName(name)
   214  				entry.Insts = append(entry.Insts, inst)
   215  			}
   216  			f.espDisp += 4
   217  		}
   218  
   219  		// TODO: Initialize parameter initialization in entry block prior to basic
   220  		// block translation. Move this code to before f.translateBlock, and remove
   221  		// f.espDisp = 0.
   222  		f.espDisp = 0
   223  		disp := int64(8)
   224  		for i, param := range f.Params {
   225  			// Use parameter in register.
   226  			switch f.CallingConv {
   227  			case enum.CallingConvX86FastCall:
   228  				switch i {
   229  				case 0:
   230  					f.defReg(x86.ECX, param)
   231  					continue
   232  				case 1:
   233  					f.defReg(x86.EDX, param)
   234  					continue
   235  				}
   236  			default:
   237  				// TODO: Add support for more calling conventions.
   238  			}
   239  			// Use parameter on stack.
   240  			m := x86asm.Mem{
   241  				Base: x86asm.ESP,
   242  				Disp: disp,
   243  			}
   244  			disp += 4
   245  			mem := x86.NewMem(m, nil)
   246  			f.defMem(mem, param)
   247  		}
   248  		target := f.Blocks[0]
   249  		entry.NewBr(target)
   250  		f.Blocks = append([]*ir.Block{entry}, f.Blocks...)
   251  	}
   252  }
   253  
   254  // liftBlock lifts the basic block from input assembly to LLVM IR.
   255  func (f *Func) liftBlock(bb *x86.BasicBlock) {
   256  	dbg.Printf("lifting basic block at %v", bb.Addr)
   257  	f.cur = f.blocks[bb.Addr]
   258  	f.Blocks = append(f.Blocks, f.cur)
   259  	for _, inst := range bb.Insts {
   260  		f.liftInst(inst)
   261  	}
   262  	f.liftTerm(bb.Term)
   263  }