-
Notifications
You must be signed in to change notification settings - Fork 774
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
Add a feature that disables panic messages #3129
Conversation
Wasn't sure if I should add this to all crates... I see that the |
With the additional fixes in the last few commits in this PR, I have now eliminated enough call sites that at least for my application, there are no longer any This means that I am able to fit my app + bootloader + DFU partitions onto a stm32f0 device:
|
There's ways to remove the panic fmt bloat without adding cfgs to every single crate. Have you tried these?
Works best when combined with opt-level Given these exist, I don't think we should add However, I see you've also changed many instances of |
Makes sense, and I have looked into the different approaches you list. I will have to resort to using such an approach if we can't find a way to offer this functionality in the upstream library (or just maintain my own fork of I want to stress that a major difference with this approach compared to the methods you listed is that you still get full panic info when something goes wrong, ie file/line/column number, debugger integration with backtraces, etc. This also works with all the existing routing logic that applies to I agree that we can definitely split out the changes regarding
Regarding the trade-offs between adding a feature flag for this vs. not supporting the use-case at all, I guess it would be possible to find a middle road? Would it be a reasonable compromise to extract the I think the reason why we have to thread feature flags like this -- where I would say the |
oh, that's true. have you tried printing only the location of the PanicInfo, and not the message? I suspect that'd still optimize out most of the fmt bloat without requiring patching every crate.
you still get a backtrace with
this has been tried many times and it doesn't work, please don't try more 😅 . defmt macros break when reexported, also we're relying on the in-crate macro name resolution to override |
To clarify: it is a goal of Embassy to allow getting code size as small as possible. My position is not "code size is not a priority so I think that is the case, at least. If you do find something that's achievable with |
OK, I will start by breaking out the I guess I'm chasing this from a few different angles at the same time, and my main goal is to meet my size constraints for now, so I won't have the time/energy to explore too many other alternatives, at least for now. The approach I'm taking right now is to load built binaries into Ghidra and other reverse-engineering tools to find code paths that lead to code bloat (along with bloaty etc as shown above). That's the only truly reliable way I have found to reason about this, because there is some really surprising stuff I wasn't able to find just reasoning about the source code (where it wasn't enough to remove an
I do agree that adding too much noise for features like this isn't worth it, on the other hand I also really value enabling people to just |
I thought I should provide some numbers to show what I'm working with:
So there's about 3kiB that disappeared through doing my handful of You can reduce code size by roughly 20% by disabling panics/having a trivial panic handler, but by roughly 30% (my app) or 40% (example stm32 bootloader) or 50% (my bootloader) if doing something invasive to the code, like changing the panic macros. |
and |
I wasn't able to test this at the moment because I haven't been able to find a nightly version that works for my build (although I have to admit that I haven't tried too hard to bisect nightly versions). EDIT: but I figure this will yield the lowest VM size for sure since it essentially removes all implicit panics, in principle, with a significant loss of ergonomics of course. |
I'm going to close this because I don't believe it's worth the effort given the reasons above, assuming Thanks anyway for the PR, tho :) If you find bloat that doesn't go away with |
@Dirbaio I have been going deep on this, see this thread for more details: https://rust-lang.zulipchat.com/#narrow/stream/327149-t-libs-api.2Fapi-changes/topic/Flexible.20fmt.20.20API.20that.20allows.20for.20deferred.20formatting I think since |
Can you expand on why? Is it because you want to still be able to get the location of the panic in logs, it is doable:
When you get a fault, you can find the line that caused it by If you additionally log the top X kb of stack, it's even possible to get a backtrace. See https://github.com/tweedegolf/stackdump for example. We do this at my company's firmware and it works great. Combine it with a utility that saves the last N kb of defmt logs on crash and uploads it to the server after reboot we even get remote crashdumps with logs+backtrace. All with zero strings in the firmware, and zero core::fmt bloat. |
Yeah, maybe you're right and I just haven't been able to spend enough time/thinking on about how to make it work. I guess in summary it sounds like you're saying "this is definitely possible to solve by combining these existing tricks" while I'm saying "I choose to hold off on trying to make this work until the solution requires fewer moving parts/is easier to reason about" I can share some more details just as a FYI/as context if you want to understand my situation, in case it helps when making future Some additional constraints are, for example, that the device only communicates over CAN-bus as the primary interface -- there's no serial interface available in the "production" context -- and it is in that context that I am trying to enable things like OTA updates. The firmware would need to receive OTA firmware over a CAN transport, and also do boot testing/communicate the outcome over CAN as well -- including panics that would indicate an update failure. I have also found that it might be tricky to reach the size constraints even with panic info removed, so I might need to find an asymmetrical solution where as part of a firmware update, the app would first flash a smaller firmware update application into a smaller partition, restart, then do the "main" firmware update into a larger partition. I have considered whether it would be possible to have a shared "CAN transport" library in a third partition that gets re-used by both the small bootloader and the main app. I have also considered things like storing a compressed version of the firmware and enabling the bootloader to do decompression-on-the-fly. All of this has added a bunch of complexity and I have found it difficult to track how things interact. For example, if I want to track whether a CAN peripheral is still available when a panic occurs, so that I might be able to communicate the failure over CAN bus -- this might not be possible/safe in general, but it would be nice if I could make it work. Another option would be to store failure info into flash so that after a panic it is possible to reboot and let the bootloader handle the firmware revert/reporting the status over CAN-bus. In general I would assume that any interaction with peripherals will be tricky when a hard fault has happened -- but maybe this is recoverable somehow and it'd be possible to make it work. All in all I want to hold off on trying to patch together something that works until I can rule out some dimensions of complexity, and not having to pay special attention to how panics are routed through the NVIC stack would be one such dimension. Maybe you could make it work with the existing tools as you describe. For my use-case I am looking for something that I will be able to trust and reason about, including not having to worry about something breaking in future refactors etc., taking into consideration the human factor of possibly having someone else write that code without the context I have in my head, ie the usual software development stuff 😄 |
Overview
Add a new feature that reduces binary size significantly for certain types of binaries. For example, for the stm32 bootloader example, about 39% of the binary is just strings used for panicking, and as shown below, the binary size can easily be reduced from
10.1kiB
to6.16kiB
.This is usually not a problem for programs that use
defmt
as the existingdefmt
infra already removes most strings from the binary, but usingdefmt
is not always desirable, e.g. for bootloaders.For my own application, I need to fit a bootloader into
3 kiB
, and this change allowed me to go from3.33 kiB
to2.77 kiB
.Details
Without this feature enabled, here's the binary size for the stm32 bootloader example:
With this feature enabled:
There are still some
.unwrap()
s and asserts left to be found, but this change removes most of them.