Android图形系统系统篇之HWC
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android图形系统系统篇之HWC相关的知识,希望对你有一定的参考价值。
参考技术A HWC (hwcomposer)是Android中进行窗口( Layer )合成和显示的HAL层模块,其实现是特定于设备的,而且通常由显示设备制造商 (OEM)完成,为 SurfaceFlinger 服务提供硬件支持。SurfaceFlinger 可以使用 OpenGL ES 合成 Layer ,这需要占用并消耗GPU资源。大多数GPU都没有针对图层合成进行优化,当 SurfaceFlinger 通过GPU合成图层时,应用程序无法使用GPU进行自己的渲染。而 HWC 通过硬件设备进行图层合成,可以减轻GPU的合成压力。
显示设备的能力千差万别,很难直接用API表示硬件设备支持合成的 Layer 数量, Layer 是否可以进行旋转和混合模式操作,以及对图层定位和硬件合成的限制等。因此HWC描述上述信息的流程是这样的:
虽然每个显示设备的能力不同,但是官方要求每个 HWC 硬件模块都应该支持以下能力:
但是并非所有情况下 HWC 都比GPU更高效,例如:当屏幕上没有任何变化时,尤其是叠加层有透明像素并且需要进行图层透明像素混合时。在这种情况下, HWC 可以要求部分或者全部叠加层都进行GPU合成,然后 HWC 持有合成的结果Buffer,如果 SurfaceFlinger 要求合成相同的叠加图层列表, HWC 可以直接显示之前合成的结果Buffer,这有助于提高待机设备的电池寿命。
HWC 也提供了 VSync 事件,用于管理渲染和图层合成时机,后续文章会进行介绍。
Android7.0提供了HWC和HWC2两个版本,默认使用HWC,但是手机厂商也可以选择HWC2,如下所示:
SurfaceFlinger 通过 HWComposer 使用 HWC 硬件能力, HWComposer 构造函数通过 loadHwcModule 方法加载HWC模块,并封装成 HWC2::Device 结构,如下所示:
上述通过 hw_get_module 方法(hardware\libhardware\hardware.c)加载 hwcomposer 模块,此模块由硬件厂商提供实现,例如:hardware\libhardware\modules\hwcomposer\hwcomposer.cpp是 hwcomposer 模块基于HWC1的default实现,对应的共享库是 hwcomposer.default.so ;hardware\qcom\display\msm8994\libhwcomposer\hwc.cpp是高通MSM8994基于HWC1的实现,对应的共享库是 hwcomposer.msm8994.so 。
如果是基于HWC2协议实现,则需要实现hwcomposer2.h中定义的 hwc2_device_t 接口,例如: class VendorComposer : public hwc2_device_t 。Android7.0的 hwcomposer 模块默认都是基于HWC1协议实现的。
每个HAL层模块实现都要定义一个 HAL_MODULE_INFO_SYM 数据结构,并且该结构的第一个字段必须是 hw_module_t ,下面是高通MSM8994 hwcomposer 模块的定义:
frameworks\native\services\surfaceflinger\DisplayHardware\HWC2.h主要定义了以下三个结构体:
它们是对 HWC 硬件模块的进一步封装,方便进行调用。 HWC2::Device 持有一个 hwc2_device_t ,用于连接硬件设备,它包含了很多HWC2_PFN开头的函数指针变量,这些函数指针定义在 hwcomposer2.h 。
在 HWC2::Device 的构造函数中,会通过 Device::loadFunctionPointers -> loadFunctionPointer(FunctionDescriptor desc, PFN& outPFN) -> hwc2_device_t::getFunction 的调用链从硬件设备中获取具体的函数指针实现。关键模板函数如下所示:
这些函数指针主要分为三类:
通过上述函数指针可以与 hwc2_device_t 表示的硬件合成模块进行交互。三类指针分别选取了一个示例:
可以通过类图,直观感受下引用关系。
HWC2::Device 构造函数除了完成获取函数指针实现以外,还会通过 Device::registerCallbacks 向硬件设备注册三个 Display 的回调:热插拔,刷新和VSync信号,如下所示:
总结一下, HWC2::Device 构造函数向硬件设备注册三个 Display 回调:热插拔,刷新和VSync信号。当 HWC2::Device 收到这些回调时,会通过监听器向外回调到对应的HWComposer函数: HWComposer::hotplug / HWComposer::invalidate / HWComposer::vsync 。HWComposer再通过这些信息驱动对应工作,后续文章进行介绍。
上文提到 HWC2::Device 中的函数指针是hardware\libhardware\include\hardware\hwcomposer2.h中定义的,除此之外,该头文件还定义了一些重要的结构体,这里介绍两个比较重要的:
DisplayType 表示显示屏类型,上面注释已经介绍,重点看下Layer合成类型:
那么一个 Layer 的合成方式是怎么确定的那?大致流程如下所示:
本篇文章只是简单介绍了HWC模块的相关类: HWComposer 、 HWC2::Device 、 HWC2::Display 和 HWC2::Layer ,以及它们的关系。此外,还着重介绍了Layer的合成方式和合成流程。后续文章会更加全面的介绍 SurfaceFlinger 是如何通过HWC模块完成Layer合成和上屏的(虚拟屏幕是到离屏缓冲区)。
Android系统篇之----免root实现Hook系统服务拦截方法
技术概念来源:[ 360开源插件框架,项目地址:https://github.com/DroidPluginTeam/DroidPlugin ]
一、Binder机制回顾
在之前一篇文章中介绍了 Android中的Binder机制和系统远程服务调用机制,本文将继续借助上一篇的内容来实现Hook系统服务拦截指定方法的逻辑,了解了上一篇文章之后,知道系统的服务其实都是一个远程Binder对象,而这个对象都是由ServiceManager大管家管理的,用户在使用系统服务的时候,会通过指定服务的Stub方法的asInterface把远程的Binder对象转化成本地化对象即可使用,而在这个过程中,我们也知道因为系统服务是在system_server进程中的,所以这个系统服务使用过程中属于跨进程调用,那么返回的对象其实就是Proxy代理对象。
二、系统中服务使用流程
本文主要就是借助这个知识点,通过Hook系统的服务来拦截服务方法,下面我们就通过系统剪切板服务案例作为分析
这里看到了,使用系统服务的时候都是用了getSystemService方法,通过定义在Context中的服务描述符常量来获取服务对象,而getSystemService方法定义在ComtextImpl.java类中:
这里维护了一个ServiceFetcher的Map结构,看看这个结构在哪里填充数据的:
在registerService方法中添加一个服务名称和一个ServiceFetcher对象,而这个方法在静态代码块中进行调用的:
我们找到了ClipboardManager这个服务:
这里其实是一个ClipboardManager对象,其实这个对象是内部封装了IClipboard.Stub功能,可以看看其他的服务:
比如这里的联网服务,直接调用了IConnectivityManager.Stub类的asInterface方法获取Proxy对象。
下面就进去ClipboardManager.java中看看究竟:
看到这里的设置剪切板内容的方法,其实内部是调用了getService方法获取对象然后在调用指定方法,那么可以大概知道了这个getService方法返回的应该就是IClipboard.Stub通过asInterface方法返回的Proxy对象:
好吧,果然是这样,这里通过ServiceManager获取到Clipboard的远端IBinder对象,然后通过asInterface方法返回一个Proxy对象即可。
到这里我们就简单的分析完了系统中的获取剪切板的服务,其实系统中的服务都是这么个逻辑,只是有的可能会在外面包装一层罢了,下面总结一下流程:
现在只要记住一点:每次获取系统服务的流程都是一样的,先通过ServiceManager的getService方法获取远端服务的IBinder对象,然后在通过指定服务的Stub类的asInterface方法转化成本地可使用对象,而这个对象其实就是一个Proxy对象,在这个过程中,Stub类继承了Binder对象和实现了AIDL接口类型,Proxy对象实现了AIDL接口类型,而AIDL接口类型实现了IInterface接口类型。
三、Hook系统服务
上面分析完了Android中系统服务的使用流程以及原理解析,下面在来看一下Android中实现Hook机制的方法和原理解析,我们知道其实在很多系统中都存在这样一个Hook技术,有的也叫作钩子,但是不管任何系统,Hook技术的核心点都是一样的,只有两点即可完成Hook技术:
1、找到Hook点,即你想Hook哪个对象,那么得先找到这个对象定义的地方,然后使用反射获取到这个对象实例。所以这里可以看到,一般Hook点都是一个类的单例方法或者是静态变量,因为这样的话Hook起来就非常方便,都是static类型,反射调用都比较方便无需具体的实例对象即可。而关于这个点也是整个Hook过程中最难的点,因为很难找到这个点。Android中主要是依靠分析系统源码类来做到的。
2、构造一个Hook原始对象的代理类,关于这个代理其实在Java中有两种方式,一种是静态代理,一种是动态代理。
静态代理:代理类中维护一个原始对象的成员变量,每个方法调用之前调用原始对象的方法即可。无需任何条件限制
动态代理:比静态代理复杂点就是有一个规则:就是原始对象必须要实现接口才可以操作,原理是因为动态代理其实是自动生成一个代理类的字节码,类名一般都是Proxy$0啥的,这个类会自动实现原始类实现的接口方法,然后在使用反射机制调用接口中的所有方法。
案例使用就很简单了,下面我们通过源码分析这个原理:
主要调用了getProxyClass0方法生成代理类的字节码:
然后生成代理类:
看到这里,其实这里的生成字节码不是很复杂的,我们可以借助asm等工具包就可以手动的生成一个字节码,然后在使用类加载器加载即可,所以会发现需要传递类加载器。
当上面生成代理类之后,我们可以把这个类反编译看一下,看一下方法的调用,在代理类中的静态代码块:
会使用反射获取到类的所有方法。
然后在指定方法中调用传递进来的InvocationHandler的回调接口的invoke方法,就是这么实现的。
从源码角度分析完动态代理之后,发现其实没什么复杂的,就是手动的生成一个代理类,然后用反射获取类中所有的方法,在指定方法中用反射调用,同时回调InvocationHandler的invoke方法即可,所以这里看到InvocationHandler中的invoke方法的第一个参数对象是代理类对象。
注意:
在JavaWeb开发中我们会用到Spring框架中的AOP编程,其实他就是利用了Java中动态代理技术,但是他依赖的是CGlib的功能,不是采用Java原生的这种自动产生代理类,从上面可以看到能够做动态代理的类必须要实现一个接口,而这一个规则有时候要求非常高,很多类都没有实现接口,导致无法实现代理功能,所以Spring采用了cglib,原理其实差不多,他采用asm工具包也是手动生成一个类,但是这个类是原始对象的子类,这样也可以实现代理功能,但是这样一来也有一个限制了,那就是代理对象不能是final类型,同时一些主要方法最好不能是final类型的,当然这个限制和上面的那个接口实现限制比起来还是好点的。
到此我们了解了Java中的Hook技术的核心知识点了,下面就用开始的剪切板服务来做实验,我们Hook系统的剪切板服务功能,拦截其方法,上面也说道了,既然要Hook服务,首先得找到Hook点,通过开始对Android中系统服务的调用流程分析知道,其实这些服务都是一些保存在ServiceManager中的远端IBinder对象,这其实是一个Hook点:
其实ServiceManager中每次在获取服务的时候,其实是先从一个缓存池中查找,如果有就直接返回了:
而这个缓存池正好是全局的static类型,所以就可以很好的使用反射机制获取到他了,然后进行操作了。
接下来,我们就需要构造一个剪切板的服务IBinder对象了,然后在把这个对象放到上面得到的池子中即可。那么按照上面的动态代理的流程,
第一、原始对象必须实现一个接口,这里也正好符合这个规则,每个远程服务其实是实现了IBinder接口的。
第二、其次是要有原始对象,这个也可以,通过上面的缓存池即可获取
有了这两个条件那么接下来就可以使用动态代理构造一个代理类了:
通过反射去获取ServiceManager中的缓存池Binder对象就不多说了,完全反射机制即可,我们先获取到缓存池,然后得到剪切板服务Binder对象,构造一个代理类,最后在设置回去即可。下面主要来看一下构造了代理类之后,如何拦截哪些方法?
这里一定要注意了,有的同学可能想直接在这里拦截setPrimaryClip这样的剪切板方法不就可以了吗?想想是肯定不可以的,为什么呢?因为我们现在代理的是远端服务的Binder对象,他还没有转化成本地对象呢?如何会有这些方法呢,而我们真正要拦截的方法是IClipboardManager,其实就是Proxy类,而这个对象也是Stub类的asInterface方法得到的,所以我们现在的思路是有了远端服务的代理对象,拦截肯定是拦截这个代理对象Binder的一些方法,那么这个远端服务有哪些方法会在这个过程中被调用呢?我们再看看之前的一个简单AIDL的例子:
在asInterface方法中可以看到,传递进来的就是一个远端服务IBinder对象,这里会先调用它的queryLocalInerface方法获取本进程的本地化对象,那么这个方法就是拦截的目标了。
然后在想,我们如果想拦截IClipboardManager的setPrimaryClip方法,其实就是要拦截ClipboardManager$Proxy的这些方法,那么还需要做一次代理,代理ClipboardManager$Proxy类对象
第一、ClipboardManager$Proxy类实现了AIDL接口类型,符合规则。
第二、我们可以直接使用反射获取到IClipboardManager$Stub类,然后反射调用它的asInterface方法就可以得到了IClipboardManager$Proxy对象了,符合规则。
到这里,看来这个对象也符合了代理的条件,那么就简单了,继续使用动态代理机制产生一个代理类即可:
这个代理类的InvocationHandler中,先需要通过反射获取到Proxy原始对象:
最后才开始实现拦截操作。
下面来看一个实验结果:
我们调用系统的剪切板服务,但是返回的结果却是:
看到了,这里的剪切板的内容已经被之前拦截,内容也被替换了。
上面就全部介绍了如何Hook系统的剪切板服务功能,流程如下:
1、我们的目的就是拦截系统的服务功能,那么最开始的入口就是服务大管家ServiceManager对象,而在他内部也正好有一个远端服务对象的IBinder缓存池,那么这个变量就是我们操作的对象了,可以先使用反射机制去获取到他,然后在获取到指定的剪切板服务IBinder对象实例。
2、下一步肯定是Hook这个剪切板服务的Binder对象,这里采用动态代理方式产生一个Binder对象代理类,符合两个规则:
1》这个Binder对象实现了IBinder接口类型
2》我们已经得到了原始的Binder对象实例
构造完代理类之后,我们拦截的方法是queryLocalInterface方法,为什么是这个方法呢?因为在整个服务使用过程中之后在Stub类中使用到了这个方法,很多同学会认为为什么不在这里直接拦截系统方法呢?这是一个误区,要想清楚,这里的代理对象是远程服务的Binder,还不是本地化对象,不能会有哪些系统方法的,所以得再做一次Hook,去Hook住系统的本地化对象。
3、在拦截了Binder对象的queryLocalInterface方法之后,再一次做一下本地化服务对象的代理生成操作,而这个本地化对象一般都是IClipboard$Proxy,那么动态代理的规则:
1》本地化服务对象都会实现AIDL接口类型(这里才有哪些我们想拦截的系统方法)
2》通过反射调用IClipboard$Stub类的asInterface方法得到IClipboard$Proxy类对象实例
符合这两个规则那么就可以产生代理对象了,然后开始拦截服务的指定方法即可。
上面总结的这个流程是可以完全用于其他系统服务的Hook工作的,因为系统服务的机制都是一致的,所以这个流程一定要理解清楚,后面的工作才好进行。
四、补充说明
有的同学可能会好奇询问,这个Hook系统服务貌似只对本应用有效吧?哈哈,的确是这样的,上面的拦截只会对本应用有效,那有的同学会问,只对本应用有效意义就不是很大的,其实这个要看个人需求了,后面会介绍一些开发中遇到的问题,就需要借助这个技术去解决了,但是真正能够拦截系统的服务对所有的应用有效,其实想想实现也不难,因为应用都会请求服务,而所有的服务都在system_server进程中,那么就可以采用root之后,注入system_server进程,那时候在开始Hook工作即可完成真正意义上的拦截操作,这个用途就大了,比如我们可以修改系统的经纬度信息,伪造假的当前位置信息,篡改设备的IMEI值,让有的游戏识别唯一设备无效等。但是这个就需要root了之后才可以操作了,具体方案感兴趣的同学可以去这里看看:Android中注入系统进程实现拦截
五、总结
到这里我们就介绍完了Android中Hook系统服务的流程,本文中主要介绍了Hook系统剪切板服务,拦截指定方法,其实后面还会继续介绍拦截AMS和PMS服务,实现应用启动的拦截操作,达到我们想要的效果。
更多内容:点击这里
关注微信公众号,最新技术干货实时推送
以上是关于Android图形系统系统篇之HWC的主要内容,如果未能解决你的问题,请参考以下文章
Android图形系统(六)-app与SurfaceFlinger服务连接过程