github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/runsc/cmd/debug.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 cmd 16 17 import ( 18 "context" 19 "os" 20 "os/signal" 21 "strconv" 22 "strings" 23 "sync" 24 "time" 25 26 "github.com/google/subcommands" 27 "golang.org/x/sys/unix" 28 "github.com/metacubex/gvisor/pkg/log" 29 "github.com/metacubex/gvisor/pkg/sentry/control" 30 "github.com/metacubex/gvisor/runsc/cmd/util" 31 "github.com/metacubex/gvisor/runsc/config" 32 "github.com/metacubex/gvisor/runsc/container" 33 "github.com/metacubex/gvisor/runsc/flag" 34 ) 35 36 // Debug implements subcommands.Command for the "debug" command. 37 type Debug struct { 38 pid int 39 stacks bool 40 signal int 41 profileBlock string 42 profileCPU string 43 profileHeap string 44 profileMutex string 45 trace string 46 strace string 47 logLevel string 48 logPackets string 49 delay time.Duration 50 duration time.Duration 51 ps bool 52 mount string 53 } 54 55 // Name implements subcommands.Command. 56 func (*Debug) Name() string { 57 return "debug" 58 } 59 60 // Synopsis implements subcommands.Command. 61 func (*Debug) Synopsis() string { 62 return "shows a variety of debug information" 63 } 64 65 // Usage implements subcommands.Command. 66 func (*Debug) Usage() string { 67 return `debug [flags] <container id>` 68 } 69 70 // SetFlags implements subcommands.Command. 71 func (d *Debug) SetFlags(f *flag.FlagSet) { 72 f.IntVar(&d.pid, "pid", 0, "sandbox process ID. Container ID is not necessary if this is set") 73 f.BoolVar(&d.stacks, "stacks", false, "if true, dumps all sandbox stacks to the log") 74 f.StringVar(&d.profileBlock, "profile-block", "", "writes block profile to the given file.") 75 f.StringVar(&d.profileCPU, "profile-cpu", "", "writes CPU profile to the given file.") 76 f.StringVar(&d.profileHeap, "profile-heap", "", "writes heap profile to the given file.") 77 f.StringVar(&d.profileMutex, "profile-mutex", "", "writes mutex profile to the given file.") 78 f.DurationVar(&d.delay, "delay", time.Hour, "amount of time to delay for collecting heap and goroutine profiles.") 79 f.DurationVar(&d.duration, "duration", time.Hour, "amount of time to wait for CPU and trace profiles.") 80 f.StringVar(&d.trace, "trace", "", "writes an execution trace to the given file.") 81 f.IntVar(&d.signal, "signal", -1, "sends signal to the sandbox") 82 f.StringVar(&d.strace, "strace", "", `A comma separated list of syscalls to trace. "all" enables all traces, "off" disables all.`) 83 f.StringVar(&d.logLevel, "log-level", "", "The log level to set: warning (0), info (1), or debug (2).") 84 f.StringVar(&d.logPackets, "log-packets", "", "A boolean value to enable or disable packet logging: true or false.") 85 f.BoolVar(&d.ps, "ps", false, "lists processes") 86 f.StringVar(&d.mount, "mount", "", "Mount a filesystem (-mount fstype:source:destination).") 87 } 88 89 // Execute implements subcommands.Command.Execute. 90 func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 91 var c *container.Container 92 conf := args[0].(*config.Config) 93 94 if conf.ProfileBlock != "" || conf.ProfileCPU != "" || conf.ProfileHeap != "" || conf.ProfileMutex != "" { 95 return util.Errorf("global -profile-{block,cpu,heap,mutex} flags have no effect on runsc debug. Pass runsc debug -profile-{block,cpu,heap,mutex} instead") 96 } 97 if conf.TraceFile != "" { 98 return util.Errorf("global -trace flag has no effect on runsc debug. Pass runsc debug -trace instead") 99 } 100 101 if d.pid == 0 { 102 // No pid, container ID must have been provided. 103 if f.NArg() != 1 { 104 f.Usage() 105 return subcommands.ExitUsageError 106 } 107 id := f.Arg(0) 108 109 var err error 110 c, err = container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{SkipCheck: true}) 111 if err != nil { 112 return util.Errorf("loading container %q: %v", f.Arg(0), err) 113 } 114 } else { 115 if f.NArg() != 0 { 116 f.Usage() 117 return subcommands.ExitUsageError 118 } 119 // Go over all sandboxes and find the one that matches PID. 120 ids, err := container.ListSandboxes(conf.RootDir) 121 if err != nil { 122 return util.Errorf("listing containers: %v", err) 123 } 124 for _, id := range ids { 125 candidate, err := container.Load(conf.RootDir, id, container.LoadOpts{Exact: true, SkipCheck: true}) 126 if err != nil { 127 log.Warningf("Skipping container %q: %v", id, err) 128 continue 129 } 130 if candidate.SandboxPid() == d.pid { 131 c = candidate 132 break 133 } 134 } 135 if c == nil { 136 return util.Errorf("container with PID %d not found", d.pid) 137 } 138 } 139 140 if !c.IsSandboxRunning() { 141 return util.Errorf("container sandbox is not running") 142 } 143 util.Infof("Found sandbox %q, PID: %d", c.Sandbox.ID, c.Sandbox.Getpid()) 144 145 // Perform synchronous actions. 146 if d.signal > 0 { 147 pid := c.Sandbox.Getpid() 148 util.Infof("Sending signal %d to process: %d", d.signal, pid) 149 if err := unix.Kill(pid, unix.Signal(d.signal)); err != nil { 150 return util.Errorf("failed to send signal %d to processs %d", d.signal, pid) 151 } 152 } 153 if d.stacks { 154 util.Infof("Retrieving sandbox stacks") 155 stacks, err := c.Sandbox.Stacks() 156 if err != nil { 157 return util.Errorf("retrieving stacks: %v", err) 158 } 159 util.Infof(" *** Stack dump ***\n%s", stacks) 160 } 161 if d.strace != "" || len(d.logLevel) != 0 || len(d.logPackets) != 0 { 162 args := control.LoggingArgs{} 163 switch strings.ToLower(d.strace) { 164 case "": 165 // strace not set, nothing to do here. 166 167 case "off": 168 util.Infof("Disabling strace") 169 args.SetStrace = true 170 171 case "all": 172 util.Infof("Enabling all straces") 173 args.SetStrace = true 174 args.EnableStrace = true 175 176 default: 177 util.Infof("Enabling strace for syscalls: %s", d.strace) 178 args.SetStrace = true 179 args.EnableStrace = true 180 args.StraceAllowlist = strings.Split(d.strace, ",") 181 } 182 183 if len(d.logLevel) != 0 { 184 args.SetLevel = true 185 switch strings.ToLower(d.logLevel) { 186 case "warning", "0": 187 args.Level = log.Warning 188 case "info", "1": 189 args.Level = log.Info 190 case "debug", "2": 191 args.Level = log.Debug 192 default: 193 return util.Errorf("invalid log level %q", d.logLevel) 194 } 195 util.Infof("Setting log level %v", args.Level) 196 } 197 198 if len(d.logPackets) != 0 { 199 args.SetLogPackets = true 200 lp, err := strconv.ParseBool(d.logPackets) 201 if err != nil { 202 return util.Errorf("invalid value for log_packets %q", d.logPackets) 203 } 204 args.LogPackets = lp 205 if args.LogPackets { 206 util.Infof("Enabling packet logging") 207 } else { 208 util.Infof("Disabling packet logging") 209 } 210 } 211 212 if err := c.Sandbox.ChangeLogging(args); err != nil { 213 return util.Errorf(err.Error()) 214 } 215 util.Infof("Logging options changed") 216 } 217 if d.ps { 218 util.Infof("Retrieving process list") 219 pList, err := c.Processes() 220 if err != nil { 221 util.Fatalf("getting processes for container: %v", err) 222 } 223 o, err := control.ProcessListToJSON(pList) 224 if err != nil { 225 util.Fatalf("generating JSON: %v", err) 226 } 227 util.Infof("%s", o) 228 } 229 if d.mount != "" { 230 opts := strings.Split(d.mount, ":") 231 if len(opts) != 3 { 232 util.Fatalf("Mount failed: invalid option: %v", d.mount) 233 } 234 fstype := opts[0] 235 src := opts[1] 236 dest := opts[2] 237 if err := c.Sandbox.Mount(c.ID, fstype, src, dest); err != nil { 238 util.Fatalf(err.Error()) 239 } 240 } 241 242 // Open profiling files. 243 var ( 244 blockFile *os.File 245 cpuFile *os.File 246 heapFile *os.File 247 mutexFile *os.File 248 traceFile *os.File 249 ) 250 if d.profileBlock != "" { 251 f, err := os.OpenFile(d.profileBlock, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 252 if err != nil { 253 return util.Errorf("error opening blocking profile output: %v", err) 254 } 255 defer f.Close() 256 blockFile = f 257 } 258 if d.profileCPU != "" { 259 f, err := os.OpenFile(d.profileCPU, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 260 if err != nil { 261 return util.Errorf("error opening cpu profile output: %v", err) 262 } 263 defer f.Close() 264 cpuFile = f 265 } 266 if d.profileHeap != "" { 267 f, err := os.OpenFile(d.profileHeap, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 268 if err != nil { 269 return util.Errorf("error opening heap profile output: %v", err) 270 } 271 defer f.Close() 272 heapFile = f 273 } 274 if d.profileMutex != "" { 275 f, err := os.OpenFile(d.profileMutex, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 276 if err != nil { 277 return util.Errorf("error opening mutex profile output: %v", err) 278 } 279 defer f.Close() 280 mutexFile = f 281 } 282 if d.trace != "" { 283 f, err := os.OpenFile(d.trace, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) 284 if err != nil { 285 return util.Errorf("error opening trace profile output: %v", err) 286 } 287 traceFile = f 288 } 289 290 // Collect profiles. 291 var ( 292 wg sync.WaitGroup 293 blockErr error 294 cpuErr error 295 heapErr error 296 mutexErr error 297 traceErr error 298 ) 299 if blockFile != nil { 300 wg.Add(1) 301 go func() { 302 defer wg.Done() 303 blockErr = c.Sandbox.BlockProfile(blockFile, d.duration) 304 }() 305 } 306 if cpuFile != nil { 307 wg.Add(1) 308 go func() { 309 defer wg.Done() 310 cpuErr = c.Sandbox.CPUProfile(cpuFile, d.duration) 311 }() 312 } 313 if heapFile != nil { 314 wg.Add(1) 315 go func() { 316 defer wg.Done() 317 heapErr = c.Sandbox.HeapProfile(heapFile, d.delay) 318 }() 319 } 320 if mutexFile != nil { 321 wg.Add(1) 322 go func() { 323 defer wg.Done() 324 mutexErr = c.Sandbox.MutexProfile(mutexFile, d.duration) 325 }() 326 } 327 if traceFile != nil { 328 wg.Add(1) 329 go func() { 330 defer wg.Done() 331 traceErr = c.Sandbox.Trace(traceFile, d.duration) 332 }() 333 } 334 335 // Before sleeping, allow us to catch signals and try to exit 336 // gracefully before just exiting. If we can't wait for wg, then 337 // we will not be able to read the errors below safely. 338 readyChan := make(chan struct{}) 339 go func() { 340 defer close(readyChan) 341 wg.Wait() 342 }() 343 signals := make(chan os.Signal, 1) 344 signal.Notify(signals, unix.SIGTERM, unix.SIGINT) 345 select { 346 case <-readyChan: 347 break // Safe to proceed. 348 case <-signals: 349 util.Infof("caught signal, waiting at most one more second.") 350 select { 351 case <-signals: 352 util.Infof("caught second signal, exiting immediately.") 353 os.Exit(1) // Not finished. 354 case <-time.After(time.Second): 355 util.Infof("timeout, exiting.") 356 os.Exit(1) // Not finished. 357 case <-readyChan: 358 break // Safe to proceed. 359 } 360 } 361 362 // Collect all errors. 363 errorCount := 0 364 if blockErr != nil { 365 errorCount++ 366 util.Infof("error collecting block profile: %v", blockErr) 367 os.Remove(blockFile.Name()) 368 } 369 if cpuErr != nil { 370 errorCount++ 371 util.Infof("error collecting cpu profile: %v", cpuErr) 372 os.Remove(cpuFile.Name()) 373 } 374 if heapErr != nil { 375 errorCount++ 376 util.Infof("error collecting heap profile: %v", heapErr) 377 os.Remove(heapFile.Name()) 378 } 379 if mutexErr != nil { 380 errorCount++ 381 util.Infof("error collecting mutex profile: %v", mutexErr) 382 os.Remove(mutexFile.Name()) 383 } 384 if traceErr != nil { 385 errorCount++ 386 util.Infof("error collecting trace profile: %v", traceErr) 387 os.Remove(traceFile.Name()) 388 } 389 390 if errorCount > 0 { 391 return subcommands.ExitFailure 392 } 393 394 return subcommands.ExitSuccess 395 }