mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add ioctl sniffing tool to run GPU workloads and report unsupported ioctl calls.
PiperOrigin-RevId: 644197930
This commit is contained in:
+4
-1
@@ -16,6 +16,9 @@ go_library(
|
||||
"uvm.go",
|
||||
],
|
||||
marshal = True,
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
visibility = [
|
||||
"//pkg/sentry:internal",
|
||||
"//tools:__subpackages__",
|
||||
],
|
||||
deps = ["//pkg/marshal"],
|
||||
)
|
||||
|
||||
@@ -170,6 +170,7 @@ type NVOS00Parameters struct {
|
||||
// RmAllocParamType should be implemented by all possible parameter types for
|
||||
// NV_ESC_RM_ALLOC.
|
||||
type RmAllocParamType interface {
|
||||
GetHClass() ClassID
|
||||
GetPAllocParms() P64
|
||||
GetPRightsRequested() P64
|
||||
SetPAllocParms(p P64)
|
||||
@@ -203,6 +204,11 @@ type NVOS21Parameters struct {
|
||||
Status uint32
|
||||
}
|
||||
|
||||
// GetHClass implements RmAllocParamType.GetHClass.
|
||||
func (n *NVOS21Parameters) GetHClass() ClassID {
|
||||
return n.HClass
|
||||
}
|
||||
|
||||
// GetPAllocParms implements RmAllocParamType.GetPAllocParms.
|
||||
func (n *NVOS21Parameters) GetPAllocParms() P64 {
|
||||
return n.PAllocParms
|
||||
@@ -403,6 +409,11 @@ type NVOS64Parameters struct {
|
||||
_ uint32
|
||||
}
|
||||
|
||||
// GetHClass implements RmAllocParamType.GetHClass.
|
||||
func (n *NVOS64Parameters) GetHClass() ClassID {
|
||||
return n.HClass
|
||||
}
|
||||
|
||||
// GetPAllocParms implements RmAllocParamType.GetPAllocParms.
|
||||
func (n *NVOS64Parameters) GetPAllocParms() P64 {
|
||||
return n.PAllocParms
|
||||
|
||||
@@ -54,7 +54,7 @@ go_library(
|
||||
],
|
||||
visibility = [
|
||||
"//pkg/sentry:internal",
|
||||
"//tools/gpu:__subpackages__",
|
||||
"//tools:__subpackages__",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
load("//tools:defs.bzl", "cc_binary", "go_binary", "proto_library")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = ["//:license"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
proto_library(
|
||||
name = "ioctl",
|
||||
srcs = ["ioctl.proto"],
|
||||
visibility = ["//tools/ioctl_sniffer:__subpackages__"],
|
||||
)
|
||||
|
||||
cc_binary(
|
||||
name = "ioctl_hook",
|
||||
srcs = [
|
||||
"ioctl_hook.cc",
|
||||
"ioctl_hook.h",
|
||||
"sniffer_bridge.cc",
|
||||
"sniffer_bridge.h",
|
||||
],
|
||||
linkshared = True,
|
||||
deps = [
|
||||
":ioctl_cc_proto",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/strings:str_format",
|
||||
"@com_google_protobuf//src/google/protobuf/io",
|
||||
],
|
||||
)
|
||||
|
||||
go_binary(
|
||||
name = "run_sniffer",
|
||||
srcs = ["run_sniffer.go"],
|
||||
static = True,
|
||||
deps = [
|
||||
"//pkg/log",
|
||||
"//tools/ioctl_sniffer/sniffer",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
# Ioctl Sniffer
|
||||
|
||||
This tool provides a way to profile GPU workloads, sniff out all the Nvidia
|
||||
`ioctl(2)` calls that are involved, and ultimately filter out the calls that are
|
||||
currently unsupported by nvproxy.
|
||||
|
||||
This is accomplished by providing a `libioctl_hook.so` shared library, which can
|
||||
be `LD_PRELOAD`ed and intercepts all `ioctl(2)` calls made. Any calls made to
|
||||
known Nvidia device files are then captured and parsed by the `sniffer` Go
|
||||
package. The sniffer compares against nvproxy's list of supported `ioctl(2)`
|
||||
numbers for the current driver version, and checks if the given call is
|
||||
contained in the list. For `NV_ESC_RM_CONTROL` and `NV_ESC_RM_ALLOC` calls, it
|
||||
also extracts the control command and allocation class respectively, and checks
|
||||
if nvproxy supports them.
|
||||
|
||||
## Usage
|
||||
|
||||
To start, we need to build the shared library and Go binary:
|
||||
|
||||
```
|
||||
make copy TARGETS=//tools/ioctl_sniffer:run_sniffer DESTINATION=bin/
|
||||
make copy TARGETS=//tools/ioctl_sniffer:ioctl_hook DESTINATION=bin/
|
||||
```
|
||||
|
||||
Once we have the binary, we can hook into any GPU workload by passing the
|
||||
corresponding command to run it to `run_sniffer`, like so:
|
||||
|
||||
```
|
||||
./run_sniffer nvidia-smi
|
||||
```
|
||||
|
||||
This should run the workload as normal and provide an output of all the
|
||||
unsupported `ioctl(2)` calls it detected at the end:
|
||||
|
||||
```
|
||||
============== Unsupported ioctls ==============
|
||||
Frontend:
|
||||
UVM:
|
||||
Control:
|
||||
Control ioctl: request=0xc020462a [nr=0x2a (42), cmd=0x20810110 (545325328)] => ret=0
|
||||
Control ioctl: request=0xc020462a [nr=0x2a (42), cmd=0x2080014b (545259851)] => ret=0
|
||||
...
|
||||
Alloc:
|
||||
Alloc ioctl: request=0xc030462b [nr=0x2b (43), hClass=0xc639 (50745)] => ret=0
|
||||
Alloc ioctl: request=0xc030462b [nr=0x2b (43), hClass=0xc640 (50752)] => ret=0
|
||||
...
|
||||
Unknown:
|
||||
```
|
||||
|
||||
Note that by default, `run_sniffer` assumes the shared library is located in the
|
||||
same directory. You can specify the path to the library with the optional
|
||||
`-ld_preload` flag.
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2024 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
syntax = "proto3";
|
||||
|
||||
package gvisor;
|
||||
|
||||
message Ioctl {
|
||||
// The path of the file that `fd` is pointing to.
|
||||
string fd_path = 1;
|
||||
|
||||
// The request argument of the ioctl.
|
||||
uint64 request = 2;
|
||||
|
||||
// The return value of the ioctl.
|
||||
int32 ret = 3;
|
||||
|
||||
// The data pointed to by `argp`. For UVM ioctl calls, the argument size is
|
||||
// not easily accessible, so `arg_data` will be empty in this case.
|
||||
bytes arg_data = 4;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2024 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#define _GNU_SOURCE 1 // Needed for access to RTLD_NEXT
|
||||
#include "tools/ioctl_sniffer/ioctl_hook.h"
|
||||
|
||||
#include <asm/ioctl.h>
|
||||
#include <dlfcn.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/match.h"
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "tools/ioctl_sniffer/ioctl.pb.h"
|
||||
#include "tools/ioctl_sniffer/sniffer_bridge.h"
|
||||
|
||||
using gvisor::Ioctl;
|
||||
|
||||
libc_ioctl libc_ioctl_handle = nullptr;
|
||||
|
||||
void init_libc_ioctl_handle() {
|
||||
if (libc_ioctl_handle) {
|
||||
return;
|
||||
}
|
||||
|
||||
libc_ioctl_handle = (libc_ioctl)dlsym(RTLD_NEXT, "ioctl");
|
||||
if (!libc_ioctl_handle) {
|
||||
printf("Failed to hook ioctl: %s\n", dlerror());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
int ioctl(int fd, uint64_t request, void *argp) {
|
||||
if (!libc_ioctl_handle) {
|
||||
init_libc_ioctl_handle();
|
||||
}
|
||||
|
||||
// Forward the ioctl call.
|
||||
int ret = libc_ioctl_handle(fd, request, argp);
|
||||
|
||||
// Check the file name to see if this is an Nvidia ioctl.
|
||||
// We only want to do protobuf logging for these ioctls.
|
||||
char file_name[PATH_MAX + 1];
|
||||
int n = readlink(absl::StrCat("/proc/self/fd/", fd).c_str(), file_name,
|
||||
sizeof(file_name) - 1);
|
||||
if (n < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
file_name[n] = '\0';
|
||||
if (!absl::StartsWith(file_name, "/dev/nvidia")) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Prepare ioctl proto for logging.
|
||||
Ioctl info;
|
||||
info.set_fd_path(file_name);
|
||||
info.set_request(request);
|
||||
info.set_ret(ret);
|
||||
|
||||
// ioctl calls to uvm don't encode their size in the request.
|
||||
uint32_t arg_size =
|
||||
strcmp(file_name, "/dev/nvidia-uvm") == 0 ? 0 : _IOC_SIZE(request);
|
||||
info.set_arg_data(argp, arg_size);
|
||||
|
||||
WriteIoctlProto(info);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef TOOLS_IOCTL_SNIFFER_IOCTL_HOOK_H_
|
||||
#define TOOLS_IOCTL_SNIFFER_IOCTL_HOOK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef int (*libc_ioctl)(int fd, uint64_t request, void *argp);
|
||||
|
||||
void init_libc_ioctl_handle();
|
||||
|
||||
#endif // TOOLS_IOCTL_SNIFFER_IOCTL_HOOK_H_
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2024 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package main sets up the ioctl sniffer and runs a given command.
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/tools/ioctl_sniffer/sniffer"
|
||||
)
|
||||
|
||||
var ldPreloadPath = flag.String("ld_preload", "./libioctl_hook.so", "The path to the LD_PRELOAD library.")
|
||||
|
||||
// Main is our main function.
|
||||
func Main() error {
|
||||
flag.Parse()
|
||||
if len(flag.Args()) == 0 {
|
||||
return fmt.Errorf("no command specified")
|
||||
}
|
||||
|
||||
// Init our sniffer
|
||||
if err := sniffer.Init(); err != nil {
|
||||
return fmt.Errorf("failed to init sniffer: %w", err)
|
||||
}
|
||||
|
||||
// Create a pipe to read the output of the command.
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create pipe: %w", err)
|
||||
}
|
||||
|
||||
// Set up command from flags
|
||||
cmd := exec.Command(flag.Arg(0), flag.Args()[1:]...)
|
||||
cmd.ExtraFiles = []*os.File{w}
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = append(os.Environ(), fmt.Sprintf("LD_PRELOAD=%s", *ldPreloadPath))
|
||||
|
||||
// Run the command and start reading the output.
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("failed to run command: %w", err)
|
||||
}
|
||||
|
||||
w.Close()
|
||||
results := sniffer.ReadHookOutput(r)
|
||||
|
||||
// Once we've read all the output, print the list of missing ioctls.
|
||||
log.Infof("%s", results)
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return fmt.Errorf("command exited with error: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := Main(); err != nil {
|
||||
log.Warningf("%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
load("//tools:defs.bzl", "go_library")
|
||||
|
||||
package(
|
||||
default_applicable_licenses = ["//:license"],
|
||||
licenses = ["notice"],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "sniffer",
|
||||
srcs = [
|
||||
"sniffer.go",
|
||||
"sniffer_bridge.go",
|
||||
],
|
||||
visibility = ["//tools/ioctl_sniffer:__subpackages__"],
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
"//pkg/abi/nvgpu",
|
||||
"//pkg/log",
|
||||
"//pkg/sentry/devices/nvproxy",
|
||||
"//tools/ioctl_sniffer:ioctl_go_proto",
|
||||
"@org_golang_google_protobuf//proto:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,278 @@
|
||||
// Copyright 2024 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package sniffer parses the output of the ioctl hook.
|
||||
package sniffer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/abi/nvgpu"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy"
|
||||
pb "gvisor.dev/gvisor/tools/ioctl_sniffer/ioctl_go_proto"
|
||||
)
|
||||
|
||||
var (
|
||||
uvmDevPath = "/dev/nvidia-uvm"
|
||||
ctlDevPath = "/dev/nvidiactl"
|
||||
deviceDevPath = regexp.MustCompile(`/dev/nvidia(\d+)`)
|
||||
)
|
||||
|
||||
const (
|
||||
frontend = iota
|
||||
uvm
|
||||
control
|
||||
alloc
|
||||
unknown
|
||||
)
|
||||
|
||||
// ioctlClass is the class of the ioctl as defined above.
|
||||
type ioctlClass uint32
|
||||
|
||||
// ioctlNr is the command number of the ioctl.
|
||||
type ioctlNr uint32
|
||||
|
||||
// controlCommand is the control command, specifically for the NV_ESC_RM_CONTROL ioctl.
|
||||
type controlCommand uint32
|
||||
|
||||
// allocClass is the alloc class, specifically for the NV_ESC_RM_ALLOC ioctl.
|
||||
type allocClass uint32
|
||||
|
||||
func (c ioctlClass) String() string {
|
||||
switch c {
|
||||
case frontend:
|
||||
return "Frontend"
|
||||
case uvm:
|
||||
return "UVM"
|
||||
case control:
|
||||
return "Control"
|
||||
case alloc:
|
||||
return "Alloc"
|
||||
default:
|
||||
return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
suppFrontendIoctls, suppUvmIoctls, suppControlCmds, suppAllocClasses map[uint32]struct{}
|
||||
)
|
||||
|
||||
// Ioctl contains the parsed ioctl protobuf information.
|
||||
type Ioctl struct {
|
||||
pb *pb.Ioctl
|
||||
class ioctlClass
|
||||
nr ioctlNr
|
||||
cmd controlCommand // Control ioctls only.
|
||||
hClass allocClass // Alloc ioctls only.
|
||||
}
|
||||
|
||||
// IsSupported returns true if the ioctl is supported by nvproxy.
|
||||
func (i Ioctl) IsSupported() bool {
|
||||
switch i.class {
|
||||
case frontend:
|
||||
_, ok := suppFrontendIoctls[uint32(i.nr)]
|
||||
return ok
|
||||
case uvm:
|
||||
_, ok := suppUvmIoctls[uint32(i.nr)]
|
||||
return ok
|
||||
case control:
|
||||
_, ok := suppControlCmds[uint32(i.cmd)]
|
||||
return ok
|
||||
case alloc:
|
||||
_, ok := suppAllocClasses[uint32(i.hClass)]
|
||||
return ok
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (i Ioctl) String() string {
|
||||
switch i.class {
|
||||
case control:
|
||||
return fmt.Sprintf("%s ioctl: request=%#x [nr=%#x (%d), cmd=%#x (%d)] => ret=%d",
|
||||
i.class, i.pb.GetRequest(), i.nr, i.nr, i.cmd, i.cmd, i.pb.GetRet())
|
||||
case alloc:
|
||||
return fmt.Sprintf("%s ioctl: request=%#x [nr=%#x (%d), hClass=%#x (%d)] => ret=%d",
|
||||
i.class, i.pb.GetRequest(), i.nr, i.nr, i.hClass, i.hClass, i.pb.GetRet())
|
||||
default:
|
||||
return fmt.Sprintf("%s ioctl: request=%#x [nr=%#x (%d), size=%d] => ret=%d",
|
||||
i.class, i.pb.GetRequest(), i.nr, i.nr, len(i.pb.GetArgData()), i.pb.GetRet())
|
||||
}
|
||||
}
|
||||
|
||||
// Results contains the list of unsupported ioctls.
|
||||
type Results struct {
|
||||
unsupportedControl map[controlCommand]Ioctl
|
||||
unsupportedAlloc map[allocClass]Ioctl
|
||||
unsupportedOther map[ioctlClass]map[ioctlNr]Ioctl
|
||||
}
|
||||
|
||||
// NewResults creates a new Results object.
|
||||
func NewResults() *Results {
|
||||
return &Results{
|
||||
unsupportedControl: make(map[controlCommand]Ioctl),
|
||||
unsupportedAlloc: make(map[allocClass]Ioctl),
|
||||
unsupportedOther: make(map[ioctlClass]map[ioctlNr]Ioctl),
|
||||
}
|
||||
}
|
||||
|
||||
// AddUnsupportedIoctl adds an unsupported ioctl to the results.
|
||||
func (r *Results) AddUnsupportedIoctl(ioctl Ioctl) {
|
||||
switch ioctl.class {
|
||||
case control:
|
||||
r.unsupportedControl[ioctl.cmd] = ioctl
|
||||
case alloc:
|
||||
r.unsupportedAlloc[ioctl.hClass] = ioctl
|
||||
default:
|
||||
if r.unsupportedOther[ioctl.class] == nil {
|
||||
r.unsupportedOther[ioctl.class] = make(map[ioctlNr]Ioctl)
|
||||
}
|
||||
r.unsupportedOther[ioctl.class][ioctl.nr] = ioctl
|
||||
}
|
||||
}
|
||||
|
||||
func printIoctls[T comparable](b *strings.Builder, class ioctlClass, m map[T]Ioctl) {
|
||||
if len(m) == 0 {
|
||||
fmt.Fprintf(b, "%v: None\n", class)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(b, "%v:\n", class)
|
||||
for _, ioctl := range m {
|
||||
fmt.Fprintf(b, "\t%v\n", ioctl)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Results) String() string {
|
||||
// We will rarely print out the results, so allocating a new strings.Builder
|
||||
// each time is fine.
|
||||
b := new(strings.Builder)
|
||||
|
||||
fmt.Fprintln(b, "============== Unsupported ioctls ==============")
|
||||
printIoctls(b, frontend, r.unsupportedOther[frontend])
|
||||
printIoctls(b, uvm, r.unsupportedOther[uvm])
|
||||
printIoctls(b, control, r.unsupportedControl)
|
||||
printIoctls(b, alloc, r.unsupportedAlloc)
|
||||
printIoctls(b, unknown, r.unsupportedOther[unknown])
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Init reads from nvproxy and sets up the supported ioctl maps.
|
||||
func Init() error {
|
||||
nvproxy.Init()
|
||||
|
||||
// Load the ABI for the host driver.
|
||||
driverVerStr, err := nvproxy.HostDriverVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get host driver version: %w", err)
|
||||
}
|
||||
driverVer, err := nvproxy.DriverVersionFrom(driverVerStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse host driver version: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("Host driver version: %v", driverVer)
|
||||
|
||||
var ok bool
|
||||
suppFrontendIoctls, suppUvmIoctls, suppControlCmds, suppAllocClasses, ok = nvproxy.SupportedIoctls(driverVer)
|
||||
if !ok {
|
||||
return fmt.Errorf("host driver version %s is not supported", driverVer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadHookOutput reads the output of the ioctl hook until an EOF is reached.
|
||||
func ReadHookOutput(r io.Reader) *Results {
|
||||
res := NewResults()
|
||||
for {
|
||||
ioctlPB, err := ReadIoctlProto(r)
|
||||
if err != nil {
|
||||
if !errors.Is(err, io.EOF) {
|
||||
log.Warningf("Error reading ioctl proto: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Parse the protobuf
|
||||
ioctl, err := ParseIoctlOutput(ioctlPB)
|
||||
if err != nil {
|
||||
log.Warningf("Error parsing ioctl %v: %v", ioctlPB, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("%s", ioctl)
|
||||
|
||||
if !ioctl.IsSupported() {
|
||||
res.AddUnsupportedIoctl(ioctl)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// ParseIoctlOutput parses an ioctl protobuf from the ioctl hook.
|
||||
func ParseIoctlOutput(ioctl *pb.Ioctl) (Ioctl, error) {
|
||||
parsedIoctl := Ioctl{pb: ioctl}
|
||||
|
||||
// Categorize and do class-specific parsing.
|
||||
switch {
|
||||
case ioctl.GetFdPath() == uvmDevPath:
|
||||
parsedIoctl.class = uvm
|
||||
parsedIoctl.nr = ioctlNr(ioctl.GetRequest())
|
||||
case ioctl.GetFdPath() == ctlDevPath || deviceDevPath.MatchString(ioctl.GetFdPath()):
|
||||
parsedIoctl.nr = ioctlNr(linux.IOC_NR(uint32(ioctl.GetRequest())))
|
||||
|
||||
switch parsedIoctl.nr {
|
||||
case nvgpu.NV_ESC_RM_CONTROL:
|
||||
data := ioctl.GetArgData()
|
||||
if uint32(len(data)) != nvgpu.SizeofNVOS54Parameters {
|
||||
return parsedIoctl, fmt.Errorf("unexpected number of bytes")
|
||||
}
|
||||
var ioctlParams nvgpu.NVOS54Parameters
|
||||
ioctlParams.UnmarshalBytes(data)
|
||||
|
||||
parsedIoctl.class = control
|
||||
parsedIoctl.cmd = controlCommand(ioctlParams.Cmd)
|
||||
case nvgpu.NV_ESC_RM_ALLOC:
|
||||
data := ioctl.GetArgData()
|
||||
var isNVOS64 bool
|
||||
switch uint32(len(data)) {
|
||||
case nvgpu.SizeofNVOS21Parameters:
|
||||
case nvgpu.SizeofNVOS64Parameters:
|
||||
isNVOS64 = true
|
||||
default:
|
||||
return parsedIoctl, fmt.Errorf("unexpected number of bytes")
|
||||
}
|
||||
ioctlParams := nvgpu.GetRmAllocParamObj(isNVOS64)
|
||||
ioctlParams.UnmarshalBytes(data)
|
||||
|
||||
parsedIoctl.class = alloc
|
||||
parsedIoctl.hClass = allocClass(ioctlParams.GetHClass())
|
||||
default:
|
||||
parsedIoctl.class = frontend
|
||||
}
|
||||
default:
|
||||
parsedIoctl.class = unknown
|
||||
parsedIoctl.nr = ioctlNr(linux.IOC_NR(uint32(ioctl.GetRequest())))
|
||||
}
|
||||
|
||||
return parsedIoctl, nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2024 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sniffer
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
pb "gvisor.dev/gvisor/tools/ioctl_sniffer/ioctl_go_proto"
|
||||
)
|
||||
|
||||
var (
|
||||
protoBytesBuf []byte
|
||||
)
|
||||
|
||||
// ReadIoctlProto reads a single ioctl proto from the given reader. Our format is:
|
||||
// - 8 byte little endian uint64 containing the size of the proto.
|
||||
// - The proto bytes.
|
||||
//
|
||||
// This should match the format in sniffer_bridge.h.
|
||||
func ReadIoctlProto(r io.Reader) (*pb.Ioctl, error) {
|
||||
// Read next proto from pipe.
|
||||
var protoSizeBuf [8]byte
|
||||
if _, err := io.ReadFull(r, protoSizeBuf[:]); err != nil {
|
||||
return nil, fmt.Errorf("failed to read proto size: %w", err)
|
||||
}
|
||||
protoSize := binary.LittleEndian.Uint64(protoSizeBuf[:])
|
||||
|
||||
// See if we need to reallocate the buffer.
|
||||
if cap(protoBytesBuf) < int(protoSize) {
|
||||
protoBytesBuf = make([]byte, protoSize)
|
||||
} else {
|
||||
protoBytesBuf = protoBytesBuf[:protoSize]
|
||||
}
|
||||
if _, err := io.ReadFull(r, protoBytesBuf); err != nil {
|
||||
return nil, fmt.Errorf("failed to read proto data: %w", err)
|
||||
}
|
||||
|
||||
// Unmarshal and parse proto.
|
||||
ioctl := &pb.Ioctl{}
|
||||
if err := proto.Unmarshal(protoBytesBuf, ioctl); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal proto: %w", err)
|
||||
}
|
||||
|
||||
return ioctl, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2024 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "tools/ioctl_sniffer/sniffer_bridge.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "tools/ioctl_sniffer/ioctl.pb.h"
|
||||
#include "google/protobuf/io/zero_copy_stream_impl.h"
|
||||
|
||||
void WriteIoctlProto(gvisor::Ioctl &ioctl) {
|
||||
// Write size of the proto message.
|
||||
uint64_t size = ioctl.ByteSizeLong();
|
||||
write(LOG_OUTPUT_FD, &size, sizeof(size));
|
||||
|
||||
// Write the proto message.
|
||||
google::protobuf::io::FileOutputStream os(LOG_OUTPUT_FD);
|
||||
ioctl.SerializeToZeroCopyStream(&os);
|
||||
os.Flush();
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright 2024 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef TOOLS_IOCTL_SNIFFER_SNIFFER_BRIDGE_H_
|
||||
#define TOOLS_IOCTL_SNIFFER_SNIFFER_BRIDGE_H_
|
||||
|
||||
#include "tools/ioctl_sniffer/ioctl.pb.h"
|
||||
|
||||
// The file descriptor to write the ioctl data to. Go's os.exec will
|
||||
// always make this available.
|
||||
constexpr int LOG_OUTPUT_FD = 3;
|
||||
|
||||
// Write the ioctl proto to the log output file descriptor. Our format is:
|
||||
// - 8 byte little endian uint64 containing the size of the proto.
|
||||
// - The proto bytes.
|
||||
// This should match the format in sniffer_bridge.go.
|
||||
void WriteIoctlProto(gvisor::Ioctl &ioctl);
|
||||
|
||||
#endif // TOOLS_IOCTL_SNIFFER_SNIFFER_BRIDGE_H_
|
||||
Reference in New Issue
Block a user