通过覆盖C标准库调用来测量堆使用情况

Cur*_*ous 2 c++ malloc dynamic-linking c++11 c++14

参考以下代码

#include <cassert>
#include <vector>
#include <dlfcn.h>
#include <limits>
#include <map>
#include <algorithm>
#include <iostream>
using std::cout;
using std::endl;
using std::vector;

/*
 * Overload the malloc call
 */
int max_heap_usage = 0;
std::map<uintptr_t, int>& heap_memory_map;
void track_max_usage(std::map<uintptr_t, int> heap_memory_map, 
        int& max_heap_usage);

void* malloc(size_t size) {

    // get the original malloc function call
    static auto original_malloc = (decltype(&malloc)) dlsym(RTLD_NEXT, "malloc");

    // Get the pointer from malloc
    void* pointer = original_malloc(size);
    uintptr_t pointer_handle = reinterpret_cast<uintptr_t>(pointer);

    // assert that the pointer does not already exist in the memory map
    assert("memory should not exist in memory map before allocation" && 
            heap_memory_map.find(pointer_handle) == heap_memory_map.end());

    // add to bookkeeping
    heap_memory_map[pointer_handle] = size;
    track_max_usage(heap_memory_map, max_heap_usage);

    return pointer;
}

void* calloc(size_t count, size_t size) {

    // get the original calloc
    static auto original_calloc = (decltype(&calloc)) dlsym(RTLD_NEXT, "calloc");

    // get the pointer returned by calloc
    void* pointer = original_calloc(count, size);
    uintptr_t pointer_handle = reinterpret_cast<uintptr_t>(pointer);

    // assert that the memory has not been allocated before
    assert("memory should not exist in the memory map before allocation" && 
            heap_memory_map.find(pointer_handle) == heap_memory_map.end());

    // add to bookkeeping
    heap_memory_map[pointer_handle] = size * count;
    track_max_usage(heap_memory_map, max_heap_usage);

    return pointer;
}

void free(void* ptr) {

    // get the original free function
    static auto original_free = (decltype(&free)) dlsym(RTLD_NEXT, "free");
    uintptr_t pointer_handle = reinterpret_cast<uintptr_t>(ptr);

    // assert that the heap memory map already has the pointer
    assert("memory to be freed does not exist in the heap memory map" && 
                heap_memory_map.find(pointer_handle) != heap_memory_map.end());

    // add to bookkeeping
    heap_memory_map.erase(pointer_handle);

    // free the memory
    original_free(ptr);
}

/*
 * Inputs:  A map containing pointer values and the amount of heap memory used
 *          after that point
 *
 *          The variable that keeps track of the max memory usage till this
 *          point
 *
 * This function updates the variable to have the max value if the current
 * memory map dictates that the memory usage is greater than what it was
 * before.
 */
void track_max_usage(std::map<uintptr_t, int>& heap_memory_map, 
        int& max_heap_usage) {

    // loop through all keys and add up the values
    int sum {0};
    for (const auto ele : heap_memory_map) { sum += ele.second; }

    // assign to max
    max_heap_usage = std::max(max_heap_usage, sum);
}

int main() {
    vector<int> vec {1, 2, 3, 4};
    for (auto ele : vec) {
        cout << ele << endl;
    }

    cout << "Total heap usage " << max_heap_usage << endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我试图覆盖malloc,calloc和free调用,这样只要有堆分配,我就可以跟踪它.不知何故,vector类似乎没有在堆上分配任何内存.有人可以解释一下究竟发生了什么吗?我怎样才能达到预期的效果呢?

谢谢!

Mik*_*han 5

您发布的程序 - 调用它main.cpp- 不能完全编译,因此它不能完全是您想要解释的令人失望的行为的程序:

error: 'heap_memory_map' declared as reference but not initialized
 std::map<uintptr_t, int>& heap_memory_map;
                       ^  
Run Code Online (Sandbox Code Playgroud)

如果我们通过声明来解决这个问题:

std::map<uintptr_t, int> heap_memory_map;
Run Code Online (Sandbox Code Playgroud)

然后我们有一个链接错误:

undefined reference to `track_max_usage(std::map<unsigned long, int, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, int> > >, int&)'
Run Code Online (Sandbox Code Playgroud)

因为声明:

void track_max_usage(std::map<uintptr_t, int> heap_memory_map, 
        int& max_heap_usage);
Run Code Online (Sandbox Code Playgroud)

与定义不符:

void track_max_usage(std::map<uintptr_t, int>& heap_memory_map, 
        int& max_heap_usage) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

如果我们通过声明来解决这个问题:

void track_max_usage(std::map<uintptr_t, int>& heap_memory_map, 
        int& max_heap_usage);
Run Code Online (Sandbox Code Playgroud)

然后我们成功编译和链接,至少如果我们不挑剔标准一致性:

$ g++ -o prog -std=c++11 -Wall main.cpp -ldl
Run Code Online (Sandbox Code Playgroud)

如果我们挑剔标准的一致性:

$ g++ -o prog -std=c++11 -Wall -pedantic main.cpp -ldl
Run Code Online (Sandbox Code Playgroud)

然后仍然存在编译错误:

main.cpp:20:25: error: declaration of ‘void* malloc(size_t)’ has a different exception specifier
 void* malloc(size_t size) {
                         ^
...
/usr/include/stdlib.h:466:14: error: from previous declaration ‘void* malloc(size_t) throw ()’
 extern void *malloc (size_t __size) __THROW __attribute_malloc__ __wur;
              ^
main.cpp: In function ‘void* calloc(size_t, size_t)’:
main.cpp:40:39: error: declaration of ‘void* calloc(size_t, size_t)’ has a different exception specifier
 void* calloc(size_t count, size_t size) {
                                       ^
...
/usr/include/stdlib.h:468:14: error: from previous declaration ‘void* calloc(size_t, size_t) throw ()’
 extern void *calloc (size_t __nmemb, size_t __size)
              ^
main.cpp: In function ‘void free(void*)’:
main.cpp:60:20: error: declaration of ‘void free(void*)’ has a different exception specifier
 void free(void* ptr) {
                    ^
...
/usr/include/stdlib.h:483:13: error: from previous declaration ‘void free(void*) throw ()’
 extern void free (void *__ptr) __THROW;
Run Code Online (Sandbox Code Playgroud)

另外两对鲤鱼:

int不保证存储堆块的大小.因此标准库说:

void* malloc(size_t size);
void* calloc(size_t num, size_t size);
Run Code Online (Sandbox Code Playgroud)

并不是:

void* malloc(int size);
void* calloc(int num, int size);
Run Code Online (Sandbox Code Playgroud)

所以通过权利你有:

size_t max_heap_usage = 0;
std::map<uintptr_t, size_t> heap_memory_map;
Run Code Online (Sandbox Code Playgroud)

此外,你真正想要的是一个void *大小的值的地图,没有理由不拥有这样的地图:

std::map<void *, size_t> heap_memory_map;
Run Code Online (Sandbox Code Playgroud)

然后克制:

uintptr_t pointer_handle = reinterpret_cast<uintptr_t>(pointer);
Run Code Online (Sandbox Code Playgroud)

可以免除.

然而,继续我们所拥有的(并且记住我们并不确切知道所拥有的)运行prog并不能简单地计算任何堆分配; 它崩溃了:

$ ./prog
Segmentation fault (core dumped)
Run Code Online (Sandbox Code Playgroud)

如果你调试它并仔细阅读回溯到segfault,你会看到循环调用序列:

operator new(unsigned long)    
__gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<unsigned long const, int> > >::allocate  /usr/include/c++/5/ext/new_allocator.h  104
std::allocator_traits<std::allocator<std::_Rb_tree_node<std::pair<unsigned long const, int> > > >::allocate  /usr/include/c++/5/bits/alloc_traits.h  360
std::_Rb_tree<unsigned long, std::pair<unsigned long const, int>, std::_Select1st<std::pair<unsigned long const, int> >, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, int> > >::_M_get_node  /usr/include/c++/5/bits/stl_tree.h  491
std::_Rb_tree<unsigned long, std::pair<unsigned long const, int>, std::_Select1st<std::pair<unsigned long const, int> >, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, int> > >::_M_create_node<std::piecewise_construct_t const&, std::tuple<unsigned long const&>, std::tuple<> >(std::piecewise_construct_t const&, std::tuple<unsigned long const&>&&, std::tuple<>&&)  /usr/include/c++/5/bits/stl_tree.h  545
std::_Rb_tree<unsigned long, std::pair<unsigned long const, int>, std::_Select1st<std::pair<unsigned long const, int> >, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, int> > >::_M_emplace_hint_unique<std::piecewise_construct_t const&, std::tuple<unsigned long const&>, std::tuple<> >(std::_Rb_tree_const_iterator<std::pair<unsigned long const, int> >, std::piecewise_construct_t const&, std::tuple<unsigned long const&>&&, std::tuple<>&&)  /usr/include/c++/5/bits/stl_tree.h  2170
std::map<unsigned long, int, std::less<unsigned long>, std::allocator<std::pair<unsigned long const, int> > >::operator[]  /usr/include/c++/5/bits/stl_map.h  483
malloc  /home/imk/develop/so/heap_track_orig/main.cpp  34
operator new(unsigned long) 
Run Code Online (Sandbox Code Playgroud)

反复令人作呕.所以程序循环直到它用完了堆栈.

这是由于致命的逻辑缺陷造成的.您正在假设程序中的所有C++动态内存管理操作都将委派给标准C库工具 malloc,calloc并且free.

好吧,至少其中一些是,特别是对它的调用operator new 起源于

heap_memory_map[pointer_handle] = size;
Run Code Online (Sandbox Code Playgroud)

当您分配一个新的堆映射元素时,委派给malloc.这是你的 malloc.再次呼吁:

heap_memory_map[pointer_handle] = size;
Run Code Online (Sandbox Code Playgroud)

然后operator new,然后回到malloc,等等到堆栈耗尽.

这是致命的逻辑缺陷,但激励的假设也是不稳定的.C++标准甚至不需要operator new和的默认实现和 operator delete分别委托给mallocfree.它没有指定C++中的动态内存管理与C的动态内存管理之间的任何关系.我在这里使用的C++编译器(Linux,GCC)实际上是委托,也可能是你的,但是实现者可能会选择委托双方 malloc/freenew/delete直接向操作系统的API.

不要尝试滚动自己的堆分析.使用适当的堆分析器.对于linux,首选 堆分析器是Valgrind的massif.您的发行版几乎肯定会提供Valgrind包,包括massif.

这是一个我将要分析的程序,massif并检查它的最大堆使用情况:

main.cpp中

#include <vector>
#include <iostream>

using namespace std;

int main() {
    vector<int> vec;
    for (int i = 0; i < 1000; ++i) {
        vec.push_back(i);
    }
    for (   ;vec.size(); vec.pop_back()) {}
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译和链接:

$ g++ -g -o prog -Wall main.cpp
Run Code Online (Sandbox Code Playgroud)

运行valgrindmassif:

$ valgrind --tool=massif ./prog
==6479== Massif, a heap profiler
==6479== Copyright (C) 2003-2015, and GNU GPL'd, by Nicholas Nethercote
==6479== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==6479== Command: ./prog
==6479== 
==6479==
Run Code Online (Sandbox Code Playgroud)

默认情况下输出堆配置文件massif.out.NNNN.我发现massif.out.6479 并运行:

$ ms_print massif.out.6479 > heap_prof.txt
Run Code Online (Sandbox Code Playgroud)

我查看heap_prof.txt并在第32行读到:

Number of snapshots: 29
 Detailed snapshots: [4, 14, 17, 20, 23, 26 (peak)] 
Run Code Online (Sandbox Code Playgroud)

这告诉我堆快照#26显示峰值使用情况.我滚动到快照#26并看到:

--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 24      2,049,029           74,768           74,752            16            0
 25      2,069,629           78,872           78,848            24            0
 26      2,070,679           78,872           78,848            24            0
99.97% (78,848B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->92.18% (72,704B) 0x4EB91FE: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| ->92.18% (72,704B) 0x4010608: call_init.part.0 (dl-init.c:72)
|   ->92.18% (72,704B) 0x4010719: _dl_init (dl-init.c:30)
|     ->92.18% (72,704B) 0x4000D08: ??? (in /lib/x86_64-linux-gnu/ld-2.21.so)
|       
->07.79% (6,144B) 0x401788: __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) (new_allocator.h:104)
  ->07.79% (6,144B) 0x401665: __gnu_cxx::__alloc_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) (alloc_traits.h:182)
    ->07.79% (6,144B) 0x4014B0: std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) (stl_vector.h:170)
      ->07.79% (6,144B) 0x400F59: std::vector<int, std::allocator<int> >::_M_insert_aux(__gnu_cxx::__normal_iterator<int*, std::vector<int, std::allocator<int> > >, int const&) (vector.tcc:353)
        ->07.79% (6,144B) 0x400CC4: std::vector<int, std::allocator<int> >::push_back(int const&) (stl_vector.h:925)
          ->07.79% (6,144B) 0x400AEC: main (main.cpp:9)
Run Code Online (Sandbox Code Playgroud)

所以该程序的最高记录堆消耗为78,872字节,其中(仅仅)为我分配了6,144字节std::vector.