深度解析 Android 系统属性

目录

Android系统属性

1.属性在哪里?

2.属性长什么样?

3.如何读写属性:

4.属性的作用

属性文件生成过程

如何添加系统属性

1.添加系统属性到 /system/build.prop

2.添加系统属性到 /vendor/build.prop

3.添加系统属性到 /product/build.prop


Android系统属性

  • 属性文件生成过程分析
  • 如何添加系统属性
  • 属性与 Selinux
  • 属性系统整体框架与启动过程分析
  • 属性读写过程源码分析

在 Android 系统中,为统一管理系统的属性,设计了一个统一的属性系统,每个属性都是一个 key-value 对。 我们可以通过 shell 命令,Native 函数接口,Java 函数接口的方式来读写这些 key-vaule 对。

1.属性在哪里?

init 进程在启动会去加载后缀为 .prop 的属性文件, 将属性文件中的属性加载到共享内存中, 这样系统就有了默认的一些属性。

属性文件都在哪里呢?

属性文件的后缀绝大部分都是 prop,我们可以在 Android 模拟器的 shell 环境下搜索:

find . -name "*.prop"

/default.prop
/data/local.prop
/system/build.prop
/system/product/build.prop
/vendor/build.prop
/vendor/odm/etc/build.prop
/vendor/default.prop
/data/property/

 我们看看 /default.prop 属性文件的内容:

cat /default.prop

#
# ADDITIONAL_DEFAULT_PROPERTIES
#
ro.actionable_compatible_property.enabled=true
ro.postinstall.fstab.prefix=/system
ro.secure=0
ro.allow.mock.location=1
ro.debuggable=1
debug.atrace.tags.enableflags=0
dalvik.vm.image-dex2oat-Xms=64m
dalvik.vm.image-dex2oat-Xmx=64m
dalvik.vm.dex2oat-Xms=64m
dalvik.vm.dex2oat-Xmx=512m
dalvik.vm.usejit=true
dalvik.vm.usejitprofiles=true
dalvik.vm.dexopt.secondary=true
dalvik.vm.appimageformat=lz4
ro.dalvik.vm.native.bridge=0
pm.dexopt.first-boot=extract
pm.dexopt.boot=extract
pm.dexopt.install=speed-profile
pm.dexopt.bg-dexopt=speed-profile
pm.dexopt.ab-ota=speed-profile
pm.dexopt.inactive=verify
pm.dexopt.shared=speed
dalvik.vm.dex2oat-resolve-startup-strings=true
dalvik.vm.dex2oat-max-image-block-size=524288
dalvik.vm.minidebuginfo=true
dalvik.vm.dex2oat-minidebuginfo=true
ro.iorapd.enable=false
tombstoned.max_tombstone_count=50
persist.traced.enable=1
ro.com.google.locationfeatures=1
ro.setupwizard.mode=DISABLED
persist.sys.usb.config=adb

可以看出属性确实是一些 key-value 对。

init 进程会调用 property_load_boot_defaults 函数来加载属性文件:

void property_load_boot_defaults(bool load_debug_prop) {
    // TODO(b/117892318): merge prop.default and build.prop files into one
    // We read the properties and their values into a map, in order to always allow properties
    // loaded in the later property files to override the properties in loaded in the earlier
    // property files, regardless of if they are "ro." properties or not.
    std::map<std::string, std::string> properties;
    if (!load_properties_from_file("/system/etc/prop.default", nullptr, &properties)) {
        // Try recovery path
        if (!load_properties_from_file("/prop.default", nullptr, &properties)) {
            // Try legacy path
            load_properties_from_file("/default.prop", nullptr, &properties);
        }
    }
    load_properties_from_file("/system/build.prop", nullptr, &properties);
    load_properties_from_file("/vendor/default.prop", nullptr, &properties);
    load_properties_from_file("/vendor/build.prop", nullptr, &properties);
    if (SelinuxGetVendorAndroidVersion() >= __ANDROID_API_Q__) {
        load_properties_from_file("/odm/etc/build.prop", nullptr, &properties);
    } else {
        load_properties_from_file("/odm/default.prop", nullptr, &properties);
        load_properties_from_file("/odm/build.prop", nullptr, &properties);
    }
    load_properties_from_file("/product/build.prop", nullptr, &properties);
    load_properties_from_file("/product_services/build.prop", nullptr, &properties);
    load_properties_from_file("/factory/factory.prop", "ro.*", &properties);

    if (load_debug_prop) {
        LOG(INFO) << "Loading " << kDebugRamdiskProp;
        load_properties_from_file(kDebugRamdiskProp, nullptr, &properties);
    }

    for (const auto& [name, value] : properties) {
        std::string error;
        if (PropertySet(name, value, &error) != PROP_SUCCESS) {
            LOG(ERROR) << "Could not set '" << name << "' to '" << value
                       << "' while loading .prop files" << error;
        }
    }

    property_initialize_ro_product_props();
    property_derive_build_fingerprint();

    update_sys_usb_config();
}

从源码中我们也可以看到 init 进程加载了哪些属性文件以及加载的顺序。

2.属性长什么样?

每一个属性是一个 key-value 对:

ro.actionable_compatible_property.enabled=true
ro.postinstall.fstab.prefix=/system
ro.secure=0
ro.allow.mock.location=1
ro.debuggable=1
debug.atrace.tags.enableflags=0
dalvik.vm.image-dex2oat-Xms=64m
dalvik.vm.image-dex2oat-Xmx=64m

等号左边是属性的名字,等号右边是属性的值

属性的分类:

  • 一般属性:普通的 key-value 对,没有其他功能,系统启动后,如果修改了某个属性值(仅修改了内存中的值,未写入到文件),再重启系统,修改的值不会被保存下来,读取到的仍是修改前的值
  • 特殊属性
    • 属性名称以 ro 开头,那么这个属性被视为只读属性。一旦设置,属性值不能改变。
    • net 开头的属性,顾名思义,就是与网络相关的属性,net 属性中有一个特殊的属性:net.change,它记录了每一次最新设置和更新的 net 属性,也就是每次设置和更新 net,属性时则会自动的更新 net.change 属性,net.change 属性的 value 就是这个被设置或者更新的 net 属性的 name。例如我们更新了属性 net.bt.name 的值,由于 net 有属性发生了变化,那么属性服务就会自动更新 net.change,将其值设置为 net.bt.name
    • 以 persist 为开头的属性值,当在系统中通过 setprop 命令设置这个属性时,就会在 /data/property/ 目录下会保存一个副本。这样在系统重启后,按照加载流程这些 persist 属性的值就不会消失了。
    • 属性 ctrl.start 和 ctrl.stop 是用来启动和停止服务。这里的服务是指定义在 rc 后缀文件中的服务。当我们向 ctrl.start 属性写入一个值时,属性服务将使用该属性值作为服务名找到该服务,启动该服务。这项服务的启动结果将会放入 init.svc.<服务名> 属性中,可以通过查询这个属性值,以确定服务是否已经启动。

3.如何读写属性:

命令行:

getprop "wlan.driver.status"
setprop "wlan.driver.status"  "timeout"

Native 代码:

char buf[20]="qqqqqq";
char tempbuf[PROPERTY_VALUE_MAX];
property_set("type_value",buf);
property_get("type_value",tempbuf,"0");

Java 代码:

String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
SystemProperties.set("service.bootanim.exit", "0");

4.属性的作用

常见的属性文件的作用如下:

属性文件生成过程

运行的系统中有很多属性文件:

/default.prop
/data/local.prop
/system/build.prop
/system/product/build.prop
/vendor/build.prop
/vendor/odm/etc/build.prop
/vendor/default.prop

那么这些属性文件是从哪里来的呢?

我们先看一下 /system/build.prop 文件的开头:

# begin common build properties
# autogenerated by build/make/tools/buildinfo_common.sh
ro.system.build.date=Fri Oct 13 17:23:40 CST 2023
# ......

注释里面说,当前文件由 build/make/tools/buildinfo_common.sh 自动生成,我们去看一下这个 shell 脚本的具体内容:

cat build/make/tools/buildinfo_common.sh

#!/bin/bash

partition="$1"

if [ "$#" -ne 1 ]; then
  echo "Usage: $0 <partition>" 1>&2
  exit 1
fi

echo "# begin common build properties"
echo "# autogenerated by $0"

echo "ro.${partition}.build.date=`$DATE`"
echo "ro.${partition}.build.date.utc=`$DATE +%s`"
echo "ro.${partition}.build.fingerprint=$BUILD_FINGERPRINT"
echo "ro.${partition}.build.id=$BUILD_ID"
echo "ro.${partition}.build.tags=$BUILD_VERSION_TAGS"
echo "ro.${partition}.build.type=$TARGET_BUILD_TYPE"
echo "ro.${partition}.build.version.incremental=$BUILD_NUMBER"
echo "ro.${partition}.build.version.release=$PLATFORM_VERSION"
echo "ro.${partition}.build.version.sdk=$PLATFORM_SDK_VERSION"

echo "ro.product.${partition}.brand=$PRODUCT_BRAND"
echo "ro.product.${partition}.device=$PRODUCT_DEVICE"
echo "ro.product.${partition}.manufacturer=$PRODUCT_MANUFACTURER"
echo "ro.product.${partition}.model=$PRODUCT_MODEL"
echo "ro.product.${partition}.name=$PRODUCT_NAME"

echo "# end common build properties"

上述脚本中,通过 echo 打印了许多信息。这些信息都进入了文件中,那么调用过程中一定做了重定向操作。

我们在 build/make 中搜索 buildinfo_common.sh,看看哪里使用了这个脚本:

cd build/make
grep -R "buildinfo_common.sh" .

./core/Makefile:BUILDINFO_COMMON_SH := build/make/tools/buildinfo_common.sh

可以看到在 build/make/core/Makefile 文件中,脚本的路径赋值给了 BUILDINFO_COMMON_SH 变量。

我们接着搜这个变量:

# 还是在 build/make 目录下
grep -R "BUILDINFO_COMMON_SH" .

./core/Makefile:BUILDINFO_COMMON_SH := build/make/tools/buildinfo_common.sh
./core/Makefile:        bash $(BUILDINFO_COMMON_SH) "$(1)" >> $(2)
./core/Makefile:$(INSTALLED_DEFAULT_PROP_TARGET): $(BUILDINFO_COMMON_SH) $(intermediate_system_build_prop)
./core/Makefile:$(intermediate_system_build_prop): $(BUILDINFO_SH) $(BUILDINFO_COMMON_SH) $(INTERNAL_BUILD_ID_MAKEFILE) $(BUILD_SYSTEM)/version_defaults.mk $(system_prop_file) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(API_FINGERPRINT)
./core/Makefile:$(INSTALLED_VENDOR_BUILD_PROP_TARGET): $(BUILDINFO_COMMON_SH) $(intermediate_system_build_prop)
./core/Makefile:$(INSTALLED_PRODUCT_BUILD_PROP_TARGET): $(BUILDINFO_COMMON_SH) $(product_prop_files)
./core/Makefile:$(INSTALLED_ODM_BUILD_PROP_TARGET): $(BUILDINFO_COMMON_SH)
./core/Makefile:$(INSTALLED_PRODUCT_SERVICES_BUILD_PROP_TARGET): $(BUILDINFO_COMMON_SH)

注意到第二行的输出 ./core/Makefile: bash $(BUILDINFO_COMMON_SH) "$(1)" >> $(2),这里调用了上面的 shell 脚本

打开文件,看到它是函数 generate-common-build-props-with-product-vars-set 的一部分:

define generate-common-build-props-with-product-vars-set
	BUILD_FINGERPRINT="$(BUILD_FINGERPRINT_FROM_FILE)" \
	BUILD_ID="$(BUILD_ID)" \
	BUILD_NUMBER="$(BUILD_NUMBER_FROM_FILE)" \
	BUILD_VERSION_TAGS="$(BUILD_VERSION_TAGS)" \
	DATE="$(DATE_FROM_FILE)" \
	PLATFORM_SDK_VERSION="$(PLATFORM_SDK_VERSION)" \
	PLATFORM_VERSION="$(PLATFORM_VERSION)" \
	TARGET_BUILD_TYPE="$(TARGET_BUILD_VARIANT)" \
	bash $(BUILDINFO_COMMON_SH) "$(1)" >> $(2)
endef

这里用函数的参数做了从定向,我们接着搜,哪里使用这个函数:

grep -R "generate-common-build-props-with-product-vars-set" .

./core/Makefile:        $(call generate-common-build-props-with-product-vars-set,$(1),$(2))
./core/Makefile:define generate-common-build-props-with-product-vars-set
./core/Makefile:                $(call generate-common-build-props-with-product-vars-set,system,$@)

有两个地方调用了 generate-common-build-props-with-product-vars-set 函数:

我们主要看第二个地方:

# 定义 system_prop_file 变量的值
# system_prop_file 代表了一个 prop 属性文件
ifdef TARGET_SYSTEM_PROP
system_prop_file := $(TARGET_SYSTEM_PROP)
else
system_prop_file := $(wildcard $(TARGET_DEVICE_DIR)/system.prop)
endif

# 这里是一个 Makefile 规则
# intermediate_system_build_prop 的值类似于 out/target/product/xxx/obj/ETC/system_build_prop_intermediates/build.prop 是一个中间文件,用于生成最终的 /system/build.prop
$(intermediate_system_build_prop): $(BUILDINFO_SH) $(BUILDINFO_COMMON_SH) $(INTERNAL_BUILD_ID_MAKEFILE) $(BUILD_SYSTEM)/version_defaults.mk $(system_prop_file) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(API_FINGERPRINT)
	@echo Target buildinfo: $@
	@mkdir -p $(dir $@)
	$(hide) echo > $@
ifneq ($(PRODUCT_OEM_PROPERTIES),)
	$(hide) echo "#" >> $@; \
	        echo "# PRODUCT_OEM_PROPERTIES" >> $@; \
	        echo "#" >> $@;
	$(hide) $(foreach prop,$(PRODUCT_OEM_PROPERTIES), \
	    echo "import /oem/oem.prop $(prop)" >> $@;)
endif
	$(hide) PRODUCT_BRAND="$(PRODUCT_SYSTEM_BRAND)" \
	        PRODUCT_MANUFACTURER="$(PRODUCT_SYSTEM_MANUFACTURER)" \
	        PRODUCT_MODEL="$(PRODUCT_SYSTEM_MODEL)" \
	        PRODUCT_NAME="$(PRODUCT_SYSTEM_NAME)" \
	        PRODUCT_DEVICE="$(PRODUCT_SYSTEM_DEVICE)" \
			# 在这里调用 generate-common-build-props-with-product-vars-set 生成中间文件
			# out/target/product/xxx/obj/ETC/system_build_prop_intermediates/build.prop
	        $(call generate-common-build-props-with-product-vars-set,system,$@)
	$(hide) TARGET_BUILD_TYPE="$(TARGET_BUILD_VARIANT)" \
	        TARGET_BUILD_FLAVOR="$(TARGET_BUILD_FLAVOR)" \
	        TARGET_DEVICE="$(TARGET_DEVICE)" \
	        PRODUCT_DEFAULT_LOCALE="$(call get-default-product-locale,$(PRODUCT_LOCALES))" \
	        PRODUCT_DEFAULT_WIFI_CHANNELS="$(PRODUCT_DEFAULT_WIFI_CHANNELS)" \
	        PRIVATE_BUILD_DESC="$(PRIVATE_BUILD_DESC)" \
	        BUILD_ID="$(BUILD_ID)" \
	        BUILD_DISPLAY_ID="$(BUILD_DISPLAY_ID)" \
	        DATE="$(DATE_FROM_FILE)" \
	        BUILD_USERNAME="$(BUILD_USERNAME)" \
	        BUILD_HOSTNAME="$(BUILD_HOSTNAME)" \
	        BUILD_NUMBER="$(BUILD_NUMBER_FROM_FILE)" \
	        BOARD_BUILD_SYSTEM_ROOT_IMAGE="$(BOARD_BUILD_SYSTEM_ROOT_IMAGE)" \
	        AB_OTA_UPDATER="$(AB_OTA_UPDATER)" \
	        PLATFORM_VERSION="$(PLATFORM_VERSION)" \
	        PLATFORM_SECURITY_PATCH="$(PLATFORM_SECURITY_PATCH)" \
	        PLATFORM_BASE_OS="$(PLATFORM_BASE_OS)" \
	        PLATFORM_SDK_VERSION="$(PLATFORM_SDK_VERSION)" \
	        PLATFORM_PREVIEW_SDK_VERSION="$(PLATFORM_PREVIEW_SDK_VERSION)" \
	        PLATFORM_PREVIEW_SDK_FINGERPRINT="$$(cat $(API_FINGERPRINT))" \
	        PLATFORM_VERSION_CODENAME="$(PLATFORM_VERSION_CODENAME)" \
	        PLATFORM_VERSION_ALL_CODENAMES="$(PLATFORM_VERSION_ALL_CODENAMES)" \
	        PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION="$(PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION)" \
	        BUILD_VERSION_TAGS="$(BUILD_VERSION_TAGS)" \
	        $(if $(OEM_THUMBPRINT_PROPERTIES),BUILD_THUMBPRINT="$(BUILD_THUMBPRINT_FROM_FILE)") \
	        TARGET_CPU_ABI_LIST="$(TARGET_CPU_ABI_LIST)" \
	        TARGET_CPU_ABI_LIST_32_BIT="$(TARGET_CPU_ABI_LIST_32_BIT)" \
	        TARGET_CPU_ABI_LIST_64_BIT="$(TARGET_CPU_ABI_LIST_64_BIT)" \
	        TARGET_CPU_ABI="$(TARGET_CPU_ABI)" \
	        TARGET_CPU_ABI2="$(TARGET_CPU_ABI2)" \
	        bash $(BUILDINFO_SH) >> $@
			# 把 system_prop_file 中的内容写入中间文件
	$(hide) $(foreach file,$(system_prop_file), \
	    if [ -f "$(file)" ]; then \
	        echo Target buildinfo from: "$(file)"; \
	        echo "" >> $@; \
	        echo "#" >> $@; \
	        echo "# from $(file)" >> $@; \
	        echo "#" >> $@; \
	        cat $(file) >> $@; \
	        echo "# end of $(file)" >> $@; \
	    fi;)

	# FINAL_BUILD_PROPERTIES 的值来自 ADDITIONAL_BUILD_PROPERTIES
	# ADDITIONAL_BUILD_PROPERTIES 主要是 PRODUCT_PROPERTY_OVERRIDES 这个变量赋值, 而这个变量就是我们开发时经常用于自定义属性的, 一般在产品配置目录下定义
	# 这里把 FINAL_BUILD_PROPERTIES 中的属性写入中间文件
	$(if $(FINAL_BUILD_PROPERTIES), \
	    $(hide) echo >> $@; \
	            echo "#" >> $@; \
	            echo "# ADDITIONAL_BUILD_PROPERTIES" >> $@; \
	            echo "#" >> $@; )
	$(hide) $(foreach line,$(FINAL_BUILD_PROPERTIES), \
	    echo "$(line)" >> $@;)
	$(hide) build/make/tools/post_process_props.py $@ $(PRODUCT_SYSTEM_PROPERTY_BLACKLIST)

build_desc :=

ifeq (,$(filter true, $(TARGET_NO_KERNEL) $(TARGET_NO_RECOVERY)))
INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img
else
INSTALLED_RECOVERYIMAGE_TARGET :=
endif

# INSTALLED_BUILD_PROP_TARGET 的值为 out/target/product/xxx/system/build.prop
# 也就是我们最终要生成的文件
$(INSTALLED_BUILD_PROP_TARGET): $(intermediate_system_build_prop) $(INSTALLED_RECOVERYIMAGE_TARGET)
	@echo "Target build info: $@"
	# 这里把中间文件写入到最终文件中,同时剔除 ro.product.first_api_level 相关的行
	$(hide) grep -v 'ro.product.first_api_level' $(intermediate_system_build_prop) > $@

以上代码总结一下:

  • 准备好一个中间文件 out/target/product/xxx/obj/ETC/system_build_prop_intermediates/build.prop
  • 调用 buildinfo_common.sh,向中间文件写入数据
  • 把 system_prop_file 中的内容写入中间文件
  • 这里把用户自定义的的属性(PRODUCT_PROPERTY_OVERRIDES)写入中间文件
  • 这里把中间文件写入到最终文件(out/target/product/xxx/system/build.pro)中,同时剔除 ro.product.first_api_level 相关的行

以上就是 /system/build.prop,其他属性文件我们都可以依葫芦画瓢进行分析了。

如何添加系统属性

常见的自定义系统属性有三类:

  • 添加系统属性到 /system/build.prop
  • 添加系统属性到 /vendor/build.prop
  • 添加系统属性到 /product/build.prop

1.添加系统属性到 /system/build.prop

我们先看看生成 /system/build.prop 文件相关的部分源码:

# build/make/core/Makefile

ifdef TARGET_SYSTEM_PROP
system_prop_file := $(TARGET_SYSTEM_PROP)
else
system_prop_file := $(wildcard $(TARGET_DEVICE_DIR)/system.prop)
endif

在上文我们分析过 system_prop_file 文件的内容最终会写入到 /system/build.prop 中,/system/build.prop 的值又来自 TARGET_SYSTEM_PROP,所以我们添加一个属性文件,再修改 TARGET_SYSTEM_PROP 的值即可。

在我们的自定义 Product device/jelly/rice14 目录下,添加一个 system.prop 文件,文件的内容如下:

ro.rice14.test=2.0

接着在 device/jelly/rice14/BoardConfig.mk 中添加:

TARGET_SYSTEM_PROP += device/jelly/rice14/system.prop

然后重新编译系统,启动模拟器:

source build/envsetup.sh
lunch rice14-eng
make -j16
emulator

启动虚拟机后,我们可以进入虚拟机终端查看属性值:

adb shell

rice14:/ # getprop ro.rice14.test                                     
2.0# end of device/jelly/rice14/system.prop

这里查询到了我们刚添加的属性。

2.添加系统属性到 /vendor/build.prop

同样的,我们先看看生成 /vendor/build.prop 文件相关的部分源码:

ifdef property_overrides_split_enabled
FINAL_VENDOR_BUILD_PROPERTIES += \
    $(call collapse-pairs, $(PRODUCT_PROPERTY_OVERRIDES))
FINAL_VENDOR_BUILD_PROPERTIES := $(call uniq-pairs-by-first-component, \
    $(FINAL_VENDOR_BUILD_PROPERTIES),=)
endif  # property_overrides_split_enabled

这里会把 PRODUCT_PROPERTY_OVERRIDES 变量中的值赋值写入到 FINAL_VENDOR_BUILD_PROPERTIES,从变量名字就可以看出,这就是最终的 vendor 属性文件,所以我们通过修改 PRODUCT_PROPERTY_OVERRIDES 变量的值即可添加属性:

# device/jelly/rice14/rice14.mk
PRODUCT_PROPERTY_OVERRIDES += \
    ro.vendor.xxx=xxx \
    ro.vendor.yyy=yyy

3.添加系统属性到 /product/build.prop

同样的,我们先看看生成 /product/build.prop 文件相关的部分源码:

ifdef TARGET_PRODUCT_PROP
product_prop_files := $(TARGET_PRODUCT_PROP)
else
product_prop_files := $(wildcard $(TARGET_DEVICE_DIR)/product.prop)
endif

FINAL_PRODUCT_PROPERTIES += \
    $(call collapse-pairs, $(PRODUCT_PRODUCT_PROPERTIES) $(ADDITIONAL_PRODUCT_PROPERTIES))
FINAL_PRODUCT_PROPERTIES := $(call uniq-pairs-by-first-component, \
    $(FINAL_PRODUCT_PROPERTIES),=)

可以看到这里会把 product_prop_files 文件和 PRODUCT_PRODUCT_PROPERTIES 变量以及 ADDITIONAL_PRODUCT_PROPERTIES 变量中的值都会写入到最终的属性文件中。

所以上两节介绍的两种方式都可以,这里我们演示先通过添加变量值的方式添加属性:

# device/jelly/rice14/rice14.mk
PRODUCT_PRODUCT_PROPERTIES += \
     ro.product.xxx=xxx \
     ro.product.yyy=yyy

属性与 Selinux

Android O 以后,属性的 Selinux 的规则变得复杂起来了,今天我们先看一个简单的例子,做基本的了解。在Hal与硬件服务的 treble 章节我们还会继续来学习属性相关的 Selinux 配置。

#读写属性的示例代码

首先我们在 device/jelly/rice14 目录下创建如下的目录:

PropTest/
├── Android.bp
└── prop_test.cpp

其中 prop_test.cpp 具体内容如下:

#include <string>
 
#include <cutils/properties.h>
#define LOG_TAG "prop_test"
#include <log/log.h>
#include <android-base/properties.h>
 

using namespace std;
 
int main(int argc, char *argv[])
{

    // 打印版本信息
    char android_version[PROPERTY_VALUE_MAX];
    property_get("ro.build.version.release", android_version, "");
 
    ALOGD("android version : %s", android_version);
 
    // 写入自定义属性
    property_set("vendor.my.prop.test", "xxx");

    // 读自定义属性
    char prop_test[PROPERTY_VALUE_MAX];
    property_get("vendor.my.prop.test", prop_test, "");
    ALOGD("prop test : %s", prop_test);
    
    while(1) {

    }
 
    return 0;
}

配套的编译文件 Android.bp 如下:

至此,整个示例程序就完成了,最后修改我们自定义 Product 的配置文件 device/jelly/rice14/rice14.mk :

PRODUCT_PACKAGES += 
    # ......
    prop_test \

#Selinux 的配置

接着需要配置 SeLinux:

在 device/jelly/rice14 目录下创建如下的文件与目录:

sepolicy/
├── file_contexts
├── property_contexts
├── property.te
└── prop_te

不要忘了配置自定义 sepolicy 目录,在 device/jelly/rice14/rice14.mk 中添加如下内容:

BOARD_SEPOLICY_DIRS += \
    device/jelly/rice14/sepolicy

首先在 property.te 中定义属性类型

type vendor_mytest_prop, property_type;

接着在 property_contexts 中配置好我们自定义属性 vendor.my.prop.test 的安全上下文:

vendor.my.prop.test u:object_r:vendor_mytest_prop:s0

接着在 prop_test.te 中配置好可执行文件和对应进程的类型和域转换规则以及属性的读写权限:


# 可执行文件对应进程类型
type  myprop_test_dt, domain;

# 可执行文件类型
type myprop_test_dt_exec, exec_type, vendor_file_type, file_type;


# 域转换规则
init_daemon_domain(myprop_test_dt)
domain_auto_trans(shell, myprop_test_dt_exec, myprop_test_dt)


# 属性读写规则
set_prop(myprop_test_dt, vendor_mytest_prop);
get_prop(myprop_test_dt, vendor_mytest_prop);

get_prop(myprop_test_dt, exported2_default_prop);

在属性读写规则中,我们添加了对 exported2_default_prop 类型属性的读取规则,exported2_default_prop 是源码中 ro.build.version.release 属性对应的 type,我是通过如下的搜索命令查找到的:

find . -name "property_contexts" | xargs grep ro.build.version.release
./system/sepolicy/public/property_contexts:ro.build.version.release u:object_r:exported2_default_prop:s0 exact string
./system/sepolicy/prebuilts/api/29.0/public/property_contexts:ro.build.version.release u:object_r:exported2_default_prop:s0 exact string
./system/sepolicy/prebuilts/api/28.0/public/property_contexts:ro.build.version.release u:object_r:exported2_default_prop:s0 exact string

最后我们需要再 file_contexts 中配置可执行文件的安全上下文:

/vendor/bin/prop_test                   u:object_r:myprop_test_dt_exec:s0

接着我们就可以执行在虚拟机的 shell 中执行 prop_test 可执行文件了:

# 重新编译源码
source build/envsetup.sh
lunch rice14-eng
make -j16
# 启动虚拟机
emulator

# 重开一个终端,进入虚拟机 shell 环境
adb shell
# root
su 
# selinux 配置为 Permissive 模式
setenforce 0
# 退出 root
exit
prop_test

接着我们查看 log:

logcat | grep prop_test                                                          
10-23 10:25:42.420  2959  2959 W prop_test: type=1400 audit(0.0:27): avc: denied { read } for name="u:object_r:vendor_mytest_prop:s0" dev="tmpfs" ino=6750 scontext=u:r:shell:s0 tcontext=u:object_r:vendor_mytest_prop:s0 tclass=file permissive=0
10-23 10:25:42.424  2959  2959 D prop_test: android version : 10
10-23 10:25:42.428  2959  2959 D prop_test: prop test : 
10-23 10:29:41.930  3704  3704 W prop_test: type=1400 audit(0.0:58): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:29:41.930  3704  3704 W prop_test: type=1400 audit(0.0:60): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:29:41.930  3704  3704 W prop_test: type=1400 audit(0.0:61): avc: denied { use } for path="/dev/goldfish_pipe" dev="tmpfs" ino=7279 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:29:41.930  3704  3704 W prop_test: type=1400 audit(0.0:62): avc: denied { use } for path="/dev/goldfish_pipe" dev="tmpfs" ino=7279 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:31:17.980  3919  3919 I prop_test: type=1400 audit(0.0:87): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=1
10-23 10:31:17.980  3919  3919 I prop_test: type=1400 audit(0.0:88): avc: denied { read write } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:object_r:devpts:s0 tclass=chr_file permissive=1
10-23 10:31:17.980  3919  3919 I prop_test: type=1400 audit(0.0:89): avc: denied { read write } for path="socket:[9765]" dev="sockfs" ino=9765 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=unix_stream_socket permissive=1
10-23 10:31:17.980  3919  3919 I prop_test: type=1400 audit(0.0:90): avc: denied { use } for path="/vendor/bin/prop_test" dev="dm-1" ino=158 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:shell:s0 tclass=fd permissive=1
10-23 10:31:18.000  3919  3919 D prop_test: android version : 10
10-23 10:31:18.002  3919  3919 D prop_test: prop test : xxx

发现我们的程序任然缺少一些权限,我们把权限相关的 log 拷贝下来,在源码根目录下新建一个 avc_log.txt 文件,把缺少权限相关的 log 拷贝进去:

# avc_log.txt
10-23 10:38:05.670  2910  2910 W prop_test: type=1400 audit(0.0:28): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:38:05.670  2910  2910 W prop_test: type=1400 audit(0.0:30): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:38:05.670  2910  2910 W prop_test: type=1400 audit(0.0:31): avc: denied { use } for path="socket:[10955]" dev="sockfs" ino=10955 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:38:05.670  2910  2910 W prop_test: type=1400 audit(0.0:32): avc: denied { use } for path="/dev/goldfish_pipe" dev="tmpfs" ino=7343 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=0
10-23 10:38:57.400  3034  3034 I prop_test: type=1400 audit(0.0:39): avc: denied { use } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=fd permissive=1
10-23 10:38:57.400  3034  3034 I prop_test: type=1400 audit(0.0:40): avc: denied { read write } for path="/dev/pts/0" dev="devpts" ino=3 scontext=u:r:myprop_test_dt:s0 tcontext=u:object_r:devpts:s0 tclass=chr_file permissive=1
10-23 10:38:57.400  3034  3034 I prop_test: type=1400 audit(0.0:41): avc: denied { read write } for path="socket:[10955]" dev="sockfs" ino=10955 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:adbd:s0 tclass=unix_stream_socket permissive=1
10-23 10:38:57.410  3034  3034 I prop_test: type=1400 audit(0.0:42): avc: denied { use } for path="/vendor/bin/prop_test" dev="dm-1" ino=158 scontext=u:r:myprop_test_dt:s0 tcontext=u:r:shell:s0 tclass=fd permissive=1

接着执行命令 audit2allow -i avc_log.txt 生成缺失的权限配置:

allow myprop_test_dt adbd:fd use;
allow myprop_test_dt adbd:unix_stream_socket { read write };
allow myprop_test_dt devpts:chr_file { read write };
allow myprop_test_dt shell:fd use;

把这些权限配置加入到 prop_test.te 后,重新编译系统,启动虚拟机,程序即可正常运行。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/471870.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

cdn尝试(减少打包体积)

如果是vue-cli创造的工程&#xff0c;在build后面加上 --report&#xff0c;就会在dist文件夹下出现report.html用于分析打包后个文件的体积 也可以使用插件&#xff1a; webpack使用webpack-bundle-analyzer进行分析&#xff1b; vite使用rollup-plugin-visualizer进行分析…

论文解读—— 基于边缘梯度方向插值和 Zernike 矩的亚像素边缘检测

论文&#xff1a;《 Subpixel edge detection based on edge gradient directional interpolation and Zernike moment》 地址&#xff1a; http://www.dpi-proceedings.com/index.php/dtcse/article/view/24488 摘要 在本文中&#xff0c;我们提出了一种基于边缘梯度方向插值…

D2587A高压大电流DC-DC,采用TO220T-5L或TO263-5L封装形式

1、概述 D2587A稳压器是专为反激式、升压和正向转换器应用而设计的单片集成电路。该器件提供四种不同的输出电压版本&#xff1a;3.3V、5V、12V 和可调节电压。这些稳压器需要的外部元器件很少&#xff0c;因此具有成本效益&#xff0c;并且易于使用。该电源开关是一款5A NPN器…

介绍5款 世界范围内比较广的 5款 mysql Database Management Tool

介绍5款 世界范围内比较广的 5款 Mysql Database Management Tool 文章目录 介绍5款 世界范围内比较广的 5款 Mysql Database Management Tool前言MySQL Workbench&#xff1a;Navicat Premium&#xff1a;DBeaver Community&#xff1a;HeidiSQL&#xff1a;SQLyog&#xff1a…

每日学习笔记:C++ STL 的无序容器(unordered_set、unordered_map)

定义 特性 能够快速查找元素 操作函数 负载系数 元素个数 / bucket个数 提供哈希函数 提供等价准则 方法一&#xff1a;重写元素的操作符 方法二&#xff1a;自定义函数对象

点云配准:从二维到三维的艺术

点云配准&#xff1a;从二维到三维的艺术 在计算机视觉和机器学习的领域中&#xff0c;配准是一个至关重要的步骤&#xff0c;它涉及到将不同视角或时间点捕获的数据集对齐到同一个坐标系统中。这一过程不仅对二维图像至关重要&#xff0c;而且在三维世界的理解中也发挥着关键作…

Apipost IDEA插件新升级,Apipost Helper上架IDEA插件市场

大家好&#xff01;今天向大家介绍一个非常方便的IDEA插件——Apipost Helper&#xff01;相信很多使用过Apipost的朋友在开发过程中都希望能够直接将编写好的API同步至Apipost&#xff0c;而无需手动填写。前段时间&#xff0c;Apipost推出了Apipost IDEA插件的内测版&#xf…

YOLOv4学习

YOLOv4学习 什么是 parameter aggregation&#xff1f;什么是 Skip-connections&#xff1f;正是梯度的反向传播才使得损失函数得以在训练过程中不断优化&#xff0c;以使得模型逐渐学习到“正确的”知识&#xff0c;对吗&#xff1f;什么是Bag-of-Freebies&#xff1f;什么是B…

LLM—Transformer作用及信息流

一、Transformer的作用 Transformer架构的精髓在于其创新性地采用了编码器与解码器的堆叠设计&#xff0c;这一设计巧妙地融合了多头自注意力机制&#xff08;Multi-Head Attention&#xff09;和位置前馈网络&#xff08;Position-wise Feed Forward Network&#xff09;两大核…

LeetCode:2312. 卖木头块(DP Java)

目录 2312. 卖木头块 题目描述&#xff1a; 实现代码与解析&#xff1a; dp 原理思路&#xff1a; 2312. 卖木头块 题目描述&#xff1a; 给你两个整数 m 和 n &#xff0c;分别表示一块矩形木块的高和宽。同时给你一个二维整数数组 prices &#xff0c;其中 prices[…

LLM之RAG实战(三十二)| 使用RAGAs和LlamaIndex评估RAG

在之前的文章中&#xff0c;我们介绍了RAG的基本流程和各种优化方法&#xff08;query重写&#xff0c;语义分块策略以及重排序等&#xff09;。那么&#xff0c;如果发现现有的RAG不够有效&#xff0c;该如何评估RAG系统的有效性呢&#xff1f; 在本文中&#xff0c;我们将介绍…

OJ_汉诺塔问题

有三根柱子和一些大小不同的圆盘&#xff0c;开始时所有的圆盘都按照从小到大的顺序堆叠在柱子A上&#xff0c;要求把所有的圆盘移动到柱子C上&#xff0c;但是移动过程中要满足以下规则&#xff1a; 每次只能移动一个圆盘。不能将一个较大的圆盘放在较小的圆盘上面。 #incl…

【工具】Docker 入门及常用指令

SueWakeup 个人主页&#xff1a;SueWakeup 系列专栏&#xff1a;为祖国的科技进步添砖Java 个性签名&#xff1a;保留赤子之心也许是种幸运吧 目录 1. 什么是 Docker &#xff1f; 2. Docker 安装 3. Docker 镜像 4. Docker 容器 4.1 运行容器 4.2 查看正在运行的容器 4…

leetcode 714

leetcode 714 题目 例子 思路1 使用dp[n][2] 存储最佳利润值&#xff0c;动态规划的思路&#xff0c;重要的是转移方程。 代码1 class Solution { public: int maxProfit(vector& prices, int fee) { int n prices.size(); //dp[i][0] 前i天手里没有股票的最大利润 //…

Spring Bean的生命周期流程

前言 Java 中的公共类称之为Java Bean&#xff0c;而 Spring 中的 Bean 指的是将对象的生命周期&#xff0c;交给Spring IoC 容器来管理的对象。所以 Spring 中的 Bean 对象在使用时&#xff0c;无需通过 new 来创建对象&#xff0c;只需要通过 DI&#xff08;依赖注入&#x…

使用光标精灵更换电脑鼠标光标样式,一键安装使用

想要让自己在使用电脑时更具个性化&#xff0c;让工作和娱乐更加愉快&#xff0c;改变你的电脑指针光标皮肤可能是一个简单而有效的方法。很多人或许并不清楚如何轻松地调整电脑光标样式&#xff0c;下面我就来分享一种简单的方法。 电脑光标在系统里通常只有几种默认图案&…

一次性支持 200 万字无损上下文!Kimi智能助手玩了个大的——月之暗面「登月」最新进展!

让大模型一次性无损地「吃下」一本书已经不是什么稀奇的事了&#xff0c;但如果我告诉你是下面&#x1f447;&#x1f3fb;这样一本近百万字的书呢&#xff1f; 没错&#xff0c;这么疯狂的事竟然真的发生了——就在昨天月之暗面&#xff08;Moonshot AI&#xff09;召集了一次…

mysql 更新时,旧值与新值相同会怎么做?

文章目录 1 问题描述2 验证2.1 验证猜想12.2 验证猜想2 3 结论4 mysql 为什么这么设计呢&#xff1f; 1 问题描述 创建一张表t&#xff0c;插入一行数据 mysql> CREATE TABLE t ( id int(11) NOT NULL primary key auto_increment, a int(11) DEFAULT NULL ) ENGINEInnoDB…

9.登入页面

登入页面 在pages中新建页面login 修改代码 <template><view></view> </template><script setup></script><style lang"scss"></style>添加头像组件 官网 https://vkuviewdoc.fsq.pub/components/avatar.html …

vue:功能【xlsx】动态行内合并

场景&#xff1a;纯前端导出excel数据&#xff0c;涉及到列合并、行合并。 注&#xff09;当前数据表头固定&#xff0c;行内数据不固定。以第一列WM为判断条件&#xff0c;相同名字的那几行数据合并单元格。合并的那几行数据&#xff0c;后面的列按需求进行合并。 注&#x…