SOONG: BPF: add libbpf_prog soong module

This commit adds the libbpf_prog soong module which is heavily based on
the bpf soong module. Due to issues encountered and concerns around
compatibility and testability of mainlined bpf modules, support for
libbpf is added as a separate module.

Specifically, when changing the bpf module from
InitAndroidArchModule(... android.MultilibCommon) to MultilibFirst, the
soong apex bpfs functionality breaks. We have not identified a good
mechanism to fix this, and as we would then be creating architecture
specific bpf program binarys, additional testing and infrastructure
would be required.

It should be noted that libbpf_prog should not be used for features
which target 32bit kernel architectures at this point, as we do not
have a mechanism to identify the kernel arch at build time. I.E. 32bit
userspace could be on either 32bit or 64bit kernel arch, and we require
arch specific includes (vmlinux.h). Therefore, we default to 32bit
userspace on 64bit kernel to match the precedence set with the legacy
bpf programs.

Example use:
libbpf_prog {
    name: "xyz.o",
    srcs: ["xyz.c"],
    arch: {
        arm: {
            local_include_dirs: ["vmlinux/arm64"],
        },
        arm64: {
            local_include_dirs: ["vmlinux/arm64"],
        },
        x86: {
            local_include_dirs: ["vmlinux/x86_64"],
        },
        x86_64: {
            local_include_dirs: ["vmlinux/x86_64"],
        },
    },
}

Bug: 359646531
Test: manual
Change-Id: Ie58515d70abee061470cf4bb803228e00d496ac3
Signed-off-by: Neill Kapron <nkapron@google.com>
This commit is contained in:
Neill Kapron
2024-07-31 22:17:36 +00:00
parent a46053f612
commit 41efab77d0
4 changed files with 386 additions and 0 deletions

View File

@@ -913,6 +913,7 @@ func translateAndroidModule(ctx SingletonContext, w io.Writer, moduleInfoJSONs *
case "*android_sdk.sdkRepoHost": // doesn't go through base_rules
case "*apex.apexBundle": // license properties written
case "*bpf.bpf": // license properties written (both for module and objs)
case "*libbpf_prog.libbpfProg": // license properties written (both for module and objs)
case "*genrule.Module": // writes non-custom before adding .phony
case "*java.SystemModules": // doesn't go through base_rules
case "*java.systemModulesImport": // doesn't go through base_rules

38
bpf/libbpf/Android.bp Normal file
View File

@@ -0,0 +1,38 @@
//
// Copyright (C) 2024 The Android Open Source Project
//
// 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 {
default_applicable_licenses: ["Android-Apache-2.0"],
}
bootstrap_go_package {
name: "soong-libbpf",
pkgPath: "android/soong/bpf/libbpf",
deps: [
"blueprint",
"blueprint-proptools",
"soong-android",
"soong-cc",
"soong-cc-config",
],
srcs: [
"libbpf_prog.go",
],
testSrcs: [
"libbpf_prog_test.go",
],
pluginFor: ["soong_build"],
}

278
bpf/libbpf/libbpf_prog.go Normal file
View File

@@ -0,0 +1,278 @@
// Copyright (C) 2024 The Android Open Source Project
//
// 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 libbpf_prog
import (
"fmt"
"io"
"runtime"
"strings"
"android/soong/android"
"android/soong/cc"
"android/soong/genrule"
"github.com/google/blueprint"
)
type libbpfProgDepType struct {
blueprint.BaseDependencyTag
}
func init() {
registerLibbpfProgBuildComponents(android.InitRegistrationContext)
pctx.Import("android/soong/cc/config")
pctx.StaticVariable("relPwd", cc.PwdPrefix())
}
var (
pctx = android.NewPackageContext("android/soong/bpf/libbpf_prog")
libbpfProgCcRule = pctx.AndroidStaticRule("libbpfProgCcRule",
blueprint.RuleParams{
Depfile: "${out}.d",
Deps: blueprint.DepsGCC,
Command: "$relPwd $ccCmd --target=bpf -c $cFlags -MD -MF ${out}.d -o $out $in",
CommandDeps: []string{"$ccCmd"},
},
"ccCmd", "cFlags")
libbpfProgStripRule = pctx.AndroidStaticRule("libbpfProgStripRule",
blueprint.RuleParams{
Command: `$stripCmd --strip-unneeded --remove-section=.rel.BTF ` +
`--remove-section=.rel.BTF.ext --remove-section=.BTF.ext $in -o $out`,
CommandDeps: []string{"$stripCmd"},
},
"stripCmd")
libbpfProgDepTag = libbpfProgDepType{}
)
func registerLibbpfProgBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("libbpf_prog", LibbpfProgFactory)
}
var PrepareForTestWithLibbpfProg = android.GroupFixturePreparers(
android.FixtureRegisterWithContext(registerLibbpfProgBuildComponents),
android.FixtureAddFile("libbpf_headers/Foo.h", nil),
android.FixtureAddFile("libbpf_headers/Android.bp", []byte(`
genrule {
name: "libbpf_headers",
out: ["foo.h",],
}
`)),
genrule.PrepareForTestWithGenRuleBuildComponents,
)
type LibbpfProgProperties struct {
// source paths to the files.
Srcs []string `android:"path"`
// additional cflags that should be used to build the libbpf variant of
// the C/C++ module.
Cflags []string `android:"arch_variant"`
// list of directories relative to the Blueprint file that will
// be added to the include path using -I
Local_include_dirs []string `android:"arch_variant"`
// optional subdirectory under which this module is installed into.
Relative_install_path string
}
type libbpfProg struct {
android.ModuleBase
properties LibbpfProgProperties
objs android.Paths
}
var _ android.ImageInterface = (*libbpfProg)(nil)
func (libbpf *libbpfProg) ImageMutatorBegin(ctx android.BaseModuleContext) {}
func (libbpf *libbpfProg) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (libbpf *libbpfProg) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (libbpf *libbpfProg) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
return true
}
func (libbpf *libbpfProg) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (libbpf *libbpfProg) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (libbpf *libbpfProg) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (libbpf *libbpfProg) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
return false
}
func (libbpf *libbpfProg) ExtraImageVariations(ctx android.BaseModuleContext) []string {
return nil
}
func (libbpf *libbpfProg) SetImageVariation(ctx android.BaseModuleContext, variation string) {
}
func (libbpf *libbpfProg) DepsMutator(ctx android.BottomUpMutatorContext) {
ctx.AddDependency(ctx.Module(), libbpfProgDepTag, "libbpf_headers")
}
func (libbpf *libbpfProg) GenerateAndroidBuildActions(ctx android.ModuleContext) {
var cFlagsDeps android.Paths
cflags := []string{
"-nostdlibinc",
// Make paths in deps files relative
"-no-canonical-prefixes",
"-O2",
"-Wall",
"-Werror",
"-Wextra",
"-isystem bionic/libc/include",
"-isystem bionic/libc/kernel/uapi",
// The architecture doesn't matter here, but asm/types.h is included by linux/types.h.
"-isystem bionic/libc/kernel/uapi/asm-arm64",
"-isystem bionic/libc/kernel/android/uapi",
"-I " + ctx.ModuleDir(),
"-g", //Libbpf builds require BTF data
}
if runtime.GOOS != "darwin" {
cflags = append(cflags, "-fdebug-prefix-map=/proc/self/cwd=")
}
ctx.VisitDirectDeps(func(dep android.Module) {
depTag := ctx.OtherModuleDependencyTag(dep)
if depTag == libbpfProgDepTag {
if genRule, ok := dep.(genrule.SourceFileGenerator); ok {
cFlagsDeps = append(cFlagsDeps, genRule.GeneratedDeps()...)
dirs := genRule.GeneratedHeaderDirs()
for _, dir := range dirs {
cflags = append(cflags, "-I "+dir.String())
}
} else {
depName := ctx.OtherModuleName(dep)
ctx.ModuleErrorf("module %q is not a genrule", depName)
}
}
})
for _, dir := range android.PathsForModuleSrc(ctx, libbpf.properties.Local_include_dirs) {
cflags = append(cflags, "-I "+dir.String())
}
cflags = append(cflags, libbpf.properties.Cflags...)
srcs := android.PathsForModuleSrc(ctx, libbpf.properties.Srcs)
for _, src := range srcs {
if strings.ContainsRune(src.Base(), '_') {
ctx.ModuleErrorf("invalid character '_' in source name")
}
obj := android.ObjPathWithExt(ctx, "unstripped", src, "o")
ctx.Build(pctx, android.BuildParams{
Rule: libbpfProgCcRule,
Input: src,
Implicits: cFlagsDeps,
Output: obj,
Args: map[string]string{
"cFlags": strings.Join(cflags, " "),
"ccCmd": "${config.ClangBin}/clang",
},
})
objStripped := android.ObjPathWithExt(ctx, "", src, "o")
ctx.Build(pctx, android.BuildParams{
Rule: libbpfProgStripRule,
Input: obj,
Output: objStripped,
Args: map[string]string{
"stripCmd": "${config.ClangBin}/llvm-strip",
},
})
libbpf.objs = append(libbpf.objs, objStripped.WithoutRel())
}
installDir := android.PathForModuleInstall(ctx, "etc", "bpf/libbpf")
if len(libbpf.properties.Relative_install_path) > 0 {
installDir = installDir.Join(ctx, libbpf.properties.Relative_install_path)
}
for _, obj := range libbpf.objs {
ctx.PackageFile(installDir, obj.Base(), obj)
}
android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs.Strings()})
ctx.SetOutputFiles(libbpf.objs, "")
}
func (libbpf *libbpfProg) AndroidMk() android.AndroidMkData {
return android.AndroidMkData{
Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
var names []string
fmt.Fprintln(w)
fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
fmt.Fprintln(w)
var localModulePath string
localModulePath = "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf/libbpf"
if len(libbpf.properties.Relative_install_path) > 0 {
localModulePath += "/" + libbpf.properties.Relative_install_path
}
for _, obj := range libbpf.objs {
objName := name + "_" + obj.Base()
names = append(names, objName)
fmt.Fprintln(w, "include $(CLEAR_VARS)", " # libbpf.libbpf.obj")
fmt.Fprintln(w, "LOCAL_MODULE := ", objName)
fmt.Fprintln(w, "LOCAL_PREBUILT_MODULE_FILE :=", obj.String())
fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", obj.Base())
fmt.Fprintln(w, "LOCAL_MODULE_CLASS := ETC")
fmt.Fprintln(w, localModulePath)
// AconfigUpdateAndroidMkData may have added elements to Extra. Process them here.
for _, extra := range data.Extra {
extra(w, nil)
}
fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
fmt.Fprintln(w)
}
fmt.Fprintln(w, "include $(CLEAR_VARS)", " # libbpf.libbpf")
fmt.Fprintln(w, "LOCAL_MODULE := ", name)
android.AndroidMkEmitAssignList(w, "LOCAL_REQUIRED_MODULES", names)
fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)")
},
}
}
func LibbpfProgFactory() android.Module {
module := &libbpfProg{}
module.AddProperties(&module.properties)
android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
return module
}

View File

@@ -0,0 +1,69 @@
// Copyright 2024 Google Inc. All rights reserved.
//
// 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 libbpf_prog
import (
"os"
"testing"
"android/soong/android"
"android/soong/cc"
)
func TestMain(m *testing.M) {
os.Exit(m.Run())
}
var prepareForLibbpfProgTest = android.GroupFixturePreparers(
cc.PrepareForTestWithCcDefaultModules,
android.FixtureMergeMockFs(
map[string][]byte{
"bpf.c": nil,
"bpf_invalid_name.c": nil,
"BpfTest.cpp": nil,
},
),
PrepareForTestWithLibbpfProg,
)
func TestLibbpfProgDataDependency(t *testing.T) {
bp := `
libbpf_prog {
name: "bpf.o",
srcs: ["bpf.c"],
}
cc_test {
name: "vts_test_binary_bpf_module",
srcs: ["BpfTest.cpp"],
data: [":bpf.o"],
gtest: false,
}
`
prepareForLibbpfProgTest.RunTestWithBp(t, bp)
}
func TestLibbpfProgSourceName(t *testing.T) {
bp := `
libbpf_prog {
name: "bpf_invalid_name.o",
srcs: ["bpf_invalid_name.c"],
}
`
prepareForLibbpfProgTest.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(
`invalid character '_' in source name`)).
RunTestWithBp(t, bp)
}