github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/cover/canonicalizer.go (about)

     1  // Copyright 2023 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 cover
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  
    10  	"github.com/google/syzkaller/pkg/log"
    11  	"github.com/google/syzkaller/pkg/vminfo"
    12  )
    13  
    14  type Canonicalizer struct {
    15  	// Map of modules stored as module name:kernel module.
    16  	modules map[string]*vminfo.KernelModule
    17  
    18  	// Contains a sorted list of the canonical module addresses.
    19  	moduleKeys []uint64
    20  }
    21  
    22  type CanonicalizerInstance struct {
    23  	canonical Canonicalizer
    24  
    25  	// Contains the canonicalize and decanonicalize conversion maps.
    26  	canonicalize   *Convert
    27  	decanonicalize *Convert
    28  }
    29  
    30  // Contains the current conversion maps used.
    31  type Convert struct {
    32  	conversionHash map[uint64]*canonicalizerModule
    33  	moduleKeys     []uint64
    34  }
    35  
    36  type convertContext struct {
    37  	errCount int
    38  	errPC    uint64
    39  	convert  *Convert
    40  }
    41  
    42  // Contains the offset and final address of each module.
    43  type canonicalizerModule struct {
    44  	offset  int64
    45  	name    string
    46  	endAddr uint64
    47  	// Discard coverage from current module.
    48  	// Set to true if module is not present in canonical.
    49  	discard bool
    50  }
    51  
    52  func NewCanonicalizer(modules []*vminfo.KernelModule, flagSignal bool) *Canonicalizer {
    53  	// Return if not using canonicalization.
    54  	if len(modules) == 0 || !flagSignal {
    55  		return &Canonicalizer{}
    56  	}
    57  	// Create a map of canonical module offsets by name.
    58  	canonicalModules := make(map[string]*vminfo.KernelModule)
    59  	for _, module := range modules {
    60  		canonicalModules[module.Name] = module
    61  	}
    62  
    63  	// Store sorted canonical address keys.
    64  	canonicalModuleKeys := make([]uint64, len(modules))
    65  	setModuleKeys(canonicalModuleKeys, modules)
    66  	return &Canonicalizer{
    67  		modules:    canonicalModules,
    68  		moduleKeys: canonicalModuleKeys,
    69  	}
    70  }
    71  
    72  func (can *Canonicalizer) NewInstance(modules []*vminfo.KernelModule) *CanonicalizerInstance {
    73  	if can.moduleKeys == nil {
    74  		return &CanonicalizerInstance{}
    75  	}
    76  	// Save sorted list of module offsets.
    77  	moduleKeys := make([]uint64, len(modules))
    78  	setModuleKeys(moduleKeys, modules)
    79  
    80  	// Create a hash between the "canonical" module addresses and each VM instance.
    81  	instToCanonicalMap := make(map[uint64]*canonicalizerModule)
    82  	canonicalToInstMap := make(map[uint64]*canonicalizerModule)
    83  	for _, module := range modules {
    84  		discard := false
    85  		canonicalAddr := uint64(0)
    86  		canonicalModule, found := can.modules[module.Name]
    87  		if !found || canonicalModule.Size != module.Size {
    88  			log.Errorf("kernel build has changed; instance module %v differs from canonical", module.Name)
    89  			discard = true
    90  		}
    91  		if found {
    92  			canonicalAddr = canonicalModule.Addr
    93  		}
    94  
    95  		instAddr := module.Addr
    96  
    97  		canonicalToInstMap[canonicalAddr] = &canonicalizerModule{
    98  			offset:  int64(instAddr - canonicalAddr),
    99  			name:    module.Name,
   100  			endAddr: module.Size + canonicalAddr,
   101  			discard: discard,
   102  		}
   103  
   104  		instToCanonicalMap[instAddr] = &canonicalizerModule{
   105  			offset:  int64(canonicalAddr - instAddr),
   106  			name:    module.Name,
   107  			endAddr: module.Size + instAddr,
   108  			discard: discard,
   109  		}
   110  	}
   111  
   112  	return &CanonicalizerInstance{
   113  		canonical: *can,
   114  		canonicalize: &Convert{
   115  			conversionHash: instToCanonicalMap,
   116  			moduleKeys:     moduleKeys,
   117  		},
   118  		decanonicalize: &Convert{
   119  			conversionHash: canonicalToInstMap,
   120  			moduleKeys:     can.moduleKeys,
   121  		},
   122  	}
   123  }
   124  
   125  func (ci *CanonicalizerInstance) Canonicalize(elems []uint64) []uint64 {
   126  	return ci.canonicalize.convertPCs(elems)
   127  }
   128  
   129  func (ci *CanonicalizerInstance) Decanonicalize(elems []uint64) []uint64 {
   130  	return ci.decanonicalize.convertPCs(elems)
   131  }
   132  
   133  // Store sorted list of addresses. Used to binary search when converting PCs.
   134  func setModuleKeys(moduleKeys []uint64, modules []*vminfo.KernelModule) {
   135  	for idx, module := range modules {
   136  		moduleKeys[idx] = module.Addr
   137  	}
   138  
   139  	// Sort modules by address.
   140  	sort.Slice(moduleKeys, func(i, j int) bool { return moduleKeys[i] < moduleKeys[j] })
   141  }
   142  
   143  func findModule(pc uint64, moduleKeys []uint64) (moduleIdx int) {
   144  	moduleIdx, _ = sort.Find(len(moduleKeys), func(moduleIdx int) int {
   145  		if pc < moduleKeys[moduleIdx] {
   146  			return -1
   147  		}
   148  		return +1
   149  	})
   150  	// Sort.Find returns the index above the correct module.
   151  	return moduleIdx - 1
   152  }
   153  
   154  func (convert *Convert) convertPCs(pcs []uint64) []uint64 {
   155  	if convert == nil {
   156  		return pcs
   157  	}
   158  	var ret []uint64
   159  	convCtx := &convertContext{convert: convert}
   160  	for _, pc := range pcs {
   161  		if newPC, ok := convert.convertPC(pc); ok {
   162  			ret = append(ret, newPC)
   163  		} else {
   164  			convCtx.discard(pc)
   165  		}
   166  	}
   167  	if msg := convCtx.discarded(); msg != "" {
   168  		log.Logf(4, "error in PC/signal conversion: %v", msg)
   169  	}
   170  	return ret
   171  }
   172  
   173  func (convert *Convert) convertPC(pc uint64) (uint64, bool) {
   174  	moduleIdx := findModule(pc, convert.moduleKeys)
   175  	// Check if address is above the first module offset.
   176  	if moduleIdx >= 0 {
   177  		module, found := convert.conversionHash[convert.moduleKeys[moduleIdx]]
   178  		if !found {
   179  			return pc, false
   180  		}
   181  		// If the address is within the found module add the offset.
   182  		if pc < module.endAddr {
   183  			if module.discard {
   184  				return pc, false
   185  			}
   186  			if module.name != "" {
   187  				pc = uint64(int64(pc) + module.offset)
   188  			}
   189  		}
   190  	}
   191  	return pc, true
   192  }
   193  
   194  func (cc *convertContext) discarded() string {
   195  	if cc.errCount == 0 {
   196  		return ""
   197  	}
   198  	errMsg := fmt.Sprintf("discarded 0x%x (and %v other PCs) during conversion", cc.errPC, cc.errCount)
   199  	return fmt.Sprintf("%v; not found in module map", errMsg)
   200  }
   201  
   202  func (cc *convertContext) discard(pc uint64) {
   203  	cc.errCount += 1
   204  	if cc.errPC == 0 {
   205  		cc.errPC = pc
   206  	}
   207  }