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 }