kernel/services/other/fatal.html · kernel/services/other/fatal.rst 공식 원문 ↗

Zephyr 3.7.0 · Kernel · Other Services

Fatal Error

Build·runtime check와 CPU exception을 reason code로 분류하고 weak policy handler에서 halt 또는 제한적 복구를 결정합니다.

Official pathkernel/services/other/fatal.html
Source filekernel/services/other/fatal.rst
Source versionZephyr 3.7.0
TranslationFull · reviewed

본문 기준: Zephyr 3.7.0 LTS3 · 이후 유지보수 패치는 별도입니다.

Part 1

요약·해설

Assertion level과 post action, user·supervisor stack protection, sentinel·canary 차이와 custom fatal handler의 consistency 위험을 설명합니다.

Checks

runtime · build

Reasons

oops · panic · exception

Policy

halt · thread abort

Part 2

접을 수 있는 영어 원문 전체

영어 원문 전체 펼치기
원문 SHA-256 925B9B94A6A66760470FE61B11DE3C6B8F8D2442E2220D399B2BD33DF195D6B1
.. _fatal:

Fatal Errors
############

Software Errors Triggered in Source Code
****************************************

Zephyr provides several methods for inducing fatal error conditions through
either build-time checks, conditionally compiled assertions, or deliberately
invoked panic or oops conditions.

Runtime Assertions
==================

Zephyr provides some macros to perform runtime assertions which may be
conditionally compiled. Their definitions may be found in
:zephyr_file:`include/zephyr/sys/__assert.h`.

Assertions are enabled by setting the ``__ASSERT_ON`` preprocessor symbol to a
non-zero value. There are two ways to do this:

- Use the :kconfig:option:`CONFIG_ASSERT` and :kconfig:option:`CONFIG_ASSERT_LEVEL` kconfig
  options.
- Add ``-D__ASSERT_ON=<level>`` to the project's CFLAGS, either on the
  build command line or in a CMakeLists.txt.

The ``__ASSERT_ON`` method takes precedence over the kconfig option if both are
used.

Specifying an assertion level of 1 causes the compiler to issue warnings that
the kernel contains debug-type ``__ASSERT()`` statements; this reminder is
issued since assertion code is not normally present in a final product.
Specifying assertion level 2 suppresses these warnings.

Assertions are enabled by default when running Zephyr test cases, as
configured by the :kconfig:option:`CONFIG_TEST` option.

The policy for what to do when encountering a failed assertion is controlled
by the implementation of :c:func:`assert_post_action`. Zephyr provides
a default implementation with weak linkage which invokes a kernel oops if
the thread that failed the assertion was running in user mode, and a kernel
panic otherwise.

__ASSERT()
----------

The ``__ASSERT()`` macro can be used inside kernel and application code to
perform optional runtime checks which will induce a fatal error if the
check does not pass. The macro takes a string message which will be printed
to provide context to the assertion. In addition, the kernel will print
a text representation of the expression code that was evaluated, and the
file and line number where the assertion can be found.

For example:

.. code-block:: c

  __ASSERT(foo == 0xF0CACC1A, "Invalid value of foo, got 0x%x", foo);

If at runtime ``foo`` had some unexpected value, the error produced may
look like the following:

.. code-block:: none

	ASSERTION FAIL [foo == 0xF0CACC1A] @ ZEPHYR_BASE/tests/kernel/fatal/src/main.c:367
		Invalid value of foo, got 0xdeadbeef
	[00:00:00.000,000] <err> os: r0/a1:  0x00000004  r1/a2:  0x0000016f  r2/a3:  0x00000000
	[00:00:00.000,000] <err> os: r3/a4:  0x00000000 r12/ip:  0x00000000 r14/lr:  0x00000a6d
	[00:00:00.000,000] <err> os:  xpsr:  0x61000000
	[00:00:00.000,000] <err> os: Faulting instruction address (r15/pc): 0x00009fe4
	[00:00:00.000,000] <err> os: >>> ZEPHYR FATAL ERROR 4: Kernel panic
	[00:00:00.000,000] <err> os: Current thread: 0x20000414 (main)
	[00:00:00.000,000] <err> os: Halting system

__ASSERT_EVAL()
---------------

The ``__ASSERT_EVAL()`` macro can also be used inside kernel and application
code, with special semantics for the evaluation of its arguments.

It makes use of the ``__ASSERT()`` macro, but has some extra flexibility. It
allows the developer to specify different actions depending whether the
``__ASSERT()`` macro is enabled or not.  This can be particularly useful to
prevent the compiler from generating comments (errors, warnings or remarks)
about variables that are only used with ``__ASSERT()`` being assigned a value,
but otherwise unused when the ``__ASSERT()`` macro is disabled.

Consider the following example:

.. code-block:: c

  int x;
  x = foo();
  __ASSERT(x != 0, "foo() returned zero!");

If ``__ASSERT()`` is disabled, then 'x' is assigned a value, but never used.
This type of situation can be resolved using the __ASSERT_EVAL() macro.

.. code-block:: c

  __ASSERT_EVAL ((void) foo(),
  		 int x = foo(),
                 x != 0,
                 "foo() returned zero!");

The first parameter tells ``__ASSERT_EVAL()`` what to do if ``__ASSERT()`` is
disabled.  The second parameter tells ``__ASSERT_EVAL()`` what to do if
``__ASSERT()`` is enabled.  The third and fourth parameters are the parameters
it passes to ``__ASSERT()``.

__ASSERT_NO_MSG()
-----------------

The ``__ASSERT_NO_MSG()`` macro can be used to perform an assertion that
reports the failed test and its location, but lacks additional debugging
information provided to assist the user in diagnosing the problem; its use is
discouraged.

Build Assertions
================

Zephyr provides two macros for performing build-time assertion checks.
These are evaluated completely at compile-time, and are always checked.

BUILD_ASSERT()
--------------

This has the same semantics as C's ``_Static_assert`` or C++'s
``static_assert``. If the evaluation fails, a build error will be generated by
the compiler. If the compiler supports it, the provided message will be printed
to provide further context.

Unlike ``__ASSERT()``, the message must be a static string, without
:c:func:`printf()`-like format codes or extra arguments.

For example, suppose this check fails:

.. code-block:: c

	BUILD_ASSERT(FOO == 2000, "Invalid value of FOO");

With GCC, the output resembles:

.. code-block:: none

	tests/kernel/fatal/src/main.c: In function 'test_main':
	include/toolchain/gcc.h:28:37: error: static assertion failed: "Invalid value of FOO"
	 #define BUILD_ASSERT(EXPR, MSG) _Static_assert(EXPR, "" MSG)
					 ^~~~~~~~~~~~~~
	tests/kernel/fatal/src/main.c:370:2: note: in expansion of macro 'BUILD_ASSERT'
	  BUILD_ASSERT(FOO == 2000,
	  ^~~~~~~~~~~~~~~~

Kernel Oops
===========

A kernel oops is a software triggered fatal error invoked by
:c:func:`k_oops()`.  This should be used to indicate an unrecoverable condition
in application logic.

The fatal error reason code generated will be ``K_ERR_KERNEL_OOPS``.

Kernel Panic
============

A kernel error is a software triggered fatal error invoked by
:c:func:`k_panic()`.  This should be used to indicate that the Zephyr kernel is
in an unrecoverable state. Implementations of
:c:func:`k_sys_fatal_error_handler()` should not return if the kernel
encounters a panic condition, as the entire system needs to be reset.

Threads running in user mode are not permitted to invoke :c:func:`k_panic()`,
and doing so will generate a kernel oops instead. Otherwise, the fatal error
reason code generated will be ``K_ERR_KERNEL_PANIC``.

Exceptions
**********

Spurious Interrupts
===================

If the CPU receives a hardware interrupt on an interrupt line that has not had
a handler installed with ``IRQ_CONNECT()`` or :c:func:`irq_connect_dynamic()`,
then the kernel will generate a fatal error with the reason code
``K_ERR_SPURIOUS_IRQ()``.

Stack Overflows
===============

In the event that a thread pushes more data onto its execution stack than its
stack buffer provides, the kernel may be able to detect this situation and
generate a fatal error with a reason code of ``K_ERR_STACK_CHK_FAIL``.

If a thread is running in user mode, then stack overflows are always caught,
as the thread will simply not have permission to write to adjacent memory
addresses outside of the stack buffer. Because this is enforced by the
memory protection hardware, there is no risk of data corruption to memory
that the thread would not otherwise be able to write to.

If a thread is running in supervisor mode, or if :kconfig:option:`CONFIG_USERSPACE` is
not enabled, depending on configuration stack overflows may or may not be
caught.  :kconfig:option:`CONFIG_HW_STACK_PROTECTION` is supported on some
architectures and will catch stack overflows in supervisor mode, including
when handling a system call on behalf of a user thread. Typically this is
implemented via dedicated CPU features, or read-only MMU/MPU guard regions
placed immediately adjacent to the stack buffer. Stack overflows caught in this
way can detect the overflow, but cannot guarantee against data corruption and
should be treated as a very serious condition impacting the health of the
entire system.

If a platform lacks memory management hardware support,
:kconfig:option:`CONFIG_STACK_SENTINEL` is a software-only stack overflow detection
feature which periodically checks if a sentinel value at the end of the stack
buffer has been corrupted. It does not require hardware support, but provides
no protection against data corruption. Since the checks are typically done at
interrupt exit, the overflow may be detected a nontrivial amount of time after
the stack actually overflowed.

Finally, Zephyr supports GCC compiler stack canaries via
:kconfig:option:`CONFIG_STACK_CANARIES`.  If enabled, the compiler will insert a canary
value randomly generated at boot into function stack frames, checking that the
canary has not been overwritten at function exit. If the check fails, the
compiler invokes :c:func:`__stack_chk_fail()`, whose Zephyr implementation
invokes a fatal stack overflow error. An error in this case does not indicate
that the entire stack buffer has overflowed, but instead that the current
function stack frame has been corrupted. See the compiler documentation for
more details.

Other Exceptions
================

Any other type of unhandled CPU exception will generate an error code of
``K_ERR_CPU_EXCEPTION``.

Fatal Error Handling
********************

The policy for what to do when encountering a fatal error is determined by the
implementation of the :c:func:`k_sys_fatal_error_handler()` function.  This
function has a default implementation with weak linkage that calls
``LOG_PANIC()`` to dump all pending logging messages and then unconditionally
halts the system with :c:func:`k_fatal_halt()`.

Applications are free to implement their own error handling policy by
overriding the implementation of :c:func:`k_sys_fatal_error_handler()`.
If the implementation returns, the faulting thread will be aborted and
the system will otherwise continue to function. See the documentation for
this function for additional details and constraints.

API Reference
*************

.. doxygengroup:: fatal_apis

Part 3

한국어 전문 번역

Source code에서 발생시키는 software error

Zephyr는 build-time check, 조건부 compile assertion, 의도적으로 호출하는 panic 또는 oops로 fatal error condition을 만드는 여러 방법을 제공합니다.

Runtime assertion

조건부로 compile할 수 있는 runtime assertion macro는 include/zephyr/sys/__assert.h에 정의됩니다.

__ASSERT_ON preprocessor symbol을 non-zero로 설정하면 assertion이 활성화됩니다. CONFIG_ASSERT와 CONFIG_ASSERT_LEVEL Kconfig를 사용하거나 build command/CMakeLists.txt의 CFLAGS에 -D__ASSERT_ON=<level>을 추가할 수 있습니다. 둘 다 쓰면 __ASSERT_ON 방식이 Kconfig보다 우선합니다.

Assertion level 1은 final product에 보통 넣지 않는 debug형 __ASSERT()가 kernel에 포함됐다는 compiler warning을 내고, level 2는 이 warning을 억제합니다. Zephyr test case에서는 CONFIG_TEST 설정으로 assertion이 기본 활성화됩니다.

Assertion 실패 뒤 정책은 assert_post_action 구현이 정합니다. Zephyr의 weak default는 실패 thread가 user mode면 kernel oops를, 아니면 kernel panic을 호출합니다.

__ASSERT()

__ASSERT()는 kernel·application code에서 선택적 runtime check를 수행하고 실패하면 fatal error를 만듭니다. Context를 설명할 format string message를 받고, kernel은 평가한 expression text와 source file·line도 출력합니다.

__ASSERT(foo == 0xF0CACC1A, "Invalid value of foo, got 0x%x", foo);

Runtime에 foo 값이 예상과 다르면 다음과 같은 error가 나올 수 있습니다.

ASSERTION FAIL [foo == 0xF0CACC1A] @ ZEPHYR_BASE/tests/kernel/fatal/src/main.c:367
        Invalid value of foo, got 0xdeadbeef
[00:00:00.000,000] <err> os: r0/a1:  0x00000004  r1/a2:  0x0000016f  r2/a3:  0x00000000
[00:00:00.000,000] <err> os: r3/a4:  0x00000000 r12/ip:  0x00000000 r14/lr:  0x00000a6d
[00:00:00.000,000] <err> os:  xpsr:  0x61000000
[00:00:00.000,000] <err> os: Faulting instruction address (r15/pc): 0x00009fe4
[00:00:00.000,000] <err> os: >>> ZEPHYR FATAL ERROR 4: Kernel panic
[00:00:00.000,000] <err> os: Current thread: 0x20000414 (main)
[00:00:00.000,000] <err> os: Halting system

__ASSERT_EVAL()

__ASSERT_EVAL()은 __ASSERT()를 이용하되 assertion enable 여부에 따라 서로 다른 action을 지정합니다. Assertion을 끄면 assertion에서만 쓰던 variable이 assigned-but-unused가 되어 compiler warning이나 remark를 만드는 문제를 피하는 데 유용합니다.

다음 code는 assertion을 끄면 x를 할당만 하고 사용하지 않습니다.

int x;
x = foo();
__ASSERT(x != 0, "foo() returned zero!");

이를 __ASSERT_EVAL()로 다음처럼 해결합니다.

__ASSERT_EVAL ((void) foo(),
               int x = foo(),
               x != 0,
               "foo() returned zero!");

첫 parameter는 __ASSERT()가 disabled일 때 할 action, 둘째는 enabled일 때 할 action입니다. 셋째와 넷째는 __ASSERT()에 전달할 condition과 message입니다.

__ASSERT_NO_MSG()

__ASSERT_NO_MSG()는 실패한 test와 위치는 보고하지만 진단을 돕는 추가 debug 정보가 없습니다. 사용을 권장하지 않습니다.

Build assertion

Zephyr는 compile time에 완전히 평가되고 항상 검사되는 build-time assertion macro 두 개를 제공합니다.

BUILD_ASSERT()

BUILD_ASSERT()는 C _Static_assert 또는 C++ static_assert와 같은 의미입니다. 평가가 실패하면 compiler build error가 발생하고 지원 compiler는 message도 표시합니다. __ASSERT()와 달리 message는 printf format이나 추가 argument가 없는 static string이어야 합니다.

다음 검사에 실패한다고 가정합니다.

BUILD_ASSERT(FOO == 2000, "Invalid value of FOO");

GCC output은 다음과 비슷합니다.

tests/kernel/fatal/src/main.c: In function 'test_main':
include/toolchain/gcc.h:28:37: error: static assertion failed: "Invalid value of FOO"
 #define BUILD_ASSERT(EXPR, MSG) _Static_assert(EXPR, "" MSG)
                                 ^~~~~~~~~~~~~~
tests/kernel/fatal/src/main.c:370:2: note: in expansion of macro 'BUILD_ASSERT'
  BUILD_ASSERT(FOO == 2000,
  ^~~~~~~~~~~~~~~~

Kernel oops

Kernel oops는 k_oops()가 발생시키는 software fatal error입니다. Application logic의 복구 불가능 condition을 나타낼 때 사용하며 reason code는 K_ERR_KERNEL_OOPS입니다.

Kernel panic

Kernel panic은 k_panic()이 발생시키는 software fatal error로 Zephyr kernel 자체가 복구 불가능 상태임을 뜻합니다. Panic이면 전체 system을 reset해야 하므로 k_sys_fatal_error_handler() 구현은 반환하면 안 됩니다.

User mode thread는 k_panic()을 호출할 수 없으며 시도하면 kernel oops가 납니다. 그 밖의 경우 reason code는 K_ERR_KERNEL_PANIC입니다.

Exception

Spurious interrupt

CPU가 IRQ_CONNECT()나 irq_connect_dynamic()으로 handler를 설치하지 않은 line에서 hardware interrupt를 받으면 kernel은 K_ERR_SPURIOUS_IRQ reason의 fatal error를 만듭니다.

Stack overflow

Thread가 실행 stack buffer보다 많은 data를 push하면 kernel이 이를 감지해 K_ERR_STACK_CHK_FAIL reason의 fatal error를 만들 수 있습니다.

User mode thread의 overflow는 항상 잡힙니다. Stack 밖 인접 memory에 쓸 permission이 없고 memory protection hardware가 막으므로 원래 쓸 수 없던 memory가 손상될 위험도 없습니다.

Supervisor mode이거나 CONFIG_USERSPACE를 끈 경우에는 설정에 따라 잡히지 않을 수 있습니다. 일부 architecture의 CONFIG_HW_STACK_PROTECTION은 CPU 기능 또는 stack 바로 옆 read-only MMU/MPU guard region으로 supervisor mode와 user system call 처리 중 overflow도 잡습니다. 그러나 detection까지의 data corruption을 보장해 막지는 못하므로 system 전체 health에 영향을 주는 매우 심각한 condition으로 취급해야 합니다.

Memory management hardware가 없으면 CONFIG_STACK_SENTINEL이 stack 끝 sentinel value 손상을 주기적으로 검사합니다. Hardware는 필요 없지만 data corruption을 막지 못하며 보통 interrupt exit 때 검사하므로 실제 overflow보다 상당히 늦게 발견할 수 있습니다.

CONFIG_STACK_CANARIES는 GCC stack canary를 사용합니다. Compiler가 boot 때 random 생성한 canary를 function stack frame에 넣고 return 때 overwrite 여부를 검사합니다. 실패하면 __stack_chk_fail()이 fatal stack overflow를 호출합니다. 이는 전체 stack buffer overflow가 아니라 현재 function frame corruption을 뜻할 수도 있습니다.

기타 exception

그 밖의 처리되지 않은 CPU exception은 K_ERR_CPU_EXCEPTION reason code를 만듭니다.

Fatal error 처리

Fatal error 정책은 k_sys_fatal_error_handler() 구현이 정합니다. Weak default는 LOG_PANIC()으로 pending logging message를 모두 dump한 뒤 k_fatal_halt()로 무조건 system을 정지합니다.

Application은 이 weak implementation을 override해 자체 정책을 만들 수 있습니다. Handler가 반환하면 faulting thread를 abort하고 system은 그 밖의 실행을 계속하지만 API 설명의 제약과 consistency 위험을 반드시 고려해야 합니다.

API 참고: fatal_apis

k_fatal_halt(reason)

Fatal error 뒤 system을 정지합니다. Architecture-specific code로 power off하거나 low-power halt를 시도하고, 지원이 없으면 interrupt를 잠근 채 idle loop에 머뭅니다. 반환하지 않습니다.

매개변수

  • reason: fatal exception reason code
k_sys_fatal_error_handler(reason, esf)

Fatal error policy handler입니다. Application code가 직접 호출하지 않으며 weak symbol이라 application이 자체 정책으로 override할 수 있습니다. Default는 무조건 system을 정지합니다. 이 함수가 반환하면 현재 faulting thread를 abort합니다.

매개변수

  • reason: fatal error 이유
  • esf: 오류 시 register state 일부 또는 전체를 담은 exception context, 경우에 따라 NULL

Source

출처

영어 원문에는 문단, 문서 지시문, 표, 코드, 코드 심벌, 경로를 그대로 보존했습니다.