触屏不可用的问题分析报告
1关键字 触屏;caps;mmi子系统 2 问题描述 开发板型号:Firefly ROC-RK3568-PC 内核版本: Firefly 4.19 问题现象:firefly3568的板子适配3.1release分支的OH,重启后,触摸屏触屏无效 测试步骤: 1.修改挂载路径和分区表,重新编译代码,获得vendor.img,sys term.img,userdata.img和parameter.t
1关键字
触屏;caps;mmi子系统
2 问题描述
开发板型号:Firefly ROC-RK3568-PC
内核版本: Firefly 4.19
问题现象:firefly3568的板子适配3.1release分支的OH,重启后,触摸屏触屏无效
测试步骤:
1.修改挂载路径和分区表,重新编译代码,获得vendor.img,sys term.img,userdata.img和parameter.txt
2.将上述获取文件烧进firefly3568
3.按下开发板的reboot按钮
4.触摸屏正常亮屏后点击任意应用
5.期待结果是进入点击的应用界面,但是无响应
3 问题原因
3.1 正常机制
mmi子系统启动前,会对mmi子系统的配置文件进行解析,解析获取的数据会在服务启动时进行属性检测,检测通过就会正常启动
3.2 异常机制
解析mmi配置文件时,获取了错误的caps值,服务启动检验caps值不通过,服务重启四次后终止启动
4 解决方案
配置文件路径为:\foundation\multimodalinput\input\multimodalinput.cfg
将错误的配置字段 "caps" : ["CAP_DAC_READ_SEARCH", "CAP_DC_OVERRIDE", "CAP_CHOWN"]
修改为正确配置如下:
"caps" : ["DAC_READ_SEARCH", "DAC_OVERRIDE", "CHOWN"]
正确的配置参考下面函数定义的字符串
static unsigned int GetCapByString(const char *capStr)
{
static const CapStrCapNum capStrCapNum[] = {
{ "CHOWN", CAP_CHOWN },
{ "DAC_OVERRIDE", CAP_DAC_OVERRIDE },
{ "DAC_READ_SEARCH", CAP_DAC_READ_SEARCH },
{ "FOWNER", CAP_FOWNER },
{ "FSETID", CAP_FSETID },
{ "KILL", CAP_KILL },
{ "SETGID", CAP_SETGID },
{ "SETUID", CAP_SETUID },
{ "SETPCAP", CAP_SETPCAP },
{ "LINUX_IMMUTABLE", CAP_LINUX_IMMUTABLE },
{ "NET_BIND_SERVICE", CAP_NET_BIND_SERVICE },
{ "NET_BROADCAST", CAP_NET_BROADCAST },
{ "NET_ADMIN", CAP_NET_ADMIN },
{ "NET_RAW", CAP_NET_RAW },
{ "IPC_LOCK", CAP_IPC_LOCK },
{ "IPC_OWNER", CAP_IPC_OWNER },
{ "SYS_MODULE", CAP_SYS_MODULE },
{ "SYS_RAWIO", CAP_SYS_RAWIO },
{ "SYS_CHROOT", CAP_SYS_CHROOT },
{ "SYS_PTRACE", CAP_SYS_PTRACE },
{ "SYS_PACCT", CAP_SYS_PACCT },
{ "SYS_ADMIN", CAP_SYS_ADMIN },
{ "SYS_BOOT", CAP_SYS_BOOT },
{ "SYS_NICE", CAP_SYS_NICE },
{ "SYS_RESOURCE", CAP_SYS_RESOURCE },
{ "SYS_TIME", CAP_SYS_TIME },
{ "SYS_TTY_CONFIG", CAP_SYS_TTY_CONFIG },
{ "MKNOD", CAP_MKNOD },
{ "LEASE", CAP_LEASE },
{ "AUDIT_WRITE", CAP_AUDIT_WRITE },
{ "AUDIT_CONTROL", CAP_AUDIT_CONTROL },
{ "SETFCAP", CAP_SETFCAP },
{ "MAC_OVERRIDE", CAP_MAC_OVERRIDE },
{ "MAC_ADMIN", CAP_MAC_ADMIN },
{ "SYSLOG", CAP_SYSLOG },
{ "WAKE_ALARM", CAP_WAKE_ALARM },
{ "BLOCK_SUSPEND", CAP_BLOCK_SUSPEND },
{ "AUDIT_READ", CAP_AUDIT_READ },
};
int mapSize = (int)ARRAY_LENGTH(capStrCapNum);
for (int j = 0; j < mapSize; j++) {
if (strcmp(capStr, capStrCapNum[j].capStr) == 0) {
return capStrCapNum[j].CapNum;
}
}
return -1;
}
5 定位过程
1.在复现后,通过与正常启动的log比较快速定位了这两条异常的log如下图所示
1.1异常log在代码中的位置如下
base\startup\init_lite\services\init\adapter\init_adapter.c
int SetAmbientCapability(int cap)
{
\#if ((defined __LINUX__) || (!defined OHOS_LITE))
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0)) {
INIT_LOGE("prctl PR_CAP_AMBIENT failed: %d", errno);
return -1;
}
\#endif
return 0;
}
1.2这个函数的Ambient特权通过prctl(PR_CAP_AMBIENT…)系统调用操作
base\startup\init_lite\services\init\init_common_service.c
int ServiceStart(Service *service)
{
.........................................................
INIT_CHECK_ONLY_ELOG(BindCpuCore(service) == SERVICE_SUCCESS,
"binding core number failed for service %s", service->name);
// permissions
INIT_ERROR_CHECK(SetPerms(service) == SERVICE_SUCCESS, _exit(PROCESS_EXIT_CODE),
"service %s exit! set perms failed! err %d.", service->name, errno);
........................................................
}
1.3服务启动时在这里进行了一系列的属性检测,属性检测不通过就会造成服务启动失败
2进入SetPerms函数发现里面有调用SetAmbientCapability,再通过SetAmbientCapability函数带入的参数service->servPerm.caps[i]进行搜索
static int SetPerms(const Service *service)
{
...................................
for (unsigned int i = 0; i < service->servPerm.capsCnt; ++i) {
if (service->servPerm.caps[i] == FULL_CAP) {
for (int j = 0; j < CAP_NUM; ++j) {
capData[j].effective = FULL_CAP;//进程当前可用的能力集,可以看做是cap_permitted的一个子集
capData[j].permitted = FULL_CAP;//进程所拥有的最大能力集
capData[j].inheritable = FULL_CAP;//进程可以传递给其子进程的能力集
break;
}
capData[CAP_TO_INDEX(service->servPerm.caps[i])].effective |= CAP_TO_MASK(service->servPerm.caps[i]);
capData[CAP_TO_INDEX(service->servPerm.caps[i])].permitted |= CAP_TO_MASK(service->servPerm.caps[i]);
capData[CAP_TO_INDEX(service->servPerm.caps[i])].inheritable |= CAP_TO_MASK(service->servPerm.caps[i]);
}
if (capset(&capHeader, capData) != 0) {
INIT_LOGE("capset faild for service: %s, error: %d", service->name, errno);
return SERVICE_FAILURE;
}
for (unsigned int i = 0; i < service->servPerm.capsCnt; ++i) {
if (service->servPerm.caps[i] == FULL_CAP) {
return SetAllAmbientCapability();
INIT_LOGE("SetAllAmbientCapability: %s failed, uid = %d", service->name, service->servPerm.uID);//
}
if (SetAmbientCapability(service->servPerm.caps[i]) != 0) {
INIT_LOGE("SetAmbientCapability faild for service: %s", service->name);
return SERVICE_FAILURE;
}
}
return SERVICE_SUCCESS;
}
3.通过搜索service->servPerm.caps[i],发现在GetServiceCaps中有被调用,通过这个函数可以看出是caps获取的方式有两种,一种是string一种是number,分别在caps = (unsigned int)cJSON_GetNumberValue(capJson);和caps = GetCapByString(capStr);后面添加了log如下所示
base\startup\init_lite\services\init\init_capability.c
int GetServiceCaps(const cJSON *curArrItem, Service *service)
{
INIT_ERROR_CHECK(service != NULL, return SERVICE_FAILURE, "service is null ptr.");
INIT_ERROR_CHECK(curArrItem != NULL, return SERVICE_FAILURE, "json is null ptr.");
service->servPerm.capsCnt = 0;
service->servPerm.caps = NULL;
int capsCnt = 0;
cJSON *filedJ = GetArrayItem(curArrItem, &capsCnt, "caps");
if (filedJ == NULL) {
return SERVICE_SUCCESS;
}
INIT_ERROR_CHECK(capsCnt <= MAX_CAPS_CNT_FOR_ONE_SERVICE, return SERVICE_FAILURE,
"service=%s, too many caps[cnt %d] for one service", service->name, capsCnt);
service->servPerm.caps = (unsigned int *)calloc(1, sizeof(unsigned int) * capsCnt);
INIT_ERROR_CHECK(service->servPerm.caps != NULL, return SERVICE_FAILURE,
"Failed to malloc for service %s", service->name);
service->servPerm.capsCnt = capsCnt;
unsigned int caps = FULL_CAP;
for (int i = 0; i < capsCnt; ++i) { // number form
cJSON *capJson = cJSON_GetArrayItem(filedJ, i);
if (cJSON_IsNumber(capJson)) { // for number
caps = (unsigned int)cJSON_GetNumberValue(capJson);
INIT_LOGI("*****************service0=%s, caps = %d.*********", service->name, caps);
} else if (cJSON_IsString(capJson)) {
char *capStr = cJSON_GetStringValue(capJson);
if (capStr == NULL || strlen(capStr) <= 0) { // check all errors
INIT_LOGE("service=%s, parse item[%d] as string, error.", service->name, i);
break;
}
caps = GetCapByString(capStr);
INIT_LOGI("*****************service=%s, caps = %d.*********", service->name, caps);
}
INIT_CHECK_RETURN_VALUE(caps >= 0, SERVICE_FAILURE);
if ((caps > CAP_LAST_CAP) && (caps != (unsigned int)FULL_CAP)) {
INIT_LOGE("service=%s, caps = %d, error.", service->name, caps);
return SERVICE_FAILURE;
}
service->servPerm.caps[i] = (unsigned int)caps;
}
return 0;
}
4.编译代码烧入开发板截取log显示如下,可以看出是通过GetCapByString(capStr)方法获取的,也可以根据GetCapByString函数看出获取的caps值异常
5.再搜索GetServiceCaps函数,最终发现是在解析cfg文件的时候会调用到GetServiceCaps,那么我们查看mmi的cfg文件,通过比较GetCapByString函数里定义的字符串和caps配置的字符串,发现caps配置错误,解析该文件时获取了错误的caps值,mmi服务启动时调用setperms,在该函数里会将获取的caps值传入SetAmbientCapability函数里,之后被prctl(PR_CAP_AMBIENT…)系统调用失败,返回错误值,使服务启动失败,造成触屏无效
{
"jobs" : [{
"name" : "post-fs",
"cmds" : [
"start udevd_service",
"sleep 1",
"start mmi_uinput_service",
"exec /system/bin/udevadm trigger",
"sleep 2",
"start multimodalinput"
]
}
],
"services" : [{
"name" : "multimodalinput",
"path" : ["/system/bin/sa_main", "/system/profile/multimodalinput.xml"],
"uid" : "system",
"gid" : ["system", "shell", "uhid"],
"caps" : ["CAP_DAC_READ_SEARCH", "CAP_DC_OVERRIDE", "CAP_CHOWN"],#当前服务所需的capability值,根据安全子系统已支持的capability,评估所需的capability,遵循最小权限原则配置。
"start-mode" : "condition"
}, {
"name" : "udevd_service",
"path" : ["/system/bin/udevd"],
"uid" : "root",
"gid" : ["system"],
"start-mode" : "condition"
}, {
"name" : "mmi_uinput_service",
"path" : ["/system/bin/uinput_inject"],
"uid" : "uhid",
"gid" : ["system", "shell", "uhid"],
"caps" : ["CAP_DAC_READ_SEARCH", "CAP_DC_OVERRIDE", "CAP_CHOWN"],
"start-mode" : "condition"
}
]
}
6 知识分享
-
Capabilities
机制是在 Linux 内核2.2
之后引入的,原理是将之前与超级用户 root(UID=0)关联的特权细分为不同的功能组,Capabilites 作为线程(Linux 并不真正区分进程和线程)的属性存在,每个功能组都可以独立启用和禁用,其本质上就是将内核调用分门别类,具有相似功能的内核调用被分到同一组中。可参考http://3ms.huawei.com/km/blogs/details/8522121 -
Ambient Capability是一种特权继承的方法,是在kernel 4.3才引入,老版本内核不支持,使用方式简洁,动态配置,不依赖文件系统xattr。可参考http://3ms.huawei.com/km/blogs/details/6041907
更多推荐
所有评论(0)