Android系统malloc_debug的使用

一.malloc_debug简介
android 的libc中有malloc_debug的hook调用,具体android源码/bionic/libc/malloc_debug下,我们可以使用malloc_debug中的hook函数对内存分配进行跟踪加测。

malloc_debug主要包含的功能如下:
1.内存分配和释放跟踪,支持的函数如下:

When malloc debug is enabled, it works by adding a shim layer that replaces
the normal allocation calls. The replaced calls are:

* `malloc`
* `free`
* `calloc`
* `realloc`
* `posix_memalign`
* `memalign`
* `malloc_usable_size`

2 支持内存边界,可以在申请的内存头部和尾部添加guard,内存越界检查,use after free,内存崩溃检查等.
3 调用栈跟踪和打印,跟踪内存分配的同时保存内存分配的调用栈,方便内存泄漏检查.

二.malloc_debug的使用
1.stop
adb shell stop
2 开启malloc_debug
adb shell setprop libc.debug.malloc.program app_process
3 设置相关检测项
adb shell setprop libc.debug.malloc.options backtrace
4 start
adb shell start

三 malloc_debug原理
/bionic/libc/Android.bp
分析bp文件可以得知我们libc.so中包含了/bionic/libc/bionic/malloc_common.cpp

// ========================================================
// libc.a + libc.so
// ========================================================
cc_library {
    defaults: ["libc_defaults"],
    name: "libc",
    product_variables: {
        platform_sdk_version: {
            asflags: ["-DPLATFORM_SDK_VERSION=%d"],
        },
    },
    static: {
        srcs: [
            "bionic/dl_iterate_phdr_static.cpp",
            "bionic/icu_static.cpp",
            "bionic/malloc_common.cpp",
            "bionic/libc_init_static.cpp",
        ],
        cflags: ["-DLIBC_STATIC"],
        whole_static_libs: ["libc_init_static", "libjemalloc"],
    },
    shared: {
        srcs: [
            "arch-common/bionic/crtbegin_so.c",
            "arch-common/bionic/crtbrand.S",
            "bionic/icu.cpp",
            "bionic/malloc_common.cpp",
            "bionic/NetdClient.cpp",
            "arch-common/bionic/crtend_so.S",
        ],
        whole_static_libs: ["libc_init_dynamic"],
    },
......
}

libc在初始化时会调用malloc_init_impl判断属性来加载libc_malloc_debug.so,调用InitMallocFunctions替换掉Libc原生的内存分配和释放函数。

malloc_comon.cpp

static const char* DEBUG_SHARED_LIB = "libc_malloc_debug.so";
static const char* DEBUG_MALLOC_PROPERTY_OPTIONS = "libc.debug.malloc.options";
static const char* DEBUG_MALLOC_PROPERTY_PROGRAM = "libc.debug.malloc.program";

// __libc_init会调用__libc_init_malloc,进而调到malloc_init_impl进行初始化
// Initializes memory allocation framework.
// This routine is called from __libc_init routines in libc_init_dynamic.cpp.
__LIBC_HIDDEN__ void __libc_init_malloc(libc_globals* globals) {
  malloc_init_impl(globals);
}

// Initializes memory allocation framework once per process.
static void malloc_init_impl(libc_globals* globals) {
  char value[PROP_VALUE_MAX];

  // If DEBUG_MALLOC_ENV_OPTIONS is set then it overrides the system properties.
  const char* options = getenv(DEBUG_MALLOC_ENV_OPTIONS);
  if (options == nullptr || options[0] == '\0') {
  //判断libc.debug.malloc.options属性如果为空,则直接返回
    if (__system_property_get(DEBUG_MALLOC_PROPERTY_OPTIONS, value) == 0 || value[0] == '\0') {
      return;
    }
    options = value;

    // Check to see if only a specific program should have debug malloc enabled.
    char program[PROP_VALUE_MAX];
    //判断libc.debug.malloc.program不为空,并且program为null,此时也是直接返回。
    if (__system_property_get(DEBUG_MALLOC_PROPERTY_PROGRAM, program) != 0 &&
        strstr(getprogname(), program) == nullptr) {
      return;
    }
  }

  //如果进行了属性设置,则将利用libc_malloc_debug.so库中的部分函数替换libc.so的相关接口。
  // Load the debug malloc shared library.
  void* malloc_impl_handle = dlopen(DEBUG_SHARED_LIB, RTLD_NOW | RTLD_LOCAL);
  if (malloc_impl_handle == nullptr) {
    error_log("%s: Unable to open debug malloc shared library %s: %s",
              getprogname(), DEBUG_SHARED_LIB, dlerror());
    return;
  }

  // Initialize malloc debugging in the loaded module.
  auto init_func = reinterpret_cast<bool (*)(const MallocDispatch*, int*, const char*)>(
      dlsym(malloc_impl_handle, "debug_initialize"));
  if (init_func == nullptr) {
    error_log("%s: debug_initialize routine not found in %s", getprogname(), DEBUG_SHARED_LIB);
    dlclose(malloc_impl_handle);
    return;
  }

  // Get the syms for the external functions.
  void* finalize_sym = dlsym(malloc_impl_handle, "debug_finalize");
  if (finalize_sym == nullptr) {
    error_log("%s: debug_finalize routine not found in %s", getprogname(), DEBUG_SHARED_LIB);
    dlclose(malloc_impl_handle);
    return;
  }

  void* get_leak_info_sym = dlsym(malloc_impl_handle, "debug_get_malloc_leak_info");
  if (get_leak_info_sym == nullptr) {
    error_log("%s: debug_get_malloc_leak_info routine not found in %s", getprogname(),
              DEBUG_SHARED_LIB);
    dlclose(malloc_impl_handle);
    return;
  }
int __system_property_get(const char *name, char *value)
{
    const prop_info *pi = __system_property_find(name);
    if(pi != 0) {
        return __system_property_read(pi, 0, value);
    } else {
        value[0] = 0;
        return 0;
    }
}

四 malloc_debug option选项
1 内存边界检查
front_guard[=SIZE_BYTES]Enables a small buffer placed before the allocated data.
rear_guard[=SIZE_BYTES] Enables a small buffer placed after the allocated data.
guard[=SIZE_BYTES] Enables both a front guard and a rear guard on all allocations.
主要原理是在分配内存的头部和尾部添加一段数据,作为边界,头部初始化为0xaa,尾部初始化为0xbb。

2 调用栈功能
backtrace[=MAX_FRAMES]
backtrace_enable_on_signal[=MAX_FRAMES]
backtrace_dump_on_exit
backtrace_dump_prefix
backtrace_full
设置保存的调用栈个数,在信号量或者退出时打印调用栈

3 malloc内存默认值
fill_on_alloc[=MAX_FILLED_BYTES] size will be set to 0xeb.
fill_on_free[=MAX_FILLED_BYTES] When an allocation is freed, fill it with 0xef.
fill[=MAX_FILLED_BYTES] This enables both the fill_on_alloc option and the fill_on_free option.
expand_alloc[=EXPAND_BYTES] Add an extra amount to allocate for every allocation.

4 释放内存存档
free_track[=ALLOCATION_COUNT] 默认值是100,最大值是16384
free_track_backtrace_num_frames[=MAX_FRAMES]

5 分配释放检测
leak_track 在进程退出时,执行finalize函数,打印当前分配的内存
record_allocs[=TOTAL_ENTRIES] 记录alloc操作,The default value is 8,000,000 and the maximum value this can be set to is 50,000,000.
record_allocs_file[=FILE_NAME] 设置record_allocs保存地址
verify_pointers free/malloc_usable_size/realloc 有效检查
abort_on_error When malloc debug detects an error, abort after sending the error log message.内存泄漏检测不在此处,只在进程退出时检测

6 verbose 开启debug info log,如果要看更多的信息,建议开启此选项

五 常见用法
1 内存泄漏检测
在shell命令下执行 #setprop libc.debug.malloc.options “backtrace leak_track verbose”
这样开启后在进程退出时会打印leak信息,在发送kill -47时会打印当前内存申请

2 内存崩溃检查
在shell命令中添加guard #setprop libc.debug.malloc.options “backtrace leak_track verbose guard”
这样会检测内存覆盖等检测

3 verify_pointers 开启可以检测use after free和double free等操作

六.malloc问题
1.申请后多次释放 (double free)
2 释放后又去使用 (used after free)
3 使用越界 (比如申请了50节内存,结果在使用时多用了8字节的内存,这样就把后面的内存的内容踩坏,引起堆结构异常)
4 释放时传给free()的地址不是malloc()申请的地址,比如:p = malloc(10); free(p + 5);
5 内存泄露:申请内存后,忘记释放或某些代码路径没有释放。

七.实际运用
1.front_guard[=SIZE_BYTES]
每次调用malloc,都在分配的区域之前填充SIZE_BYTES,填充内容为0xaa
例子:setprop libc.debug.malloc.options front_guard=16

2.backtrace[=MAX_FRAMES]
MAX_FRAMES 最大值256 默认值16

每次在调用malloc时,malloc debug 都会记录malloc的调用栈(trace),栈的最大深度为MAX_FRAMES,MAX_FRAMES 越大,对malloc的性能影响越大。当进程收到信号(SIGRTMAX - 17)Android通常该信号值为47时,会触发malloc debug 的dump heap trace功能。默认dump路径在/data/local/tmp/ backtrace_heap.PID.txt。
给进程发信号通过kill -s 47 PID,进程收到信号后并不会马上dump backtrace,二是会等到下次调用malloc 或者 free时才会触发。所以如果发送信号后没有产生trace文件,请继续针对调试的进程做测试。

举例:
setprop libc.debug.malloc.options backtrace=5

3.backtrace_enable_on_signal[=MAX_FRAMES]
使能这个选项,通过给进程发送信号 45,可以动态开启和关闭backtrace 功能。

4.backtrace_dump_on_exit
进程退出后自动dump trace 文件,得到的dump文件为 $pid.exit.txt 结尾

5.backtrace_dump_prefix
trace dump的路径,如果需要放置其他目录,如/sdcard/heap, 则dump的文件路径为/sdcard/heap.$PID.txt

6.leak_track
程序结束后,如果有未free的指针,logcat中会打印出来

7.record_allocs[=TOTAL_ENTRIES],该选项很占内存,建议不开启
对进程中使用malloc, calloc, realloc 的地方进行记录,打印的格式如下
Threadid: action pointer size
471: malloc 0x72e00330c0 6
471: realloc 0x72e0005220 0x72e00330c0 12
471: free 0x72e012fcc0
471: free 0x72e0005220
471: malloc 0x72e01ade40 56
471: malloc 0x72e00330c0 6
注意,最大记录8,000,000 条

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Introduction The usual implementation of malloc and free are unforgiving to errors in their callers' code, including cases where the programmer overflows an array, forgets to free memory, or frees a memory block twice. This often does not affect the program immediately, waiting until the corrupted memory is used later (in the case of overwrites) or gradually accumulating allocated but unused blocks. Thus, debugging can be extremely difficult. In this assignment, you will write a wrapper for the malloc package that will catch errors in the code that calls malloc and free. The skills you will have learned upon the completion of this exercise are pointer arithmetic and a greater understanding of the consequences of subtle memory mistakes. Logistics Unzip debugging_malloc.zip into an empty directory. The files contained are as follows: File(s): Function: debugmalloc.c Contains the implementation of the three functions you will be writing. This is the one file you will be editing and handing in. debugmalloc.h Contains the declaration of the functions, as well as the macros that will call them. driver.c Contains main procedure and the code that will be calling the functions in the malloc package dmhelper.c, dmhelper.h Contain the helper functions and macros that you will be calling from your code grader.pl Perl script that runs your code for the various test cases and gives you feedback based on your current code debugmalloc.dsp Exercise 3 project file debugmalloc.dsw Exercise 3 workspace file tailor.h, getopt.c, getopt.h Tools that are used only by the driver program for I/O purposes. You will not need to know what the code in these files do. Others Required by Visual C++. You do not need to understand their purpose Specification Programs that use this package will call the macros MALLOC and FREE. MALLOC and FREE are used exactly the same way as the malloc() and free() functions in the standard C malloc package. That is, the line void *ptr = MALLOC ( n ) ;will allocate a payload of at least n bytes, and ptr will point to the front of this block. The line FREE(ptr);will cause the payload pointed to by ptr to be deallocated and become available for later use. The macros are defined as follows: #define MALLOC(s) MyMalloc(s, __FILE__, __LINE__) #define FREE(p) MyFree(p, __FILE__, __LINE__) The __FILE__ macro resolves to the filename and __LINE__ resolves to the current line number. The debugmalloc.c file contains three functions that you are required to implement, as shown: void *MyMalloc(size_t size, char *filename, int linenumber); void MyFree(void *ptr, char *filename, int linenumber); int AllocatedSize(); Using the macros above allow MyMalloc and MyFree to be called with the filename and line number of the actual MALLOC and FREE calls, while retaining the same form as the usual malloc package. By default, MyMalloc and MyFree() simply call malloc() and free(), respectively, and return immediately. AllocatedSize() should return the number of bytes currently allocated by the user: the sum of the requested bytes through MALLOC minus the bytes freed using FREE. By default, it simply returns 0 and thus is unimplemented. The definitions are shown below: void *MyMalloc(size_t size, char *filename, int linenumber) { return (malloc(size)); } void MyFree(void *ptr, char *filename, int linenumber) { free(ptr); } int AllocatedSize() { return 0; } Your job is to modify these functions so that they will catch a number of errors that will be described in the next section. There are also two optional functions in the debugmalloc.c file that you can implement: void PrintAllocatedBlocks(); int HeapCheck(); PrintAllocatedBlocks should print out information about all currently allocated blocks. HeapCheck should check all the blocks for possible memory overwrites. Implementation Details To catch the errors, you will allocate a slightly larger amount of space and insert a header and a footer around the "requested payload". MyMalloc() will insert information into this area, and MyFree() will check to see if the information has not changed. The organization of the complete memory block is as shown below: Header Checksum ... Fence Payload Footer Fence Note:MyMalloc() returns a pointer to the payload, not the beginning of the whole block. Also, the ptr parameter passed into MyFree(void *ptr) will point to the payload, not the beginning of the block. Information that you might want to store in this extra (header, footer) area include: a "fence" immediately around the requested payload with a known value like 0xCCDEADCC, so that you can check if it has been changed when the block is freed. the size of the block a checksum for the header to ensure that it has not been corrupted (A checksum of a sequence of bits is calculated by counting the number of "1" bits in the stream. For example, the checksum for "1000100010001000" is 4. It is a simple error detection mechanism.) the filename and line number of the MALLOC() call The errors that can occur are: Error #1: Writing past the beginning of the user's block (through the fence) Error #2: Writing past the end of the user's block (through the fence) Error #3: Corrupting the header information Error #4: Attempting to free an unallocated or already-freed block Error #5: Memory leak detection (user can use ALLOCATEDSIZE to check for leaks at the end of the program) To report the first four errors, call one of these two functions: void error(int errorcode, char *filename, int linenumber); errorcode is the number assigned to the error as stated above. filename and linenumber contain the filename and line number of the line (the free call) in which the error is invoked. For example, call error(2, filename, linenumber) if you come across a situation where the footer fence has been changed. void errorfl(int errorcode, char *filename_malloc, int linenumber_malloc, char *filename_free, int linenumber_free); This is the same as the error(), except there are two sets of filenames and line numbers, one for the statement in which the block was malloc'd, and the other for the statement in which the block was free'd (and the error was invoked). The fact that MyMalloc() and MyFree() are given the filename and line number of the MALLOC() and FREE() call can prove to be very useful when you are reporting errors. The more information you print out, the easier it will be for the programmer to locate the error. Use errorfl() instead of error() whenever possible. errorfl() obviously cannot be used on situations where FREE() is called on an unallocated block, since it was not ever MALLOC'd. Note: You will only be reporting errors from MyFree(). None of the errors can be caught in MyMalloc() In the case of memory leaks, the driver program will call AllocatedSize(), and the grader will look at its return value and possible output. AllocatedSize() should return the number of bytes currently allocated from MALLOC and FREE calls. For example, the code segment: void *ptr1 = MALLOC(10), *ptr2 = MALLOC(8); FREE(ptr2); printf("%d\n", AllocatedSize()); should print out "10". Once you have gotten to the point where you can catch all of the errors, you can go an optional step further and create a global list of allocated blocks. This will allow you to perform analysis of memory leaks and currently allocated memory. You can implement the void PrintAllocatedBlocks() function, which prints out the filename and line number where all currently allocated blocks were MALLOC()'d. A macro is provided for you to use to print out information about a single block in a readable and gradeable format: PRINTBLOCK(int size, char *filename, int linenumber) Also, you can implement the int HeapCheck() function. This should check all of the currently allocated blocks and return -1 if there is an error and 0 if all blocks are valid. In addition, it should print out the information about all of the corrupted blocks, using the macro #define PRINTERROR(int errorcode, char *filename, int linenumber), with errorcode equal to the error number (according to the list described earlier) the block has gone through. You may find that this global list can also allow you to be more specific in your error messages, as it is otherwise difficult to determine the difference between an overwrite of a non-payload area and an attempted FREE() of an unallocated block. Evaluation You are given 7 test cases to work with, plus 1 extra for testing a global list. You can type "debugmalloc -t n" to run the n-th test. You can see the code that is being run in driver.c. If you have Perl installed on your machine, use grader.pl to run all the tests and print out a table of results. There are a total of 100 possible points. Here is a rundown of the test cases and desired output (do not worry about the path of the filename): Test case #1 Code char *str = (char *) MALLOC(12); strcpy(str, "123456789"); FREE(str); printf("Size: %d\n", AllocatedSize()); PrintAllocatedBlocks(); Error # None Correct Output Size: 0 Points worth 10 Details 10 points for not reporting an error and returning 0 in AllocatedSize() Test case #2 Code char *str = (char *) MALLOC(8); strcpy(str, "12345678"); FREE(str); Error # 2 Correct Output Error: Ending edge of the payload has been overwritten. in block allocated at driver.c, line 21 and freed at driver.c, line 23 Points worth 15 Details 6 pts for catching error 3 pts for printing the filename/line numbers 6 pts for correct error message Test case #3 Code char *str = (char *) MALLOC(2); strcpy(str, "12"); FREE(str); Error # 2 Correct Output Error: Ending edge of the payload has been overwritten. in block allocated at driver.c, line 28 and freed at driver.c, line 30 Points worth 15 Details 6 pts for catching error 3 pts for printing the filename/line numbers 6 pts for correct error message Test case #4 Code void *ptr = MALLOC(4); *ptr2 = MALLOC(6); FREE(ptr); printf("Size: %d\n", AllocatedSize()); PrintAllocatedBlocks(); Error # None Correct Output Size: 6 Currently allocated blocks: 6 bytes, created at driver.c, line 34 Points worth 15 Details 15 pts for not reporting an error and returning 6 from AllocatedSize Extra for printing out the extra block Test case #5 Code void *ptr = MALLOC(4); FREE(ptr); FREE(ptr); Error # 4 Correct Output Error: Attempting to free an unallocated block. in block freed at driver.c, line 43 Points worth 15 Details 15 pts for catching error Extra for correct error message Test case #6 Code char *ptr = (char *) MALLOC(4); *((int *) (ptr - 8)) = 8 + (1 << 31); FREE(ptr); Error # 1 or 3 Correct Output Error: Header has been corrupted.or Error: Starting edge of the payload has been overwritten. in block allocated at driver.c, line 47 and freed at driver.c, line 49 Points worth 15 Details 9 pts for catching error 6 pts for a correct error message Test case #7 Code char ptr[5]; FREE(ptr); Error # 4 Correct Output Error: Attempting to free an unallocated block. in block freed at driver.c, line 54 Points worth 15 Details 15 pts for recognizing error Extra for printing correct error message Test case #8 (Optional) Code int i; int *intptr = (int *) MALLOC(6); char *str = (char *) MALLOC(12); for(i = 0; i < 6; i++) { intptr[i] = i; } if (HeapCheck() == -1) { printf("\nCaught Errors\n"); } Error # None Correct Output Error: Ending edge of the payload has been overwritten. Invalid block created at driver.c, line 59 Caught Errors Points worth Extra Details "Caught Errors" indicates that the HeapCheck() function worked correctly. Extra points possible. Your instructor may give you extra credit for implementing a global list and the PrintAllocatedBlocks() and HeapCheck() functions.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值