|
8 | 8 | "runtime"
|
9 | 9 | "strings"
|
10 | 10 | "testing"
|
| 11 | + "unsafe" |
11 | 12 | )
|
12 | 13 |
|
13 | 14 | func TestCaller(t *testing.T) {
|
@@ -165,3 +166,87 @@ func TestNilName(t *testing.T) {
|
165 | 166 | t.Errorf("Name() = %q, want %q", got, "")
|
166 | 167 | }
|
167 | 168 | }
|
| 169 | + |
| 170 | +var dummy int |
| 171 | + |
| 172 | +func inlined() { |
| 173 | + // Side effect to prevent elimination of this entire function. |
| 174 | + dummy = 42 |
| 175 | +} |
| 176 | + |
| 177 | +// A function with an InlTree. Returns a PC within the function body. |
| 178 | +// |
| 179 | +// No inline to ensure this complete function appears in output. |
| 180 | +// |
| 181 | +//go:noinline |
| 182 | +func tracebackFunc(t *testing.T) uintptr { |
| 183 | + // This body must be more complex than a single call to inlined to get |
| 184 | + // an inline tree. |
| 185 | + inlined() |
| 186 | + inlined() |
| 187 | + |
| 188 | + // Acquire a PC in this function. |
| 189 | + pc, _, _, ok := runtime.Caller(0) |
| 190 | + if !ok { |
| 191 | + t.Fatalf("Caller(0) got ok false, want true") |
| 192 | + } |
| 193 | + |
| 194 | + return pc |
| 195 | +} |
| 196 | + |
| 197 | +// Test that CallersFrames handles PCs in the alignment region between |
| 198 | +// functions (int 3 on amd64) without crashing. |
| 199 | +// |
| 200 | +// Go will never generate a stack trace containing such an address, as it is |
| 201 | +// not a valid call site. However, the cgo traceback function passed to |
| 202 | +// runtime.SetCgoTraceback may not be completely accurate and may incorrect |
| 203 | +// provide PCs in Go code or the alignement region between functions. |
| 204 | +// |
| 205 | +// Go obviously doesn't easily expose the problematic PCs to running programs, |
| 206 | +// so this test is a bit fragile. Some details: |
| 207 | +// |
| 208 | +// * tracebackFunc is our target function. We want to get a PC in the |
| 209 | +// alignment region following this function. This function also has other |
| 210 | +// functions inlined into it to ensure it has an InlTree (this was the source |
| 211 | +// of the bug in issue 44971). |
| 212 | +// |
| 213 | +// * We acquire a PC in tracebackFunc, walking forwards until FuncForPC says |
| 214 | +// we're in a new function. The last PC of the function according to FuncForPC |
| 215 | +// should be in the alignment region (assuming the function isn't already |
| 216 | +// perfectly aligned). |
| 217 | +// |
| 218 | +// This is a regression test for issue 44971. |
| 219 | +func TestFunctionAlignmentTraceback(t *testing.T) { |
| 220 | + pc := tracebackFunc(t) |
| 221 | + |
| 222 | + // Double-check we got the right PC. |
| 223 | + f := runtime.FuncForPC(pc) |
| 224 | + if !strings.HasSuffix(f.Name(), "tracebackFunc") { |
| 225 | + t.Fatalf("Caller(0) = %+v, want tracebackFunc", f) |
| 226 | + } |
| 227 | + |
| 228 | + // Iterate forward until we find a different function. Back up one |
| 229 | + // instruction is (hopefully) an alignment instruction. |
| 230 | + for runtime.FuncForPC(pc) == f { |
| 231 | + pc++ |
| 232 | + } |
| 233 | + pc-- |
| 234 | + |
| 235 | + // Is this an alignment region filler instruction? We only check this |
| 236 | + // on amd64 for simplicity. If this function has no filler, then we may |
| 237 | + // get a false negative, but will never get a false positive. |
| 238 | + if runtime.GOARCH == "amd64" { |
| 239 | + code := *(*uint8)(unsafe.Pointer(pc)) |
| 240 | + if code != 0xcc { // INT $3 |
| 241 | + t.Errorf("PC %v code got %#x want 0xcc", pc, code) |
| 242 | + } |
| 243 | + } |
| 244 | + |
| 245 | + // Finally ensure that Frames.Next doesn't crash when processing this |
| 246 | + // PC. |
| 247 | + frames := runtime.CallersFrames([]uintptr{pc}) |
| 248 | + frame, _ := frames.Next() |
| 249 | + if frame.Func != f { |
| 250 | + t.Errorf("frames.Next() got %+v want %+v", frame.Func, f) |
| 251 | + } |
| 252 | +} |
0 commit comments