Android rom開發:自定義序列號ro.serialno

本文基於Android 5.1版本SDK。

客戶需求:自定義sn,統一規則編號。

第一部分:背景知識

Android的sn由SystemProperties.get("ro.serialno", "");而來。

ro.serialno並不像常見的其他屬性一樣存在於build.prop等prop文件中,而是在/system/core/init/init.c裏由ro.boot.serialno轉換而來,轉換的函數是export_kernel_boot_props,源碼如下:

static void export_kernel_boot_props(void)
{
    char tmp[PROP_VALUE_MAX];
    int ret;
    unsigned i;
    struct {
        const char *src_prop;
        const char *dest_prop;
        const char *def_val;
    } prop_map[] = {
        { "ro.boot.serialno", "ro.serialno", "", },
        { "ro.boot.mode", "ro.bootmode", "unknown", },
        { "ro.boot.baseband", "ro.baseband", "unknown", },
        { "ro.boot.bootloader", "ro.bootloader", "unknown", },
    };

    for (i = 0; i < ARRAY_SIZE(prop_map); i++) {
        ret = property_get(prop_map[i].src_prop, tmp);
        if (ret > 0)
            property_set(prop_map[i].dest_prop, tmp);
        else
            property_set(prop_map[i].dest_prop, prop_map[i].def_val);
    }

    ret = property_get("ro.boot.console", tmp);
    if (ret)
        strlcpy(console, tmp, sizeof(console));

    /* save a copy for init's usage during boot */
    property_get("ro.bootmode", tmp);
    strlcpy(bootmode, tmp, sizeof(bootmode));

    /* if this was given on kernel command line, override what we read
     * before (e.g. from /proc/cpuinfo), if anything */
    ret = property_get("ro.boot.hardware", tmp);
    if (ret)
        strlcpy(hardware, tmp, sizeof(hardware));
    property_set("ro.hardware", hardware);

    snprintf(tmp, PROP_VALUE_MAX, "%d", revision);
    property_set("ro.revision", tmp);

    /* TODO: these are obsolete. We should delete them */
    if (!strcmp(bootmode,"factory"))
        property_set("ro.factorytest", "1");
    else if (!strcmp(bootmode,"factory2"))
        property_set("ro.factorytest", "2");
    else
        property_set("ro.factorytest", "0");
}

重點看for循環部分,可以明顯看到,ro.serialno的值是由ro.boot.serialno而來。

export_kernel_boot_props在process_kernel_cmdline中調用

static void process_kernel_cmdline(void)
{
    /* don't expose the raw commandline to nonpriv processes */
    chmod("/proc/cmdline", 0440);

    /* first pass does the common stuff, and finds if we are in qemu.
     * second pass is only necessary for qemu to export all kernel params
     * as props.
     */
    import_kernel_cmdline(0, import_kernel_nv);
    if (qemu[0])
        import_kernel_cmdline(1, import_kernel_nv);

    /* now propogate the info given on command line to internal variables
     * used by init as well as the current required properties
     */
    export_kernel_boot_props();
}

process_kernel_cmdline中還調用了import_kernel_nv,import_kernel_nv的方法體如下:

static void import_kernel_nv(char *name, int for_emulator)
{
    char *value = strchr(name, '=');
    int name_len = strlen(name);

    if (value == 0) return;
    *value++ = 0;
    if (name_len == 0) return;

    if (for_emulator) {
        /* in the emulator, export any kernel option with the
         * ro.kernel. prefix */
        char buff[PROP_NAME_MAX];
        int len = snprintf( buff, sizeof(buff), "ro.kernel.%s", name );

        if (len < (int)sizeof(buff))
            property_set( buff, value );
        return;
    }

    if (!strcmp(name,"qemu")) {
        strlcpy(qemu, value, sizeof(qemu));
    } else if (!strncmp(name, "androidboot.", 12) && name_len > 12) {
        const char *boot_prop_name = name + 12;
        char prop[PROP_NAME_MAX];
        int cnt;

        cnt = snprintf(prop, sizeof(prop), "ro.boot.%s", boot_prop_name);
        if (cnt < PROP_NAME_MAX)
            property_set(prop, value);
    }
}

代碼很清晰,從cmdline中將androidboot.*的屬性拿出來設置給了ro.boot.*的屬性。在終端裏面執行cat /proc/cmdline,如下:

shell@msm8909:/ # cat /proc/cmdline
sched_enable_hmp=1 console=ttyHSL0,115200,n8 androidboot.console=ttyHSL0 androidboot.hardware=qcom user_debug=31 msm_rtb.filter=0x3F ehci-hcd.pa
rk=3 androidboot.bootdevice=7824900.sdhci lpm_levels.sleep_disabled=1 earlyprintk androidboot.selinux=permissive androidboot.emmc=true androidbo
ot.serialno=c336a56 androidboot.baseband=msm mdss_mdp3.panel=1:dsi:0:qcom,mdss_dsi_jd9366_800p_video:1:none

可以看到androidboot.serialno=c336a56,說明sn是在bootloader中獲得,通過cmdline傳遞到內核。

至此,sn的傳遞和賦值流程基本梳理清楚。

第二部分:實現重新對sn賦值

自定義sn的思路是在系統的persist分區(persist分區的內容不會因爲恢復出廠設置而被清除,重新刷機會清除)裏面預設一個文本文件,系統啓動之後,從app裏面向這個文本文件寫入按規則編號的sn,然後重啓系統,init進程重新讀取文本里面的值來設置ro.serialno和ro.boot.serialno。

首先在init.c裏面修改,屏蔽掉ro.boot.serialno賦值的邏輯,然後在export_kernel_boot_props的for循環裏面,i=0的時候是對ro.serialno進行賦值,在這裏增加需要的代碼:fopen打開文本獲取到文件指針fp->fgets從fp讀取一行內容到buf裏面->調用property_set將buf的值設置給ro.serialno和ro.boot.serialno。

修改之後,重新編譯boot.img。

make bootimage -j8

然後重刷boot:

adb reboot bootloader
fastboot flash boot boot.img
fastboot reboot

重啓進系統,dmesg | grep init查看打印,尷尬的問題來了,fp是null。看來在init進程啓動的時候,persist分區還沒掛載,因此訪問不到persist分區。在init.c裏讀取文本這個路子行不通。

在這裏插入圖片描述

換個思路

init.rc裏面on boot之後,以root權限執行腳本,腳本中讀取文本內容,然後將內容設置給ro.serialno和ro.boot.serialno。

腳本內容和init.rc增加的代碼都很簡單:

on boot
    start setsn

service setsn /sbin/busybox sh /system/etc/setsn.sh service
    oneshot
    disabled
    user root
    group root

**********************************
**********************************
#!/system/bin/sh

SN_FILE_PATH=/persist/sn.txt
SN_FILE_VALUE=`cat $SN_FILE_PATH`
SN_BACKUP_VALUE=`getprop persist.serialno.backup`

if [[ "$SN_FILE_VALUE" != "" ]];
then
    SN=$SN_FILE_VALUE
else
    SN=$SN_BACKUP_VALUE
fi

setprop ro.serialno $SN
setprop ro.boot.serialno $SN

此方法實測有效。

PS:
在調試過程中發現,init進程中對ro.serialno和ro.boot.serialno只能設值一次,如果不屏蔽init.c裏面設值的邏輯,那麼執行腳本將不會改變sn。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章