Skip to content

Commit 2ac150b

Browse files
committed
Special handling for stack entries above runtime.sigpanic.
1 parent 2db4a24 commit 2ac150b

File tree

3 files changed

+87
-40
lines changed

3 files changed

+87
-40
lines changed

stack.go

Lines changed: 69 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,23 +30,15 @@ type Call uintptr
3030
// %+v equivalent to %+s:%d
3131
// %#v equivalent to %#s:%d
3232
func (pc Call) Format(s fmt.State, c rune) {
33-
// BUG(ChrisHines): Subtracting one from pc is a work around for
34-
// https://code.google.com/p/go/issues/detail?id=7690. The idea for this
35-
// work around comes from rsc's initial patch at
36-
// https://codereview.appspot.com/84100043/#ps20001, but as noted in the
37-
// issue discussion, it is not a complete fix since it doesn't handle some
38-
// cases involving signals. Just the same, it handles all of the other
39-
// cases I have tested.
40-
pcFix := uintptr(pc) - 1
41-
fn := runtime.FuncForPC(pcFix)
33+
fn := runtime.FuncForPC(uintptr(pc))
4234
if fn == nil {
4335
fmt.Fprintf(s, "%%!%c(NOFUNC)", c)
4436
return
4537
}
4638

4739
switch c {
4840
case 's', 'v':
49-
file, line := fn.FileLine(pcFix)
41+
file, line := fn.FileLine(uintptr(pc))
5042
switch {
5143
case s.Flag('#'):
5244
// done
@@ -94,7 +86,7 @@ func (pc Call) Format(s fmt.State, c rune) {
9486
}
9587

9688
case 'd':
97-
_, line := fn.FileLine(pcFix)
89+
_, line := fn.FileLine(uintptr(pc))
9890
fmt.Fprint(s, line)
9991

10092
case 'n':
@@ -116,31 +108,29 @@ func (pc Call) Format(s fmt.State, c rune) {
116108
// name returns the import path qualified name of the function containing the
117109
// call.
118110
func (pc Call) name() string {
119-
pcFix := uintptr(pc) - 1 // work around for go issue #7690
120-
fn := runtime.FuncForPC(pcFix)
111+
fn := runtime.FuncForPC(uintptr(pc))
121112
if fn == nil {
122113
return "???"
123114
}
124115
return fn.Name()
125116
}
126117

127118
func (pc Call) file() string {
128-
pcFix := uintptr(pc) - 1 // work around for go issue #7690
129-
fn := runtime.FuncForPC(pcFix)
119+
fn := runtime.FuncForPC(uintptr(pc))
130120
if fn == nil {
131121
return "???"
132122
}
133-
file, _ := fn.FileLine(pcFix)
123+
file, _ := fn.FileLine(uintptr(pc))
134124
return file
135125
}
136126

137-
// Trace records a sequence of function invocations from a goroutine stack.
138-
type Trace []Call
127+
// CallStack records a sequence of function invocations from a goroutine stack.
128+
type CallStack []Call
139129

140-
// Format implements fmt.Formatter by printing the Trace as square brackes ([,
130+
// Format implements fmt.Formatter by printing the CallStack as square brackes ([,
141131
// ]) surrounding a space separated list of Calls each formatted with the
142132
// supplied verb and options.
143-
func (pcs Trace) Format(s fmt.State, c rune) {
133+
func (pcs CallStack) Format(s fmt.State, c rune) {
144134
s.Write([]byte("["))
145135
for i, pc := range pcs {
146136
if i > 0 {
@@ -151,52 +141,98 @@ func (pcs Trace) Format(s fmt.State, c rune) {
151141
s.Write([]byte("]"))
152142
}
153143

144+
// findSigpanic intentially executes faulting code to generate a stack
145+
// trace containing an entry for runtime.sigpanic.
146+
func findSigpanic() *runtime.Func {
147+
var fn *runtime.Func
148+
func() int {
149+
defer func() {
150+
if p := recover(); p != nil {
151+
pcs := pcStackPool.Get().([]uintptr)
152+
pcs = pcs[:cap(pcs)]
153+
n := runtime.Callers(2, pcs)
154+
for _, pc := range pcs[:n] {
155+
f := runtime.FuncForPC(pc)
156+
if f.Name() == "runtime.sigpanic" {
157+
fn = f
158+
break
159+
}
160+
}
161+
pcStackPool.Put(pcs)
162+
}
163+
}()
164+
// intentional division by zero fault
165+
a, b := 1, 0
166+
return a / b
167+
}()
168+
return fn
169+
}
170+
171+
var (
172+
sigpanic *runtime.Func
173+
spOnce sync.Once
174+
)
175+
154176
var pcStackPool = sync.Pool{
155177
New: func() interface{} { return make([]uintptr, 1000) },
156178
}
157179

158-
// Callers returns a Trace for the current goroutine with element 0
180+
// Trace returns a CallStack for the current goroutine with element 0
159181
// identifying the calling function.
160-
func Callers() Trace {
182+
func Trace() CallStack {
183+
spOnce.Do(func() {
184+
sigpanic = findSigpanic()
185+
})
186+
161187
pcs := pcStackPool.Get().([]uintptr)
162188
pcs = pcs[:cap(pcs)]
189+
163190
n := runtime.Callers(2, pcs)
164191
cs := make([]Call, n)
192+
193+
var prevFn *runtime.Func
165194
for i, pc := range pcs[:n] {
166-
cs[i] = Call(pc)
195+
pcFix := pc
196+
if prevFn != sigpanic {
197+
pcFix--
198+
}
199+
cs[i] = Call(pcFix)
200+
prevFn = runtime.FuncForPC(pc)
167201
}
202+
168203
pcStackPool.Put(pcs)
204+
169205
return cs
170206
}
171207

172-
// TrimBelow returns a slice of the Trace with all entries below pc removed.
173-
func (pcs Trace) TrimBelow(pc Call) Trace {
208+
// TrimBelow returns a slice of the CallStack with all entries below pc removed.
209+
func (pcs CallStack) TrimBelow(pc Call) CallStack {
174210
for len(pcs) > 0 && pcs[0] != pc {
175211
pcs = pcs[1:]
176212
}
177213
return pcs
178214
}
179215

180-
// TrimAbove returns a slice of the Trace with all entries above pc removed.
181-
func (pcs Trace) TrimAbove(pc Call) Trace {
216+
// TrimAbove returns a slice of the CallStack with all entries above pc removed.
217+
func (pcs CallStack) TrimAbove(pc Call) CallStack {
182218
for len(pcs) > 0 && pcs[len(pcs)-1] != pc {
183219
pcs = pcs[:len(pcs)-1]
184220
}
185221
return pcs
186222
}
187223

188-
// TrimBelowName returns a slice of the Trace with all entries below the
224+
// TrimBelowName returns a slice of the CallStack with all entries below the
189225
// lowest with function name name removed.
190-
func (pcs Trace) TrimBelowName(name string) Trace {
226+
func (pcs CallStack) TrimBelowName(name string) CallStack {
191227
for len(pcs) > 0 && pcs[0].name() != name {
192228
pcs = pcs[1:]
193229
}
194230
return pcs
195231
}
196232

197-
// TrimAboveName returns a slice of the Trace with all entries above the
233+
// TrimAboveName returns a slice of the CallStack with all entries above the
198234
// highest with function name name removed.
199-
func (pcs Trace) TrimAboveName(name string) Trace {
235+
func (pcs CallStack) TrimAboveName(name string) CallStack {
200236
for len(pcs) > 0 && pcs[len(pcs)-1].name() != name {
201237
pcs = pcs[:len(pcs)-1]
202238
}
@@ -219,10 +255,10 @@ func inGoroot(path string) bool {
219255
return strings.HasPrefix(path, goroot)
220256
}
221257

222-
// TrimRuntime returns a slice of the Trace with the topmost entries from the
258+
// TrimRuntime returns a slice of the CallStack with the topmost entries from the
223259
// go runtime removed. It considers any calls originating from files under
224260
// GOROOT as part of the runtime.
225-
func (pcs Trace) TrimRuntime() Trace {
261+
func (pcs CallStack) TrimRuntime() CallStack {
226262
for len(pcs) > 0 && inGoroot(pcs[len(pcs)-1].file()) {
227263
pcs = pcs[:len(pcs)-1]
228264
}

stack_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,39 +190,39 @@ func BenchmarkCallPlusNFmt(b *testing.B) {
190190
}
191191
}
192192

193-
func BenchmarkCallers(b *testing.B) {
193+
func BenchmarkTrace(b *testing.B) {
194194
for i := 0; i < b.N; i++ {
195-
stack.Callers()
195+
stack.Trace()
196196
}
197197
}
198198

199-
func deepStack(depth int, b *testing.B) stack.Trace {
199+
func deepStack(depth int, b *testing.B) stack.CallStack {
200200
if depth > 0 {
201201
return deepStack(depth-1, b)
202202
}
203203
b.StartTimer()
204-
s := stack.Callers()
204+
s := stack.Trace()
205205
b.StopTimer()
206206
return s
207207
}
208208

209-
func BenchmarkCallers10(b *testing.B) {
209+
func BenchmarkTrace10(b *testing.B) {
210210
b.StopTimer()
211211

212212
for i := 0; i < b.N; i++ {
213213
deepStack(10, b)
214214
}
215215
}
216216

217-
func BenchmarkCallers50(b *testing.B) {
217+
func BenchmarkTrace50(b *testing.B) {
218218
b.StopTimer()
219219

220220
for i := 0; i < b.N; i++ {
221221
deepStack(50, b)
222222
}
223223
}
224224

225-
func BenchmarkCallers100(b *testing.B) {
225+
func BenchmarkTrace100(b *testing.B) {
226226
b.StopTimer()
227227

228228
for i := 0; i < b.N; i++ {

stackinternal_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package stack
2+
3+
import "testing"
4+
5+
func TestFindSigpanic(t *testing.T) {
6+
t.Parallel()
7+
sp := findSigpanic()
8+
if got, want := sp.Name(), "runtime.sigpanic"; got != want {
9+
t.Errorf("got == %v, want == %v", got, want)
10+
}
11+
}

0 commit comments

Comments
 (0)