ADD software version display

1. Problem Description

在手机拨号盘输入暗码*#xxx#,弹出对话框,显示手机各image版本号。

2. Analysis

已知条件:

  1. 手机各个分区版本已经写入到了工程源码的指定文件中(一般是放在development/version/include/version.inc文件中),其具体的形式如下所示:
  1. #define SCATTER_VER "K4H5EMMCCE00"
  2. #define PRELOADER_VER "P4H5H0H0CE00"
  3. #define UBOOT_VER "U4H5H0H0CE00"
  4. #define ANDROID_BOOT_VER "B4H5H0H0CE00"
  5. #define RECOVERY_VER "R4H5H0H0CE00"
  6. #define ANDROID_SYS_VER "Y4H5H0H0CE00"
  7. #define CACHE_VER "H4H5H0H0CE00"
  8. #define USRDATA_VER "S4H5H0H0CE00"
  9. #define LOGO_VER "L4H5H0H0CE00"
  10. #define FAT_VER "F4H5H0H0CE00"
  11. #define CUSTPACK_VER "C4H5HEU0CE00"
  12. #define SIMLOCK_VER "X4H0H0H0CE00"
  13. #define AP_DATABASE_VER "A4H5H0H0CE00"
  14. #define MODEM_DATABASE_VE "O4H5H0H0CE00"
  15. #define TRUSTZONE_VER "T4H5H0H0CE00"
  1. 输入暗码的dialog软件为第三方apk,该软件使用读系统属性的方法来获取版本信息,提供的系统属性接口如下:
  1. ro.tct.cust.ver /*保存custpack分区版本号的系统属性*/
  2. ro.tct.non.ver /*保存modem分区版本号的系统属性*/
  3. ro.tct.sys.ver /*保存system分区版本号的系统属性*/
  4. ro.tct.boot.ver /*保存boot分区版本号的系统属性*/
  5. ro.tct.preloader.ver /*保存preloader分区版本号的系统属性*/
  6. ro.tct.bootloader.ver /*保存bootloader分区版本号的系统属性*/
  7. ro.tct.reco.ver /*保存recovery分区版本号的系统属性*/

即当输入*#xxx#后,该apk会读取设备中的各个系统属性,然后将读到的值通过AlertDialog显示出来。

分析思路

既然已经知道了各个分区版本号所存储的文件位置,又知道三方apk读取信息的接口,那么需要做的就是:

  1. 读取文件version.inc中的数据;
  2. 将读到的值存入到对应的系统属性中;

问题似乎已经得到了解决,但是system、boot、preloader、bootloader、recovery分区的版本号在fota版本升级后会变化。也就是说通过上面的做法,将编译好的系统刷如设备中,该设备的各个分区版本号是永远不能改变的,那么上面的做法就无法满足版本升级后部分分区版本号改变的要求了。

因此,可以分为2种不同情况来处理,具体如下图所示:

  • 对于在fota升级前后不需要改变的版本号,直接读取该版本号,然后将它写入对应系统属性中。

  • 对于在fota升级前后需要改变的版本号,首先,将它读取出来写入到一个中间文件(对于boot和system版本号是存放.ver文件中,其他三个的版本号是放在对应的.img文件中特定位置并且加有标识字符)中,;然后,在手机每次启动时,动态的读取这个中间文件的值,并将读到的值写入对应系统属性中;如果有fota升级,在升级时会同时替换升级分区的中间文件,这样就可以保证每次显示版本号时,都是读取的最新版本号。

3. Solution

3.1 处理升级前后不改变的版本号

  1. 修改/build/core/Makefile文件
    从文件development/version/include/version.inc中分别读取modem分区版本号和custpack分区版本号,然后写入系统属性中,具体代码如下:
  1. VERSIONDEF := development/version/include/version.inc #引进version.inc文件
  2. VERSIONLEN := 12
  3. CUSTPACK_VER := $(shell awk ' /CUSTPACK_VER/ { print substr($$NF, 2,$(VERSIONLEN) ) }' $(TOPDIR)$(VERSIONDEF))
  4. MODEM_DATABASE_VE := $(shell awk ' /MODEM_DATABASE_VE/ { print substr($$NF, 2,$(VERSIONLEN) ) }' $(TOPDIR)$(VERSIONDEF))
  5. $(hide) TARGET_BUILD_TYPE="$(TARGET_BUILD_VARIANT)"
  6. ......
  7. CUSTPACK_VER="$(CUSTPACK_VER)" \
  8. MODEM_DATABASE_VE="$(MODEM_DATABASE_VE)" \
  9. ......
  1. 修改文件build/tools/buildinfo.sh
  1. echo "ro.tct.cust.ver=$CUSTPACK_VER"
  2. echo "ro.tct.non.ver=$MODEM_DATABASE_VE“

到现在modem分区版本号和custpack分区版本号已经写入到文件out目录下build.prop文件中,通过打开该文件可以看到从version.inc中读到的版本号。

3.2 处理升级前后改变的版本号

  • 修改/build/core/Makefile文件
  1. #write boot version to boot.ver
  2. if [ -f $(TOPDIR)$(VERSIONDEF) ];#将boot分区版本号写入到临时文件boot.ver
  3. then
  4. awk ' /ANDROID_BOOT_VER/ { print substr($$NF, 2, $(VERSIONLEN)) }' $(TOPDIR)$(VERSIONDEF) > $(TARGET_ROOT_OUT)/boot.ver ;
  5. fi
  6. #write boot version to boot.ver
  7. ......
  8. define build-systemimage-target
  9. #write system version to system.ver
  10. if [ -f $(TOPDIR)$(VERSIONDEF) ];#将system分区版本号写入到临时文件boot.ver
  11. then
  12. awk ' /ANDROID_SYS_VER/ { print substr($$NF, 2,$(VERSIONLEN)) }' $(TOPDIR)$(VERSIONDEF) > $(TARGET_OUT)/system.ver;
  13. fi
  14. #write system version to system.ver
  15. #write recovery version and the string of JRD_VERSION_MARK_ to recovery.img
  16. RECOVER_VERSION = JRD_VERSION_MARK_$(shell awk ' /RECOVERY_VER/ { print substr($$NF, 2,$(VERSIONLEN) ) }' $(TOPDIR)$(VERSIONDEF))
  17. # Assumes this has already been stripped
  18. ifdef BOARD_KERNEL_CMDLINE
  19. INTERNAL_RECOVERYIMAGE_ARGS += --cmdline "$(BOARD_KERNEL_CMDLINE) $(RECOVER_VERSION)"
  20. else
  21. INTERNAL_RECOVERYIMAGE_ARGS += --cmdline "$(RECOVER_VERSION)"
  22. Endif
  23. #write recovery version and the string of JRD_VERSION_MARK_ to recovery.img
  • 修改文件/vendor/mediatek/proprietary/bootable/bootloader/lk/Android.mk在指定头文件中定义一个宏定义,编译后再打开该version.h文件,可以看到如#define LK_VER JRD_VERSION_MARK_U4HB3004CE00样子的宏定义。
  1. VERSIONDEF := development/version/include/version.inc
  2. VERSIONLEN := 12
  3. if [ -f $(LK_ROOT_DIR)/$(VERSIONDEF) ];
  4. then
  5. echo -n "#define LK_VER JRD_VERSION_MARK_" > $(LK_ROOT_DIR)/vendor/mediatek/proprietary/bootable/bootloader/lk/include/version.h ;
  6. awk ' /UBOOT_VER/ { print substr($$NF, 2, $(VERSIONLEN)) }' $(LK_ROOT_DIR)/$(VERSIONDEF) >> $(LK_ROOT_DIR)/vendor/mediatek/proprietary/bootable/bootloader/lk/include/version.h ;
  7. fi
  • 修改文件vendor/mediatek/proprietary/bootable/bootloader/lk/kernel/main.c,将前面的宏定义LK_VER写入到对应的.img文件中去。
  1. #include "version.h"
  2. ststatic int bootstrap2(void *arg)
  3. {
  4. ......
  5. #ifdef LK_VER
  6. #define xstr(s) str(s)
  7. #define str(s) #s
  8. printf("%s\n", xstr(LK_VER));/*大概是只要使用了该宏,就会为该宏分配存储空间并存储在目标文件img中*/
  9. #endif
  10. ......
  11. }
  • 修改文件vendor/mediatek/proprietary/bootable/bootloader/preloader/Android.mk在指定头文件中定义一个宏定义,编译后再打开该version.h文件,可以看到如#define PRELOADER_VER JRD_VERSION_MARK_P4HB3004CE00样子的宏定义。
  1. VERSIONDEF := development/version/include/version.inc
  2. VERSIONLEN := 12
  3. if [ -f $(PRELOADER_ROOT_DIR)/$(VERSIONDEF) ];
  4. then
  5. echo -n "#define PRELOADER_VER JRD_VERSION_MARK_" > $(PRELOADER_DIR)/platform/mt6735/src/core/inc/version.h ;
  6. awk ' /PRELOADER_VER/ { print substr($$NF, 2, $(VERSIONLEN)) }' $(PRELOADER_ROOT_DIR)/$(VERSIONDEF) >> $(PRELOADER_DIR)/platform/mt6735/src/core/inc/version.h ;
  7. fi
  • 修改文件vendor/mediatek/proprietary/bootable/bootloader/preloader/platform/mt6735/src/core/main.c,将前面的宏定义PRELOADER_VER写入到对应的.img文件中去。
  1. #include "version.h"
  2. static void bldr_pre_process(void)
  3. {
  4. ......
  5. #ifdef PRELOADER_VER
  6. #define xstr(s) str(s)
  7. #define str(s) #s
  8. printf("%s\n", xstr(PRELOADER_VER));
  9. #endif
  10. ......
  11. }
  • 修改文件vendor/jrdcom/proprietary/traceability/main.c,将.ver文件中的版本号读取出来,然后写入相应的系统属性.ro中
  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <errno.h>
  4. #include <stdlib.h>
  5. #include <sys/types.h>
  6. #include <fcntl.h>
  7. #include <unistd.h>
  8. #define __ALLOCATE_CFG_AUDIO_DEFAULT_H
  9. #include "libnvram.h"
  10. #include "CFG_PRODUCT_INFO_File.h"
  11. #include "Custom_NvRam_LID.h"
  12. #include <cutils/properties.h>
  13. //#define printf(x...) do { KLOG_ERROR("!!!!!!!!!!!!!!", x); } while (0)
  14. #include <android/log.h>
  15. #define LOG_TAG "gettra"
  16. #define printf(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
  17. //VERSION parameter
  18. #define VERSION_FILE_PATCH "/system/system.ver"
  19. #define VERSION_OFFSET 0
  20. #define VERSION_SIZE 12
  21. #define VERSION_ANBOOT_FILE_PATCH "/boot.ver"
  22. int main(int argc, char *argv[])
  23. {
  24. int fid_ver;
  25. unsigned char ver[13]={0};
  26. unsigned char anbootver[13]={0};
  27. int fid_anbootver;
  28. memset(ver, 0x00, 12);
  29. memset(anbootver, 0x00, 12);
  30. //get version and set ro.tct.sys.ver
  31. fid_ver = open( VERSION_FILE_PATCH, O_RDONLY);
  32. if(fid_ver < 0)
  33. {
  34. perror("open:error");
  35. }
  36. else
  37. {
  38. lseek(fid_ver, VERSION_OFFSET , SEEK_SET);
  39. read(fid_ver, ver, VERSION_SIZE);
  40. close(fid_ver);
  41. }
  42. fid_anbootver = open( VERSION_ANBOOT_FILE_PATCH, O_RDONLY);
  43. if(fid_anbootver < 0)
  44. {
  45. //perror("open:error");
  46. printf("fid_anbootver error\n");
  47. }
  48. else
  49. {
  50. printf("fid_anbootver \n");
  51. lseek(fid_anbootver, VERSION_OFFSET , SEEK_SET);
  52. read(fid_anbootver, anbootver, VERSION_SIZE);
  53. close(fid_anbootver);
  54. }
  55. property_set("ro.tct.sys.ver", ver);
  56. property_set("ro.tct.boot.ver", anbootver);
  57. printf("gettracability exe over!\n");
  58. return 1;
  59. }
  60. #undef __ALLOCATE_CFG_AUDIO_DEFAULT_H
  • 修改文件vendor/jrdcom/proprietary/version/main.c,将.img文件中的版本号读取出来,然后写入相应的系统属性.ro中
  1. /*
  2. * Get version number from raw partition image, output to one file.
  3. */
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include <errno.h>
  7. #include <sys/types.h>
  8. #include <sys/stat.h>
  9. #include <fcntl.h>
  10. #include <unistd.h>
  11. #include <sys/system_properties.h>
  12. #include <utils/Log.h>
  13. #define printf ALOGE
  14. #define PRELOADER "/dev/block/mmcblk0boot0"
  15. #define BOOTLOADER "/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/lk"
  16. #define RECOVERYIMG "/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/recovery"
  17. #define PRELOADER_SIZE 0x40000
  18. #define BOOTLOADER_SIZE 0x60000
  19. #define RECOVERYIMG_SIZE 0x900000
  20. #define JRD_VERSION_MARK "JRD_VERSION_MARK_"
  21. #define JRD_VERSION_MARK_LEN 17
  22. #define JRD_VERSION_LEN 12
  23. static char buff[RECOVERYIMG_SIZE];
  24. /*这个方法就是先从指定img文件中读size个字节到一个临时数组ver中,然后根据size的大小,将数组中的值存入相应的系统属性.ro文件中*/
  25. int getver(char *img, int size)
  26. {
  27. int ret = 0;
  28. char ver[JRD_VERSION_MARK_LEN + JRD_VERSION_LEN + 1];
  29. char setver[JRD_VERSION_MARK_LEN + JRD_VERSION_LEN + 1]={'\0'};
  30. int f;
  31. ssize_t s;
  32. int p;
  33. /*为数组分配存储空间大小为 17+12+1*/
  34. memset(ver, 0, JRD_VERSION_MARK_LEN);
  35. /*先讲表示字段"JRD_VERSION_MARK_"拷贝到数组中*/
  36. strncpy(ver, JRD_VERSION_MARK, JRD_VERSION_MARK_LEN);
  37. // read image
  38. f = open(img, O_RDONLY);
  39. if (f < 0) {
  40. printf("can not open image!!!!!!!!!!!!!!!!!!\n");
  41. return f;
  42. }
  43. /*将.img文件中的前size个字符拷贝到buff中*/
  44. s = read(f, buff, size);
  45. printf("read out byte cnt: %d!!!!!!!!!!!!!!!!!!\n", s);
  46. f = close(f);
  47. // search the mark string
  48. for(p = 0; p < size; p++) {
  49. // match the first char
  50. if (buff[p] == ver[0]) {
  51. int t;
  52. for(t = 0; t < JRD_VERSION_MARK_LEN; t++) {
  53. if(buff[p + t] != ver[t])
  54. break;
  55. }
  56. // match whole mark string
  57. if(t == JRD_VERSION_MARK_LEN)
  58. break;
  59. }
  60. }
  61. // not find
  62. if (p == size)
  63. return ret;
  64. /*p + strlen(JRD_VERSION_MARK)为版本号在buff中的偏移位*/
  65. // find it
  66. strncpy(ver, buff + p + strlen(JRD_VERSION_MARK), JRD_VERSION_LEN);
  67. ver[JRD_VERSION_LEN] = '\n';
  68. if(size == PRELOADER_SIZE)
  69. {
  70. strncpy(setver,ver, JRD_VERSION_LEN);
  71. setver[JRD_VERSION_LEN] = '\0';、
  72. /*将版本号写入系统属性ro.tct.preloader.ver中*/
  73. if(property_set("ro.tct.preloader.ver", setver) <0)
  74. {
  75. printf("can not set ro.tct.preloader.ver!!!!!!!!!!!!!!!\n");
  76. }
  77. }else if (size == BOOTLOADER_SIZE){
  78. strncpy(setver,ver, JRD_VERSION_LEN);
  79. setver[JRD_VERSION_LEN] = '\0';
  80. /*将版本号写入系统属性ro.tct.bootloader.ver中*/
  81. if(property_set("ro.tct.bootloader.ver", setver) <0)
  82. {
  83. printf("can not set ro.tct.bootloader.ver!!!!!!!!!!!!!!!\n");
  84. }
  85. }else{
  86. strncpy(setver,ver, JRD_VERSION_LEN);
  87. setver[JRD_VERSION_LEN] = '\0';
  88. /*将版本号写入系统属性ro.tct.reco.ver中*/
  89. if(property_set("ro.tct.reco.ver", setver) <0)
  90. {
  91. printf("can not set ro.tct.reco.ver!!!!!!!!!!!!!!!\n");
  92. }
  93. }
  94. return ret;
  95. }
  96. int main(int argc, char *argv[])
  97. {
  98. int ret = 0;
  99. printf("PRELOADER!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
  100. ret = getver(PRELOADER, PRELOADER_SIZE);
  101. printf("BOOTLOADER!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
  102. ret = getver(BOOTLOADER, BOOTLOADER_SIZE);
  103. printf("RECOVERYIMG!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
  104. ret = getver(RECOVERYIMG, RECOVERYIMG_SIZE);
  105. return ret;
  106. }

到现在基本改完了,然后编译系统并将编好系统刷入设备中,验证发现无法显示出所有的版本号。难到上面的分析有问题?通过log分析发现,问题出在SELinux权限的方面。那什么是SELinux呢?说简单点,就是针对某个进程,它是否具有访问某个文件资源的权限。而在上面运行vendor/jrdcom/proprietary/version/main.c代码的进程需要访问lk、preloader、recovery这个3个分区的文件资源。我们需要将该进程读这3个分区的权限放开,才能获得分区中的版本号,不然无法获取版本号的值。
需要注意的是要具体查看项目中到底使用到哪个目录下的sepolicy规则,可以通过查看BoardConfig.mk文件来确定

  1. #SELinux Policy File Configuration
  2. ifeq ($(strip $(MTK_BASIC_PACKAGE)), yes)
  3. BOARD_SEPOLICY_DIRS += \
  4. device/mediatek/mt6735/sepolicy/basic
  5. endif
  6. ifeq ($(strip $(MTK_BSP_PACKAGE)), yes)
  7. BOARD_SEPOLICY_DIRS += \
  8. device/mediatek/mt6735/sepolicy/basic \
  9. device/mediatek/mt6735/sepolicy/bsp
  10. endif
  11. ifneq ($(strip $(MTK_BASIC_PACKAGE)), yes)
  12. ifneq ($(strip $(MTK_BSP_PACKAGE)), yes)
  13. BOARD_SEPOLICY_DIRS += \
  14. device/mediatek/mt6735/sepolicy/basic \
  15. device/mediatek/mt6735/sepolicy/bsp \
  16. device/mediatek/mt6735/sepolicy/full
  17. endif
  18. endif

具体的selinux规则修改如下:

  • 在文件[sepolicy规则目录]/file.te中定义一个type,具体如下:
  1. type getver_data_file, file_type;
  • 在文件[sepolicy规则目录]/device.te中定义一个type,具体如下:
  1. type lk_block_device, dev_type;
  • 在文件[sepolicy规则目录]/file_contexts中定义定义文件的安全上下文,具体如下:
  1. #lk文件的安全上下文
  2. /dev/block/platform/mtk-msdc\.0/[0-9]+\.msdc0/by-name/lk u:object_r:lk_block_device:s0
  3. #getver文件的安全上下文
  4. /system/bin/getver u:object_r:getver_exec:s0
  5. /data/imgver u:object_r:getver_data_file:s0
  • 在文件[sepolicy规则目录]下创建一个getver.te文件,具体如下:
  1. # getver
  2. #为getver应用定义域
  3. type getver, domain;
  4. #定义一个getver_exec类型的type
  5. type getver_exec, exec_type, file_type;
  6. init_daemon_domain(getver)
  7. # Give getver a place where only getver can store files; everyone else is off limits
  8. file_type_auto_trans(getver, system_data_file, getver_data_file)
  9. #定义getver应用对文件的访问规则
  10. allow getver getver_data_file:file {create rw_file_perms};
  11. allow getver block_device:file {open rw_file_perms};
  12. allow getver block_device:dir {search rw_file_perms};
  13. allow getver nvram_data_file:file {open rw_file_perms};
  14. allow getver nvram_data_file:lnk_file {r_file_perms};
  15. allow getver nvram_data_file:dir {search r_file_perms};
  16. allow getver nvdata_file:dir {search rw_file_perms};
  17. allow getver userdata_block_device:blk_file {rw_file_perms};
  18. allow getver preloader_block_device:blk_file {r_file_perms};
  19. allow getver recovery_block_device:blk_file {r_file_perms};
  20. allow getver lk_block_device:blk_file {open r_file_perms};
  21. #allow getver system_data_file:dir {create search rw_file_perms add_name};
  22. allow getver getver_data_file:file {create open read write};
  23. #added by xuanfeng.ye for Task1304435 begin
  24. allow getver system_file:file {execute_no_trans};
  25. #added by xuanfeng.ye for Task1304435 end
  26. #20170204-jrdhz-yaosen.lin-add-for-t3695219-to-show version-brand
  27. allow getver property_socket:sock_file { write };
  28. allow getver system_prop:property_service { set };
  29. allow getver init:unix_stream_socket { connectto };

经过上面的修改,运行vendor/jrdcom/proprietary/version/main.c代码的进程就可以读取img文件中的版本号,并且将版本号写入对应的系统属性中了。而三方dialog apk就可以通过读取对应的系统属性来在界面上显示各个版本号了,对于系统属性读写需不需要加selinux访问规则就要看项目具体的配置了,如果项目对系统属性也使用了selinux,则要对系统属性加上对应selinux规则才能正确显示版本号。

4. Summary

这做这个defect的时候,需要区分软件升级前后要改变的版本号和不需要改变的版本号,对应升级前后一直不变的版本号,只需要将它们读出来,静态的写入到文件中,在需要使用的时候再拿出来就可以了。而对应升级前后需要改变的版本号,可以通过中间文件的形式来处理即在每次开机的时候通过读取中间文件来获取版本号,同时在软件升级时候同时更新中间文件来确保版本号也一起更新过,而在做这个问题的时候,遇到的难点则是selinux部分,因为对selinux的知识也不是很熟悉,写的selinux规则也是照猫画虎将别人的搬过来,而没有实际理解它们的含义,所以导致编译N次都没有成功的读取出img部分的版本号。整个问题的解决思路还是比较简单的,特别需要的就是各个地方的细节,比如最开始写的selinux规则,不管怎么写都没有效果,最后发现原来是使用的那个sepolicy目录根据就没有编译进系统。