Update code to use pault.ag/go/sniff package (#5038)
* Update code to use pault.ag/go/sniff package * Update go dependencies
This commit is contained in:
parent
3e2bbbed3d
commit
d0423c6d4f
17 changed files with 213 additions and 59 deletions
33
vendor/github.com/armon/go-proxyproto/protocol.go
generated
vendored
33
vendor/github.com/armon/go-proxyproto/protocol.go
generated
vendored
|
|
@ -49,6 +49,7 @@ type Listener struct {
|
|||
Listener net.Listener
|
||||
ProxyHeaderTimeout time.Duration
|
||||
SourceCheck SourceChecker
|
||||
UnknownOK bool // allow PROXY UNKNOWN
|
||||
}
|
||||
|
||||
// Conn is used to wrap and underlying connection which
|
||||
|
|
@ -62,6 +63,7 @@ type Conn struct {
|
|||
useConnAddr bool
|
||||
once sync.Once
|
||||
proxyHeaderTimeout time.Duration
|
||||
unknownOK bool
|
||||
}
|
||||
|
||||
// Accept waits for and returns the next connection to the listener.
|
||||
|
|
@ -83,6 +85,7 @@ func (p *Listener) Accept() (net.Conn, error) {
|
|||
}
|
||||
newConn := NewConn(conn, p.ProxyHeaderTimeout)
|
||||
newConn.useConnAddr = useConnAddr
|
||||
newConn.unknownOK = p.UnknownOK
|
||||
return newConn, nil
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +122,22 @@ func (p *Conn) Read(b []byte) (int, error) {
|
|||
return p.bufReader.Read(b)
|
||||
}
|
||||
|
||||
func (p *Conn) ReadFrom(r io.Reader) (int64, error) {
|
||||
if rf, ok := p.conn.(io.ReaderFrom); ok {
|
||||
return rf.ReadFrom(r)
|
||||
}
|
||||
return io.Copy(p.conn, r)
|
||||
}
|
||||
|
||||
func (p *Conn) WriteTo(w io.Writer) (int64, error) {
|
||||
var err error
|
||||
p.once.Do(func() { err = p.checkPrefix() })
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return p.bufReader.WriteTo(w)
|
||||
}
|
||||
|
||||
func (p *Conn) Write(b []byte) (int, error) {
|
||||
return p.conn.Write(b)
|
||||
}
|
||||
|
|
@ -209,13 +228,20 @@ func (p *Conn) checkPrefix() error {
|
|||
|
||||
// Split on spaces, should be (PROXY <type> <src addr> <dst addr> <src port> <dst port>)
|
||||
parts := strings.Split(header, " ")
|
||||
if len(parts) != 6 {
|
||||
if len(parts) < 2 {
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Invalid header line: %s", header)
|
||||
}
|
||||
|
||||
// Verify the type is known
|
||||
switch parts[1] {
|
||||
case "UNKNOWN":
|
||||
if !p.unknownOK || len(parts) != 2 {
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Invalid UNKNOWN header line: %s", header)
|
||||
}
|
||||
p.useConnAddr = true
|
||||
return nil
|
||||
case "TCP4":
|
||||
case "TCP6":
|
||||
default:
|
||||
|
|
@ -223,6 +249,11 @@ func (p *Conn) checkPrefix() error {
|
|||
return fmt.Errorf("Unhandled address type: %s", parts[1])
|
||||
}
|
||||
|
||||
if len(parts) != 6 {
|
||||
p.conn.Close()
|
||||
return fmt.Errorf("Invalid header line: %s", header)
|
||||
}
|
||||
|
||||
// Parse out the source address
|
||||
ip := net.ParseIP(parts[2])
|
||||
if ip == nil {
|
||||
|
|
|
|||
2
vendor/github.com/mitchellh/go-ps/README.md
generated
vendored
2
vendor/github.com/mitchellh/go-ps/README.md
generated
vendored
|
|
@ -1,4 +1,4 @@
|
|||
# Process List Library for Go
|
||||
# Process List Library for Go [](https://godoc.org/github.com/mitchellh/go-ps)
|
||||
|
||||
go-ps is a library for Go that implements OS-specific APIs to list and
|
||||
manipulate processes in a platform-safe way. The library can find and
|
||||
|
|
|
|||
3
vendor/github.com/mitchellh/go-ps/go.mod
generated
vendored
Normal file
3
vendor/github.com/mitchellh/go-ps/go.mod
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/mitchellh/go-ps
|
||||
|
||||
go 1.13
|
||||
2
vendor/github.com/mitchellh/go-ps/process_freebsd.go
generated
vendored
2
vendor/github.com/mitchellh/go-ps/process_freebsd.go
generated
vendored
|
|
@ -1,4 +1,4 @@
|
|||
// +build freebsd,amd64
|
||||
// +build freebsd
|
||||
|
||||
package ps
|
||||
|
||||
|
|
|
|||
10
vendor/github.com/mitchellh/go-ps/process_unix.go
generated
vendored
10
vendor/github.com/mitchellh/go-ps/process_unix.go
generated
vendored
|
|
@ -56,7 +56,7 @@ func processes() ([]Process, error) {
|
|||
|
||||
results := make([]Process, 0, 50)
|
||||
for {
|
||||
fis, err := d.Readdir(10)
|
||||
names, err := d.Readdirnames(10)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
|
|
@ -64,14 +64,8 @@ func processes() ([]Process, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
for _, fi := range fis {
|
||||
// We only care about directories, since all pids are dirs
|
||||
if !fi.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
// We only care if the name starts with a numeric
|
||||
name := fi.Name()
|
||||
if name[0] < '0' || name[0] > '9' {
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
13
vendor/github.com/paultag/sniff/LICENSE
generated
vendored
13
vendor/github.com/paultag/sniff/LICENSE
generated
vendored
|
|
@ -1,13 +0,0 @@
|
|||
Copyright 2017 Paul Tagliamonte
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
144
vendor/github.com/paultag/sniff/parser/parser.go
generated
vendored
144
vendor/github.com/paultag/sniff/parser/parser.go
generated
vendored
|
|
@ -1,144 +0,0 @@
|
|||
/* {{{ Copyright 2017 Paul Tagliamonte
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License. }}} */
|
||||
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var TLSHeaderLength = 5
|
||||
|
||||
/* This function is basically all most folks want to invoke out of this
|
||||
* jumble of bits. This will take an incoming TLS Client Hello (including
|
||||
* all the fuzzy bits at the beginning of it - fresh out of the socket) and
|
||||
* go ahead and give us the SNI Name they want. */
|
||||
func GetHostname(data []byte) (string, error) {
|
||||
if len(data) == 0 || data[0] != 0x16 {
|
||||
return "", fmt.Errorf("Doesn't look like a TLS Client Hello")
|
||||
}
|
||||
|
||||
extensions, err := GetExtensionBlock(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sn, err := GetSNBlock(extensions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sni, err := GetSNIBlock(sn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(sni), nil
|
||||
}
|
||||
|
||||
/* Given a Server Name TLS Extension block, parse out and return the SNI
|
||||
* (Server Name Indication) payload */
|
||||
func GetSNIBlock(data []byte) ([]byte, error) {
|
||||
index := 0
|
||||
|
||||
for {
|
||||
if index >= len(data) {
|
||||
break
|
||||
}
|
||||
length := int((data[index] << 8) + data[index+1])
|
||||
endIndex := index + 2 + length
|
||||
if data[index+2] == 0x00 { /* SNI */
|
||||
sni := data[index+3:]
|
||||
sniLength := int((sni[0] << 8) + sni[1])
|
||||
return sni[2 : sniLength+2], nil
|
||||
}
|
||||
index = endIndex
|
||||
}
|
||||
return []byte{}, fmt.Errorf(
|
||||
"Finished parsing the SN block without finding an SNI",
|
||||
)
|
||||
}
|
||||
|
||||
/* Given a TLS Extensions data block, go ahead and find the SN block */
|
||||
func GetSNBlock(data []byte) ([]byte, error) {
|
||||
index := 0
|
||||
|
||||
if len(data) < 2 {
|
||||
return []byte{}, fmt.Errorf("Not enough bytes to be an SN block")
|
||||
}
|
||||
|
||||
extensionLength := int((data[index] << 8) + data[index+1])
|
||||
if extensionLength+2 > len(data) {
|
||||
return []byte{}, fmt.Errorf("Extension looks bonkers")
|
||||
}
|
||||
data = data[2 : extensionLength+2]
|
||||
|
||||
for {
|
||||
if index+3 >= len(data) {
|
||||
break
|
||||
}
|
||||
length := int((data[index+2] << 8) + data[index+3])
|
||||
endIndex := index + 4 + length
|
||||
if data[index] == 0x00 && data[index+1] == 0x00 {
|
||||
return data[index+4 : endIndex], nil
|
||||
}
|
||||
|
||||
index = endIndex
|
||||
}
|
||||
|
||||
return []byte{}, fmt.Errorf(
|
||||
"Finished parsing the Extension block without finding an SN block",
|
||||
)
|
||||
}
|
||||
|
||||
/* Given a raw TLS Client Hello, go ahead and find all the Extensions */
|
||||
func GetExtensionBlock(data []byte) ([]byte, error) {
|
||||
/* data[0] - content type
|
||||
* data[1], data[2] - major/minor version
|
||||
* data[3], data[4] - total length
|
||||
* data[...38+5] - start of SessionID (length bit)
|
||||
* data[38+5] - length of SessionID
|
||||
*/
|
||||
var index = TLSHeaderLength + 38
|
||||
|
||||
if len(data) <= index+1 {
|
||||
return []byte{}, fmt.Errorf("Not enough bits to be a Client Hello")
|
||||
}
|
||||
|
||||
/* Index is at SessionID Length bit */
|
||||
if newIndex := index + 1 + int(data[index]); (newIndex + 2) < len(data) {
|
||||
index = newIndex
|
||||
} else {
|
||||
return []byte{}, fmt.Errorf("Not enough bytes for the SessionID")
|
||||
}
|
||||
|
||||
/* Index is at Cipher List Length bits */
|
||||
if newIndex := (index + 2 + int((data[index]<<8)+data[index+1])); (newIndex + 1) < len(data) {
|
||||
index = newIndex
|
||||
} else {
|
||||
return []byte{}, fmt.Errorf("Not enough bytes for the Cipher List")
|
||||
}
|
||||
|
||||
/* Index is now at the compression length bit */
|
||||
if newIndex := index + 1 + int(data[index]); newIndex < len(data) {
|
||||
index = newIndex
|
||||
} else {
|
||||
return []byte{}, fmt.Errorf("Not enough bytes for the compression length")
|
||||
}
|
||||
|
||||
/* Now we're at the Extension start */
|
||||
if len(data[index:]) == 0 {
|
||||
return nil, fmt.Errorf("No extensions")
|
||||
}
|
||||
return data[index:], nil
|
||||
}
|
||||
|
||||
// vim: foldmethod=marker
|
||||
11
vendor/github.com/pkg/errors/.travis.yml
generated
vendored
11
vendor/github.com/pkg/errors/.travis.yml
generated
vendored
|
|
@ -1,15 +1,10 @@
|
|||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.4.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
||||
- make check
|
||||
|
|
|
|||
44
vendor/github.com/pkg/errors/Makefile
generated
vendored
Normal file
44
vendor/github.com/pkg/errors/Makefile
generated
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
PKGS := github.com/pkg/errors
|
||||
SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS))
|
||||
GO := go
|
||||
|
||||
check: test vet gofmt misspell unconvert staticcheck ineffassign unparam
|
||||
|
||||
test:
|
||||
$(GO) test $(PKGS)
|
||||
|
||||
vet: | test
|
||||
$(GO) vet $(PKGS)
|
||||
|
||||
staticcheck:
|
||||
$(GO) get honnef.co/go/tools/cmd/staticcheck
|
||||
staticcheck -checks all $(PKGS)
|
||||
|
||||
misspell:
|
||||
$(GO) get github.com/client9/misspell/cmd/misspell
|
||||
misspell \
|
||||
-locale GB \
|
||||
-error \
|
||||
*.md *.go
|
||||
|
||||
unconvert:
|
||||
$(GO) get github.com/mdempsky/unconvert
|
||||
unconvert -v $(PKGS)
|
||||
|
||||
ineffassign:
|
||||
$(GO) get github.com/gordonklaus/ineffassign
|
||||
find $(SRCDIRS) -name '*.go' | xargs ineffassign
|
||||
|
||||
pedantic: check errcheck
|
||||
|
||||
unparam:
|
||||
$(GO) get mvdan.cc/unparam
|
||||
unparam ./...
|
||||
|
||||
errcheck:
|
||||
$(GO) get github.com/kisielk/errcheck
|
||||
errcheck $(PKGS)
|
||||
|
||||
gofmt:
|
||||
@echo Checking code is gofmted
|
||||
@test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)"
|
||||
11
vendor/github.com/pkg/errors/README.md
generated
vendored
11
vendor/github.com/pkg/errors/README.md
generated
vendored
|
|
@ -41,11 +41,18 @@ default:
|
|||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Roadmap
|
||||
|
||||
With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows:
|
||||
|
||||
- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible)
|
||||
- 1.0. Final release.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
Before sending a PR, please discuss your change by raising an issue.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
8
vendor/github.com/pkg/errors/errors.go
generated
vendored
8
vendor/github.com/pkg/errors/errors.go
generated
vendored
|
|
@ -82,7 +82,7 @@
|
|||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// fmt.Printf("%+s:%d\n", f, f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
|
|
@ -159,6 +159,9 @@ type withStack struct {
|
|||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
// Unwrap provides compatibility for Go 1.13 error chains.
|
||||
func (w *withStack) Unwrap() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
|
|
@ -241,6 +244,9 @@ type withMessage struct {
|
|||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
// Unwrap provides compatibility for Go 1.13 error chains.
|
||||
func (w *withMessage) Unwrap() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
|
|
|
|||
38
vendor/github.com/pkg/errors/go113.go
generated
vendored
Normal file
38
vendor/github.com/pkg/errors/go113.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// +build go1.13
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
stderrors "errors"
|
||||
)
|
||||
|
||||
// Is reports whether any error in err's chain matches target.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error is considered to match a target if it is equal to that target or if
|
||||
// it implements a method Is(error) bool such that Is(target) returns true.
|
||||
func Is(err, target error) bool { return stderrors.Is(err, target) }
|
||||
|
||||
// As finds the first error in err's chain that matches target, and if so, sets
|
||||
// target to that error value and returns true.
|
||||
//
|
||||
// The chain consists of err itself followed by the sequence of errors obtained by
|
||||
// repeatedly calling Unwrap.
|
||||
//
|
||||
// An error matches target if the error's concrete value is assignable to the value
|
||||
// pointed to by target, or if the error has a method As(interface{}) bool such that
|
||||
// As(target) returns true. In the latter case, the As method is responsible for
|
||||
// setting target.
|
||||
//
|
||||
// As will panic if target is not a non-nil pointer to either a type that implements
|
||||
// error, or to any interface type. As returns false if err is nil.
|
||||
func As(err error, target interface{}) bool { return stderrors.As(err, target) }
|
||||
|
||||
// Unwrap returns the result of calling the Unwrap method on err, if err's
|
||||
// type contains an Unwrap method returning error.
|
||||
// Otherwise, Unwrap returns nil.
|
||||
func Unwrap(err error) error {
|
||||
return stderrors.Unwrap(err)
|
||||
}
|
||||
58
vendor/github.com/pkg/errors/stack.go
generated
vendored
58
vendor/github.com/pkg/errors/stack.go
generated
vendored
|
|
@ -5,10 +5,13 @@ import (
|
|||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
// For historical reasons if Frame is interpreted as a uintptr
|
||||
// its value represents the program counter + 1.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
|
|
@ -37,6 +40,15 @@ func (f Frame) line() int {
|
|||
return line
|
||||
}
|
||||
|
||||
// name returns the name of this function, if known.
|
||||
func (f Frame) name() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
return fn.Name()
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
|
|
@ -54,22 +66,16 @@ func (f Frame) Format(s fmt.State, verb rune) {
|
|||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
io.WriteString(s, f.name())
|
||||
io.WriteString(s, "\n\t")
|
||||
io.WriteString(s, f.file())
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
io.WriteString(s, strconv.Itoa(f.line()))
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
io.WriteString(s, funcname(f.name()))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
|
|
@ -77,6 +83,16 @@ func (f Frame) Format(s fmt.State, verb rune) {
|
|||
}
|
||||
}
|
||||
|
||||
// MarshalText formats a stacktrace Frame as a text string. The output is the
|
||||
// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs.
|
||||
func (f Frame) MarshalText() ([]byte, error) {
|
||||
name := f.name()
|
||||
if name == "unknown" {
|
||||
return []byte(name), nil
|
||||
}
|
||||
return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
|
|
@ -94,18 +110,32 @@ func (st StackTrace) Format(s fmt.State, verb rune) {
|
|||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
io.WriteString(s, "\n")
|
||||
f.Format(s, verb)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
st.formatSlice(s, verb)
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
st.formatSlice(s, verb)
|
||||
}
|
||||
}
|
||||
|
||||
// formatSlice will format this StackTrace into the given buffer as a slice of
|
||||
// Frame, only valid when called with '%s' or '%v'.
|
||||
func (st StackTrace) formatSlice(s fmt.State, verb rune) {
|
||||
io.WriteString(s, "[")
|
||||
for i, f := range st {
|
||||
if i > 0 {
|
||||
io.WriteString(s, " ")
|
||||
}
|
||||
f.Format(s, verb)
|
||||
}
|
||||
io.WriteString(s, "]")
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue