heapprofd

. Heapprofd 工具如何抓取内存信息

环境配置:

1.需要安装 python3

2.下载 heap_profile 工具,下载链接:

https://github.com/google/perfetto

工具目录: perfetto\tools\heap_profile

3.如果系统是userdebug版本,则可以抓取所有应用以及系统进程的内存信息,如果系统是user版本,则需要抓取的应用是userdebug版本,或者是应用在 manifest 中开启了 profileable 标记

   

   

抓取步骤:

执行命令: python d:\code\perfetto-master\tools\heap_profile -n 进程名

第一次执行的时候,工具会去google的网站下载 traceconv.exe 工具,但是这个下载需要FQ下载,建议手动使用如下地址下载好这个工具,下载地址为:

https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v32.1/windows-amd64/traceconv.exe

然后将 traceconv.exe 放到 C:\Users\huanghan3\.local\share\perfetto\prebuilts 目录下面即可

   

连续抓取步骤:

方式一:执行命令: python d:\code\perfetto-master\tools\heap_profile -c 5000 -n 进程名

方式二:执行命令: python d:\code\perfetto-master\tools\heap_profile -n 进程名,开启另一个窗口执行如下 命令,每执行一次就可以抓取一次内存信息

adb shell killall -USR1 heapprofd

 

备注:还有其他两种方式抓取 heapprofd,一个是使用纯命令方式,类似如用命令方式抓取trace的方式;另一个是使用 https://ui.perfetto.dev/ 网站方式来抓取 heapprofd 来抓取信息

   

抓取的内存信息分析:

使用 https://ui.perfetto.dev/  打开raw-trace文件即可,显示界面如下:

   

   

   

. Heapprofd 工具特性及其实现原理

heapprofd具有如下特性:

1.不用特别设置,只需要一个命令就可以生成需要的信息

2.可以抓取正在运行的进程,在抓取时,进程不需要重启

3.可以抓取应用进程的native的内存信息

4.在不抓取信息时,对系统性能完全没有影响

5.可以抓取系统上所有的进程信息

6.没有或者极少的额外进程内存开销

7.对系统性能影响极小

   

heapprofd 实现原理:

heapprofd既然也是一款监控内存的工具,那么也是需要代理-堆栈回溯-缓存记录三个模块的

heapprofd的代理部分采用的是hook malloc、calloc、realloc、free 等内存分配相关的函数,在系统中

建立一块共享内存buffer,然后按照百分比例进行采样,将进程中的内存申请堆栈复制到共享内存buffer里面,接着使用 libunwindstack 进行堆栈的回溯,最后根据回溯的信息构建一个 Bookkeeping 来跟踪内存分配

   

   

heapprofd vs malloc_info() vs RSS

heapprofd 能够提供从默认C/C++分配器请求的内存具体大小值。对于java应用程序启动时早期的初始化发生的分配是不会记录的,另外从Zygote fork的native 服务也是不会记录的。

   

malloc_info 是一个 libc 函数,它为您提供有关分配器的信息,在 userdebug 版本中通过使用命令:

am dumpheap -m <PID> /data/local/tmp/heap.txt

可以获取heap信息, 这种记录的数据通常会比 heapprofd 看到的内存多,多的部分一个是 before zygote init,另一个是 thread caches 保留的部分free 内存

   

Heap RSS 是分配器从操作系统申请的内存量。 这比前两个数字大,因为只能以页面大小的块获取内存,有些申请的内存没有页面大,也是按页进行申请的, 这可以通过运行 adb shell dumpsys meminfo <PID> 并查看"Private Dirty"列来获得。 如果设备内核使用ZRAM,并且进程的内存换出到 ZRAM,RSS 最终也可能比其他两个小。

三者统计内存的总结如下表:

   

   

. Heapprofd 源码分析

heapprofd 的初始化有两条线路,一条是在 libc 初始化的时候进行 heapprofd 初始化, 如果要抓取的进程还没启动,则使用这条线路, 另一条是在收到要初始化的信号时,在下一次malloc的时候进行heapprofd的初始化,如果进程已经起来了,则使用的是这条线路。 两条线路最终都是通过 CommonInstallHooks 去做 heapprofd 的代理 hook 操作

线路1:

在 libc malloc 初始化时会去判断需要加载哪些 hook 模块,这里有malloc debug, heapprofd等模块的加载判断

// Initializes memory allocation framework once per process.

static void MallocInitImpl(libc_globals* globals) {

  char prop[PROP_VALUE_MAX];

  char* options = prop;

  MaybeInitGwpAsanFromLibc(globals);

#if defined(USE_SCUDO)

  __libc_shared_globals()->scudo_stack_depot = __scudo_get_stack_depot_addr();

  __libc_shared_globals()->scudo_region_info = __scudo_get_region_info_addr();

  __libc_shared_globals()->scudo_ring_buffer = __scudo_get_ring_buffer_addr();

  __libc_shared_globals()->scudo_ring_buffer_size = __scudo_get_ring_buffer_size();

#endif

  // Prefer malloc debug since it existed first and is a more complete

  // malloc interceptor than the hooks.

  bool hook_installed = false;

// 如果有设置环境选项 LIBC_DEBUG_MALLOC_OPTIONS 或者有设置 libc.debug.malloc.options 则表示malloc debug功能开启,

// 加载 libc_malloc_debug.so

  if (CheckLoadMallocDebug(&options)) {

    hook_installed = InstallHooks(globals, options, kDebugPrefix, kDebugSharedLib);

// 如果有设置环境选项 LIBC_HOOKS_ENABLE 或者有设置 libc.debug.hooks.enable 则表示需要 hook 相关的函数

  } else if (CheckLoadMallocHooks(&options)) {

    hook_installed = InstallHooks(globals, options, kHooksPrefix, kHooksSharedLib);

  }

// 如果 hook_installed 为 false,则需要判断是否需要加载 heapprofd

  if (!hook_installed) {

    if (HeapprofdShouldLoad()) { // 如果返回值为true,则加载 heapprofd_client.so

      HeapprofdInstallHooksAtInit(globals);

    }

  } else {

    // Record the fact that incompatible hooks are active, to skip any later

    // heapprofd signal handler invocations.

    HeapprofdRememberHookConflict();

  }

}

   

判断是否要加载 heapprofd 模块

  bool HeapprofdShouldLoad() {

    // First check for heapprofd.enable. If it is set to "all", enable

    // heapprofd for all processes. Otherwise, check heapprofd.enable.${prog},

    // if it is set and not 0, enable heap profiling for this process.

    char property_value[PROP_VALUE_MAX];

    // 如果没有读取到 heapprofd.enable 属性值,则返回 false, 系统默认是没有 heapprofd.enable 属性的,在使用工具抓取heaprofd后,

    // 就会设置 heapprofd.enable 1,抓取完后,就会设置 heapprofd.enable 为空

    if (__system_property_get(kHeapprofdPropertyEnable, property_value) == 0) {

      return false;

    }

    // 如果读取 heapprofd.enable 的属性值为 all , 则返回 true

    if (strcmp(property_value, "all") == 0) {

      return true;

    }

   

    char program_property[kHeapprofdProgramPropertyPrefixSize + kMaxCmdlineSize];

    // 根据 /proc/self/cmdline 读取的信息来获取监控的进程名,比如我在cmd中输入的命令是监控的 surfaceflinger

    // 则获取到的属性是 heapprofd.enable.surfaceflinger

    if (!GetHeapprofdProgramProperty(program_property,

                                     sizeof(program_property))) {

      return false;

    }

    // heapprofd 获取进程的时候,会将 heapprofd.enable.surfaceflinger 置为 1

    if (__system_property_get(program_property, property_value) == 0) {

      return false;

    }

    // 返回 true

    return property_value[0] != '\0';

  }

   

   

加载 heapprofd_client.so

static void CommonInstallHooks(libc_globals* globals) {

  void* impl_handle = atomic_load(&gHeapprofdHandle);

  // gHeapprofdHandle 初始化值为 nullptr,也就是第一次需要加载 heapprofd_client.so

  if (impl_handle == nullptr) {

    impl_handle = LoadSharedLibrary(kHeapprofdSharedLib, kHeapprofdPrefix, &globals->malloc_dispatch_table);

    if (impl_handle == nullptr) {

      return;

    }

    atomic_store(&gHeapprofdHandle, impl_handle);

  } else if (!InitSharedLibrary(impl_handle, kHeapprofdSharedLib, kHeapprofdPrefix, &globals->malloc_dispatch_table)) {

    return;

  }

  FinishInstallHooks(globals, nullptr, kHeapprofdPrefix);

}

   

线路2:

   

__attribute__((constructor(1))) static void __libc_preinit() {

  // The linker has initialized its copy of the global stack_chk_guard, and filled in the main

  // thread's TLS slot with that value. Initialize the local global stack guard with its value.

  __stack_chk_guard = reinterpret_cast<uintptr_t>(__get_tls()[TLS_SLOT_STACK_GUARD]);

  __libc_preinit_impl();

}

   

   

__attribute__((noinline))

static void __libc_preinit_impl() {

#if defined(__i386__)

  __libc_init_sysinfo();

#endif

  // Register libc.so's copy of the TLS generation variable so the linker can

  // update it when it loads or unloads a shared object.

  TlsModules& tls_modules = __libc_shared_globals()->tls_modules;

  tls_modules.generation_libc_so = &__libc_tls_generation_copy;

  __libc_tls_generation_copy = tls_modules.generation;

  __libc_init_globals();

  __libc_init_common();

  __libc_init_scudo();

#if __has_feature(hwaddress_sanitizer)

  // Notify the HWASan runtime library whenever a library is loaded or unloaded

  // so that it can update its shadow memory.

  // This has to happen before _libc_init_malloc which might dlopen to load

  // profiler libraries.

  __libc_shared_globals()->load_hook = __hwasan_library_loaded;

  __libc_shared_globals()->unload_hook = __hwasan_library_unloaded;

#endif

  // Hooks for various libraries to let them know that we're starting up.

  __libc_globals.mutate(__libc_init_malloc);

  // Install reserved signal handlers for assisting the platform's profilers.

  // libc 初始化 profiling

  __libc_init_profiling_handlers();

  __libc_init_fork_handler();

  __libc_shared_globals()->set_target_sdk_version_hook = __libc_set_target_sdk_version;

  netdClientInit();

}

   

   

// 注册信号监听函数 HandleProfilingSignal

__LIBC_HIDDEN__ void __libc_init_profiling_handlers() {

  struct sigaction action = {};

  action.sa_flags = SA_SIGINFO | SA_RESTART;

  action.sa_sigaction = HandleProfilingSignal;

  sigaction(BIONIC_SIGNAL_PROFILER, &action, nullptr);

  // The perfetto_hprof ART plugin installs a signal handler to handle this signal. That plugin

  // does not get loaded for a) non-apps, b) non-profilable apps on user. The default signal

  // disposition is to crash. We do not want the target to crash if we accidentally target a

  // non-app or non-profilable process.

  signal(BIONIC_SIGNAL_ART_PROFILER, SIG_IGN);

}

   

// 收到 perfetto 端发送的 BIONIC_SIGNAL_PROFILER 信号后,执行 HandleProfilingSignal

// 如果信号传递过来的值为0, 则进一步执行 HandleHeapprofdSignal

static void HandleProfilingSignal(int /*signal_number*/, siginfo_t* info, void* /*ucontext*/) {

  ErrnoRestorer errno_restorer;

  if (info->si_code != SI_QUEUE) {

    return;

  }

  int signal_value = info->si_value.sival_int;

  async_safe_format_log(ANDROID_LOG_INFO, "libc", "%s: received profiling signal with si_value: %d",

                        getprogname(), signal_value);

  // Proceed only if the process is considered profileable.

  bool profileable = false;

  android_mallopt(M_GET_PROCESS_PROFILEABLE, &profileable, sizeof(profileable));

  if (!profileable) {

    async_safe_write_log(ANDROID_LOG_ERROR, "libc", "profiling signal rejected (not profileable)");

    return;

  }

  // Temporarily override SIGSYS handling, in a best-effort attempt at not

  // crashing if we happen to be running in a process with a seccomp filter that

  // disallows some of the syscalls done by this signal handler. This protects

  // against SECCOMP_RET_TRAP with a crashing SIGSYS handler (typical of android

  // minijails). Won't help if the filter is using SECCOMP_RET_KILL_*.

  // Note: the override is process-wide, but short-lived. The syscalls are still

  // blocked, but the overridden handler recovers from SIGSYS, and fakes the

  // syscall return value as ENOSYS.

  struct sigaction sigsys_override = {};

  sigsys_override.sa_sigaction = &HandleSigsysSeccompOverride;

  sigsys_override.sa_flags = SA_SIGINFO;

  struct sigaction old_act = {};

  // 注册监听信号 SIGSYS

  sigaction(SIGSYS, &sigsys_override, &old_act);

  // 如果信号值为 0,则需要调用 HandleHeapprofdSignal

  if (signal_value == kHeapprofdSignalValue) {

    HandleHeapprofdSignal();

  } else if (signal_value == kTracedPerfSignalValue) {

    HandleTracedPerfSignal();

  } else {

    async_safe_format_log(ANDROID_LOG_ERROR, "libc", "unrecognized profiling signal si_value: %d",

                          signal_value);

  }

  sigaction(SIGSYS, &old_act, nullptr);

}

   

// 此函数主要作用是处理 malloc 替换过程中出现的各种情况

void HandleHeapprofdSignal() {

  if (atomic_load(&gHeapprofdState) == kIncompatibleHooks) {

    error_log("%s: not enabling heapprofd, malloc_debug/malloc_hooks are enabled.", getprogname());

    return;

  }

  // We cannot grab the mutex here, as this is used in a signal handler.

  MaybeModifyGlobals(kWithoutLock, [] {

    // ...........

    // 此处省略大量代码

    // Now, replace the malloc function so that the next call to malloc() will

    // initialize heapprofd.

// 此处将 gEphemeralDispatch 的 malloc 替换成 MallocInitHeapprofdHook , 在 MallocInitHeapprofdHook 中会初始化 heapprofd,

// 并调用 native 的 malloc

    gEphemeralDispatch.malloc = MallocInitHeapprofdHook;

    // And finally, install these new malloc-family interceptors.

    __libc_globals.mutate([](libc_globals* globals) {

      atomic_store(&globals->default_dispatch_table, &gEphemeralDispatch);

      if (!MallocLimitInstalled()) {

        atomic_store(&globals->current_dispatch_table, &gEphemeralDispatch);

      }

    });

    atomic_store(&gHeapprofdState, kEphemeralHookInstalled);

  });

  // Otherwise, we're racing against malloc_limit's enable logic (at most once

  // per process, and a niche feature). This is highly unlikely, so simply give

  // up if it does happen.

}

   

   

// 创建线程调用 InitHeapprofd初始化 heapprofd,返回 malloc 的结果

extern "C" void* MallocInitHeapprofdHook(size_t bytes) {

  MaybeModifyGlobals(kWithLock, [] {

    MallocHeapprofdState expected = kEphemeralHookInstalled;

    // 如果 gHeapprofdState kEphemeralHookInstalled 一致,则将 gHeapprofdState 改为 kRemovingEphemeralHook ,返回true

    // 否则将 gHeapprofdState 的值赋值给 expected,返回false

    if (atomic_compare_exchange_strong(&gHeapprofdState, &expected, kRemovingEphemeralHook)) {

      __libc_globals.mutate([](libc_globals* globals) {

        // 读取 heapprofd hook 之前的  default_dispatch_table

        const MallocDispatch* previous_dispatch = atomic_load(&gPreviousDefaultDispatchTable);

        // 修改 default_dispatch_table 为之前保存的值

        atomic_store(&globals->default_dispatch_table, previous_dispatch);

        if (!MallocLimitInstalled()) {

          atomic_store(&globals->current_dispatch_table, previous_dispatch);

        }

      });

      // gHeapprofdState 的状态改为 kInitialState

      atomic_store(&gHeapprofdState, kInitialState);

      pthread_t thread_id;

      if (pthread_create(&thread_id, nullptr, InitHeapprofd, nullptr) != 0) {

        error_log("%s: heapprofd: failed to pthread_create.", getprogname());

      } else if (pthread_setname_np(thread_id, "heapprofdinit") != 0) {

        error_log("%s: heapprod: failed to pthread_setname_np", getprogname());

      } else if (pthread_detach(thread_id) != 0) {

        error_log("%s: heapprofd: failed to pthread_detach", getprogname());

      }

    } else {

      warning_log("%s: heapprofd: could not transition kEphemeralHookInstalled -> "

          "kRemovingEphemeralHook. current state (possible race): %d. this can be benign "

          "if two threads try this transition at the same time", getprogname(),

          expected);

    }

  });

  // If we had a previous dispatch table, use that to service the allocation,

  // otherwise fall back to the native allocator.

  // This could be modified by a concurrent HandleHeapprofdSignal, but that is

  // benign as we will dispatch to the ephemeral handler, which will then dispatch

  // to the underlying one.

  const MallocDispatch* previous_dispatch = atomic_load(&gPreviousDefaultDispatchTable);

  if (previous_dispatch) {

    return previous_dispatch->malloc(bytes);

  }

  return NativeAllocatorDispatch()->malloc(bytes);

}

   

   

   

static void* InitHeapprofd(void*) {

  MaybeModifyGlobals(kWithLock, [] {

    MallocHeapprofdState expected = kInitialState;

    // 如果 gHeapprofdState kInitialState 一致,则将 gHeapprofdState 改为 kInstallingHook ,返回true

    // 否则将 gHeapprofdState 的值赋值给 expected,返回false

    if (atomic_compare_exchange_strong(&gHeapprofdState, &expected, kInstallingHook)) {

      __libc_globals.mutate([](libc_globals* globals) {

        CommonInstallHooks(globals);

      });

      // gHeapprofdState 的状态改为 kHookInstalled

      atomic_store(&gHeapprofdState, kHookInstalled);

    } else {

      error_log("%s: heapprofd: failed to transition kInitialState -> kInstallingHook. "

          "current state (possible race): %d", getprogname(), expected);

    }

  });

  return nullptr;

}

   

   

// 加载 heapprofd_client.so

static void CommonInstallHooks(libc_globals* globals) {

  void* impl_handle = atomic_load(&gHeapprofdHandle);

  // gHeapprofdHandle 初始化值为 nullptr,也就是第一次需要加载 heapprofd_client.so

  if (impl_handle == nullptr) {

    impl_handle = LoadSharedLibrary(kHeapprofdSharedLib, kHeapprofdPrefix, &globals->malloc_dispatch_table);

    if (impl_handle == nullptr) {

      return;

    }

    atomic_store(&gHeapprofdHandle, impl_handle);

  } else if (!InitSharedLibrary(impl_handle, kHeapprofdSharedLib, kHeapprofdPrefix, &globals->malloc_dispatch_table)) {

    return;

  }

  FinishInstallHooks(globals, nullptr, kHeapprofdPrefix);

}

   

两条线路最终都是调用到 CommonInstallHooks 用来加载 heapprofd_client.so , 并使用 heapprofd 里面的内存申请函数来替换 libc 里面的函数

   

void* LoadSharedLibrary(const char* shared_lib, const char* prefix, MallocDispatch* dispatch_table) {

  void* impl_handle = nullptr;

  // Try to load the libc_malloc_* libs from the "runtime" namespace and then

  // fall back to dlopen() to load them from the default namespace.

  //

  // The libraries are packaged in the runtime APEX together with libc.so.

  // However, since the libc.so is searched via the symlink in the system

  // partition (/system/lib/libc.so -> /apex/com.android.runtime/bionic/libc.so)

  // libc.so is loaded into the default namespace. If we just dlopen() here, the

  // linker will load the libs found in /system/lib which might be incompatible

  // with libc.so in the runtime APEX. Use android_dlopen_ext to explicitly load

  // the ones in the runtime APEX.

  // 加载 lib 库,如果直接用 dlopen 加载的是 /system/lib/libc.so, 如果是 runtime 环境,则会出现不兼容情况,

  // 这里需要在 runtime 环境下加载 runtime 下的 libc.so

  struct android_namespace_t* runtime_ns = android_get_exported_namespace("com_android_runtime");

  if (runtime_ns != nullptr) {

    const android_dlextinfo dlextinfo = {

      .flags = ANDROID_DLEXT_USE_NAMESPACE,

      .library_namespace = runtime_ns,

    };

    impl_handle = android_dlopen_ext(shared_lib, RTLD_NOW | RTLD_LOCAL, &dlextinfo);

  }

  if (impl_handle == nullptr) {

    impl_handle = dlopen(shared_lib, RTLD_NOW | RTLD_LOCAL);

  }

  if (impl_handle == nullptr) {

    error_log("%s: Unable to open shared library %s: %s", getprogname(), shared_lib, dlerror());

    return nullptr;

  }

  // 在加载 so 库,则需要将函数指针表进行初始化

  if (!InitSharedLibrary(impl_handle, shared_lib, prefix, dispatch_table)) {

    dlclose(impl_handle);

    impl_handle = nullptr;

  }

  return impl_handle;

}

   

   

bool InitSharedLibrary(void* impl_handle, const char* shared_lib, const char* prefix, MallocDispatch* dispatch_table) {

  static constexpr const char* names[] = {

    "initialize",

    "finalize",

    "get_malloc_leak_info",

    "free_malloc_leak_info",

    "malloc_backtrace",

    "write_malloc_leak_info",

  };

  // 先使用 for 循环对 gFunctions 进行初始化 , 后面会用到这个 gFunctions 做初始化,获取泄露信息,结束等动作

  for (size_t i = 0; i < FUNC_LAST; i++) {

    char symbol[128];

    snprintf(symbol, sizeof(symbol), "%s_%s", prefix, names[i]);

    gFunctions[i] = dlsym(impl_handle, symbol);

    if (gFunctions[i] == nullptr) {

      error_log("%s: %s routine not found in %s", getprogname(), symbol, shared_lib);

      ClearGlobalFunctions();

      return false;

    }

  }

  // 然后初始化 globals->malloc_dispatch_table, 也就是将 malloc , free 等函数指针指向 heapprofd 里面对应的函数 heaprofd_malloc

  if (!InitMallocFunctions(impl_handle, dispatch_table, prefix)) {

    ClearGlobalFunctions();

    return false;

  }

  return true;

}

   

   

然后在 FinishInstallHooks 里面调用 heapprofd_initialize 来初始化 heapprofd 。

bool FinishInstallHooks(libc_globals* globals, const char* options, const char* prefix) {

  // init_func 指向的是 perfetto 端的 heapprofd_initialize

  init_func_t init_func = reinterpret_cast<init_func_t>(gFunctions[FUNC_INITIALIZE]);

  // If GWP-ASan was initialised, we should use it as the dispatch table for

  // heapprofd/malloc_debug/malloc_debug.

  const MallocDispatch* prev_dispatch = GetDefaultDispatchTable();

  if (prev_dispatch == nullptr) {

    prev_dispatch = NativeAllocatorDispatch();

  }

  // 调用 perfetto 端的初始化函数 heapprofd_initialize , 其中 options null

  if (!init_func(prev_dispatch, &gZygoteChild, options)) {

    error_log("%s: failed to enable malloc %s", getprogname(), prefix);

    ClearGlobalFunctions();

    return false;

  }

  // Do a pointer swap so that all of the functions become valid at once to

  // avoid any initialization order problems.

  atomic_store(&globals->default_dispatch_table, &globals->malloc_dispatch_table);

  if (!MallocLimitInstalled()) {

    atomic_store(&globals->current_dispatch_table, &globals->malloc_dispatch_table);

  }

  // Use atexit to trigger the cleanup function. This avoids a problem

  // where another atexit function is used to cleanup allocated memory,

  // but the finalize function was already called. This particular error

  // seems to be triggered by a zygote spawned process calling exit.

  int ret_value = __cxa_atexit(MallocFiniImpl, nullptr, nullptr);

  if (ret_value != 0) {

    // We don't consider this a fatal error.

    warning_log("failed to set atexit cleanup function: %d", ret_value);

  }

  return true;

}

posted on 2023-08-09 18:01  人在github  阅读(1201)  评论(0)    收藏  举报

导航