2020-07-30 02:36 PM
I have inherited a GUI project that has a memory leak which I tracked down to the use of the LISTBOX.
If I remove the LISTBOX the memory usage never rises however if I utilize a LISTBOX even if empty memory used by STemWIN will rise until it runs out and crashes.
Is this a known issue,or do I have an implementation error. Any examples of correct implementation or help would be appreciated.
2024-11-13 02:09 AM - edited 2024-11-13 02:28 AM
Maybe it helps. We had several memory leaks in STemWin related code also and found the root case.
We used APPW_GetLockedText to get translation texts by translation ID and then used that text in several places like LISTVIEW_AddColumn, DROPDOWN_AddString and APPW_SetText. As it turned out, APPW_GetLockedText allocates memory from the heap and it is not freed automatically (because all those named functions make their own copy of the text). But APPW_GetLockedText returns GUI_HMEM pointer and it must be freed by the user with GUI_ALLOC_Free.
Unfortunately APPW_GetLockedText is not documented (at least in STemWin 6.4.0 that we use) so it was unnoticed at first. Only hint to this was the return value type in the header file.
However, we found this leak after figuring out emWin heap pool structure with memory viewer and writing parser for it on the target. For everbody sake I share that piece of code:
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <ctype.h>
#include "GUI.h"
/**
* Parse (ST)emWin heap block.
* heap: Pointer to heap as 32-bit words.
* heap_size_in_wors: Heap size in words.
*/
void parse_emwin_heap(uint32_t * heap, uint32_t heap_size_in_dwords)
{
uint32_t i = 0;
uint32_t offset;
uint32_t block;
uint32_t size_head;
uint32_t size_tail;
uint32_t size_in_words;
uint32_t unknown_words[2];
uint8_t * data;
uint32_t data_size;
uint8_t text[9];
uint32_t total_used = 0;
uint32_t total_alloc = 0;
GUI_ALLOC_INFO alloc_info;
// Write table header
printf("| Offset | Block | Size | Text | Flags? (no idea) |");
printf("+--------+-------+-------+----------+-------------------+");
while (i < heap_size_in_dwords)
{
// Calculate offset in bytes
offset = i * sizeof(uint32_t);
// Parse block number which is a 32-bit little-endian
block = heap[i];
// Parse block size from head side in bytes as 32-bit little-endian.
// Block size includes size and block number.
size_head = heap[i + 1];
// Size must be aligned to 32-bits
if ((size_head % sizeof(uint32_t)) != 0)
{
printf("Block %" PRIu32 " at 0x%" PRIX32 " size not aligned: %" PRIu32, block, offset, size_head);
break;
}
// Calculate block size in words
size_in_words = size_head / sizeof(uint32_t);
// Size can't be less than head and tail consume
if (size_in_words < 5)
{
printf("Block %" PRIu32 " at 0x%" PRIX32 " too small: %" PRIu32, block, offset, size_head);
break;
}
// Size can't exceed heap area
if (i + size_in_words > heap_size_in_dwords)
{
printf("Block %" PRIu32 " at 0x%" PRIX32 " too large: %" PRIu32, block, offset, size_head);
break;
}
// Parse block size at tail side in bytes as 32-bit little-endian
size_tail = heap[i + size_in_words - 1];
// Head and tail sizes must match
if (size_head != size_tail)
{
printf("Block %" PRIu32 " at 0x%" PRIX32 " head size %" PRIu32 " != tail size %" PRIu32, block, offset, size_head, size_tail);
break;
}
// Get data pointer.
// There doesn't seem to be true data size in bytes.
data = (uint8_t *)&heap[i + 2];
data_size = (size_in_words - 5) * sizeof(uint32_t);
// Parse unknown words (maybe they are some kind of flags?)
unknown_words[0] = heap[i + size_in_words - 3];
unknown_words[1] = heap[i + size_in_words - 2];
// Show first 4 ASCII characters or dots if not ASCII
text[0] = isprint(data[0]) ? data[0] : '.';
text[1] = isprint(data[1]) ? data[1] : '.';
text[2] = isprint(data[2]) ? data[2] : '.';
text[3] = isprint(data[3]) ? data[3] : '.';
text[4] = 0;
if (data_size > 4)
{
// Show additional 4 ASCII characters or dots if not ASCII
text[4] = isprint(data[4]) ? data[4] : '.';
text[5] = isprint(data[5]) ? data[5] : '.';
text[6] = isprint(data[6]) ? data[6] : '.';
text[7] = isprint(data[7]) ? data[7] : '.';
text[8] = 0;
}
// Report all used blocks
if (block != 0)
{
printf("| %6" PRIX32 " | %5" PRIu32 " | %5" PRIu32 " | %8s | %8X %8X |",
offset, block, data_size, text, unknown_words[0], unknown_words[1]);
}
// Move to next block
i += size_in_words;
// Count total
total_alloc += size_in_words * sizeof(uint32_t);
if (block != 0)
{
total_used += size_in_words * sizeof(uint32_t);
}
}
// Note: there is something at the end of the memory pool also, but don't know what that is.
// Check what GUI tells about heap
GUI_ALLOC_GetMemInfo(&alloc_info);
// Report totals
printf("Parse totals: used %" PRIu32 " B, alloc %" PRIu32 " B, area %" PRIu32 " B",
total_used, total_alloc, heap_size_in_dwords * sizeof(uint32_t));
printf("GUI totals: used %" PRIu32 " B, alloc %" PRIu32 " B, area %" PRIu32 " B",
alloc_info.UsedBytes, alloc_info.AllocSize, alloc_info.TotalBytes);
}
Even though it tends to exit the loop with some kind of error, it gives same total as GUI gives. So it kind of works.