Skip to content

Runtime dynamic memory tracing #2487

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 26, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
289 changes: 289 additions & 0 deletions TESTS/mbed_drivers/mem_trace/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
/*
* Copyright (c) 2013-2016, ARM Limited, All Rights Reserved
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "mbed.h"
#include "greentea-client/test_env.h"
#include "unity/unity.h"
#include "utest/utest.h"
#include "mbed_mem_trace.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>

#ifndef MBED_MEM_TRACING_ENABLED
#error [NOT_SUPPORTED] test not supported
#endif

using namespace utest::v1;

/******************************************************************************/
/* Helper functions and data structures */
/******************************************************************************/

// This structure keeps data about the various memory allocation operations,
// as traced by 'test_trace_cb' below.
#define TEST_MAX_MEMORY_OPS 10
// Trace results for all possible operations
typedef struct {
uint8_t op;
void *res;
union {
struct {
size_t arg_size;
} malloc_info;
struct {
void *arg_ptr;
size_t arg_size;
} realloc_info;
struct {
size_t arg_nmemb;
size_t arg_size;
} calloc_info;
struct {
void *arg_ptr;
} free_info;
};
} mem_trace_data_t;
// Memory operation statistics
typedef struct {
mem_trace_data_t op_data[TEST_MAX_MEMORY_OPS];
uint32_t total_ops;
bool invalid_op, overflow;
} stats_t;
static stats_t stats;

// Clear all the memory statistics
static void test_clear_stats() {
memset(&stats, 0, sizeof(stats));
}

// Memory tracer callback that records each operation in "stats" (above)
extern "C" void test_trace_cb(uint8_t op, void *res, void *caller, ...) {
va_list va;
mem_trace_data_t *pmem = stats.op_data + stats.total_ops;

if (stats.total_ops >= TEST_MAX_MEMORY_OPS) {
stats.overflow = true;
return;
}
va_start(va, caller);
pmem->op = op;
pmem->res = res;
switch(op) {
case MBED_MEM_TRACE_MALLOC:
pmem->malloc_info.arg_size = va_arg(va, size_t);
break;

case MBED_MEM_TRACE_REALLOC:
pmem->realloc_info.arg_ptr = va_arg(va, void *);
pmem->realloc_info.arg_size = va_arg(va, size_t);
break;

case MBED_MEM_TRACE_CALLOC:
pmem->calloc_info.arg_nmemb = va_arg(va, size_t);
pmem->calloc_info.arg_size = va_arg(va, size_t);
break;

case MBED_MEM_TRACE_FREE:
pmem->free_info.arg_ptr = va_arg(va, void *);
break;

default:
stats.invalid_op = true;
}
stats.total_ops ++;
va_end(va);
}

// Generic sanity checks for the tracer
static void check_sanity(uint32_t expected_ops) {
TEST_ASSERT_FALSE(stats.overflow);
TEST_ASSERT_FALSE(stats.invalid_op);
TEST_ASSERT_EQUAL_UINT32(stats.total_ops, expected_ops);
}

// Check a "malloc" operation
static void check_malloc_op(const mem_trace_data_t *p, void *expected_res, size_t expected_arg_size) {
TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_MALLOC);
TEST_ASSERT_EQUAL_PTR(p->res, expected_res);
TEST_ASSERT_EQUAL_UINT32(p->malloc_info.arg_size, expected_arg_size);
}

// Check a "free" operation
static void check_free_op(const mem_trace_data_t *p, void *expected_arg_ptr) {
TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_FREE);
TEST_ASSERT_EQUAL_PTR(p->free_info.arg_ptr, expected_arg_ptr);
}

// Check a "realloc" operation
static void check_realloc_op(const mem_trace_data_t *p, void *expected_res, void *expected_arg_ptr, size_t expected_arg_size) {
TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_REALLOC);
TEST_ASSERT_EQUAL_PTR(p->res, expected_res);
TEST_ASSERT_EQUAL_UINT32(p->realloc_info.arg_ptr, expected_arg_ptr);
TEST_ASSERT_EQUAL_UINT32(p->realloc_info.arg_size, expected_arg_size);
}

// Check a "calloc" operation
static void check_calloc_op(const mem_trace_data_t *p, void *expected_res, size_t expected_arg_nmemb, size_t expected_arg_size) {
TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_CALLOC);
TEST_ASSERT_EQUAL_PTR(p->res, expected_res);
TEST_ASSERT_EQUAL_UINT32(p->calloc_info.arg_nmemb, expected_arg_nmemb);
TEST_ASSERT_EQUAL_UINT32(p->calloc_info.arg_size, expected_arg_size);
}

/******************************************************************************/
/* Tests */
/******************************************************************************/

// Allocate a single buffer, then free it. Check that tracing matches the operations.
static void test_case_single_malloc_free() {
const size_t block_size = 126;
const mem_trace_data_t *pmem = stats.op_data;

test_clear_stats();
mbed_mem_trace_set_callback(test_trace_cb);
// Allocate a single memory block
void *p = malloc(block_size);
TEST_ASSERT_NOT_EQUAL(p, NULL);
// Free the memory block
free(p);
// Stop tracing
mbed_mem_trace_set_callback(NULL);
// Check tracer result
check_sanity(2);
check_malloc_op(pmem ++, p, block_size);
check_free_op(pmem, p);
}

// Test all memory operations (malloc, realloc, free, calloc)
static void test_case_all_memory_ops() {
const size_t malloc_size = 40, realloc_size = 80, nmemb = 25, size = 10;
const mem_trace_data_t *pmem = stats.op_data;

test_clear_stats();
mbed_mem_trace_set_callback(test_trace_cb);
// Allocate a single memory block, the realloc it
void *p_malloc = malloc(malloc_size);
TEST_ASSERT_NOT_EQUAL(p_malloc, NULL);
void *p_realloc = realloc(p_malloc, realloc_size);
TEST_ASSERT_NOT_EQUAL(p_realloc, NULL);
// Use calloc() now
void *p_calloc = calloc(nmemb, size);
//TEST_ASSERT_NOT_EQUAL(p_calloc, NULL);
// Free the realloc() pointer first, then the calloc() one
free(p_realloc);
free(p_calloc);
// Stop tracing
mbed_mem_trace_set_callback(NULL);
// Check tracer result
check_sanity(6);
check_malloc_op(pmem ++, p_malloc, malloc_size);
check_realloc_op(pmem ++, p_realloc, p_malloc, realloc_size);
// calloc() calls malloc() internally
check_malloc_op(pmem ++, p_calloc, nmemb * size);
check_calloc_op(pmem ++, p_calloc, nmemb, size);
check_free_op(pmem ++, p_realloc);
check_free_op(pmem, p_calloc);
}

// Test that tracing is off when using a NULL callback
static void test_case_trace_off() {
const size_t malloc_size = 10;

test_clear_stats();
// We don't want any tracing
mbed_mem_trace_set_callback(NULL);
// Allocate a buffer and free it
void *p_malloc = malloc(malloc_size);
TEST_ASSERT_NOT_EQUAL(p_malloc, NULL);
free(p_malloc);
// Check that we didn't trace anything
check_sanity(0);
}

// Test partial tracing (start tracing, stop tracing, restart later)
static void test_case_partial_trace() {
const size_t malloc_size_1 = 20, malloc_size_2 = 30;
const mem_trace_data_t *pmem = stats.op_data;

test_clear_stats();
// Start tracing
mbed_mem_trace_set_callback(test_trace_cb);
// Allocate a buffer
void *p_malloc_1 = malloc(malloc_size_1);
TEST_ASSERT_NOT_EQUAL(p_malloc_1, NULL);
// Disable tracing before freeing the first buffer
mbed_mem_trace_set_callback(NULL);
free(p_malloc_1);
// Allocate another buffer (still not traced)
void *p_malloc_2 = malloc(malloc_size_2);
TEST_ASSERT_NOT_EQUAL(p_malloc_2, NULL);
// Re-enable tracing
mbed_mem_trace_set_callback(test_trace_cb);
// And free the second buffer (this operation should be tracer)
free(p_malloc_2);
// Stop tracing
mbed_mem_trace_set_callback(NULL);
// Check tracer result
check_sanity(2);
check_malloc_op(pmem ++, p_malloc_1, malloc_size_1);
check_free_op(pmem, p_malloc_2);
}

// Test new/delete tracing
static void test_case_new_delete() {
const mem_trace_data_t *pmem = stats.op_data;

test_clear_stats();
// Start tracing
mbed_mem_trace_set_callback(test_trace_cb);
// Test new, new[], delete and delete[]
int *p_int = new int;
int *p_int_array = new int[10];
delete p_int;
delete[] p_int_array;
// Stop tracing
mbed_mem_trace_set_callback(NULL);
// Check tracer result
check_sanity(4);
check_malloc_op(pmem ++, p_int, sizeof(int));
check_malloc_op(pmem ++, p_int_array, 10 * sizeof(int));
check_free_op(pmem ++, p_int);
check_free_op(pmem ++, p_int_array);
}

static Case cases[] = {
Case("single malloc/free", test_case_single_malloc_free),
Case("all memory operations", test_case_all_memory_ops),
Case("trace off", test_case_trace_off),
Case("partial trace", test_case_partial_trace),
Case("test new/delete", test_case_new_delete)
};

static status_t greentea_test_setup(const size_t number_of_cases) {
GREENTEA_SETUP(20, "default_auto");
return greentea_test_setup_handler(number_of_cases);
}

static Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);

int main() {
// Disable stdout buffering to prevent any unwanted allocations
setvbuf(stdout, NULL, _IONBF, 0);
Harness::run(specification);
}

Loading