[fuzz] Use .runtime_deps for copying fuzzer binaries In experiment mode the fuzz tool needs to copy around the fuzzer binaries for archiving purposes, etc. Previously it just bulk copied all of the .so files with the binary, which was probably more than needed and also could have led to symlinks being really messed up. Instead now it uses the GN generated .runtime_deps file to figure out what libraries need to be copied. Fixes: 552913365 Change-Id: Ib5262e2c6c4e0e2d8f52f8641d89c6a144fe8ea0 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/338635 Auto-Submit: Ryan Harrison <rharrison@chromium.org> Commit-Queue: Ryan Harrison <rharrison@chromium.org> Reviewed-by: James Price <jrprice@google.com> Commit-Queue: James Price <jrprice@google.com>
diff --git a/tools/src/cmd/fuzz/experiment.go b/tools/src/cmd/fuzz/experiment.go index eda1503..41968fb 100644 --- a/tools/src/cmd/fuzz/experiment.go +++ b/tools/src/cmd/fuzz/experiment.go
@@ -679,30 +679,84 @@ } for _, fuzzer := range fuzzers { - srcPath := filepath.Join(t.build, fuzzer+fileutils.ExeExt) - dstPath := filepath.Join(binDir, fuzzer+fileutils.ExeExt) - if err := fileutils.CopyFile(dstPath, srcPath, t.osWrapper); err != nil { - return fmt.Errorf("failed to copy built binary %s to bin folder: %w", fuzzer, err) - } - } - - files, err := t.osWrapper.ReadDir(t.build) - if err == nil { - for _, f := range files { - if !f.IsDir() { - ext := filepath.Ext(f.Name()) - if ext == ".so" || ext == ".dylib" || ext == ".dll" { - srcLib := filepath.Join(t.build, f.Name()) - dstLib := filepath.Join(binDir, f.Name()) - _ = fileutils.CopyFile(dstLib, srcLib, t.osWrapper) - } - } + err := copyFuzzerAndDependencies(t, fuzzer, binDir) + if err != nil { + return err } } return nil }) } +// copyFuzzerAndDependencies makes a copy of a fuzzer binary and its runtime dependencies in the target bin folder. The runtime dependencies come from the GN output file <fuzzer>.runtime_deps, but only includes the dynamic libraries listed there and none of the harness/framework files. +func copyFuzzerAndDependencies(t *taskConfig, fuzzer string, binDir string) error { + srcPath := filepath.Join(t.build, fuzzer+fileutils.ExeExt) + dstPath := filepath.Join(binDir, fuzzer+fileutils.ExeExt) + if err := fileutils.CopyFile(dstPath, srcPath, t.osWrapper); err != nil { + return fmt.Errorf("failed to copy built binary %s to bin folder: %w", fuzzer, err) + } + + runtimeDepsPath := filepath.Join(t.build, fuzzer+".runtime_deps") + if fileutils.IsFile(runtimeDepsPath, t.osWrapper) { + depsBytes, err := t.osWrapper.ReadFile(runtimeDepsPath) + if err != nil { + return fmt.Errorf("failed to read runtime deps for %s: %w", fuzzer, err) + } + + depsStr := string(depsBytes) + for line := range strings.SplitSeq(depsStr, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + + // Skip copying the fuzzer binary itself, as it was copied above + if line == fuzzer || line == fuzzer+fileutils.ExeExt { + continue + } + + // Only copy if it's a shared library + if !isSharedLibrary(line) { + continue + } + + srcDep := filepath.Join(t.build, line) + dstDep := filepath.Join(binDir, line) + + // Ensure parent directory exists + if err := t.osWrapper.MkdirAll(filepath.Dir(dstDep), 0755); err != nil { + return fmt.Errorf("failed to create directory for runtime dependency %s: %w", line, err) + } + + info, err := t.osWrapper.Stat(srcDep) + if err != nil { + return fmt.Errorf("runtime dependency %s missing: %w", srcDep, err) + } + + if !info.IsDir() { + // Note: Go resolves symlinks automatically in the os package, so using CopyFile leads to symlinks (e.g. libvulkan.so.1) in the destination folder being real files with the correct content. + if err := fileutils.CopyFile(dstDep, srcDep, t.osWrapper); err != nil { + return fmt.Errorf("failed to copy runtime dependency %s to bin folder: %w", line, err) + } + } + } + } + return nil +} + +// isSharedLibrary returns true if the path points to a shared library file (e.g. .so, .dylib, .dll, or versioned variants like .so.1). +func isSharedLibrary(path string) bool { + ext := filepath.Ext(path) + if ext == ".so" || ext == ".dylib" || ext == ".dll" { + return true + } + // Handle versioned shared libraries (e.g. libvulkan.so.1) + if strings.Contains(path, ".so.") || strings.Contains(path, ".dylib.") { + return true + } + return false +} + // runMicrobenchmark executes a short fuzzer run to determine the execution // speed (runs/sec) on the current hardware for normalization of results. func runMicrobenchmark(t *taskConfig, root string, fuzzer string, settings *ExperimentSettings) (float64, error) {
diff --git a/tools/src/cmd/fuzz/experiment_test.go b/tools/src/cmd/fuzz/experiment_test.go index 4aa7f81..54cea78 100644 --- a/tools/src/cmd/fuzz/experiment_test.go +++ b/tools/src/cmd/fuzz/experiment_test.go
@@ -32,6 +32,7 @@ "testing" "dawn.googlesource.com/dawn/tools/src/execwrapper" + "dawn.googlesource.com/dawn/tools/src/fileutils" "dawn.googlesource.com/dawn/tools/src/oswrapper" "github.com/stretchr/testify/require" ) @@ -443,3 +444,89 @@ }) } } + +func TestPrepareBinariesWithRuntimeDeps(t *testing.T) { + fs := oswrapper.CreateFSTestOSWrapper() + + // Create a test build directory and the fuzzer binary + _ = fs.MkdirAll("/build", 0755) + _ = fs.WriteFile("/build/tint_wgsl_fuzzer", []byte("fuzzer-binary"), 0755) + + // Create runtime_deps file + depsContent := "tint_wgsl_fuzzer\nlib/libswiftshader.so\nlibvulkan.so.1\nsrc/some_other_dep.dat\n" + _ = fs.WriteFile("/build/tint_wgsl_fuzzer.runtime_deps", []byte(depsContent), 0644) + + // Create test runtime dependencies to copy + _ = fs.MkdirAll("/build/lib", 0755) + _ = fs.WriteFile("/build/lib/libswiftshader.so", []byte("swiftshader-binary"), 0755) + _ = fs.WriteFile("/build/libvulkan.so.1", []byte("vulkan-binary"), 0755) + _ = fs.MkdirAll("/build/src", 0755) + _ = fs.WriteFile("/build/src/some_other_dep.dat", []byte("some-data"), 0644) + + ew := execwrapper.NewTestExecWrapperForSuccess([]byte("main\n"), nil) + + cfg := &taskConfig{ + mainConfig: mainConfig{ + build: "/build", + osWrapper: fs, + execWrapper: ew, + }, + } + + settings := &ExperimentSettings{ + Hash: "mock-hash", + } + + binDir := "/experiment/bin" + err := prepareBinaries(cfg, settings, binDir, []string{"tint_wgsl_fuzzer"}) + require.NoError(t, err) + + // Verify binary was copied + require.True(t, fileutils.IsFile("/experiment/bin/tint_wgsl_fuzzer", fs)) + contentBin, _ := fs.ReadFile("/experiment/bin/tint_wgsl_fuzzer") + require.Equal(t, "fuzzer-binary", string(contentBin)) + + // Verify runtime dependencies were copied + require.True(t, fileutils.IsFile("/experiment/bin/lib/libswiftshader.so", fs)) + contentLib, _ := fs.ReadFile("/experiment/bin/lib/libswiftshader.so") + require.Equal(t, "swiftshader-binary", string(contentLib)) + + // Verify versioned runtime dependencies were copied + require.True(t, fileutils.IsFile("/experiment/bin/libvulkan.so.1", fs)) + contentVul, _ := fs.ReadFile("/experiment/bin/libvulkan.so.1") + require.Equal(t, "vulkan-binary", string(contentVul)) + + // Verify non-library dependencies were NOT copied + require.False(t, fileutils.IsFile("/experiment/bin/src/some_other_dep.dat", fs)) +} + +func TestPrepareBinariesNoRuntimeDeps(t *testing.T) { + fs := oswrapper.CreateFSTestOSWrapper() + + // Create a test build directory and the fuzzer binary + _ = fs.MkdirAll("/build", 0755) + _ = fs.WriteFile("/build/tint_wgsl_fuzzer", []byte("fuzzer-binary"), 0755) + + ew := execwrapper.NewTestExecWrapperForSuccess([]byte("main\n"), nil) + + cfg := &taskConfig{ + mainConfig: mainConfig{ + build: "/build", + osWrapper: fs, + execWrapper: ew, + }, + } + + settings := &ExperimentSettings{ + Hash: "mock-hash", + } + + binDir := "/experiment/bin" + err := prepareBinaries(cfg, settings, binDir, []string{"tint_wgsl_fuzzer"}) + require.NoError(t, err) + + // Verify binary was copied + require.True(t, fileutils.IsFile("/experiment/bin/tint_wgsl_fuzzer", fs)) + contentBin, _ := fs.ReadFile("/experiment/bin/tint_wgsl_fuzzer") + require.Equal(t, "fuzzer-binary", string(contentBin)) +}