在framework的代码中,经常看到如下的权限检测的代码:
Binder.getCallingUid()字面理解是获取调用方的uid,但是这个代码是目标进程调用的,如何通过一个静态方法调用,就拿到调用方的uid呢?
更多问答 >>
-
2023-10-25 00:22
-
2024-06-17 09:16
-
每日一问 | Java线程栈的栈溢出(StackOverflowError)是如何检测的?
2024-02-19 18:30 -
每日一问 | targetSdkVersion 有什么神奇的魔力?
2023-05-24 17:30 -
每日一问 | Android 模块化依赖中的资源冲突该如何规避?
2023-05-24 17:30 -
每日一问 | Android 默认开启硬件加速与设置hardwareAccelerated是一回事吗?
2023-05-24 17:30
每日一问 | Binder是如何做到跨进程权限控制的?
原问题出处:每日一问 | Binder是如何做到跨进程权限控制的?:https://wanandroid.com/wenda/show/266241.代码解答
Binder.getCallingUid()字面理解是获取调用方的uid,但是这个代码是目标进程调用的,如何通过一个静态方法调用,就拿到调用方的uid呢?
这里看看Binder.getCallingUid()的代码:
发现getCallingUid()方法是Native函数,那么它是动态注册到那个cpp类的方法呢?其实在Android系统启动过程中,Zygote进程就已经动态注册的该方法,Zygote进程是由Init进程通过init.zygote.rc文件而创建的,zygote所对应的可执行程序app_process,所对应的源文件frameworks/base/cmds/app_process/app_main.cpp。
上图的第4步startReg()方法就是Zygote注册Native的方法入口:综上所述,流程最终会调用register_android_os_Binder(JNIEnv* env)方法,这个函数定义在frameworks/base/core/jni/android_util_Binder.cpp里:
综上所述,兜兜转转Binder.getCallingUid()从Java层最终会调用IPCThreadState.getCallingUid()的Native层:
正如代码所展示的,IPCThreadState.getCallingUid()直接返回mCallingUid字段,写入mCallingUid引用多个方法里,发现在executeCommand()方法:
由上面代码可知,在IPCThreadState.executeCommand()执行解析Binder驱动BR_TRANSACTION的ioctl系统调用命令返回的结果时,mCallingUid赋值为binder_transaction_data的sender_euid,根据名字应该是调用方的uid,下面是Binder非oneway和oneway调用的简单流程图:
从上面2张图可知,Binder非oneway和oneway调用都会执行Binder驱动BR_TRANSACTION的ioctl系统调用,这里看看下面完整的Binder调用的简单流程图,以startService()的Binder调用为例:Binder复杂的程度远远不能就用一张图来说得清楚,这里只是让大家知道个大概的流程,从图中得知,在Kernel层的Binder驱动里的binder_thread_read()方法处理BR_TRANSACTION的ioctl系统调用命令:由上面的流程图可知,binder_thread_read()是从binder_transaction()执行进来的,在它里sender_euid字段被赋值:
如果熟悉Linux kernel进程的管理和调度机制的话,应该就非常熟悉task_struct结构体就是进程描述符了,它包含了一个进程所需的所有信息。因为在Client端发送数据到Service端的场景下,上面代码binder_transaction()传入的参数*proc就是Client端的binder_proc对象,binder驱动层的每一个binder_proc结构体都与用户空间的一个用于binder通信的进程一一对应。
每个App在启动前必须先创建一个进程,该进程是由Zygote进程fork出来,进程具有独立的资源空间,用于承载app上运行的各种Activity/Service等组件,进程在创建时候打开Binder驱动,然后才能进行Binder IPC通讯,如startActivity()调用流程AMS先检测目标进程是否启动,若没则创建目标进程。这里看一下上图的关键方法onZygoteInit()会进入Native层调用AndroidRuntime.cpp注册关联的方法:在AndroidRuntime的头文件中的onZygoteInit()定义为纯虚函数,其子类AppRuntime实现在frameworks/base/cmds/app_process/app_main.cpp里定义:
这里看一下打开Binder驱动的代码:
这样Client进程打开Binder驱动时调用binder_open(),就将当前进程的task_struct进程描述符绑定到了对应的binder_proc的tsk字段里。也就能获取Client进程的uid了。
2.总结
由于笔者水平有限仅给各位提供参考,希望能够抛砖引玉,也欢迎各位在评论区留言交流意见。有同学看到这些觉得难,其实,这并不奇怪,一般写应用界面根本就不会接触到,因为这需要了解Framework基本的知识才行,如Android系统启动的流程、进程的创建、四大组件与AMS的流程。我认为学习Framework层需要具备以下基础:
如果没有以上这2个必备的基础,你在看Binder源码很可能停留在表面,当然前面所说的也只不过是整个Binder的冰山一角而已。学习新东西往往有共性所在,正如你会Jetpack Compose,那么你也会70%的Flutter了。不管看多少书,更重要的是自己思考,动手重复的实践!也许这个过程很耗时间,但是,这个不断以代码去验证自己的某些猜想的过程。
3.构建优雅的Android Kernel内核和AOSP平台代码阅读环境
3.1 阅读AOSP源码的方式
在2023年的当下,笔者不再建议使用Source Insight来阅读本地源码(PS:类似Eclipse开发视感),这里探究用AOSP源码提供的脚本工具生成使用于Android Studio和CLion项目来构建源码环境:
3.2 AOSP源码环境搭建准备工作
3.2.1 系统硬件配置
更多详情可以参考官方资料:
软硬件要求:https://source.android.google.cn/docs/setup/start/requirements?hl=zh-cn3.2.2 Ubuntu安装与配置依赖软件
笔者使用的2016年MacBook Pro 15寸高配版本,故这里以虚拟机方案解决Ubuntu系统问题。
因为Paralles Desktop是收费的,故这里使用VMware Workstation Player个人版免费版本:https://www.vmware.com/cn/products/workstation-player.html,注册VMware账号即可拿到license进行安装。因为Ubuntu官方镜像在国内下载速度较慢,故这里使用阿里云镜像站提供的ubuntu-18.04-desktop-amd64.iso:https://mirrors.aliyun.com/oldubuntu-releases/releases/18.04.5/ubuntu-18.04-desktop-amd64.iso
接下来将ubuntu-18.04-desktop-amd64.iso用VMware Workstation Player导入即可。
如果插上了移动硬盘但虚拟机识别不了,请打开虚拟机设置->usb和蓝牙,选择对应的UBS的协议版本即可:因为笔者的2016年MacBook Pro 15寸硬盘只有512G,但目前可用空间也就200G远远不能满足编译AOSP和Android Kernel的空间大小,所以这里选择外接移动硬盘,注意:要把移动硬盘格式化成Ext4格式
接下来打开终端执行apt命令安装所需要的软件包:
因为生成构建源码至少需要16G+的内存大小,所以在虚拟机调整swap大小:
最后就是下载AndroidStudio和CLion2020版本(PS:2020以后版的较新版本导入生成的Native项目Cmake会构建失败)到opt目录(PS:从snap软件商店下载,后面AIDEgen不能自动识别到它们的安装路径导致不能自动启动导入生成的项目代码),这里以安装CLion为例 (AndroidStudio同理,但安装完需要打开并安装SDK等相关配置) :
1.从CLion官网下载压缩包:https://download.jetbrains.com/cpp/CLion-2020.3.4.tar.gz2.创建opt目录,把CLion压缩包解压到clion-stable。(ps:只有命名为‘clion-stable’才会被AIDEgen会自动识别拉起CLion,而AndroidStudio安装包要解压在opt/android-studio目录)3.启动CLion并创建桌面快捷方式
在完成CLion配置后,随便新建个项目进入主界面,在顶部菜单栏选择即可:Tools -> Create Desktop Entry
3.2.3 下载AOSP分支源码
首先要在终端配置git的用户名和邮箱:
然后就是安装Repo(PS:这是下载源码的工具):参考官方Repo的资料:https://source.android.google.cn/docs/setup/download?hl=zh-cn#repo
将文件中首行的'#!/usr/bin/env python'改为'#!/usr/bin/env python3',这样Repo就以Python3执行,而不会报错或者执行不兼容的Python2。
最后再配置Python3别名和Repo镜像源地址的环境变量:最后就是在移动硬盘创建工作目录进行拉取下载AOSP代码,这里不是拉取master分支,而是android-13.0.0_r41分支,这样拉取单个分支代码占用存储空间会小很多,更多分支参考官网:https://source.android.google.cn/docs/setup/about/build-numbers?hl=zh-cn#source-code-tags-and-builds,笔者在拉过程中网络下载速度平均30M/s,峰值60M/s,可能是清华大学开源软件镜像站限制了网速发挥不了千兆网络宽度的速度,约1小时18分左右就完成下载,但下载完成Repo自动执行checkout代码又花了15分钟左右,整个过程花了1个半小时左右,所以这个时间长短取决于您的网络带宽和电脑的CPU的核心数和硬盘、内存的读写性能。
3.2.4 AOSP的模块概念
AOSP的模块:文件目录下有 Android.bp(Soong构建配置文件) 或者 Android.mk(GUN Make构建配置文件)即可视为模块:
envsetup.sh脚本封装了很多有用的命令,'allmod'查看AOSP所有模块,'pathmod'查看模块在源码的路径,可以执行'hmm'查看更多命令的用法。
3.3 IDEGen生成AOSP源码Java环境(不推荐)
执行上面的命令也是十分的耗时的,当成功后在AOSP代码根目录下可以找到 android.iml 和 android.ipr 两个文件。打开Android Studio,点击File --> Open,选中生成的 android.ipr 文件即可。但是整个源码的体积很大,如果全部导入会非常非常卡顿,可以排除一些代码,可以在android.iml下修改:
也可以在AndroidStudio选择目录手动排除:
最后就是点击顶部菜单栏File -> Project Structure配置项目对应的JDK、SDK了。3.4 Soong生成AOSP源码Native环境(不推荐)
Soong编译模块期间顺带着把Native代码依赖关系记录到一个文件CMakeLists.txt里,上命令执行非常耗时,生成的CMakeLists.txt文件在对应的路径:‘out/development/ide/clion/frameworks/native/libs/binder/libbinder-arm-android/CMakeLists.txt’,其中‘/frameworks/native/libs/binder’是指对应模块在源码的路径,‘libbinder-arm-android’是代表lib<模块名>-架构名-<平台名>意思。更多参考官方Soong文档:https://source.android.google.cn/docs/setup/build?hl=zh-cn
用CLion打开刚生成的CMakeLists.txt即可,打开后目录结构是扁平的,需要修改工程根目录,在顶部菜单栏选择Tools -> CMake -> Change Project Root,以上面为例选择源码frameworks/native/libs/binder目录即可。如果您想引导多个模块,只能手动创建CMakeLists.txt把对应生成模块的CMakeLists.txt添加进来:
3.5 AIDEGen 生成AOSP源码Java\Native环境(推荐)
AIDEGen是在2020开始释放出现在AOSP源码中,帮助开发者自动完成项目在IDE上的模块依赖、SDK、JDK等环境配置,不再需要开发者手动配置,注意的是从Android 10开始支持生成Java项目,而Native项目则从Android 11开始才完全支持,在完成任务后自动启动指定的IDE并导入项目代码。
如果您之前已经使用aidegen生成过模块,可以添加‘-s’跳过部分过程加速任务,更多Aidegen的参数说明如下:
-d
--depth
-i
--ide
-p
--ide-path
-n
--no_launch
-r
--config-reset
-s
--skip-build
-v
--verbose
-a
--android-tree
-e
--exclude-paths
-l
--language
-h
--help
3.6 最佳实践
如果您完成Ubuntu软硬件,AndroidStudio、CLion、Git、Repo、Python配置,到了用Repo拉取源码这最耗时的步骤,首先在Android Studio、CLion的设置受信任项目的路径,这样AIDEgen启动它们是就不弹出这个窗口:
打开IDE设置菜单:Build、Execution、Deployment -> Trusted Locations,设置AOSP路径即可最后执行以下命令就会完成源码拉取、编译依赖、启动IDE的任务,如果您的电脑性能好、宽带网速可以,一觉醒来您就发现可以AndroidStudio、CLion查看Framework源码了。3.7 构建Android Kernel内核源码环境
因为AOSP把Kernel内核分离开,若要查看Kernel层的模块代码(如Binder驱动),则需要单独下载Kernel分支了,而不同的设备有不同的内核分支,这里下载的是通用内核common-android13-5.15分支。更多参考官网:https://source.android.google.cn/docs/setup/build/building-kernels?hl=zh-cn
上面命令执行通过repo拉取代码,编译,然后使用‘gen_compile_commands.py’脚本在根目录生成‘compile_commands.json’的依赖索引文件,直接用CLion打开即可阅读Kernel内核的代码了。
3.8 支持正版软件
VScode在AIGEGen也支持java、Native项目导入,但要安装对应的Java、Camke、C、C++,CQuery插件,相对繁琐并索引代码速度慢,而Kernel内核源码也能通过clangd插件构建索引,总而言之,CLion导入Native项目却非常省心而高效,但CLion是收费的:
1.若资金允许请购买正版:https://www.jetbrains.com.cn/clion/buy/#personal
2.学生凭学生证可免费申请正版授权:https://www.jetbrains.com/shop/eform/students3.创业公司可5折购买正版授权:https://www.jetbrains.com/shop/eform/startup虚拟机软件写错了,不是VMware Workstation Player,是VMware Fusion才对。
图床崩掉了,可以查看掘金文章:https://juejin.cn/post/7250008208159309861
好多的图片都挂了。
不好意思,这个图床是Gitter仓库的连接可能被限制了,您可以查看掘金文章:https://juejin.cn/post/7250008208159309861
server侧进程必然运行在对应的binder线程中,那么不管执行的是静态方法还是动态方法,我们都能从当前binder线程中拿到线程私有的IPCThreadState,那么这便不难了,很容易能想到是从binder建立请求的时候带过来的
对应的调用链client进程:binder_transaction,这里保存了一部分自己的信息 -> 写入到对应binder驱动中server进程:joinThreadPool 启动一个无限循环等待binder驱动数据的逻辑 -> 接收到信息 -> exeuteCommand -> 从中取出调用进程的信息,其中也就包括这里的getCallingUID这个Binder的静态方法会调用到IPCThreadState中,IPCThreadState::self()使用了TLS获取实例,这样每个Binder线程就可以拿到属于自己当前在处理的调用方uid了。
每个Binder线程的调用方uid哪来的呢?当然是调用方传过来的了,Binder驱动层面就定义了 binder_transaction_data 数据结构,里面就包含了调用方的uid、pid、binder命令,数据块大小和偏移等。有没有人能解释一下,这想问什么 ?
想问这个方法咋实现的