Files
Ayush RanjanandgVisor bot 32d34dc396 Update nvidia_driver_differ to also check ioctl numbers.
Updated the tooling to fetch the ioctl number used in the driver source code
and compare it to the one used in nvproxy. It is possible that ioctl numbers
change across driver versions (under our foot). It is imperative that we mimic
these changes in nvproxy. For example, in 575.57.08 driver release,
NV2080_CTRL_CMD_FB_QUERY_DRAM_ENCRYPTION_INFOROM_SUPPORT changed from
0x20801358 to 0x20801357.

To achieve this effect, the AST parser has been updated in the previous change
to be able to parse numerical constants. However, it is unable to parse macros
that evaluate to constants (trust me, I tried). So as a workaround, the
nvidia_driver_differ declares "const uint64_t" variables in the source file
which is created with all relevant includes (see parser.WriteIncludeFile()).
These variables are defined with name "GVISOR_{IOCTL_MACRO} = IOCTL_MACRO" so
they should not conflict with anything in the driver. Then we use the AST
parser to evaluate the value of the const variable and return that.

PiperOrigin-RevId: 793350829
2025-08-10 12:01:26 -07:00

195 lines
5.8 KiB
Go

// 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 parser
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy"
"gvisor.dev/gvisor/pkg/sentry/devices/nvproxy/nvconf"
)
// Runner is a helper for running the driver_ast_parser on a given set of structs.
type Runner struct {
// Working directory for the runner.
dir string
parserPath string
inputPath string
nonUVMIoctls []nvproxy.IoctlName
uvmIoctls []nvproxy.IoctlName
}
// NewRunner creates a new Runner around a given parser file and a temporary working directory.
func NewRunner(parserPath string) (*Runner, error) {
// Create a temp directory for the runner.
dir, err := os.MkdirTemp(os.TempDir(), "run_differ_*")
if err != nil {
return nil, fmt.Errorf("failed to create temporary directory: %w", err)
}
return &Runner{
dir: dir,
parserPath: parserPath,
}, nil
}
// Cleanup removes the working directory for the runner.
func (r *Runner) Cleanup() error {
return os.RemoveAll(r.dir)
}
// CreateInputFile saves a list of structs for the runner to parse.
func (r *Runner) CreateInputFile(info *nvproxy.DriverABIInfo) error {
numNonUvm := len(info.FrontendInfos) + len(info.ControlInfos) + len(info.AllocationInfos)
numUvm := len(info.UvmInfos)
r.nonUVMIoctls = make([]nvproxy.IoctlName, 0, numNonUvm)
r.uvmIoctls = make([]nvproxy.IoctlName, 0, numUvm)
inputJSON := InputJSON{
Structs: make([]nvproxy.DriverStructName, 0, numNonUvm+numUvm),
Constants: make([]nvproxy.IoctlName, 0, numNonUvm+numUvm),
}
handleIoctlInfo := func(info nvproxy.IoctlInfo, isUVM bool) {
if info.Name == "" {
return
}
if isUVM {
r.uvmIoctls = append(r.uvmIoctls, info.Name)
} else {
r.nonUVMIoctls = append(r.nonUVMIoctls, info.Name)
}
// Add "GVISOR_" prefix to all constants; see WriteIncludeFile().
inputJSON.Constants = append(inputJSON.Constants, "GVISOR_"+info.Name)
for _, structDef := range info.Structs {
inputJSON.Structs = append(inputJSON.Structs, structDef.Name)
}
}
for _, info := range info.FrontendInfos {
handleIoctlInfo(info, false)
}
for _, info := range info.ControlInfos {
handleIoctlInfo(info, false)
}
for _, info := range info.AllocationInfos {
handleIoctlInfo(info, false)
}
for _, info := range info.UvmInfos {
handleIoctlInfo(info, true)
}
f, err := os.CreateTemp(r.dir, "input_*.json")
if err != nil {
return fmt.Errorf("failed to create temporary structs list: %w", err)
}
defer f.Close()
if err := json.NewEncoder(f).Encode(inputJSON); err != nil {
return fmt.Errorf("failed to write structs list to file: %w", err)
}
r.inputPath = f.Name()
return nil
}
// parseSourceFile runs driver_ast_parser on sourceFile for the structs listed in structsFile,
// and returns the parsed JSON output.
func (r *Runner) parseSourceFile(sourcePath string) (*OutputJSON, error) {
if r.inputPath == "" {
return nil, fmt.Errorf("input file not created")
}
// Run driver_ast_parser on the source file.
cmd := exec.Command(r.parserPath, "--input", r.inputPath, sourcePath)
var stderr bytes.Buffer
cmd.Stderr = &stderr
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to run driver_ast_parser: %v\n%s", err, stderr.String())
}
// Unmarshal the output
var defs OutputJSON
if err := json.Unmarshal(out, &defs); err != nil {
return nil, fmt.Errorf("failed to unmarshal output file: %w", err)
}
// Remove the "GVISOR_" prefix from all constants; see WriteIncludeFile().
constants := make(map[string]uint64)
for name, value := range defs.Constants {
constants[strings.TrimPrefix(name, "GVISOR_")] = value
}
defs.Constants = constants
return &defs, nil
}
// runParserConfig runs the driver_ast_parser on the given config options and merges all the
// JSON outputs into a single OutputJSON.
func (r *Runner) runParserConfig(config []ClangASTConfig) (*OutputJSON, error) {
var allDefs *OutputJSON = nil
for _, config := range config {
defs, err := r.parseSourceFile(config.Filename)
if err != nil {
return nil, fmt.Errorf("failed to parse source file: %w", err)
}
if allDefs == nil {
allDefs = defs
} else {
allDefs.Merge(*defs)
}
}
return allDefs, nil
}
// ParseDriver checks out the git repo for the given version, and runs the driver_ast_parser on the
// source code.
func (r *Runner) ParseDriver(version nvconf.DriverVersion) (*OutputJSON, error) {
// Create a temp directory to run the parser in.
// This is needed to set up compile_commands.json, since it needs to be named that exactly.
dir, err := os.MkdirTemp(r.dir, "run_differ_*")
if err != nil {
return nil, fmt.Errorf("failed to create temporary directory: %w", err)
}
defer os.RemoveAll(dir)
source, err := CloneDriverSource(dir, version)
if err != nil {
return nil, fmt.Errorf("failed to clone git repo: %w", err)
}
config, err := CreateIncludeFiles(dir, *source, r.nonUVMIoctls, r.uvmIoctls)
if err != nil {
return nil, fmt.Errorf("failed to create include files: %w", err)
}
if err := CreateCompileCommandsFile(dir, config); err != nil {
return nil, fmt.Errorf("failed to create compile_commands.json: %w", err)
}
defs, err := r.runParserConfig(config)
if err != nil {
return nil, fmt.Errorf("failed to run driver_ast_parser: %w", err)
}
return defs, nil
}