-
Notifications
You must be signed in to change notification settings - Fork 6
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
Every error is rb_vm_raise
Is this normal?
#3
Comments
Crashlytics groups the crash reports by the name of the last symbol in the stack trace. In this case, It would be ideal if there was an option to group crashes by exception message or other metric. I'm leaving this issue open and will update it if I find a way to do this. |
It would be great if something could be done on this. Our clients have access to the Crashlyrics account too, and they have no way of knowing what these messages mean so assume everything is urgent. >.< |
Hey All - Matt from Crashlytics here. This is "normal" in the sense that Crashlytics' backend system needs fairly extensive knowledge about system internals to do even semi-intelligent crash grouping. We have lots of custom rules/patterns for iOS itself, but not for ruby motion's internals. We only group on the last symbol as a last resort when we have no other means of determining what to blame. Now, there is some hope :) We have an API (iOS only I'm afraid) that is specifically built to accommodate this kind of environment. See -recordCustomExceptionName:reason:frameArray: in Crashlytics.h. It could allow you to actually expose a pure-Ruby stack trace in our UI. Now, this still would not solve the problem of our server-side analysis not understanding your code/environment. But, it might prove to be a better approach. I've seen it work well enough for Lua, Javascript, and C# in production. Nothing is going to be as good as us making a custom analysis engine, but that's not something we can commit to at the moment. For now, I hope this is a) technically feasible b) enough to get you by for now. |
@mattmassicotte Thanks for chiming in! I will investigate in this direction and try to build a system to report more friendly stack traces 🙂 |
I'm happy to help however I can. Let me know how this goes! |
@mattmassicotte Im making some progress, but just got stuck in something:
I thought about NOT calling Crashlytics exception handler from my own exception handler, but then if I call Any idea how to prevent Crashlytics from logging the default exception and prevent the signal handler from logging an exception I have already handled? Also, do you know of any open source implementation of something similar to this? Maybe one of those you mentioned (Lua, Javascript, and C#) Thanks! |
I'm not aware of an open-source example, unfortunately. Can you explain to me why you need the set_terminate? Does RubyMotion rely on the C++ exception mechanism for Ruby exceptions? If so, this will be trickier. I don't believe this is how Lua works, and is definitely not how Javascript works either. Both propagate the exception within their own VMs, and have an opportunity to call native code before terminating the process. |
RubyMotion compiles .rb files into LLVM IR that is then linked into the final executable. Exceptions are really Objc exceptions and use the C++ exception mechanism. For all Crashlytics knows, RubyMotion exceptions are Objc exceptions. The issue comes from the fact that the stack trace contains symbols from methods of the application AND internal symbols from the RubyMotion VM. This is better understood with an example:
For all the symbols from the application binary (sample-app), only those that are symbolicated correspond to Ruby methods written by RubyMotion users:
In this case class AppDelegate
def foo
end
end The rest of the symbols, are internal symbols from the RubyMotion VM:
These symbols not only give little useful information to the user, but also confuse Crashlytics grouping mechanism into believing all crashes whose last symbol from the application binary is e.g. My idea is to use a custom exception handler which takes only the relevant symbols from |
I've noticed a couple differences when using
This is the same exception, with custom logging and with default logging |
Ah - yes. Marking them as non-fatal was, I believe, a bug. For reasons, its not straight-forward for us to fix it. The lack of other stack traces is kinda surprising, but I haven't looked into it recently. Once you do the custom logging, does the process exit? |
This is the code I have come up with: #import "Crashlytics/Crashlytics.h"
#import <exception>
#import <dlfcn.h>
OBJC_EXTERN void *__cxa_allocate_exception(size_t thrown_size);
OBJC_EXTERN void __cxa_throw(void *exc, void *typeinfo, void (*destructor)(void *)) __attribute__((noreturn));
OBJC_EXTERN void *__cxa_begin_catch(void *exc);
OBJC_EXTERN void __cxa_end_catch(void);
OBJC_EXTERN void __cxa_rethrow(void);
OBJC_EXTERN void *__cxa_current_exception_type(void);
static void (*old_terminate)(void) = nil;
#define BLACKLIST_SIZE 6
static char *blacklist[BLACKLIST_SIZE] = {
"rb_vm_raise",
"rb_exc_raise",
"rb_vm_dispatch",
"vm_dispatch",
"rb_vm_trigger_method_missing",
"rb_vm_method_missing"
};
void
SendExceptionToCrashlytics(NSException*exception)
{
NSArray *addresses = [exception callStackReturnAddresses];
NSMutableArray *stack_frames = [NSMutableArray new];
for (NSNumber *address in addresses) {
Dl_info info;
if (dladdr((void*)[address unsignedIntegerValue], &info) == 0) {
continue;
}
bool should_continue = false;
for (int i = 0; i < BLACKLIST_SIZE; i++) {
if (strcmp(blacklist[i], info.dli_sname) == 0) {
should_continue = true;
break;
}
}
if (should_continue) {
continue;
}
CLSStackFrame *stack_frame = [CLSStackFrame stackFrameWithAddress:[address unsignedIntegerValue]];
[stack_frames addObject:stack_frame];
}
[[Crashlytics sharedInstance] recordCustomExceptionName:[exception name] reason:[exception reason] frameArray:stack_frames];
}
void
RubyMotionCrashlyticsExceptionHandler(void)
{
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
SendExceptionToCrashlytics((NSException*)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
extern "C"
void
RubyMotionCrashlyticsInstallExceptionHandler(void)
{
old_terminate = std::set_terminate(&RubyMotionCrashlyticsExceptionHandler);
} So right now the I do not exit after the custom logging, I call the next exception handler. As you can see, the symbol blacklist is pretty small at this point. I was wondering, what are the current heuristics Crashlytics uses to determine which frames are blamed for a crash? Would it be possible for us (RubyMotion) to provide a list of internal symbols to mark as never to be blamed for a crash? |
I think that would be the simplest option, and would produce the best results. It does require some work on our end, which I cannot commit to at the moment. Let me get back to you, post-WWDC. |
That would be awesome! :) I've implemented this workaround in a production app so I can start gathering information on which other symbols we may need to filter out. |
After speaking with the team, I don't think this is something that we can address in the immediate term on our end. We've noted it needs to happen, and will keep you up to date on any changes here. Please let me know how your experiment goes as well. |
@mattmassicotte @MarkVillacampa I'm wondering if any progress was made on this since the last comment. We're running into this issue as well and it makes it hard to filter through the crashes when they are all grouped together. |
@andrewhavens I'm afraid I no longer work on the Fabric/Crashlytics products. I don't have access to the code anymore - but I'm happy to consult and/or refer as I'm able. In the meantime, I'd definitely recommend reaching out to the Crashlytics support people. |
@andrewhavens have you tried my workaround here? It worked fairly well for me in production. EDIT: this is an updated version with some extra function names added #import "Crashlytics/Crashlytics.h"
#import <exception>
#import <dlfcn.h>
OBJC_EXTERN void *__cxa_allocate_exception(size_t thrown_size);
OBJC_EXTERN void __cxa_throw(void *exc, void *typeinfo, void (*destructor)(void *)) __attribute__((noreturn));
OBJC_EXTERN void *__cxa_begin_catch(void *exc);
OBJC_EXTERN void __cxa_end_catch(void);
OBJC_EXTERN void __cxa_rethrow(void);
OBJC_EXTERN void *__cxa_current_exception_type(void);
static void (*old_terminate)(void) = nil;
#define BLACKLIST_SIZE 16
static const char *blacklist[BLACKLIST_SIZE] = {
"rary_aref",
"rb_const_get_0",
"rb_exc_raise",
"rb_exc_new",
"rb_invalid_str",
"rb_mod_const_missing",
"rb_vm_raise",
"rb_vm_dispatch",
"rb_vm_trigger_method_missing",
"rb_vm_method_missing",
"send_internal",
"rb_f_raise",
"rb_Integer",
"rb_num2long",
"send_internal",
"vm_dispatch"
};
extern "C"
void
SendExceptionToCrashlytics(NSException*exception)
{
NSArray *addresses = [exception callStackReturnAddresses];
NSMutableArray *stack_frames = [NSMutableArray new];
for (NSNumber *address in addresses) {
Dl_info info;
if (dladdr((void*)[address unsignedIntegerValue], &info) == 0) {
continue;
}
bool should_continue = false;
for (int i = 0; i < BLACKLIST_SIZE; i++) {
if (strcmp(blacklist[i], info.dli_sname) == 0) {
should_continue = true;
break;
}
}
if (should_continue) {
continue;
}
CLSStackFrame *stack_frame = [CLSStackFrame stackFrameWithAddress:[address unsignedIntegerValue]];
[stack_frames addObject:stack_frame];
}
[[Crashlytics sharedInstance] recordCustomExceptionName:[exception name] reason:[exception reason] frameArray:stack_frames];
}
void
RubyMotionCrashlyticsExceptionHandler(void)
{
if (! __cxa_current_exception_type()) {
// No current exception.
(*old_terminate)();
}
else {
// There is a current exception. Check if it's an objc exception.
@try {
__cxa_rethrow();
} @catch (id e) {
SendExceptionToCrashlytics((NSException*)e);
(*old_terminate)();
} @catch (...) {
// It's not an objc object. Continue to C++ terminate.
(*old_terminate)();
}
}
}
extern "C"
void
RubyMotionCrashlyticsInstallExceptionHandler(void)
{
old_terminate = std::set_terminate(&RubyMotionCrashlyticsExceptionHandler);
} |
Hey @MarkVillacampa Thanks for chiming in. I thought you said there were still problems with this approach. Didn't you say the exception would be logged twice? Also, can you tell me how I would integrate this patch into my app? 😄 Can we update the motion-fabric gem to automatically apply this patch? |
The exception would be recorded twice, once as a crash, and once as a non-fatal exception: You'll need to ignore the crashes whose last function in the stack trace is included in the blacklist. The way you'd integrate this into your application is the following:
Fabric.with([Crashlytics.sharedInstance])
RubyMotionCrashlyticsInstallExceptionHandler()
void RubyMotionCrashlyticsInstallExceptionHandler(void);
app.vendor_project("vendor/trace-helper", :static,
cflags: "-F'#{File.expand_path(Motion::Project::CocoaPods::PODS_ROOT)}/Crashlytics/iOS/'") And that's it! It can definitely be included in Let me know if it works. |
Hi guys
I've installed
motion-fabric
to use crashlytics.It seems to be catching the errors OK, but they are all listed as
rb_vm_raise
errors, under the same issue.Is this the expected behaviour, or am I doing something dumb?
The text was updated successfully, but these errors were encountered: