How to make a crash in C dereference a null pointer in a (fairly) portable way?

I am writing my own tester for my current project. One feature (which is probably quite common with test runners) is that each test file is executed in a child process, so the tester can correctly detect and report a test failure.

I also want to test the test leader itself, so one test test should crash. I know that a β€œcrash” does not fall under the C standard and can occur as a result of undefined behavior. Thus, this issue is more related to the behavior of real-world realizations.

My first attempt was to simply dereference the null pointer:

int c = *((int *)0); 

This worked in a debug build on GNU / Linux and Windows, but the release build failed because the unused variable c was optimized, so I added

 printf("%d", c); // to prevent optimizing away the crash 

and thought I was settled. However, trying my code with clang instead of gcc showed a surprise at compile time:

  [CC] obj/x86_64-pc-linux-gnu/release/src/test/test/test_s.o src/test/test/test.c:34:13: warning: indirection of non-volatile null pointer will be deleted, not trap [-Wnull-dereference] int c = *((int *)0); ^~~~~~~~~~~ src/test/test/test.c:34:13: note: consider using __builtin_trap() or qualifying pointer with 'volatile' 1 warning generated. 

Indeed, the clang compiled test file did not work.

So, I followed the warning tip, and now my test file looks like this:

 PT_TESTMETHOD(test_expected_crash) { PT_Test_expectCrash(); // crash intentionally int *volatile nptr = 0; int c = *nptr; printf("%d", c); // to prevent optimizing away the crash } 

This solved my immediate problem, the test file "works" (aka crashes) with both gcc and clang .

I assume that dereferencing a null pointer has undefined behavior, clang can compile my first code into something that won't work. The volatile qualifier removes the ability to be sure at compile time that this will actually result in dereferencing a null value.

Now my questions are:

  • Does this final code provide zero dereferencing at runtime?
  • Is dereferencing zero really a pretty portable way to crash on most platforms?
+5
source share
4 answers

The answer related to abort() was great, I really didn't think about it, and this is a really great portable way to force abnormal program termination.

Having tried it using my code, I came across msvcrt (Microsoft C runtime) implements abort() special chat way, it outputs the following to stderr :

This application asked Runtime to terminate it in an unusual way. For more information, contact support.

This is not so pleasant, at least it unnecessarily clutters the output of a full test run. So I looked at __builtin_trap() , which is also mentioned in the clang warning. Turns out this gives me exactly what I was looking for:

The LLVM code generator translates __builtin_trap () to the trap command, if supported by the target ISA. Otherwise, the built-in function is converted to a call to cancel.

It is also available in gcc since version 4.2.4:

This function causes the program to exit abnormally. GCC implements this function using a target-specific mechanism (for example, intentionally executing an illegal instruction) or causing an interrupt.

Since this makes something look like a real glitch, I prefer it to just abort() . For backup, it still tries to do its own illegal operation, such as reversing the null pointer, but just adds the abort() call if the program somehow does it without crashing.

So, in general, the solution looks like this: testing for the minimal version of GCC and using the much more convenient __has_builtin() macro provided by clang:

 #undef HAVE_BUILTIN_TRAP #ifdef __GNUC__ # define GCC_VERSION (__GNUC__ * 10000 \ + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) # if GCC_VERSION > 40203 # define HAVE_BUILTIN_TRAP # endif #else # ifdef __has_builtin # if __has_builtin(__builtin_trap) # define HAVE_BUILTIN_TRAP # endif # endif #endif #ifdef HAVE_BUILTIN_TRAP # define crashMe() __builtin_trap() #else # include <stdio.h> # define crashMe() do { \ int *volatile iptr = 0; \ int i = *iptr; \ printf("%d", i); \ abort(); } while (0) #endif // [...] PT_TESTMETHOD(test_expected_crash) { PT_Test_expectCrash(); // crash intentionally crashMe(); } 
+3
source

I would not rely on this method as reliable if I were you.

Can't you use abort() , which is part of the C standard and is guaranteed to trigger an abnormal program termination event?

+13
source

you can write memory, not read it.

 *((int *)0) = 0; 
+1
source

The program below should work. However, this can lead to some collateral damage.


 #include <string.h> void crashme( char *str) { char *omg; for(omg=strtok(str, "" ); omg ; omg=strtok(NULL, "") ) { strcat(omg , "wtf"); } *omg =0; // always NUL-terminate a NULL string !!! } int main(void) { char buff[20]; // crashme( "WTF" ); // works! // crashme( NULL ); // works, too crashme( buff ); // Maybe a bit too slow ... return 0; } 
0
source

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


All Articles