Dynamic vector allocation in external RAM - c++

I am currently working on a large project of my own on an STM32F7 cortex-m7 microcontroller in C++ using GCC. I need to store a wide array in an external SDRAM (16 MB) containing vectors of notes structures (12 bytes each). I have already a working FMC and a custom ram region created
/* Specify the memory areas */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 512K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
SDRAM (xrw) : ORIGIN = 0xC0000000, LENGTH = 16M
}
/* Define output sections */
SECTIONS
{
/* Section créée pour l'allocation dans la SDRAM externe*/
.fmc :
{
. = ALIGN(8);
*(.fmc)
*(.fmc*)
. = ALIGN(8);
} >SDRAM
my array is declared like this :
std::vector<SequencerNoteEvent> NotesVectorArray[kMaxPpqn] __attribute__((section(".fmc")));
So far it's OK. I created an array of vectors in my external RAM. How can i proceed in order to make my vectors element creation
NotesVectorArray[position].push_back(note);
happening in the same external RAM dynamically ? I am currently only able to declare static data using the __attribute__(section)
I read lot of things about C++ allocators, memory pools, but i don't get where the allocation take place in the vector code and how i should replace it... I "just" need to have the same allocation system than the usual but in another part of my memory for this precise type.
It seems possible to have multiple heaps. Where is located the connection between scatter file and the effective memory allocation ?
Thanks in advance,
Ben

I was facing the same issue and got a working solution by redefining operator new. The benefit of this over using a custom allocator is that all dynamic allocations will automatically be in SDRAM, so you're free to use std::function or any STL container that requires the heap, without having to pass a custom allocator argument every time.
I did the following:
Remove the standard library by adding -nostdlib to the compilation flags. I also added this to the linker flags. Also remove and --specs=... from your compiler and linker flags. Bonus: you'll save ~60-80k of code space!
Write your own replacement for operator new, operator delete. I created a new file called new.cpp added the following: (taken from https://arobenko.gitbooks.io/bare_metal_cpp/content/compiler_output/dyn_mem.html)
//new.cpp
#include <cstdlib>
#include <new>
void *operator new(size_t size) noexcept { return malloc(size); }
void operator delete(void *p) noexcept { free(p); }
void *operator new[](size_t size) noexcept { return operator new(size); }
void operator delete[](void *p) noexcept { operator delete(p); }
void *operator new(size_t size, std::nothrow_t) noexcept { return operator new(size); }
void operator delete(void *p, std::nothrow_t) noexcept { operator delete(p); }
void *operator new[](size_t size, std::nothrow_t) noexcept { return operator new(size); }
void operator delete[](void *p, std::nothrow_t) noexcept { operator delete(p); }
Define a variable in the linker script that corresponds to the lowest SDRAM address (start of the heap). This isn't strictly necessary, as you could use a constant (0xC0000000) in your code in step 4, but it makes things easier to keep the SDRAM address in just one place. Just add one line: PROVIDE( _fmc_start = . );
//linker script
[snip]
/* Section créée pour l'allocation dans la SDRAM externe*/
.fmc :
{
. = ALIGN(8);
PROVIDE( _fmc_start = . );
*(.fmc)
*(.fmc*)
. = ALIGN(8);
} >SDRAM
The new implementation of new will directly call malloc(), which will call _sbrk() when it needs a block of memory in the heap.
So you have to define _sbrk() to keep track of the heap pointer, and simply start the pointer at the lowest SDRAM address that you just defined in the linker script.
Here's what I did:
//sbrk.cpp, or append this to new.cpp
#include <cstdlib>
extern "C" size_t _sbrk(int incr)
{
extern char _fmc_start; // Defined by the linker
static char *heap_end;
char *prev_heap_end;
if (heap_end == 0)
heap_end = &_fmc_start;
prev_heap_end = heap_end;
//Optionally can check for out-of-memory error here
heap_end += incr;
return (size_t)prev_heap_end;
}
At this point, if you try to compile you will probably get many linker errors. The exact errors will depend on what parts of the standard library your project uses besides just new and delete. In my case, I was using std::function and the compiler complained that it didn't have __throw_bad_function_call(). You probably will also see an error for things like _init() and _fini() and __errno. The basic strategy is to create your own empty function stub or variable declaration for each of these. It's worth doing a quick search for each of these to see what purpose they serve, you might find out your project or a library you're including is using some features you weren't aware of! When you create a stub, you'll need to match the function signature correctly, so that will require searching the internet as well. Many of the stubs are for handling errors/exceptions, so you can decide how to handle it in the function body (or ignore errors).
For example, here's some info on the (obsolete, but required) _init() and _fini() functions: https://tldp.org/HOWTO/Program-Library-HOWTO/miscellaneous.html
Here's some info on __dso_handle: https://lists.debian.org/debian-gcc/2003/07/msg00057.html
__cxa_pure_virtual(): What is the purpose of __cxa_pure_virtual?
Here's what my project required to work:
//syscalls.cpp
extern "C" {
void _init(void) {}
void _fini(void) {}
int __errno;
void *__dso_handle = (void *)&__dso_handle;
}
namespace std
{
//These are error handlers. They could alternatively restart, or log an error
void __throw_bad_function_call() { while (1); }
void __cxa_pure_virtual() { while (1); }
} // namespace std
After all that, it will finally compile and if you use a debugger you'll see that the address of your vectors start at 0xC0000000!

Related

What is the best memory management for GCC C++? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 months ago.
Improve this question
What's the problem of my memory management? Because it causes crash that I show in a comment in the code below ("A memory block could not be found when trying to free."). I know that my memory management is not thread safe because I use global variables g_numBlocks and g_blocks that can cause risk when using multiple threads.
Since my memory management code seems too complex, can anyone suggest a stable and better "Memory Management for C++" to avoid memory leaks.
The code that contains bug
#include "emc-memory.h" // <-- Declare the functions MALLOC() and FREE() from other library.
#include <vector>
int main() {
printf("HERE(1)\n");
{
std::vector<string> paths = { // <-- Problem, 'std::vector' & 'string' use internal malloc/free & operator new/delete that are overwritten with my own custom memory management.
"/foo/bar.txt",
"/foo/bar.",
"/foo/bar",
"/foo/bar.txt/bar.cc",
"/foo/bar.txt/bar.",
"/foo/bar.txt/bar",
"/foo/.",
"/foo/..",
"/foo/.hidden",
"/foo/..bar",
};
} // <-- It crashes here, error in FREE(): "A memory block could not be found when trying to free.".
printf("HERE(2)\n"); // The reason I know it crashes above is this line is not evaluated, only "HERE(1)" is printed. I'm using [RelWithDebInfo] with blurry debugging info.
return 0;
}
Compilers:
[Visual Studio 2015] [Debug]: No problem.
[Visual Studio 2015] [RelWithDebInfo]: No problem.
[GCC 12.1.0 x86_64-w64-mingw32] [Debug]: No problem.
[GCC 12.1.0 x86_64-w64-mingw32] [RelWithDebInfo]: Broken which means there's a bug.
In "emc-memory.h" in other library .so file
extern const char* __file;
extern int __line;
#define new (__file = __FILE__, __line = __LINE__, 0) ? 0 : new
enum MEMORYBLOCKTYPE {
MEMORYBLOCKTYPE_MALLOC,
MEMORYBLOCKTYPE_NEW,
};
void *MALLOC(size_t size, MEMORYBLOCKTYPE type);
void *REALLOC(void *block, size_t newSize);
void FREE(void *block, MEMORYBLOCKTYPE type);
#define malloc(size) ((__file = __FILE__, __line = __LINE__, 0) ? 0 : MALLOC(size, MEMORYBLOCKTYPE_MALLOC))
#define realloc(block, newSize) REALLOC(block, newSize)
#define free(block) FREE(block, MEMORYBLOCKTYPE_MALLOC)
In "emc-memory.cpp" in other library .so file
I use this code in a link to override the operator new & delete: https://codereview.stackexchange.com/questions/7216/custom-operator-new-and-operator-delete
typedef unsigned long long BlockId; // The reason it's 64-bit is a memory block can be freed and reallocated multiple times, which means that there can be a lot of ids.
BlockId g_blockId = 0;
BlockId newBlockId() {
return g_blockId++;
}
struct Block {
const char *file;
int line;
const char *scope;
char *hint;
size_t size;
BlockId id; // That id is used for comparison because it will never be changed but the block pointer can be changed.
void *block;
MEMORYBLOCKTYPE type;
};
bool g_blocks_initialized = false;
int g_numBlocks;
Block **g_blocks;
void *MALLOC(size_t size, MEMORYBLOCKTYPE type) {
if (g_blocks_initialized == false) {
g_blocks_initialized = true;
_initializeList(g_numBlocks, g_blocks);
}
Block *b = (Block *)malloc(sizeof(*b));
b->file = __file ; __file = nullptr;
b->line = __line ; __line = 0;
b->scope = __scope; __scope = nullptr;
b->hint = allocateMemoryHint(__hint);
b->size = size;
b->id = newBlockId();
b->block = malloc(size);
b->type = type;
_addListItem(g_numBlocks, g_blocks, b);
return b->block;
}
void FREE(void *block, MEMORYBLOCKTYPE type) {
if (block == nullptr) {
return; // 'free' can free a nullptr.
}
for (int i = 0; i < g_numBlocks; i++) {
Block *b = g_blocks[i];
if (b->block == block) {
if (b->type != type) {
switch (type) {
case MEMORYBLOCKTYPE_MALLOC: EMC_ERROR("The memory block type must be MALLOC."); break;
case MEMORYBLOCKTYPE_NEW: EMC_ERROR("The memory block type must be NEW."); break;
default: EMC_ERROR("Error"); break;
}
}
_removeListItem(g_numBlocks, g_blocks, b);
freeMemoryHint(b->hint); b->hint = nullptr;
SAFE_FREE(b->block);
SAFE_FREE(b);
return;
}
}
EMC_ERROR("A memory block could not be found when trying to free.\n\nExamples:\n - Calling free(pointer) where pointer was not set to zero after it's been called twice, the solution was to use SAFE_FREE(). And if possible, replace any free() with SAFE_FREE(). For example, see Lexer::read0() on the original line \"free(out.asIdentifier);\".\n - If an 'Engine' object is destroyed before destroying a Vulkan object then it can cause this error (It can happen with 'Release' or 'RelWithDebInfo' configuration but not with 'Debug' configuration), that problem happened to me and I stuck there for hours until I realized it.");
}
I would humbly suggest that without a very clear reason to think otherwise the best memory management for GCC C++ is the out-of-the-box default memory management for GCC C++.
That would mean your best solution would have been to do nothing or as it is now strip out your overrides of the global operators.
You may find in some area of a system the default memory management is sub-optimal but in 2022 the default options are very effective and if you find a general purpose strategy that is better it's a publishable paper.
However your question tells us nothing about the application in question or your motivations for thinking you should even try to change the memory management let alone give advice on what to.
Sure you can add a global allocation mutex to block memory management and make it thread-safe. I will be surprised if that doesn't turn out to more than throw away whatever advantage you're hoping to gain.
Not sure where SAFE_FREE is coming from.
If you see in MALLOC function, they use c runtime malloc. Meaning that if you want to free the block you need to use the corresponding free() function.
Make sure that SAFE_FREE is indeed using the c runtime free with the correct parameters.

how to find and track objects and functions that using a dynamically allocated memory?

if we have a code like this :
void g(int *n){
int m=n[1];
n= null ;
}
void main(){
int ∗ p = (int∗)malloc(10 ∗ sizeof (int));
int * q= p;
g(p);
}
so we know if we overload malloc, calloc , realloc ,new ,free and delete functions we can track first pointer that create or delete with this functions(p pointer in above example ) but how can i find and track other pointers and functions that using this allocated memory ? ( q pointer and g function in above example ) .should i overload Assignment statement and function call ? if yes how ? in other words i want to know live objects and last used time and location of an allocated memory too .
i want to implement an custom memory leak detection tools so i need to find all objects and pointer that using an allocated memory before report it that's leak or not .
What you're talking about is called reference counting. Searching stackoverflow for this topic gives you these results:
What is a smart pointer and when should I use one?
How does a reference-counting smart pointer's reference counting work?
what exactly reference counting in c++ means?,
The standard template library already has a std::shared_ptr which does that.
We need to keep track of the lifecycle of the resource, this is possible by controlling the creation, the copying and the deletion. If you do it without using a class, you'll end up with some functions, a state variable and a global variable. This is not very effective in keeping the concept of the shared resource in the focus of the user's mind. One tends to forget that one is using a shared resource because one sees a naked pointer and tends to use it like one, which would disregard the provided shared-pointer functionality.
If you were to encapsulate the functionality in a class, you should want to implement the concept for all types i.e. you should want to use templates. one way would be this:
#include <vector>
#include <stdexcept>
#include <cstdlib>
#ifndef SHARED_PTR_H
#define SHARED_PTR_H
template<class Tres_t>
class shared_pointer
{
// members
Tres_t* data_;
static std::vector<std::size_t> ref_counts_;
std::size_t ref_index_ = {};
public:
// lifecycle
shared_pointer() = delete;
shared_pointer(const std::size_t& size)
: data_{nullptr}
{
data_ = static_cast<Tres_t*>(std::malloc(size * sizeof(Tres_t)));
ref_counts_.push_back(1);
ref_index_ = ref_counts_.size() - 1;
}
shared_pointer(const shared_pointer& rhs)
: data_{rhs.data_}, ref_index_{rhs.ref_index_}
{
if (ref_index_ >= ref_counts_.size())
{
throw std::runtime_error("shared_pointer_ctor_index_error");
}
++ref_counts_[ref_index_];
}
shared_pointer(shared_pointer&& rhs)
: data_{rhs.data_}, ref_index_{rhs.ref_index_} {}
shared_pointer& operator=(const shared_pointer& rhs)
{
data_ = rhs.data_;
ref_index_ = rhs.ref_index_;
if (ref_index_ >= ref_counts_.size())
{
throw std::runtime_error("shared_pointer_ctor_index_error");
}
++ref_counts_[ref_index_];
}
shared_pointer& operator=(shared_pointer&& rhs)
{
data_ = rhs.data_;
ref_index_ = rhs.ref_index_;
}
~shared_pointer()
{
if (ref_counts_[ref_index_] == 0)
{
std::logic_error("shared_point_dtor_reference_counting_error");
}
--ref_counts_[ref_index_];
if (ref_counts_[ref_index_] == 0)
{
std::free(data_);
}
}
// main functionality
Tres_t* data()
{
return data_;
}
const Tres_t* data() const
{
return data_;
}
};
template<class Tres_t>
std::vector<std::size_t> shared_pointer<Tres_t>::ref_counts_ = {};
#endif
I've tested this code only rudimentarily so I'll leave it to you to test and improve it.

Can I be sure that the binary code of the functions will be copied sequentially?

Sorry if this question already exist, because I hope this approach is used but i just don't know how this called. So, my purpose to execute sequence of functions from memory, for this I copied size of first and last func.
This is my first try:
source.cpp
void func1(int var1, int var2)
{
func2();
func3();
//etc.
}
void func2(...){...}
void func3(...){...}
void funcn(){return 123;}//last func as border, I do not use it
//////////////////////////////////////////////////
main.cpp
#include"source.cpp"
long long size= (long long)funcn-(long long)func1;// i got size of binary code of this funcs;
// and then i can memcpy it to file or smth else and execute by adress of first
Firstly it's worked correct, but after updating my functions it's crashed. Size has become negative.
Then i tried to attach it to memory hardlier:
source.cpp
extern void(*pfunc1)(int, int);
extern void(*pfuncn)();
void(*pfunc1)(int , int) = &func1;
void(*funcn)() = &funcn;
static void __declspec(noinline) func1(int var1, int var2)
{
//the same impl
}
static void __declspec(noinline) func2(...){...}
static void __declspec(noinline) func3(...){...}
static void __declspec(noinline) funcn(...){retunr 123;}
//////////////////////////////////
main.cpp
#include"source.cpp"
long long size= (long long) pfuncn - (long long) pfunc1;
//same impl
This worked after my 1st update, but then, I had to update it again, and now this gives me wrong size. Size was near 900+ bytes. I changed some funcs, and size become 350+ bytes i haven't changed that many.
I disabled optimizations and inline optimizations.
So my question is how to be sure that my func1 will be less adress then last funcn and what could change their locations in memory. Thank you for attention.
// and then i can memcpy it to file or smth else and execute by adress of first
copy it in memory and then call it in allocated memory and then call by adress of allocation.
This needs to be stated:
You cannot copy code from one location to another and hope for it to work.
There's no guarantees that all the code required to call a function
be located in a contiguous block.
There's no guarantee the function pointer actually point to the
beginning of the needed code.
There's no guarantees you can effectively write to executable memory. To the OS, you'd look a lot like a virus.
there's no guarantees the code is relocatable (able to work after being moved to a different location). for this it requires to use only relative addresses
In short: unless you have supporting tools that go beyond the scope of standard C++, don't even think about it.
GCC family only!
You can force the compiler to put the whole function to separate section. Then you can know the memory area where the funcion resides.
int __attribute__((section(".foosection"))) foo()
{
/* some code here */
}
in linker script in the .text you need to add
.text :
{
/* ... */
__foosection_start = .;
*(*foosection)
*(.foosection*)
__foosection_end = .;
/* .... */
and in the place where you want to know or use it
extern unsigned char __foosection_start[];
extern unsigned char __foosection_end[];
void printfoo()
{
printf("foosection start: %p, foosection end: %p\n ", (void *)__foosection_start, (void *)__foosection_end);
}
This is probably not possible because of a requirement you did not mention, but why not use an array of function pointers?
std::function<void()> funcs[] = {
func2,
func3,
[](){ /* and an inline lambda, because why not */ },
};
// Call them in sequence like so:
for (auto& func: funcs) {
func();
}

Debug assertion failure with stringstream::imbue and custom global operator new

I apologize for this rather localized question, but I was hoping to get someone else's view on it to make sure I'm not doing something obviously wrong.
I believe I've run into a bug in either the Visual C++ Runtime library, or somewhere in Microsoft's implementation of std::stringstream. The issue only manifests itself with a combination of when:
imbue() is called to change the locale on a stringstream, and
A custom global operator new is used which returns a pointer offset from the base address returned by malloc() used to allocate the block.
I've been able to reproduce this with the following minimal test case:
#include <sstream>
static void *localMalloc(size_t bytes)
{
unsigned char *ptr = static_cast<unsigned char *>( malloc(bytes + 64) );
ptr += 64;
printf("malloc of %d bytes: %ph\n", bytes, ptr);
return ptr;
}
void *operator new(size_t bytes) { return localMalloc(bytes); }
void *operator new[](size_t bytes) { return localMalloc(bytes); }
void operator delete(void *ptr) throw() { /* do nothing */ }
void operator delete[](void *ptr) throw() { /* do nothing */ }
struct DecimalSeparator : std::numpunct<char>
{
char do_decimal_point() const
{
return '.';
}
};
int main()
{
std::stringstream ss;
ss.imbue(std::locale(std::locale(), new DecimalSeparator));
ss << 5; // <-- is_block_type_valid(header->_block_use) assertion failure here
return 0;
}
If either:
The ptr += 64; line in localMalloc() or
The ss.imbue() call in main()
are commented out, the code works as expected, and the assertion does not happen.
I have attempted to step into the code using the debugger as much as I possibly can, but I am currently unable to pinpoint the location at where the code fails in the STL as Visual Studio dumps me into raw disassembly mode after stepping out of basic_stringbuf::overflow() making debugging nearly impossible. As far as I can tell, I haven't seen any invalid memory writes outside of the memory being allocated, so I'm not completely sure where the CRT is checking the heap or why it thinks the pointer is invalid.
Note that operator delete is intentionally ignoring the free in this test case for brevity. It makes no difference if free() is properly called on the memory block or not.
So far, tests on the following compilers and platforms have resulted in:
VS2015 Update 2: fails
VS2015 Update 3: fails
VS2015 Update 3 + KB3165756: fails
clang-703.0.31 on OSX: OK
Does anyone see anything odd here that I missed?
This is more than likely not a bug, but instead your version of delete is not being called, and instead the Visual Studio's debug runtime library's version of the global delete is called. Having two or more versions of the global delete operator in the same program is undefined behavior.
From this reference (Global replacements), the behavior is stated to be undefined when this occurs.
From the C++ ISO standard:
3.7.4 Dynamic storage duration [basic.stc.dynamic]
//...
§2 The library provides default definitions for the global allocation
and deallocation functions. Some global allocation and deallocation
functions are replaceable (18.6.1). A C++ program shall provide at
most one definition of a replaceable allocation or deallocation
function.
Running with Visual Studio 2015 using the release version Visual Studio runtime library does not produce this error, and the replacement global delete does in fact get called.
Visual Studio 2015 online compiler results.

Override global new/delete and malloc/free with tcmalloc library

I want to override new/delete and malloc/free. I have tcmalloc library linked in my application. My aim is to add stats.
From new I am calling malloc. Below is an example it's global.
void* my_malloc(size_t size, const char *file, int line, const char *func)
{
void *p = malloc(size);
....
....
....
return p;
}
#define malloc(X) my_malloc(X, __FILE__, __LINE__, __FUNCTION__)
void *
operator new(size_t size)
{
auto new_addr = malloc(size);
....
...
return new_addr;
}
New/delete override is working fine.
My question is what happen to other file where I have use malloc directly for example
first.cpp
malloc(sizeof(..))
second.cpp
malloc(sizeof(..))
How this malloc call get's interpret as my macro is not in header file.
tcmalloc provides new/delete hooks that can be used to implement any kind of tracking/accounting of memory usage. See e.g. AddNewHook in https://github.com/gperftools/gperftools/blob/master/src/gperftools/malloc_hook.h