xref: /qemu/tests/qtest/libqos/libqos-malloc.c (revision a976a99a)
1 /*
2  * libqos malloc support
3  *
4  * Copyright (c) 2014
5  *
6  * Author:
7  *  John Snow <jsnow@redhat.com>
8  *
9  * This work is licensed under the terms of the GNU GPL, version 2 or later.
10  * See the COPYING file in the top-level directory.
11  */
12 
13 #include "qemu/osdep.h"
14 #include "libqos-malloc.h"
15 #include "qemu/host-utils.h"
16 
17 typedef struct MemBlock {
18     QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME;
19     uint64_t size;
20     uint64_t addr;
21 } MemBlock;
22 
23 #define DEFAULT_PAGE_SIZE 4096
24 
25 static void mlist_delete(MemList *list, MemBlock *node)
26 {
27     g_assert(list && node);
28     QTAILQ_REMOVE(list, node, MLIST_ENTNAME);
29     g_free(node);
30 }
31 
32 static MemBlock *mlist_find_key(MemList *head, uint64_t addr)
33 {
34     MemBlock *node;
35     QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
36         if (node->addr == addr) {
37             return node;
38         }
39     }
40     return NULL;
41 }
42 
43 static MemBlock *mlist_find_space(MemList *head, uint64_t size)
44 {
45     MemBlock *node;
46 
47     QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
48         if (node->size >= size) {
49             return node;
50         }
51     }
52     return NULL;
53 }
54 
55 static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr)
56 {
57     MemBlock *node;
58     g_assert(head && insr);
59 
60     QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
61         if (insr->addr < node->addr) {
62             QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME);
63             return insr;
64         }
65     }
66 
67     QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME);
68     return insr;
69 }
70 
71 static inline uint64_t mlist_boundary(MemBlock *node)
72 {
73     return node->size + node->addr;
74 }
75 
76 static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right)
77 {
78     g_assert(head && left && right);
79 
80     left->size += right->size;
81     mlist_delete(head, right);
82     return left;
83 }
84 
85 static void mlist_coalesce(MemList *head, MemBlock *node)
86 {
87     g_assert(node);
88     MemBlock *left;
89     MemBlock *right;
90     char merge;
91 
92     do {
93         merge = 0;
94         left = QTAILQ_PREV(node, MLIST_ENTNAME);
95         right = QTAILQ_NEXT(node, MLIST_ENTNAME);
96 
97         /* clowns to the left of me */
98         if (left && mlist_boundary(left) == node->addr) {
99             node = mlist_join(head, left, node);
100             merge = 1;
101         }
102 
103         /* jokers to the right */
104         if (right && mlist_boundary(node) == right->addr) {
105             node = mlist_join(head, node, right);
106             merge = 1;
107         }
108 
109     } while (merge);
110 }
111 
112 static MemBlock *mlist_new(uint64_t addr, uint64_t size)
113 {
114     MemBlock *block;
115 
116     if (!size) {
117         return NULL;
118     }
119     block = g_new0(MemBlock, 1);
120 
121     block->addr = addr;
122     block->size = size;
123 
124     return block;
125 }
126 
127 static uint64_t mlist_fulfill(QGuestAllocator *s, MemBlock *freenode,
128                                                                 uint64_t size)
129 {
130     uint64_t addr;
131     MemBlock *usednode;
132 
133     g_assert(freenode);
134     g_assert_cmpint(freenode->size, >=, size);
135 
136     addr = freenode->addr;
137     if (freenode->size == size) {
138         /* re-use this freenode as our used node */
139         QTAILQ_REMOVE(s->free, freenode, MLIST_ENTNAME);
140         usednode = freenode;
141     } else {
142         /* adjust the free node and create a new used node */
143         freenode->addr += size;
144         freenode->size -= size;
145         usednode = mlist_new(addr, size);
146     }
147 
148     mlist_sort_insert(s->used, usednode);
149     return addr;
150 }
151 
152 /* To assert the correctness of the list.
153  * Used only if ALLOC_PARANOID is set. */
154 static void mlist_check(QGuestAllocator *s)
155 {
156     MemBlock *node;
157     uint64_t addr = s->start > 0 ? s->start - 1 : 0;
158     uint64_t next = s->start;
159 
160     QTAILQ_FOREACH(node, s->free, MLIST_ENTNAME) {
161         g_assert_cmpint(node->addr, >, addr);
162         g_assert_cmpint(node->addr, >=, next);
163         addr = node->addr;
164         next = node->addr + node->size;
165     }
166 
167     addr = s->start > 0 ? s->start - 1 : 0;
168     next = s->start;
169     QTAILQ_FOREACH(node, s->used, MLIST_ENTNAME) {
170         g_assert_cmpint(node->addr, >, addr);
171         g_assert_cmpint(node->addr, >=, next);
172         addr = node->addr;
173         next = node->addr + node->size;
174     }
175 }
176 
177 static uint64_t mlist_alloc(QGuestAllocator *s, uint64_t size)
178 {
179     MemBlock *node;
180 
181     node = mlist_find_space(s->free, size);
182     if (!node) {
183         fprintf(stderr, "Out of guest memory.\n");
184         g_assert_not_reached();
185     }
186     return mlist_fulfill(s, node, size);
187 }
188 
189 static void mlist_free(QGuestAllocator *s, uint64_t addr)
190 {
191     MemBlock *node;
192 
193     if (addr == 0) {
194         return;
195     }
196 
197     node = mlist_find_key(s->used, addr);
198     if (!node) {
199         fprintf(stderr, "Error: no record found for an allocation at "
200                 "0x%016" PRIx64 ".\n",
201                 addr);
202         g_assert_not_reached();
203     }
204 
205     /* Rip it out of the used list and re-insert back into the free list. */
206     QTAILQ_REMOVE(s->used, node, MLIST_ENTNAME);
207     mlist_sort_insert(s->free, node);
208     mlist_coalesce(s->free, node);
209 }
210 
211 /*
212  * Mostly for valgrind happiness, but it does offer
213  * a chokepoint for debugging guest memory leaks, too.
214  */
215 void alloc_destroy(QGuestAllocator *allocator)
216 {
217     MemBlock *node;
218     MemBlock *tmp;
219     QAllocOpts mask;
220 
221     /* Check for guest leaks, and destroy the list. */
222     QTAILQ_FOREACH_SAFE(node, allocator->used, MLIST_ENTNAME, tmp) {
223         if (allocator->opts & (ALLOC_LEAK_WARN | ALLOC_LEAK_ASSERT)) {
224             fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; "
225                     "size 0x%016" PRIx64 ".\n",
226                     node->addr, node->size);
227         }
228         if (allocator->opts & (ALLOC_LEAK_ASSERT)) {
229             g_assert_not_reached();
230         }
231         g_free(node);
232     }
233 
234     /* If we have previously asserted that there are no leaks, then there
235      * should be only one node here with a specific address and size. */
236     mask = ALLOC_LEAK_ASSERT | ALLOC_PARANOID;
237     QTAILQ_FOREACH_SAFE(node, allocator->free, MLIST_ENTNAME, tmp) {
238         if ((allocator->opts & mask) == mask) {
239             if ((node->addr != allocator->start) ||
240                 (node->size != allocator->end - allocator->start)) {
241                 fprintf(stderr, "Free list is corrupted.\n");
242                 g_assert_not_reached();
243             }
244         }
245 
246         g_free(node);
247     }
248 
249     g_free(allocator->used);
250     g_free(allocator->free);
251 }
252 
253 uint64_t guest_alloc(QGuestAllocator *allocator, size_t size)
254 {
255     uint64_t rsize = size;
256     uint64_t naddr;
257 
258     if (!size) {
259         return 0;
260     }
261 
262     rsize += (allocator->page_size - 1);
263     rsize &= -allocator->page_size;
264     g_assert_cmpint((allocator->start + rsize), <=, allocator->end);
265     g_assert_cmpint(rsize, >=, size);
266 
267     naddr = mlist_alloc(allocator, rsize);
268     if (allocator->opts & ALLOC_PARANOID) {
269         mlist_check(allocator);
270     }
271 
272     return naddr;
273 }
274 
275 void guest_free(QGuestAllocator *allocator, uint64_t addr)
276 {
277     if (!addr) {
278         return;
279     }
280     mlist_free(allocator, addr);
281     if (allocator->opts & ALLOC_PARANOID) {
282         mlist_check(allocator);
283     }
284 }
285 
286 void alloc_init(QGuestAllocator *s, QAllocOpts opts,
287                 uint64_t start, uint64_t end,
288                 size_t page_size)
289 {
290     MemBlock *node;
291 
292     s->opts = opts;
293     s->start = start;
294     s->end = end;
295 
296     s->used = g_new(MemList, 1);
297     s->free = g_new(MemList, 1);
298     QTAILQ_INIT(s->used);
299     QTAILQ_INIT(s->free);
300 
301     node = mlist_new(s->start, s->end - s->start);
302     QTAILQ_INSERT_HEAD(s->free, node, MLIST_ENTNAME);
303 
304     s->page_size = page_size;
305 }
306 
307 void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts)
308 {
309     allocator->opts |= opts;
310 }
311 
312 void migrate_allocator(QGuestAllocator *src,
313                        QGuestAllocator *dst)
314 {
315     MemBlock *node, *tmp;
316     MemList *tmpused, *tmpfree;
317 
318     /* The general memory layout should be equivalent,
319      * though opts can differ. */
320     g_assert_cmphex(src->start, ==, dst->start);
321     g_assert_cmphex(src->end, ==, dst->end);
322 
323     /* Destroy (silently, regardless of options) the dest-list: */
324     QTAILQ_FOREACH_SAFE(node, dst->used, MLIST_ENTNAME, tmp) {
325         g_free(node);
326     }
327     QTAILQ_FOREACH_SAFE(node, dst->free, MLIST_ENTNAME, tmp) {
328         g_free(node);
329     }
330 
331     tmpused = dst->used;
332     tmpfree = dst->free;
333 
334     /* Inherit the lists of the source allocator: */
335     dst->used = src->used;
336     dst->free = src->free;
337 
338     /* Source is now re-initialized, the source memory is 'invalid' now: */
339     src->used = tmpused;
340     src->free = tmpfree;
341     QTAILQ_INIT(src->used);
342     QTAILQ_INIT(src->free);
343     node = mlist_new(src->start, src->end - src->start);
344     QTAILQ_INSERT_HEAD(src->free, node, MLIST_ENTNAME);
345     return;
346 }
347