6

利用HOOK实现响应速度秒级测试

 2 years ago
source link: https://www.51cto.com/article/711418.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者 | 金剑超、杨琛,单位:中国移动智慧家庭运营中心

Labs 导读

随着移动互联网技术的飞速发展,移动应用的用户量和使用量与日俱增,用户对移动应用的性能提出更高的要求,当前性能测试技术跟不上移动应用的发展速度,测试效率偏低,测试结果不能反馈真实用户感受。本文利用Hook技术实现安卓应用响应速度秒级测试,旨在为移动应用性能测试提供新思路。

a657bce643934325037023c752ed45ffa4d6c8.png
a8e4214821cad6d855d0246aee7544b3494179.png

os:测试卒!产品卒!

77376aa17ff0711014f2003777d706a2d1423f.png

Part 01  Android平台Hook技术

Android操作系统有一套自己的事件分发机制,应用程序、应用触发事件、后台逻辑处理,都是根据事件流程一步步地向下执行。Hook(钩子) 就是在事件传送到终点前截获并监控事件的传输,像个钩子钩上事件一样,并且能够在钩上事件时处理一些自己特定的事件,其本质就是劫持函数调用。

1.1 Android Hook工作流程

Hook 的原理是改变目标函数的指向。Android是基于Linux内核的操作系统,每个Android进程都有自己独立的进程空间,要实现Hook首先需要将代码注入到目标进程空间,使得代码在目标进程空间中有对应的地址,最后修改寄存器需要执行的函数地址。

在Android中常用ptrace函数附着进程,mmap函数分配临时内存存放代码,dlopen 函数装载动态链接库文件来实现Hook,具体流程如下:

  • 使用ptrace attach附着在目标进程上;
  • 调用mmap分配内存,向内存中写入注入so文件;
  • 调用dlopen打开注入so文件,通过dlsym获取入口函数地址;
  • 使用ptrace_call调用入口函数;
  • 使用ptrace detach 释放目标进程;
48d109b8848424db05a332c8fa71d62d3f660d.png

图1 目标进程注入代码过程

1.2 Android平台常见Hook技术

Android 系统提供了两种开发模式,基于 Android SDK 的 Java 语言开发,基于 Android NDK 的 Native C/C++ 语言开发。根据API Hook 对应API ,AndroidHook分为两种,一种是Java层Hook,一种是Native层Hook。

Java层Hook主要是通过对 Android 平台的虚拟机注入与 Java 反射的方式,来改变 Android 虚拟机调用函数的方式,从而达到 Java 函数重定向的目的。常见框架有Xposed、Cydia Substrate等。

Native层Hook主要是针对使用NDK开发出来的so 库文件,包括对Android 操作系统底层的Linux 函数重定向,通过修改so 库文件( ELF 格式文件)中的全局偏移表GOT 表或符号表SYM 表来实现函数重定向。常见的框架有DeXposed、AndFix等。

1.3 Xposed框架

Xposed是目前使用最广的Android平台开源Hook框架,该框架通过替换 /system/bin/app_process 程序控制 Zygote 进程,使系统在启动过程中加载 XposedBridge.jar文件,从而完成对 Zygote 进程及其创建的 Dalvik 虚拟机的劫持。

Xposed框架主要功能是建立了一个模块安装平台,在安装Xposed框架之后,开发者可以自定义开发各种功能模块,添加到xposed installer中使用,实现修改目标应用程序的功能,例如修改手机序列号,GPS坐标,电话号码,运营商等等信息,当其他应用试图读取这些信息的时候就返回修改值或随机数。

Part 02  测试工具设计和实现

Android应用在运行过程中会调用系统提供的API,通过对系统API的HOOK,获取其调用信息,再对API的调用关系和调用时间进行分析,可以实现系统API间调用耗时统计。基于该原理,本文设计了一套Android应用响应时间测试工具。

2.1 设计方案

本文实现的Android应用响应时间测试工具整体架构如图2所示,其包括xposed模块、分析模块。xposed模块负责事件获取和信息输出,主要功能在于Android系统运行过程中获取点击事件信息和绘制事件信息,输出到logcat。分析模块监听logcat日志,对采集到的信息进行分析并计算响应耗时。

95517d343e0ad89a50e2030b911a66345656e1.png

图2 响应时间测试工具架构图

2.2 点击事件HOOK实现

Android应用采用事件驱动的形式与用户交互,当用户触摸屏幕时会产生点击事件(MotionEvent),然后通过事件分发传递给具体业务进行相关处理。点击事件的分发过程主要由以下三个函数来实现:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()。

一个完整的用户点击屏幕操作工作流程为,用户触摸产生点击事件,调用Activity.dispatchTouchEvent()传递到Activity中,Activity接受到事件后调用ViewGroup.dispatchTouchEvent()传递ViewGroup,ViewGroup接收到事件后调用ViewGroup.onInterceptTouchEvent()确认事件是否拦截,若未拦截则查找当前被点击的子View,调用该View的dispatchTouchEvent()实现事件传递,View接收到事件后调用View.onTouchEvent()进行处理,最终完成点击事件的消费。

从上述事件分发流程可知,点击事件的分发由Activity.dispatchTouchEvent()开始,因此我们选择该函数作为点击的起始时间。利用Xposed框架实现对Activity.dispatchTouchEvent()的Hook,并在该函数执行前输出对应的log信息,包含事件Tag和事件时间戳,具体实现代码如下:

/**
 * Function: Hook dispatchTouchEvent, 获取点击事件时间戳
 */
findAndHookMethod("android.app.Activity
, loadPackageParam.classLoader,"dispatchTouchEvent", MotionEvent.class,new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
        // 日志输出点击事件时间戳
        Log.d("speedtest", "Touch Timestamp: " + System.currentTimeMillis() + " ms");
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
    }
});

2.3 绘制事件HOOK实现

Android系统的显示过程可以简单概括为应用层进行测量、布局、绘制,完成后将Surface数据存放在匿名共享内存中,SurfaceFlinger将多个Surface缓存数据进行合成,提交到屏幕的后缓冲区,再通过Android的刷新机制把数据更新到屏幕,通过应用层不断绘制和屏幕不断刷新最终显示出一帧帧的画面。

Android应用程序的显示过程包含了两个部分应用侧绘制和系统侧渲染,根据Xposed框架的适用范围和Hook的难易程度,我们选择应用侧绘制作为Hook目标。Android中的任何一个布局、任何一个控件其实都是直接或间接继承自View实现的,所有View都遵循相同的绘制流程,通过Measure和Layout来确定当前需要绘制的View所在的大小和位置,通过绘制(Draw)到Surface。

View的绘制过程又可细分如下:

  • 绘制背景:drawBackground(canvas);
  • 绘制自己:onDraw(canvas);
  • 绘制子View:onDraw(canvas);
  • 绘制前景色跟滚动条:onDrawForeground(canvas)。

依据上述分析,我们选择View.onDrawForeground()作为目标函数,并通过Xposed框架实现Hook,在该函数执行前后输出对应的log信息,包含事件Tag和事件时间戳,具体实现代码如下:

/**
 * Function: Hook dispatchDraw, 获取绘制事件时间戳
 */
findAndHookMethod("android.view.View", loadPackageParam.classLoader,"onDrawForeground", Canvas.class,new XC_MethodHook() {
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        super.beforeHookedMethod(param);
        // 日志输出绘制开始时间戳
        Log.d("speedtest", "Draw Timestamp Start: " + System.currentTimeMillis() + " ms");
    }
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        // 日志输出绘制结束时间戳
        Log.d("speedtest", "Draw Timestamp End: " + System.currentTimeMillis() + " ms");
    }
});

2.4 分析模块实现

该模块核心功能是日志监听和页面耗时计算,实现监听点击事件和绘制事件的时间戳信息输出,提取对应事件戳,通过分析计算页面加载耗时。

Logcat是Android操作系统的一个命令工具,主要作用是获取应用程序的日志信息,手机端通过USB连接ADBShell后,可以在命令行窗口中通过”adb Logcat”命令来查看。本模块的日志监听主要通过命令”adb logcat -s Tag”完成,实现日志采集并进行Tag过滤。

该模块还通过python正则匹配提取日志信息中的事件时间戳信息,并计算页面加载耗时。该模块的执行过程如下:

  • 启动日志监听;
  • 实时采集logcat信息,并提取事件信息和时间戳信息存入本地数据库;
  • 点击页面耗时分析,计算页面耗时,计算公式为:耗时=最后Draw Timestamp End时间戳 - 起始Touch Timestamp时间戳
  • 停止日志监听,测试结束;

Part 03  测试工具应用分析

为了验证该工具的准确性,我们选择进行activity启动耗时、录屏分析获取响应时间作为对比进行了实际测试,测试手机型为Xiaomi 9,Android系统版本7.1.2,录屏设备为罗技C920(录制帧率20fps),测试页面为和小区公告页面,详细测试记录如下表:

表1 不同方式页面加载耗时测试记录表

52bfe31276b336c5b21028f87aa97576df6ab8.png

我们对15次测试结果进行统计分析(见图3),可以发现通过Hook方式获取到的页面加载耗时与录屏分析获取的页面加载耗时基本一致,证明该工具可以在Android应用响应速度测试使用,并能反馈用户真实体验。

06e104c329825fdd949901768cbfde7bf57dae.png

图3 不同方式页面加载耗时统计结果图

Part 04  结束语

本文设计并实现了一种基于Hook技术的Android应用响应时间测试工具,其该工具的核心是利用Hook API技术实现关键事件信息的采集,实现响应时间测试,其具有测试结果精准、测试结果秒级输出等特点。Hook技术除了在响应时间上使用外,还可用于其他性能测试,比如崩溃信息获取、流量统计等,本文以响应时间测试工具为例描述具体实现过程,旨在为其他基于Hook技术的测试工具提供参考。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK