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

     1  // Copyright 2024 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 rpcserver
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"sort"
    10  	"time"
    11  
    12  	"github.com/google/syzkaller/pkg/report"
    13  	"github.com/google/syzkaller/prog"
    14  )
    15  
    16  // LastExecuting keeps the given number of last executed programs
    17  // for each proc in a VM, and allows to query this set after a crash.
    18  type LastExecuting struct {
    19  	count     int
    20  	procs     []ExecRecord
    21  	hanged    []ExecRecord // hanged programs, kept forever
    22  	positions []int
    23  }
    24  
    25  type ExecRecord struct {
    26  	ID   int
    27  	Proc int
    28  	Prog []byte
    29  	Time time.Duration
    30  }
    31  
    32  func MakeLastExecuting(procs, count int) *LastExecuting {
    33  	return &LastExecuting{
    34  		count:     count,
    35  		procs:     make([]ExecRecord, procs*count),
    36  		positions: make([]int, procs),
    37  	}
    38  }
    39  
    40  // Note execution of the 'prog' on 'proc' at time 'now'.
    41  func (last *LastExecuting) Note(id, proc int, progData []byte, now time.Duration) {
    42  	pos := &last.positions[proc]
    43  	last.procs[proc*last.count+*pos] = ExecRecord{
    44  		ID:   id,
    45  		Proc: proc,
    46  		Prog: progData,
    47  		Time: now,
    48  	}
    49  	*pos++
    50  	if *pos == last.count {
    51  		*pos = 0
    52  	}
    53  }
    54  
    55  // Note a hanged program.
    56  func (last *LastExecuting) Hanged(id, proc int, progData []byte, now time.Duration) {
    57  	last.hanged = append(last.hanged, ExecRecord{
    58  		ID: id,
    59  		// Use unique proc for these programs b/c pkg/repro will either use the program with matching ID,
    60  		// of take the last program from each proc, and we want the hanged programs to be included.
    61  		Proc: prog.MaxPids + len(last.hanged),
    62  		Prog: progData,
    63  		Time: now,
    64  	})
    65  }
    66  
    67  // Returns a sorted set of last executing programs.
    68  // The records are sorted by time in ascending order.
    69  // ExecRecord.Time is the difference in start executing time between this
    70  // program and the program that started executing last.
    71  func (last *LastExecuting) Collect() []ExecRecord {
    72  	procs := append(last.procs, last.hanged...)
    73  	last.procs = nil // The type must not be used after this.
    74  	last.hanged = nil
    75  	sort.Slice(procs, func(i, j int) bool {
    76  		return procs[i].Time < procs[j].Time
    77  	})
    78  	max := procs[len(procs)-1].Time
    79  	for i := len(procs) - 1; i >= 0; i-- {
    80  		if procs[i].Time == 0 {
    81  			procs = procs[i+1:]
    82  			break
    83  		}
    84  		procs[i].Time = max - procs[i].Time
    85  	}
    86  	return procs
    87  }
    88  
    89  func PrependExecuting(rep *report.Report, lastExec []ExecRecord) {
    90  	buf := new(bytes.Buffer)
    91  	fmt.Fprintf(buf, "last executing test programs:\n\n")
    92  	for _, exec := range lastExec {
    93  		fmt.Fprintf(buf, "%v ago: executing program %v (id=%v):\n%s\n", exec.Time, exec.Proc, exec.ID, exec.Prog)
    94  	}
    95  	fmt.Fprintf(buf, "kernel console output (not intermixed with test programs):\n\n")
    96  	rep.Output = append(buf.Bytes(), rep.Output...)
    97  	n := len(buf.Bytes())
    98  	rep.StartPos += n
    99  	rep.EndPos += n
   100  	rep.SkipPos += n
   101  }