-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Limit dynamic memory allocation to stack + reserve #555
base: master
Are you sure you want to change the base?
Conversation
The current _sbrk allows the entire RAM (including space already used by the stack) to be allocated by malloc/new calls. This change limits it to the bottom of the stack + an application modifiable reserve (to make sure there is a minimum amount of space reserved for future stack growth).
The current _sbrk allows the entire RAM (including space already used by the stack) to be allocated by malloc/new calls. This change limits it to the bottom of the stack + an application modifiable reserve (to make sure there is a minimum amount of space reserved for future stack growth).
merge to master
Oops - I got it wrong. Seems like it posted all the diffs to the main fork, even if they are 0 change diffs... The actual diff is 707ebdf... |
This looks like its your area of expertise. Could you let me know what you think ? |
malloc isn't ever going to be perfect, You are only looking at a snapshot of the stack usage when you call malloc. In the next call, the stack might walk all over the heap. A better strategy is to allocate all the memory you need up front so that it isn't changing. You should also monitor your maximum stack usage by coloring the stack and then monitoring it. |
As noted, the ARM7m series lacks hardware support for stack protection, so there can be no perfect solution on this hardware. The proposal is a small change that at least prevents malloc from immediately trashing the stack. For advanced users, __sbrk_stack_reserve is exposed so they can provide an estimate of stack requirements for additional safety. Pre-allocating memory is a worse strategy, as it is allocated on the heap anyway, and will still be trashed if the stack grows. Using malloc with a stack reserve will at least fail the malloc, if there is not enough space for the estimated stack requirements. While my proposal is not perfect (it can not be perfect due to hardware limitations), it adds virtually no overhead and at least provides a degree of protection. |
what do you plan to do when your malloc fails? |
The alternative to the approach suggested is to move the STACK pointer to say 0x2000 0000+1024 at the start. Move the start of heap to 0x2000 0000+1024 and let it grow to 0x2000 5000. If the stack grows too much it will trigger a fault. they will never clash. *Assuming BluepIll You can override the Reset_handler and memory routines I think they are all weak symbols. |
Thinking about it more you would also have to adjust the start of RAM to be above the stack .. so 0x2000 0000+1024 and heap to start at start of RAM + size of data + size of bss |
malloc() will fail at the moment, if you attempt to allocate beyond the end of RAM. A well behaved application will detect the null return and either attempt a smaller malloc(), or fail in a sane way. A badly behaved application will always fail no matter what. The current sbrk() implementation will cause malloc() to fail and return NULL when allocating beyond the end of RAM. It will succeed, but crash the board if you allocate below the end of RAM, but above the bottom of the stack. It will succeed and work normally if you allocate below the bottom of the stack. The proposed change causes the allocation to fail gracefully in the conditions that would have resulted in an immediate hang, at least giving the application developer the option of handling it gracefully. I did consider your other proposals, but found them unworkable/impractical. Moving the stack to the bottom of RAM is a great solution, as it is the only way to get hardware stack protection on this mcu. But it has problems. It will be impossible to pick an initial stack size that will be compatible with all existing apps/libraries (I have seen some that use big automatic variables, and some that use big dynamic allocations), so the target stack size would need to be modified on a per-application basis. Bootloader RAM would need to be above this configurable stack (probably only practical to locate it at the end of RAM) - but all bootloaders will need to be recompiled and reflashed to make the boards compatible with this change. Pre-allocating memory and dishing it out is just an all round bad idea. You need to add your own memory manager. Overload new() and delete() to use your new memory manager. Avoid every libc call which uses malloc() internally (printf and many others). And in the end all you achieve is that the compiler tells you how much stack space you have available. That is why I finally proposed this change. It is not ideal (no hardware stack protection). Is fully compatible with all well behaved programs (all programs, if you set __sbrk_stack_reserve to 0). Removes the case where malloc() currently crashes. And for the more advanced programmers, allows them to specify the required stack size (so you get all the advantages of pre-allocated memory, without any disadvantages). |
I personally feel you should suffer failure if you are using malloc()/new in an embedded application. Memory fragmentation will eventually cause your device to fail at the least opportune moment. So I'm probably the wrong person to be commenting on this. |
I'm afraid I agree with Rick. Changing the core to handle the over allocation of RAM could potentially cause problems for existing users of the core as it appears to change the amount of RAM available slightly IMHO, embedded applications should be written so that they don't allocate more RAM than is available. And using multiple dynamic allocations and de-allocations, would potentially cause memory fragmentation and lead to there not being enough RAM for the application to run. I think that Arduino's String class may use dynamic allocation, but I think the jury is out on whether the inclusion of the String class as part of the API was a good idea or not. |
While I agree that you should not really use dynamic memory in an embedded application, you do need to take the Arduino environment into account. C++ itself relies on dynamic memory for many constructs. And the libc packaged with the development environment depends on malloc for more than 50% of its functions (especially when you start using network related stuff). If you want to move away from dynamic memory, the first thing to do is replace the default libc (which is really not intended for embedding) with one designed for embedding (eg ucLibc or its descendants). Otherwise anything but the most trivial applications will be using dynamic memory anyway. Since the development environment as it is at the moment relies on dynamic memory, I feel that some effort should be made to ensure that it is as safe as it can be. |
I am totally an amateur regarding the dynamic memory allocation. But... |
If you want examples, the only reason I looked at this was due to two posts on the forum, both of which generated unexpected crashes (instead of NULL returns): When I looked at these cases I noticed what could only be called a bug in the _sbrk() implementation, and proposed a minimal fix. |
Add -fno-use-cxa-atexit compiler flag
I have been trying various alternatives, so that there can be some scratch space for libc and libstdc++ (which all use dynamic memory for various functions). As a variant on Rick's suggestion, I modified the current _sbrk() to '__weak' and provided my own one in my application, which allocates from a static array. This works perfectly, but I still saw throb() for various test cases. A little digging shows that C++ relies on exceptions for allocation failures. So there is no way dynamic memory can ever be really safe in this environment. The best we can hope for is to make it safer... With that in mind, how do you feel about making _sbrk() weak so that application writers can provide their own implementation when required? |
Although the changes are moderate, the effect is unpredictable. |
This is old, but to provide some conclusion I think declaring _sbrk() weak should not affect anything at all, so why not? |
Actually, you can already limit dynamic memory allocation by defining the
The code in
That currently sets the upper heap limit to whatever the linker defines as The STM32F103VG has 96K of RAM so I'm not opposed to making And yes, before someone says something about the STM32F103VG not being a supported target. I know the "official core" doesn't support it, but I've added support locally on my fork. However, the same define also works for the other variants/targets, since |
The current _sbrk() implementation allows dynamic allocations all the way to the top of RAM - overwriting the stack and crashing the board.
There does not seem to be a way to hardware limit the stack, but this small change provides a minimal amount of protection, and a mechanism to tune it for the application being developed.
The 'ret' variable should be allocated at the very bottom of the stack - so its address is the top of the free space for the heap. __sbrk_stack_reserve is a tunable parameter to allow the application developer to leave additional space for further stack growth.