Matrix 中的 memory hook 也是基于 xhook 的实现的,可以记录 malloc、calloc、free、mmap 等函数的调用。
1.初始化
初始化主要做了二件事,一是 hook 函数的调用,二是开启了一个 while(true) 线程将记录的数据由一组容器转移变换到另一组容器中。
JNIEXPORT void JNICALL
Java_com_tencent_matrix_hook_memory_MemoryHook_installHooksNative(JNIEnv* env, jobject thiz,
jobjectArray hook_so_patterns,
jobjectArray ignore_so_patterns,
jboolean enable_debug) {
// 开启线程
memory_hook_init();
xhook_block_refresh();
{
jsize size = env->GetArrayLength(hook_so_patterns);
for (int i = 0; i < size; ++i) {
auto jregex = (jstring) env->GetObjectArrayElement(hook_so_patterns, i);
const char* regex = env->GetStringUTFChars(jregex, nullptr);
// hook 函数调用
hook(regex);
env->ReleaseStringUTFChars(jregex, regex);
}
}
...
xhook_grouped_ignore(HOOK_REQUEST_GROUPID_MEMORY, ".*/libandroid_runtime\\.so$", nullptr);
xhook_unblock_refresh();
}
- hook 函数调用
static void hook(const char *regex) { // regex 是 hook 哪个 so 的名字,
for (auto f : HOOK_MALL_FUNCTIONS) {
int ret = xhook_grouped_register(HOOK_REQUEST_GROUPID_MEMORY, regex, f.name, f.handler_ptr, f.origin_ptr);
LOGD(TAG, "hook fn, regex: %s, sym: %s, ret: %d", regex, f.name, ret);
}
LOGD(TAG, "mmap enabled ? %d", enable_mmap_hook);
// 是否 hook mmap
if (enable_mmap_hook) {
for (auto f: HOOK_MMAP_FUNCTIONS) {
xhook_grouped_register(HOOK_REQUEST_GROUPID_MEMORY, regex, f.name, f.handler_ptr, f.origin_ptr);
}
}
}
// 其中需要 hook 的函数有如下
// 与开辟、释放空间相关的
const HookFunction HOOK_MALL_FUNCTIONS[] = {
{"malloc", (void *) h_malloc, NULL},
{"calloc", (void *) h_calloc, NULL},
{"realloc", (void *) h_realloc, NULL},
{"free", (void *) h_free, NULL},
{"memalign", (void *) HANDLER_FUNC_NAME(memalign), NULL},
{"posix_memalign", (void *) HANDLER_FUNC_NAME(posix_memalign), NULL},
// CXX functions
#ifndef __LP64__
...
#else
{"_Znwm", (void*) HANDLER_FUNC_NAME(_Znwm), NULL},
{"_ZnwmSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZnwmSt11align_val_t), NULL},
{"_ZnwmSt11align_val_tRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnwmSt11align_val_tRKSt9nothrow_t), NULL},
{"_ZnwmRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnwmRKSt9nothrow_t), NULL},
{"_Znam", (void*) HANDLER_FUNC_NAME(_Znam), NULL},
{"_ZnamSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZnamSt11align_val_t), NULL},
{"_ZnamSt11align_val_tRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnamSt11align_val_tRKSt9nothrow_t), NULL},
{"_ZnamRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZnamRKSt9nothrow_t), NULL},
{"_ZdlPvm", (void*) HANDLER_FUNC_NAME(_ZdlPvm), NULL},
{"_ZdlPvmSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZdlPvmSt11align_val_t), NULL},
{"_ZdaPvm", (void*) HANDLER_FUNC_NAME(_ZdaPvm), NULL},
{"_ZdaPvmSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZdaPvmSt11align_val_t), NULL},
#endif
{"_ZdlPv", (void*) HANDLER_FUNC_NAME(_ZdlPv), NULL},
{"_ZdlPvSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZdlPvSt11align_val_t), NULL},
{"_ZdlPvSt11align_val_tRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZdlPvSt11align_val_tRKSt9nothrow_t), NULL},
{"_ZdlPvRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZdlPvRKSt9nothrow_t), NULL},
{"_ZdaPv", (void*) HANDLER_FUNC_NAME(_ZdaPv), NULL},
{"_ZdaPvSt11align_val_t", (void*) HANDLER_FUNC_NAME(_ZdaPvSt11align_val_t), NULL},
{"_ZdaPvSt11align_val_tRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZdaPvSt11align_val_tRKSt9nothrow_t), NULL},
{"_ZdaPvRKSt9nothrow_t", (void*) HANDLER_FUNC_NAME(_ZdaPvRKSt9nothrow_t), NULL},
{"strdup", (void*) HANDLER_FUNC_NAME(strdup), (void **) ORIGINAL_FUNC_NAME(strdup)},
{"strndup", (void*) HANDLER_FUNC_NAME(strndup), (void **) ORIGINAL_FUNC_NAME(strndup)},
};
// 与 mmap 相关的
static const HookFunction HOOK_MMAP_FUNCTIONS[] = {
{"mmap", (void *) h_mmap, NULL},
{"munmap", (void *) h_munmap, NULL},
{"mremap", (void *) h_mremap, NULL},
#if __ANDROID_API__ >= __ANDROID_API_L__
{"mmap64", (void *) h_mmap64, NULL},
#endif
};
// 转换前
int ret = xhook_grouped_register(HOOK_REQUEST_GROUPID_MEMORY, regex, f.name, f.handler_ptr, f.origin_ptr);
// 实际 hook
int ret = xhook_grouped_register(HOOK_REQUEST_GROUPID_MEMORY, ".*library-not-exists\\.so$",malloc, (void *) h_malloc, NULL);
而实际调用的 h_malloc 函数也需要进行一点点转化才可以得到
DEFINE_HOOK_FUN(void *, malloc, size_t __byte_count) {
CALL_ORIGIN_FUNC_RET(void*, p, malloc, __byte_count);
LOGI(TAG, "+ malloc %p", p);
DO_HOOK_ACQUIRE(p, __byte_count);
return p;
}
// 将上面的代码进行宏替换后得到
fn_malloc_t orig_malloc;
void * h_malloc(size_t __byte_count){
if(!orig_malloc){
void *handle = dlopen("libc.so", 0x00001);
if(handle){
orig_malloc = (fn_malloc_t)dlsym(handle,malloc)
}
}
void* p = orig_malloc(__byte_count);
LOGI(TAG, "+ malloc %p", p);
void * __caller_addr = __builtin_return_address(0);
on_alloc_memory(caller, p, __byte_count);
return p;
}
当调用相关 hook 函数后,先通过 dlopen
打开 libc.so
,在拿到 libc.so
里面的 malloc
原函数,先调用原函数,拿到 hook 函数的返回地址之后再通过 on_alloc_memory
函数来记录 原malloc
函数的返回地址,开辟的大小及 hook 函数的返回地址。
- 开启一个线程,
memory_hook_init
// memory_meta_container 构造函数 舒适化了 ptr_meta_containers、stack_meta_containers 这二个容器
static memory_meta_container m_memory_meta_container;
// memory_meta_container() {
// size_t cap = ptr_meta_capacity();// 1
// ptr_meta_containers.reserve(cap);// 申请n个元素的内存空间
// for (int i = 0; i < cap; ++i) {
// ptr_meta_containers.emplace_back(new ptr_meta_container_wrapper_t);
// }
//
// cap = stack_meta_capacity();// 1
// stack_meta_containers.reserve(cap);
// for (int i = 0; i < cap; ++i) {
// stack_meta_containers.emplace_back(new stack_container_wrapper_t);
// }
// }
// BufferManagement 构造函数中将容器 containers_ 初始化并添加了 1<<8 个 BufferQueueContainer() 对象
static BufferManagement m_memory_messages_containers_(&m_memory_meta_container);
// BufferManagement::BufferManagement(memory_meta_container *memory_meta_container) {
//
// containers_.reserve(MAX_PTR_SLOT);// 1<<8
// for (int i = 0; i < MAX_PTR_SLOT; ++i) {
// auto container = new BufferQueueContainer();
// containers_.emplace_back(container);
// }
// memory_meta_container_ = memory_meta_container;
// }
// 调用的是这里
void memory_hook_init() {
m_memory_messages_containers_.start_process();
}
void BufferManagement::start_process() {
if (processing_) return;
processing_ = true;
pthread_create(&thread_, nullptr,
reinterpret_cast<void *(*)(void *)>(&BufferManagement::process_routine),
this);
pthread_detach(thread_);
}
// 数据由 BufferManagement::containers_ 容器转移变化到了 BufferManagement::memory_meta_container_ 里面
// 而 BufferManagement::memory_meta_container_ 是个静态对象
[[noreturn]] void BufferManagement::process_routine(BufferManagement *this_) {
size_t last_total_message_counter = 0;
size_t total_message_counter = 0;
while (true) {
if (!this_->queue_swapped_) this_->queue_swapped_ = new BufferQueue(SIZE_AUGMENT); // 192
size_t busy_queue = 0;
for (auto container : this_->containers_) { // 遍历 1<<8 个对象
HOOK_LOG_ERROR("Process routine ... ");
BufferQueue *swapped = nullptr;
{
std::lock_guard<std::mutex> lock(container->mutex_);
if (container->queue_ && !container->queue_->empty()) { // 判断是否为空,不为空就将其 queue_ 赋值给 queue_swapped_ 并将原来的值清空
HOOK_LOG_ERROR("Swap queue ... ");
// 将数据赋值给 swapped,并且把 container 里面的数据置空
swapped = container->queue_;
container->queue_ = this_->queue_swapped_;
}
}
if (swapped && swapped->size() >= 5) {
busy_queue++;
}
if (swapped) {
HOOK_LOG_ERROR("Swapped ... ");
swapped->process( // 依次取出 messages_ 和 allocation_message_t 记录的信息
// 依次拿到记录在 container 内的 messages_ 和 allocations_ 容器 里面的内容
[&](message_t *message, allocation_message_t *allocation_message) {
total_message_counter++;
if (message->type == message_type_allocation || message->type == message_type_reallocation ||
message->type == message_type_mmap) {
if (UNLIKELY(allocation_message == nullptr)) {
CRITICAL_CHECK(allocation_message);
return;
}
uint64_t stack_hash = 0;
// 开辟大小不为 0 且 有 native stack 就 hash
// 之前 wechat backtrace 工具记录的
if (allocation_message->size != 0 &&
allocation_message->backtrace.frame_size != 0) {
stack_hash = hash_frames(
allocation_message->backtrace.frames,
allocation_message->backtrace.frame_size);
}
// 一个 static obj
// 有一样的 native stack 的话就放在一个类似与链表的地方,没有的话就新建一个 像 HashMap<String,LinkArray>?
// 返回插入的 ptr 和 stack 对象给这边add数据,对饮之前的 messages_ 和 allocation_message_t 对象
// memory_meta_container_ 是一个静态变量
this_->memory_meta_container_->insert(
reinterpret_cast<const void *>(allocation_message->ptr),
stack_hash,
allocation_message,
[&](ptr_meta_t *ptr_meta, stack_meta_t *stack_meta) { // 回调填充数据
ptr_meta->ptr = reinterpret_cast<void *>(allocation_message->ptr);
ptr_meta->size = allocation_message->size;
ptr_meta->attr.is_mmap =
message->type == message_type_mmap;
if (UNLIKELY(!stack_meta)) {
ptr_meta->caller = allocation_message->caller;
return;
}
stack_meta->size += allocation_message->size;
if (stack_meta->backtrace.frame_size == 0 &&
allocation_message->backtrace.frame_size != 0) {
stack_meta->backtrace = allocation_message->backtrace;
stack_meta->caller = allocation_message->caller;
}
});
} else if (message->type == message_type_deletion ||
message->type == message_type_munmap) {
this_->memory_meta_container_->erase(
reinterpret_cast<const void *>(message->ptr));
}
});
// messages_ 及 allocations_ 容器置空
swapped->reset();
this_->queue_swapped_ = swapped;
}
}
}
}
总结:将数据由 BufferManagement::containers_ 容器转移到了 BufferManagement::memory_meta_container_ 里面。
2.hook 记录相关信息
分析 malloc 及 free 这二个函数 hook 之后记录的信息。
- 1.malloc hook
在初始化中提到malloc
的 hook 函数是h_malloc
,而该函数通过dlopen
打开libc.so
,在拿到libc.so
里面的malloc
原函数,先调用原函数,拿到 hook 函数的返回地址之后再通过on_alloc_memory
函数来记录原malloc
函数的返回地址,开辟的大小及 hook 函数的返回地址。
void on_alloc_memory(void *caller, void *ptr, size_t byte_count) {
on_acquire_memory(caller, ptr, byte_count, message_type_allocation);
}
// m_memory_messages_containers_ 是上面的 BufferManagement 对象
static inline void on_acquire_memory(
void *caller,
void *ptr,
size_t byte_count,
message_type type) {
// BufferManagement::containers_ 里面有 1<<8 个 BufferQueueContainer 对象
// hash ptr 后从 1<<8 拿到其 BufferQueueContainer
BufferQueueContainer *container = m_memory_messages_containers_.containers_[memory_ptr_hash(
(uintptr_t) ptr)];
{
memory_backtrace_t backtrace{0};
// 应该是 wechat 的 native stack tools 代码,先不用管
if (LIKELY(byte_count > 0 && is_stacktrace_enabled && should_do_unwind(byte_count))) {
size_t frame_size = 0;
do_unwind(backtrace.frames, MEMHOOK_BACKTRACE_MAX_FRAMES,
frame_size);
backtrace.frame_size = frame_size;
}
// 原子操作上锁 并初始化 BufferQueueContainer(即 container) 中的 queue_ 对象
container->lock();
// inline void lock() {
// if (UNLIKELY(!mutex_.try_lock())) {
// g_locker_collision_counter.fetch_add(1, std::memory_order_relaxed);
// mutex_.lock();
// }
//
// if (UNLIKELY(!queue_)) {
// queue_ = new BufferQueue(SIZE_AUGMENT);//192
// }
// }
auto message = container->queue_->enqueue_allocation_message(type);
// inline allocation_message_t *enqueue_allocation_message(message_type type) {
// // 初始化的操作 resize=194 T 是 message_t 类型
// // void *buffer = realloc(queue_, sizeof(T) * resize);
// // queue_ = static_cast<T *>(buffer);
// 从容量为194的容器取出 index 从0开始累加的 message_t
// 总结 就是给 index = idx 的 message_t 赋值
// message_t *msg_idx = &messages_->queue_[messages_->idx_++];// idx 从0开始加
// msg_idx->type = type; // message_type_allocation
// msg_idx->index = allocations_->idx_; // 也是从0开始加
// // 总结 返回 index = idx 的 allocation_message_t ,准备给这个对象赋值
// allocation_message_t *buffer = &allocations_->queue_[allocations_->idx_++];
// *buffer = {};
// return buffer;
// }
if (UNLIKELY(!message)) {
BufferQueueContainer::g_message_overflow_counter.fetch_add(1,
std::memory_order_relaxed);
CHECK_MESSAGE_OVERFLOW(!message);
} else {
// 给上面返回的 allocation_message_t 对象赋值,有 malloc 函数的返回地址,开辟的字节数,malloc 所在函数的返回地址及 wechat 的一个栈帧
message->ptr = reinterpret_cast<uintptr_t>(ptr);
message->size = byte_count;
message->caller = reinterpret_cast<uintptr_t>(caller);
if (backtrace.frame_size) {
message->backtrace = backtrace;
}
}
container->unlock();
}
}
- malloc hook 总结:
- 取 BufferManagement::containers_ 容器(size=1<<8) BufferQueueContainer,index 是 ptr 的一种 hash
- 初始化 BufferQueueContainer 中的 queue_ = new BufferQueue(SIZE_AUGMENT);//192
BufferQueue 中有二个对象 messages_ 和 allocations_,二个都是容器(size=194)
- 初始化 BufferQueueContainer 中的 queue_ = new BufferQueue(SIZE_AUGMENT);//192
-
- &messages_->queue_[messages_->idx_++];取出 queue_ 中 index = idx(该值是自加1的)并赋值
- 3.1 主要是赋hook的类型( message_type_allocation malloc的类型)和 allocations_ 容器的索引
-
- allocation_message_t *buffer = &allocations_->queue_[allocations_->idx_++];取出 queue_ 中 index = idx(该值是自加1的)并赋值
- 4.1 主要是malloc 函数的返回地址,开辟的字节数,malloc 所在函数的返回地址及 wechat 的一个栈帧
- 2.free hook
free hook 的代码宏替换后会调到on_free_memory
函数,ptr 是释放空间的指针
void on_free_memory(void *ptr) {
on_release_memory(ptr, false);
}
static inline void on_release_memory(void *ptr, bool is_munmap) {
BufferQueueContainer *container = m_memory_messages_containers_.containers_[memory_ptr_hash(
(uintptr_t) ptr)];
container->lock();
bool ret = container->queue_->enqueue_deletion_message(reinterpret_cast<uintptr_t>(ptr),
is_munmap);
// inline bool enqueue_deletion_message(uintptr_t ptr, bool is_munmap) {
//
// // Fast path. 如果ptr相也就是malloc开辟的空间被free了,messages_->idx_>0
// if (messages_->idx_ > 0) {
// message_t *msg_idx = &messages_->queue_[messages_->idx_ - 1];
// if (((!is_munmap && msg_idx->type == message_type_allocation)
// || (is_munmap && msg_idx->type == message_type_mmap))
// && allocations_->queue_[msg_idx->index].ptr == ptr) {
// msg_idx->type = message_type_nil;
// messages_->idx_--;
// allocations_->idx_--;
// return true;
// }
// }
//
// if (UNLIKELY(!messages_->check_realloc())) {
// return false;
// }
//
// message_t *msg_idx = &messages_->queue_[messages_->idx_++];
// msg_idx->type = is_munmap ? message_type_munmap : message_type_deletion;
// msg_idx->ptr = ptr;
//
// return true;
// }
container->unlock();
if (UNLIKELY(!ret)) {
BufferQueueContainer::g_message_overflow_counter.fetch_add(1, std::memory_order_relaxed);
CHECK_MESSAGE_OVERFLOW(!ret);
}
}
- free hook 总结
- 取 BufferManagement::containers_ 容器(size=1<<8) BufferQueueContainer,index 是 ptr 的一种 hash
- 初始化 BufferQueueContainer 中的 queue_ = new BufferQueue(SIZE_AUGMENT);//192
BufferQueue 中有二个对象 messages_ 和 allocations_,二个都是容器(size=194)
- 初始化 BufferQueueContainer 中的 queue_ = new BufferQueue(SIZE_AUGMENT);//192
-
- &messages_->queue_[messages_->idx_++];取出 queue_ 中 index = idx(该值是自加1的)并赋值
- 3.1 如果((!mmap&&type==malloc)||(mmap&&type=mmap))&&(allocations_->queue_[msg_idx->index].ptr == ptr)的话就删除之前的 message_t 对象里面的内容,否则执行3.2
- 3.2 主要是赋hook的类型( message_type_deletion delete类型)和 ptr free 的指针
一句话就是 free 的 ptr 是之前记录的就删除记录的内容,不是之前的记录该 free 的 ptr 及 type
3.dump 信息
在 hook 之后,相关的信息就被记录到 BufferManagement::containers_
中了,而在初始化中又会将记录的信息转移到BufferManagement::memory_meta_container_
这个容器中,所以想得到记录的相关信息只需要遍历这个容器即可。
void dump(bool enable_mmap, const char *log_path, const char *json_path) {
dump_impl(log_file, json_file, enable_mmap);
DUMP_RECORD("/sdcard/Android/data/com.tencent.mm/memory-record.dump");
}
static inline void dump_impl(FILE *log_file, FILE *json_file, bool mmap) {
std::map<void *, caller_meta_t> heap_caller_metas;
std::map<void *, caller_meta_t> mmap_caller_metas;
std::map<uint64_t, stack_meta_t> heap_stack_metas;
std::map<uint64_t, stack_meta_t> mmap_stack_metas;
// 返回值是 放了多少个数据
size_t ptr_meta_size = collect_metas(heap_caller_metas,
mmap_caller_metas,
heap_stack_metas,
mmap_stack_metas);
// 下面就是将上面容器中的数据组装起来
cJSON *json_obj = cJSON_CreateObject();
cJSON *so_native_size_arr = cJSON_AddArrayToObject(json_obj, "SoNativeSize");
// native heap allocation
dump_callers(log_file, so_native_size_arr, heap_caller_metas);
cJSON *native_heap_arr = cJSON_AddArrayToObject(json_obj, "NativeHeap");
dump_stacks(log_file, native_heap_arr, heap_stack_metas);
if (mmap) {
...
}
char *printed = cJSON_PrintUnformatted(json_obj);
flogger0(json_file, "%s", printed);
LOGD(TAG, "===> %s", printed);
cJSON_free(printed);
cJSON_Delete(json_obj);
}
static inline size_t collect_metas(std::map<void *, caller_meta_t> &heap_caller_metas,
std::map<void *, caller_meta_t> &mmap_caller_metas,
std::map<uint64_t, stack_meta_t> &heap_stack_metas,
std::map<uint64_t, stack_meta_t> &mmap_stack_metas) {
LOGD(TAG, "collect_metas");
size_t ptr_meta_size = 0;
// 由初始化的 memory_hook_init(); 函数,将记录的 hook 数据从 BufferManagement::containers_ 容器转移变化到了 BufferManagement::memory_meta_container_ 里面
// 也就是 m_memory_meta_container 这个里面有记录的 hook 的数据
// 取出了 ptr, &ptr_meta, stack_meta 数据
m_memory_meta_container.for_each(
[&](const void *ptr, ptr_meta_t *meta, stack_meta_t *stack_meta) { // 依次取出了 ptr, &ptr_meta, stack_meta 数据
// 区分 native heap 和 mmap 的 caller 和 stack
auto &dest_caller_metes =
meta->attr.is_mmap ? mmap_caller_metas : heap_caller_metas;
auto &dest_stack_metas = meta->attr.is_mmap ? mmap_stack_metas : heap_stack_metas;
void *caller;
if (stack_meta) {
caller = reinterpret_cast<void *>(stack_meta->caller);
} else {
caller = reinterpret_cast<void *>(meta->caller);
}
// 向参数里面的 map 放数据
if (caller) {
caller_meta_t &caller_meta = dest_caller_metes[caller];
caller_meta.pointers.insert(ptr);
caller_meta.total_size += meta->size;
}
// 向参数里面的 map 放数据
if (stack_meta) {
auto &dest_stack_meta = dest_stack_metas[(uint64_t) stack_meta];
dest_stack_meta.backtrace = stack_meta->backtrace;
// 没错, 这里的确使用 ptr_meta 的 size, 因为是在遍历 ptr_meta, 因此原来 stack_meta 的 size 仅起引用计数作用
dest_stack_meta.size += meta->size;
dest_stack_meta.caller = stack_meta->caller;
}
ptr_meta_size++;
});
LOGD(TAG, "collect_metas done");
return ptr_meta_size; // map 中的个数
}
void for_each(std::function<void(const void *, ptr_meta_t *, stack_meta_t *)> __callback) {
// 这个就是初始化中开启线程转移变化数据的目的容器
for (const auto cw : ptr_meta_containers) {
std::lock_guard<std::mutex> container_lock(cw->mutex);
cw->container.enumerate([&](const void *ptr, ptr_meta_t &ptr_meta) {
if (ptr_meta.stack_hash) {
TARGET_STACK_CONTAINER_LOCKED(stack_meta_container, ptr_meta.stack_hash);
// 宏替换后的
// stack_container_wrapper_t *stack_meta_container = stack_meta_containers.data()[stack_meta_hash(ptr_meta.stack_hash)];
// std::lock_guard<std::mutex> stack_lock(stack_meta_container->mutex)
stack_meta_t *stack_meta = nullptr;
if (LIKELY(stack_meta_container->container.exist(ptr_meta.stack_hash))) {
if (ptr_meta.attr.is_stack_idx) {
stack_meta = &stack_meta_container->container.get(ptr_meta.stack_idx);
} else {
stack_meta = reinterpret_cast<stack_meta_t *>(ptr_meta.ext_stack_ptr);
}
}
__callback(ptr, &ptr_meta, stack_meta); // 回调到上面那个函数
} else {
__callback(ptr, &ptr_meta, nullptr);
}
});
}
}
至此分析结束。