pkg/mgrconfig, pkg/cover: introduce the android_split_build flag

Source files for Pixel devices are split between the common AOSP kernel
(path/to/kernel/aosp) and the device-specific drivers residing in a
separate dir (path/to/kernel/private/google-modules for Android 14
and path/to/kernel/gs/google-modules for older Android versions).
See https://source.android.com/docs/setup/build/building-pixel-kernels
for details.

Android build system may reference these dirs in various ways, for which
syzkaller cannot always understand where it should look for the source.

The newly introduced android_split_build flags handles the problem by adding a
list of "delimiters" used when normalizing the kernel source paths.
If the path contains any of such delimiters, then everything preceding the last
delimiter in the path is replaced with the contents of "kernel_src" from the
manager config.

By default we only support "/aosp/" and "/private/" corresponding to
modern Android systems as delimiters.
diff --git a/pkg/cover/backend/backend.go b/pkg/cover/backend/backend.go
index 8a7f3b8..41ab064 100644
--- a/pkg/cover/backend/backend.go
+++ b/pkg/cover/backend/backend.go
@@ -65,7 +65,7 @@
 
 const LineEnd = 1 << 30
 
-func Make(target *targets.Target, vm, objDir, srcDir, buildDir string,
+func Make(target *targets.Target, vm, objDir, srcDir, buildDir string, splitBuild bool,
 	moduleObj []string, modules []host.KernelModule) (*Impl, error) {
 	if objDir == "" {
 		return nil, fmt.Errorf("kernel obj directory is not specified")
@@ -76,5 +76,12 @@
 	if vm == "gvisor" {
 		return makeGvisor(target, objDir, srcDir, buildDir, modules)
 	}
-	return makeELF(target, objDir, srcDir, buildDir, moduleObj, modules)
+	var delimiters []string
+	if splitBuild {
+		// Path prefixes used by Android Pixel kernels. See
+		// https://source.android.com/docs/setup/build/building-pixel-kernels for more
+		// details.
+		delimiters = []string{"/aosp/", "/private/"}
+	}
+	return makeELF(target, objDir, srcDir, buildDir, delimiters, moduleObj, modules)
 }
diff --git a/pkg/cover/backend/dwarf.go b/pkg/cover/backend/dwarf.go
index 5902d60..96fb5ed 100644
--- a/pkg/cover/backend/dwarf.go
+++ b/pkg/cover/backend/dwarf.go
@@ -29,6 +29,7 @@
 	objDir                string
 	srcDir                string
 	buildDir              string
+	splitBuildDelimiters  []string
 	moduleObj             []string
 	hostModules           []host.KernelModule
 	readSymbols           func(*Module, *symbolInfo) ([]*Symbol, error)
@@ -153,6 +154,7 @@
 	objDir := params.objDir
 	srcDir := params.srcDir
 	buildDir := params.buildDir
+	splitBuildDelimiters := params.splitBuildDelimiters
 	modules, err := discoverModules(target, objDir, params.moduleObj, params.hostModules, params.getModuleOffset)
 	if err != nil {
 		return nil, err
@@ -223,7 +225,7 @@
 			continue // drop the unit
 		}
 		// TODO: objDir won't work for out-of-tree modules.
-		unit.Name, unit.Path = cleanPath(unit.Name, objDir, srcDir, buildDir)
+		unit.Name, unit.Path = cleanPath(unit.Name, objDir, srcDir, buildDir, splitBuildDelimiters)
 		allUnits[nunit] = unit
 		nunit++
 	}
@@ -247,7 +249,7 @@
 		Units:   allUnits,
 		Symbols: allSymbols,
 		Symbolize: func(pcs map[*Module][]uint64) ([]Frame, error) {
-			return symbolize(target, objDir, srcDir, buildDir, pcs)
+			return symbolize(target, objDir, srcDir, buildDir, splitBuildDelimiters, pcs)
 		},
 		RestorePC:   makeRestorePC(params, pcBase),
 		CoverPoints: allCoverPointsMap,
@@ -383,7 +385,7 @@
 	return ranges, units, nil
 }
 
-func symbolizeModule(target *targets.Target, objDir, srcDir, buildDir string,
+func symbolizeModule(target *targets.Target, objDir, srcDir, buildDir string, splitBuildDelimiters []string,
 	mod *Module, pcs []uint64) ([]Frame, error) {
 	procs := runtime.GOMAXPROCS(0) / 2
 	if need := len(pcs) / 1000; procs > need {
@@ -441,7 +443,7 @@
 			err0 = res.err
 		}
 		for _, frame := range res.frames {
-			name, path := cleanPath(frame.File, objDir, srcDir, buildDir)
+			name, path := cleanPath(frame.File, objDir, srcDir, buildDir, splitBuildDelimiters)
 			frames = append(frames, Frame{
 				Module: mod,
 				PC:     frame.PC + mod.Addr,
@@ -463,11 +465,11 @@
 	return frames, nil
 }
 
-func symbolize(target *targets.Target, objDir, srcDir, buildDir string,
+func symbolize(target *targets.Target, objDir, srcDir, buildDir string, splitBuildDelimiters []string,
 	pcs map[*Module][]uint64) ([]Frame, error) {
 	var frames []Frame
 	for mod, pcs1 := range pcs {
-		frames1, err := symbolizeModule(target, objDir, srcDir, buildDir, mod, pcs1)
+		frames1, err := symbolizeModule(target, objDir, srcDir, buildDir, splitBuildDelimiters, mod, pcs1)
 		if err != nil {
 			return nil, err
 		}
@@ -509,8 +511,49 @@
 	return pcs, nil
 }
 
-func cleanPath(path, objDir, srcDir, buildDir string) (string, string) {
+// Source files for Android may be split between two subdirectories: the common AOSP kernel
+// and the device-specific drivers: https://source.android.com/docs/setup/build/building-pixel-kernels.
+// Android build system references these subdirectories in various ways, which often results in
+// paths to non-existent files being recorded in the debug info.
+//
+// cleanPathAndroid() assumes that the subdirectories reside in `srcDir`, with their names being listed in
+// `delimiters`.
+// If one of the `delimiters` occurs in the `path`, it is stripped together with the path prefix, and the
+// remaining file path is appended to `srcDir + delimiter`.
+// If none of the `delimiters` occur in the `path`, `path` is treated as a relative path that needs to be
+// looked up in `srcDir + delimiters[i]`.
+func cleanPathAndroid(path, srcDir string, delimiters []string, existFn func(string) bool) (string, string) {
+	if len(delimiters) == 0 {
+		return "", ""
+	}
+	reStr := "(" + strings.Join(delimiters, "|") + ")(.*)"
+	re := regexp.MustCompile(reStr)
+	match := re.FindStringSubmatch(path)
+	if match != nil {
+		delimiter := match[1]
+		filename := match[2]
+		path := filepath.Clean(srcDir + delimiter + filename)
+		return filename, path
+	}
+	// None of the delimiters found in `path`: it is probably a relative path to the source file.
+	// Try to look it up in every subdirectory of srcDir.
+	for _, delimiter := range delimiters {
+		absPath := filepath.Clean(srcDir + delimiter + path)
+		if existFn(absPath) {
+			return path, absPath
+		}
+	}
+	return "", ""
+}
+
+func cleanPath(path, objDir, srcDir, buildDir string, splitBuildDelimiters []string) (string, string) {
 	filename := ""
+
+	path = filepath.Clean(path)
+	aname, apath := cleanPathAndroid(path, srcDir, splitBuildDelimiters, osutil.IsExist)
+	if aname != "" {
+		return aname, apath
+	}
 	absPath := osutil.Abs(path)
 	switch {
 	case strings.HasPrefix(absPath, objDir):
diff --git a/pkg/cover/backend/dwarf_test.go b/pkg/cover/backend/dwarf_test.go
index b82bea7..98170fa 100644
--- a/pkg/cover/backend/dwarf_test.go
+++ b/pkg/cover/backend/dwarf_test.go
@@ -38,3 +38,67 @@
 		}
 	}
 }
+
+type CleanPathAndroidTest struct {
+	SrcDir     string
+	Delimiters []string
+	// Each test case is a triple of {path, epath, ename}, where path is passed to cleanPathAndroid(),
+	// and (epath, ename) is its expected return value.
+	Files    [][3]string
+	FnExists func(string) bool
+}
+
+func TestCleanPathAndroid(t *testing.T) {
+	tests := []CleanPathAndroidTest{
+		// Test that paths with "/aosp/" and "/private/" in them are normalized.
+		{
+			SrcDir:     "/src/kernel",
+			Delimiters: []string{"/aosp/", "/private/"},
+			Files: [][3]string{
+				{"/src/kernel/aosp/mm/mmap.c", "mm/mmap.c", "/src/kernel/aosp/mm/mmap.c"},
+				{"/src/kernel/out/cache/feedface/aosp/mm/mmap.c", "mm/mmap.c", "/src/kernel/aosp/mm/mmap.c"},
+				{"/src/kernel/out/cache/cafebabe/private/google_modules/module/mod.c", "google_modules/module/mod.c",
+					"/src/kernel/private/google_modules/module/mod.c"},
+				{"/some/other/path/aosp/mm/mmap.c", "mm/mmap.c", "/src/kernel/aosp/mm/mmap.c"},
+			},
+			FnExists: func(string) bool { return true },
+		},
+		// Test that for empty delimiters empty results are returned.
+		{
+			SrcDir:     "/src/kernel/",
+			Delimiters: []string{},
+			Files: [][3]string{
+				{"/src/kernel/mm/mmap.c", "", ""},
+			},
+			FnExists: func(string) bool { return true },
+		},
+		// Test that for path that does not contain a delimiter the result is constructed based on FnExists().
+		{
+			SrcDir:     "/src/kernel/",
+			Delimiters: []string{"/aosp/", "/private/"},
+			Files: [][3]string{
+				{"mm/mmap.c", "mm/mmap.c", "/src/kernel/aosp/mm/mmap.c"},
+			},
+			FnExists: func(s string) bool { return s == "/src/kernel/aosp/mm/mmap.c" },
+		},
+		// Test that for path that does not contain a delimiter the result is constructed based on FnExists().
+		{
+			SrcDir:     "/src/kernel/",
+			Delimiters: []string{"/aosp/", "/private/"},
+			Files: [][3]string{
+				{"mm/mmap.c", "mm/mmap.c", "/src/kernel/private/mm/mmap.c"},
+			},
+			FnExists: func(s string) bool { return s != "/src/kernel/aosp/mm/mmap.c" },
+		},
+	}
+	for _, test := range tests {
+		for _, files := range test.Files {
+			path, epath, ename := files[0], files[1], files[2]
+			rpath, rname := cleanPathAndroid(path, test.SrcDir, test.Delimiters, test.FnExists)
+			if (rpath != epath) || (rname != ename) {
+				t.Fatalf("cleanPathAndroid(`%s`, `%s`, %v, ...) unexpectedly returned (`%s`, `%s`) instead of (`%s`, `%s`)\n",
+					path, test.SrcDir, test.Delimiters, rpath, rname, epath, ename)
+			}
+		}
+	}
+}
diff --git a/pkg/cover/backend/elf.go b/pkg/cover/backend/elf.go
index 08d8890..b3846ed 100644
--- a/pkg/cover/backend/elf.go
+++ b/pkg/cover/backend/elf.go
@@ -15,13 +15,14 @@
 	"github.com/google/syzkaller/sys/targets"
 )
 
-func makeELF(target *targets.Target, objDir, srcDir, buildDir string,
-	moduleObj []string, hostModules []host.KernelModule) (*Impl, error) {
+func makeELF(target *targets.Target, objDir, srcDir, buildDir string, splitBuildDelimiters, moduleObj []string,
+	hostModules []host.KernelModule) (*Impl, error) {
 	return makeDWARF(&dwarfParams{
 		target:                target,
 		objDir:                objDir,
 		srcDir:                srcDir,
 		buildDir:              buildDir,
+		splitBuildDelimiters:  splitBuildDelimiters,
 		moduleObj:             moduleObj,
 		hostModules:           hostModules,
 		readSymbols:           elfReadSymbols,
diff --git a/pkg/cover/report.go b/pkg/cover/report.go
index a886eb0..6bf7e0a 100644
--- a/pkg/cover/report.go
+++ b/pkg/cover/report.go
@@ -33,7 +33,7 @@
 func MakeReportGenerator(cfg *mgrconfig.Config, subsystem []mgrconfig.Subsystem,
 	modules []host.KernelModule, rawCover bool) (*ReportGenerator, error) {
 	impl, err := backend.Make(cfg.SysTarget, cfg.Type, cfg.KernelObj,
-		cfg.KernelSrc, cfg.KernelBuildSrc, cfg.ModuleObj, modules)
+		cfg.KernelSrc, cfg.KernelBuildSrc, cfg.AndroidSplitBuild, cfg.ModuleObj, modules)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/mgrconfig/config.go b/pkg/mgrconfig/config.go
index e40643b..19ba352 100644
--- a/pkg/mgrconfig/config.go
+++ b/pkg/mgrconfig/config.go
@@ -49,6 +49,8 @@
 	KernelSrc string `json:"kernel_src,omitempty"`
 	// Location of the driectory where the kernel was built (if not set defaults to KernelSrc)
 	KernelBuildSrc string `json:"kernel_build_src,omitempty"`
+	// Is the kernel built separately from the modules? (Specific to Android builds)
+	AndroidSplitBuild bool `json:"android_split_build"`
 	// Kernel subsystem with paths to each subsystem
 	//	"kernel_subsystem": [
 	//		{ "name": "sound", "path": ["sound", "techpack/audio"]},