[转]在static代码块或static变量的初始化过程中使用ServiceManager提供的api的陷阱
一. 案例
1.源码:
/** @hide */
private TelephonyManager(int slotId) {
mContext = null;
mSlotId = slotId;
if (sRegistry == null) {
if (sRegistry == null) {
sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
}
}
}
private static TelephonyManager[] sInstance = new TelephonyManager[MultiSimManager.MAX_SLOT_COUNT + 1];
static {
for (int i = 0; i < sInstance.length; ++i) {
sInstance[i] = new TelephonyManager(i);
}
}
这段代码很简单,就是在static代码块中new TelephonyManager对象,而在TelephonyManager的构造函数中调用ServiceManager的getService方法来获取telephony.registry服务的代理对象.
2.影响:
这段看似很简单的代码曾导致miui系统无法正常启动,连续两天无法出包,导致数以十计的工程师因此无法正常工作,花费系统组工程师大量时间来排查原因.
二. 案例分析
1.根据抓取log初步排查:
11-06 12:36:29.519 1150 1150 E JavaBinder: !!! FAILED BINDER TRANSACTION !!!
11-06 12:36:29.519 1150 1150 E ServiceManager: error in addService
11-06 12:36:29.519 1150 1150 E ServiceManager: android.os.TransactionTooLargeException
11-06 12:36:29.519 1150 1150 E ServiceManager: at android.os.BinderProxy.transact(Native Method)
11-06 12:36:29.519 1150 1150 E ServiceManager: at android.os.ServiceManagerProxy.addService(ServiceManagerNative.java:150)
11-06 12:36:29.519 1150 1150 E ServiceManager: at android.os.ServiceManager.addService(ServiceManager.java:72)
11-06 12:36:29.519 1150 1150 E ServiceManager: at com.android.server.ServerThread.initAndLoop(SystemServer.java:215)
11-06 12:36:29.519 1150 1150 E ServiceManager: at com.android.server.SystemServer.main(SystemServer.java:1285)
11-06 12:36:29.519 1150 1150 E ServiceManager: at java.lang.reflect.Method.invokeNative(Native Method)
11-06 12:36:29.519 1150 1150 E ServiceManager: at java.lang.reflect.Method.invoke(Method.java:515)
11-06 12:36:29.519 1150 1150 E ServiceManager: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:807)
11-06 12:36:29.519 1150 1150 E ServiceManager: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:623)
11-06 12:36:29.519 1150 1150 E ServiceManager: at dalvik.system.NativeStart.main(Native Method)
查 看相关堆栈信息初步这是system_server进程在add power服务的时候失败. 经过查看相关change, 在无法打包的时间点没有任何工程师进过有关Power和SystemServer相关的改动,故怀疑在add power服务之前就应该有服务died.
在 system_server进程中add的第一个服务是sensorservice, 通过打印log发现,在add sensorservice服务的时候就已经出现了错误,但是查看sensorservice相关的实现,同样没有任何相关的改动. 再次怀疑可能是别的模块相关改动
导致sensorservice无法正常启动,通过将正常包/system/framework中相关的 jar包逐个push到无法启动的机器发现:framework.jar push进去之后,sensorservice能够正常启动. 故,肯定是framework相关改动导致. 通过查看changes
发现正是包含上述案例中源码的change导致. 因为此时telephony.registry服务尚未注册到servicemanager中去,就开始通过ServiceManager的getService接口来获取telephony.registry的代理对象.
2. 问题:为什么上述案例中的代码会导致sensorservice无法正常add到servicemanager中呢?
A.查看servicemanager的log
打开servicemanager中相关log,发现add service的binder请求根本就没有发送到servicemanager中.
那么可以肯定一点:system_server — add sensorservice request --> servicemanager 这个过程中出了问题.
B. 查看内核相关的log:
<5>[ 22.727416] C3 [ system_server, 1150] type=1400 audit(1415277389.349:10): avc: denied { transfer } for pid=1150 comm="system_server" scontext=u:r:zygote:s0 tcontext=u:r:servicemanager:s0 tclass=binder
通过查看kmsg可知,这是selinux导致的问题,于是我关闭selinux开关发现sensorservice能够正确的add到servicemanager中去,那么可以肯定一点,确实是selinux导致的问题.
C. 寻找binder通讯过程中与selinux权限的验证接口:
源码:kernel/driver/staging/android/binder.c:
static void binder_transaction(struct binder_proc *proc,
struct binder_thread *thread,
struct binder_transaction_data *tr, int reply)
{
......
// 请注意这里的proc->tsk变量,在后面我们会介绍其是如何进行初始化操作的.
if (security_binder_transfer_binder(proc->tsk, target_proc->tsk)) {
return_error = BR_FAILED_REPLY;
goto err_binder_get_ref_for_node_failed;
}
......
}
正是在security_binder_transfer_binder这个函数无法通过selinux验证导致binder通讯失败而无法add 到servicemanager中去导致.
3. 进一步分析:
A. 冰山一角:
system_server ---binder(add sensorservice) --> servicemanager
正是在这个通讯过程中由于selinux验证无法通过导致sensorservice无法注册到servicemanager中去,通过kmsg可知system_server的scontext和tcontext分别是:
scontext: scontext=u:r:zygote:s0
tcontext: tcontext=u:r:servicemanager:s0 // system_server的tcontext同时也是servicemanager的scontext
通过ps -Z命令查看正常启动机器,system_server的scontext应该是: u:r:system:s0, 但在这里却是zygote进程的scontext, 好端端的system_server进程的scontext 怎么TMD就变成了zygote进程的
scontext ?而查看对应zygote进程的sepolicy配置文件发现其并没有与servicemanager进程进行binder通讯的权限,
现在可以肯定的是正是案例中的代码造成了现在这种局面。接下来我们反过来分析下案例中代码的执行时机来发现.
B. 穿越冰山一角:
案 例中的代码是在静态块中new 出多个TelephonyManager实例,在TelephonyManager的构造函数中通过ServiceManager的getService 接口来获取telephony.registry服务的代理对象,在Android中,
zygote进程在启动的时候有个preload的操作,在preload的执行过程会调用如下接口来预加载指定名称的类:
public static Class<?> forName(String className) throws ClassNotFoundException {
return forName(className, true, VMStack.getCallingClassLoader());
}
Class的forName函数的第二个参数设置为true代表在预加载指定类之后会需要初始化对应的static模块和变量,而此时还在是在zygote进程中执行.
而在初始话静态变量的过程中如下调用过程:
sRegistry = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
这 个调用过程首先会通过调用IServiceManager::defaultServiceManager函数通过 ProcessState::self()->getContextObject()来获取servicemanager的代理对象,然后通过 servicemanager的代理对象来获取telephony.registry服务的代理对象,
而在new ProcessState的过程中会执行如下代码:
sp<ProcessState> ProcessState::self() // ProcessState是设计模式中的单例模式,每个进程只能拥有其一个实例
{
Mutex::Autolock _l(gProcessMutex);
if (gProcess != NULL) {
return gProcess;
}
gProcess = new ProcessState;
return gProcess;
}
......
static int open_driver()
{
int fd = open("/dev/binder", O_RDWR);
......
return fd;
}
ProcessState::ProcessState()
: mDriverFD(open_driver())
, mVMStart(MAP_FAILED)
, mManagesContexts(false)
, mBinderContextCheckFunc(NULL)
, mBinderContextUserData(NULL)
, mThreadPoolStarted(false)
, mThreadPoolSeq(1)
{
......
}
在new一个ProcessState的过程中会调用open系统调用来打开binder文件,这最终会陷入内核中执行如下代码:
static int binder_open(struct inode *nodp, struct file *filp)
{
struct binder_proc *proc;
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
if (proc == NULL)
return -ENOMEM;
get_task_struct(current);
// 在这里将zygote进程的进程描述符保存到了binder_proc结构体的成员变量tsk中去.
proc->tsk = current;
......
// 又将proc结构保存到/dev/binder文件的结构struct file中的private_data成员变量中去.
filp->private_data = proc;
......
}
ProcessState 是一个单例,也就是每个进程只能拥有ProcessState的一个实例,即:只要多个进程打开的/dev/binder的文件句柄值fd是一样的,代表 最终指向的binder_proc也是同一个, 即proc的成员变量tsk指向的也是同一个进程描述符.
C. 探明冰山下的靠山:
system_server 进程是调用IServiceManager::defaultService()函数中的 ProcessState()::self()->getContextObject()来获取servicemanager的代理对象,然后调用 addService来注册sensorservice到servicemanager中去的.
而案例中的代码同样是在zygote进程中 调用IServiceManager::defaultService()函数中的 ProcessState()::self()->getContextObject()来获取servicemanager的代理对象,然后调用 getService来获取telephony.registry服务的.
system_server进程是由zygote进程 fork()出来的,那么system_server进程和zygote进程将拥有完全相同的ProcessState()实例,完全相同的/dev /binder文件句柄值,完全相同的binder_proc变量,最终binder_proc指向的tsk也是同一个进程 --> zygote,
但是, fork系统调用在zygote进程中fork 出system_server进程的时候,两个进程对应selinux的scontext却是完全不一样的,最终就造成了system_server进程 向servicemanager中注册sensorservice的时候取出的binder_proc中的tsk是zygote进程,
在binder通讯的过程中就使用了zygote进程的scontext来进行selinux校验,最终导致了校验失败. 造成系统无法正常启动.
补充:在调查的过程中发现mediaserver进程在启动的过程中也会调用IServiceManager::defaultService()函数中的 ProcessState()::self()->getContextObject()然后调用addService来注册 listen.service到servicemanager中去,但是其是能够成功的,
这是因为mediaserver进程和zygote进程同属于init进程的子进程,其和zygote进程没有这种fork()关系. 不会共享ProcessState实例中的fd.
三. 如何避免这种问题
1.不要在static块或变量初始化过程中调用ServiceManager提供的API(addService, getService, checkService, listServices)
2. 在调用getService和checkService的时候一定要注意调用时机,确保对应的Server端已经注册到servicemanager中去,否则获取到的代理对象也是null, 调用没有任何意义.
[转]在static代码块或static变量的初始化过程中使用ServiceManager提供的api的陷阱的更多相关文章
- 牛客网Java刷题知识点之关键字static、static成员变量、static成员方法、static代码块和static内部类
不多说,直接上干货! 牛客网Java刷题知识点之关键字static static代表着什么 在Java中并不存在全局变量的概念,但是我们可以通过static来实现一个“伪全局”的概念,在Java中st ...
- 类加载器在加载类 的时候就已经对类的static代码块和static变量进行了初始化
类装载器ClassLoader 类装载器工作机制 类装载器就是寻找类的节码文件并构造出类在JVM内部表示对象的组件.在Java中,类装载器把一个类装入JVM中,要经过以下步骤: [1.]装载:查找和导 ...
- static代码块与{}代码块的比较
第一个例子: public class StaticDemo { { System.out.println("{} 代码块"); } static{ System.out.prin ...
- Java单例模式的各种实现(饿汉、懒汉、静态内部类、static代码块、enum枚举类型)
饿汉模式 饿汉模式就是立即加载,在方法调用前,实例就已经被创建了,所以是线程安全的. public class MyObject1 { private static MyObject1 myObjec ...
- class 中的 构造方法、static代码块、私有/公有/静态/实例属性、继承 ( extends、constructor、super()、static、super.prop、#prop、get、set )
part 1 /** * << class 中的 static 代码块与 super.prop 的使用 * * - ...
- final与 static的区别;static代码块以及嵌套类介绍
本篇文章主要分为两个模块进行介绍:1.final,staic,static final之间的异同:2. static 模块:3.嵌套类的概念 1.final,staic,static final之间的 ...
- 0507 构造代码块和static案例,接口interface
0507构造代码块和static案例,接口interface [重点] 1.局部变量,成员变量,静态变量的特点 2.接口 接口语法:interface A {} 接口内的成员变量[缺省属性]publi ...
- java中的static代码块为什么只执行一次
原因在最后,这是其中的一个小例子. 如: SessionFactory负责保存和使用所有配置信息,消耗内存资源非常大 所以一个web项目要保证只创建一个SessionFactory 那么在使用hibe ...
- Java中static代码块,{}大括号代码块,构造方法代码块执行顺序!
注:下列代码中的注释都是JUnit4单元测试运行结果. 首先,没有父类的(父类是Object)的类A package Static.of; public class A { { System.out. ...
随机推荐
- 一) Spring 介绍、IOC控制反转思想与DI依赖注入
一.spring介绍1.IOC反转控制思想(Inversion of Control)与DI依赖注入(Dependency Injection)2.AOP面向切面的编程思想与动态代理3.作用:项目的粘 ...
- [kuangbin带你飞]专题二十二 区间DP-E-POJ - 1651
区间DP模板题 做区间DP的题目的时候,我们考虑DP[i][j]的含义是什么? 由题意大概是这样的,我们可以从n个数中每次选一个我们以前没选过的数字拿走,需要消耗a[i]*a[i+1]*a[i-1]的 ...
- Springcloud zuul和shiro结合
一.目标1.外部请求统一从网关zuul进入,并且服务内部互相调用接口要校验权限 2.cloud和shiro结合,达到单点登录,和集中一个服务完成权限管理,其他业务服务不需要关注权限如何实现 3.其他服 ...
- Docker 核心技术之数据管理
Docker 数据卷简介 为什么用数据卷 宿主机无法直接访问容器中的文件 容器中的文件没有持久化,导致容器删除后,文件数据也随之消失 容器之间也无法直接访问互相的文件 为解决这些问题,docker加入 ...
- 基于 docker 的redis 主从+哨兵(快速部署)
很简单(字多的步骤见:http://www.cnblogs.com/vipzhou/p/8580495.html) 1.直接启动3个容器 docker network create --subnet ...
- Linux如何管理目录和文件属性
概述:在Linux文件系统的安全模型中,为系统中的文件(或目录)赋予了两个属性:访问权限和文件所有者,简称为“权限”和“归属”.其中,访问权限包括读取.写入.可执行三种基本类型,归属包括属主(拥有该文 ...
- web开发中各种宽高
Gosper 曲线:https://www.cnblogs.com/tgzhu/p/8286616.html
- mysql查看死锁和解除锁
解除正在死锁的状态有两种方法: 第一种: 1.查询是否锁表 show OPEN TABLES where In_use > 0; 2.查询进程(如果您有SUPER权限,您可以看到所有线程.否则, ...
- Oracle表之间关联更新
经常会遇到一个表需要根据另一个表数据来更新数据,总结了核心的sql脚本命令如下: A表如下x y--------------ka dakb dbkc ...
- bzoj 1926: [Sdoi2010]粟粟的书架 (主席树+二分)
链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1926 题面; 1926: [Sdoi2010]粟粟的书架 Time Limit: 30 Se ...