github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/pkg/report/decompile.go (about) 1 // Copyright 2021 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package report 5 6 import ( 7 "bufio" 8 "bytes" 9 "fmt" 10 "os" 11 "regexp" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/google/syzkaller/pkg/osutil" 17 "github.com/google/syzkaller/sys/targets" 18 ) 19 20 type DecompilerFlagMask uint64 21 22 // Extra flags that control the flow of decompilation. 23 const ( 24 FlagForceArmThumbMode DecompilerFlagMask = 1 << iota 25 ) 26 27 const objdumpCallTimeout = 10 * time.Second 28 29 type DecompiledOpcode struct { 30 Offset int 31 IsBad bool 32 Instruction string 33 FullDescription string 34 } 35 36 // Decompiles a byte array with opcodes into human-readable descriptions. 37 // Target must specify the environment from which the opcodes were taken. 38 func DecompileOpcodes(rawOpcodes []byte, flags DecompilerFlagMask, target *targets.Target) ([]DecompiledOpcode, error) { 39 args, err := objdumpBuildArgs(flags, target) 40 if err != nil { 41 return nil, err 42 } 43 44 outBytes, err := objdumpExecutor(rawOpcodes, args, target) 45 if err != nil { 46 return nil, err 47 } 48 49 list := objdumpParseOutput(outBytes) 50 if len(list) == 0 && len(rawOpcodes) > 0 { 51 return nil, fmt.Errorf("no instructions found while the total size is %v bytes", len(rawOpcodes)) 52 } 53 return list, nil 54 } 55 56 func objdumpExecutor(rawOpcodes []byte, args []string, target *targets.Target) ([]byte, error) { 57 fileName, err := osutil.TempFile("syz-opcode-decompiler") 58 if err != nil { 59 return nil, fmt.Errorf("failed to create temp file: %w", err) 60 } 61 defer os.Remove(fileName) 62 63 err = osutil.WriteFile(fileName, rawOpcodes) 64 if err != nil { 65 return nil, fmt.Errorf("failed to write to temp file: %w", err) 66 } 67 68 return osutil.RunCmd(objdumpCallTimeout, "", target.Objdump, append(args, fileName)...) 69 } 70 71 // nolint: lll 72 var objdumpAsmLineRegexp = regexp.MustCompile(`\s+([a-fA-F0-9]+)\:\s+((?:[a-fA-F0-9]{2,8}\s*)*[a-fA-F0-9]{2,8})\s+(.*?)\s*$`) 73 74 func objdumpParseOutput(rawOutput []byte) []DecompiledOpcode { 75 ret := []DecompiledOpcode{} 76 for s := bufio.NewScanner(bytes.NewReader(rawOutput)); s.Scan(); { 77 result := objdumpAsmLineRegexp.FindStringSubmatch(string(s.Bytes())) 78 if result == nil { 79 continue 80 } 81 offset, err := strconv.ParseUint(result[1], 16, 64) 82 if err != nil { 83 continue 84 } 85 const objdumpBadInstruction = "(bad)" 86 ret = append(ret, DecompiledOpcode{ 87 Offset: int(offset), 88 IsBad: result[3] == objdumpBadInstruction, 89 Instruction: result[3], 90 FullDescription: strings.TrimRight(result[0], " \t"), 91 }) 92 } 93 return ret 94 } 95 96 func objdumpBuildArgs(flags DecompilerFlagMask, target *targets.Target) ([]string, error) { 97 // objdump won't be able to decompile a raw binary file unless we specify the exact 98 // architecture through the -m parameter. 99 ret := []string{"-b", "binary", "-D"} 100 switch target.Arch { 101 case targets.ARM64: 102 ret = append(ret, "-maarch64") 103 case targets.ARM: 104 ret = append(ret, "-marm") 105 if flags&FlagForceArmThumbMode != 0 { 106 ret = append(ret, "-M", "force-thumb") 107 } 108 case targets.I386: 109 ret = append(ret, "-mi386") 110 case targets.AMD64: 111 ret = append(ret, "-mi386", "-Mx86-64") 112 case targets.MIPS64LE: 113 ret = append(ret, "-mmips") 114 case targets.PPC64LE: 115 ret = append(ret, "-mppc") 116 case targets.S390x: 117 ret = append(ret, "-m", "s390:64-bit") 118 case targets.RiscV64: 119 ret = append(ret, "-mriscv") 120 default: 121 return nil, fmt.Errorf("cannot build objdump args for %#v", target.Arch) 122 } 123 124 return ret, nil 125 }