]> Cypherpunks.ru repositories - gostls13.git/blob - src/runtime/trace2stack.go
runtime: add execution tracer v2 behind GOEXPERIMENT=exectracer2
[gostls13.git] / src / runtime / trace2stack.go
1 // Copyright 2023 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 //go:build goexperiment.exectracer2
6
7 // Trace stack table and acquisition.
8
9 package runtime
10
11 import (
12         "internal/abi"
13         "internal/goarch"
14         "unsafe"
15 )
16
17 const (
18         // Maximum number of PCs in a single stack trace.
19         // Since events contain only stack id rather than whole stack trace,
20         // we can allow quite large values here.
21         traceStackSize = 128
22
23         // logicalStackSentinel is a sentinel value at pcBuf[0] signifying that
24         // pcBuf[1:] holds a logical stack requiring no further processing. Any other
25         // value at pcBuf[0] represents a skip value to apply to the physical stack in
26         // pcBuf[1:] after inline expansion.
27         logicalStackSentinel = ^uintptr(0)
28 )
29
30 // traceStack captures a stack trace and registers it in the trace stack table.
31 // It then returns its unique ID.
32 //
33 // skip controls the number of leaf frames to omit in order to hide tracer internals
34 // from stack traces, see CL 5523.
35 //
36 // Avoid calling this function directly. gen needs to be the current generation
37 // that this stack trace is being written out for, which needs to be synchronized with
38 // generations moving forward. Prefer traceEventWriter.stack.
39 func traceStack(skip int, mp *m, gen uintptr) uint64 {
40         var pcBuf [traceStackSize]uintptr
41
42         gp := getg()
43         curgp := gp.m.curg
44         nstk := 1
45         if tracefpunwindoff() || mp.hasCgoOnStack() {
46                 // Slow path: Unwind using default unwinder. Used when frame pointer
47                 // unwinding is unavailable or disabled (tracefpunwindoff), or might
48                 // produce incomplete results or crashes (hasCgoOnStack). Note that no
49                 // cgo callback related crashes have been observed yet. The main
50                 // motivation is to take advantage of a potentially registered cgo
51                 // symbolizer.
52                 pcBuf[0] = logicalStackSentinel
53                 if curgp == gp {
54                         nstk += callers(skip+1, pcBuf[1:])
55                 } else if curgp != nil {
56                         nstk += gcallers(curgp, skip, pcBuf[1:])
57                 }
58         } else {
59                 // Fast path: Unwind using frame pointers.
60                 pcBuf[0] = uintptr(skip)
61                 if curgp == gp {
62                         nstk += fpTracebackPCs(unsafe.Pointer(getfp()), pcBuf[1:])
63                 } else if curgp != nil {
64                         // We're called on the g0 stack through mcall(fn) or systemstack(fn). To
65                         // behave like gcallers above, we start unwinding from sched.bp, which
66                         // points to the caller frame of the leaf frame on g's stack. The return
67                         // address of the leaf frame is stored in sched.pc, which we manually
68                         // capture here.
69                         pcBuf[1] = curgp.sched.pc
70                         nstk += 1 + fpTracebackPCs(unsafe.Pointer(curgp.sched.bp), pcBuf[2:])
71                 }
72         }
73         if nstk > 0 {
74                 nstk-- // skip runtime.goexit
75         }
76         if nstk > 0 && curgp.goid == 1 {
77                 nstk-- // skip runtime.main
78         }
79         id := trace.stackTab[gen%2].put(pcBuf[:nstk])
80         return id
81 }
82
83 // traceStackTable maps stack traces (arrays of PC's) to unique uint32 ids.
84 // It is lock-free for reading.
85 type traceStackTable struct {
86         tab traceMap
87 }
88
89 // put returns a unique id for the stack trace pcs and caches it in the table,
90 // if it sees the trace for the first time.
91 func (t *traceStackTable) put(pcs []uintptr) uint64 {
92         if len(pcs) == 0 {
93                 return 0
94         }
95         id, _ := t.tab.put(noescape(unsafe.Pointer(&pcs[0])), uintptr(len(pcs))*unsafe.Sizeof(uintptr(0)))
96         return id
97 }
98
99 // dump writes all previously cached stacks to trace buffers,
100 // releases all memory and resets state.
101 //
102 // This must run on the system stack because it flushes buffers and thus
103 // may acquire trace.lock.
104 //
105 //go:systemstack
106 func (t *traceStackTable) dump(gen uintptr) {
107         w := unsafeTraceWriter(gen, nil)
108
109         // Iterate over the table.
110         lock(&t.tab.lock)
111         for i := range t.tab.tab {
112                 stk := t.tab.bucket(i)
113                 for ; stk != nil; stk = stk.next() {
114                         stack := unsafe.Slice((*uintptr)(unsafe.Pointer(&stk.data[0])), uintptr(len(stk.data))/unsafe.Sizeof(uintptr(0)))
115
116                         // N.B. This might allocate, but that's OK because we're not writing to the M's buffer,
117                         // but one we're about to create (with ensure).
118                         frames := makeTraceFrames(gen, fpunwindExpand(stack))
119
120                         // Returns the maximum number of bytes required to hold the encoded stack, given that
121                         // it contains N frames.
122                         maxBytes := 1 + (2+4*len(frames))*traceBytesPerNumber
123
124                         // Estimate the size of this record. This
125                         // bound is pretty loose, but avoids counting
126                         // lots of varint sizes.
127                         //
128                         // Add 1 because we might also write traceEvStacks.
129                         var flushed bool
130                         w, flushed = w.ensure(1 + maxBytes)
131                         if flushed {
132                                 w.byte(byte(traceEvStacks))
133                         }
134
135                         // Emit stack event.
136                         w.byte(byte(traceEvStack))
137                         w.varint(uint64(stk.id))
138                         w.varint(uint64(len(frames)))
139                         for _, frame := range frames {
140                                 w.varint(uint64(frame.PC))
141                                 w.varint(frame.funcID)
142                                 w.varint(frame.fileID)
143                                 w.varint(frame.line)
144                         }
145                 }
146         }
147         t.tab.reset()
148         unlock(&t.tab.lock)
149
150         w.flush().end()
151 }
152
153 // makeTraceFrames returns the frames corresponding to pcs. It may
154 // allocate and may emit trace events.
155 func makeTraceFrames(gen uintptr, pcs []uintptr) []traceFrame {
156         frames := make([]traceFrame, 0, len(pcs))
157         ci := CallersFrames(pcs)
158         for {
159                 f, more := ci.Next()
160                 frames = append(frames, makeTraceFrame(gen, f))
161                 if !more {
162                         return frames
163                 }
164         }
165 }
166
167 type traceFrame struct {
168         PC     uintptr
169         funcID uint64
170         fileID uint64
171         line   uint64
172 }
173
174 // makeTraceFrame sets up a traceFrame for a frame.
175 func makeTraceFrame(gen uintptr, f Frame) traceFrame {
176         var frame traceFrame
177         frame.PC = f.PC
178
179         fn := f.Function
180         const maxLen = 1 << 10
181         if len(fn) > maxLen {
182                 fn = fn[len(fn)-maxLen:]
183         }
184         frame.funcID = trace.stringTab[gen%2].put(gen, fn)
185         frame.line = uint64(f.Line)
186         file := f.File
187         if len(file) > maxLen {
188                 file = file[len(file)-maxLen:]
189         }
190         frame.fileID = trace.stringTab[gen%2].put(gen, file)
191         return frame
192 }
193
194 // tracefpunwindoff returns true if frame pointer unwinding for the tracer is
195 // disabled via GODEBUG or not supported by the architecture.
196 func tracefpunwindoff() bool {
197         return debug.tracefpunwindoff != 0 || (goarch.ArchFamily != goarch.AMD64 && goarch.ArchFamily != goarch.ARM64)
198 }
199
200 // fpTracebackPCs populates pcBuf with the return addresses for each frame and
201 // returns the number of PCs written to pcBuf. The returned PCs correspond to
202 // "physical frames" rather than "logical frames"; that is if A is inlined into
203 // B, this will return a PC for only B.
204 func fpTracebackPCs(fp unsafe.Pointer, pcBuf []uintptr) (i int) {
205         for i = 0; i < len(pcBuf) && fp != nil; i++ {
206                 // return addr sits one word above the frame pointer
207                 pcBuf[i] = *(*uintptr)(unsafe.Pointer(uintptr(fp) + goarch.PtrSize))
208                 // follow the frame pointer to the next one
209                 fp = unsafe.Pointer(*(*uintptr)(fp))
210         }
211         return i
212 }
213
214 // fpunwindExpand checks if pcBuf contains logical frames (which include inlined
215 // frames) or physical frames (produced by frame pointer unwinding) using a
216 // sentinel value in pcBuf[0]. Logical frames are simply returned without the
217 // sentinel. Physical frames are turned into logical frames via inline unwinding
218 // and by applying the skip value that's stored in pcBuf[0].
219 func fpunwindExpand(pcBuf []uintptr) []uintptr {
220         if len(pcBuf) > 0 && pcBuf[0] == logicalStackSentinel {
221                 // pcBuf contains logical rather than inlined frames, skip has already been
222                 // applied, just return it without the sentinel value in pcBuf[0].
223                 return pcBuf[1:]
224         }
225
226         var (
227                 lastFuncID = abi.FuncIDNormal
228                 newPCBuf   = make([]uintptr, 0, traceStackSize)
229                 skip       = pcBuf[0]
230                 // skipOrAdd skips or appends retPC to newPCBuf and returns true if more
231                 // pcs can be added.
232                 skipOrAdd = func(retPC uintptr) bool {
233                         if skip > 0 {
234                                 skip--
235                         } else {
236                                 newPCBuf = append(newPCBuf, retPC)
237                         }
238                         return len(newPCBuf) < cap(newPCBuf)
239                 }
240         )
241
242 outer:
243         for _, retPC := range pcBuf[1:] {
244                 callPC := retPC - 1
245                 fi := findfunc(callPC)
246                 if !fi.valid() {
247                         // There is no funcInfo if callPC belongs to a C function. In this case
248                         // we still keep the pc, but don't attempt to expand inlined frames.
249                         if more := skipOrAdd(retPC); !more {
250                                 break outer
251                         }
252                         continue
253                 }
254
255                 u, uf := newInlineUnwinder(fi, callPC)
256                 for ; uf.valid(); uf = u.next(uf) {
257                         sf := u.srcFunc(uf)
258                         if sf.funcID == abi.FuncIDWrapper && elideWrapperCalling(lastFuncID) {
259                                 // ignore wrappers
260                         } else if more := skipOrAdd(uf.pc + 1); !more {
261                                 break outer
262                         }
263                         lastFuncID = sf.funcID
264                 }
265         }
266         return newPCBuf
267 }
268
269 // startPCForTrace returns the start PC of a goroutine for tracing purposes.
270 // If pc is a wrapper, it returns the PC of the wrapped function. Otherwise it
271 // returns pc.
272 func startPCForTrace(pc uintptr) uintptr {
273         f := findfunc(pc)
274         if !f.valid() {
275                 return pc // may happen for locked g in extra M since its pc is 0.
276         }
277         w := funcdata(f, abi.FUNCDATA_WrapInfo)
278         if w == nil {
279                 return pc // not a wrapper
280         }
281         return f.datap.textAddr(*(*uint32)(w))
282 }