Skip to content

Commit 8259735

Browse files
author
Bogdan Marinescu
committed
Runtime dynamic memory tracing
This commit adds a dynamic memory tracer that calls a callback whenever one of the basic memory allocation functions (malloc, realloc, calloc, free) is called. The operation of the tracer is guarded by the 'MBED_MEM_TRACING_ENABLED` macro.
1 parent bdb4ab9 commit 8259735

File tree

6 files changed

+381
-22
lines changed

6 files changed

+381
-22
lines changed

hal/api/mbed_mem_trace.h

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/* mbed Microcontroller Library
2+
* Copyright (c) 2006-2016 ARM Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef __MBED_MEM_TRACE_H__
18+
#define __MBED_MEM_TRACE_H__
19+
20+
#ifdef __cplusplus
21+
extern "C" {
22+
#endif
23+
24+
#include <sys/types.h>
25+
26+
/* Masks for tracer */
27+
#define MBED_MEM_TRACE_MALLOC (1 << 0)
28+
#define MBED_MEM_TRACE_REALLOC (1 << 1)
29+
#define MBED_MEM_TRACE_CALLOC (1 << 2)
30+
#define MBED_MEM_TRACE_FREE (1 << 3)
31+
#define MBED_MEM_TRACE_ALL (MBED_MEM_TRACE_MALLOC | MBED_MEM_TRACE_REALLOC | MBED_MEM_TRACE_CALLOC | MBED_MEM_TRACE_FREE)
32+
#define MBED_MEM_TRACE_DISABLE (0)
33+
34+
/* Prefix for the output of the default tracer */
35+
#define MBED_MEM_DEFAULT_TRACER_PREFIX "#"
36+
37+
/**
38+
* Type of the callback used by the memory tracer. This callback is called when a memory
39+
* allocation operation (malloc, realloc, calloc, free) is called and tracing is enabled
40+
* for that memory allocation function.
41+
*
42+
* @param op the ID of the operation (MBED_MEM_TRACE_MALLOC, MBED_MEM_TRACE_REALLOC,
43+
* MBED_MEM_TRACE_CALLOC or MBED_MEM_TRACE_FREE).
44+
* @param res the result that the memory operation returned (NULL for 'free').
45+
* @param caller the caller of the memory operation. Note that the value of 'caller' might be
46+
* unreliable.
47+
*
48+
* The rest of the parameters passed 'mbed_mem_trace_cb_t' are the same as the memory operations
49+
* that triggered its call (see 'man malloc' for details):
50+
*
51+
* - for malloc: cb(MBED_MEM_TRACE_MALLOC, res, caller, size).
52+
* - for realloc: cb(MBED_MEM_TRACE_REALLOC, res, caller, ptr, size).
53+
* - for calloc: cb(MBED_MEM_TRACE_CALLOC, res, caller, nmemb, size).
54+
* - for free: cb(MBED_MEM_TRACE_FREE, NULL, caller, ptr).
55+
*/
56+
typedef void (*mbed_mem_trace_cb_t)(uint8_t op, void *res, void* caller, ...);
57+
58+
/**
59+
* Set up for the memory tracer. This function can be called more than one time
60+
* to set up different masks and/or callbacks.
61+
*
62+
* @param mask the tracer mask. It can be a bitwise combination of MBED_MEM_TRACE_MALLOC,
63+
* MBED_MEM_TRACE_REALLOC, MBED_MEM_TRACE_CALLOC, MBED_MEM_TRACE_FREE, or
64+
* MBED_MEM_TRACE_ALL to trace all memory operations, or MBED_MEM_TRACE_DISABLE
65+
* to disable tracing.
66+
* @param cb the callback to call on each enabled memory operation.
67+
*/
68+
void mbed_mem_trace_setup(uint8_t mask, mbed_mem_trace_cb_t cb);
69+
70+
/**
71+
* Trace a call to 'malloc'.
72+
* @param res the result of running 'malloc'.
73+
* @param size the 'size' argument given to 'malloc'.
74+
* @return 'res' (the first argument).
75+
*/
76+
void* mbed_mem_trace_malloc(void *res, size_t size);
77+
78+
/**
79+
* Trace a call to 'realloc'.
80+
* @param res the result of running 'realloc'.
81+
* @param ptr the 'ptr' argument given to 'realloc'.
82+
* @param size the 'size' argument given to 'realloc'.
83+
* @return 'res' (the first argument).
84+
*/
85+
void* mbed_mem_trace_realloc(void *res, void *ptr, size_t size);
86+
87+
/**
88+
* Trace a call to 'calloc'.
89+
* @param res the result of running 'calloc'.
90+
* @param nmemb the 'nmemb' argument given to 'calloc'.
91+
* @param size the 'size' argument given to 'calloc'.
92+
* @Return 'res' (the first argument).
93+
*/
94+
void* mbed_mem_trace_calloc(void *res, size_t num, size_t size);
95+
96+
/**
97+
* Trace a call to 'free'.
98+
* @param ptr the 'ptr' argument given to 'free'.
99+
*/
100+
void mbed_mem_trace_free(void *ptr);
101+
102+
/**
103+
* Default memory trace callback. DO NOT CALL DIRECTLY. It is meant to be used
104+
* as the second argument of 'mbed_mem_trace_setup'.
105+
*
106+
* The default callback outputs trace data using 'printf', in a format that's
107+
* easily parsable by an external tool. For each memory operation, the callback
108+
* outputs a line that begins with '#<op>:<0xresult>;<0xcaller>-':
109+
*
110+
* - 'op' identifies the memory operation ('m' for 'malloc', 'r' for 'realloc',
111+
* 'c' for 'calloc' and 'f' for 'free').
112+
* - 'result' (base 16) is the result of the memor operation. This is always NULL
113+
* for 'free', since 'free' doesn't return anything.
114+
* -'caller' (base 16) is the caller of the memory operation. Note that the value
115+
* of 'caller' might be unreliable.
116+
*
117+
* The rest of the output depends on the operation being traced:
118+
*
119+
* - for 'malloc': 'size', where 'size' is the original argument to 'malloc'.
120+
* - for 'realloc': '0xptr;size', where 'ptr' (base 16) and 'size' are the original arguments to 'realloc'.
121+
* - for 'calloc': 'nmemb;size', where 'nmemb' and 'size' are the original arguments to 'calloc'.
122+
* - for 'free': '0xptr', where 'ptr' (base 16) is the original argument to 'free'.
123+
*
124+
* Examples:
125+
*
126+
* - '#m:0x20003240;0x600d-50' encodes a 'malloc' that returned 0x20003240, was called
127+
* by the instruction at 0x600D with a the 'size' argument equal to 50.
128+
* - '#f:0x0;0x602f-0x20003240' encodes a 'free' that was called by the instruction at
129+
* 0x602f with the 'ptr' argument equal to 0x20003240.
130+
*/
131+
void mbed_mem_default_callback(uint8_t op, void *res, void *caller, ...);
132+
133+
#ifdef __cplusplus
134+
}
135+
#endif
136+
137+
#endif// #ifndef __MBED_MEM_TRACE_H__
138+

hal/api/toolchain.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,4 +263,17 @@ typedef int FILEHANDLE;
263263
#define EXTERN extern
264264
#endif
265265

266+
/**
267+
* Helper function that identifies the caller of a function
268+
*
269+
* @return Address of the calling function
270+
*/
271+
#if defined(TOOLCHAIN_ARM)
272+
#define MBED_CALLER_ADDR() __builtin_return_address(0)
273+
#elif defined(TOOLCHAIN_GCC)
274+
#define MBED_CALLER_ADDR() __builtin_extract_return_addr(__builtin_return_address(0))
275+
#else
276+
#define MBED_CALLER_ADDR() (NULL)
277+
#endif
278+
266279
#endif

hal/common/mbed_alloc_wrappers.c

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* mbed Microcontroller Library
2+
* Copyright (c) 2006-2016 ARM Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "api/mbed_mem_trace.h"
18+
#include <stddef.h>
19+
#include <stdio.h>
20+
21+
#if defined(TOOLCHAIN_GCC)
22+
23+
#ifdef FEATURE_UVISOR
24+
#include "uvisor-lib/uvisor-lib.h"
25+
#endif/* FEATURE_UVISOR */
26+
27+
// TODO: memory tracing doesn't work with uVisor enabled.
28+
#if !defined(FEATURE_UVISOR)
29+
void * __wrap__malloc_r(struct _reent * r, size_t size) {
30+
extern void * __real__malloc_r(struct _reent * r, size_t size);
31+
#ifdef MBED_MEM_TRACING_ENABLED
32+
return mbed_mem_trace_malloc(__real__malloc_r(r, size), size);
33+
#else
34+
return __real__malloc_r(r, size);
35+
#endif
36+
}
37+
void * __wrap__realloc_r(struct _reent * r, void * ptr, size_t size) {
38+
extern void * __real__realloc_r(struct _reent * r, void * ptr, size_t size);
39+
#ifdef MBED_MEM_TRACING_ENABLED
40+
return mbed_mem_trace_realloc(__real__realloc_r(r, ptr, size), ptr, size);
41+
#else
42+
return __real__realloc_r(r, ptr, size);
43+
#endif
44+
}
45+
void __wrap__free_r(struct _reent * r, void * ptr) {
46+
extern void __real__free_r(struct _reent * r, void * ptr);
47+
__real__free_r(r, ptr);
48+
#ifdef MBED_MEM_TRACING_ENABLED
49+
mbed_mem_trace_free(ptr);
50+
#endif
51+
}
52+
void * __wrap__calloc_r(struct _reent * r, size_t nmemb, size_t size) {
53+
extern void* __real__calloc_r(struct _reent * r, size_t nmemb, size_t size);
54+
#ifdef MBED_MEM_TRACING_ENABLED
55+
return mbed_mem_trace_calloc(__real__calloc_r(r, nmemb, size), nmemb, size);
56+
#else
57+
return __real__calloc_r(r, nmemb, size);
58+
#endif
59+
}
60+
#endif // if !defined(FEATURE_UVISOR)
61+
62+
#elif defined(TOOLCHAIN_ARM) // #if defined(TOOLCHAIN_GCC)
63+
64+
void* $Sub$$malloc(size_t size) {
65+
#ifdef MBED_MEM_TRACING_ENABLED
66+
return trace_malloc($Super$$malloc(size), size);
67+
#else
68+
return $Super$$malloc(size);
69+
#endif
70+
}
71+
72+
void* $Sub$$realloc(void *ptr, size_t size) {
73+
#ifdef MBED_MEM_TRACING_ENABLED
74+
return trace_realloc($Super$$realloc(ptr, size), ptr, size);
75+
#else
76+
return $Super$$realloc(ptr, size);
77+
#endif
78+
}
79+
80+
void *$Sub$$calloc(size_t nmemb, size_t size) {
81+
#ifdef MBED_MEM_TRACING_ENABLED
82+
return trace_calloc($Super$$calloc(nmemb, size), nmemb, size);
83+
#else
84+
return $Super$$calloc(nmemb, size);
85+
#endif
86+
}
87+
88+
void $Sub$$free(void *ptr) {
89+
$Super$$free(ptr);
90+
#ifdef MBED_MEM_TRACING_ENABLED
91+
trace_free(ptr);
92+
#endif
93+
}
94+
95+
#else // #if defined(TOOLCHAIN_GCC)
96+
97+
#ifdef MBED_MEM_TRACING_ENABLED
98+
#error Memory tracing not supported with the current toolchain.
99+
#endif
100+
101+
#endif // #if defined(TOOLCHAIN_GCC)
102+

hal/common/mbed_mem_trace.c

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/* mbed Microcontroller Library
2+
* Copyright (c) 2006-2016 ARM Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <stdlib.h>
18+
#include <stdarg.h>
19+
#include <stdio.h>
20+
#include "api/mbed_mem_trace.h"
21+
#include "api/critical.h"
22+
#include "api/toolchain.h"
23+
24+
/******************************************************************************
25+
* Internal variables, functions and helpers
26+
*****************************************************************************/
27+
28+
/* The current tracing mask (see mbed_mem_trace.h for details). */
29+
static uint8_t mem_trace_mask = MBED_MEM_TRACE_DISABLE;
30+
/* The callback function that will be called after a traced memory operations finishes. */
31+
static mbed_mem_trace_cb_t mem_trace_cb;
32+
/* 'already_tracing' guards "trace inside trace" situations (for example, the implementation
33+
* of realloc() might call malloc() internally, and since malloc() is also traced, this could
34+
* result in two calls to the callback function instead of one. */
35+
static uint8_t already_tracing;
36+
37+
/******************************************************************************
38+
* Public interface
39+
*****************************************************************************/
40+
41+
void mbed_mem_trace_setup(uint8_t mask, mbed_mem_trace_cb_t cb) {
42+
mem_trace_mask = mask;
43+
mem_trace_cb = cb;
44+
}
45+
46+
void* mbed_mem_trace_malloc(void *res, size_t size) {
47+
uint8_t expected = 0;
48+
49+
if (mem_trace_cb && (mem_trace_mask & MBED_MEM_TRACE_MALLOC)) {
50+
if (core_util_atomic_cas_u8(&already_tracing, &expected, 1)) {
51+
mem_trace_cb(MBED_MEM_TRACE_MALLOC, res, MBED_CALLER_ADDR(), size);
52+
already_tracing = 0;
53+
}
54+
}
55+
return res;
56+
}
57+
58+
void* mbed_mem_trace_realloc(void *res, void *ptr, size_t size) {
59+
uint8_t expected = 0;
60+
61+
if (mem_trace_cb && (mem_trace_mask & MBED_MEM_TRACE_REALLOC)) {
62+
if (core_util_atomic_cas_u8(&already_tracing, &expected, 1)) {
63+
mem_trace_cb(MBED_MEM_TRACE_REALLOC, res, MBED_CALLER_ADDR(), ptr, size);
64+
already_tracing = 0;
65+
}
66+
}
67+
return res;
68+
}
69+
70+
void* mbed_mem_trace_calloc(void *res, size_t num, size_t size) {
71+
uint8_t expected = 0;
72+
73+
if (mem_trace_cb && (mem_trace_mask & MBED_MEM_TRACE_CALLOC)) {
74+
if (core_util_atomic_cas_u8(&already_tracing, &expected, 1)) {
75+
mem_trace_cb(MBED_MEM_TRACE_CALLOC, res, MBED_CALLER_ADDR(), num, size);
76+
already_tracing = 0;
77+
}
78+
}
79+
return res;
80+
}
81+
82+
void mbed_mem_trace_free(void *ptr) {
83+
uint8_t expected = 0;
84+
85+
if (mem_trace_cb && (mem_trace_mask & MBED_MEM_TRACE_FREE)) {
86+
if (core_util_atomic_cas_u8(&already_tracing, &expected, 1)) {
87+
mem_trace_cb(MBED_MEM_TRACE_FREE, NULL, MBED_CALLER_ADDR(), ptr);
88+
already_tracing = 0;
89+
}
90+
}
91+
}
92+
93+
void mbed_mem_default_callback(uint8_t op, void *res, void *caller, ...) {
94+
va_list va;
95+
size_t temp_s1, temp_s2;
96+
void *temp_ptr;
97+
98+
va_start(va, caller);
99+
switch(op) {
100+
case MBED_MEM_TRACE_MALLOC:
101+
temp_s1 = va_arg(va, size_t);
102+
printf(MBED_MEM_DEFAULT_TRACER_PREFIX "m:%p;%p-%u\n", res, caller, temp_s1);
103+
break;
104+
105+
case MBED_MEM_TRACE_REALLOC:
106+
temp_ptr = va_arg(va, void*);
107+
temp_s1 = va_arg(va, size_t);
108+
printf(MBED_MEM_DEFAULT_TRACER_PREFIX "r:%p;%p-%p;%u\n", res, caller, temp_ptr, temp_s1);
109+
break;
110+
111+
case MBED_MEM_TRACE_CALLOC:
112+
temp_s1 = va_arg(va, size_t);
113+
temp_s2 = va_arg(va, size_t);
114+
printf(MBED_MEM_DEFAULT_TRACER_PREFIX "c:%p;%p-%u;%u\n", res, caller, temp_s1, temp_s2);
115+
break;
116+
117+
case MBED_MEM_TRACE_FREE:
118+
temp_ptr = va_arg(va, void*);
119+
printf(MBED_MEM_DEFAULT_TRACER_PREFIX "f:%p;%p-%p\n", res, caller, temp_ptr);
120+
break;
121+
122+
default:
123+
printf("?\n");
124+
}
125+
va_end(va);
126+
}
127+

0 commit comments

Comments
 (0)