Skip to content
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

MISRA C Avoid dynamic memory allocation #9892

Closed
ceolin opened this issue Sep 10, 2018 · 12 comments
Closed

MISRA C Avoid dynamic memory allocation #9892

ceolin opened this issue Sep 10, 2018 · 12 comments
Assignees
Labels
area: MISRA-C bug The issue is a bug, or the PR is fixing a bug priority: medium Medium impact/importance bug
Milestone

Comments

@ceolin
Copy link
Member

ceolin commented Sep 10, 2018

Zephyr's kernel is using rb tree defined in rb.[ch]. This algorithm is using alloca() function to allocate memory using stack. If the allocation fails the behaviour is undefined.

part of: #9552

@ceolin ceolin added Feature A planned feature with a milestone priority: medium Medium impact/importance bug area: MISRA-C labels Sep 10, 2018
@ceolin ceolin added this to the v1.14.0 milestone Sep 10, 2018
@ceolin
Copy link
Member Author

ceolin commented Sep 10, 2018

@andyross This is not a trivial piece of code, can we check how much work is required to fix it ?

@andyross
Copy link
Contributor

So... alloca() is allocating memory "dynamically", but on the stack. It won't fail in the sense of malloc() where someone else is using the memory. The only failure case is a stack overflow, which is something that is always possible. What exactly does MISRA say? Note that there are a few other spots in our codebase where we're doing equivalent allocations via variable-sized arrays (e.g. int foo[some_runtime_value];) which are likely to need attention too.

There's no good workaround. The "fix" would be to always allocate a maximally sized block (which isn't huge -- probably hundreds of bytes) I guess. Maybe we could put this under a CONFIG_MISRA_PEDANTRY flag or something so that normal apps can use the appropriate tool.

But really I'd hope that stack allocation would be possible in conforming MISRA code. I mean... fundamentally it's no different than recursion in terms of expression and safety.

@ceolin
Copy link
Member Author

ceolin commented Sep 11, 2018

yep, it's allocating on stack but still dynamic. MISRA dooms dynamic allocation and everything should be pre-allocated. Btw, variable length arrays and alloca are different, they have different scope, memory allocated by alloca is release only when the function exits while the former is when the block exits.
VLAs are also part of C99 standard while alloca is not part of the standard. As fair as I understood there is no violation using VLAs. I'll double check it though.

@ceolin
Copy link
Member Author

ceolin commented Sep 11, 2018

sorry, VLAs are also not allowed. Dammit, we may have to define one pattern for these cases and maybe create a deviation. Anyway, between alloca() and VLAs I stand to VLAs, at least is covered by standard and we have less chance to find problems with different compilers.

@ceolin
Copy link
Member Author

ceolin commented Sep 26, 2018

Dir 4.12 and rule 21.3

@andyross
Copy link
Contributor

FWIW: everywhere I'm aware of this is being done, the memory usage isn't unbounded (in fact it's generally log-sized in whatever the relevant data structure is). It's possible to decide on a tractable maximum size upfront and simply use that, obviously at the cost of significantly larger stacks for the common case.

@mped-oticon
Copy link
Collaborator

But really I'd hope that stack allocation would be possible in conforming MISRA code. I mean... fundamentally it's no different than recursion in terms of expression and safety.

MISRA-C prohibits recursion, but you could probably have guessed this already.

Personal opinion: It seems silly to rely on dynamic memory to implement dynamic memory.
That makes me in favor of avoiding VLA+alloca, placing that need exclusively on malloc instead.

@mped-oticon
Copy link
Collaborator

FWIW, these are the places VLAs are currently used:

ext/lib/crypto/tinycrypt/source/hmac.c
	int tc_hmac_set_key(TCHmacState_t ctx, const uint8_t *key,
lib/mempool/mempool.c
	int _sys_mem_pool_block_alloc(struct sys_mem_pool_base *p, size_t size,
	void _sys_mem_pool_block_free(struct sys_mem_pool_base *p, u32_t level,
lib/rbtree/rb.c
	void rb_insert(struct rbtree *tree, struct rbnode *node)
	void rb_remove(struct rbtree *tree, struct rbnode *node)
subsys/fs/fcb/fcb_append.c
	int fcb_append_finish(struct fcb *fcb, struct fcb_entry *loc)

@andyross
Copy link
Contributor

An alloca()/VLA is hundreds of cycles faster than malloc() on most architectures (often more on some workloads if you factor in long term costs from fragmentation). In general it's implementable via a single subtraction from the stack pointer. It's absolutely the right way to solve problems like this where you have a need for small-ish amounts of non-constant-size memory that will affirmatively live only for the duration of a function call. That's why it's in the language, after all.

That said: the mempool and rbtree uses are mine, and those can be bounded well enough such that we can use the same code with a tolerable cost paid in stack sizing. I strongly suspect the same is true of tinycrypt (where it's likely to be a buffer sized according to algorithm and thus has some reasonable maximum). No idea about the flash thing.

@nashif nashif added bug The issue is a bug, or the PR is fixing a bug and removed Feature A planned feature with a milestone labels Feb 21, 2019
@ceolin
Copy link
Member Author

ceolin commented Feb 26, 2019

@andyross will this be done for 1.14 or should I change the milestone to future ?

@andyross
Copy link
Contributor

andyross commented Feb 26, 2019

In some form, sure. Worst case we can add maximally-sized iteration arrays somewhere (on the stack, or globally with appropriate locking, etc...).

(Edit: for clarity, that's talking about usage in the kernel. I don't think the tinycrypt and subsys/fs directories are in the line of fire for MISRAfiation in 1.14, right?)

andyross pushed a commit to andyross/zephyr that referenced this issue Feb 27, 2019
MISRA rules (see zephyrproject-rtos#9892) forbid alloca() and family, even though those
features can be valuable performance and memory size optimizations
useful to Zephyr.

Introduce a MISRA_SANE kconfig, which when true enables a gcc error
condition whenever a variable length array is used.

When enabled, the mempool code will use a theoretical-maximum array
size on the stack instead of one tailored to the current pool
configuration.

The rbtree code will do similarly, but because the theoretical maximum
is quite a bit larger (236 bytes on 32 bit platforms) the array is
placed into struct rbtree instead so it can live in static data (and
also so I don't have to go and retune all the test stack sizes!).
Current code only uses at most two of these (one in the scheduler when
SCHED_SCALABLE is selected, and one for dynamic kernel objects when
USERSPACE and DYNAMIC_OBJECTS are set).

This tunable is false by default, but is selected in a single test (a
subcase of tests/kernel/common) for coverage.  Note that the I2C and
SPI subsystems contain uncorrected VLAs, so a few platforms need to be
blacklisted with a filter.

Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
andrewboie pushed a commit that referenced this issue Feb 28, 2019
MISRA rules (see #9892) forbid alloca() and family, even though those
features can be valuable performance and memory size optimizations
useful to Zephyr.

Introduce a MISRA_SANE kconfig, which when true enables a gcc error
condition whenever a variable length array is used.

When enabled, the mempool code will use a theoretical-maximum array
size on the stack instead of one tailored to the current pool
configuration.

The rbtree code will do similarly, but because the theoretical maximum
is quite a bit larger (236 bytes on 32 bit platforms) the array is
placed into struct rbtree instead so it can live in static data (and
also so I don't have to go and retune all the test stack sizes!).
Current code only uses at most two of these (one in the scheduler when
SCHED_SCALABLE is selected, and one for dynamic kernel objects when
USERSPACE and DYNAMIC_OBJECTS are set).

This tunable is false by default, but is selected in a single test (a
subcase of tests/kernel/common) for coverage.  Note that the I2C and
SPI subsystems contain uncorrected VLAs, so a few platforms need to be
blacklisted with a filter.

Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
@andyross
Copy link
Contributor

andyross commented Mar 1, 2019

Merged the VLA patch for the kernel, which I think is the scope here. There are a few spots in the I2C and SPI drivers that can be hit (as failures -- we error on VLA usage now) by trying to build the full test suite with CONFIG_MISRA_SANE. We can address those in specific bugs as needed.

@andyross andyross closed this as completed Mar 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: MISRA-C bug The issue is a bug, or the PR is fixing a bug priority: medium Medium impact/importance bug
Projects
None yet
Development

No branches or pull requests

4 participants