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  }