gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/sentry/kernel/task_log.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kernel 16 17 import ( 18 "fmt" 19 "runtime/trace" 20 "sort" 21 22 "gvisor.dev/gvisor/pkg/context" 23 "gvisor.dev/gvisor/pkg/hostarch" 24 "gvisor.dev/gvisor/pkg/log" 25 "gvisor.dev/gvisor/pkg/usermem" 26 ) 27 28 const ( 29 // maxStackDebugBytes is the maximum number of user stack bytes that may be 30 // printed by debugDumpStack. 31 maxStackDebugBytes = 1024 32 // maxCodeDebugBytes is the maximum number of user code bytes that may be 33 // printed by debugDumpCode. 34 maxCodeDebugBytes = 128 35 ) 36 37 // Infof logs an formatted info message by calling log.Infof. 38 func (t *Task) Infof(fmt string, v ...any) { 39 if log.IsLogging(log.Info) { 40 log.InfofAtDepth(1, *t.logPrefix.Load()+fmt, v...) 41 } 42 } 43 44 // Warningf logs a warning string by calling log.Warningf. 45 func (t *Task) Warningf(fmt string, v ...any) { 46 if log.IsLogging(log.Warning) { 47 log.WarningfAtDepth(1, *t.logPrefix.Load()+fmt, v...) 48 } 49 } 50 51 // Debugf creates a debug string that includes the task ID. 52 func (t *Task) Debugf(fmt string, v ...any) { 53 if log.IsLogging(log.Debug) { 54 log.DebugfAtDepth(1, *t.logPrefix.Load()+fmt, v...) 55 } 56 } 57 58 // IsLogging returns true iff this level is being logged. 59 func (t *Task) IsLogging(level log.Level) bool { 60 return log.IsLogging(level) 61 } 62 63 // DebugDumpState logs task state at log level debug. 64 // 65 // Preconditions: The caller must be running on the task goroutine. 66 func (t *Task) DebugDumpState() { 67 t.debugDumpRegisters() 68 t.debugDumpStack() 69 t.debugDumpCode() 70 if mm := t.MemoryManager(); mm != nil { 71 t.Debugf("Mappings:\n%s", mm) 72 } 73 t.Debugf("FDTable:\n%s", t.fdTable) 74 } 75 76 // debugDumpRegisters logs register state at log level debug. 77 // 78 // Preconditions: The caller must be running on the task goroutine. 79 func (t *Task) debugDumpRegisters() { 80 if !t.IsLogging(log.Debug) { 81 return 82 } 83 regmap, err := t.Arch().RegisterMap() 84 if err != nil { 85 t.Debugf("Registers: %v", err) 86 } else { 87 t.Debugf("Registers:") 88 var regs []string 89 for reg := range regmap { 90 regs = append(regs, reg) 91 } 92 sort.Strings(regs) 93 for _, reg := range regs { 94 t.Debugf("%-8s = %016x", reg, regmap[reg]) 95 } 96 } 97 } 98 99 // debugDumpStack logs user stack contents at log level debug. 100 // 101 // Preconditions: The caller must be running on the task goroutine. 102 func (t *Task) debugDumpStack() { 103 if !t.IsLogging(log.Debug) { 104 return 105 } 106 m := t.MemoryManager() 107 if m == nil { 108 t.Debugf("Memory manager for task is gone, skipping application stack dump.") 109 return 110 } 111 t.Debugf("Stack:") 112 start := hostarch.Addr(t.Arch().Stack()) 113 // Round addr down to a 16-byte boundary. 114 start &= ^hostarch.Addr(15) 115 // Print 16 bytes per line, one byte at a time. 116 for offset := uint64(0); offset < maxStackDebugBytes; offset += 16 { 117 addr, ok := start.AddLength(offset) 118 if !ok { 119 break 120 } 121 var data [16]byte 122 n, err := m.CopyIn(t, addr, data[:], usermem.IOOpts{ 123 IgnorePermissions: true, 124 }) 125 // Print as much of the line as we can, even if an error was 126 // encountered. 127 if n > 0 { 128 t.Debugf("%x: % x", addr, data[:n]) 129 } 130 if err != nil { 131 t.Debugf("Error reading stack at address %x: %v", addr+hostarch.Addr(n), err) 132 break 133 } 134 } 135 } 136 137 // debugDumpCode logs user code contents at log level debug. 138 // 139 // Preconditions: The caller must be running on the task goroutine. 140 func (t *Task) debugDumpCode() { 141 if !t.IsLogging(log.Debug) { 142 return 143 } 144 m := t.MemoryManager() 145 if m == nil { 146 t.Debugf("Memory manager for task is gone, skipping application code dump.") 147 return 148 } 149 t.Debugf("Code:") 150 // Print code on both sides of the instruction register. 151 start := hostarch.Addr(t.Arch().IP()) - maxCodeDebugBytes/2 152 // Round addr down to a 16-byte boundary. 153 start &= ^hostarch.Addr(15) 154 // Print 16 bytes per line, one byte at a time. 155 for offset := uint64(0); offset < maxCodeDebugBytes; offset += 16 { 156 addr, ok := start.AddLength(offset) 157 if !ok { 158 break 159 } 160 var data [16]byte 161 n, err := m.CopyIn(t, addr, data[:], usermem.IOOpts{ 162 IgnorePermissions: true, 163 }) 164 // Print as much of the line as we can, even if an error was 165 // encountered. 166 if n > 0 { 167 t.Debugf("%x: % x", addr, data[:n]) 168 } 169 if err != nil { 170 t.Debugf("Error reading stack at address %x: %v", addr+hostarch.Addr(n), err) 171 break 172 } 173 } 174 } 175 176 // trace definitions. 177 // 178 // Note that all region names are prefixed by ':' in order to ensure that they 179 // are lexically ordered before all system calls, which use the naked system 180 // call name (e.g. "read") for maximum clarity. 181 const ( 182 traceCategory = "task" 183 runRegion = ":run" 184 blockRegion = ":block" 185 faultRegion = ":fault" 186 ) 187 188 // updateInfoLocked updates the task's cached log prefix and tracing 189 // information to reflect its current thread ID. 190 // 191 // Preconditions: The task's owning TaskSet.mu must be locked. 192 func (t *Task) updateInfoLocked() { 193 // Log the TID and PID in root pidns and t's pidns. 194 rootPID := t.tg.pidns.owner.Root.tgids[t.tg] 195 rootTID := t.tg.pidns.owner.Root.tids[t] 196 pid := t.tg.pidns.tgids[t.tg] 197 tid := t.tg.pidns.tids[t] 198 if rootPID == pid && rootTID == tid { 199 prefix := fmt.Sprintf("[% 4d:% 4d] ", pid, tid) 200 t.logPrefix.Store(&prefix) 201 } else { 202 prefix := fmt.Sprintf("[% 4d(%4d):% 4d(%4d)] ", rootPID, pid, rootTID, tid) 203 t.logPrefix.Store(&prefix) 204 } 205 206 t.rebuildTraceContext(rootTID) 207 } 208 209 // rebuildTraceContext rebuilds the trace context. 210 // 211 // Precondition: the passed tid must be the tid in the root namespace. 212 func (t *Task) rebuildTraceContext(tid ThreadID) { 213 // Re-initialize the trace context. 214 if t.traceTask != nil { 215 t.traceTask.End() 216 } 217 218 // Note that we define the "task type" to be the dynamic TID. This does 219 // not align perfectly with the documentation for "tasks" in the 220 // tracing package. Tasks may be assumed to be bounded by analysis 221 // tools. However, if we just use a generic "task" type here, then the 222 // "user-defined tasks" page on the tracing dashboard becomes nearly 223 // unusable, as it loads all traces from all tasks. 224 // 225 // We can assume that the number of tasks in the system is not 226 // arbitrarily large (in general it won't be, especially for cases 227 // where we're collecting a brief profile), so using the TID is a 228 // reasonable compromise in this case. 229 t.traceContext, t.traceTask = trace.NewTask(context.Background(), fmt.Sprintf("tid:%d", tid)) 230 } 231 232 // traceCloneEvent is called when a new task is spawned. 233 // 234 // ntid must be the new task's ThreadID in the root namespace. 235 func (t *Task) traceCloneEvent(ntid ThreadID) { 236 if !trace.IsEnabled() { 237 return 238 } 239 trace.Logf(t.traceContext, traceCategory, "spawn: %d", ntid) 240 } 241 242 // traceExitEvent is called when a task exits. 243 func (t *Task) traceExitEvent() { 244 if !trace.IsEnabled() { 245 return 246 } 247 trace.Logf(t.traceContext, traceCategory, "exit status: %s", t.exitStatus) 248 } 249 250 // traceExecEvent is called when a task calls exec. 251 func (t *Task) traceExecEvent(image *TaskImage) { 252 if !trace.IsEnabled() { 253 return 254 } 255 file := image.MemoryManager.Executable() 256 if file == nil { 257 trace.Logf(t.traceContext, traceCategory, "exec: << unknown >>") 258 return 259 } 260 defer file.DecRef(t) 261 262 // traceExecEvent function may be called before the task goroutine 263 // starts, so we must use the async context. 264 name := file.MappedName(t.AsyncContext()) 265 trace.Logf(t.traceContext, traceCategory, "exec: %s", name) 266 }