cobufi-go

module
v0.0.0-...-0436c32 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2024 License: BSD-3-Clause

README

AdvocateGo: Automated Detection and Verification Of Concurrency bugs through Analysis of Trace recordings of program Executions in Go."

[!WARNING] This program is still under development and may return no or wrong results.

What

We want to analyze concurrent Go programs to automatically find potential concurrency bug. The different analysis scenarios can be found in doc/Analysis.md.

We also implement a trace replay mechanism, to replay a trace as recorded.

Recording

To analyze the program, we first need to record it. To do this, we modify the go runtime to automatically record a program while it runs. The modified runtime can be found in the go-patch directory. Running a program with this modified go runtime will create a trace of the program including

  • spawning of new routines
  • atomic operations
  • mutex operations
  • channel operations
  • select operations
  • wait group operations
  • once operations

The following is a short explanation about how to build and run the new runtime and create the trace. A full explanation of the created trace can be found in the doc directory.

[!WARNING] The recording of atomic operations is only tested with amd64. For arm64 an untested implementation exists.

How

The go-patch folder contains a modified version of the go runtime. With this modified version it is possible to save a trace of the program.

To build the new runtime, run the make.bash or make.bat file in the src directory. This will create a bin directory containing a go executable. This executable can be used as your new go environment e.g. with ./go run main.go or ./go build. Please make sure, that the program expects go version 1.21 or earlier.

It is necessary to set the GOROOT environment variable to the path of go-patch, e.g. with

export GOROOT=$HOME/ADVOCATE/go-patch/

To create a trace, add

advocate.InitTracing(0)
defer advocate.Finish()

at the beginning of the main function. Also include the following imports

advocate

In some cases, we can get a fatal error: schedule: holding lock. In this case increase the argument in runtime.InitAtomics(0) until the problem disappears.

After that run the program with ./go run main.go or ./go build && ./main, using the new runtime.

Example

Let's create the trace for the following program:

package main

import (
	"time"
)

func main() {
	c := make(chan int, 0)

	go func() {
		c <- 1
	}()

	go func() {
		<-c
	}()

	time.Sleep(10 * time.Millisecond)
	close(c)
}

After adding the preamble, we get

package main

import (
	"advocate"
	"time"
)

func main() {
	// ======= Preamble Start =======
		advocate.InitTracing(0)
		defer advocate.Finish()
	// ======= Preamble End =======

	c := make(chan int, 0)

	go func() {
		c <- 1  // line 17
	}()

	go func() {
		<-c  // line 21
	}()

	time.Sleep(10 * time.Millisecond)
	close(c)  // line 25
}

Running this create a trace folder containing one trace file for each routine. The non-empty trace files contain the following trace:

G,20,16,/home/.../main.go:16;G,21,17,/home/.../main.go:20;C,26,26,4,C,f,0,0,/home/.../main.go:25

C,23,24,4,S,f,1,0,/home/.../main.go:17

C,22,25,4,R,f,1,0,/home/.../main.go:21

In this example the file paths are shortened for readability. In the real trace, the full path is given.

The trace includes both the concurrent operations of the program it self, as well as internal operations used by the go runtime. An explanation of the trace file including the explanations for all elements can be found in the doc directory.

Analysis and Reorder

We can now analyze the created file using the program in the analyzer folder. For now we only support the search for potential send on a closed channel, but we plan to expand the use cases in the future. The analyzer can also create a new reordered trace, in which a detected possible bug actually occurs. This new trace can then used in the replay, to confirm and simplify the removal of the bug.

[!WARNING] The Reorder is still in development and may result in incorrect traces

The analyzer can take the following command line arguments:

  • -c Ignore happens before relations of critical sections (default false)
  • -d int Debug Level, 0 = silent, 1 = errors, 2 = info, 3 = debug (default 1) (default 1)
  • -f Assume a FIFO ordering for buffered channels (default false)
  • -i int Index of the result to use for the reordered trace file. Only needed if -n is set. 1 based (default -1)
  • -n Create a reordered trace file from a given analysis result without running the analysis. -r and -i are required. If not set, a rewritten trace can be created from the current analysis results
  • -p Do not print the results to the terminal (default false). Automatically set -x to true
  • -r string Path to where the result file should be saved. If not set, it is saved in the trace folder
  • -t string Path to the trace folder to analyze or rewrite -w Do not print warnings (default false) -x Do not ask to create a reordered trace file after the analysis (default false)

If we assume the trace from our example is saved in file trace.go and run the analyzer with

./analyzer -x -t /trace

it will create the following result, show it in the terminal and print it into an result_readable.log file:

==================== Summary ====================

-------------------- Critical -------------------
1 Possible send on closed channel:
	close: /home/.../main.go:25@26
	send : /home/.../main.go:17@23
-------------------- Warning --------------------
2 Possible receive on closed channel:
	close: /home/.../main.go:25@26
	recv : /home/.../main.go:21@22

The send can cause a panic of the program, if it occurs. It is therefor an error message (in terminal red).

A receive on a closed channel does not cause a panic, but returns a default value. It can therefor be a desired behavior. For this reason it is only considered a warning (in terminal orange, can be silenced with -w).

Trace Replay

The trace replay reruns a given program as given in the recorded trace. Please be aware, that only the recorded elements are considered for the trace replay. This means, that the order of non-recorded operations between two or more routines can still very.

How

To start the replay, add the following header at the beginning of the main function:

advocate.EnableReplay()
defer advocate.WaitForReplayFinish()

Also include the following import:

"advocate"

Now the program can be run with the modified go routine, identical to the recording of the trace (remember to export the new gopath).

It is important that the program is not changed between recording and replay. This is especially true for the positions of operations on the code. For this reason it can be beneficial to add the following header, instead the two separate ones for recording and replay:

if true {
	// init tracing
	advocate.InitTracing(0)
	defer advocate.Finish()
} else {
	// init replay
	advocate.EnableReplay()
	defer advocate.WaitForReplayFinish()
}

With changing true to false one can switch between recording and replay.

Warning:

It is the users responsibility of the user to make sure, that the input to the program, including e.g. API calls are equal for the recording and the tracing. Otherwise the replay is likely to get stuck.

Do not change the program code between trace recording and replay. The identification of the operations is based on the file names and lines, where the operations occur. If they get changed, the program will most likely block without terminating. If you need to change the program, you must either rerun the trace recording or change the effected trace elements in the recorded trace. This also includes the adding of the replay header. Make sure, that it is already in the program (but commented out), when you run the recording.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL