A preliminary study on the cause of semaphore collapse

1.SIGSEGV

1. What are segmentation faults?

That’s what it says on the wiki

A segmentation fault (often shortened to SIGSEGV) is a particular error condition that can occur during the operation of  computer software. A segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (for example, attempting to write to a read-only location, or to overwrite part of the operating system). Segmentation is one approach to memory management and protection in the operating system. It has been superseded by paging for most purposes, but much of the terminology of segmentation is still used, "segmentation fault" being an example. Some operating systems still have segmentation at some logical level although paging is used as the main memory management policy. On Unix-like operating systems, a process that accesses an invalid memory address receives the SIGSEGV signal. On Microsoft Windows, a process that accesses invalid memory receives the STATUS_ACCESS_VIOLATION exception.Copy the code

The above text does not give a definition of SIGSEGV, saying only that it is “an error condition during the operation of computer software.” The text describes when SIGSEGV occurs “when a program tries to access an area of memory that is not allowed to access (e.g., trying to write a piece of memory that belongs to the operating system), or accesses an area of memory with the wrong type (e.g., trying to write a piece of read-only memory). This description is accurate. To deepen our understanding, let’s summarize SIGSEGV in more detail.

  1. SIGSEGV is an error that occurs when accessing memory, which falls under the category of memory management
  2. SIGSEGV is a user mode concept, which is the operating system when user mode program error access memory processing.
  3. SIGSEGV is generated when a user-mode program accesses memory that it is not allowed to access (access means read, write, or execute).
  4. SIGSEGV is generated when a user-mode program accesses permitted memory in the wrong way.

From the perspective of user-mode program development, we do not need to understand the complex memory management mechanisms of operating systems, which are hardware platform-specific. However, it is helpful to understand SIGSEGV by knowing how the kernel sends SIGSEGV signals. Understanding Linux Kernel Edition 3 and Understanding the Linux Virtual Memory Manager have an overview of this. By contrast, The author thinks ULK’s diagram is more intuitive.The red section of Figure 1 shows the overall flow of the kernel sending SIGSEGV signals to user-mode programs. When a user program accesses an address that causes SIGSEGV, the hardware first generates a Page fault, or “page missing exception.” In the kernel’s Page fault handler, it first determines whether the address belongs to the address space of the user-mode program [*]. For example, the address space of the Intel 32-bit IA32 CPU is 0,3 gb, and the address space of the kernel is 3G and 4G. If the address belongs to the user-mode address space, check whether the access type matches the type of the memory area. If no, send SIGSEGV signal. If the address does not belong to the user-mode address space, check whether the operation to access the address occurred in user-mode. If so, send SIGSEGV signal.

[*] The user-mode program address space refers specifically to the range of address Spaces that programs can access. Broadly speaking, the address space of a process should include a portion of kernel space that it cannot access.

Figure 2 depicts in more detail the flow of SIGSEGV signals sent by the kernel. Instead of describing the flow in the diagram, I will describe the specific flow in the examples in later chapters.

2. Pointer out of bounds and SIGSEGV

Often see a post to confuse the two, and the relationship between the two is indeed subtle. Here, we put pointer operations (addition and subtraction) caused by out of bounds, wild Pointers, null Pointers are classified as pointer out of bounds. SIGSEGV is often caused by pointer overbounds, but not all pointer overbounds will cause SIGSEGV. An out-of-bounds pointer that does not reference it will not cause SIGSEGV. Dereference of an out-of-bounds pointer does not necessarily cause SIGSEGV. It sounds crazy, but it is. SIGSEGV involves various aspects of the operating system, C library, compiler and linker. Let’s take some specific examples to illustrate.

2.1 SIGSEGV caused by incorrect access type

#include <stdio.h>
#include <stdlib.h>
  
int main() {
    char* s = "hello world";
    s[1] = 'H';
}
Copy the code

This is the most common example. In this example, “hello world” is a constant string that will be put in the.rodata section (GCC) after compilation, and will be merged into the text segment with the code segment at the end of the link generation. This is SIGSEGV caused by the wrong access type.

Their order in Figure 2 is:

1 -> 3 -> 4 -> 6 -> 8 -> 11 ->10

2.2 Memory that does not belong to the process address space is accessed

#include <stdio.h>
#include <stdlib.h>
int main() {
    int* p = (int*)0xC0000fff;
    *p = 10;
}
Copy the code

In this example, we access an address that belongs to the kernel (IA32, 32bit). Of course, few people write programs like this, but your program can do this without realizing it (which is discussed later). This example flows in Figure 2:

1 -> 2 -> 11 -> 10

2.3 Non-existent memory is accessed

The most common case is dereferencing a null pointer, as in:

#include <stdio.h>
#include <stdlib.h>
   
int main () {
    int *a = NULL;
    *a = 1;
}
Copy the code

In practice, the null pointer in this example might point to a user-mode address space, but the page it points to does not actually exist. The process of generating SIGSEGV in Figure 2 is as follows:

1 -> 3 -> 4 -> 5 -> 11 ->10

2.4 Stack overflow, sometimes SIGSEGV, sometimes nothing at all

This is also a common CU menstrual patch. Most C textbooks will tell you that when you return from a function, the contents on the function stack are automatically “freed.” “Free” strikes most beginners as free(), as if the memory doesn’t exist, so when he accesses the supposedly non-existent memory and finds that everything is fine, he is deeply confused.

#include <stdio.h>
#include <stdlib.h>
int* foo() {
    int a = 10;
    return &a;
}
int main() {
    int* b;
    b = foo();
    printf ("%d\n", *b);
}
Copy the code

When you compile this program, you will see “warning: function returns address of local variable”. GCC has already warned you about possible stack overflow. The actual operation results are all normal. The reason is that operating systems typically manage memory at the granularity of “pages,” which in Linux is typically 4K, and the kernel allocates memory to the process stack at 4K granularity. Therefore, if the stack overflow is smaller than the page size, no SIGSEGV will be generated. Does it say that if stack overflow exceeds 4K, SIGSEGV will be generated? Look at this example:

#include <stdio.h>
#include <stdlib.h>

char* foo() {
    char buf[8192];
    memset (buf, 0x55, sizeof(buf));
    return buf;
}
int main() {
    char* c;
   c = foo();
    printf ("%#x\n", c[5000]);
}
Copy the code

Although our stack overflow has exceeded the 4K size, the performance is still fine. This is because the “stack autofree” mentioned in the C tutorial actually changes the stack pointer, and the memory it points to is not reclaimed when the function returns. In our example, the accessed stack overflow memory is still there. Invalid stack memory (that is, unreclaimed stack memory outside of the stack pointer range) is unpredictably reclaimed by the operating system as needed, and thus it is impossible to predict when accessing illegal stack contents will trigger SIGSEGV.

Well, in the above example, our stack overflow example, whether larger than a page size or smaller than a page size, accesses allocated stack memory that has not been reclaimed. Does accessing unallocated stack memory necessarily cause SIGSEGV? The answer is no.

#include <stdio.h> #include <stdlib.h> int main() { char* c; C = (char*)&c -- 8192 *2; *c = 'a'; printf ("%c\n", *c); }Copy the code

On IA32, the stack grows downward by default, and we overflow the stack by 16K, accessing an unallocated stack region (at least from our program’s point of view). The 16K value was chosen so that our overflow range was large enough to be larger than the initial stack size (4K or 8K) that the kernel assigned to the process. In theory, we should see the expected SIGSEGV, but it didn’t. Everything was fine. The answer lies in the kernel’s Page fault handler:

if (error_code & PF_USER) {

              /*

               * Accessing the stack below %sp is always a bug.

               * The large cushion allows instructions like enter

               * and pusha to work.  ("enter $65535,$31" pushes

               * 32 pointers and then decrements %sp by 65535.)

               */

              if (address + 65536 + 32 * sizeof(unsigned long) < regs->sp)

                     goto bad_area;

       }

       if (expand_stack(vma, address))

              goto bad_area;
Copy the code

The kernel leaves room for instructions like Enter [*], and from the code’s point of view, the stack overflow is theoretically okay if it is less than 64K or so, and the stack expands automatically. Confusingly, the author uses the following example to test the stack overflow threshold and gets the range of 70K to 80K instead of 65K to 66K as expected.

[*] For details about the Enter directive, See Intel(R) 64 and IA-32 Architectures Software Developer Manual Volume 1, Section 6.5, PROCEDURE CALLS FOR Block-structured LANGUAGES”

#include <stdio.h> #include <stdlib.h> #define GET_ESP(esp) do { \ asm volatile ("movl %%esp, %0\n\t" : "=m" (esp)); \ } while (0) #define K 1024 int main() { char* c; int i = 0; unsigned long esp; GET_ESP (esp); printf ("Current stack pointer is %#x\n", esp); while (1) { c = (char*)esp - i * K; *c = 'a'; GET_ESP (esp); printf ("esp = %#x, overflow %dK\n", esp, i); i ++; }}Copy the code

The author also cannot explain the magic among them at present, this magical procedure! When SIGSEGV occurs in the above example, the flow in Figure 2 is as follows:

1 -> 3 -> 4 -> 5 -> 11 -> 10

At this point, we can at least see that SIGSEGV is closely related to the operating system (stack allocation and reclamation) and the compiler (who knows if it uses directives like Enter), rather than the textbook “function returns and the stack it uses is automatically reclaimed”.

2.5 the heap

#include <stdio.h> #include <stdlib.h> #define K 1024 int main () { char* c; int i = 0; c = malloc (1); while (1) { c += i*K; *c = 'a'; printf ("overflow %dK\n", i); i ++; }}Copy the code

Looking at the stack example, you can see that the relationship between SIGSEGV and the heap depends on your memory allocator, which usually means depending on the IMPLEMENTATION of the C library.

The above example generates SIGSEGV at 15K on my machine. Let’s change the size of the initial malloc. SIGSEGV was delayed to overflow 180K when the initial allocation was 16M. SIGSEGV was delayed to overflow 571K when 160M was allocated. We know that memory allocators often have different mechanisms for allocating different sizes of memory, and this example demonstrates this in some ways. The flow of SIGSEGV in figure 2 is as follows:

1 -> 3 -> 4 -> 5 -> 11 -> 10

It is rare to use a wild pointer to randomly access the heap, but more often asked “why did I access a free() block of memory without SIGSEGV”, as in the following example:

#include <stdio.h>
#include <stdlib.h>
   
#define K 1024
int main () {
    int* a;
   
    a = malloc (sizeof(int));
    *a = 100;
    printf ("%d\n", *a);
    free (a);
    printf ("%d\n", *a);
}
Copy the code

SIGSEGV does not occur, but the memory pointed to by A after free() is zeroed out, a reasonable explanation is for security. No one will ever ask why SIGSEGV didn’t happen again. Yes, memory after free() is not necessarily returned to the operating system immediately; it stays there until the actual return occurs.

2.6 What if it is a wild pointer to a global extent?

Looking at the above two examples, I think this is really nothing to talk about.

2.7 The function jumps to an invalid address

This is also a common cause of SIGSEGV, as shown in the following example:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void foo () {
    char c;
    memset (&c, 0x55, 128);
}
int main () {
    foo();
}
Copy the code

By using a stack overflow, we overwrite the return address of function foo to 0x55555555, and the function jumps to an invalid address for execution, eventually raising SIGSEGV. There are too many possibilities in the flow in Figure 2, ranging from 1->3 ->4 ->… ->10, from 4 to 10, almost every path is possible. Of course, in this case, the page 0x55555555 points to is not in memory, and the flow in Figure 2 is as follows:

1 – > 3 – > 4 – > 5-11 – > > 10

If the page corresponding to the invalid address (the page belongs to the user-mode address space) exists in memory and it is executable [*], the program will execute a bunch of random instructions. Once memory is accessed during the execution of these instructions, the process that generated SIGSEGV is almost impossible to trace (unless you follow up with a debugging tool). Seeing this, a reasonable question is: why does the program execute a random instruction in an invalid address, but not an invalid instruction? Executing on a piece of unknown memory, encountering illegal instructions is more likely, so should receive SIGILL signal ah?

[*] If the segment register type checking is not used, only the page table is protected, which is readable by the traditional 32-bit IA32. After the advent of NX technology, the page level can also control whether it can be executed.

This is not the case. Our IA32 architecture uses such a complex instruction set that it is not easy to find the encoding of an illegal instruction. In the following example:

#include <stdio.h>
#include <stdlib.h>
int main() {
    char buf[128] = "asdfaowerqoweurqwuroahfoasdbaoseur20   234123akfhasbfqower53453";
    sleep(1);
}
Copy the code

The author randomly typed some characters in BUF, disassembled their contents and got the following result:

0xbffa9e00: popa 0xbffa9e01: jae 0xbffa9e67 0xbffa9e03: popaw 0xbffa9e05: outsl %ds:(%esi),(%dx) 0xbffa9e06: ja 0xbffa9e6d 0xbffa9e08: jb 0xbffa9e7b 0xbffa9e0a: outsl %ds:(%esi),(%dx) 0xbffa9e0b: ja 0xbffa9e72 0xbffa9e0d: jne 0xbffa9e81 0xbffa9e0f: jno 0xbffa9e88 0xbffa9e11: jne 0xbffa9e85 0xbffa9e13: outsl %ds:(%esi),(%dx) 0xbffa9e14: popa 0xbffa9e15: push $0x73616f66 0xbffa9e1a: bound %esp,%fs:0x6f(%ecx) 0xbffa9e1e: jae 0xbffa9e85 0xbffa9e20: jne 0xbffa9e94 0xbffa9e22: xor (%eax),%dh 0xbffa9e24: and %ah,(%eax) 0xbffa9e26: and %dh,(%edx) 0xbffa9e28: Xor (%ecx,% ESI,1),% ESI 0xBFFA9E2B: xOR (%ebx),% DH 0xBFFA9E2D: POPA 0xBFFA9E2E: IMul $0x61,0x68(% ESI),% ESP 0xBFFa9e32: jae 0xbffa9e96 0xbffa9e34: data16 0xbffa9e35: jno 0xbffa9ea6 0xbffa9e37: ja 0xbffa9e9e 0xbffa9e39: jb 0xbffa9e70 0xbffa9e3b: xor 0x33(,%esi,1),%esi 0xbffa9e42: add %al,(%eax) 0xbffa9e44: add %al,(%eax) 0xbffa9e46: add %al,(%eax) 0xbffa9e48: add %al,(%eax) 0xbffa9e4a: add %al,(%eax) 0xbffa9e4c: add %al,(%eax) 0xbffa9e4e: add %al,(%eax) 0xbffa9e50: add %al,(%eax) 0xbffa9e52: add %al,(%eax) 0xbffa9e54: add %al,(%eax) 0xbffa9e56: add %al,(%eax) 0xbffa9e58: add %al,(%eax) 0xbffa9e5a: add %al,(%eax) 0xbffa9e5c: add %al,(%eax) 0xbffa9e5e: add %al,(%eax)Copy the code

…………………………………………………………………………………………………………………………………

Not a single illegal order! You can also create some random content and try it out and see how many illegal instructions you can get. Therefore, in the actual situation, when the function jumps to the invalid address execution, the probability of encountering SIGSEGV is far greater than SIGILL.

Let’s construct a case of encountering SIGILL as follows:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

#define GET_EBP(ebp)    \

    do {    \

        asm volatile ("movl %%ebp, %0\n\t" : "=m" (ebp));  \

    } while (0)
char buf[128];

void foo () {
    printf ("Hello world\n");
}
void build_ill_func() {
   int i = 0;
    memcpy (buf, foo, sizeof(buf));
    while (1) {
        /*
         * Find *call* instruction and replace it with
         * *ud2a* to generate a #UD exception
         */
        if ( buf[i] == 0xffffffe8 ) {
            buf[i] = 0x0f;
            buf[i+1] = 0x0b;
            break;
        }
        i ++;
    }
}
void overflow_ret_address () {
    unsigned long ebp;
    unsigned long addr = (unsigned long)buf;
    int i;
    GET_EBP (ebp);
    for ( i=0; i<16; i++ )
        memcpy ((void*)(ebp + i*sizeof(addr)), &addr, sizeof(addr));
    printf ("ebp = %#x\n", ebp);
}
int main() {
    printf ("%p\n", buf);
    build_ill_func ();
    overflow_ret_address ();
}
Copy the code

2.8 Invalid system call parameters

We filled a global BUF with some instructions, one of which was UD2A, which was used to construct an illegal instruction trap in the IA32 instruction set. In overflow_ret_address(), we override the return address of the function by stack overflow, so that when the function returns, it jumps to BUF execution, and finally the ud2A instruction generates a SIGILL signal. Note that this example uses the EBP frame pointer register. Do not use the -fomit-frame-pointer parameter at compile time, otherwise you will not get the desired result. This is a special case. In particular, access to illegal memory in the previous example occurred in user mode. In this case, access to illegal memory occurs in kernel mode. This is usually when copy_from_user() or copy_to_user() is executed. The flow is shown in Figure 2:1 ->… . -> 11 -> 12 -> 13 The kernel uses the fixup[*] technique to handle such errors. ULK says that the usual process is to send a SIGSEGV signal, but in practice most system calls can return an EFAULT (bad Address) code to avoid terminating user-mode programs. This case is not an example, and I can’t think of a system call that simulates this without returning an EFAULT error. ##3. How to avoid SIGSEGV Good programming habits are always the best prevention. Good habits include:

Write programs as per C standard as possible. I say as much as possible because the C standard has too many platform-specific and undefined behaviors, some of which actually have fait accompli standards. For example, in the C standard, an out-of-bounds pointer leads to undefined behavior, whereas in practice, an out-of-bounds and undereferenced pointer is not catastrophic. To borrow an example from CU, here it is:

#include <stdio.h>
#include <stdlib.h>
int main () {
    char a[] = "hello";
    char* p;
    for ( p = a+5; p>=a; p-- )
        printf ("%c\n", *p);
}
Copy the code

After the loop ends, p points to the element in front of array A. This is an undefined behavior in the C standard, but the program is actually safe. There is no need to rewrite the program so that p does not become a wild pointer:

#include <stdio.h> #include <stdlib.h> int main () { char a[] = "hello"; char* p; for ( p = a+5; p! =a; p-- ) { printf ("%c\n", *p); } printf ("%c\n", *p); }Copy the code

Of course, there may be a compiler in the world that deals with “out-of-bounds but undereferenced” wild Pointers, such as raising a SIGSEGV. I can not guarantee 100%, so we in practice or their own discretion.

Know your program inside and out. Unlike other programmers, C programmers need complete knowledge of their programs and precise control. Especially in the allocation and release of memory. Before you operate on each pointer, you should know where the memory it points to comes from (stack, heap, global area) and the lifetime of that memory. Only by using memory wisely can SIGSEGV be avoided to the greatest extent.

Use Assert a lot. I like to use a lot of assert in my programs, and I check for things I don’t think should happen. While assert does not prevent SIGSEGV directly, it does throw errors sooner rather than later. The closer you are to the fault, the easier it is to root cause. Many times when SIGSEGV appears, the program is already far away.

Turn on the -wall -werror compilation option. If the program is written by itself, 0 warning should always be an indicator (0 warning does not include warnings caused by compiler versions). A common form of SIGSEGV comes from passing the wrong type of argument to a function. Such as:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main () {
    char buf[12];
    int buff;
    strcpy (buff, "hello");
}
Copy the code

In this example, the original purpose is to copy a string to buf, but because there is a buff variable with a similar name to BUF, strcpy raises SIGSEGV as expected due to a clerical error (which is probably from your editor’s auto-completion, such as CTRL -p, CTRL -n for Vim). Warning: Passing argument 1 of strcpy makes pointer from integer without a cast

This further requires us to use the compiler’s type checking functions as much as possible, including more functions and less macros (especially macros for complex functions), more typed Pointers for function parameters and less void* Pointers, etc. This example is the inadvertent behavior we mentioned in Section 2.2.

Use less fancy tricks and more standard methods. A good program should be logical, clean, and easy to read like a catchy article. Experiments full of obscure grammar and weird tricks are not welcome. Many people like to use performance issues as an excuse to use non-standard methods, when in fact they have no idea what the impact will be on performance. The author once rewrote a frequently executed exception handling function in assembly in a project, reducing the execution cycle of the function from more than 2000 machine cycles to more than 40. Full of joy, I submitted a patch to project maintainer and received a maintainer reply: “Zhang, have you tested exactly how much performance your patch can bring? I don’t want to replace elegant C code with this obscure assembly without obvious data.” So I did a kernel build to test patch, which took 15 minutes, and the overall performance improvement of my patch was about 0.1%. Therefore, try to write clear and unambiguous code, not only to avoid SIGSEGV, but also to debug after SIGSEGV occurs.

When you have a requirement that the standard method cannot meet, there are only two possibilities: 1. The design is wrong from the beginning, which leads to the wrong requirement; 2. 2. You haven’t read enough code to know what the industry’s standard approach to solving this problem is. Computers have evolved over decades, and if you’re not doing cutting-edge research, the chances of encountering a problem that has to be solved in a nonstandard way are pretty slim. As we often find SIGSEGV in the C library using GDB tracing, don’t shout that the C library is buggy, most of the time you pass in the wrong parameters in the first place.

Main content sources

SEGMENTATION FAULT IN LINUX causes and Avoidance

2.SIGILL

1. Illegal instruction, namely SIGILL

Is a type of error provided by the POSIX standard. SIGILL, by its name, is a sentence in a process that starts that is not recognized by the CPU as a correct instruction. This type of error is sent by the operating system to the process, and the operating system uses the SIGILL signal to terminate the program if the process tries to execute some form of error, unknown, or privileged instruction. SIGILL corresponds to a constant of 4.

2. Causes of SIGILL

Examples of incorrect code

typedef void(*FUNC)(void);
int main(void)
{
    const static unsigned char insn[4] = { 0xff, 0xff, 0xff, 0xff };
    FUNC function = (FUNC) insn;
    function();
}
Copy the code

2.1 Write incorrect data segments into code segments

The process’s data in the code snippet is to be executed as an instruction. If you accidentally overwrite an existing code snippet, you may end up with ill-formed instructions. This error is particularly likely to occur In just-in-time compilers. Similarly, if you accidentally overwrite a return address in an active record on the stack, the program may perform meaningless in-memory operations based on the error address. Further, it can be considered that any problem that leads to data error may bring illegal instruction problem. For example, a hard drive fails.

2.2 Evolution of instruction set

For example, SIMD instruction, since the Introduction of MMX in pentium 4, X86 chips have continuously added and expanded SIMD support, SSE, SSE2, SSE3, SSE42, AVX, AVX2. By default, many compilers have autovectorization enabled in O2 or O3. This causes many executable programs compiled on newer architectures to have illegal Instruction problems when running on older machines.

2.3 Toolchain BUG

For ordinary C language through the compiler to generate executable programs. Generally have passed the rigorous test, do not happen casually this kind of problem. So if you run into this error and try static chain, and there is no embedded assembly in the program, you can almost assume that there is a problem with the tool chain. The compiler? Assembler or linker.

2.4 Access alignment or floating-point format issues

According to experience, the wrong instruction may be related to the fetch address instruction. In addition, whether the format of floating point numbers conforms to IEEE standards may also have an impact.

3. Troubleshooting methods

1. Does the program have privileged instructions or access privileged registers? 2. Does the executable compiled on the younger CPU run on the older CPU? There is no embedded assembly program, check first. The compiler rarely generates code with this problem. On X86 platforms, it is particularly important to pay attention to the mixing of 64-bit and 32-bit assembly instructions

4. Does the program have the opportunity to write data in the process code segment space? 5. Is stack operation safe enough? 6. Pay attention to whether the ABI of the program is correct, especially whether dynamic chain and static chain are handled correctly, try to avoid the problem of dynamic chain executable file call error library (ARM EABI, MIPS N32/O32/N64 are likely to have this problem) 7. Is the tool chain the right one?

4. Reference documents

SIGILL definition of Illegal Instruction

The SIGABRT and SIGIOT signal is sent to a process to tell it to abort. i.e. to terminate. The signal is usually initiated by the process itself when it calls abort() function of the C Standard Library, but it can be sent to the process from outside like any other signal. In plain English, it is produced by calling abort and the process exits abnormally.

2. Causes of SIGABRT

2.1. Multiple free times lead to SIGABRT

#include "stdlib.h" #include "string.h" #include "stdio.h int main() { void *pc = malloc(1024); free(pc); //free(pc); Printf (" Free OK! \n"); return 0; }Copy the code

2.2 Execute abort ()

#include "string.h" #include "stdio.h" #include "stdlib.h" int main() { printf("before run abort! \n"); abort(); printf("after run abort! \n"); return 0; }Copy the code

2.3 Run the Assert function

#include "string.h" #include "stdio.h" #include "assert.h" #include "stdlib.h" int main(){ printf("before run assert! \n"); If the value is 0, an error is reported; Void * PC = malloc(1024); #else void *pc = NULL; #endif assert( pc ! = NULL ); printf("after run assert! \n"); return 0; }Copy the code

3. References

SIGABRT is the cause of SIGABRT signal generated by program running

4.SIGBUS

1. What is SIGBUG

The SIGBUS signal is sent to a process when it causes a bus error. The conditions that lead to The signal being sent are, for example, incorrect memory access alignment or non-existent physical address.

Typically, SIGBUS is caused by a process that causes a Bus error. In computing, a bus error is a fault raised by hardware, notifying an operating system (OS) that a process is trying to access memory that the CPU cannot physically address: an invalid address for the address bus, hence the name. In modern use on most architectures these are much rarer than segmentation faults, which occur primarily due to memory access violations: problems in the logical address or permissions. On POSIX-compliant platforms, bus errors usually result in the SIGBUS signal being sent to the process that caused the error. SIGBUS can also be caused by any general device fault that the computer detects, Though a bus error rarely means that the computer hardware is physically broken — it is normally caused by a bug in software.[citation needed] Bus errors may also be raised for certain other paging errors;

2. Causes of SIGBUS

2.1 Unaligned reads or writes

In fact, bus errors are almost always caused by misaligned reads or writes. It is called a bus error because the component that blocks on an unaligned memory access request is the address bus. Alignment means that data items can only be stored in memory whose address is an integer multiple of the size of the data item. Data alignment is required in modern computer architectures, especially RISC architectures, because the extra logic associated with arbitrary alignment can make the entire memory system larger and slower. Hardware such as the cache controller and memory management unit (MMU) can be greatly simplified and accelerated by forcing each memory access to be limited to a single cache row or a single page. We use the term address alignment to address this problem, rather than saying outright that memory cross-page access is prohibited, but they say the same thing. For example, when accessing an 8-byte double, the address is only allowed to be an integer multiple of 8. So a double can be stored at address 24, 8008, or 32768, but not at address 1006(because it is not divisible by 8). Page and cache sizes are carefully designed so that alignment rules are followed to ensure that an atomic data item does not cross the boundaries of a page or cache block.

Here is a code example

#include<stdio.h> union { char a[10]; int i; } u; int main(void){ int *p = (int *) (&(u.a[1])); /** * unaligned addresses in p will cause a bus error, * because the union of arrays and ints ensures that a is aligned with the 4 bytes of int, * so "a+1" is definitely not int-aligned */ *p = 17; printf("%d %p %p %p\n", *p, &(u.a[0]), &(u.a[1]), &(u.i)); printf("%lu %lu\n", sizeof(char), sizeof(int)); return 0; }Copy the code

5.SIGTRAP

1. What is SIGTRAP?

The SIGTRAP signal is sent to a process when an exception (or trap) occurs: A condition that a debugger has requested to be informed of — for example, when a particular function is executed, or when a particular variable changes value. Typically SIGTRAP is generated by breakpoint instructions or other trap instructions. Used by the debugger. If no debugger is attached, the process terminates and a crash report is generated. Lower-level libraries (for example, libDispatch) capture processes when they encounter fatal errors.

2. Cause that the SIGTRAP is sent to the process

2.1 Breakpoint instruction is triggered

In Debugger mode, a breakpoint is set. When the program runs to a breakpoint, SIGTRAP is raised.

2.2 Other Trap commands are triggered

Relevant information is limited, to be updated later

6.SIGFPE

1. What is SIGFPE?

SIG is a generic prefix for signal names. FPE is an acronym for Floating-point Exception. On POSIX-compatible platforms, SIGFPE is a signal sent to a process when it has performed an incorrect arithmetic operation. SIGFPE’s symbolic constants are defined in the header file signal.h. Signal names are often used because signal numbers can change on different platforms.

2. Cause that SIGFPE is sent to the process

1.FPE_INTDIV Integer divided by zero

2.FPE_INTOVF Integer overflow

3.FPE_FLTDIV Floating point divided by zero

4.FPE_FLTOVF Floating point overflow

5.FPE_FLTUND Floating point underflow

6.FPE_FLTRES floating point result is incorrect

7.FPE_FLTINV Invalid floating point operation

8.FPE_FLTSUB floating point subscript is out of bounds

This is an example of an ANSI C program that tries to perform an incorrect arithmetic operation called integer divided by zero, or FPE_INTDIV.

int main(){      
 int x = 42/0;      
}
Copy the code

3. References

Documentation ArchiveDeveloperSearch Exception Programming Topics

reference

POSIX signals