Android 最强保活黑科技的最强技术实现

你们好,我是老玩童。今天来跟你们分享 TIM 最强保活思路的几种实现方法。这篇文章我将经过 ioctl 跟 binder 驱动交互,实现以最快的方式唤醒新的保活服务,最大程度防止保活失败。同时,我也将跟您分享,我是怎么作到在不甚了解 binder 的状况下,快速实现 ioctl binder 这种高级操做。php

声明:如今这个保活方式在 MIUI 等定制 Android 系统中已经不能保活,大部分时候只能活在模拟器中了。但对于咱们的轻量定制的 Android 系统,一些系统级应用的保活,这个方案仍是有用的。java

随着 Android 阵营的各大手机厂商对于续航的高度重视,两三年前的手机发布会更是把反保活做为一个系统的卖点,不断提出了各类反保活的方案,致使如今想实现应用保活简直难于上青天,甚至都须要一个团队来专门研究这个事情。连微信这种超级 APP,也要拜倒在反保活的石榴裙下,容许后台启动太费电,不容许后台启动就收不到消息。。Android 发现了一个保活野路子就堵一条,然而不少场景是有保活的强需求的,有木有考虑过咱们开发者的感觉,本身人何须为难本身人😭。linux

我以为这是一个 Android 设计的不合理的地方,路子能够堵,但仍是有必要留一个统一的保活接口的。这个接口由 Google 实现也好,厂商来实现也好,总好过如今很笨拙的系统自启动管理或者是 JobScheduler。我以为本质上来讲,让应用开发者想尽各类办法去作保活,这个事情是没有意义的,保活的路子被封了,但保活仍是须要作,保活的成本也提升了,简直浪费生命。Android 的锅。(仅表明我的观点)android

黑科技进程保活原理

大概 2 个月前,Gityuan 大佬放出了一份分析 TIM 的黑科技保活的博客史上最强 Android 保活思路:深刻剖析腾讯 TIM 的进程永生技术 [1](后来不知道什么缘由又删除了),顿时间掀起了一阵波澜,仿佛让开发者们又看到了应用保活的一丝但愿。Gityuan 大佬经过超强的专业技术分析,为咱们解开了 TIM 保活方案的终极奥义。c++

后来,为数很少的维术大佬在 Gityuan 大佬的基础上,发布了博客 Android 黑科技保活实现原理揭秘 [2] 又进行了系统进程查杀相关的源码分析。为咱们带来的结论是,Android 系统杀应用的时候,会去杀进程组,循环 40 遍不停地杀进程,每次杀完以后等 5msgit

总之,引用维术的话语,原理以下:github

  1. 利用 Linux 文件锁的原理,使用 2 个进程互相监听各自的文件锁,来感知彼此的死亡。
  2. 经过 fork 产生子进程,fork 的进程同属一个进程组,一个被杀以后会触发另一个进程被杀,从而被文件锁感知。

具体来讲,建立 2 个进程 p1, p2,这两个进程经过文件锁互相关联,一个被杀以后拉起另一个;同时 p1 通过 2 次 fork 产生孤儿进程 c1,p2 通过 2 次 fork 产生孤儿进程 c2,c1 和 c2 之间创建文件锁关联。这样假设 p1 被杀,那么 p2 会立马感知到,而后 p1 和 c1 同属一个进程组,p1 被杀会触发 c1 被杀,c1 死后 c2 立马感觉到从而拉起 p1,所以这四个进程三三之间造成了铁三角,从而保证了存活率。web

按照维术大佬的理论,只要进程我复活的足够快,系统它就杀不死我,嘿嘿。shell

维术大佬写了一个简单的实现,代码在这里:github.com/tiann/Leoric[3],这个方案是当检测到进程被杀时,会经过 JNI 的方式,调用 Java 层的方法来复活进程。为了实现稳定的保活,尤为是系统杀进程只给了 5ms 复活的机会,使用 JNI 这种方式复活进程如今达不到最优的效果。编程

Java 层复活进程

复活进程,其实就是启动指定的 Service。当 native 层检测到有进程被杀时,为了可以快速启动新 Service。咱们能够经过反射,拿到 ActivityManager 的 remote binder,直接经过这个 binder 发送数据,便可实现快速启动 Service。

Class<?> amnCls = Class.forName("android.app.ActivityManagerNative");
amn = activityManagerNative.getMethod("getDefault").invoke(amnCls);
Field mRemoteField = amn.getClass().getDeclaredField("mRemote");
mRemoteField.setAccessible(true);
mRemote = (IBinder) mRemoteField.get(amn);

启动 Service 的 Intent:

Intent intent = new Intent();
ComponentName component = new ComponentName(context.getPackageName(), serviceName);
intent.setComponent(component);

封装启动 Service 的 Parcel:

Parcel mServiceData = Parcel.obtain();
mServiceData.writeInterfaceToken("android.app.IActivityManager");
mServiceData.writeStrongBinder(null);
mServiceData.writeInt(1);
intent.writeToParcel(mServiceData, 0);
mServiceData.writeString(null); // resolvedType
mServiceData.writeInt(0);
mServiceData.writeString(context.getPackageName()); // callingPackage
mServiceData.writeInt(0);

启动 Service:

mRemote.transact(transactCode, mServiceData, null, 1);

在 native 层进行 binder 通讯

在 Java 层作进程复活的工做,这个方式是比较低效的,最好的方式是在 native 层使用纯 C/C++来复活进程。方案有两个。

其一,维术大佬给出的方案是利用 libbinder.so, 利用 Android 提供的 C++接口,跟 ActivityManagerService 通讯,以唤醒新进程。

  1. Java 层建立 Parcel (含 Intent),拿到 Parcel 对象的 mNativePtr(native peer),传到 Native 层。
  2. native 层直接把 mNativePtr 强转为结构体指针。
  3. fork 子进程,创建管道,准备传输 parcel 数据。
  4. 子进程读管道,拿到二进制流,重组为 parcel。

其二,Gityuan 大佬则认为使用 ioctl 直接给 binder 驱动发送数据以唤醒进程,才是更高效的作法。然而,这个方法,大佬们并无提供思路。

那么今天,咱们就来实现这两种在 native 层进行 Binder 调用的骚操做。

方式一  利用 libbinder.so 与 ActivityManagerService 通讯

上面在 Java 层复活进程一节中,是向 ActivityManagerService 发送特定的封装了 Intent 的 Parcel 包来实现唤醒进程。而在 native 层,没有 Intent 这个类。因此就须要在 Java 层建立好 Intent,而后写到 Parcel 里,再传到 Native 层。

Parcel mServiceData = Parcel.obtain();
mServiceData.writeInterfaceToken("android.app.IActivityManager");
mServiceData.writeStrongBinder(null);
mServiceData.writeInt(1);
intent.writeToParcel(mServiceData, 0);
mServiceData.writeString(null); // resolvedType
mServiceData.writeInt(0);
mServiceData.writeString(context.getPackageName()); // callingPackage
mServiceData.writeInt(0);

查看Parcel 的源码 [4] 能够看到,Parcel 类有一个 mNativePtr 变量:

private long mNativePtr; // used by native code
// android4.4 mNativePtr 是 int 类型

能够经过反射获得这个变量:

private static long getNativePtr(Parcel parcel) {
try {
Field ptrField = parcel.getClass().getDeclaredField("mNativePtr");
ptrField.setAccessible(true);
return (long) ptrField.get(parcel);
} catch (Exception e) {
e.printStackTrace();
}
return 0;
}

这个变量对应了 C++中Parcel 类 [5] 的地址,所以能够强转获得 Parcel 指针:

Parcel *parcel = (Parcel *) parcel_ptr;

然而,NDK 中并无提供 binder 这个模块,咱们只能从 Android 源码中扒到 binder 相关的源码,再编译出 libbinder.so。腾讯 TIM 应该就是魔改了 binder 相关的源码。

提取 libbinder.so

为了不 libbinder 的版本兼容问题,这里咱们能够采用一个更简单的方式,拿到 binder 相关的头文件,再从系统中拿到 libbinder.so,固然 binder 模块还依赖了其它的几个 so,要一块儿拿到,否则编译的时候会报连接错误。

adb pull /system/lib/libbinder.so ./
adb pull /system/lib/libcutils.so ./
adb pull /system/lib/libc.so ./
adb pull /system/lib/libutils.so ./

若是须要不一样 SDK 版本,不一样架构的系统 so 库,能够在 Google Factory Images[6] 网页里找到适合的版本,下载相应的固件,而后解包 system.img(须要在 windows 或 linux 中操做),提取出目标 so。

binder_libs
├── arm64-v8a
│ ├── libbinder.so
│ ├── libc.so
│ ├── libcutils.so
│ └── libutils.so
├── armeabi-v7a
│ ├── ...
├── x86
│ ├── ...
└── x86_64
├── ...

为了不兼容问题,我这里只让这些 so 参与了 binder 相关的头文件的连接,而没有实际使用这些 so。这是利用了 so 的加载机制,若是应用 lib 目录没有相应的 so,则会到 system/lib 目录下查找。

SDK24 以上,系统禁止了从 system 中加载 so 的方式,因此使用这个方法务必保证 targetApi <24。

不然,将会报找不到 so 的错误。能够把上面的 so 放到 jniLibs 目录解决这个问题,但这样就会有兼容问题了。

CMake 修改:

# 连接 binder_libs 目录下的全部 so 库
link_directories(binder_libs/${CMAKE_ANDROID_ARCH_ABI})
# 引入 binder 相关的头文件
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include/)
# libbinder.so libcutils.so libutils.so libc.so 等库连接到 libkeep_alive.so
target_link_libraries(
keep_alive
${log-lib} binder cutils utils c)

进程间传输 Parcel 对象

C++里面还能传输对象?不存在的。好在 Parcel 能直接拿到数据地址,并提供了构造方法。因此咱们能够经过管道把 Parcel 数据传输到其它进程。

Parcel *parcel = (Parcel *) parcel_ptr;
size_t data_size = parcel->dataSize();
int fd[2];
// 建立管道
if (pipe(fd) < 0) {return;}

pid_t pid;
// 建立子进程
if ((pid = fork()) < 0) {
exit(-1);
} else if (pid == 0) {//第一个子进程
if ((pid = fork()) < 0) {
exit(-1);
} else if (pid > 0) {
// 托孤
exit(0);
}

uint8_t data[data_size];
// 托孤的子进程,读取管道中的数据
int result = read(fd[0], data, data_size);
}

// 父进程向管道中写数据
int result = write(fd[1], parcel->data(), data_size);

从新建立 Parcel:

Parcel parcel;
parcel.setData(data, data_size);

传输 Parcel 数据

// 获取 ServiceManager
sp<IServiceManager> sm = defaultServiceManager();
// 获取 ActivityManager binder
sp<IBinder> binder = sm->getService(String16("activity"));
// 传输 parcel
int result = binder.get()->transact(code, parcel, NULL, 0);

方式二  使用 ioctl 与 binder 驱动通讯

方式一让我尝到了一点甜头,实现了大佬的思路,不由让鄙人浮想联翩,感慨万千,鄙人的造诣已经如此之深,不久就会人在美国,刚下飞机,迎娶白富美,走向人生巅峰矣......

咳咳。不由想到 ioctl 的方式我也能够尝试着实现一下。ioctl 是一个 linux 标准方法,那么咱们就直奔主题看看,binder 是什么,ioctl 怎么跟 binder driver 通讯。

Binder 介绍

Binder 是 Android 系统提供的一种 IPC 机制。每一个 Android 的进程,均可以有一块用户空间和内核空间。用户空间在不一样进程间不能共享,内核空间能够共享。Binder 就是一个利用能够共享的内核空间,完成高性能的进程间通讯的方案。

Binder 通讯采用 C/S 架构,从组件视角来讲,包含 Client、Server、ServiceManager 以及 binder 驱动,其中 ServiceManager 用于管理系统中的各类服务。如图:

能够看到,注册服务、获取服务、使用服务,都是须要通过 binder 通讯的。

  • Server 经过注册服务的 Binder 通讯把本身托管到 ServiceManager
  • Client 端能够经过 ServiceManager 获取到 Server
  • Client 端获取到 Server 后就可使用 Server 的接口了

Binder 通讯的表明类是 BpBinder(客户端) 和 BBinder(服务端)。

ps:有关 binder 的详细知识,你们能够查看 Gityuan 大佬的Binder 系列 [7] 文章。

ioctl 函数

ioctl(input/output control) 是一个专用于设备输入输出操做的系统调用,它诞生在这样一个背景下:

操做一个设备的 IO 的传统作法,是在设备驱动程序中实现 write 的时候检查一下是否有特殊约定的数据流经过,若是有的话,后面就跟着控制命令(socket 编程中经常这样作)。可是这样作的话,会致使代码分工不明,程序结构混乱。因此就有了 ioctl 函数,专门向驱动层发送或接收指令。

Linux 操做系统分为了两层,用户层和内核层。咱们的普通应用程序处于用户层,系统底层程序,好比网络栈、设备驱动程序,处于内核层。为了保证安全,操做系统要阻止用户态的程序直接访问内核资源。一个Ioctl接口是一个独立的系统调用,经过它用户空间能够跟设备驱动沟通了。函数原型:

int ioctl(int fd, int request, …);

做用:经过 IOCTL 函数实现指令的传递

  • fd 是用户程序打开设备时使用 open 函数返回的文件描述符
  • request 是用户程序对设备的控制命令
  • 后面的省略号是一些补充参数,和 cmd 的意义相关

应用程序在调用ioctl进行设备控制时,最后会调用到设备注册struct file_operations结构体对象时的unlocked_ioctl或者compat_ioctl两个钩子上,例如 Binder 驱动的这两个钩子是挂到了 binder_ioctl 方法上:

static const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};

它的实现以下:

static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
/*根据不一样的命令,调用不一样的处理函数进行处理*/
switch (cmd) {
case BINDER_WRITE_READ:
/*读写命令,数据传输,binder IPC 通讯的核心逻辑*/
ret = **binder_ioctl_write_read**(filp, cmd, arg, thread);
break;
case BINDER_SET_MAX_THREADS:
/*设置最大线程数,直接将值设置到 proc 结构的 max_threads 域中。*/
break;
case BINDER_SET_CONTEXT_MGR:
/*设置 Context manager,即将本身设置为 ServiceManager,详见 3.3*/
break;
case BINDER_THREAD_EXIT:
/*binder 线程退出命令,释放相关资源*/
break;
case BINDER_VERSION: {
/*获取 binder 驱动版本号,在 kernel4.4 版本中,32 位该值为 7,64 位版本该值为 8*/
break;
}
return ret;
}

具体内核层的实现,咱们就不关心了。到这里咱们了解到,Binder 在 Android 系统中会有一个设备节点,调用 ioctl 控制这个节点时,实际上会调用到内核态的 binder_ioctl 方法。

为了利用 ioctl 启动 Android Service,必然是须要用 ioctl 向 binder 驱动写数据,而这个控制命令就是BINDER_WRITE_READ。binder 驱动层的一些细节咱们在这里就不关心了。那么在什么地方会用 ioctl 向 binder 写数据呢?

IPCThreadState.talkWithDriver

阅读 Gityuan 的Binder 系列 6—获取服务 (getService)[8] 一节,在 binder 模块下IPCThreadState.cpp[9] 中有这样的实现 (源码目录:frameworks/native/libs/binder/IPCThreadState.cpp):

status_t IPCThreadState::talkWithDriver(bool doReceive) {
...
binder_write_read bwr;
bwr.write_buffer = (uintptr_t)mOut.data();
status_t err;
do {
//经过 ioctl 不停的读写操做,跟 Binder Driver 进行通讯
if (ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr) >= 0)
err = NO_ERROR;
...
} while (err == -EINTR); //当被中断,则继续执行
...
return err;
}

能够看到 ioctl 跟 binder driver 交互很简单,一个参数是 mProcess->mDriverFD,一个参数是 BINDER_WRITE_READ,另外一个参数是 binder_write_read 结构体,很幸运的是,NDK 中提供了linux/android/binder.h这个头文件,里面就有 binder_write_read 这个结构体,以及 BINDER_WRITE_READ 常量的定义。

[惊不惊喜意不意外 ]

#include<linux/android/binder.h>
struct binder_write_read {
binder_size_t write_size;
binder_size_t write_consumed;
binder_uintptr_t write_buffer;
binder_size_t read_size;
binder_size_t read_consumed;
binder_uintptr_t read_buffer;
};
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)

这意味着,这些结构体和宏定义极可能是版本兼容的。

那咱们只须要到时候把数据揌到 binder_write_read 结构体里面,就能够进行 ioctl 系统调用了!

/dev/binder

再来看看 mProcess->mDriverFD 是什么东西。mProcess 也就是ProcessState.cpp[10](源码目录:frameworks/native/libs/binder/ProcessState.cpp):

ProcessState::ProcessState(const char *driver)
: mDriverName(String8(driver))
, mDriverFD(open_driver(driver))
, ...
{}

从 ProcessState 的构造函数中得知,mDriverFD 由 open_driver 方法初始化。

static int open_driver(const char *driver) {
int fd = open(driver, O_RDWR | O_CLOEXEC);
if (fd >= 0) {
int vers = 0;
status_t result = ioctl(fd, BINDER_VERSION, &vers);
}
return fd;
}

ProcessState 在哪里实例化呢?

sp<ProcessState> ProcessState::self() {
if (gProcess != nullptr) {
return gProcess;
}
gProcess = new ProcessState(kDefaultDriver);
return gProcess;
}

能够看到,ProcessState 的 gProcess 是一个全局单例对象,这意味着,在当前进程中,open_driver 只会执行一次,获得的 mDriverFD 会一直被使用。

const char* kDefaultDriver = "/dev/binder";

而 open 函数操做的这个设备节点就是/dev/binder。

纳尼?在应用层直接操做设备节点?Gityuan 大佬不会骗我吧?通常来讲,Android 系统在集成 SELinux 的安全机制以后,普通应用甚至是系统应用,都不能直接操做一些设备节点,除非有 SELinux 规则,给应用所属的域或者角色赋予了那样的权限。

看看文件权限:

➜  ~ adb shell
chiron:/ $ ls -l /dev/binder
crw-rw-rw- 1 root root 10, 49 1972-07-03 18:46 /dev/binder

能够看到,/dev/binder 设备对全部用户可读可写。

再看看,SELinux 权限:

chiron:/ $ ls -Z /dev/binder
u:object_r:binder_device:s0 /dev/binder

查看源码中对 binder_device 角色的 SELinux 规则描述:

allow domain binder_device:chr_file rw_file_perms;

也就是全部 domain 对 binder 的字符设备有读写权限,而普通应用属于 domain。

既然这样,肝它!

写个 Demo 试一下

验证一下上面的想法,看看 ioctl 给 binder driver 发数据好很差使。

一、打开设备

int fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
if (fd < 0) {
LOGE("Opening '%s' failed: %s\n", "/dev/binder", strerror(errno));
} else {
LOGD("Opening '%s' success %d: %s\n", "/dev/binder", fd, strerror(errno));
}

二、ioctl

Parcel *parcel = new Parcel;
parcel->writeString16(String16("test"));
binder_write_read bwr;
bwr.write_size = parcel->dataSize();
bwr.write_buffer = (binder_uintptr_t) parcel->data();
int ret = ioctl(fd, BINDER_WRITE_READ, bwr);
LOGD("ioctl result is %d: %s\n", ret, strerror(errno));

三、查看日志

D/KeepAlive: Opening '/dev/binder' success, fd is 35
D/KeepAlive: ioctl result is -1: Invalid argument

打开设备节点成功了,耶✌️!可是 ioctl 失败了🤔,失败缘由是Invalid argument,也就是说能够通讯,可是 Parcel 数据有问题。来看看数据应该是什么样的。

binder_write_read 结构体数据封装

IPCThreadState.talkWithDriver 方法中,bwr.write_buffer 指针指向了 mOut.data(),显然 mOut 是一个 Parcel 对象。

binder_write_read bwr;
bwr.write_buffer = (uintptr_t)mOut.data();

再来看看何时会向 mOut 中写数据:

status_t IPCThreadState::writeTransactionData(int32_t cmd, uint32_t binderFlags,
int32_t handle, uint32_t code, const Parcel& data, status_t* statusBuffer)
{
binder_transaction_data tr;
tr.data.ptr.buffer = data.ipcData();
...
mOut.writeInt32(cmd);
mOut.write(&tr, sizeof(tr));
return NO_ERROR;
}

writeTransactionData 方法中,会往 mOut 中写入一个 binder_transaction_data 结构体数据,binder_transaction_data 结构体中又包含了做为参数传进来的 data Parcel 对象。

writeTransactionData 方法会被 transact 方法调用:

status_t IPCThreadState::transact(int32_t handle, uint32_t code, const Parcel& data,
Parcel* reply, uint32_t flags) {
status_t err = data.errorCheck(); // 数据错误检查
flags |= TF_ACCEPT_FDS;
if (err == NO_ERROR) {
// 传输数据
err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
}
...

// 默认状况下,都是采用非 oneway 的方式, 也就是须要等待服务端的返回结果
if ((flags & TF_ONE_WAY) == 0) {
if (reply) {
//等待回应事件
err = waitForResponse(reply);
}else {
Parcel fakeReply;
err = waitForResponse(&fakeReply);
}
} else {
err = waitForResponse(NULL, NULL);
}
return err;
}

IPCThreadState 是跟 binder driver 真正进行交互的类。每一个线程都有一个IPCThreadState,每一个IPCThreadState中都有一个 mIn、一个 mOut。成员变量 mProcess 保存了 ProcessState 变量 (每一个进程只有一个)。

接着看一下一次 Binder 调用的时序图:

Binder 介绍一节中说过,BpBinder 是 Binder Client,上层想进行进程间 Binder 通讯时,会调用到 BpBinder 的 transact 方法,进而调用到 IPCThreadState 的 transact 方法。来看看 BpBinder 的 transact 方法的定义:

status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
if (mAlive) {
status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
if (status == DEAD_OBJECT) mAlive = 0;
return status;
}
return DEAD_OBJECT;
}

BpBinder::transact 方法的 code/data/reply/flags 这几个参数都是调用的地方传过来的,如今惟一不知道的就是 mHandle 是什么东西。mHandle 是 BpBinder(也就是 Binder Client) 的一个 int 类型的局部变量(句柄),只要拿到了这个 handle 就至关于拿到了 BpBinder。

ioctl 启动 Service 分几步?

下面是在依赖 libbinder.so 时,启动 Service 的步骤:

// 获取 ServiceManager
sp<IServiceManager> sm = defaultServiceManager();
// 获取 ActivityManager binder
sp<IBinder> binder = sm->getService(String16("activity"));
// 传输 parcel
int result = binder.get()->transact(code, parcel, NULL, 0);

一、获取到 IServiceManager Binder Client;

二、从 ServiceManager 中获取到 ActivityManager Binder Client;

三、调用 ActivityManager binder 的 transact 方法传输 Service 的 Parcel 数据。

经过 ioctl 启动 Service 也应该是相似的步骤:

一、获取到 ServiceManager 的 mHandle 句柄;

二、进行 binder 调用获取到 ActivityManager 的 mHandle 句柄;

三、进行 binder 调用传输启动 Service 的指令数据。

这里有几个问题:

一、不依赖 libbinder.so 时,ndk 中没有 Parcel 类的定义,parcel 数据哪里来,怎么封装?

二、如何获取到 BpBinder 的 mHandle 句柄?

如何封装 Parcel 数据

Parcel 类是 Binder 进程间通讯的一个基础的、必不可少的数据结构,往 Parcel 中写入的数据其实是写入到了一块内部分配的内存上,最后把这个内存地址封装到 binder_write_read 结构体中。Parcel 做为一个基础的数据结构,和 Binder 相关类是能够解耦的,能够直接拿过来使用,咱们能够根据须要对有耦合性的一些方法进行裁剪。

c++ Parcel 类路径:frameworks[11]/native[12]/libs[13]/binder[14]/Parcel.cpp[15]

jni Parcel 类路径:frameworks[16]/base[17]/core[18]/jni[19]/android_os_Parcel.cpp[20]

如何获取到 BpBinder 的 mHandle 句柄

具体流程参考Binder 系列 4—获取 ServiceManager[21]

一、获取 ServiceManager 的 mHandle 句柄

defaultServiceManager() 方法用来获取gDefaultServiceManager对象,gDefaultServiceManager 是 ServiceManager 的单例。

sp<IServiceManager> defaultServiceManager() {
if (gDefaultServiceManager != NULL) return gDefaultServiceManager;
while (gDefaultServiceManager == NULL) {
gDefaultServiceManager = interface_cast<IServiceManager>(
ProcessState::self()->getContextObject(NULL));
}
}
return gDefaultServiceManager;
}

getContextObject 方法用来获取 BpServiceManager 对象(BpBinder),查看其定义:

sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/) {
sp<IBinder> context = getStrongProxyForHandle(0);
return context;
}

能够发现,getStrongProxyForHandle 是一个根据 handle 获取 IBinder 对象的方法,而这里 handle 的值为 0,能够得知,ServiceManager 的 mHandle 恒为 0

二、获取 ActivityManager 的 mHandle 句柄

获取 ActivityManager 的 c++方法是:

sp<IBinder> binder = serviceManager->getService(String16("activity"));

BpServiceManager.getService:

virtual sp<IBinder> getService(const String16& name) const {
sp<IBinder> svc = checkService(name);
if (svc != NULL) return svc;
return NULL;
}

BpServiceManager.checkService:

virtual sp<IBinder> checkService( const String16& name) const {
Parcel data, reply;
//写入 RPC 头
data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
//写入服务名
data.writeString16(name);
remote()->transact(CHECK_SERVICE_TRANSACTION, data, &reply);
return reply.readStrongBinder();
}

能够看到,CHECK_SERVICE_TRANSACTION 这个 binder 调用是有返回值的,返回值会写到 reply 中,经过 reply.readStrongBinder() 方法,便可从 reply 这个 Parcel 对象中读取到 ActivityManager 的 IBinder。每一个 Binder 对象必需要有它本身的 mHandle 句柄,否则,transact 操做是没办法进行的。因此,颇有可能,Binder 的 mHandle 的值是写到 reply 这个 Parcel 里面的。

看看 reply.readStrongBinder() 方法搞了什么鬼:

sp<IBinder> Parcel::readStrongBinder() const {
sp<IBinder> val;
readNullableStrongBinder(&val);
return val;
}
status_t Parcel::readNullableStrongBinder(sp<IBinder>* val) const {
return unflattenBinder(val);
}

调用到了 Parcel::unflattenBinder 方法,顾名思义,函数最终想要获得的是一个 Binder 对象,而 Parcel 中存放的是二进制的数据,unflattenBinder 极可能是把 Parcel 中的一个结构体数据给转成 Binder 对象。

看看 Parcel::unflattenBinder 方法的定义:

status_t Parcel::unflattenBinder(sp<IBinder>* out) const {
const flat_binder_object* flat = readObject(false);
if (flat) {
...
sp<IBinder> binder =
ProcessState::self()->getStrongProxyForHandle(flat->handle);
}
return BAD_TYPE;
}

果真如此,从 Parcel 中能够获得一个 flat_binder_object 结构体,这个结构体重有一个 handle 变量,这个变量就是 BpBinder 中的 mHandle 句柄。

所以,在不依赖 libbinder.so 的状况下,咱们能够本身组装数据发送给 ServiceManager,进而获取到 ActivityManager 的 mHandle 句柄。

IPCThreadState 是一个被 Binder 依赖的类,它是能够从源码中抽离出来为咱们所用的。上一节中说到,Parcel 类也是能够从源码中抽离出来的。

经过以下的操做,咱们就能够实现 ioctl 获取到 ActivityManager 对应的 Parcel 对象 reply:

Parcel data, reply;
// data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
// IServiceManager::getInterfaceDescriptor() 的值是 android.app.IActivityManager
data.writeInterfaceToken(String16("android.app.IActivityManager"));
data.writeString16(String16("activity"));
IPCThreadState::self()->transact(
0/*ServiceManger 的 mHandle 句柄恒为 0*/,
CHECK_SERVICE_TRANSACTION, data, reply, 0);

reply 变量也就是咱们想要的包含了 flat_binder_object 结构体的 Parcel 对象,再通过以下的操做就能够获得 ActivityManager 的 mHandle 句柄:

const flat_binder_object* flat = reply->readObject(false);
return flat->handle;

三、传输启动指定 Service 的 Parcel 数据

上一步已经拿到 ActivityManger 的 mHandle 句柄,好比值为 1。这一步的过程和上一步相似,本身封装 Parcel,而后调用 IPCThreadState::transact 方法传输数据,伪代码以下:

Parcel data;
// 把 Service 相关信息写到 parcel 中
writeService(data, packageName, serviceName, sdk_version);
IPCThreadState::self()->transact(
1/*上一步获取的 ActivityManger 的 mHandle 句柄值是 1*/,
CHECK_SERVICE_TRANSACTION, data, reply,
1/*TF_ONE_WAY*/);

四、writeService 方法须要作什么事情?

下面这段代码是 Java 中封装 Parcel 对象的方法:

Intent intent = new Intent();
ComponentName component = new ComponentName(context.getPackageName(), serviceName);
intent.setComponent(component);

Parcel mServiceData = Parcel.obtain();
mServiceData.writeInterfaceToken("android.app.IActivityManager");
mServiceData.writeStrongBinder(null);
mServiceData.writeInt(1);
intent.writeToParcel(mServiceData, 0);
mServiceData.writeString(null); // resolvedType
mServiceData.writeInt(0);
mServiceData.writeString(context.getPackageName()); // callingPackage
mServiceData.writeInt(0);

能够看到,有 Intent 类转 Parcel,ComponentName 类转 Parcel,这些类在 c++中是没有对应的类的。因此须要咱们参考intent.writeToParcel/ComponentName.writeToParcel等方法的源码的实现,自行封装数据。下面这段代码就是把启动 Service 的 Intent 写到 Parcel 中的方法:

void writeIntent(Parcel &out, const char *mPackage, const char *mClass) {
// mAction
out.writeString16(NULL, 0);
// uri mData
out.writeInt32(0);
// mType
out.writeString16(NULL, 0);
// // mIdentifier
out.writeString16(NULL, 0);
// mFlags
out.writeInt32(0);
// mPackage
out.writeString16(NULL, 0);
// mComponent
out.writeString16(String16(mPackage));
out.writeString16(String16(mClass));
// mSourceBounds
out.writeInt32(0);
// mCategories
out.writeInt32(0);
// mSelector
out.writeInt32(0);
// mClipData
out.writeInt32(0);
// mContentUserHint
out.writeInt32(-2);
// mExtras
out.writeInt32(-1);
}

继续写 Demo 试一下

上面已经知道了怎么经过 ioctl 获取到 ActivityManager,能够写 demo 试一下:

// 打开 binder 设备
int fd = open("/dev/binder", O_RDWR | O_CLOEXEC);
Parcel data, reply;
// data.writeInterfaceToken(IServiceManager::getInterfaceDescriptor());
// IServiceManager::getInterfaceDescriptor() 的值是 android.app.IActivityManager
data.writeInterfaceToken(String16("android.app.IActivityManager"));
data.writeString16(String16("activity"));
IPCThreadState::self()->transact(
0/*ServiceManger 的 mHandle 句柄恒为 0*/,
CHECK_SERVICE_TRANSACTION, data, reply, 0);

const flat_binder_object *flat = reply->readObject(false);
if (flat) {
LOGD("write_transact handle is:%llu", flat->handle);
}else {
LOGD("write_transact failed, error=%d", status);
}

给 IPCThreadState::transact 加上一些日志,打印结果以下:

D/KeepAlive: BR_DEAD_REPLY
D/KeepAlive: write_transact failed, error=-32

reply 中始终读不到数据。这是为何?如今已经不报Invalid argument的错误了,说明 Parcel 数据格式可能没问题了。可是不能成功把数据写给 ServiceManager,或者 ServiceManager 返回的数据不能成功写回来。

想到 Binder 是基于内存的一种 IPC 机制,数据都是对的,那问题就出在内存上了。这就要说到 Binder 基本原理以及 Binder 内存转移关系。

Binder 基本原理:

Binder 的 Client 端和 Server 端位于不一样的进程,它们的用户空间是相互隔离。而内核空间由 Linux 内核进程来维护,在安全性上是有保障的。因此,Binder 的精髓就是在内核态开辟了一块共享内存。

数据发送方写数据时,内核态经过 copy_from_user() 方法把它的数据拷贝到数据接收方映射 (mmap) 到内核空间的地址上。这样,只须要一次数据拷贝过程,就能够完成进程间通讯。

由此可知,没有这块内核空间是没办法完成 IPC 通讯的。Demo 失败的缘由就是缺乏了一个 mmap 过程,以映射一块内存到内核空间。修改以下:

#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
int mDriverFD = open("/dev/binder", O_RDWR | O_CLOEXEC);
mmap(0, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, mDriverFD, 0);

日志:

D/KeepAlive: BR_REPLY
D/KeepAlive: write_transact handle is:1

搞定!

最后

相关的代码我已经发布到 Github(lcodecorex[22]/KeepAlive[23]),master分支是利用 libbinder.so 与 ActivityManagerService 通讯的版本,ioctl分支是使用 ioctl 与 binder 驱动通讯的版本。

固然,这个保活的办法虽然很强,但如今也只能活在模拟器里了。

说一下个人方法论。

一、肯定问题和目标。

研究一个比较复杂的东西的时候,咱们比较难有一个大局观。这个时候,就须要明确本身须要什么?有问题,才能推进本身学习,而后顺腾摸瓜,最后弄清本身的模块在系统中的位置。

这篇文章,咱们肯定了目标是直接经过 ioctl 进行 Binder 通讯,进而肯定 Binder 通讯的关键是拿到 mHandle 句柄。同时也理清了 Binder 通讯的一个基本流程。

二、时序图很重要。

大佬们画的时序图,可快帮助咱们快速理清框架的思路。

三、实践出真知。

纸上得来终觉浅,绝知此事要躬行。我一直践行的一个学习方式是学以至用,能够及时写 Demo 帮助咱们巩固知识以及分析问题。

鸣谢

Gityuan 大佬的Binder 系列 [24]

Android 黑科技保活实现原理揭秘 [25]

binder Driver (binder IPC) 功能介绍与分析 [26]

Binder 驱动之设备控制`binder_ioctl`[27]

Google 官方 Android 源码浏览网站 [28]

做者:小玩童 

连接:https://juejin.im/post/5e820b61e51d45470652e7b8

参考资料

[1]

史上最强 Android 保活思路:深刻剖析腾讯 TIM 的进程永生技术: http://www.52im.net/forum.php?mod=viewthread&tid=2893&highlight=%B1%A3%BB%EE

[2]

Android 黑科技保活实现原理揭秘: http://weishu.me/2020/01/16/a-keep-alive-method-on-android/

[3]

github.com/tiann/Leoric: https://github.com/tiann/Leoric

[4]

Parcel 的源码: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/Parcel.java;l=336

[5]

Parcel 类: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/Parcel.cpp

[6]

Google Factory Images: https://developers.google.cn/android/images?h1=zh=cn#angler

[7]

Binder 系列: http://gityuan.com/2015/10/31/binder-prepare/

[8]

Binder 系列 6—获取服务 (getService): http://gityuan.com/2015/11/15/binder-get-service/

[9]

IPCThreadState.cpp: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/IPCThreadState.cpp

[10]

ProcessState.cpp: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/ProcessState.cpp

[11]

frameworks: https://cs.android.com/android/platform/superproject/+/master:frameworks/

[12]

native: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/

[13]

libs: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/

[14]

binder: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/

[15]

Parcel.cpp: https://cs.android.com/android/platform/superproject/+/master:frameworks/native/libs/binder/Parcel.cpp

[16]

frameworks: https://cs.android.com/android/platform/superproject/+/master:frameworks/

[17]

base: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/

[18]

core: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/

[19]

jni: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/jni/

[20]

android_os_Parcel.cpp: https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/jni/android_os_Parcel.cpp

[21]

Binder 系列 4—获取 ServiceManager: http://gityuan.com/2015/11/08/binder-get-sm/

[22]

lcodecorex: https://github.com/lcodecorex

[23]

KeepAlive: https://github.com/lcodecorex/KeepAlive

[24]

Binder 系列: http://gityuan.com/2015/10/31/binder-prepare/

[25]

Android 黑科技保活实现原理揭秘: http://weishu.me/2020/01/16/a-keep-alive-method-on-android/

[26]

binder Driver (binder IPC) 功能介绍与分析: https://blog.csdn.net/vshuang/article/details/88823044

[27]

Binder 驱动之设备控制binder_ioctl: https://www.jianshu.com/p/49830c3473b7

[28]

Google 官方 Android 源码浏览网站: https://cs.android.com/


本文分享自微信公众号 - 贾小昆(zywudev)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索