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

minimal implementation of sdk-version dependent... #752

Closed
wants to merge 1 commit into from

Conversation

supervacuus
Copy link
Collaborator

...dynamic loader, but mostly to show that using the generic unwinder is probably a dead end. There was a lot more time invested than is visible, but I wanted to break this down to a minimal implementation to be able to discuss its issues. This contains an approach to tackle #743, but it has many problems:

  • libunwindstack, in SDK 28+, is still not exposed via any recent NDK and is thus not accessible to App developers or their dependencies but only to platform developers.
  • it seems clear that the generic unwinder does not wrap AOSP libunwindstack to make it available to app developers indirectly, so while this PR uses it for SDKs >= 28, the unwinder doesn't provide a better stack-trace than our stripped-down version of libunwindstack.
  • in addition to the above, the generic unwinder does not provide an interface to unwind relatively to the registers in ucontext_t or a passed-in address, which leads to stack traces that start from sentry-native frames.

So, at this point, it is relatively obvious that we cannot release this simply because it provides no visible improvements to the users (or rather regressions if you are deploying on targets with SDK >= 28).

It is not all bad, though:

  • by packaging our fork of libunwindstack in a shared library, which we dynamically load during sentry_init, I could get down to 1.5MiB (for the .so) from 2MiB (for the .a) on aarch64.
  • solely depending on the NDK unwinder (not that it would help us in the current implementation), produces an unwinder .soof only 26KiB, which would allow users who target only SDK >= 28 to drop the heavy legacy .so from their deployment packages.

So the question is how we should proceed with this (if at all):

  • we can stay with what we currently have and only provide the upstream updates.
  • we can pull in the upstream updates and change the unwinder on android to be dynamically loaded during sentry_init, cash in the package-size reduction, but not provide any improvements for SDK >= 28.
  • we can further investigate and try to resolve the C++ symbols from the platform libunwindstack dynamically, but it is very unclear how successful this will be. For one, it requires us to provide a minimal header environment to build the unwinder code in sentry-native and it requires the platform's builders to provide symbol-name stability for us to find even the small subset of the interface across deployed androids in the wild.
  • we can see if the backtrace() implementation in API 33 wraps libunwindstack and then replace the generic unwinder implementation we have now with backtrace(). In the worst case, it would at least provide us with an interface for a relative start into the stack.

Please chime in with further suggestions.

...dynamic loader but mostly to show that using the generic
unwinder is probably a dead end.
@github-actions
Copy link

Fails
🚫 Please consider adding a changelog entry for the next release.
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

## Unreleased

- minimal implementation of sdk-version dependent ([#752](https://github.com/getsentry/sentry-native/pull/752))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description.

Generated by 🚫 dangerJS against 6297019

Copy link
Member

@Swatinem Swatinem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm, although it is a bit complex admittedly.

No matter what we decide specifically, we should update our fork regardless, maybe independently from this PR.

void
sentry__unload_unwinder()
{
int result = dlclose(handle);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

manpage doesn’t really say if you can safely pass NULL here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should surely check this either way.

Comment on lines +13 to +14
static android_unwind_stack_t android_unwind_stack;
static void *handle;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should explicitly initialize these to NULL

@@ -1,6 +1,5 @@
extern "C" {
#include "sentry_boot.h"
#include "sentry_core.h"
#include <sentry.h>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this whole file isn’t used anymore, is it?

(void)addr;
(void)uctx;
trace_state state = { ptrs, ptrs + max_frames };
_Unwind_Backtrace(unwind_callback, &state);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always confuse the _Unwind_* and the unw_* APIs. So unfortunate that the first one does not allow unwinding from uctx.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unw_ prefix is the "namespace" of libunwind which would be very convenient library and I would love to use, but AOSP dropped it and upstream doesn't really support Android.

But even if it would provide a uctx interface, I am not sure if the added complexity justifies the changes, as long as it doesn't offer any expected/significant improvements in the stack trace (for instance by mapping to libunwindstack).

This is especially true since most users use a pre-built package, which doesn't gain anything from the size improvements when using only a slimmapper.so, without ("manually") removing the legacy.so.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

libunwind is still deployed with NDK as the unwinder for C++ exception-handling and C scope-cleanups but it is not exposed as an app-developer library.

The hint that NDK versions below r23 re-export the symbols of the previous libgcc unwinder, potentially screwing with client code that uses exception-handling, shows that stability of that "interface" is not a driver for decisions). Nonetheless, i can also try to access this implementation.

@supervacuus
Copy link
Collaborator Author

No matter what we decide specifically, we should update our fork regardless, maybe independently from this PR.

This is probably the most valuable outcome of this investigation.

@supervacuus
Copy link
Collaborator Author

Closing since there is no conclusive net improvement visible for the changes required:

  • libunwindstack is still a platform library and not exposed to NDK users.
  • we cannot load (via dlopen/dlsym) the deployed libunwindstack.so system-library, even if we were able to provide a header-less mapping to its symbols, because android doesn't allow loading system libraries. I think this is for good reason, because an application cannot provide a stable mapping cross-version or cross-OEM. It is hidden for a reason.
  • using the generic unwinder does improve the stack trace slightly (it eliminates some frames, which we cannot for which we haven't even a mapping entry), but has no interface that allows us to provide a ucontext to start from (making the stack trace less helpful for users).
  • backtrace() in SDK_VER 33 is also implemented in terms of the generic unwinder: https://android-review.googlesource.com/c/platform/bionic/+/1863333/6/libc/bionic/execinfo.cpp (which we can
  • libunwinder is not supported as an NDK library and is currently only packaged in NDK as an unwinder for C++ exception handling.

what we can extract from this:

  • we can use an upstream synced version of our stripped down libunwindstack (which does not provide any considerable improvements visible in our limited testing). This will happen in a separate PR after merging Chore: update from upstream libunwindstack-ndk#6
  • we found out - as a side investigation - that for native crashdumps on android in release R and above we probably should use the tombstones, which provide a much richer crash context than we could ever extract ourselves (without access to system libraries). This should be discussed with in a bigger round, outside the confines of a PR.
  • we can try to further reduce the size a bit using our knowledge from this PR after updating our libunwindstack

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants