内存池可视化

项目概述

该项目提供了一套 hook 机制用于获取一个 C++ 项目整个声明周期中的内存峰值,并使用 html 呈现出来,源代码

hook 的函数

项目内部定义了以下的全部函数,用于hook host端的内存申请和释放行为,在项目内部统一使用这些函数

extern void* (*m_sys_malloc)(size_t);
extern void (*m_sys_free)(void*);
extern void* (*m_sys_calloc)(size_t, size_t);
extern void* (*m_sys_realloc)(void*, size_t);
extern int (*m_sys_posix_memalign)(void**, size_t, size_t);

问题总结

host 端递归调用问题

采用线程局部内存(TLS)解决

struct Tls {
   size_t recur_depth = 0;
   static void dtor(void* tls) {
       if (tls) {
           static_cast<Tls*>(tls)->~Tls();
           m_sys_free(tls);
       }
   }
};

这里使用了一个 recur_depth 变量,每次递归 +1,当值大于 0 时直接返回,避免递归调用 malloc,该问题的解决方案也可以参考 debug_malloc,但实际上两者没有什么差异

dma 调用 host 端函数发生死锁问题

定义了一个线程局部变量,避免发生死锁

static thread_local bool is_dma_hook = false;

项目内部 stl 容器内存申请和释放

自定义了一个alloctor类,内部采用 hook 的 malloc 和 free 进行内存申请和释放

template<typename T>
class SysAlloc : public NonCopyable {
public:
    typedef size_t     size_type;
    typedef ptrdiff_t  difference_type;
    typedef T*         pointer;
    typedef const T*   const_pointer;
    typedef T&         reference;
    typedef const T&   const_reference;
    typedef T          value_type;

    template<typename X>
    struct rebind { typedef SysAlloc<X> other; };

    SysAlloc() throw() {}

    template<typename X>
    SysAlloc(const SysAlloc<X>&) throw() {}

    SysAlloc(const SysAlloc&) = delete;
    void operator=(const SysAlloc&) = delete;

    ~SysAlloc() throw() {};

    pointer address(reference x) const { return &x; }
    const_pointer address(const_reference x) const { return &x; }

    pointer allocate(size_type n, const void * hint = 0);
    void deallocate(pointer p, size_type);

    void construct(pointer p, const value_type& x) { new(p) value_type(x); }
    void destroy(pointer p) { p->~value_type(); }

private:
    size_type max_size() const throw() { return size_t(-1) / sizeof(T); }
};

std::mutex 在项目结束时提前 destroy

项目结束时仍有一些内存需要调用 free 进行释放,此时的锁已经被销毁,报错使用了已销毁的锁,为了解决这个问题,使用原子变量定义了一个自旋锁

class Spinlock : public NonCopyable {
    std::atomic_flag m_state = ATOMIC_FLAG_INIT;

public:
    void lock() { while (m_state.test_and_set(std::memory_order_acquire)); }
    void unlock() { m_state.clear(std::memory_order_release); }
};

内存信息表示

定义了一个结构体用于表示内存信息

 struct Block {
     size_t size;
     int64_t free_time;
     char* backtrace;

     Block() = default;
     Block(size_t len);
     ~Block();
 };
 std::map<int64_t, Block, std::less<int64_t>, SysAlloc<std::pair<const int64_t, Block>>> m_peak_info;

需要主意 stl 容器的插入函数会调用 Block 类的拷贝构造函数,但是内存资源宝贵,我不希望频繁的拷贝函数堆栈信息,采用这种方法赋值

 m_peak_info[current_time] = {size};
 m_peak_info[current_time].backtrace = backtrace(4);

C 基础函数使用 malloc

例如 printf、IO 流会调用 malloc,项目内部使用一个 bool 标志来判断这种情况

bool m_is_exit = 0;

问题:

1、Hook mmap 发生递归

  • 静态类的创建会调用mmap,测试方法,hook mmap,执行程序创建静态类
  • hook mmap 的时候需要注意,但本身 hook mmap 就是一件非常危险的行为,因为程序启动的加载过程需要使用 mmap 来进行映射

2、Android 内存分配器

3、Hook mmap 会激活 android scudo 分配器

创建了一个 MmapLock 防止 mmap 发生递归

class MmapLock {
public:
    MmapLock() { is_recur.store(true, std::memory_order_release); }
    ~MmapLock() { is_recur.store(false, std::memory_order_release); }
    static inline bool isRecur() { return is_recur.load(std::memory_order_acquire); }

private:
    static std::atomic_bool is_recur;
};
std::atomic_bool MmapLock::is_recur(false);

在调用 mmap hook 代码时发现如果创建 MmapLock 类对象就会激活 scudo 分配器

void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset) {
    if (MmapLock::isRecur() || fd > -1) {
        return mmap64(addr, length, prot, flags, fd, offset); 
    }
    MmapLock ml;
    return AllocHook::inst().mmap(addr, length, prot, flags, fd, offset);
}

调用堆栈信息如下:

#00 pc 0000000000032e6c /data/local/tmp/ghy/liballoc_hook.so (mmap+112)
#01 pc 0000000000041810 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::map(void*, unsigned long, char const*, unsigned long, scudo::MapPlatformData*)+80)
#02 pc 00000000000460ec /apex/com.android.runtime/lib64/bionic/libc.so (scudo::SizeClassAllocator64scudo::AndroidConfig::init(int)+44)
#03 pc 0000000000045f9c /apex/com.android.runtime/lib64/bionic/libc.so (scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>::init()+380)
#04 pc 00000000000458a4 /apex/com.android.runtime/lib64/bionic/libc.so (scudo::TSDRegistrySharedT<scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>, 8u, 2u>::init(scudo::Allocator<scudo::AndroidConfig, &(scudo_malloc_postinit)>*)+36)
#05 pc 00000000000455d8 /apex/com.android.runtime/lib64/bionic/libc.so (scudo_malloc_disable_memory_tagging+104)
#06 pc 000000000004b3e0 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_preinit_impl()+48)
#07 pc 00000000000524dc /apex/com.android.runtime/bin/linker64 (__dl__ZN6soinfo17call_constructorsEv+748)
#08 pc 0000000000052284 /apex/com.android.runtime/bin/linker64 (__dl__ZN6soinfo17call_constructorsEv+148)
#09 pc 0000000000052284 /apex/com.android.runtime/bin/linker64 (__dl__ZN6soinfo17call_constructorsEv+148)
#10 pc 0000000000052284 /apex/com.android.runtime/bin/linker64 (__dl__ZN6soinfo17call_constructorsEv+148)
#11 pc 0000000000052284 /apex/com.android.runtime/bin/linker64 (__dl__ZN6soinfo17call_constructorsEv+148)
#12 pc 0000000000052284 /apex/com.android.runtime/bin/linker64 (__dl__ZN6soinfo17call_constructorsEv+148)
#13 pc 0000000000052284 /apex/com.android.runtime/bin/linker64 (__dl__ZN6soinfo17call_constructorsEv+148)
#14 pc 00000000000b9af8 /apex/com.android.runtime/bin/linker64 (__dl__ZL29__linker_init_post_relocationR19KernelArgumentBlockR6soinfo+4200)
#15 pc 00000000000b8a38 /apex/com.android.runtime/bin/linker64 (__dl___linker_init+1432)
#16 pc 0000000000054e94 /apex/com.android.runtime/bin/linker64 (__dl__start+4)

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值