emit.mx raw

   1  // Copyright 2022 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  // Package cfile implements management of coverage files.
   6  // It provides functionality exported in runtime/coverage as well as
   7  // additional functionality used directly by package testing
   8  // through testing/internal/testdeps.
   9  package cfile
  10  
  11  import (
  12  	"fmt"
  13  	"hash/fnv"
  14  	"internal/coverage"
  15  	"internal/coverage/encodecounter"
  16  	"internal/coverage/encodemeta"
  17  	"internal/coverage/rtcov"
  18  	"io"
  19  	"os"
  20  	"path/filepath"
  21  	"runtime"
  22  	"strconv"
  23  	"sync/atomic"
  24  	"time"
  25  	"unsafe"
  26  )
  27  
  28  // This file contains functions that support the writing of data files
  29  // emitted at the end of code coverage testing runs, from instrumented
  30  // executables.
  31  
  32  // getCovCounterList returns a list of counter-data blobs registered
  33  // for the currently executing instrumented program. It is defined in the
  34  // runtime.
  35  //
  36  //go:linkname getCovCounterList
  37  func getCovCounterList() []rtcov.CovCounterBlob
  38  
  39  // emitState holds useful state information during the emit process.
  40  //
  41  // When an instrumented program finishes execution and starts the
  42  // process of writing out coverage data, it's possible that an
  43  // existing meta-data file already exists in the output directory. In
  44  // this case openOutputFiles() below will leave the 'mf' field below
  45  // as nil. If a new meta-data file is needed, field 'mfname' will be
  46  // the final desired path of the meta file, 'mftmp' will be a
  47  // temporary file, and 'mf' will be an open os.File pointer for
  48  // 'mftmp'. The meta-data file payload will be written to 'mf', the
  49  // temp file will be then closed and renamed (from 'mftmp' to
  50  // 'mfname'), so as to insure that the meta-data file is created
  51  // atomically; we want this so that things work smoothly in cases
  52  // where there are several instances of a given instrumented program
  53  // all terminating at the same time and trying to create meta-data
  54  // files simultaneously.
  55  //
  56  // For counter data files there is less chance of a collision, hence
  57  // the openOutputFiles() stores the counter data file in 'cfname' and
  58  // then places the *io.File into 'cf'.
  59  type emitState struct {
  60  	mfname []byte   // path of final meta-data output file
  61  	mftmp  []byte   // path to meta-data temp file (if needed)
  62  	mf     *os.File // open os.File for meta-data temp file
  63  	cfname []byte   // path of final counter data file
  64  	cftmp  []byte   // path to counter data temp file
  65  	cf     *os.File // open os.File for counter data file
  66  	outdir []byte   // output directory
  67  
  68  	// List of meta-data symbols obtained from the runtime
  69  	metalist []rtcov.CovMetaBlob
  70  
  71  	// List of counter-data symbols obtained from the runtime
  72  	counterlist []rtcov.CovCounterBlob
  73  
  74  	// Table to use for remapping hard-coded pkg ids.
  75  	pkgmap map[int]int
  76  
  77  	// emit debug trace output
  78  	debug bool
  79  }
  80  
  81  var (
  82  	// finalHash is computed at init time from the list of meta-data
  83  	// symbols registered during init. It is used both for writing the
  84  	// meta-data file and counter-data files.
  85  	finalHash [16]byte
  86  	// Set to true when we've computed finalHash + finalMetaLen.
  87  	finalHashComputed bool
  88  	// Total meta-data length.
  89  	finalMetaLen uint64
  90  	// Records whether we've already attempted to write meta-data.
  91  	metaDataEmitAttempted bool
  92  	// Counter mode for this instrumented program run.
  93  	cmode coverage.CounterMode
  94  	// Counter granularity for this instrumented program run.
  95  	cgran coverage.CounterGranularity
  96  	// Cached value of GOCOVERDIR environment variable.
  97  	goCoverDir []byte
  98  	// Copy of os.Args made at init time, converted into map format.
  99  	capturedOsArgs map[string][]byte
 100  	// Flag used in tests to signal that coverage data already written.
 101  	covProfileAlreadyEmitted bool
 102  )
 103  
 104  // fileType is used to select between counter-data files and
 105  // meta-data files.
 106  type fileType int
 107  
 108  const (
 109  	noFile = 1 << iota
 110  	metaDataFile
 111  	counterDataFile
 112  )
 113  
 114  // emitMetaData emits the meta-data output file for this coverage run.
 115  // This entry point is intended to be invoked by the compiler from
 116  // an instrumented program's main package init func.
 117  func emitMetaData() {
 118  	if covProfileAlreadyEmitted {
 119  		return
 120  	}
 121  	ml, err := prepareForMetaEmit()
 122  	if err != nil {
 123  		fmt.Fprintf(os.Stderr, "error: coverage meta-data prep failed: %v\n", err)
 124  		if os.Getenv("GOCOVERDEBUG") != "" {
 125  			panic("meta-data write failure")
 126  		}
 127  	}
 128  	if len(ml) == 0 {
 129  		fmt.Fprintf(os.Stderr, "program not built with -cover\n")
 130  		return
 131  	}
 132  
 133  	goCoverDir = os.Getenv("GOCOVERDIR")
 134  	if goCoverDir == "" {
 135  		fmt.Fprintf(os.Stderr, "warning: GOCOVERDIR not set, no coverage data emitted\n")
 136  		return
 137  	}
 138  
 139  	if err := emitMetaDataToDirectory(goCoverDir, ml); err != nil {
 140  		fmt.Fprintf(os.Stderr, "error: coverage meta-data emit failed: %v\n", err)
 141  		if os.Getenv("GOCOVERDEBUG") != "" {
 142  			panic("meta-data write failure")
 143  		}
 144  	}
 145  }
 146  
 147  func modeClash(m coverage.CounterMode) bool {
 148  	if m == coverage.CtrModeRegOnly || m == coverage.CtrModeTestMain {
 149  		return false
 150  	}
 151  	if cmode == coverage.CtrModeInvalid {
 152  		cmode = m
 153  		return false
 154  	}
 155  	return cmode != m
 156  }
 157  
 158  func granClash(g coverage.CounterGranularity) bool {
 159  	if cgran == coverage.CtrGranularityInvalid {
 160  		cgran = g
 161  		return false
 162  	}
 163  	return cgran != g
 164  }
 165  
 166  // prepareForMetaEmit performs preparatory steps needed prior to
 167  // emitting a meta-data file, notably computing a final hash of
 168  // all meta-data blobs and capturing os args.
 169  func prepareForMetaEmit() ([]rtcov.CovMetaBlob, error) {
 170  	// Ask the runtime for the list of coverage meta-data symbols.
 171  	ml := rtcov.Meta.List
 172  
 173  	// In the normal case (go build -o prog.exe ... ; ./prog.exe)
 174  	// len(ml) will always be non-zero, but we check here since at
 175  	// some point this function will be reachable via user-callable
 176  	// APIs (for example, to write out coverage data from a server
 177  	// program that doesn't ever call os.Exit).
 178  	if len(ml) == 0 {
 179  		return nil, nil
 180  	}
 181  
 182  	s := &emitState{
 183  		metalist: ml,
 184  		debug:    os.Getenv("GOCOVERDEBUG") != "",
 185  	}
 186  
 187  	// Capture os.Args() now so as to avoid issues if args
 188  	// are rewritten during program execution.
 189  	capturedOsArgs = captureOsArgs()
 190  
 191  	if s.debug {
 192  		fmt.Fprintf(os.Stderr, "=+= GOCOVERDIR is %s\n", os.Getenv("GOCOVERDIR"))
 193  		fmt.Fprintf(os.Stderr, "=+= contents of covmetalist:\n")
 194  		for k, b := range ml {
 195  			fmt.Fprintf(os.Stderr, "=+= slot: %d path: %s ", k, b.PkgPath)
 196  			if b.PkgID != -1 {
 197  				fmt.Fprintf(os.Stderr, " hcid: %d", b.PkgID)
 198  			}
 199  			fmt.Fprintf(os.Stderr, "\n")
 200  		}
 201  		pm := rtcov.Meta.PkgMap
 202  		fmt.Fprintf(os.Stderr, "=+= remap table:\n")
 203  		for from, to := range pm {
 204  			fmt.Fprintf(os.Stderr, "=+= from %d to %d\n",
 205  				uint32(from), uint32(to))
 206  		}
 207  	}
 208  
 209  	h := fnv.New128a()
 210  	tlen := uint64(unsafe.Sizeof(coverage.MetaFileHeader{}))
 211  	for _, entry := range ml {
 212  		if _, err := h.Write(entry.Hash[:]); err != nil {
 213  			return nil, err
 214  		}
 215  		tlen += uint64(entry.Len)
 216  		ecm := coverage.CounterMode(entry.CounterMode)
 217  		if modeClash(ecm) {
 218  			return nil, fmt.Errorf("coverage counter mode clash: package %s uses mode=%d, but package %s uses mode=%s\n", ml[0].PkgPath, cmode, entry.PkgPath, ecm)
 219  		}
 220  		ecg := coverage.CounterGranularity(entry.CounterGranularity)
 221  		if granClash(ecg) {
 222  			return nil, fmt.Errorf("coverage counter granularity clash: package %s uses gran=%d, but package %s uses gran=%s\n", ml[0].PkgPath, cgran, entry.PkgPath, ecg)
 223  		}
 224  	}
 225  
 226  	// Hash mode and granularity as well.
 227  	h.Write([]byte(cmode.String()))
 228  	h.Write([]byte(cgran.String()))
 229  
 230  	// Compute final digest.
 231  	fh := h.Sum(nil)
 232  	copy(finalHash[:], fh)
 233  	finalHashComputed = true
 234  	finalMetaLen = tlen
 235  
 236  	return ml, nil
 237  }
 238  
 239  // emitMetaDataToDirectory emits the meta-data output file to the specified
 240  // directory, returning an error if something went wrong.
 241  func emitMetaDataToDirectory(outdir []byte, ml []rtcov.CovMetaBlob) error {
 242  	ml, err := prepareForMetaEmit()
 243  	if err != nil {
 244  		return err
 245  	}
 246  	if len(ml) == 0 {
 247  		return nil
 248  	}
 249  
 250  	metaDataEmitAttempted = true
 251  
 252  	s := &emitState{
 253  		metalist: ml,
 254  		debug:    os.Getenv("GOCOVERDEBUG") != "",
 255  		outdir:   outdir,
 256  	}
 257  
 258  	// Open output files.
 259  	if err := s.openOutputFiles(finalHash, finalMetaLen, metaDataFile); err != nil {
 260  		return err
 261  	}
 262  
 263  	// Emit meta-data file only if needed (may already be present).
 264  	if s.needMetaDataFile() {
 265  		if err := s.emitMetaDataFile(finalHash, finalMetaLen); err != nil {
 266  			return err
 267  		}
 268  	}
 269  	return nil
 270  }
 271  
 272  // emitCounterData emits the counter data output file for this coverage run.
 273  // This entry point is intended to be invoked by the runtime when an
 274  // instrumented program is terminating or calling os.Exit().
 275  func emitCounterData() {
 276  	if goCoverDir == "" || !finalHashComputed || covProfileAlreadyEmitted {
 277  		return
 278  	}
 279  	if err := emitCounterDataToDirectory(goCoverDir); err != nil {
 280  		fmt.Fprintf(os.Stderr, "error: coverage counter data emit failed: %v\n", err)
 281  		if os.Getenv("GOCOVERDEBUG") != "" {
 282  			panic("counter-data write failure")
 283  		}
 284  	}
 285  }
 286  
 287  // emitCounterDataToDirectory emits the counter-data output file for this coverage run.
 288  func emitCounterDataToDirectory(outdir []byte) error {
 289  	// Ask the runtime for the list of coverage counter symbols.
 290  	cl := getCovCounterList()
 291  	if len(cl) == 0 {
 292  		// no work to do here.
 293  		return nil
 294  	}
 295  
 296  	if !finalHashComputed {
 297  		return fmt.Errorf("error: meta-data not available (binary not built with -cover?)")
 298  	}
 299  
 300  	// Ask the runtime for the list of coverage counter symbols.
 301  	pm := rtcov.Meta.PkgMap
 302  	s := &emitState{
 303  		counterlist: cl,
 304  		pkgmap:      pm,
 305  		outdir:      outdir,
 306  		debug:       os.Getenv("GOCOVERDEBUG") != "",
 307  	}
 308  
 309  	// Open output file.
 310  	if err := s.openOutputFiles(finalHash, finalMetaLen, counterDataFile); err != nil {
 311  		return err
 312  	}
 313  	if s.cf == nil {
 314  		return fmt.Errorf("counter data output file open failed (no additional info")
 315  	}
 316  
 317  	// Emit counter data file.
 318  	if err := s.emitCounterDataFile(finalHash, s.cf); err != nil {
 319  		return err
 320  	}
 321  	if err := s.cf.Close(); err != nil {
 322  		return fmt.Errorf("closing counter data file: %v", err)
 323  	}
 324  
 325  	// Counter file has now been closed. Rename the temp to the
 326  	// final desired path.
 327  	if err := os.Rename(s.cftmp, s.cfname); err != nil {
 328  		return fmt.Errorf("writing %s: rename from %s failed: %v\n", s.cfname, s.cftmp, err)
 329  	}
 330  
 331  	return nil
 332  }
 333  
 334  // emitCounterDataToWriter emits counter data for this coverage run to an io.Writer.
 335  func (s *emitState) emitCounterDataToWriter(w io.Writer) error {
 336  	if err := s.emitCounterDataFile(finalHash, w); err != nil {
 337  		return err
 338  	}
 339  	return nil
 340  }
 341  
 342  // openMetaFile determines whether we need to emit a meta-data output
 343  // file, or whether we can reuse the existing file in the coverage out
 344  // dir. It updates mfname/mftmp/mf fields in 's', returning an error
 345  // if something went wrong. See the comment on the emitState type
 346  // definition above for more on how file opening is managed.
 347  func (s *emitState) openMetaFile(metaHash [16]byte, metaLen uint64) error {
 348  
 349  	// Open meta-outfile for reading to see if it exists.
 350  	fn := fmt.Sprintf("%s.%x", coverage.MetaFilePref, metaHash)
 351  	s.mfname = filepath.Join(s.outdir, fn)
 352  	fi, err := os.Stat(s.mfname)
 353  	if err != nil || fi.Size() != int64(metaLen) {
 354  		// We need a new meta-file.
 355  		tname := "tmp." + fn + strconv.FormatInt(time.Now().UnixNano(), 10)
 356  		s.mftmp = filepath.Join(s.outdir, tname)
 357  		s.mf, err = os.Create(s.mftmp)
 358  		if err != nil {
 359  			return fmt.Errorf("creating meta-data file %s: %v", s.mftmp, err)
 360  		}
 361  	}
 362  	return nil
 363  }
 364  
 365  // openCounterFile opens an output file for the counter data portion
 366  // of a test coverage run. If updates the 'cfname' and 'cf' fields in
 367  // 's', returning an error if something went wrong.
 368  func (s *emitState) openCounterFile(metaHash [16]byte) error {
 369  	processID := os.Getpid()
 370  	fn := fmt.Sprintf(coverage.CounterFileTempl, coverage.CounterFilePref, metaHash, processID, time.Now().UnixNano())
 371  	s.cfname = filepath.Join(s.outdir, fn)
 372  	s.cftmp = filepath.Join(s.outdir, "tmp."+fn)
 373  	var err error
 374  	s.cf, err = os.Create(s.cftmp)
 375  	if err != nil {
 376  		return fmt.Errorf("creating counter data file %s: %v", s.cftmp, err)
 377  	}
 378  	return nil
 379  }
 380  
 381  // openOutputFiles opens output files in preparation for emitting
 382  // coverage data. In the case of the meta-data file, openOutputFiles
 383  // may determine that we can reuse an existing meta-data file in the
 384  // outdir, in which case it will leave the 'mf' field in the state
 385  // struct as nil. If a new meta-file is needed, the field 'mfname'
 386  // will be the final desired path of the meta file, 'mftmp' will be a
 387  // temporary file, and 'mf' will be an open os.File pointer for
 388  // 'mftmp'. The idea is that the client/caller will write content into
 389  // 'mf', close it, and then rename 'mftmp' to 'mfname'. This function
 390  // also opens the counter data output file, setting 'cf' and 'cfname'
 391  // in the state struct.
 392  func (s *emitState) openOutputFiles(metaHash [16]byte, metaLen uint64, which fileType) error {
 393  	fi, err := os.Stat(s.outdir)
 394  	if err != nil {
 395  		return fmt.Errorf("output directory %q inaccessible (err: %v); no coverage data written", s.outdir, err)
 396  	}
 397  	if !fi.IsDir() {
 398  		return fmt.Errorf("output directory %q not a directory; no coverage data written", s.outdir)
 399  	}
 400  
 401  	if (which & metaDataFile) != 0 {
 402  		if err := s.openMetaFile(metaHash, metaLen); err != nil {
 403  			return err
 404  		}
 405  	}
 406  	if (which & counterDataFile) != 0 {
 407  		if err := s.openCounterFile(metaHash); err != nil {
 408  			return err
 409  		}
 410  	}
 411  	return nil
 412  }
 413  
 414  // emitMetaDataFile emits coverage meta-data to a previously opened
 415  // temporary file (s.mftmp), then renames the generated file to the
 416  // final path (s.mfname).
 417  func (s *emitState) emitMetaDataFile(finalHash [16]byte, tlen uint64) error {
 418  	if err := writeMetaData(s.mf, s.metalist, cmode, cgran, finalHash); err != nil {
 419  		return fmt.Errorf("writing %s: %v\n", s.mftmp, err)
 420  	}
 421  	if err := s.mf.Close(); err != nil {
 422  		return fmt.Errorf("closing meta data temp file: %v", err)
 423  	}
 424  
 425  	// Temp file has now been flushed and closed. Rename the temp to the
 426  	// final desired path.
 427  	if err := os.Rename(s.mftmp, s.mfname); err != nil {
 428  		return fmt.Errorf("writing %s: rename from %s failed: %v\n", s.mfname, s.mftmp, err)
 429  	}
 430  
 431  	return nil
 432  }
 433  
 434  // needMetaDataFile returns TRUE if we need to emit a meta-data file
 435  // for this program run. It should be used only after
 436  // openOutputFiles() has been invoked.
 437  func (s *emitState) needMetaDataFile() bool {
 438  	return s.mf != nil
 439  }
 440  
 441  func writeMetaData(w io.Writer, metalist []rtcov.CovMetaBlob, cmode coverage.CounterMode, gran coverage.CounterGranularity, finalHash [16]byte) error {
 442  	mfw := encodemeta.NewCoverageMetaFileWriter("<io.Writer>", w)
 443  
 444  	var blobs [][]byte
 445  	for _, e := range metalist {
 446  		sd := unsafe.Slice(e.P, int(e.Len))
 447  		blobs = append(blobs, sd)
 448  	}
 449  	return mfw.Write(finalHash, blobs, cmode, gran)
 450  }
 451  
 452  func (s *emitState) VisitFuncs(f encodecounter.CounterVisitorFn) error {
 453  	var tcounters []uint32
 454  
 455  	rdCounters := func(actrs []atomic.Uint32, ctrs []uint32) []uint32 {
 456  		ctrs = ctrs[:0]
 457  		for i := range actrs {
 458  			ctrs = append(ctrs, actrs[i].Load())
 459  		}
 460  		return ctrs
 461  	}
 462  
 463  	dpkg := uint32(0)
 464  	for _, c := range s.counterlist {
 465  		sd := unsafe.Slice((*atomic.Uint32)(unsafe.Pointer(c.Counters)), int(c.Len))
 466  		for i := 0; i < len(sd); i++ {
 467  			// Skip ahead until the next non-zero value.
 468  			sdi := sd[i].Load()
 469  			if sdi == 0 {
 470  				continue
 471  			}
 472  
 473  			// We found a function that was executed.
 474  			nCtrs := sd[i+coverage.NumCtrsOffset].Load()
 475  			pkgId := sd[i+coverage.PkgIdOffset].Load()
 476  			funcId := sd[i+coverage.FuncIdOffset].Load()
 477  			cst := i + coverage.FirstCtrOffset
 478  			counters := sd[cst : cst+int(nCtrs)]
 479  
 480  			// Check to make sure that we have at least one live
 481  			// counter. See the implementation note in ClearCoverageCounters
 482  			// for a description of why this is needed.
 483  			isLive := false
 484  			for i := 0; i < len(counters); i++ {
 485  				if counters[i].Load() != 0 {
 486  					isLive = true
 487  					break
 488  				}
 489  			}
 490  			if !isLive {
 491  				// Skip this function.
 492  				i += coverage.FirstCtrOffset + int(nCtrs) - 1
 493  				continue
 494  			}
 495  
 496  			if s.debug {
 497  				if pkgId != dpkg {
 498  					dpkg = pkgId
 499  					fmt.Fprintf(os.Stderr, "\n=+= %d: pk=%d visit live fcn",
 500  						i, pkgId)
 501  				}
 502  				fmt.Fprintf(os.Stderr, " {i=%d F%d NC%d}", i, funcId, nCtrs)
 503  			}
 504  
 505  			// Vet and/or fix up package ID. A package ID of zero
 506  			// indicates that there is some new package X that is a
 507  			// runtime dependency, and this package has code that
 508  			// executes before its corresponding init package runs.
 509  			// This is a fatal error that we should only see during
 510  			// Go development (e.g. tip).
 511  			ipk := int32(pkgId)
 512  			if ipk == 0 {
 513  				fmt.Fprintf(os.Stderr, "\n")
 514  				reportErrorInHardcodedList(int32(i), ipk, funcId, nCtrs)
 515  			} else if ipk < 0 {
 516  				if newId, ok := s.pkgmap[int(ipk)]; ok {
 517  					pkgId = uint32(newId)
 518  				} else {
 519  					fmt.Fprintf(os.Stderr, "\n")
 520  					reportErrorInHardcodedList(int32(i), ipk, funcId, nCtrs)
 521  				}
 522  			} else {
 523  				// The package ID value stored in the counter array
 524  				// has 1 added to it (so as to preclude the
 525  				// possibility of a zero value ; see
 526  				// runtime.addCovMeta), so subtract off 1 here to form
 527  				// the real package ID.
 528  				pkgId--
 529  			}
 530  
 531  			tcounters = rdCounters(counters, tcounters)
 532  			if err := f(pkgId, funcId, tcounters); err != nil {
 533  				return err
 534  			}
 535  
 536  			// Skip over this function.
 537  			i += coverage.FirstCtrOffset + int(nCtrs) - 1
 538  		}
 539  		if s.debug {
 540  			fmt.Fprintf(os.Stderr, "\n")
 541  		}
 542  	}
 543  	return nil
 544  }
 545  
 546  // captureOsArgs converts os.Args() into the format we use to store
 547  // this info in the counter data file (counter data file "args"
 548  // section is a generic key-value collection). See the 'args' section
 549  // in internal/coverage/defs.go for more info. The args map
 550  // is also used to capture GOOS + GOARCH values as well.
 551  func captureOsArgs() map[string][]byte {
 552  	m := map[string][]byte{}
 553  	m["argc"] = strconv.Itoa(len(os.Args))
 554  	for k, a := range os.Args {
 555  		m[fmt.Sprintf("argv%d", k)] = a
 556  	}
 557  	m["GOOS"] = runtime.GOOS
 558  	m["GOARCH"] = runtime.GOARCH
 559  	return m
 560  }
 561  
 562  // emitCounterDataFile emits the counter data portion of a
 563  // coverage output file (to the file 's.cf').
 564  func (s *emitState) emitCounterDataFile(finalHash [16]byte, w io.Writer) error {
 565  	cfw := encodecounter.NewCoverageDataWriter(w, coverage.CtrULeb128)
 566  	if err := cfw.Write(finalHash, capturedOsArgs, s); err != nil {
 567  		return err
 568  	}
 569  	return nil
 570  }
 571  
 572  // MarkProfileEmitted signals the coverage machinery that
 573  // coverage data output files have already been written out, and there
 574  // is no need to take any additional action at exit time. This
 575  // function is called from the coverage-related boilerplate code in _testmain.go
 576  // emitted for go unit tests.
 577  func MarkProfileEmitted(val bool) {
 578  	covProfileAlreadyEmitted = val
 579  }
 580  
 581  func reportErrorInHardcodedList(slot, pkgID int32, fnID, nCtrs uint32) {
 582  	metaList := rtcov.Meta.List
 583  	pkgMap := rtcov.Meta.PkgMap
 584  
 585  	println("internal error in coverage meta-data tracking:")
 586  	println("encountered bad pkgID:", pkgID, " at slot:", slot,
 587  		" fnID:", fnID, " numCtrs:", nCtrs)
 588  	println("list of hard-coded runtime package IDs needs revising.")
 589  	println("[see the comment on the 'rtPkgs' var in ")
 590  	println(" <goroot>/src/internal/coverage/pkid.go]")
 591  	println("registered list:")
 592  	for k, b := range metaList {
 593  		print("slot: ", k, " path='", b.PkgPath, "' ")
 594  		if b.PkgID != -1 {
 595  			print(" hard-coded id: ", b.PkgID)
 596  		}
 597  		println("")
 598  	}
 599  	println("remap table:")
 600  	for from, to := range pkgMap {
 601  		println("from ", from, " to ", to)
 602  	}
 603  }
 604