📜 ⬆️ ⬇️

Crash reports in * nix: backtrace, SEGFAULT (and reinterpret_cast)

Hi, dear habrayuzer!

All program developers sooner or later face the problem of the fall of the program from the user. But not everyone can get access to a specific computer on which something is going wrong, run gdb there and repeat the crash. And even getting information from the user is extremely difficult: a message “a program crashes, what to do?” Comes to the bugtracker (or tech support), but the user does not attach technical information that is so important to the developer. Yes, and not everyone will write about it! Just stop using the program - that's all.

Some OS offer to send crash-report to developers. But! OS developers, not you, that is, not the people who really need it! And then come to the aid of their own crash reports, which your program should send to your server. But how to make them? How to properly handle SEGFAULT and at the same time send intelligible information to the developer?

On Habré was already an interesting article from Arenim , dedicated to the treatment of beautiful. Briefly, I repeat the point: we catch the POSIX-signal SIGSEGV, and after processing it we exit the program.

void catchCrash(int signum) { reportTrouble(); //  - signal(signum, SIG_DFL); //   exit(3); //   } int main() { signal(SIGSEGV, catchCrash); //-- ... --// } 

Now it's up to you: localize the problem! And although the above method works in Windows, we can get a normal backtrace only in * nix (in fact, you can get it in Windows, but for this you have to distribute the debug build, which is not very good). So, we smoke manuals and do this:
 void reportTrouble() { void *callstack[128]; int frames = backtrace(callstack, 128); char **strs=backtrace_symbols(callstack, frames); //      crash_report.txt //         -  , , etc FILE *f = fopen("crash_report.txt", "w"); if (f) { for(int i = 0; i < frames; ++i) { fprintf(f, "%s\n", strs[i]); } fclose(f); } free(strs); system("curl -A \"MyAppCrashReporter\" --form report_file=@\"crash_report.txt\" http://reports.myserver.com"); } 

And everything, the report went to the server! If you want, you can ask the user before sending - and not whether to send us a reporter? Of course, in the GUI program it is a bit dangerous - after all, after the SEGFAULT, the adequacy of the internal state of the graphical framework (well, or bare X) is not guaranteed, so it’s better to warn the user in advance (for example, in the license agreement) send anonymous reports. ” The main thing is not to enter the user's personal information and other data into the report, it is not only immoral, but it can also be prosecuted (unless, of course, at the end of the license agreement the user’s consent is not written in small letters).
')
We will now test the method described in practice. Let's create a simple program with a simple class and simple additional functions. And try to drop this code. The simplest thing is to call the method at the null pointer to the class, but this is too primitive, let the pointer point “to the sky” better, so interesting. How to achieve this? Well, of course, apply the beloved reinterpret_cast all of us! So, to make the backtrack more interesting, we create the functions goCrash() and crash(void *) .
 int crash(void *obj) { Crasher *crasher = reinterpret_cast<Crasher *>(obj); crasher->doSomething(); return -1; } void goCrash() { const char *str = "Hello, crash!"; const char *str2 = "Hello again, crash!"; char str3[200]; sprintf(str3, "%s\t\t%s\n", str, str2); long long add = rand() % 20000 + 1500234000l; // fire in my leg! crash(reinterpret_cast<void *>(str3 - add)); } 

Well, it seems that we will send to our class Crasher some previously unknown address. Very curious! Let's declare the class:

 #define P_DOUBLE_COUNT 10000 class Crasher { public: // c-tor Crasher() { myPrivateString = new char[100]; sprintf(myPrivateString, "%s\n", "that\'s my private string!"); myPrivateInteger = 100; for (int i = 0; i < P_DOUBLE_COUNT; ++i) myPrivateDoubles[i] = i / 100.0; } // func void doSomething() { // here we can (?) crash fprintf(stderr, "%s\n", "That\'sa function!"); doSomethingPrivate(); } private: void doSomethingPrivate() { // crash? oh, no... fprintf(stderr, "%s myPrivateInteger == %d\n", "That\'sa private function!", myPrivateInteger); fprintf(stderr, "myPrivateDoubles[1] == %f\n", myPrivateDoubles[1]); fprintf(stderr, "myPrivateString == %p\n", myPrivateString); // still alive? crash! crash! crash! ((Crasher*)NULL)->doSomething(); } private: char *myPrivateString; int myPrivateInteger; double myPrivateDoubles[P_DOUBLE_COUNT]; }; 

Note that in the doSomethingPrivate() function, we all call the function at the null pointer. So, just in case. Suddenly, after calling doSomething() for an undefined address, will the program still survive?

You can now build and run our program. And what will we see? The program worked successfully, but curl cursed that the server was not found. Well, this is nonsense, you can temporarily replace his call with cat crash_report.txt order to see our crash report right away. So what else do we see?

And we see the line "That's a function!" derived from the doSomething() method! Interesting, isn't it? The pointer points to the sky, and the methods work? Well, not exactly.

The program is crashing (most likely) on the call to doSomethingPrivate() , and the backtrack tells us eloquently about it:
 0 segfault 0x000000010d0a98c8 _Z13reportTroublev + 40 1 segfault 0x000000010d0a99d0 _Z10catchCrashi + 16 2 libsystem_c.dylib 0x00007fff99b5dcfa _sigtramp + 26 3 ??? 0x00007fff00000000 0x0 + 140733193388032 4 segfault 0x000000010d0a9c67 _ZN7Crasher11doSomethingEv + 71 5 segfault 0x000000010d0a9880 _Z5crashPv + 32 6 segfault 0x000000010d0a9ac7 _Z7goCrashv + 199 7 segfault 0x000000010d0a9b33 main + 67 8 segfault 0x000000010d0a9854 start + 52 

Let's experiment, to begin with, let's not add an extra address shift when calling crash() , what will the program output? Where does it crash? Ahem!
 That's a function! That's a private function! myPrivateInteger == 1752392050 myPrivateDoubles[1] == 60993401604041306737928347282702617388988841504491171140800281285302442927306116721201046092641903128620672849302937378251940003901836219046866981678295779355600933772275817062376375849852470059862498765690530537583237171035779906888043337758015488.000000 myPrivateString == 0x63202c6f6c6c6548 That's a function! 0 segfault 0x0000000109a5e8c8 _Z13reportTroublev + 40 1 segfault 0x0000000109a5e9d0 _Z10catchCrashi + 16 2 libsystem_c.dylib 0x00007fff99b5dcfa _sigtramp + 26 3 ??? 0x0000040000000000 0x0 + 4398046511104 4 segfault 0x0000000109a5ec67 _ZN7Crasher11doSomethingEv + 71 5 segfault 0x0000000109a5ec1a _ZN7Crasher18doSomethingPrivateEv + 208 6 segfault 0x0000000109a5ec67 _ZN7Crasher11doSomethingEv + 71 7 segfault 0x0000000109a5e880 _Z5crashPv + 32 8 segfault 0x0000000109a5eac4 _Z7goCrashv + 196 9 segfault 0x0000000109a5eb33 main + 67 10 segfault 0x0000000109a5e854 start + 52 

It can be seen that it crashes on the second call to doSomethingPrivate() , and the first one went off with a bang, although it didn’t bring us anything that was intended.

So, why even when calling a method at a null pointer, a segment appears only on the second function? What is the difference? Experienced plugs have long guessed and did not read this article , and for the rest I will explain. They differ in the use of class variables! If variables are not used, then it does not matter at all which pointer to call the function, because the hidden parameter this not used, namely in it we have garbage. In the second example (without a shift), a private function is called with this ' om pointing to our string, and our class variables will point to parts of this string and contain, respectively, any garbage included in it. And in the first case, the pointer is likely to simply refer to the memory area inaccessible to the program, so the first call to the private function will be painted.

Why in this article description of such elementary things? Well, of course, you have to show how to crash programs! And explain why calling class methods on invalid pointers does not always lead to crash. If the full code is interesting, I ask, as always, on the githab .

In general, successful debugging! And smaller crash reports;)

Source: https://habr.com/ru/post/147280/


All Articles