首页
社区
课程
招聘
[原创]自定义Android15系统跟踪Java方法
发表于: 2025-4-8 09:38 4739

[原创]自定义Android15系统跟踪Java方法

2025-4-8 09:38
4739

[警告]

仅供学习参考,请勿使用非法用途!

如果你自己根据文章内容将功能实现,你对Android系统的理解将上一个层次。

自定义Android 15(API级别35)操作系统。

改完之后能用5~10年不落伍。

高版本操作系统权限管理更严格,APP的一些操作会被拒绝。

有了更严格的权限校验是优势,我的改造不会去禁用掉SELinux

我通过虚拟机构建,内存至少48GB,硬盘建议分配最大2TB的空间。

如果你自己根据文章内容将功能实现,你对Android系统的理解将上一个层次。

为了性能,只对被跟踪方法或者被跟踪的方法改为解释执行,不将整个APP改为解释执行。

位于runtime/art_method.cc。

这个地方判断是否通过解释器执行:

当调用到我的测试函数的时候,调用到Invoke函数的调用栈:

栈深度0号和1号是进入了自定义函数,不需要贴出来。C/C++获取调用栈是通用逻辑,此处不贴出来,自行搜索。

跟踪方法的效果先看《跟踪Java方法调用》一节。

找到ShadowFrame类,它位于runtime/interpreter/shadow_frame.h。新增两个public字段:

跟踪描述数据结构:

初始化跟踪信息:

当被跟踪方法被检测到需要做跟踪的时候,在当前方法所属的ShadowFrame里面添加传递信息。在下面《代理被跟踪 Java方法》的《改动Execute函数》一节中,在检查方法是否要被跟踪的时候,能够拿到ShadowFrame对象,如果方法需要被跟踪,检查是否需要被跟踪,如果需要,在ShadowFrame对象里面添加“跟踪描述数据结构”,深度是0。

跟踪信息传递:

当调用一个Java方法的时候会创建一个新的ShadowFrame,在创建新的ShadowFrame时候调用函数的ShadowFrame也存在,在这个时候可以做跟踪信息传递,需要改动这几位置:

上面这三个函数都调用了ShouldStayInSwitchInterpreter函数,还需要配合这个函数做改造,请见下一节。

函数位于runtime/interpreter/interpreter_common.cc。这个函数用于判断被调的Java方法通过解释器执行还是通过Java编译完成的机器码执行,对于需要被跟踪的方法,我们要改成解释器执行。

这个函数的源码:

我需要在const void* code = method->GetEntryPointFromQuickCompiledCode();这条语句之前判断是否有跟踪信息,我们的跟踪信息保存在栈帧内,但是函数没有传入栈帧,所以需要将入参从ArtMethod* method改为ShadowFrame& shadow_frame, ArtMethod* method,新增ShadowFrame类型的入参。

更改后,上面三处调用这个函数的地方需要把栈帧传递进来,不同函数传入的栈帧对象不同:

代理被跟踪方法架构

位于runtime/art_method.cc。在上文对这个函数的改造中,对于需要被跟踪的Java方法改成了解释执行,改动完成后会调用到Execute函数(下文会做描述),我在Execute函数内做了跟踪代理,但是如果是从Java层调用到Native函数那么不会进入Execute函数,我希望从Java直接调用到Native的函数也能被检测、代理,本节会讲如何做这件事情。

代码说明:

上面调用代理类、方法,是Java方法,需要将ArtMethod::Invoke的入参uint32_t* args转换成能够通过Jni接口CallObjectMethodV调用的入参,它通过jvalue*接收方法参数数组,jvalue是一个联合类型。

转换说明:

正确的取出参数:如果是解释执行,ArtMethod::Invoke函数会调用到EnterInterpreterFromInvoke函数(位于runtime/interpreter/interpreter.cc),这个函数内解析了args,将它里面的数据设置到了ShadowFrame里面,参数取值参考这段代码。

参数类型转换:

基础类型:取出来后直接赋值到jvalue里面的相应字段即可。

对象类型:它比较复杂,如果你阅读并理解第1步的参数取值代码就会发现,对象取出来的是ObjPtr<mirror::Object>类型,它可以取出来mirror::Object *类型,通过这个代码将它jobject:

参数转换完成后,就可以通过JNI接口调用你自己的代理类、方法了。

我们的代理方法返回对象类型,类型结构如:

CallObjectMethodV调用后返回值是jobject类型,返回值是Java的Result对象,如果useNewValue等于true,我要提取对象里面的value字段并返回,此时在Native层拿到的value是jobject对象。

通过阅读代码可知我需要返回JValue类型,JValue类型内部存储的对象需要调用SetL,入参是ObjPtr<mirror::Object>类型(参考art/runtime/jvalue.h文件)。我需要将jobject转换成ObjPtr<mirror::Object>,转换代码:

位于runtime/interpreter/interpreter.cc。执行到这个函数意味着目前是解释执行。

这个函数内在后面一点的位置会从栈帧内获得ArtMethod对象:ArtMethod *method = shadow_frame.GetMethod();。把这个语句提到函数开头:

在获得ArtMethod *之后做下面逻辑:

这三点在《ArtMethod::Invoke函数内处理跟踪和代理》这一节以及讲过,区别是这个函数内以及为这个被调方法创建了栈帧,我需要在栈帧内取出被调方法参数。栈帧的创建在上文中提到的EnterInterpreterFromInvoke函数内,这个函数内调用的是SetXXX函数向栈帧设置方法参数,我只需要调用相应的GetXXX方法就可以取出。

如果要知道方法参数+返回值,可以在这个函数最后调用ExecuteSwitch之后,做调用代理方法和返回值拦截相关逻辑。

为什么在ArtMethod::Invoke和Execute函数内都要检查一次是否需要跟踪?

ArtMethod::Invoke检查跟踪:

Execute函数:

某个被跟踪方法执行后还直接或间接调用了哪些Java方法、第一个调用到的Native方法。举例:假设被跟踪 Java方法test调用了Java方法test1,test1调用了Native方法nativeTest3,ROM内能够识别出调用了test1和nativeTest3,将test、test1、nativeTest3这三个方法所属类、方法名,参数打印出来,打印test1和nativeTest3的时候会指出它们的调用来自于test1。

上面《将目标方法改为解释执行》的步骤一定要做。

请见《跟踪Java方法调用解释执行》一节,这一节不仅让跟踪方法解释执行,还传递了跟踪标识。

所谓跟踪数据是指要跟踪哪些方法。找到VMRuntime_setProcessPackageName函数,位于runtime/native/dalvik_system_VMRuntime.cc,这个函数设置了包名,我在设置了包名的时候就加载这个包对应的跟踪数据。

找到源码中雷系下面的代码,你看一下系统是怎么遍历并打印Java调用堆栈的:

类似下面的报错:

如果懒得看到底哪个符号找不到,把这段报错放到大模型里面让它给解析出来。这段报错是ArtMethod的一些方法找不到符号,依赖了#include "art_method.h"头文件后如果还报此类错误,那么需要依赖#include "art_method-inl.h"-inl.h结尾的头文件存放所有inline函数的实现,如果你调用到了inline函数,不能只依赖函数定义,必须要依赖实现。

我自定义ROM里面自定义了AIDL,AIDL生成的代码也会被代码质量问题检测Lint工具报错,AIDL是不能添加诸如:@SuppressLint、@NonNull这样的注解的。遇到这样的问题有几种解法:

tools/metalava/metalava/src/main/java/com/android/tools/metalava/lint/ApiLint.kt

checkFlaggedApiOnNewApi函数把report语句注释掉。

需要重新生成lint-baseline.txt文件。

Lint报错后会在后面显示类似下面的提示:

上面的第2点提示了如何重新生成基线文件(lint-baseline.xml)。运行完成后,重新编译系统。

lint-baseline.txt 的作用与局限性

如果不修改SELinux规则,注册服务的时候将会在logcat打印出这样的错误:

进入system/sepolicy/private/system_server.te文件,添加allow system_server my_customer_system_service:service_manager add;,如果你添加到最后,注意最后一行必须是空行。

如果不修改SELinux规则,获得服务的时候将会在logcat打印出这样的错误:

打开system/sepolicy/private/service_contexts文件,添加:

这行语句不要添加到文件最后面,我添加到文件最后面的时候出现了编译失败。

在service_contexts文件的相同目录下新增文件my_customer_system_service.te,内容:

注意,最后一行必须是空行。

在SELinux规则里面新增服务编译报错:

打开service_fuzzer_bindings.go文件,ServiceFuzzerBindings列表里面新增:

我没有放到最后,我是在列表里面插入了一条。

if (UNLIKELY(!runtime->IsStarted() ||
           (self->IsForceInterpreter() && !IsNative() && !IsProxyMethod() && IsInvokable()))) {
art::ArtMethod *artMethod = xxx;

std::string className = artMethod->GetDeclaringClass()->PrettyDescriptor();
const char *methodName = artMethod->GetName();
std::string signature = artMethod->GetSignature().ToString();
#02  0x7344c8a508  art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)
#03  0x7344e6316c  bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)
#04  0x734506c58c  
#05  0x7345157fdc  
#06  0x7344cbbdf8  
#07  0x7344cbb534  
#08  0x7344e6316c  bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)
#09  0x734506c58c  
#10  0x7345157fdc  
#11  0x7344cbbdf8  
#12  0x7344cbb534  
#13  0x7344e6316c  bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)
#14  0x734506c58c  
#15  0x7345157fdc  
#16  0x7344cbbdf8  
#17  0x7344cbb534  
#18  0x7344e6316c  bool art::interpreter::DoCall<false>(art::ArtMethod*, art::Thread*, art::ShadowFrame&, art::Instruction const*, unsigned short, bool, art::JValue*)
#19  0x734506c58c  
#20  0x7345157fdc  
#21  0x7344cbbdf8  
#22  0x7344cbbb14  
#23  0x7344cbab90  
#24  0x7345155a3c  
  跟踪描述数据结构 *traceObj = nullptr; // 最好用智能指针,这样不会出现内存泄漏的问题

  /**
   * 执行跟踪深度,要值传递,不能放到"跟踪描述数据结构"里面
   */
   int depth = 0;
class 跟踪描述数据结构 {
public:
    // true: 继续跟踪; false: 不继续跟踪
    bool enable = false;

    // 还可以添加起始跟踪的类名、方法名、方法签名。这些数据在后续跟踪方法的日志打印中可以一起打印出来,这样就知道是从哪里开始的
    ......
};
bool ShouldStayInSwitchInterpreter(ArtMethod* method)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  if (!Runtime::Current()->IsStarted()) {
    // For unstarted runtimes, always use the interpreter entrypoint. This fixes the case where
    // we are doing cross compilation. Note that GetEntryPointFromQuickCompiledCode doesn't use
    // the image pointer size here and this may case an overflow if it is called from the
    // compiler. b/62402160
    return true;
  }

  if (UNLIKELY(method->IsNative() || method->IsProxyMethod())) {
    return false;
  }

  if (Thread::Current()->IsForceInterpreter()) {
    // Force the use of interpreter when it is required by the debugger.
    return true;
  }

  if (Thread::Current()->IsAsyncExceptionPending()) {
    // Force use of interpreter to handle async-exceptions
    return true;
  }

  const void* code = method->GetEntryPointFromQuickCompiledCode();
  return Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(code);
}
NO_STACK_PROTECTOR
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
                       const char* shorty) {
  ......

  Runtime* runtime = Runtime::Current();
  // Call the invoke stub, passing everything as arguments.
  // If the runtime is not yet started or it is required by the debugger, then perform the
  // Invocation by the interpreter, explicitly forcing interpretation over JIT to prevent
  // cycling around the various JIT/Interpreter methods that handle method invocation.
  ////////////////////////////////////////////////////////////////////////////////////
  // 判断是否是有个有效的Java方法
  bool isValidJavaMethod = !IsNative() && !IsProxyMethod() && IsInvokable();
  auto isInterpreter = UNLIKELY(!runtime->IsStarted() ||
           (self->IsForceInterpreter() && isValidJavaMethod));
  if (这个APP是否对部分方法添加了跟踪) {
    // 这个APP对部分方法添加了跟踪
    
    /*
     为什么先判断跟踪再判断是否要解释执行?
     是因为对于在Java层注册的Native方法,也需要判断是否要被跟踪
     */
      
    // 检查这个方法是否需要被跟踪

    // 如果需要,那么将isInterpreter设置为true继续执行,它会执行到Execute函数内,其中会判断是否需要代理类并且做相关的逻辑
  }
  ////////////////////////////////////////////////////////////////////////////////////
  if (isInterpreter) {
    if (IsStatic()) {
      art::interpreter::EnterInterpreterFromInvoke(
          self, this, nullptr, args, result, /*stay_in_interpreter=*/ true);
    } else {
      mirror::Object* receiver =
          reinterpret_cast<StackReference<mirror::Object>*>(&args[0])->AsMirrorPtr();
      art::interpreter::EnterInterpreterFromInvoke(
          self, this, receiver, args + 1, result, /*stay_in_interpreter=*/ true);
    }
  } else {
    ......

      ////////////////////////////////////////////////////////////////////////////////////

      bool isUseNewValue = false;
      if (IsNative()) {
        // 是Natvie方法,而不是Java方法执行本地编译好的机器码

        if (是否需要被跟踪,上面判断过,直接复用) {
          JValue newValue;
          bool useNewValue = false;
          
          // 检查需要被跟踪的方法是否有代理类
         // 被跟踪方法有代理类的情况下,调用解析方法入参,将入参转换成jvalue数组,通过CallObjectMethodV调用到代理类,然后能获得返回对象。
         处理跟踪(被跟踪方法相关信息, &newValue, useNewValue); 
          if (useNewValue) {
            // 需要被拦截,设置新的值
            *result = newValue;
            isUseNewValue = true;
          }
        }
      }

      if (!isUseNewValue) {
        // 使用新值的情况下,不调用原方法

        if (!IsStatic()) {
          (*art_quick_invoke_stub)(this, args, args_size, self, result, shorty);
        } else {
          (*art_quick_invoke_static_stub)(this, args, args_size, self, result, shorty);
        }
        ......
      }
      
      // 也可以在Native方法执行完成后调用代理方法做拦截
    } else {
      LOG(INFO) << "Not invoking '" << PrettyMethod() << "' code=null";
      if (result != nullptr) {
        result->SetJ(0);
      }
    }
  }

  // Pop transition.
  self->PopManagedStackFragment(fragment);
}

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 6天前 被不歪编辑 ,原因: 修改文章问题
收藏
免费 7
支持
分享
最新回复 (6)
雪    币: 28
活跃值: (1471)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
看看,感谢分享。
2025-4-8 15:01
0
雪    币: 3281
活跃值: (12500)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
按照文章说的代理方法改变被跟踪方法的返回值,这实现了hook一样效果吗
2025-4-9 09:26
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
有些函数是不经过ArtMethod::Invoke的吧
2025-9-26 18:01
0
雪    币: 308
活跃值: (914)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
5
自己能 有些函数是不经过ArtMethod::Invoke的吧
在Invoke或者你认为合适的时机强制解释执行:
    bool setThreadForceInterpreter(art::Thread *self) {
        if (self == nullptr) {
            return false;
        }

        uint32_t force_interpreter_count = self->ForceInterpreterCount();
        if (force_interpreter_count <= 0) {
            art::MutexLock thread_list_mu(self, *art::Locks::thread_list_lock_);
            self->IncrementForceInterpreterCount();
        }

        return true;
    }
2025-10-8 16:29
0
雪    币: 1549
活跃值: (4718)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
6
123
2026-3-30 10:15
0
雪    币: 141
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
123
4天前
0
游客
登录 | 注册 方可回帖
返回