> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Decrypt Go: Three reasons for panic
- URL: https://huizhou92.com/decrypt-go-three-reasons-for-panic/
- Published: 2024-06-27T07:45:59.000Z
- Updated: 2026-09-06T11:39:09.000Z
- Author: huizhou92
- Tags: tech, golang, panic, #lang-en

## Why is Panic Worth Thinking About?

When learning Go, many questions often arise. Sometimes, what seems to be understood is actually not. What exactly is panic? It seems obvious, but it can be difficult to explain. I'm going to use two articles to understand the concept of panic thoroughly:

- Posture: Understanding the origins of panic. It doesn't just come out of nowhere. There are three main postures to consider.
- Principles: Fully comprehending the internal workings of panic and understanding its deeper principles.

> This article is first published in the medium MPP plan. If you are a medium user, please follow me in [medium](https://medium.huizhou92.com/?ref=huizhou92.com). Thank you very much.

## The Three Postures of Panic

When does panic occur? Let's start with the "form." From a developer's perspective, panic can be categorized into active and passive.

**Active posture**:

- Developers actively call the `panic()` function.

**Passive posture**:

- The compiler triggers hidden code.
- The kernel sends a signal to the process.

### Hidden Code by the Compiler

Go is simple yet powerful, and the compiler plays a crucial role. It handles many tasks on behalf of programmers, such as logic supplementation and memory escape analysis. This includes the throwing of panics!

Let's take a classic example: dividing by zero in integer arithmetic triggers a panic. How does this happen?

Consider the following minimal code snippet:

```go
func divzero(a, b int) int { 
	c := a / b 
	return c 
}

```

This function has a risk of division by zero. When `b` is equal to 0, the program triggers a panic and exits, as shown below:

```sh
root@ubuntu:~/code/gopher/src/panic# ./test_zero panic: runtime error: integer divide by zero goroutine 1 [running]: main.zero(0x64, 0x0, 0x0) /root/code/gopher/src/panic/test_zero.go:6 +0x52

```

Now, the question is: How does the program trigger a panic?

**Code reveals all secrets.**

Looking at the code, it seems simple—just one line: `c := a / b`, right?

Well, it's actually **assembly code**. The hidden logic added by the compiler is not visible in the source code.

By using the dlv debugger to set a breakpoint in the `divzero` function and executing `disassemble`, we can uncover the secret. Here is a snippet of the assembly code with annotations:

```shell
 (dlv) disassemble
TEXT main.zero(SB) /root/code/gopher/src/panic/test_zero.go
    // Check if b is equal to 0
    test_zero.go:6  0x4aa3c1    4885c9          test rcx, rcx
    // If it's not equal to 0, jump to 0x4aa3c8 and execute the instruction; otherwise, continue execution
    test_zero.go:6  0x4aa3c4    7502            jnz 0x4aa3c8
    // If execution reaches this point, it means b is 0, so jump to 0x4aa3ed, which is call $runtime.panicdivide
=>  test_zero.go:6  0x4aa3c6    eb25            jmp 0x4aa3ed
    test_zero.go:6  0x4aa3c8    4883f9ff        cmp rcx, -0x1
    test_zero.go:6  0x4aa3cc    7407            jz 0x4aa3d5
    test_zero.go:6  0x4aa3ce    4899            cqo
    test_zero.go:6  0x4aa3d0    48f7f9          idiv rcx
    // ...
    test_zero.go:7  0x4aa3ec    c3              ret
    // See the magical function?
    test_zero.go:6  0x4aa3ed    e8ee27f8ff      call $runtime.panicdivide

```

Do you see the hidden function?

The compiler secretly adds an `if/else` logic and even includes the code for `runtime.panicdivide`.

1. If `b` is equal to 0, it jumps to the function `runtime.panicdivide`.  
Take a look at the `panicdivide` function. It's a simplified wrapper:

```go
// runtime/panic.go
func panicdivide() {
    panicCheck2("integer divide by zero")
    panic(divideError)
}

```

As you can see, it calls the `panic()` function.

This is how a panic is triggered when dividing by zero. It's not something that magically appears out of nowhere; rather, it's the additional logic added by the compiler to ensure that a panic is triggered when the divisor is 0.

### Triggered by Process Signal

The most typical example is illegal memory access, such as accessing a nil pointer, which triggers a panic. How does this happen?

Consider this minimal example:

```go
func nilptr(b *int) int { 
	c := *b 
	return c 
}

```

When calling `nilptr(nil)`, it will cause the process to exit with an exception:

```sh
root@ubuntu:~/code/gopher/src/panic# ./test_nil panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x4aa3bc] goroutine 1 [running]: main.nilptr(0x0, 0x0) /root/code/gopher/src/panic/test_nil.go:6 +0x1c

```

Now, the question is: How does this panic occur?

When the Go process starts, it registers the default signal handler, `sigtramp`. When the CPU encounters a 0 address, it triggers a page fault exception, indicating an illegal address. The kernel sends a `SIGSEGV` signal to the process. When this signal is received, the `sigtramp` function handles it, ultimately calling the `panic()` function:

```sh
sigtramp (pure assembly code)
	-> sigtrampgo (signal_unix.go)
		-> sighandler (signal_sighandler.go)
			-> preparePanic (signal_amd64x.go)
				-> sigpanic (signal_unix.go)
					-> panicmem
						-> panic

```

In the `sigpanic` function, the `panicmem` function is called, which then calls the `panic()` function. This leads to Go's panic handling.

Similar to `panicdivide`, `panicmem` is a minimal wrapper for `panic()`:

```go
func panicmem() {
	panicCheck2("invalid memory address or nil pointer dereference")
	panic(memoryError) 
}

```

This method triggers the panic function by using a software interrupt via a signal. It allows the Go registered signal handler to be invoked, enabling panic handling at the language level.  
You may wonder when the logic for signal handling is registered.

During process initialization, when creating M0 (thread), the system call `sigaction` is used to register the signal handling function as `sigtramp`. The call stack looks like this:

```golang
mstartm0 (proc.go)
	-> initsig (signal_unix.go:113)
		-> setsig (os_linux.go)

```

As a result, when a software interrupt is triggered, the Go signal handling function is called, allowing panic handling at the language level.

### Active Panic by Developers

The third way is when developers actively call `panic` themselves:

```go
func main() { 
	panic("panic test") 
}

```

This is a simple function call—very straightforward.

## Discussing the Essence of Panic

Now that we have explored the postures of panic, all three methods ultimately rely on the `panic()` function. So, one thing is clear: panic is a language-level handling mechanism.

By default, after a panic occurs, if Go doesn't handle it in any specific way, the default behavior is to print the reason for the panic, print the stack trace, and exit the program.

Now, let's go back to the fundamental question: What exactly is panic?

I won't delve into the concept, but I'll describe a few simple facts:

1. The `panic()` function internally creates a crucial data structure called `_panic` and associates it with the goroutine.
2. The `panic()` function executes the `_defer` function chain and handles the state of `_panic` accordingly.

What does it mean to handle `_panic`?

It means looping through the `_defer` function chain on the goroutine. If all the `_defer` functions are executed and the state of `_panic` hasn't been recovered, there is no other option but to exit the process and print the stack trace.

If a friend on the `_defer` chain recovers the state of `_panic`, marking it as recovered, the process ends there. The normal code execution continues after that, following the `deferreturn` logic.

So, what is panic?

It's just a special function call. That's all it is.

How special is it? I will explore its deep principles in the next article. In the meantime, consider a few questions:

- What exactly is panic? Is it a structure or a function?
- Why does panic cause a Go process to exit?
- Why does recover need to be placed within a defer to work?
- Why does the process still not recover even when recover is placed within a defer?
- Why is it possible to panic again after a panic? What are the consequences?

## Summary

1. Panic can occur in three ways: it can be actively triggered by developers, assisted by the compiler's logic, or triggered by a software interrupt signal.
2. Regardless of the posture, all methods ultimately rely on the `panic()` function. Panic is merely a language-level handling mechanism.
3. By default, after a panic occurs, if not handled, the program prints the panic cause, the stack trace, and exits the process.