Android bpf简单上手教程

2023-11-18

背景

    业界对Android的性能监控、hook、系统分析、抓包、逆向等领域的关注和投入在不断提升,作为Linux Kernel中新兴的优美的一套技术框架,bpf逐渐在Android中被用于监控、分析、优化和逆向,产出了众多的工具。此外,Google原生系统的上层服务(如网络流量统计、网络黑白名单)也转向使用ebpf来实现。

    bpf可以理解成Kernel的虚拟机字节码,它借助C语言编译成特定的ELF,通过专用的系统调用注册到Kernel,挂载到指定的tracepoint,进行代码流程的hook,与App的插桩、性能优化的检查点非常类似。

    本文以Android源码AOSP为基础,讲解一个bpf程序应该如何被编译、集成到系统、在系统中启动/运行,并使用C语言编写了一个可以实际运行的bpf程序(https://github.com/NasdaqGodzilla/cpuprobe)。

编译

    常规的C语言bpf程序需要编译为Kernel BPF子系统能够识别的专用字节码,Android采用了一套独特的编译系统(Android.bp)来包装包含bpf程序在内的编译流程。

    一个最简单的bpf编译描述如下:

bpf {
    name: "cpu_stats.o",
    srcs: ["cpu_stats.c"],
    cflags: [
        "-Wall",
        "-Werror",
    ],
}

    这段写在Android.bp内的bpf代码声明了需要编译一个名称为cpu_stats.o的bpf程序,srcs指定了其源文件,cflags指定了向编译器传递的编译参数。

    前面说过,一个bpf程序,从结构上说是一个elf,从逻辑上说是一个被Kernel BPF虚拟机加载后才能执行的字节码(类似JS解释器加载JS),因此它编译期间不会发生链接,不会产生可执行bin文件,也不能直接执行。

    通过AOSP的模块编译指令即可编译:

m cpu_stats.o

集成

    常规的bpf程序有多种集成方式,包括调用bpf()、通过python、bpftrace等(本质也是bpf())调用。但出于安全和效率的原因,Android屏蔽了bpf(),仅提供了bpf loader在开机阶段加载预置的bpf程序,在开机后,系统就不能再加载任何bpf程序了。

    bpf loader会加载存放在/system/etc/bpf目录下的所有bpf程序,因此将我们编译出来的bpf程序放入该路径(或通过Android.bp、Android.mk等预置)即可让系统自动为我们加载我们的bpf程序:

adb push cpu_stats.o /system/etc/bpf
adb push cpu_stats_client /system/bin/

    这里我们还push了一个cpu_stats_client,稍后解释它的作用。

    push后,重启设备,让Android自动加载这个bpf程序,加载成功的话日志大概长这样:

LibBpfLoader: Loading ELF object /system/etc/bpf/cpu_stats.o with license GPL
LibBpfLoader: Loaded code section 3 (tracepoint_sched_sched_switch)
LibBpfLoader: Loaded relo section 3 (.reltracepoint/sched/sched_switch)
LibBpfLoader: Adding section 3 to cs list
LibBpfLoader: bpf_create_map name cpu_stats_map, ret: 27
LibBpfLoader: map_fd found at 0 is 27 in /system/etc/bpf/cpu_stats.o
LibBpfLoader: applying relo to instruction at byte offset: 144,               insn offset 18 , insn 118
LibBpfLoader: New bpf core prog_load for /system/etc/bpf/cpu_stats.o (tracepoint_sched_sched_switch) returned: 28
bpfloader: Attempted load object: /system/etc/bpf/cpu_stats.o, ret: Success

启动

    系统启动时,会自动把上述目录的bpf程序交给内核进行加载。内核为了安全,会进行字节码分析,检测待加载的bpf程序是否带有Bug如越界、空指针、死循环等,以此来排除崩溃风险和恶意攻击。如果程序没有什么问题,就会成功加载。

    加载成功的一个标志是,bpf程序定义的map(bpf的数据结构,稍后解释)会出现在指定目录下:

local@device:/ # ls /sys/fs/bpf/map_cpu_stats_cpu_stats_map
/sys/fs/bpf/map_cpu_stats_cpu_stats_map

    这个map由bpf程序在代码里面通过函数定义得到,当bpf程序hook命中时,Kernel会回调bpf程序,bpf程序通常将其业务逻辑中产生的需要存储的数据保存在这个map里面。这个map的两大主要作用,一是让bpf程序能够保存数据(因为bpf程序即不能使用内核的栈、堆,也不能使用被hook命中的应用程序的栈、堆),二是这个map可以让应用程序读取到,是一种bpf程序向应用程序进行通信的方式,bpf程序可以往这个map写东西,向用户态应用程序通信。

程序解释

    从bpf程序本身的结构来看,bpf程序主要有两大部分组成:1. 指定的hook点以及这个hook点的处理函数,用于hook;2. 定义的bpf map,用于存储数据。

    执行hook并保存数据

    1. 通过DEFINE_BPF_MAP来创建bpf map

    2. 通过SEC()来指定hook点,当kernel对应事件发生时,会回调SEC指定的函数

    3. SEC指定的回调函数内执行hook逻辑,可以向bpf map读写数据,用于存储数据,或用于向用户态应用程序传递信息

DEFINE_BPF_MAP(cpu_stats_map, LRU_HASH, CPU_STATS_BPFMAP_KEY, CPU_STATS_BPFMAP_VALUE, CPU_STATS_MAXENTRIES);

// DEFINE_BPF_PROG("tracepoint/sched/sched_switch", AID_ROOT, AID_NET_ADMIN, cpu_stats_tp_sched_switch) (struct switch_args* args) {
SEC("tracepoint/sched/sched_switch")
int cpu_stats_tp_sched_switch(struct switch_args* args) {
    CPU_STATS_BPFMAP_KEY ktime_ns = bpf_ktime_get_ns();
    CPU_STATS_BPFMAP_VALUE record = {
        .ktime_ns = ktime_ns,
        .smp_id = bpf_get_smp_processor_id(),
        .uid_gid = bpf_get_current_uid_gid(),
        .pid_tgid = bpf_get_current_pid_tgid(),
    };
    memset(record.comm, '\0', sizeof(record.comm));
    memcpy(record.comm, args->next_comm, sizeof(record.comm));

    bpf_cpu_stats_map_update_elem(&ktime_ns, &record, BPF_ANY);

    return 0;
}

char _license[] SEC("license") = "GPL";

    读取数据

    前面“集成”小节里面示例命令有将一个cpu_stats_client push到/system/bin,实际上它就是用来读取数据的工具。

    1. 通过bpf_obj_get()来取得bpf程序和bpf map的句柄(不是使用常规的fd open)

    2. 通过android::bpf::BpfMap来遍历bpf map里面的所有数据(Key-Value对)

    可以访问文章开头的Github链接查看完整代码

总结

    bpf提供了强大的功能,凭借数量众多的tracepoint和kprobe,绝大多数位置都能提供hook,在不修改内核的情况下能实现非常精彩的业务逻辑,在Android上必然能发挥重要作用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android bpf简单上手教程 的相关文章

  • Android 在画布上遮罩位图生成黑色空间

    我有一个蒙版位图 一半是红色 一半是透明的 如下所示https www dropbox com s 931ixef6myzusi0 s 2 png https www dropbox com s 931ixef6myzusi0 s 2 pn
  • 如何使用 WifiEnterpriseConfig 设置“使用系统证书”

    我想为我的应用程序的用户配置企业 WiFi 网络 因此 用户必须输入用户名和密码 其余的由应用程序处理 身份验证由 RADIUS 服务器 FreeRadius 3 处理 该服务器使用 LetsEncrypt 颁发的证书 因此无需导入我自己的
  • 位图背景图像应支持哪些屏幕尺寸/密度组合?

    我正在编写一个应用程序 我需要一些全屏位图背景 基于我天真的阅读支持多屏 http developer android com guide practices screens support html在 Android 文档中 为了涵盖我的
  • 包管理器已去世

    我收到一位安装了很多应用程序的用户发来的邮件 称当我的应用程序使用以下代码收集活动信息时 他遇到了问题 getPackageManager queryIntentActivities mAinIntent 0 完整来源在这里 https g
  • 使用busybox在后台安装apk

    我可以在 root 设备上使用 busybox 在后台安装 apk 吗 我看到类似的东西 但它不起作用 process install CommandCapture command new CommandCapture 0 chmod 77
  • Twowayview 滚动时自动添加内边距

    我在用双向视图 https github com lucasr twoway view在我的一个项目中 这是android的扩展回收者视图 https developer android com reference android supp
  • 如何在 Picasso 中使用磁盘缓存?

    我正在使用 Picasso 在我的 Android 应用程序中显示图像 load image This is within a activity so this context is activity public void loadIma
  • Android 片段之间的阴影分隔符

    我有一个类似于平板电脑的 ICS Gmail 应用程序的布局 ListFragment左边是内容 右边是内容 我想知道如何构建布局 使两个片段之间有一个阴影分隔符 就像在 Gmail 应用程序中一样 如下所示 另外 由于这适用于这个问题 我
  • 如何在Android中访问现有的sqlite数据库?

    到目前为止 我们已经在 Android 中开发了在运行时创建数据库的应用程序 我们想知道如何在 Android 应用程序中访问预构建或现有的数据库 sqlite 文件 请提供详细信息 查看文档android database sqlite
  • 我如何从android中的复选框获取值

    我想获取复选框中所选项目的名称 但我只得到一些字母数字 如何从复选框中获取所选项目的名称 public View getView int position View convertView ViewGroup parent View vie
  • startActivityForResult中的requestCode是什么意思

    我想知道我是否正确理解 requestCode 的概念 这个整数的用途是什么 我将其设置为哪个整数有关系吗 private static int CAMERA REQUEST 谢谢 requestCode 可帮助您识别您从哪个 Intent
  • Android ViewModel LiveData 在按钮单击时更新视图

    我正在关注这个tutorial https developer android com topic libraries architecture guide html common problems faced by app develop
  • Android:如果任务管理器终止,则重新调用应用程序

    如果应用程序线程被任务管理器杀死 则应用程序线程将关闭 需要重新调用应用程序 就像它被其他应用程序或任务管理器杀死一样 任何想法 您必须使用 START STICKY 命令运行后台服务 只需扩展 Service 并重写 onCommand
  • 如何使用共享首选项在两个 Android 应用程序之间共享数据?

    我有两个应用程序 App1 和 App2 我想使用共享首选项在 App1 中保存数据并在 App2 中访问 反之亦然 我可以在 App1 中保存数据并在 App2 中访问数据 但反之则不行 这就是我现在正在做的 在清单中 android s
  • 可用屏幕的尺寸

    我使用的是 Nexus 7 1280x800 android 4 2 2 API 17 我想获取屏幕的大小 将其划分为相同高度和宽度的正方形部分 我正在使用 FrameLayout 我的方块是 ImageView 的子类 我这样做 cont
  • 在循环中按名称访问变量

    我正在开发一个 Android 项目 并且有很多可绘制对象 这些绘图的名称都类似于icon 0 png icon 1 png icon 100 png 我想将这些可绘制对象的所有资源 ID 添加到整数 ArrayList 中 对于那些不了解
  • 在 OpenGL 中渲染纹理 1 到 1

    所以我想做的是使用 OpenGL 和 C 将纹理渲染到平面上 作为显示图像的一种方式 但是我需要确保在渲染纹理时没有对纹理进行任何处理 抗锯齿 插值 平滑 模糊等 这是 OpenGL 处理渲染纹理的默认方式吗 或者是否需要设置一些标志才能禁
  • 按“重置应用程序首选项”后,我的应用程序的所有权限都被撤销

    我开发了一个应用程序 支持Android 6 0 当我在 设置 gt 应用程序 gt 重置应用程序首选项 中重置应用程序首选项时 我的应用程序的所有权限都将被撤销 并且应用程序不会重新启动 撤销权限后未能重新启动应用程序可能会导致许多意外崩
  • android 中的 java.net.URL ..新手问题

    我是java新手 正在尝试android开发 以下代码生成 malformedURLException 有人可以帮助我识别异常吗 任何提示都会非常有帮助 package com example helloandroid import and
  • Android - 如何简单地拖放按钮?

    我在这里找到了一个适合初学者的教程 http androidrox wordpress com 2011 05 13 android sample app drag and drop image using touch http andro

随机推荐

  • 【React】路由(详解)

    目录 单页应用程序 SPA 路由 前端路由 后端路由 路由的基本使用 使用步骤 常用组件说明 BrowserRouter和HashRouter的区别 路由的执行过程 默认路由 精确匹配 Switch的使用 重定向路由 嵌套路由 向路由组件传
  • 计算机网络体系结构 - 运输层

    一 运输层协议概述 运输层为应用进程之间提供端到端的逻辑通信 二 运输层的端口 端口 port 也称为协议端口号 protocol port number 对上层的应用进程进行标识 端口用一个16位端口号进行标志 端口号只具有本地意义 端口
  • 剑指offer-输出字符串所有种类的排列组合

    常规题 先校验长度 不符合则直接输出 符合则判断是否为最后一个字符 是则直接new对象输出 不是则交换begin和i位置的数字 再用递归输出 public class Test28 先校验 public static void permut
  • 笔试

    文章目录 前言 40 复位电路设计 1 recovery time和removal time 2 同步复位和异步复位 3 异步复位同步释放 本文参考 往期精彩 前言 嗨 今天来学习复位电路设计相关问题 微信关注 FPGA学习者 获取更多精彩
  • cec2017(python):红狐优化算法(Red fox optimization,RFO)求解cec2017

    一 红狐优化算法 红狐优化算法 Red fox optimization RFO 由Dawid Po ap和 Marcin Wo niak于2021年提出 该算法模拟了红狐的狩猎行为 具有收敛速度快 寻优精度高等优势 参考文献 Poap D
  • easyexcel读取excel将数据存到mysql【一个简单的例子】

    读取excel 1 xml里面增加maven
  • 使用Java程序向手机发送短信

    JAVA发送手机短信 有几种方法 1 使用webservice接口发送手机短信 这个可以使用sina提供的webservice进行发送 需要进行注册 2 使用短信mao的方式进行短信的发送 这种方式应该是比较的常用 前提是需要购买硬件设备
  • 变分推断

    一 概述 对于概率模型来说 如果从频率派角度来看就会是一个优化问题 从贝叶斯角度来看就会是一个积分问题 从贝叶斯角度来看 如果已有数据 x x x 对于新的样本 x hat x
  • 转债打新监听

    不炒股 只打新捡点小钱 package com github niefy modules job run import cn hutool core convert Convert import cn hutool core util Nu
  • 软件测试基础知识个人笔记

    一 为什么要测试 软件系统越来越成为生活中不可或缺的一部分 缺陷不可避免 软件不正确执行可能会导致很多问题 二 软件测试的定义 软件测试 描述一种用来促进鉴定软件的正确性 完整性 安全性和质量的过程 维基百科 公司主流观点 测试是一个包含计
  • eclipse注释模板

    eclipse注释模板 一 配置步骤 Eclipse中 gt Window gt Preferences gt Java gt Code Style gt Code Templates 二 模板示例 Files 文件 Title file
  • java 多线程执行时间测试,TestNG中实现多线程并行,提速用例的执行时间

    TestNG是一个开源自动化测试工具 TestNG源于Junit 最初用来做单元测试 可支持异常测试 忽略测试 超时测试 参数化测试和依赖测试 除了单元测试 TestNG的强大功能让他在接口和UI自动化中也占有一席之地 以Java为例 目前
  • Spring源码之Bean的生命周期

    Spring已经成为了目前最流行的第三方开源框架之一 我们在充分享受Spring IOC容器带来的便捷时 也应该考虑一下Spring这个大工厂是如何将一个个的Bean生产出来的 我们一起来讨论一下Spring中Bean的生命周期 Sprin
  • 内容提供者ContentProvider和内容解析者ContentResolver

    简介 ContentProvider 在android中的作用是对外共享数据 也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问 其他应用可以通过ContentProvider 对你应用中的数据进行添删改查 关
  • 微信小程序第三篇:获取页面节点信息

    文章目录 获取节点信息 createSelectorQuery selectViewport boundingClientRect fields 获取节点信息 createSelectorQuery wx createSelectorQue
  • MySQL数据库基础学习小终结:连接查询、表结构的修改、约束条件、MySQL与python交互

    目录 一 连接查询 1 内连接 2 外连接 二 表结构的修改 alter 1 修改表名 2 修改字段名 3 修改字段类型 modify 4 添加字段 1 添加日期字段 datetime 2 enum字段 5 删除字段 三 约束条件 1 默认
  • <artifactId>mysql-connector-java</artifactId>

    加载MySQL驱动程序 Class forName com mysql cj jdbc Driver 建立连接 String url jdbc mysql localhost database name String username us
  • CAS乐观锁使用AtomicStampedReference版本号控制手动实现原子计数

    原子计数可以直接使用AtomicInteger 下面采用AtomicStampedReference的版本号控制原子操作解决ABA问题 最终结果一定最大的是200000 1A gt 2B gt 3A public class AtomicR
  • Nginx+uWSGI+Supervisor配置

    目录 一 Nginx 1 Nginx是什么 Nginx的特点 Nginx的事件处理机制 Nginx的内部 进程 模型 Nginx是如何处理一个请求 2 部署nginx 安装nginx nginx操作 测试nginx是否能用 配置nginx
  • Android bpf简单上手教程

    背景 业界对Android的性能监控 hook 系统分析 抓包 逆向等领域的关注和投入在不断提升 作为Linux Kernel中新兴的优美的一套技术框架 bpf逐渐在Android中被用于监控 分析 优化和逆向 产出了众多的工具 此外 Go