Skip to content

Commit a3a9daf

Browse files
authored
Merge pull request #2487 from ARMmbed/memory_instrumentation
Runtime dynamic memory tracing
2 parents a7c7ced + 8b908ab commit a3a9daf

File tree

6 files changed

+894
-201
lines changed

6 files changed

+894
-201
lines changed

TESTS/mbed_drivers/mem_trace/main.cpp

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
/*
2+
* Copyright (c) 2013-2016, ARM Limited, All Rights Reserved
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
* not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
#include "mbed.h"
19+
#include "greentea-client/test_env.h"
20+
#include "unity/unity.h"
21+
#include "utest/utest.h"
22+
#include "mbed_mem_trace.h"
23+
#include <stdlib.h>
24+
#include <stdio.h>
25+
#include <stdarg.h>
26+
27+
#ifndef MBED_MEM_TRACING_ENABLED
28+
#error [NOT_SUPPORTED] test not supported
29+
#endif
30+
31+
using namespace utest::v1;
32+
33+
/******************************************************************************/
34+
/* Helper functions and data structures */
35+
/******************************************************************************/
36+
37+
// This structure keeps data about the various memory allocation operations,
38+
// as traced by 'test_trace_cb' below.
39+
#define TEST_MAX_MEMORY_OPS 10
40+
// Trace results for all possible operations
41+
typedef struct {
42+
uint8_t op;
43+
void *res;
44+
union {
45+
struct {
46+
size_t arg_size;
47+
} malloc_info;
48+
struct {
49+
void *arg_ptr;
50+
size_t arg_size;
51+
} realloc_info;
52+
struct {
53+
size_t arg_nmemb;
54+
size_t arg_size;
55+
} calloc_info;
56+
struct {
57+
void *arg_ptr;
58+
} free_info;
59+
};
60+
} mem_trace_data_t;
61+
// Memory operation statistics
62+
typedef struct {
63+
mem_trace_data_t op_data[TEST_MAX_MEMORY_OPS];
64+
uint32_t total_ops;
65+
bool invalid_op, overflow;
66+
} stats_t;
67+
static stats_t stats;
68+
69+
// Clear all the memory statistics
70+
static void test_clear_stats() {
71+
memset(&stats, 0, sizeof(stats));
72+
}
73+
74+
// Memory tracer callback that records each operation in "stats" (above)
75+
extern "C" void test_trace_cb(uint8_t op, void *res, void *caller, ...) {
76+
va_list va;
77+
mem_trace_data_t *pmem = stats.op_data + stats.total_ops;
78+
79+
if (stats.total_ops >= TEST_MAX_MEMORY_OPS) {
80+
stats.overflow = true;
81+
return;
82+
}
83+
va_start(va, caller);
84+
pmem->op = op;
85+
pmem->res = res;
86+
switch(op) {
87+
case MBED_MEM_TRACE_MALLOC:
88+
pmem->malloc_info.arg_size = va_arg(va, size_t);
89+
break;
90+
91+
case MBED_MEM_TRACE_REALLOC:
92+
pmem->realloc_info.arg_ptr = va_arg(va, void *);
93+
pmem->realloc_info.arg_size = va_arg(va, size_t);
94+
break;
95+
96+
case MBED_MEM_TRACE_CALLOC:
97+
pmem->calloc_info.arg_nmemb = va_arg(va, size_t);
98+
pmem->calloc_info.arg_size = va_arg(va, size_t);
99+
break;
100+
101+
case MBED_MEM_TRACE_FREE:
102+
pmem->free_info.arg_ptr = va_arg(va, void *);
103+
break;
104+
105+
default:
106+
stats.invalid_op = true;
107+
}
108+
stats.total_ops ++;
109+
va_end(va);
110+
}
111+
112+
// Generic sanity checks for the tracer
113+
static void check_sanity(uint32_t expected_ops) {
114+
TEST_ASSERT_FALSE(stats.overflow);
115+
TEST_ASSERT_FALSE(stats.invalid_op);
116+
TEST_ASSERT_EQUAL_UINT32(stats.total_ops, expected_ops);
117+
}
118+
119+
// Check a "malloc" operation
120+
static void check_malloc_op(const mem_trace_data_t *p, void *expected_res, size_t expected_arg_size) {
121+
TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_MALLOC);
122+
TEST_ASSERT_EQUAL_PTR(p->res, expected_res);
123+
TEST_ASSERT_EQUAL_UINT32(p->malloc_info.arg_size, expected_arg_size);
124+
}
125+
126+
// Check a "free" operation
127+
static void check_free_op(const mem_trace_data_t *p, void *expected_arg_ptr) {
128+
TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_FREE);
129+
TEST_ASSERT_EQUAL_PTR(p->free_info.arg_ptr, expected_arg_ptr);
130+
}
131+
132+
// Check a "realloc" operation
133+
static void check_realloc_op(const mem_trace_data_t *p, void *expected_res, void *expected_arg_ptr, size_t expected_arg_size) {
134+
TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_REALLOC);
135+
TEST_ASSERT_EQUAL_PTR(p->res, expected_res);
136+
TEST_ASSERT_EQUAL_UINT32(p->realloc_info.arg_ptr, expected_arg_ptr);
137+
TEST_ASSERT_EQUAL_UINT32(p->realloc_info.arg_size, expected_arg_size);
138+
}
139+
140+
// Check a "calloc" operation
141+
static void check_calloc_op(const mem_trace_data_t *p, void *expected_res, size_t expected_arg_nmemb, size_t expected_arg_size) {
142+
TEST_ASSERT_EQUAL_UINT8(p->op, MBED_MEM_TRACE_CALLOC);
143+
TEST_ASSERT_EQUAL_PTR(p->res, expected_res);
144+
TEST_ASSERT_EQUAL_UINT32(p->calloc_info.arg_nmemb, expected_arg_nmemb);
145+
TEST_ASSERT_EQUAL_UINT32(p->calloc_info.arg_size, expected_arg_size);
146+
}
147+
148+
/******************************************************************************/
149+
/* Tests */
150+
/******************************************************************************/
151+
152+
// Allocate a single buffer, then free it. Check that tracing matches the operations.
153+
static void test_case_single_malloc_free() {
154+
const size_t block_size = 126;
155+
const mem_trace_data_t *pmem = stats.op_data;
156+
157+
test_clear_stats();
158+
mbed_mem_trace_set_callback(test_trace_cb);
159+
// Allocate a single memory block
160+
void *p = malloc(block_size);
161+
TEST_ASSERT_NOT_EQUAL(p, NULL);
162+
// Free the memory block
163+
free(p);
164+
// Stop tracing
165+
mbed_mem_trace_set_callback(NULL);
166+
// Check tracer result
167+
check_sanity(2);
168+
check_malloc_op(pmem ++, p, block_size);
169+
check_free_op(pmem, p);
170+
}
171+
172+
// Test all memory operations (malloc, realloc, free, calloc)
173+
static void test_case_all_memory_ops() {
174+
const size_t malloc_size = 40, realloc_size = 80, nmemb = 25, size = 10;
175+
const mem_trace_data_t *pmem = stats.op_data;
176+
177+
test_clear_stats();
178+
mbed_mem_trace_set_callback(test_trace_cb);
179+
// Allocate a single memory block, the realloc it
180+
void *p_malloc = malloc(malloc_size);
181+
TEST_ASSERT_NOT_EQUAL(p_malloc, NULL);
182+
void *p_realloc = realloc(p_malloc, realloc_size);
183+
TEST_ASSERT_NOT_EQUAL(p_realloc, NULL);
184+
// Use calloc() now
185+
void *p_calloc = calloc(nmemb, size);
186+
//TEST_ASSERT_NOT_EQUAL(p_calloc, NULL);
187+
// Free the realloc() pointer first, then the calloc() one
188+
free(p_realloc);
189+
free(p_calloc);
190+
// Stop tracing
191+
mbed_mem_trace_set_callback(NULL);
192+
// Check tracer result
193+
check_sanity(6);
194+
check_malloc_op(pmem ++, p_malloc, malloc_size);
195+
check_realloc_op(pmem ++, p_realloc, p_malloc, realloc_size);
196+
// calloc() calls malloc() internally
197+
check_malloc_op(pmem ++, p_calloc, nmemb * size);
198+
check_calloc_op(pmem ++, p_calloc, nmemb, size);
199+
check_free_op(pmem ++, p_realloc);
200+
check_free_op(pmem, p_calloc);
201+
}
202+
203+
// Test that tracing is off when using a NULL callback
204+
static void test_case_trace_off() {
205+
const size_t malloc_size = 10;
206+
207+
test_clear_stats();
208+
// We don't want any tracing
209+
mbed_mem_trace_set_callback(NULL);
210+
// Allocate a buffer and free it
211+
void *p_malloc = malloc(malloc_size);
212+
TEST_ASSERT_NOT_EQUAL(p_malloc, NULL);
213+
free(p_malloc);
214+
// Check that we didn't trace anything
215+
check_sanity(0);
216+
}
217+
218+
// Test partial tracing (start tracing, stop tracing, restart later)
219+
static void test_case_partial_trace() {
220+
const size_t malloc_size_1 = 20, malloc_size_2 = 30;
221+
const mem_trace_data_t *pmem = stats.op_data;
222+
223+
test_clear_stats();
224+
// Start tracing
225+
mbed_mem_trace_set_callback(test_trace_cb);
226+
// Allocate a buffer
227+
void *p_malloc_1 = malloc(malloc_size_1);
228+
TEST_ASSERT_NOT_EQUAL(p_malloc_1, NULL);
229+
// Disable tracing before freeing the first buffer
230+
mbed_mem_trace_set_callback(NULL);
231+
free(p_malloc_1);
232+
// Allocate another buffer (still not traced)
233+
void *p_malloc_2 = malloc(malloc_size_2);
234+
TEST_ASSERT_NOT_EQUAL(p_malloc_2, NULL);
235+
// Re-enable tracing
236+
mbed_mem_trace_set_callback(test_trace_cb);
237+
// And free the second buffer (this operation should be tracer)
238+
free(p_malloc_2);
239+
// Stop tracing
240+
mbed_mem_trace_set_callback(NULL);
241+
// Check tracer result
242+
check_sanity(2);
243+
check_malloc_op(pmem ++, p_malloc_1, malloc_size_1);
244+
check_free_op(pmem, p_malloc_2);
245+
}
246+
247+
// Test new/delete tracing
248+
static void test_case_new_delete() {
249+
const mem_trace_data_t *pmem = stats.op_data;
250+
251+
test_clear_stats();
252+
// Start tracing
253+
mbed_mem_trace_set_callback(test_trace_cb);
254+
// Test new, new[], delete and delete[]
255+
int *p_int = new int;
256+
int *p_int_array = new int[10];
257+
delete p_int;
258+
delete[] p_int_array;
259+
// Stop tracing
260+
mbed_mem_trace_set_callback(NULL);
261+
// Check tracer result
262+
check_sanity(4);
263+
check_malloc_op(pmem ++, p_int, sizeof(int));
264+
check_malloc_op(pmem ++, p_int_array, 10 * sizeof(int));
265+
check_free_op(pmem ++, p_int);
266+
check_free_op(pmem ++, p_int_array);
267+
}
268+
269+
static Case cases[] = {
270+
Case("single malloc/free", test_case_single_malloc_free),
271+
Case("all memory operations", test_case_all_memory_ops),
272+
Case("trace off", test_case_trace_off),
273+
Case("partial trace", test_case_partial_trace),
274+
Case("test new/delete", test_case_new_delete)
275+
};
276+
277+
static status_t greentea_test_setup(const size_t number_of_cases) {
278+
GREENTEA_SETUP(20, "default_auto");
279+
return greentea_test_setup_handler(number_of_cases);
280+
}
281+
282+
static Specification specification(greentea_test_setup, cases, greentea_test_teardown_handler);
283+
284+
int main() {
285+
// Disable stdout buffering to prevent any unwanted allocations
286+
setvbuf(stdout, NULL, _IONBF, 0);
287+
Harness::run(specification);
288+
}
289+

0 commit comments

Comments
 (0)