Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

94305
1828
210800


[리눅스커널] CONFIG_FUNCTION_TRACER를 키는 방법 Linux Kernel - Core Analysis

defconfig 파일에서 아래 컨피그를 추가하면, 

CONFIG_IRQSOFF_TRACER=y
CONFIG_SCHED_TRACER=y
CONFIG_STACK_TRACER=y

.config에서 아래 내용을 확인할 수 있다.

CONFIG_FTRACE=y
CONFIG_FUNCTION_TRACER=y
CONFIG_FUNCTION_GRAPH_TRACER=y
CONFIG_IRQSOFF_TRACER=y
# CONFIG_PREEMPT_TRACER is not set
CONFIG_SCHED_TRACER=y

'디버깅을 통해서 배우는 리눅스 커널의 구조와 원리' 전체 목차 -----Table of Contents-----

@ 리눅스 개발자분들께...

제 블로그에 오셔서 댓글로 책이 언제 출간되는지 궁금해하시는 분이 계신 것 같은데요.

아래 링크에서 만나실 수 있습니다.  이 책은 리눅스 개발에 도움이 되는 유용한 내용을 담고 있으니, 많은 리눅스 시스템 개발자분들이 읽어 주셨으면 좋겠습니다.

❑ 디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

❑ 디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2

이 책의 주요 독자는 신입 개발자 혹은 졸업반 학생과 같은 리눅스 커널의 입문자이며 세부 예상 독자는 다음과 같습니다.

▣ 리눅스 보드로 졸업 과제를 준비 중인 졸업반 학생
▣ 연구실의 프로젝트를 리눅스 환경(리눅스 보드, 리눅스 배포판)에서 진행 중인 대학원생
▣ 리눅스 드라이버의 동작 원리를 더 깊게 알고 싶은 분
▣ 리눅스 커널을 실무에서 어떻게 디버깅하는지 알고 싶은 리눅스 시스템 개발자

# 책 디자인 스틸 컷은 다음과 같습니다. 
 


아래 링크와 같이 유튜브 강의 동영상을 업로드를 하니 책을 읽으면서 들으시면 좋습니다.



디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1

리눅스의 전망과 소개


라즈베리 파이 설정 

라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드


리눅스커널 디버깅


프로세스

프로세스 소개 
프로세스 확인하기  
프로세스는 어떻게 생성할까?  
유저 레벨 프로세스 실행 실습  
커널 스레드  
커널 내부 프로세스의 생성 과정   
프로세스의 종료 과정 분석  
태스크 디스크립터(task_struct 구조체)  
스레드 정보: thread_info 구조체  
프로세스의 태스크 디스크립터에 접근하는 매크로 함수  
프로세스 디버깅  
   * glibc의 fork() 함수를 gdb로 디버깅하기  

인터럽트 처리

인터럽트 소개  
   * 리눅스 커널에서의 인터럽트 처리 흐름    
인터럽트 컨텍스트  
인터럽트 핸들러는 언제 호출될까?  
인터럽트 핸들러는 어떻게 등록할까?  
인터럽트 디스크립터  
인터럽트 디버깅  


인터럽트 후반부 처리









6.9 Soft IRQ 서비스는 누가 언제 처리하나?




6.13 Soft IRQ 디버깅
6.13.1 ftrace Soft IRQ 이벤트 분석 방법
6.13.2 /proc/softirqs로 Soft IRQ 서비스 실행 횟수 확인

워크큐

워크큐 소개
워크큐 종류 알아보기
워크란  
워크를 워크큐에 어떻게 큐잉할까?
   워크를 큐잉할 때 호출하는 워크큐 커널 함수 분석   
워커 쓰레드란
워크큐 실습 및 디버깅
   ftrace로 워크큐 동작 확인   
   인터럽트 후반부로 워크큐 추가 실습 및 로그 분석 
   Trace32로 워크큐 자료 구조 디버깅하기 
딜레이 워크 소개  
   딜레이 워크는 누가 언제 호출할까?
라즈베리파이 딜레이 워크 실습 및 로그 확인  


디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2

커널 시간관리

커널 타이머 관리 주요 개념 소개
jiffies란
커널 타이머 제어
동적 타이머 초기화
동적 타이머 등록하기
동적 타이머는 누가 언제 실행하나?
라즈베리파이 커널 타이머 실습 및 로그 분석
   
커널 동기화

커널 동기화 기본 개념 소개
레이스 발생 동작 확인
커널 동기화 기법 소개
스핀락
뮤텍스란
커널 동기화 디버깅

프로세스 스케줄링

스케줄링 소개
프로세스 상태 관리
   어떤 함수가 프로세스 상태를 바꿀까?
스케줄러 클래스
런큐
CFS 스케줄러
   CFS 관련 세부 함수 분석  
선점 스케줄링(Preemptive Scheduling)   
프로세스는 어떻게 깨울까?
스케줄링 핵심 schedule() 함수 분석
컨택스트 스위칭
스케줄링 디버깅
   스케줄링 프로파일링
     CPU에 부하를 주는 테스트   
     CPU에 부하를 주지 않는 테스트 

시스템 콜

시스템 콜 주요 개념 소개
유저 공간에서 시스템 콜은 어떻게 발생할까
시스템 콜 핸들러는 어떤 동작을 할까? 
시스템 콜 실행 완료 후 무슨 일을 할까?
시스템 콜 관련 함수  
시스템 콜 디버깅  
   

시그널이란

시그널이란
시그널 설정은 어떻게 할까
시그널 생성 과정 함수 분석


가상 파일시스템

가상 파일시스템 소개
파일 객체
파일 객체 함수 오퍼레이션 동작
프로세스는 파일객체 자료구조를 어떻게 관리할까?
슈퍼블록 객체
아이노드 객체
덴트리 객체
가상 파일시스템 디버깅


커널 메모리 관리
 
가상 주소를 물리 주소로 어떻게 변환할까?   
메모리 존(Zone)에 대해서   
커널 메모리 할당은 어떻게 할까   
슬랩 메모리 할당자와 kmalloc 슬랩 캐시 분석   
커널 메모리 디버깅


[부록 A] GCC 지시어
   * inline    
   * noinline    
   * __noreturn   
   * unused   
[부록 B] 리눅스 커널 실력을 키우는 방법
[부록 C] 리눅스 커널 프로젝트에 기여하기  
C.1 리눅스 커널 오픈소스 프로젝트 소개 
   * 용어  
C.2 설정 방법 
C.3 패치 코드를 작성한 후 이메일로 보내기  
C.5 리눅스 커널 오픈소스 프로젝트로 얻는 지식 


[Arm프로세서] Armv8: Synchronous 익셉션(Exception)의 실행 흐름 ARMv8: 익셉션(Exception)

Synchronous 익셉션의 유발 인자는 다음과 같이 분류될 수 있습니다.


표 9.6 Synchronous 익셉션을 유발하는 원인

소프트웨어적으로 치명적인 오류를 유발하는 명령어나 유저 공간에서 svc 명령어를 실행하면 Synchronous 익셉션이 유발됩니다.

 Armv7 아키텍처의 메모리 어보트 타입 익셉션에 익숙하신 분은 위 표를 보면 조금 당황할 수 있는데요. 이는 메모리 어보트 타입 익셉션을 분류하는 방식이 Armv8 아키텍처와는 다르기 때문입니다. 이 차이점에 대해서 조금 짚어 보겠습니다.

메모리 어보트 익셉션의 차이점: Armv7 vs Armv8

시스템 개발자들에게 "Arm 프로세서의 익셉션이 무엇인가?"라고 질문하면, 대부분 Armv7 아키텍처의 익셉션을 떠올리는 경우가 많습니다. 만약에 유효하지 않은 메모리 공간에 접근하는 명령어를 실행하면, Armv7 아키텍처 기반 프로세서는 어떻게 동작할까요? 바로 데이터 어보트를 유발합니다.

또한 명령어를 페치하다가 페치할 수 없는 조건에서는 Arm7 아키텍처에서는 프리페치 어보트가 유발됩니다. 이어서 비트 플립이나 유효하지 않은 코-프로세서 명령어를 실행하면, Armv7 아키텍처에서는 Undefined Instruction 익셉션을 유발합니다.

그런데 위에서 언급한 같은 명령어를 Armv8 아키텍처 기반의 프로세서에서 실행되면 어떤 익셉션이 유발될까요? Arm 코어는 익셉션이 유발된 세부 원인을 체크해 익셉션 신드롬 레지스터에 익셉션의 세부 원인을 나타내는 비트를 저장한 후, 모두 Synchronous 익셉션이 유발됩니다. 

이게 Armv8 아키텍처를 배우는 분들이 낯설어하는 부분인데, Q/A를 통해 정리해봅시다.

Q: 'ldr r5,[r2]' 명령어를 실행하려고 할때, r2 레지스터가 0x0이면 Arm7 아키텍처에서는 데이터 어보트라는 익셉션을 유발합니다. 그렇다면, 같은 조건(x2 = 0x0)에서 'ldr x5,[x2]' 명령어를 Arm8 아키텍처 기반 Arm 프로세서에서 실행하면 어떤 익셉션을 유발할까요?

A: 익셉션 신드롬 레지스터에 '데이터 어보트' 익셉션 클래스 비트를 저장한 후 Synchronous 익셉션을 유발합니다.

Q: 그렇다면, Synchronous 익셉션 핸들러에서 익셉션 신드롬 레지스터를 읽어 익셉션 클래스 정보를 확인해야 겠네요.

A: 맞습니다. Armv7 아키텍처에서 데이터 어보트 익셉션은 Armv8 아키텍처에서는 익셉션 클래스로 관리됩니다.

Q: 만약 프리페치 어보트를 유발하는 명령어를 Armv7 아키텍처에서 실행하면 프리페치 어보트가 유발됩니다. 그렇다면 비슷한 상황에서 Armv8 아키텍처에서는 어떤 익셉션이 유발될까요?

A: Arm 코어는 익셉션 신드롬 레지스터에 '인스트럭션 페치' 익셉션 클래스 비트를 저장한 후 Synchronous 익셉션을 유발합니다.

이어지는 포스트에서 '메모리 어보트'로 익셉션이 유발되는 과정을 다루니, 이번 절에서 다룬 내용을 잘 기억합시다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


>

[공유] YES24 '2020년 베스트 IT 전문서' 로 선정: 디버깅을 통해 배우는 리눅스 커널의 구조와 원리 Question_Announcement

인터넷 서점의 대표주자인 YES24에서 진행된 '2020년 IT 연말결산' 이벤트에서 '디버깅을 통해 배우는 리눅스 커널의 구조와 원리' 책이 '2020년 베스트 IT 전문서' 중 하나로 선정됐습니다.

아래 링크를 접근하면 관련 내용을 확인할 수 있습니다.




최근 IT 책의 동향과 흐름을 보면 '머신러닝, 빅데이터, AI'과 같은 주제가 인기인데, 이 가운데 많은 개발자들이 어렵다고 느끼는 '리눅스 커널'과 같은 '클래식'한 책이 '머신러닝, 빅데이터, AI' 책들과 함께 '2020년 베스트 IT 전문서'에 선정됐다는 점이 의미가 있다고 생각합니다.  

또한 '디버깅을 통해 배우는 리눅스 커널의 구조와 원리' 책은 YES24에서 판매지수가 8500을 넘어서고 있는데, 이 또한 놀라운 일이 아닐 수 없습니다. 개발자들이 가장 어렵다고 느끼는 주제의 책이 이렇게 많이 읽힌다는 사실에 주위 지인들이 의야해 하고 있습니다.



이 모든 결과는 이 책을 관심있게 봐주시는 '임베디드 리눅스 시스템(BSP) 개발자님' 분들 덕분입니다. 개발자님들의 성원에 보답하고자 꾸준히 책의 강의 동영상과 실전 개발에 도움이 될 유익한 컨텐츠를 유튜브로 올리고 있으니 참고하셨으면 좋겠습니다. 컨텐츠를 개발에 잘 활용하셔서 일찍 퇴근하시길 빕니다.



또한 이 책을 열심히 홍보해주신 존경하는 리눅스 강사님들, 교수님들, '문c 블로그' 운영자이신 문영일 선배님께도 감사하다는 말씀을 드리고 싶습니다. 2020년이 지나가기 전에 연락 드리고 인사 드리겠습니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자









[Arm프로세서] Armv8: 익셉션 핸들러(Exception Handler) ARMv8: 익셉션(Exception)

Arm 코어가 익셉션을 감지하면 익셉션 벡터 주소로 프로그램 카운터를 브랜치합니다. 여기까지가 Arm 코어가 하드웨어적으로 처리하는 부분인데, 그 다음부터 어떻게 동작할까요? Arm 코어의 프로그램 카운터가 특정 주소로 바뀌면, 해당 주소에 위치한 어셈블리 명령어를 페치해 실행합니다. 즉, 익셉션 벡터 주소로 프로그램 카운터를 브랜치하면 이 주소에 위치한 명령어가 실행되는 것입니다. 이 부분부터 소프트웨어적으로 익셉션을 핸들링하는 동작이라고 볼 수 있습니다. 익셉션 벡터에 위치한 명령어가 실행되면 이를 "익셉션 핸들러가 처리된다"라고 합니다.

익셉션 벡터에 위치한 명령어 세트를 보통 익셉션 핸들러라고 하는데, 이해를 돕기 위해 다음과 같이 Armv8 아키텍처에서 빌드된 익셉션 핸들러 코드를 소개합니다.

01 ffffff8008082000 <vectors>:
02 ffffff8008082000:   d10503ff    sub sp, sp, #0x140
03 ffffff8008082004:   8b2063ff    add sp, sp, x0
...
04 ffffff8008082200:   d10503ff    sub sp, sp, #0x140
05 ffffff8008082204:   8b2063ff    add sp, sp, x0
06 ffffff8008082208:   cb2063e0    sub x0, sp, x0
07 ffffff800808220c:   37700080    tbnz    w0, #14, ffffff800808221c <vectors+0x21c>

만약 Arm 코어가 익셉션(EL1에서 Synchronous 익셉션 유발)을 감지하면 04번째 줄과 같이 ffffff8008082200 주소로 프로그램 카운터가 바뀝니다. ffffff8008082200와 같은 주소가 익셉션 벡터이고, 02~05번째 줄에 있는 명령어를 익셉션 핸들러라고 합니다.

익셉션의 종류에 따라 익셉션 핸들러는 각기 다른 방식으로 처리합니다.

   ❑ Synchronous 익셉션: 시스템 리셋/시스템 콜 실행
   ❑ IRQ 익셉션: 인터럽트를 처리하는 인터럽트 서비스 루틴 실행 
   ❑ SError 익셉션: 시스템 리셋  

그럼 익셉션 핸들러가 익셉션의 종류 별로 어떤 동작을 하는 지 더 자세히 알아보겠습니다.

첫 번째로, Synchronous 익셉션 핸들러는 크게 2가지 방식으로 익셉션을 처리합니다. 먼저 소프트웨어적으로 치명적인 오류를 유발하는 명령어가 실행되면, Synchronous 익셉션이 유발됩니다. 이후 Synchronous 익셉션 핸들러에서는 익셉션이 발생한 세부 원인을 ESR_ELx(익셉션 신드롬 레지스터)를 읽어 체크한 후 다음과 같은 동작을 수행합니다.

   ❑ 유저 애플리케이션: 프로세스를 종료
   ❑ 운영체제의 커널이나 커널 드라이버: 시스템을 리셋

또한 유저 애플리케이션이 구동되는 EL0에서 svc 명령어를 실행(시스템 콜)하면 Arm 코어는 Synchronous 익셉션을 유발합니다. Synchronous 익셉션 핸들러에서는 익셉션이 발생한 세부 원인을 ESR_ELx 레지스터를 통해 확인한 후, 시스템 콜 핸들러를 호출합니다.

[중요] 
Synchronous 익셉션이 유발되면 익셉션 핸들러에서 ESR_ELx를 읽으면 익셉션이 유발된 세부 원인을 파악할 수 있습니다. Arm8 아키텍처에서 익셉션이 유발된 세부 원인을 익셉션 클래스라고 정의하는데, 익셉션 클래스는 어떤 이유로 익셉션이 유발됐는지를 나타내는 6개의 비트 정보로 구성돼 있습니다. 참고로 Arm 코어는 익셉션을 감지하는 과정에서, 익셉션이 유발된 원인을 확인한 후 익셉션 클래스에서 정의된 비트를 ESR_ELx 레지스터의 [31:26] 비트에 저장합니다. 


둘째, IRQ 익셉션이 유발되면 호출되는 익셉션 핸들러의 처리 방식은 Arm7 아키텍처와 같습니다. IRQ 익셉션은 외부 하드웨어의 변화를 알리기 위한 인터페이스로 동작하므로, 익셉션 핸들러에서 인터럽트 서비스 루틴을 실행합니다. 운영체제의 커널은 인터럽트 서비스 루틴을 통해 인터럽트 핸들러를 호출하며, 인터럽트 핸들러에서는 발생한 인터럽트에 대한 세세한 처리를 수행합니다.

셋째, Armv8 아키텍처에서 새롭게 정의된 SError 익셉션은 외부 메모리 유닛에서 어보트가 감지될 경우 비동기적으로 유발됩니다. 대부분 소프트웨어적으로 치명적인 오류가 있을 때 외부 메모리 유닛에서 어보트가 감지되므로, SError 익셉션 핸들러는 주요 레지스터 정보를 콘솔 로그로 출력한 다음 시스템을 리셋시키는 동작을 수행합니다. 

아래 링크에서 SError 익셉션 핸들러가 실행될 때 출력되는 로그를 확인할 수 있습니다.

https://bugzilla.kernel.org/show_bug.cgi?id=200429

[  192.082927] Kernel panic - not syncing: Asynchronous SError Interrupt
[  192.082928] CPU: 4 PID: 2424 Comm: devmem Not tainted 4.17.0-02102-gdcfa25a-dirty #109
[  192.082929] Hardware name: Stingray Combo SVK (BCM958742K) (DT)
[  192.082930] Call trace:
[  192.082939]  dump_backtrace+0x0/0x1b8
[  192.082941]  show_stack+0x14/0x1c
[  192.082943]  dump_stack+0x90/0xb0
[  192.082945]  panic+0x140/0x2a8
[  192.082946]  __stack_chk_fail+0x0/0x18 // nmi_panic
[  192.082947]  arm64_serror_panic+0x74/0x80
[  192.082948]  do_serror+0x48/0xa0
[  192.082949]  el0_error_naked+0x10/0x18
[  192.082954] SMP: stopping secondary CPUs
[  192.082957] Kernel Offset: disabled
[  192.082959] CPU features: 0x21806008
[  192.082959] Memory Limit: none
[  192.267536] ---[ end Kernel panic - not syncing: Asynchronous SError Interrupt ]---

로그에는 SError 익셉션이 발생한 콜 스택과 커널 패닉이 실행될 것이라는 정보를 확인할 수 있습니다.

여기까지 Armv8 아키텍처의 익셉션을 구성하는 주요 기능에 대해 소개했습니다. 각 기능이 어떤 방식으로 동작하는지 대해서는 이어지는 절에서 살펴보겠습니다. 이어지는 절에서는 전체 시스템의 관점으로 익셉션의 타입 별로 처리되는 실행 흐름을 살펴보겠습니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


>

[Arm프로세서] Armv8: 익셉션 벡터 테이블(Exception Vector Table) ARMv8: 익셉션(Exception)

Armv8 아키텍처의 익셉션 벡터 테이블을 이루는 핵심 개념은 Armv7 아키텍처의 익셉션과 같습니다. Arm 코어는 익셉션을 감지하면 익셉션의 종류 별로 지정된 주소로 프로그램 카운터를 브랜치합니다. 익셉션 벡터 테이블은 익셉션 벡터로 구성된 테이블인데, 익셉션의 종류와 익셉션의 종류 별 오프셋 주소로 구성돼 있습니다. 
 
Armv7 아키텍처의 익셉션 벡터 테이블과의 차이점

그런데 Armv8 아키텍처의 익셉션 테이블은 Armv7 아키텍처의 익셉션 테이블과 어떤 차이점이 있을까요? 이 질문에 다음과 같이 답할 수 있습니다.

   ❑ 익셉션 레벨 별로 익셉션 벡터 테이블이 구성돼 있다.
   ❑ 익셉션 벡터의 주소가 0x80 바이트 간격으로 정렬돼 있다.

첫 번째 차이점은 익셉션을 분류하는 방식입니다.
Armv7 아키텍처는 어떤 Arm 동작 모드에서 익셉션이 유발되던 익셉션 종류 별로 지정된 익셉션 벡터로 프로그램 카운터를 브랜치합니다. 하지만 Armv8 아키텍처의 익셉션 벡터 테이블은 익셉션 레벨 별로 구성돼 있습니다. 그래서 Armv8 아키텍처의 익셉션 벡터 테이블을 이해하려면 익셉션 레벨의 정확한 의미를 파악해야 합니다.

두 번째 차이점은 익셉션 벡터의 오프셋의 간격입니다.
Armv7 아키텍처는 0x4 바이트 간격으로 익셉션 벡터가 정렬돼 있습니다. 그래서 익셉션 벡터 주소의 코드는 익셉션의 종류 별로 처리하는 레이블로 브랜치하는 코드로 구성돼 있습니다. 하지만 Armv8 아키텍처에서는 0x80 바이트로 정렬돼 있어, 익셉션 벡터 주소에서 익셉션을 처리하는 명령어를 바로 실행할 수 있습니다.

익셉션 벡터를 프로그램 카운터로 브랜치하는 방식 

익셉션이 유발될 때 Arm 코어가 익셉션 벡터를 프로그램 카운터로 브랜치하는 방식은 Armv7 아키텍처와 유사합니다. Armv8 스팩 문서에서 이 내용을 조금 더 확인해봅시다.

출처: 'DDI0487Fc_armv8_arm.pdf'
01 aarch64/exceptions/takeexception/AArch64.TakeException
02 AArch64.TakeException(bits(2) target_el, ExceptionRecord exception,
                                bits(64) preferred_exception_return, integer vect_offset)
...
03 BranchTo(VBAR[]<63:11>:vect_offset<10:0>, BranchType_EXCEPTION);

[정보]
Arm 사에서 배포한 스팩 문서에서는 Arm 코어에서 하드웨어적으로 처리되는 동작을 슈도코드를 사용해 설명합니다. 위 코드는 익셉션을 유발하는 동작을 나타냅니다.

BranchTo() 함수를 호출하는 코드에서 주의 깊게 봐야 할 구문은 첫 번째 인자인데, 첫 번째 인자로 전달되는 주소로 프로그램 카운터를 브랜치합니다. 위 코드에서  VBAR[]<63:11>는 익셉션 벡터 테이블의 베이스 주소, vect_offset<10:0>는 익셉션 종류 별 오프셋 주소를 의미하는데, 'VBAR[]<63:11>:vect_offset<10:0>' 구문은 익셉션 벡터 베이스 주소에서 익셉션 종류 별 오프셋을 더한 주소를 뜻합니다.

코드 분석 내용을 정리하면 "익셉션 벡터 베이스 주소에서 익셉션 종류 별 오프셋을 더한 주소로 프로그램 카운터를 브랜치한다"라고 할 수 있습니다. 브랜치되는 주소는 다음 공식과 같습니다.

   ❑ 브랜치되는 주소(익셉션 벡터) = 익셉션 벡터 베이스 주소 + 익셉션 종류 별 오프셋

Armv8 아키텍처의 익셉션 벡터 테이블에 대한 자세한 내용은 다음 절에서 살펴볼 예정입니다. 

이어서 프로그램 카운터를 익셉션 벡터로 브랜치하면 익셉션 벡터 주소에 위치한 익셉션 핸들러가 처리되는 과정을 살펴봅시다. 

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


>

[Arm프로세서] Procedure Call Standard for the Arm 관련 자료 함수호출 규약(Call Convention)

함수 호출 규약은 arm 사에서 'Procedure Call Standard for the Arm'라고 부르는데요.
이와 관련된 Arm 아키텍처의 스팩 문서는 아래 링크에서 확인할 수 있습니다.
Arm-Architecture-ABI-rel-2020Q4.zip
https://developer.arm.com/documentation/ihi0055/latest/

만약 PDF 포멧의 문서를 내려받고 싶으면 아래 링크에 접근해 문서를 내려 받으면 됩니다.

https://github.com/ARM-software/abi-aa/releases

친절하게도 최신 버전의 문서를 올려놨네요.

---
release 2020Q4

The biggest change in this release is the addition of 10 converted specifications:

Procedure Call Standard for the Arm Architecture (aapcs32)
ELF for the Arm Architecture (aaelf32)
DWARF for the Arm Architecture (aadwarf32)
C++ ABI for the Arm Architecture (cppabi32)
Run-time ABI for the Arm Architecture (rtabi32)
DWARF for the Arm 64-bit Architecture (aadwarf64)
---

[Arm프로세서] Armv8: 레지스터 업데이트(익셉션을 이루는 기능) ARMv8: 익셉션(Exception)

Armv8 아키텍처에서 Arm 코어가 익셉션을 감지하면 특정 EL(익셉션 레벨)에서만 접근할 수 있는 레지스터를 다음과 같이 변경합니다.  

   ❑ 익셉션이 발생한 시점의 프로세서의 상태를 나타내는 PSTATE 레지스터를 SPSR_ELx 레지스터에 백업
   ❑ 익셉션이 발생한 순간에 실행된 주소 기준으로, 익셉션이 유발된 다음 명령어로 복귀할 주소를 
      ELR_ELx(익셉션 링크 레지스터) 레지스터에 백업

익셉션이 발생한 순간의 정보를 레지스터에 저장하는 이유는, 익셉션의 타입에 따라 익셉션이 발생하기 직전의 EL과 주소로 복귀하기 위해서입니다.
또한 Arm 코어는 익셉션이 발생한 세부 원인을 익셉션 신드롬 레지스터(Exception Syndrom Register: ESR_ELx)에 저장합니다. ESR_ELx 레지스터에 익셉션이 발생한 세부 원인(Cause)을 나타내는 익셉션 클래스를 저장하는데, Synchronous 유형의 익셉션이 발생했을 때 익셉션 클래스를 읽을 수 있습니다. 익셉션 클래스는 익셉션 신드롬 레지스터의 비트[31:26]에 포함돼 있습니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


>

[Arm프로세서] Armv8: 익셉션(Exception)의 유발요인 ARMv8: 익셉션(Exception)

익셉션이 동작하는 원리의 전체 맥락과 흐름을 파악하려면 익셉션을 유발하는 원인을 구체적으로 알아야 합니다. 먼저 그림의 왼쪽 아랫 부분에 있는 원은 익셉션을 유발하는 요인을 나타내는데, 그 세부 항목은 다음과 같습니다.

   ❑ 메모리 어보트: 기존 Armv7 아키텍처에서 데이터 어보트(Data Abort), 프리페치 어보트(Prefetch Abort),
        Undefined Instruction 익셉션을 유발하는 상황
   ❑ 외부 인터럽트(IRQ/FIQ)
   ❑ SW 인터럽트  

익셉션을 유발하는 첫 번째 요인은 메모리 어보트를 유발하는 명령어를 실행했을 때입니다. 
Armv7 아키텍처에서 메모리 어보트를 유발하는 명령어를 실행했을 때 익셉션이 유발하듯, Armv8 아키텍처에서도 메모리 어보트를 유발하는 명령어를 실행하면 Arm 코어는 익셉션을 유발합니다. Armv7 아키텍처에서 메모리 어보트를 유발하는 명령어를 Arm 코어가 실행하면 데이터 어보트(Data Abort), 프리페치 어보트(Prefetch Abort), Undefined Instruction 익셉셔은 유발하나, Armv8 에서는 Synchronous 익셉션을 유발합니다.

익셉션이 유발되는 두 번째 요인은 무엇일까요? 외부 하드웨어에서 유발된 인터럽트입니다. Arm 코어가 외부 하드웨어에서 유발한 감지하면 'IRQ 인터럽트'나 'FIQ(Fast IRQ) 인터럽트'  익셉션을 유발합니다. 여기서 말하는 외부 하드웨어는 휴대폰의 센서나 컴퓨터에서 사용하는 키보드나 마우스와 같은 디바이스를 의미합니다.

[정보]
Armv7와 Armv8 아키텍처에서 인터럽트 타입 익셉션을 분류하는 방식이 거의 유사한데, 이 내용은 다음 표에서 확인할 수 있습니다.


표 9.5의 왼쪽 부분은 Armv7 아키텍처에서 정의된 인터럽트 타입 'IRQ 인터럽트'와 'FIQ 인터럽트' 익셉션 별로 처리되는 인터럽트 벡터의 오프셋입니다. 표의 오른쪽 부분에 보이는 Armv8 아키텍처와 비교하면 심플해 보입니다. Armv7 아키텍처에서는 어떤 Arm 동작 모드에서 익셉션이 유발돼도 브랜치되는 익셉션 벡터 주소는 같기 때문입니다.

눈을 돌려 표 9.5의 오른쪽 부분에 있는 Armv8 아키텍처의 인터럽트 타입 익셉션들은 Armv7에 비해 뭔가 더 복잡해 보입니다. Armv8 아키텍처에서 정의된 'IRQ 인터럽트'와 'FIQ 인터럽트' 익셉션은 Armv7 아키텍처에 정의된 'IRQ 인터럽트'와 'FIQ 인터럽트' 익셉션과 같은 종류입니다. 즉, Armv7와 Armv8 아키텍처에서 정의된 인터럽트 타입 익셉션의 종류는 같습니다. 가장 큰 차이점은 "익셉션이 발생한 익셉션 레벨(ELx)에 따라 익셉션 벡터 오프셋이 다르다"라는 사실입니다.

마지막으로 익셉션이 유발되는 요인은 소프트웨어 인터럽트입니다. 용어 그대로 소프트웨어으로 유발되는 인터럽트인데, 'svc' 명령어를 실행하면 Arm 프로세서는 익셉션을 유발합니다. 

Armv7 아키텍처에서는 운영체제에서 유저 애플리케이션이 실행되는 User 모드에서 'svc' 명령어를 실행해 커널 함수가 실행되는 슈퍼바이저 모드로 진입합니다. 그런데 Armv8 아키텍처는 동작 방식이 약간 다른데, 다음과 같이 동작합니다. 

    "유저 애플리케이션이 실행되는 EL0(익셉션 레벨 0)에서 'svc' 명령어를 실행하면 
     커널 코드가 실행되는 EL1(익셉션 레벨 1)으로 진입합니다."

유저 애플리케이션에서 svc 명령어를 실행하면 커널 공간으로 진입하게 되는데, 운영체제 관점으로 이를 시스템 콜이라고 부릅니다. Armv7/Armv8 아키텍처 공통으로 유저 애플리케이션이 커널 코드가 동작하는 커널 모드로 진입하려면 'svc' 명령어를 실행해야 합니다.

그런데 Armv8 아키텍처에서는 svc 명령어를 실행하면 이를 처리하기 위한 별도의 익셉션을 유발하지 않습니다. svc 명령어를 실행하면 Synchronous 익셉션을 유발하게 되는데, 자세한 내용은 다음 절에서 설명하겠습니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


>

[리눅스커널] 로그 레벨을 업데이트하는 커밋(라즈베리 파이3) 2. 라즈베리 파이 설정

아래 패치를 반영하면 로그 레벨을 5로 수정할 수 있습니다.

diff --git a/arch/arm/boot/dts/bcm2710-rpi-3-b.dts b/arch/arm/boot/dts/bcm2710-rpi-3-b.dts
index d69d6a187e0c..27abf73922b2 100644
--- a/arch/arm/boot/dts/bcm2710-rpi-3-b.dts
+++ b/arch/arm/boot/dts/bcm2710-rpi-3-b.dts
@@ -12,7 +12,7 @@
        model = "Raspberry Pi 3 Model B";

        chosen {
-               bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 snd_bcm2835.enable_hdmi=1";
+               bootargs = "coherent_pool=1M 8250.nr_uarts=1 snd_bcm2835.enable_compat_alsa=0 loglevel=5 snd_bcm2835.enable_hdmi=1";
        };

        aliases {

[Arm프로세서] Armv8 익셉션(Exception)을 이루는 주요 개념 ARMv8: 익셉션(Exception)

Armv8 아키텍처에서 "익셉션이 발생하면 익셉션 벡터로 프로그램 카운터가 브랜치된다"라는 수준으로 익셉션을 이해하시는 분이 있습니다. 익셉션을 모르는 분보다는 낫겠지만 Armv8 익셉션의 동작 원리를 제대로 이해하고 배운 내용을 실전 프로젝트에 활용할 정도로 익혀두는 것이 바람직합니다. 그렇다면 실전 프로젝트에 활용할 정도로 익셉션을 제대로 배우려면 어떻게 해야 할까요?  익셉션과 관련된 내용을 읽을 때 다음과 같은 질문을 스스로에 던지면서 답을 찾도록 고민할 필요가 있습니다.

   ❑ 익셉션은 소프트웨어적으로 어떤 명령어가 실행될 때 유발될까?
   ❑ Arm 프로세서가 익셉션을 감지한 후 소프트웨어적으로 어떤 처리를 수행할까?
 
위에서 소개한 질문에 답을 하려면, 다음 그림에서 소개한 익셉션을 구성하는 주요 개념에 대해 파악할 필요가 있습니다. 

 
그림 9.3 Armv8 익셉션을 구성하는 주요 개념

그림 9.3은 8장에서 다룬 그림 8.1과 거의 비슷해 보입니다. Armv8 아키텍처에서 익셉션을 분류하는 방식이 Armv7 아키텍처와 다르지만, 익셉션이 처리되는 전체 흐름은 같습니다.

먼저 그림의 가운데 부분에 보이는 선에 대해서 알아봅시다. 이 선을 기준으로 윗 부분은 하드웨어적으로 Arm 코어가 익셉션을 감지하면서 처리하는 동작이고, 아랫 부분은 소프트웨어적으로 익셉션을 처리하는 과정을 나타냅니다.

그림 9.3에서 먼저 ‘유발 요인’으로 표기된 원의 내용부터 살펴봅시다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


>

[Arm프로세서] ESR_ELx(익셉션 신드롬 레지스터) 익셉션 클래스 ARMv8: 익셉션(Exception)

Armv8 익셉션에서 익셉션 신드롬 레지스터는 중요합니다. Synchronous 익셉션이나 SError 익셉션이 발생했을 때, 익셉션 신드롬 레지스터는 익셉션이 발생한 세부 원인(Reason)을 저장하기 때문입니다. 일반적인 상황에서, IRQ/FIQ 타입 익셉션이 발생하면 신드롬 레지스터는 업데이트되진 않습니다.
이번에도 Arm 스팩 문서를 보면서 익셉션 신드롬 레지스터가 무엇인지 알아봅시다. 

출처: DEN0024A_v8_architecture_PG.pdf
10.2.6 The Exception Syndrome Register
The Exception Syndrome Register, ESR_ELn, contains information which allows the exception handler to determine the reason for the exception. It is updated only for synchronous exceptions and SError.

위 내용은 ESR_ELx을 익셉션 신드롬 레지스터라고 하며, 익셉션 핸들러에서 익셉션이 유발된 세부 정보를 알 수 있는 정보를 담고 있다"라고 요약할 수 있습니다.

Arm 코어가 Synchronous 혹은 SError 익셉션을 유발하면 ESR_ELx 레지스터의 비트[31:26]에는 익셉션이 발생한 세부 원인을 저장해 줍니다. ESR_ELx 레지스터의 비트[31:26]에 저장되는 비트 패턴을 익셉션 클래스(Exception Class)라고 합니다.

다음 표는 Armv8에서 정의된 주요 익셉션 클래스의 내용을 나타냅니다.


표 9.4 Armv8에서 정의된 주요 익셉션 클래스 


[정보]
위 테이블의 가장 왼쪽 행에 보이는 익셉션 클래스의 이름은 Arm사의 개발자가 리눅스 커널 소스에 정의한 *esr_class_str[] 배열을 참고했습니다.

다음은 익셉션 클래스를 문자열로 정의한 *esr_class_str[] 배열의 선언부입니다.

https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/kernel/traps.c
static const char *esr_class_str[] = {
[0 ... ESR_ELx_EC_MAX] = "UNRECOGNIZED EC",
[ESR_ELx_EC_UNKNOWN] = "Unknown/Uncategorized",
...
[ESR_ELx_EC_IABT_LOW] = "IABT (lower EL)",
[ESR_ELx_EC_IABT_CUR] = "IABT (current EL)",
[ESR_ELx_EC_PC_ALIGN] = "PC Alignment",
[ESR_ELx_EC_DABT_LOW] = "DABT (lower EL)",
[ESR_ELx_EC_DABT_CUR] = "DABT (current EL)",

*esr_class_str[] 배열은 Armv8 스팩 문서의 내용을 기반으로 선언됐음을 알 수 있습니다.

Armv7 아키텍처에서 알고 있는 데이터 어보트, 프리패치 어보트, Undefined Instruction 익셉션은 Armv8 아키텍처에서는 익셉션 클래스(Exception Class)에서 확인할 수 있습니다. 

여기까지 Armv8 아키텍처에서 정의하는 익셉션 벡터에 대한 이해를 돕기 위해 Synchronous, Asynchronous 익셉션 타입과 익셉션 클래스를 소개했습니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자





[Arm프로세서] Armv8: Asynchronous 익셉션 타입이란 ARMv8: 익셉션(Exception)

Synchronous 익셉션은 Arm 코어가 어셈블리 명령어를 실행하는 과정에 유발됩니다. 그렇다면 Aynchronous 타입 익셉션은 무엇이고 어떻게 유발될까요? 외부 하드웨어에서 인터럽트가 발생하거나 외부 메모리에서 어보트가 발생하면 유발되는 익셉션 타입을 Aynchronous이라고 합니다.

이번에도 arm사가 배포한 스팩 문서를 보면서 Asynchronous 타입 익셉션에 대해 살펴봅시다.

출처: DEN0024A_v8_architecture_PG.pdf
10.2 Synchronous and asynchronous exceptions
An asynchronous exception is not generated by executing instructions, while the return address might not always provide details of what caused the exception. 
...
Sources of asynchronous exceptions are IRQ (normal priority interrupt), FIQ (fast interrupt) or SError (System Error).

내용은 "Asynchronous 타입 익셉션은, 소프트웨어적으로 어셈블리 명령어를 실행하는 과정이 아닌, 하드웨어 디바이스에 의해 발생한다"라고 요약 수 있습니다. Asynchronous 타입으로 분류되는 익셉션들은 다음과 같습니다

   ❑ IRQ 인터럽트
   ❑ FIQ 인터럽트
   ❑ SError (System Error)

'IRQ 인터럽트' 익셉션과 'FIQ 인터럽트' 익셉션은 어디선가 많이 본 용어 같습니다. Armv7의 익셉션의 종류 중 하나로 이해하면 되는데, IRQ는 외부 하드웨어 디바이스에서 발생하는 인터럽트를 뜻하고, FIQ는 우선 순위를 높혀 처리되는 인터럽트를 의미합니다. 

[정보]
일반적으로 FIQ는 트러스트 존과 같은 Secure OS로 처리되도록 설정하며, 대부분 운영체제의 커널은 인터럽트는 'IRQ 인터럽트' 익셉션을 활용해 처리됩니다. 'FIQ 인터럽트'는 Armv8 아키텍처에서 지원한다는 정도로 익혀둡시다. 

문장의 가장 마지막 부분에 SError(System Error)에 의해서 Asynchronous 익셉션이 발생할 수 있습니다. SError 익셉션은 IOMMU(I/O Memory Management Unit)이나 외부 메모리에서 어보트가 확인되면 유발됩니다. Arm 스팩 문서에서는 SError를 설명할 때, SError 인터럽트가 발생했다라고도 표기합니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


 


[Arm프로세서] Armv8: Synchronous 타입 익셉션 ARMv8: 익셉션(Exception)

먼저 Synchronous 타입 익셉션에 대해 먼저 알아보겠습니다. Synchronous 타입 익셉션에 대해서 자세히 살펴보기 전에 arm사에서 배포한 스팩 문서에서 Synchronous 타입 익셉션을 어떻게 설명하는지 알아보겠습니다.

출처: DEN0024A_v8_architecture_PG.pdf
10.2 Synchronous and asynchronous exceptions
In AArch64, exceptions may be either synchronous, or asynchronous. An exception is described as synchronous if it is generated as a result of execution or attempted execution of the instruction stream, and where the return address provides details of the instruction that caused it.

영어로 된 문장을 알기 쉽게 풀면 다음과 같이 요약할 수 있습니다.

   ❑ 익셉션들은 Synchronous이나 Asynchronous 유형으로 분류될 수 있다.
   ❑ Synchronous 타입 익셉션은 Arm 명령어를 실행하거나 Arm 명령어를 실행을 시도하는 과정에서 
     발생되는 익셉션이다. 

[정보]
Synchronous 타입 익셉션에는 어떤 익셉션이 있을까요? 바로 Synchronous 익셉션입니다. Synchronous 타입으로 분류되는 익셉션은 Synchronous 익셉션입니다.

쉽게 설명하면 Synchronous 타입 익셉션은 다음과 같이 정의내릴 수 있습니다. 

    “Arm 코어가 어셈블리 명령어를 실행하다가 발생하는 익셉션이다.” 

Arm 코어가 어셈블리 명령어를 실행하다가 익셉션을 유발할 상황이라고 판단하면 Synchronous 익셉션을 유발하는 것입니다. 그런데 Arm 어셈블리 명령어를 실행하다가 익셉션이 유발된다고 하면 뭔가 포괄적인 것 같습니다. Synchronous 익셉션을 유발하는 조금 더 구체적인 세부 원인은 무엇일까요?

다시 arm 스팩 문서를 보면서 Synchronous 익셉션을 유발하는 세부 원인에 대해 알아봅시다.

출처: DEN0024A_v8_architecture_PG.pdf
10.2.1 Synchronous aborts
Synchronous exceptions can occur for a number of possible reasons:
   ❑ Aborts from the MMU. For example, permission failures or memory areas marked as Access flag fault.
   ❑ SP and PC alignment checking.
   ❑ Unallocated instructions.
   ❑ Service Calls (SVCs, SMCs and HVCs).

문서의 내용으로 보아 Synchronous 익셉션이 유발되는 세부 원인은 다음과 같이 요약할 수 있습니다.

   ❑ 인스트럭션 어보트(Instruction aborts) 
   ❑ 데이터 어보트(Data Aborts) 
   ❑ 스택과 프로그램 카운터 정렬 오류(SP and PC alignment)
   ❑ 소프트웨어적인 인터럽트를 유발하는 서비스 콜 (SVCs, SMCs and HVCs)

위와 같이 Synchronous 익셉션 타입 익셉션을 유발하는 원인(Source)를 익셉션 클래스(Exception Class)라고 합니다. 

[정보]
위에서 명시된 SVCs, SMCs and HVCs는 다음 표에서 확인할 수 있습니다.


표 9.3 SVCs, SMCs and HVCs의 의미

그렇다면 익셉션 클래스는 어떤 과정을 거쳐 읽을 수 있을까요? Armv8 아키텍처에서는 EL1에서 "Synchronous 익셉션 타입" 익셉션이 발생하면 다음과 같이 동작합니다.

   ❑ 익셉션 벡터 테이블의 시작 주소 기준으로 +0x200 주소로 프로그램 카운터를 브랜치
   ❑ 익셉션 신드롬 레지스터를 읽어 익셉션 클래스를 읽음
   ❑ 익셉션 클래스 별로 후속 처리

[중요]
익셉션이 발생하면 소프트웨어 개발자가 반드시 확인해야 할 레지스터가 ESR_ELx(익셉션 신드롬 레지스터)입니다. Arm 코어가 익셉션을 감지하면 '하드웨어적으로' 익셉션이 유발된 원인을 ESR_ELx에 써주기 때문입니다.

정리하면, Arm 명령어를 소프트웨어적으로 실행하는 과정에서 발생하는 익셉션이 바로 Synchronous 익셉션입니다. 

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


>

[라즈베리파이] 64비트 라즈비안 커널: 크로스 컴파일 빌드 2. 라즈베리 파이 설정

이번 포스트에서는 64비트로 라즈비안 커널을 빌드하는 방법을 소개합니다. 먼저 라즈베이 파이3 기준으로 라즈비안 커널을 빌드하는 빌드 셸 스크립트를 소개합니다.

파일 이름: 64_rpi3_kernel_build.sh

#!/bin/bash

export PATH=$PATH:/opt/gcc-linaro-4.9.4-2017.01-x86_64_aarch64-linux-gnu/bin/
KERNEL=kernel8

echo "configure build output path"
TOP_PATH=$( cd "$(dirname "$0")" ; pwd )
OUTPUT="$TOP_PATH/out"

BUILD_LOG="$TOP_PATH/rpi_build_log.txt"

rpi_build_start_time=`date +%s`

OUTPUT_PATH=$( cd "$(dirname "$0")" ; pwd )
OUTPUT="$OUTPUT_PATH/out64"

pushd linux > /dev/null
make ARCH=arm64 O=$OUTPUT CROSS_COMPILE=aarch64-linux-gnu- bcmrpi3_defconfig -j16 2>&1

make ARCH=arm64 O=$OUTPUT CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs  -j16 2>&1 | tee $BUILD_LOG

popd > /dev/null

먼저 라즈베이 파이4 기준으로 라즈비안 커널을 빌드하는 방법을 알아보겠습니다. 
아래 빌드 셸 스크립트를 실행하면 됩니다.

파일 이름: 64_rpi4_kernel_build.sh

#!/bin/bash

export PATH=$PATH:/opt/gcc-linaro-4.9.4-2017.01-x86_64_aarch64-linux-gnu/bin/
KERNEL=kernel8

echo "configure build output path"
TOP_PATH=$( cd "$(dirname "$0")" ; pwd )
OUTPUT="$TOP_PATH/out"

BUILD_LOG="$TOP_PATH/rpi_build_log.txt"

rpi_build_start_time=`date +%s`

OUTPUT_PATH=$( cd "$(dirname "$0")" ; pwd )
OUTPUT="$OUTPUT_PATH/out64"

pushd linux > /dev/null
make ARCH=arm64 O=$OUTPUT CROSS_COMPILE=aarch64-linux-gnu- bcm2711_defconfig -j16 2>&1

make ARCH=arm64 O=$OUTPUT CROSS_COMPILE=aarch64-linux-gnu- Image modules dtbs  -j16 2>&1 | tee $BUILD_LOG

popd > /dev/null

이번 포스트에서 소개한 64비트 라즈비안 커널을 빌드하는 셸 스크립트는 아래 링크(라즈베리 파이 홈페이지)에 소개된 내용을 참고했습니다.

https://www.raspberrypi.org/documentation/linux/kernel/building.md


[Arm프로세서] Armv8 익셉션(Exception)의 종류와 분류 체계 ARMv8: 익셉션(Exception)

Armv8의 익셉션의 동작 원리는 Armv7과 크게 다르지 않습니다. Armv8 아키텍처 기반의 Arm 프로세서에서는 익셉션이 발생하면 익셉션의 종류별로 지정된 익셉션 벡터 주소로 프로그램 카운터가 브랜치합니다. 하지만 익셉션을 분류하는 방법과 체계가 Armv7 아키텍처와 다릅니다. Armv8 아키텍처는 익셉션의 종류를 체계화해서 처리하기 위해 다음과 같은 개념을 도입합니다.

   ❑ Synchronous 익셉션 타입 
   ❑ Asynchronous 익셉션 타입
   ❑ 익셉션 클래스와 신드롬 레지스터 

다음 표를 보면 Armv8 아키텍처에서 익셉션을 분류하는 체계를 알 수 있습니다.


표 9.2 Armv8 아키텍처에서 익셉션의 종류와 유발 인자 

표를 보면 익셉션의 타입을 Synchronous 와 Asynchronous으로 분류한다는 사실을 알 수 있습니다. Synchronous 타입의 익셉션은 Synchronous이고, Asynchronous 타입의 익셉션은 IRQ 인터럽트 익셉션, FIQ 인터럽트 익셉션, SError 익셉션입니다. 가장 오른쪽 행에는 익셉션이 유발된 원인이 보입니다.

[중요]
가장 오른쪽 행에는 익셉션이 유발된 원인이 보이는데, ESR_ELx(익셉션 신드롬 레지스터)의 [31:26] 비트에 담긴 익셉션 클래스로 세부적으로 익셉션이 발생한 원인을 파악할 수 있습니다. 

익셉션의 타입을 Synchronous 와 Asynchronous으로 분류하고, 익셉션 클래스라는 정보를 통해 세부적으로 익셉션이 발생한 원인을 파악할 수 있는 것입니다.

여기까지 읽으면 Armv8 아키텍처의 익셉션이 정말 복잡하고 어렵다고 느낄 수 있는데요. 겁먹을 필요는 없습니다. 여러분이 이미 익숙하게 알고 있는 Armv7의 익셉션과 비교해 설명할 예정이니까요.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자



>

[Arm프로세서] 기능적인 측면으로 Armv8 아키텍처의 특징 ARMv8: 익셉션(Exception)

이번에는 기능적인 측면에서 Armv8 익셉션의 특징에 대해 알아봅시다.

첫째, Armv8 익셉션은 고성능 컴퓨터는 물론 대용량의 데이터를 처리하는 클라우스 서버를 지원합니다. 특히 대용량 데이터를 안전하게 처리하려면 보안이 중요한데, Armv8 아키텍처에서는 다양한 방식으로 데이터를 보호할 수 있는 트러스트존과 같은 기능을 제공합니다. 또한 2개 이상의 운영체제를 구동하는 하이퍼바이저가 다양한 조건에서 유연하게 구동할 수 있는 옵션을 제공합니다. 

이런 기능들은 모두 Armv8 아키텍처의 익셉션 기반 위에서 실행되므로, Armv8 익셉션의 동작 원리에 대해 잘 알고 있어야 합니다.

둘째, Armv8 아키텍처에서 하위 호환성을 지원하기 위한 다양한 기능을 제공합니다. 특히 Armv8 익셉션 벡터 테이블은 32비트로 구동되는 애플리케이션 및 운영체제 커널까지, 32비트 하위 호환성을 지원합니다. 이런 Armv8 아키텍처 기반 위에, 이미 기존 운영체제에서 구동되는 애플리케이션을 실행할 수 있는 운영체제를 설계할 수 있습니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자



>

[Arm프로세서] Armv8 아키텍처의 익셉션 분류 체계와 익셉션 벡터 테이블 소개 ARMv8: 익셉션(Exception)

Armv7 아키텍처의 익셉션과 비교했을 때, Armv8 아키텍처에서는 익셉션을 분류하는 체계와 익셉션 벡터 테이블이 약간 다릅니다. 그 특징에 대해서 더 자세히 알아봅시다.

첫째, Armv8 아키텍처에서는 익셉션의 종류를 계층 구조로 재정의했습니다. 먼저 익셉션을 Synchronous와 Asynchronous와 같이 큰 카테고리로 분류하고, 하부 카테고리로 익셉션 클래스를 정의했습니다. Armv7 의 익셉션의 종류는 익셉션 클래스에서 확인할 수 있습니다.


표 9.1 Armv8 아키텍처에서 익셉션의 분류 체계

먼저 익셉션을 Synchronous와 Asynchronous와 같이 큰 카테고리로 분류합니다. Arm 코어가 명령어를 실행하다가 유발하는 익셉션을 Synchronous, 외부 인터럽트나 외부 메모리 어보트와 같이 외부에서 비동기적으로 유발되는 익셉션을 Asynchronous로 분류합니다. 이처럼 기존 Armv7 아키텍처의 익셉션과 비교해 익셉션을 분류하는 방식이 다릅니다.

둘째, 익셉션 레벨(EL)이란 개념을 도입해 이 기준으로 익셉션을 처리합니다. 익셉션이 유발된 익셉션 레벨 별로 익셉션 벡터 주소가 존재하며, 익셉션 레벨 별로 익셉션 링크 레지스터를 정의합니다.

다음 그림은 Armv8 아키텍처에서 익셉션 레벨 별로 익셉션 처리되는 구조를 나타냅니다.


그림 9.1 Armv8 아키텍처에서 익셉션 레벨 별로 익셉션이 처리되는 흐름

그림의 윗 부분을 먼저 보겠습니다. 유저 애플리케이션이 구동되는 EL0에서 익셉션이 발생하면 다음 순서로 처리됩니다.
  
    1. 유저 애플리케이션이 구동되는 EL0에서 익셉션이 발생
    2. EL1으로 진입
    3. EL0용 익셉션 벡터로 브랜치 

이어서 그림의 아랫 부분은 운영체제의 커널이 구동되는 EL1에서 익셉션이 발생하면 처리되는 흐름입니다. EL1에서 익셉션이 유발되면 다음 순서로 처리됩니다.

   1. 운영체제 커널이 구동되는 EL1에서 익셉션이 발생
   2. EL1용 익셉션 벡터로 브랜치

이처럼 Arm코어는 익셉션이 유발되면 익셉션이 발생한 익셉션 레벨 별로 지정된 익셉션 벡터로 프로그램 카운터를 브랜치합니다.

[정보]
EL0과 EL1은 각각 Armv7 아키텍처의 User 모드, Supervisor 모드에 대응됩니다.
Armv7 아키텍처의 User 모드에서는 유저 애플리케이션이 구동하고, Supervisor 모드에서는 운영체제의 커널이 동작합니다. 마찬가지로, Armv8 아키텍처의 EL0에서는 유저 애플리케이션이 실행되고, EL1에서는 운영체제의 커널이 동작합니다.  

Armv7 아키텍처에서는 어떤 Arm 동작 모드에서 익셉션이 유발돼도 익셉션 종류 별로 지정된 익셉션 벡터로 프로그램 카운터가 브랜치됩니다. 예를 들어 유저 애플리케이션이 실행되는 User 모드나 운영체제의 커널이 구동되는 슈퍼바이저 모드에서 데이터 어보트가 유발되면, 데이터 어보트 익셉션에 해당되는 익셉션 벡터로 프로그램 카운터가 브랜치됩니다. 

그래서 익셉션 핸들러에서 익셉션이 유발된 시점의 Arm 동작 모드를 읽어서 이를 세분화하는 명령어를 입력해야 합니다. 예를 들어, 리눅스 커널인 경우 다음 코드와 같이 Arm 동작 모드를 읽어서 세분화해 처리하는 동작을 확인할 수 있습니다. 

다음은 Armv7 아키텍처 기반 리눅스 커널에서 구현된 Undefined Instruction 익셉션 핸들러의 구현부입니다.

01 NSR:FFFF11A0|E88D4001        vector_und:   stm     r13,{r0,r14}
02 NSR:FFFF11A4|E14FE000                      mrs     r14,spsr
03 NSR:FFFF11A8|E58DE008                      str     r14,[r13,#0x8]
04 NSR:FFFF11AC|E10F0000                      mrs     r0,cpsr
05 NSR:FFFF11B0|E2200008                      eor     r0,r0,#0x8       ; r0,r0,#8
06 NSR:FFFF11B4|E16FF000                      msr     spsr_cxsf,r0
07 NSR:FFFF11B8|E20EE00F                      and     r14,r14,#0x0F    ; r14,r14,#15
08 NSR:FFFF11BC|E1A0000D                      cpy     r0,r13
09 NSR:FFFF11C0|E79FE10E                      ldr     r14,[pc,+r14,lsl #0x2]
10 NSR:FFFF11C4|E1B0F00E                      movs    pc,r14
11 NSP:FFFF11C8|C010F680                      dcd     0xC010F680       ; __und_usr
12 NSP:FFFF11CC|C010F130                      dcd     0xC010F130       ; __und_invalid
13 NSP:FFFF11D0|C010F130                      dcd     0xC010F130       ; __und_invalid
14 NSP:FFFF11D4|C010F2C0                      dcd     0xC010F2C0       ; __und_svc

02번째 줄은 spsr 레지스터를 읽어 r14 레지스터에 저장하는 명령어입니다.
r14 레지스터는 익셉션이 발생한 시점의 Arm 동작 모드가 저장돼 있는데, 이 값에 따라 다른 레이블로 브랜치하는 코드(01~10번째 줄)가 실행됩니다.

그런데 Armv8 아키텍처에서는 익셉션이 발생한 익셉션 레벨 기준으로 익셉션 벡터가 브랜치되므로, 구지 소프트웨어적으로 익셉션이 유발된 시점의 익셉션 레벨에 따라 처리할 필요가 없습니다. 

넷째, 기존 Armv7의 익셉션 벡터 주소는 워드(32비트 기준: 4바이트)로 정렬됐으나, Armv8의 경우 0x80바이트 단위로 정렬됐습니다. 익셉션 벡터 주소에서 익셉션를 바로 처리할 수 있는 명령어를 실행할 수 있습니다.

다음은 Arm 사에서 배포한 문서에 담긴 익셉션 벡터 테이블입니다.
 

그림 9.2 Armv8 아키텍처의 익셉션 벡터 테이블

위 그림에서 박스로 표기된 부분을 보면 0x000, 0x080, 0x100, 0x180이 보이는데 이는 익셉션의 종류 별 익셉션 벡터 오프셋을 나타냅니다. 이처럼 Armv8 아키텍처는 익셉션의 종류 별 오프셋 주소의 사이즈가 0x080 바이트입니다.

[정보]
익셉션 벡터 베이스 주소에 익셉션 벡터 오프셋 주소를 더한 주소로 프로그램 카운터가 브랜치됩니다.


Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


>

[리눅스] LK(Little Kernel): 전처리 파일을 위한 설정(--save-temps) Linux Kernel - Core Analysis

LK(Little Kernel)에서 전처리 파일을 추출하기 위해서 아래 패치 코드를 반영하면 됩니다.

diff --git a/engine.mk b/engine.mk
index 74f289c..51ff0f1 100644
--- a/engine.mk
+++ b/engine.mk
@@ -204,7 +204,7 @@ endif

 # default to no ccache
 CCACHE ?=
-CC := $(CCACHE) $(TOOLCHAIN_PREFIX)${TOOLCHAIN}
+CC := $(CCACHE) $(TOOLCHAIN_PREFIX)${TOOLCHAIN} --save-temps
 LD := $(TOOLCHAIN_PREFIX)ld
 OBJDUMP := $(TOOLCHAIN_PREFIX)objdump
 OBJCOPY := $(TOOLCHAIN_PREFIX)objcopy


[IT 에세이] 인센티브에 양과 같이, 앞을 못보는 소프트웨어 개발자들 임베디드 에세이

연말이 되면 직장인을 포함해, 많은 개발자들은 자신들이 얼마나 많은 인센티브를 받을 지 기대합니다. "연봉의 몇 프로를 받을 것 같다"와 같은 이야기를 하죠. 하지만 이건 카메라 앵글 중에 밝은 부분만을 포커스로 잡은 것이라 볼 수 있습니다.

사실, 많은 임원들이 12월달에 짤립니다. 자기는 당연히 연장(물론 계약이죠)될 걸로 믿었던 임원들이 집에 갑니다. 또한 회사의 오너가 쓰는 펜 끝에서 수 십명에서 수 백명의 인원이 체스판 말들처럼 옮겨집니다. 이 인원들에게 일자리가 있으면 다행이긴 하지만 아닌 경우도 상당합니다.

이런 상황에서 직장인이나 개발자 분들은 동료에게 카톡으로 인센티브 몇 퍼센트 받았냐고 물어봅니다. 예상보다 적게 받으면 서로를 위로해주고 회사를 욕하기도 하죠. 이런 글을 읽다가 파울로 코엘료의 '연금술사'라는 책에서 현재 상황을 투영하는 부분이 떠오르네요.

바로 주인공인 산티아고가 자신이 기르는 양들의 모습을 보면서 드는 생각을 언급한 부분인데요.
양들은 단지 맛있는 먹이와 물만 제대로 제공하면, 자기 동료 양들이 털이 깎이고 도축되는 걸 보고도, 그 최악의 상황이 자기 차례가 될 때까지, 그저 물과 먹이에만 만족하도록 길들여져 있다는 겁니다.

물론 1년 동안 소속된 조직의 기준에 맞게 일을 잘해서 좋은 평가를 받고, 인센티브를 연봉의 몇 퍼센트라도 더 받은 직장인이나 개발자 분들에게 "축하드립니다"라고 말씀드리고 싶어요. 하지만 정말 중요한 건 이런게 아닌 것 같아요. 

정말로 정말로 무서운건; 

   소속된 조직의 매니저나 평가자들의 눈에 들지 못해, 인센티브를 더 받지 못하는 게 아니라, 
   이 양들처럼 길들여져서 주위 동료들 짤려나가는 순간까지도 자기 몫의 먹이(인센티브)에만 시선이 고정되는 겁니다.

어쩌면 그게 조직의 의도일 수 있다고 봅니다. 자신의 캐리어를 멀리 내다 보지 못하고, 양들이 자신이 먹을 먹이에 길들여져 있는 것 처럼 코 앞의 연봉과 인센티브에 연연하게 만드는 것이죠.

이번에는 조금 더 다크한 부분으로 카메라의 앵글을 비춰 볼까요?

조직에서 평가자의 입장에 있는 매니저들은 연말 평가 기간이 되면 자신에게 무슨 막강한 권한이 있는 듯 착각하곤 합니다.  연말 평가와 관련된 면담을 할 때 자신이 무슨 판결문을 낭독하는 판사가 된 듯한 착각에 빠지면서 다음과 같은 훈시를 합니다.

    * 1년 동안 아무것도 한 일이 없네! 
    * Jira에 있는 로그 분석은 거의 쓰레기 수준이야.

자신이 살아남기 위해, 혹은 자신의 성과를 위해, 구성원들을 필요 이상으로 위협하고 억압하는 것이죠. 
그래서 매니저들은 오로지 자신의 성과를 위한 상황과 수준에 맞게 개발자들을 몰아갑니다. 양들을 몰고 먹이를 주듯이 말이죠. 

하지만 현실은;

    * 말 잘 듣는 개발자를 이용해먹고, 상황 안 좋아지면 가차없이 내칩니다!

왜냐면 평가자의 입장에 있는 매니저들도 언제 짤릴 지 모르는 처지기 때문이죠. 개발자를 억압하는 매니저들도 자신의 상사 앞에서는 개처럼 비벼대고 굽신거릴 수 밖에 없는 입장입니다.

가끔 평가를 받는 개발자나 직장인들은 이런 알량한 평가 지표(오로지 매니저의 성과를 위한)를 위해 동료들끼리 편가르고, 질시하기도 합니다. 이런 걸 매니저들이 조장하기도 하는데요. 이런 분들에게 다음과 같이 말씀드리고 싶어요.

   "조직 생활이 마치 인생의 전부인 양 길들여지길 바라는 조직의 울타리나 프레임에 갇히지말고, 자신의 인생에서 
    자신이 주인공이 되는 삶을 사는 것은 어떨까요?"  

자신이 속한 조직이나 회사가, 여러분의 목을 쥐고 흔드는 상황이 되기 전에, 스스로 언제든 마음만 먹으면 회사를 떠날 수 있는 준비를 해보는 것도 좋을 것 같아요. 인센티브 몇 퍼센트에 너무 집착하지 않고, 스스로 자신의 캐리어를 설계하고 독립하는, 어디에서도 인정 받을 수 있는 능력을 키우는 데 더 많은 시간을 투자하는 게 좋지 않을까요?

[Arm프로세서] Armv8 익셉션(Exception)의 전반적인 특징 ARMv8: 익셉션(Exception)

그 동안 Arm 아키텍처는 CPU 아키텍처 시장에서 '저전력', '소형 디바이스' 용으로 사용되는 CPU 아키텍처로 분류됐습니다. 최대한 적은 트렌지스터를 사용해 최소의 소모 전력으로 CPU를 설계했기 때문입니다. 물론 Arm 코어의 CPU 아키텍처도 최대한 심플하게 디자인했습니다.

익셉션의 구조 관점으로 보면, Armv7 아키텍처의 익셉션도 다음과 같이 심플하게 설계됐다고 볼 수 있습니다. 

   ❑ 익셉션 벡터가 4바이트 단위로 정렬
   ❑ 어떤 Arm 동작 모드에서 익셉션이 발생해도 익셉션 벡터로 브랜치
   ❑ 익셉션의 종류도 8개로 정의 내림

그런데, Armv8 아키텍처는 CPU 아키텍처 시장에 "우리는 고성능 컴퓨터나 클라우드 서버에 진출하겠다"라고 출사표를 던지고 설계한 것으로 보입니다. 기존의 소형 저전력 디바이스 용으로 설계된 CPU 아키텍처가 아니라 인텔의 x86과 경쟁할 수 있는 CPU 아키텍처를 설계한 것입니다.  

이런 면모는 Armv8 아키텍처의 익셉션에서 볼 수 있습니다. 이제 Armv8 아키텍처의 익셉션의 주요 특징을 Armv7 익셉션과 비교하면서 알아보겠습니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자


---

[Arm프로세서] Armv8 아키텍처의 익셉션 소개 ARMv8: 익셉션(Exception)

많은 분들이 생각하는 익셉션의 개념은 'Armv7 아키텍처의 익셉션'인 경우가 많아, Armv8 아키텍처를 배울 때 Armv7 아키텍처의 익셉션과 비슷할 것이라 예상합니다. 익셉션이 발생하면 지정한 주소로 프로그램 카운터를 브랜치하는 기본 개념은 Armv7/Armv8 아키텍처가 같으나, 익셉션을 처리하고 분류하는 체계와 세세한 처리 방식이 많이 다릅니다.

Armv8 아키텍처의 익셉션은 고성능 컴퓨터에서 적용되는 트러스트 존이나 가상화 시스템인 하이퍼바이저를 이해하기 위해 반드시 알아야 할 기반 지식이므로, 반드시 잘 알아야 둬야 합니다. 

익셉션(Exception)은 Armv8 아키텍처의 핵심 기능 중 하나입니다. Armv8 아키텍처에서 정의된 익셉션를 활용해 하이퍼바이저와 같은 가상 시스템을 설계할 수 있습니다. 또한 트러스트 존을 제대로 이해하려면 먼저 익셉션의 동작 방식을 알아야 합니다. 

Armv8 아키텍처에서 익셉션은 어떻게 정의내릴 수 있을까요? 기존에 소개된 Armv8 아키텍처의 익셉션과는 어떤 차이점이 있을까요? Armv8 아키텍처에서는 기존 Armv7 아키텍처와 비교해 익셉션의 종류를 분류하는 방식이 조금 다르지만, 익셉션의 동작 원리는 동일합니다. Armv8 아키텍처의 익셉션은 다음과 같이 설명할 수 있습니다.

    “익셉션이란 Arm 코어가 명령어를 처리하다가 예외 사항이 발생할 때 이를 처리하는 
    방식이다. 익셉션이 발생하면 익셉션 벡터로 프로그램 카운터가 브랜치된다.”

위 문장을 읽으면 "어, Armv8 아키텍처의 익셉션은 Armv7 익셉션과 거의 같네"라는 생각이 들수 있습니다. 하지만 Armv8 아키텍처의 익셉션은 Armv7 익셉션과 비교했을 때 기본 개념은 같지만 익셉션을 분류하고 처리하는 방식이 다릅니다.

이제부터 Armv8 아키텍처의 익셉션에 대해서 배워 봅시다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자




[Arm프로세서] Armv7: 데이터 어보트(Data Abort) 익셉션이 발생하는 사례 ARMv7: 익셉션(Exception)

데이터 어보트는 명령어를 실행할 수 없은 상황에서 ARM 코어가 유발합니다. 그렇다면, 다음 명령어의 01번째 줄에서 데이터 어보트가 발생할 수 있을까요?

01 0xc000d000:  e5925000 ldr  r5, [r2]
02 0xc000d004:  e2855004 add r5, r5, #0x4
03 0xc000d008:  e3550000 cmp r5, #0x0

만약 r2 레지스터가 0x0과 같은 주소를 담고 있으면, 데이터 어보트가 유발될 수 있습니다. 'ldr  r5, [r2]' 명령어는 r2 레지스터가 담고 있는 주소에 엑세스해서 해당 주소에 있는 데이터를 r5 레지스터에 로딩하는 동작입니다. 그런데 r2 레지스터가 0x0을 담고 있으면, 유효하지 않는 메모리로 판단하므로 ARM 코어는 데이터 어보트를 유발하게 됩니다. 

시스템 소프트웨어 개발자 입장에서 메모리 어보트 타입 익셉션이 언제, 어느 상황에서 발생하는지 더 말씀드리고 싶지만, 이 책의 범위를 벗어나므로 이 정도로 정리하겠습니다. 어떤 이슈를 만나더라도 ARM 아키텍처의 익셉션의 동작 원리를 실전 프로젝트에 활용할 정도로 익혀둔 상태면 그리 당황하지 않고 대응할 것입니다.


Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자



[Arm프로세서] Armv7: 프리페치 어보트 익셉션이 발생하는 사례 ARMv7: 익셉션(Exception)

프리페치 어보트는 말 그대로 명령어를 페치하지 못할 때 ARM 코어가 유발합니다. 그렇다면, 다음 명령어의 01번째 줄에서 프리페치 어보트가 발생할 수 있을까요?

01 0xc000d000:  e5925000 ldr  r5, [r2]
02 0xc000d004:  e2855004 add r5, r5, #0x4
03 0xc000d008:  e3550000 cmp r5, #0x0

'ldr  r5, [r2]' 명령어를 페치하는데 큰 문제는 없는 상황입니다. 역시, 01번째 줄도 파이프 라인의 어느 단계에서 익셉션이 발생하는지에 대한 이해를 돕기 위해 예를 든 것입니다.

그래서 이번에는 다른 예시를 들겠습니다. 프로세스의 스택이 오염됐을 때 프리페치 어보트가 발생하는 경우가 많은데, 다음은 프리페치 어보트가 발생할 때의 로그입니다. 

[ 198.237032 / Unhandled prefetch abort: section translation fault (0x005) at 0x00000000
[ 198.237058 / 11-12 17:02:15.213][1] PC is at __hyp_idmap_text_end+0x768/0xb58
[ 198.237082 / 11-12 17:02:15.213][1] LR is at unix_dgram_recvmsg+0x90/0x3d8
[ 198.237099 / 11-12 17:02:15.213][1] pc : [<00000016>] lr : [<c0c744e0>] psr: 600f0013
[ 198.237099 / 11-12 17:02:15.213][1] sp : ea411d70 ip : c0c74450 fp : ea9741dc
[ 198.237122 / 11-12 17:02:15.213][1] r10: c414db80 r9 : 00000001 r8 : 00000251
[ 198.237125 / 11-12 17:02:15.213][1]r7 : 00000000 r6 : 00000000 r5 : c015517c r4 : 00000000
[ 198.237167 / 11-12 17:02:15.213][1]r3 : 00000000 r2 : 00000001 r1 : c130fb80 r0 : 00000000
[ 198.237176 / 01-12 17:02:15.213][1] Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment user
[ 198.237182 / 01-12 17:02:15.213][1] Control: 10c0383d Table: 6a97806a DAC: 00000015
[ 198.237197 / 01-12 17:02:15.213][1] Process logd.writer (pid: 418, stack limit = 0xea410238)

프로세스는 특정 함수를 호출할 때 스택 주소와 링크 레지스터를 스택 공간에 저장(푸시)합니다. 그런데, 프로세스의 스택 공간이 오염되면 잘못된 주소로 프로그램 카운터가 브랜치가 되고, 결과 위와 같은 로그와 같이 프리페치 어보트가 발생할 수 있습니다.



Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자



[Arm프로세서] Armv7: Undefined Instruction 익셉션이 발생하는 사례 ARMv7: 익셉션(Exception)

Undefined Instruction 익셉션은 ARM 코어에서 명령어를 디코딩하는 단계에서 유발됩니다. 다음 명령어의 1번째 줄을  실행할 때, ARM 코어는 Undefined Instruction 익셉션을 왜 발생할까요? 

       주소             명령어
01 0xc000d000  ldr  r5,[r2]
02 0xc000d004  add r5, r5, #0x4
03 0xc000d008  cmp r5, #0x0

01~03번째 줄에는 분명히 우리가 디코딩(해석)할 수 있는 명령어입니다. 그런데 ARM 코어는 위와 같은 명령어를 왜 디코딩하지 못할까요? 그 이유에 대해서 더 알아봅시다.

실전 프로젝트에서 Undefined Instruction 익셉션은 크게 다음과 같은 상황에서 유발됩니다.

    * 메모리 비트 플립이나 잘못된 메모리 복사로 기계어가 오염됐을 때
    * 코프로세서가 없거나 활성화를 하지 않았는데, 코프로세서를 설정하는 명령어를 실행했을 때 

이해를 돕기 위해 명령어와 기계어로 같이 표기한 01~03번째 줄 명령어를 봅시다. 

01 0xc000d000:  e5925000 ldr  r5, [r2]
02 0xc000d004:  e2855004 add r5, r5, #0x4
03 0xc000d008:  e3550000 cmp r5, #0x0

01번째 줄에 있는 'ldr  r5, [r2]' 명령어에 대응되는 기계어가 0xe5925000입니다.
그런데, 만약 사용하고 있는 메모리가 불량이거나 다른 코드에서 잘못된 메모리 복사를 수행하는 코드가 수행되면, 01번째 줄의 기계어와 명령어가 다음과 같이 바뀔 수 있습니다.

01 0xc000d000:  87dcb159    undef   0x87dcb159
02 0xc000d004:  e2855004 add r5, r5, #0x4
03 0xc000d008:  e3550000 cmp r5, #0x0

ARM 코어는 0x87dcb159이란 기계어를 디코딩을 하지 못하니, Undefined Instruction 익셉션을 유발하는 것입니다.

Undefined Instruction 익셉션은 소프트웨어적인 관점으로만 그 원인을 분석하면 답을 못찾는 경우가 많습니다. 다음과 같은 질문을 던지면서 차근 차근 원인을 찾아 가야 합니다.

   * Undefined Instruction이 발생하는 패턴은 일정한가?
   * 내려받은 이미지가 제대로 빌드가 됐는가?
   * 메모리(RAM)에 전달되는 전원에는 이상이 없는가?

위 질문을 스스로 던져보고 조금만 고민하면 빨리 답을 찾을 수 있습니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자







[Arm프로세서] Armv7: 익셉션 핸들러란 ARMv7: 익셉션(Exception)

처음 익셉션 벡터 테이블을 분석하면 다음과 같은 의문이 생기는 경우가 많습니다.  

    "이와 관련된 코드는 어디에 구현돼 있을까?"

이 질문에 답을 하려면 익셉션 벡터와 관련된 코드가 무엇인지 알아야 하는데, 이를 익셉션 벡터 핸들러라고 합니다. 익셉션 벡터 핸들러는 익셉션의 종류 별로 소프트웨어적으로 처리되는 목적의 명령어로 구성돼 있는데 익셉션 벡터 주소에서 브랜치됩니다.

익셉션 핸들러 코드 분석하기

이제부터 오픈 소스 기반의 운영체제인 리눅스 커널에서 확인된 익셉션 벡터와 익셉션 백터 핸들러의 코드를 보면서 배운 내용을 다져봅시다. 다음은 익셉션 벡터와 익셉션 핸들러를 브랜치하는 코드입니다.

01 ffff0000:   ea0003ff   b       0xffff1004 <vector_rst>
02 ffff0004:   ea000465   b       0xffff11a0 <vector_und>
03 ffff0008:   e59ffff0   ldr     pc, [pc, #4080] ; 0xffff1000 // <vector_swi>
04 ffff000c:   ea000443   b       0xffff1120 <vector_pabt>
05 ffff0010:   ea000422   b       0xffff10a0 <vector_dabt>
06 ffff0014:   ea000481   b       0xffff1220 <vector_addrexcptn>
07 ffff0018:   ea000400   b       0xffff1020 <vector_irq>
08 ffff001c:   ea000487   b       0xffff1240 <vector_fiq>

리눅스 커널에서는 익셉션 벡터 테이블의 베이스 주소를 ffff0000로 지정합니다. 그렇다면 위 코드에서 익셉션 벡터는 무엇일까요? ffff0000~ffff001c 구간의 주소가 익셉션 벡터이며, 4바이트 단위로 정렬된 것을 볼 수 있습니다. 

위 코드를 ARM 아키텍처 관점으로 다음과 같이 설명할 수 있습니다.

    "ARM 코어가 익셉션을 유발하면 ffff0000~ffff001c 구간의 주소로 
    프로그램 카운터를 브랜치한다."

물론 이 동작은 ARM 코어에서 하드웨어적으로 처리됩니다.

이번에는 메모리 어보트 타입 익셉션이 발생하면 어느 익셉션 벡터로 프로그램 카운터가 브랜치되는지 알아봅시다. 먼저 다음은 익셉션 벡터 테이블에서 봤던 오프셋 주소 정보입니다.

   * Undefined Instruction: +0x04(0xffff_0004)
   * 프리페치 어보트: +0x0C(0xffff_000C)
   * 데이터 어보트:  +0x10(0xffff_0010)

메모리 어보트 타입 익셉션이 발생하면 어느 익셉션 벡터로 프로그램 카운터가 브랜치되는지 알아봅시다.

Undefined Instruction 익셉션이 유발되면, 익셉션 벡터 베이스 주소(0xffff_0000)에서 오프셋인 0x04를 더해 0xffff_0004 주소로 프로그램 카운터가 브랜치됩니다. 프리페치 어보트와 데이터 어보트도 같은 규칙에 따라, 0xffff_000C, 0xffff_0010 주소로 프로그램 카운터가 브랜치됩니다. 

그런데 ARM 코어가 프로그램 카운터를 특정 주소로 브랜치하면 해당 주소에 위치한 명령어가 실행됩니다. 즉, 익셉션의 종류 별로 다음과 같은 어셈블리 명령어가 실행됩니다.

   * Undefined Instruction: "b vector_und"(0xffff_0004)
   * 프리페치 어보트: "b vector_pabt" (0xffff_000C)
   * 데이터 어보트: "b vector_dabt"    (0xffff_0010)

ARM 어셈블리 명령어로 ‘b’는 지정한 주소나 레이블로 브랜치하는 동작입니다. “b vector_und” 명령어가 실행되면 vector_und이란 레이블로 브랜치됩니다. 프리페치 어보트와 데이터 어보트도 같은 원리로 ‘vector_pabt’와 ‘vector_dabt’ 레이블로 브랜치됩니다.

익셉션 벡터와 익셉션 핸들러를 브랜치하는 코드 분석하기

이번에는 익셉션의 종류별로 익셉션 벡터와 해당 익셉션 벡터에 위치한 코드를 표로 정리해봅시다.
표 8.5 익셉션 벡터와 익셉션 핸들러

표 8.5와 같이 익셉션 벡터를 처리할 수 있는 주소 공간이 4바이트이므로, 익셉션 벡터 주소에는 vector_rst부터 vector_fiq까지 익셉션의 종류 별로 처리하는 레이블을 브랜치하는 코드로 구성돼 있습니다. 사실 익셉션의 종류 별로 후속 처리하기 위해서는 다양한 명령어가 수행되야 하는데, 해당 코드는 vector_rst부터 vector_fiq 레이블에 위치해 있습니다.


Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자



[Arm프로세서] Armv7: 익셉션 벡터 테이블이란 ARMv7: 익셉션(Exception)

익셉션이 발생하면 ARM 프로세서는 익셉션 별로 지정된 주소로 프로그램 카운터를 브랜치합니다. 익셉션 별로 지정된 주소를 익셉션 벡터라고 하는데, 익셉션 벡터는 8개의 연속된 4바이트 단위의 주소로 구성돼 있습니다. 이 8개의 익셉션 벡터를 익셉션 벡터 테이블이라고 합니다. 

먼저 다음 표를 보면서 익셉션 벡터 테이블을 배워 봅시다.
 
표 8.4 익셉션 벡터 테이블

표의 왼쪽 행은 익셉션 벡터 베이스 주소 기준의 오프셋인데, 오른쪽 행을 보면 오프셋 별에 대응되는 익셉션의 종류가 보입니다. 이로써, 익셉션 벡터의 주소는 익셉션의 종류에 따라 다르다는 점을 확인할 수 있습니다. 

가장 왼쪽 행의 윗 부분은 '익셉션 벡터 테이블의 베이스' 주소인데, 먼저 이 내용부터 알아봅시다. ARM 프로세서는 익셉션의 종류 별로 지정된 주소로 프로그램 카운터를 브랜치하기 위해 먼저 익셉션 벡터 테이블의 베이스 주소를 찾습니다.

익셉션 벡터의 베이스 주소는 VBAR(Vector Base Address Register) 레지스터를 통해 설정하며, 0x0과 0xffff_0000로 지정할 수 있습니다. 익셉션 벡터의 베이스 주소는 다음과 같이 분류될 수 있습니다.

   * 노멀 벡터: 0x0
   * 하이 벡터: 0xffff_0000

이어서 왼쪽 열은 0x0~0x1C의 주소가 보이는데, 이는 익셉션 벡터 테이블의 베이스 주소 기준의 오프셋을 나타냅니다.

익셉션 벡터의 주소는 "익셉션 벡터 베이스 주소"에서 익셉션 종류 별로 명시된 오프셋 주소를 더해 계산합니다. 만약 "익셉션 벡터 베이스 주소"가 0xffff0000인데, '데이터 어보트'란 익셉션이 발생하면 ARM 프로세서는 프로그램 카운터를 다음 공식에 따라 0xffff0010 주소로 브랜치합니다.

   * “0xffff0000 + 0x10 = 익셉션 벡터 베이스 주소 + 데이터 어보트의 오프셋 주소” 

만약 "IRQ" 익셉션이 발생하면 ARM 프로세서는 어떤 주소로 프로그램 카운터를 브랜치할까요? 다음 공식에 따라 0xffff_0018 주소로 브랜치됩니다.

    “0xffff0000 + 0x18 = 익셉션 벡터 베이스 주소 + IRQ 익셉션의 오프셋 주소” 

이 방식으로 ARM 프로세서는 익셉션이 발생하면 익셉션 종류 별로 지정된 주소로 프로그램 카운터를 브랜치합니다.

이번에는 다음 그림을 보면서 익셉션의 타입별로 브랜치되는 익셉션 벡터 주소를 알아봅시다.

 

그림 8.13 익셉션의 타입 별로 브랜치되는 익셉션 벡터 주소

그림의 가장 윗 부분은 Undefined Instruction, 프리페치 어보트, 데이터 어보트와 같은 메모리 어보트 타입 익셉션을 나타냅니다. 익셉션의 종류에 따라 익셉션 벡터 베이스 주소에 다음과 같은 오프셋을 더해 프로그램 카운터를 브랜치합니다.

   * Undefined Instruction: +0x04(0xffff_0004)
   * 프리페치 어보트: +0x0C(0xffff_000C)
   * 데이터 어보트:  +0x10(0xffff_0010)

괄호에 표기된 주소는 익셉션 벡터 베이스 주소가 0xffff_0000일 때 브랜치되는 주소입니다.

그림의 가운데 부분은 인터럽트 타입 익셉션을 나타냅니다. 익셉션의 종류에 따라 익셉션 벡터 베이스 주소에 다음과 같은 오프셋을 더해 프로그램 카운터를 브랜치합니다.

   * IRQ 인터럽트: +0x18(0xffff_0018)
   * FIQ 인터럽트: +0x1C(0xffff_001C)

마지막으로, 가장 아랫부분을 보겠습니다. 소프트웨어 인터럽트를 유발할 때 브랜치되는 주소를 나타내는데, 아래와 같이 프로그램 카운터가 브랜치됩니다.

  * 소프트웨어 인터럽트: +0x08(0xffff_0008)

여기까지 ARM 코어가 익셉션을 유발할 때의 동작 원리를 알 수 있는 익셉션 벡터 테이블에 대해 알아봤습니다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자









[Armv8] SError 익셉션 디버깅 시그니처 - 리눅스 커널 ARMv8: 익셉션(Exception)

SError 익셉션에 대한 디버깅 시그니처를 모아 봤습니다.

[  372.386749]  panic+0x260/0x2ac
[  372.386754]  __stack_chk_fail+0x0/0x28
[  372.386756]  arm64_serror_panic+0x80/0x90
[  372.386759]  do_serror+0xdc/0xe0
[  372.386762]  el0_error_naked+0x10/0x18
[  372.386764] irq event stamp: 2035
[  372.386770] hardirqs last  enabled at (2035): [<ffff0000082a857c>] handle_mm_fault+0x22c/0x320
[  372.386774] hardirqs last disabled at (2034): [<ffff0000082a842c>] handle_mm_fault+0xdc/0x320
[  372.386779] softirqs last  enabled at (1858): [<ffff0000080879f8>] fpsimd_restore_current_state+0x38/0x78
[  372.386783] softirqs last disabled at (1856): [<ffff0000080879d8>] fpsimd_restore_current_state+0x18/0x78
[  372.386785] ---[ end trace 383dd3590c25ba7c ]--- 

[  192.082927] Kernel panic - not syncing: Asynchronous SError Interrupt
[  192.082928] CPU: 4 PID: 2424 Comm: devmem Not tainted 4.17.0-02102-gdcfa25a-dirty #109
[  192.082929] Hardware name: Stingray Combo SVK (BCM958742K) (DT)
[  192.082930] Call trace:
[  192.082939]  dump_backtrace+0x0/0x1b8
[  192.082941]  show_stack+0x14/0x1c
[  192.082943]  dump_stack+0x90/0xb0
[  192.082945]  panic+0x140/0x2a8
[  192.082946]  __stack_chk_fail+0x0/0x18
[  192.082947]  arm64_serror_panic+0x74/0x80
[  192.082948]  do_serror+0x48/0xa0
[  192.082949]  el0_error_naked+0x10/0x18
[  192.082954] SMP: stopping secondary CPUs
[  192.082957] Kernel Offset: disabled
[  192.082959] CPU features: 0x21806008
[  192.082959] Memory Limit: none


[  209.234792] SError Interrupt on CPU4, code 0xbf000000 -- SError
[  209.234796] CPU: 4 PID: 829 Comm: dmidecode Not tainted 4.19.0-2-arm64 #1 Debian 4.19.16-1
[  209.234798] Hardware name: Theobroma Systems RK3399-Q7 SoM (DT)
...
[  209.234881] Kernel panic - not syncing: Asynchronous SError Interrupt
[  209.234884] CPU: 4 PID: 829 Comm: dmidecode Not tainted 4.19.0-2-arm64 #1 Debian 4.19.16-1
[  209.234886] Hardware name: Theobroma Systems RK3399-Q7 SoM (DT)
[  209.234888] Call trace:
[  209.234890]  dump_backtrace+0x0/0x180
[  209.234892]  show_stack+0x24/0x30
[  209.234894]  dump_stack+0x90/0xb4
[  209.234895]  panic+0x128/0x290
[  209.234897]  nmi_panic+0x7c/0x80
[  209.234899]  arm64_serror_panic+0x80/0x8c
[  209.234901]  is_valid_bugaddr+0x0/0x1c
[  209.234903]  el0_error_naked+0x10/0x18
[  209.234935] SMP: stopping secondary CPUs
[  209.234937] Kernel Offset: disabled

[    3.562109] Kernel panic - not syncing: Asynchronous SError Interrupt
[    3.562111] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 5.4.3 #1
[    3.562112] Hardware name: LS1043A RDB Board (DT)
[    3.562113] Call trace:
[    3.562114]  dump_backtrace+0x0/0x150
[    3.562115]  show_stack+0x14/0x20
[    3.562116]  dump_stack+0xbc/0x100
[    3.562117]  panic+0x16c/0x37c
[    3.562118]  __stack_chk_fail+0x0/0x18
[    3.562119]  arm64_serror_panic+0x74/0x88
[    3.562120]  do_serror+0x70/0x138
[    3.562121]  el1_error+0x84/0xf8

[    0.000000] Kernel panic - not syncing: Asynchronous SError Interrupt
[    0.000000] CPU: 0 PID: 0 Comm: swapper/0 Not tainted 5.4.0-xilinx-v2020.1 #1
[    0.000000] Hardware name: Avnet Ultra96 Rev1 (DT)
[    0.000000] Call trace:
[    0.000000]  dump_backtrace+0x0/0x140
[    0.000000]  show_stack+0x14/0x20
[    0.000000]  dump_stack+0xac/0xd0
[    0.000000]  panic+0x140/0x2f8
[    0.000000]  __stack_chk_fail+0x0/0x18
[    0.000000]  arm64_serror_panic+0x74/0x80
[    0.000000]  do_serror+0x114/0x118
[    0.000000]  el1_error+0x84/0xf8
[    0.000000]  xintc_read.isra.0+0x0/0x48
[    0.000000]  xilinx_intc_of_init+0x258/0x30c
[    0.000000]  of_irq_init+0x184/0x2e4
[    0.000000]  irqchip_init+0x14/0x1c
[    0.000000]  init_IRQ+0xe8/0x118
[    0.000000]  start_kernel+0x26c/0x42c

[ 198.344203] 000: Kernel panic - not syncing: Asynchronous SError Interruptd+
[ 198.351886] 000: CPU: 0 PID: 408 Comm: xz Not tainted 5.4.40-rt24 #35
[ 198.359080] 000: Hardware name: Orange Pi RK3399 Board (DT)
[ 198.365303] 000: Call trace:
[ 198.368504] 000: dump_backtrace+0x0/0x140
[ 198.373078] 000: show_stack+0x14/0x20
[ 198.377261] 000: dump_stack+0xbc/0x100
[ 198.381542] 000: panic+0x160/0x324
[ 198.385435] 000: nmi_panic+0x60/0x90
[ 198.389521] 000: arm64_serror_panic+0x74/0x80
[ 198.394481] 000: do_serror+0x7c/0x130
[ 198.398664] 000: el1_error+0x84/0xf8
[ 198.402751] 000: __arch_copy_from_user+0x1f4/0x240
[ 198.408195] 000: copy_page_from_iter+0xdc/0x2b0
[ 198.413351] 000: pipe_write+0x204/0x448
[ 198.417731] 000: new_sync_write+0x100/0x180
[ 198.422498] 000: __vfs_write+0x2c/0x40
[ 198.426770] 000: vfs_write+0xb0/0x1d0
[ 198.430954] 000: ksys_write+0x64/0xe8
[ 198.435137] 000: __arm64_sys_write+0x18/0x20
[ 198.440000] 000: el0_svc_common.constprop.1+0x7c/0xe8
[ 198.445739] 000: el0_svc_handler+0x20/0x80
[ 198.450408] 000: el0_svc+0x8/0xc
[ 198.454107] 000: SMP: stopping secondary CPUs

[    0.807808] Kernel panic - not syncing: Asynchronous SError Interrupt
[    0.807809] CPU: 2 PID: 1 Comm: swapper/0 Not tainted 5.9.0-rc5-next-20200914-00001-gf965d3ec86fa #67
[    0.807810] Hardware name: LS1046A RDB Board (DT)
[    0.807811] Call trace:
[    0.807812]  dump_backtrace+0x0/0x1c0
[    0.807813]  show_stack+0x18/0x28
[    0.807814]  dump_stack+0xd8/0x134
[    0.807814]  panic+0x180/0x398
[    0.807815]  add_taint+0x0/0xb0
[    0.807816]  arm64_serror_panic+0x78/0x88
[    0.807817]  do_serror+0x68/0x180
[    0.807818]  el1_error+0x84/0x100
[    0.807818]  pci_generic_config_read+0x3c/0xe0
[    0.807819]  dw_pcie_rd_other_conf+0x78/0x110
[    0.807820]  pci_bus_read_config_dword+0x88/0xe8
[    0.807821]  pci_bus_generic_read_dev_vendor_id+0x30/0x1b0
[    0.807822]  pci_bus_read_dev_vendor_id+0x4c/0x78
[    0.807823]  pci_scan_single_device+0x80/0x100
[    0.807824]  pci_scan_slot+0x38/0x130
[    0.807825]  pci_scan_child_bus_extend+0x54/0x2a0
[    0.807826]  pci_scan_child_bus+0x14/0x20
[    0.807827]  pci_scan_bridge_extend+0x230/0x570
[    0.807828]  pci_scan_child_bus_extend+0x134/0x2a0
[    0.807829]  pci_scan_root_bus_bridge+0x64/0xf0
[    0.807829]  pci_host_probe+0x18/0xc8
[    0.807830]  dw_pcie_host_init+0x220/0x378
[    0.807831]  ls_pcie_probe+0x104/0x140
[    0.807832]  platform_drv_probe+0x54/0xa8
[    0.807833]  really_probe+0x118/0x3e0
[    0.807834]  driver_probe_device+0x5c/0xc0
[    0.807835]  device_driver_attach+0x74/0x80
[    0.807835]  __driver_attach+0x8c/0xd8
[    0.807836]  bus_for_each_dev+0x7c/0xd8
[    0.807837]  driver_attach+0x24/0x30
[    0.807838]  bus_add_driver+0x154/0x200
[    0.807839]  driver_register+0x64/0x120
[    0.807839]  __platform_driver_probe+0x7c/0x148
[    0.807840]  ls_pcie_driver_init+0x24/0x30
[    0.807841]  do_one_initcall+0x60/0x1d8
[    0.807842]  kernel_init_freeable+0x1f4/0x24c
[    0.807843]  kernel_init+0x14/0x118
[    0.807843]  ret_from_fork+0x10/0x34
[    0.807854] SMP: stopping secondary CPUs
[    0.807855] Kernel Offset: 0x394c64080000 from 0xffff800010000000
[    0.807856] PHYS_OFFSET: 0xffff8bfd40000000
[    0.807856] CPU features: 0x0240022,21806000
[    0.807857] Memory Limit: none

[  641.029551] Kernel panic - not syncing: Asynchronous SError Interrupt
[  641.029553] CPU: 2 PID: 5232 Comm: insmod Tainted: G        W  O      4.19.0-jfd #1
[  641.029555] Hardware name: dublin (DT)
[  641.029556] Call trace:
[  641.029557]  dump_backtrace+0x0/0x180
[  641.029558]  show_stack+0x14/0x20
[  641.029560]  dump_stack+0x9c/0xbc
[  641.029561]  panic+0x130/0x278
[  641.029562]  nmi_panic+0x6c/0x70
[  641.029563]  arm64_serror_panic+0x74/0x80
[  641.029565]  is_valid_bugaddr+0x0/0x8
[  641.029566]  el1_error+0x7c/0xdc
[  641.029568]  _mali_osk_mem_iowrite32+0x10/0x20 [mali]
[  641.029569]  mali_pp_create+0x7c/0x350 [mali]
[  641.029571]  mali_initialize_subsystems+0x12c/0x5f8 [mali]
[  641.029572]  mali_probe+0xf0/0x358 [mali]
[  641.029574]  platform_drv_probe+0x50/0xa0
[  641.029575]  really_probe+0x1e0/0x298
[  641.029577]  driver_probe_device+0x54/0xe8
[  641.029578]  __driver_attach+0xe4/0xe8
[  641.029579]  bus_for_each_dev+0x70/0xc0
[  641.029581]  driver_attach+0x20/0x28
[  641.029582]  bus_add_driver+0x1dc/0x208
[  641.029583]  driver_register+0x60/0x110
[  641.029585]  __platform_driver_register+0x44/0x50
[  641.029586]  init_module+0x30/0x140 [mali]
[  641.029588]  do_one_initcall+0x74/0x178
[  641.029589]  do_init_module+0x54/0x1c0
[  641.029591]  load_module+0x1ae4/0x2108
[  641.029592]  __se_sys_finit_module+0xb8/0xc8
[  641.029594]  __arm64_sys_finit_module+0x18/0x20
[  641.029595]  el0_svc_common+0x84/0xd8
[  641.029596]  el0_svc_handler+0x6c/0x88
[  641.029598]  el0_svc+0x8/0xc

[  119.555236] CPU: 1 PID: 20401 Comm: dd Not tainted 5.4.5-1-ARTIX #1
[  119.555237] Hardware name: Firefly-RK3399 Board (DT)
[  119.555238] Call trace:
[  119.555239]  dump_backtrace+0x0/0x170
[  119.555240]  show_stack+0x24/0x30
[  119.555241]  dump_stack+0xac/0xd0
[  119.555242]  panic+0x144/0x31c
[  119.555243]  __stack_chk_fail+0x0/0x28
[  119.555244]  arm64_serror_panic+0x84/0x90
[  119.555245]  do_serror+0x11c/0x120
[  119.555246]  el1_error+0x84/0xf8
[  119.555248]  __arch_copy_from_user+0x1bc/0x240
[  119.555249]  iov_iter_copy_from_user_atomic+0xec/0x370
[  119.555250]  generic_perform_write+0xf0/0x1d0
[  119.555251]  __generic_file_write_iter+0x134/0x1b8
[  119.555252]  ext4_file_write_iter+0xd0/0x338
[  119.555253]  new_sync_write+0xf8/0x190
[  119.555254]  __vfs_write+0x74/0x90
[  119.555255]  vfs_write+0xe4/0x1c8
[  119.555257]  ksys_write+0x78/0x100
[  119.555257]  __arm64_sys_write+0x24/0x30
[  119.555259]  el0_svc_handler+0x84/0x190
[  119.555260]  el0_svc+0x8/0xc
[  119.555280] SMP: stopping secondary CPUs
[  119.555280] Kernel Offset: disabled
[  119.555282] CPU features: 0x0002,20006008
[  119.555283] Memory Limit: none

http://lkml.iu.edu/hypermail/linux/kernel/2004.1/01376.html
Kernel panic - not syncing: Asynchronous SError Interrupt
CPU: 7 PID: 2605 Comm: hexdump Tainted: G S 5.4.30 #122
Call trace:
dump_backtrace+0x0/0x188
show_stack+0x20/0x2c
dump_stack+0xdc/0x144
panic+0x168/0x36c
panic+0x0/0x36c
arm64_serror_panic+0x78/0x84
do_serror+0x130/0x138
el1_error+0x84/0xf8
tmc_read_prepare_etb+0x88/0xb8
tmc_open+0x40/0xd8
misc_open+0x120/0x158
chrdev_open+0xb8/0x1a4
do_dentry_open+0x268/0x3a0
vfs_open+0x34/0x40
path_openat+0x39c/0xdf4
do_filp_open+0x90/0x10c
do_sys_open+0x150/0x3e8
__arm64_compat_sys_openat+0x28/0x34
el0_svc_common+0xa8/0x160
el0_svc_compat_handler+0x2c/0x38
el0_svc_compat+0x8/0x10

[Armv8] SError 익셉션 클래스의 정체 ARMv8: 익셉션(Exception)

arm 사의 개발자들이 리눅스 커널에 훌륭한 디버깅 용 코드를 많이 반영했습니다. 
그 중 하나가 익셉션 클래스를 관리하는 esr_class_str 전역 변수인데, 이 배열의 원소 중 SError 항목이 있습니다.

다음은 esr_class_str 배열의 선언부입니다.

https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/kernel/traps.c
static const char *esr_class_str[] = {
[0 ... ESR_ELx_EC_MAX] = "UNRECOGNIZED EC",
[ESR_ELx_EC_UNKNOWN] = "Unknown/Uncategorized",
[ESR_ELx_EC_WFx] = "WFI/WFE",
[ESR_ELx_EC_CP15_32] = "CP15 MCR/MRC",
...
[ESR_ELx_EC_FP_EXC32] = "FP (AArch32)",
[ESR_ELx_EC_FP_EXC64] = "FP (AArch64)",
[ESR_ELx_EC_SERROR] = "SError",

ESR_ELx_EC_SERROR 매크로가 보이는데요, 이 매크로의 선언부는 다음과 같습니다.

https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/include/asm/esr.h 
#define ESR_ELx_EC_SERROR (0x2F)

ESR_ELx_EC_SERROR은 0x2F(이진수: 101111)로 정의돼 있는데, Armv8 스팩 문서를 보면 관련 내용을 확인할 수 있습니다.

EC == 0b101111
SError interrupt.
See ISS encoding for an SError interrupt.

[Armv8] SError(System Error) 익셉션이란 ARMv8: 익셉션(Exception)

SError(System Error) 익셉션은 Armv8 아키텍처에서 소개된 익셉션입니다. 기존 Armv7 아키텍처에 익숙한 분들에겐 생소해 보일 수 있는데요, arm사에서 배포한 문서를 보면서 SError(System Error) 익셉션의 정체를 알아봅시다.

SError(System Error) 익셉션의 정의

'DDI0487Fc_armv8_arm' 문서의 D13.2.36 ESR_EL1, Exception Syndrome Register (EL1)' 절을 보면 다음과 같은 내용을 확인할 수 있습니다.

---
EC == 0b101111
SError interrupt.
See ISS encoding for an SError interrupt
---

SError에 interrupt를 붙혀서 표현하는데, 익셉션 클래스에 SError interrupt가 정의돼 있습니다.

SError(System Error) 익셉션의 전체 흐름

SError(System Error) 익셉션은 크게 2가지 흐름으로 처리됩니다.

첫 번째는 리눅스 커널이 구동되는 EL1에서 SError 익셉션이 유발되는 흐름입니다.

   1. EL1에서 커널 코드가 실행 중 SError 익셉션 발생 
   2. SError 익셉션 벡터에서 el1_error 레이블 호출

두 번째는 유저 애플리케이션이 구동되는 EL0에서 SError 익셉션이 유발되는 흐름입니다. 

   1. EL0에서 유저 애플리케이션이 실행 중 SError 익셉션 발생 
   2. SError 익셉션 벡터에서 el1_error 레이블 호출

SError(System Error) 익셉션 핸들러 분석: EL1에서 SError 익셉션 유발

SError(System Error) 익셉션의 정체를 정확히 알려면 익셉션 핸들러 코드를 분석하면 됩니다. SError(System Error) 익셉션의 익셉션 핸들러 코드를 분석하겠습니다.

VBAR_EL1(익셉션 벡터 베이스 주소)은 0xFFFFFFC010082800라는 점을 주의합시다. 

01 MX:FFFFFFC010082800|D10503FF    vectors:  sub     SP,SP,#0x140     ; SP,SP,#320
02 MX:FFFFFFC010082804|8B2063FF              add     SP,SP,x0
03 MX:FFFFFFC010082808|CB2063E0              sub     x0,SP,x0
04 MX:FFFFFFC01008280C|37700080              tbnz    x0,#0x0E,0xFFFFFFC01008281C   ; x0,#14,0xFFFFFFC01008281C
...
05 MX:FFFFFFC010082B80|D10503FF              sub     SP,SP,#0x140     ; SP,SP,#320
06 MX:FFFFFFC010082B84|8B2063FF              add     SP,SP,x0
07 MX:FFFFFFC010082B88|CB2063E0              sub     x0,SP,x0
08 MX:FFFFFFC010082B8C|37700080              tbnz    x0,#0x0E,0xFFFFFFC010082B9C   ; x0,#14,0xFFFFFFC010082B9C
09 MX:FFFFFFC010082B90|CB2063E0              sub     x0,SP,x0
10 MX:FFFFFFC010082B94|CB2063FF              sub     SP,SP,x0
11 MX:FFFFFFC010082B98|140007B0              b       0xFFFFFFC010084A58   ; el1_error

SError(System Error) 익셉션 벡터는 VAR_EL1 기준으로 +0x480 만큼 오프셋을 적용한 주소이니, SError 익셉션이 유발되면 0xFFFFFFC010082B80 주소로 프로그램 카운터가 브랜치됩니다.

SError(System Error) 익셉션이 발생하면 0xFFFFFFC010082B80 주소로 프로그램 카운터가 브랜치 된 후,
아래 코드와 같이 11번째 줄이 실행됩니다.

11 MX:FFFFFFC010082B98|140007B0              b       0xFFFFFFC010084A58   ; el1_error

el1_error 라는 레이블을 브랜치하는 동작입니다.

el1_error 레이블의 코드는 다음과 같습니다.

01 MX:FFFFFFC010084A58|A90007E0        el1_error:stp     x0,x1,[SP]
...
02 MX:FFFFFFC010084B04|D5385201                  mrs     x1,#0x3,#0x0,c5,c2,#0x0   ; x1, ESR_EL1
03 MX:FFFFFFC010084B08|D50348FF                  msr     DAIFClr,#0x8     ; DAIFClr,#8
04 MX:FFFFFFC010084B0C|910003E0                  mov     x0,SP
05 MX:FFFFFFC010084B10|940ADF37                  bl      0xFFFFFFC01033C7EC   ; do_serror
 
05번째 줄과 같이 do_serror() 함수를 호출합니다.

el1_error의 구현부는 'arch/arm64/kernel/entry.S' 파일에서 확인할 수 있습니다.

https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/kernel/entry.S
el1_error:
kernel_entry 1
mrs x1, esr_el1
gic_prio_kentry_setup tmp=x2
enable_dbg
mov x0, sp
bl do_serror

이어서 do_serror() 함수의 구현부를 보겠습니다.

https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/kernel/traps.c
asmlinkage void do_serror(struct pt_regs *regs, unsigned int esr)
{
const bool was_in_nmi = in_nmi();

if (!was_in_nmi)
nmi_enter();

/* non-RAS errors are not containable */
if (!arm64_is_ras_serror(esr) || arm64_is_fatal_ras_serror(regs, esr))
arm64_serror_panic(regs, esr);

if (!was_in_nmi)
nmi_exit();
}

was_in_nmi 변수가 true인 경우를 제외하고는 arm64_serror_panic() 함수를 호출합니다.

다음은 arm64_serror_panic() 함수의 구현부입니다.

https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/kernel/traps.c
void __noreturn arm64_serror_panic(struct pt_regs *regs, u32 esr)
{
console_verbose();

pr_crit("SError Interrupt on CPU%d, code 0x%08x -- %s\n",
smp_processor_id(), esr, esr_get_class_string(esr));
if (regs)
__show_regs(regs);

nmi_panic(regs, "Asynchronous SError Interrupt");

cpu_park_loop();
unreachable();
}

"SError Interrupt on CPU..."와 같은 메시지를 로그로 출력한 다음 nmi_panic() 함수를 호출해 커널 패닉을 유발합니다.

이번에는 EL1에서 SError 익셉션이 발생했을 때의 콜 스택을 소개합니다.
다음 링크에서 SError 익셉션이 유발했을 때의 콜 스택을 확인할 수 있습니다.

[  119.555187] SError Interrupt on CPU1, code 0xbf000000 -- SError
[  119.555189] CPU: 1 PID: 20401 Comm: dd Not tainted 5.4.5-1-ARTIX #1
[  119.555191] Hardware name: Firefly-RK3399 Board (DT)
[  119.555192] pstate: 20000005 (nzCv daif -PAN -UAO)
[  119.555193] pc : __arch_copy_from_user+0x1bc/0x240
...
[  119.555234] Kernel panic - not syncing: Asynchronous SError Interrupt
[  119.555236] CPU: 1 PID: 20401 Comm: dd Not tainted 5.4.5-1-ARTIX #1
[  119.555237] Hardware name: Firefly-RK3399 Board (DT)
[  119.555238] Call trace:
[  119.555239]  dump_backtrace+0x0/0x170
[  119.555240]  show_stack+0x24/0x30
[  119.555241]  dump_stack+0xac/0xd0
[  119.555242]  panic+0x144/0x31c
[  119.555243]  nmi_panic+0x28/0x28
[  119.555244]  arm64_serror_panic+0x84/0x90
[  119.555245]  do_serror+0x11c/0x120
[  119.555246]  el1_error+0x84/0xf8
[  119.555248]  __arch_copy_from_user+0x1bc/0x240
[  119.555249]  iov_iter_copy_from_user_atomic+0xec/0x370
[  119.555250]  generic_perform_write+0xf0/0x1d0
[  119.555251]  __generic_file_write_iter+0x134/0x1b8
[  119.555252]  ext4_file_write_iter+0xd0/0x338
[  119.555253]  new_sync_write+0xf8/0x190
[  119.555254]  __vfs_write+0x74/0x90
[  119.555255]  vfs_write+0xe4/0x1c8
[  119.555257]  ksys_write+0x78/0x100
[  119.555257]  __arm64_sys_write+0x24/0x30
[  119.555259]  el0_svc_handler+0x84/0x190
[  119.555260]  el0_svc+0x8/0xc
[  119.555280] SMP: stopping secondary CPUs
[  119.555280] Kernel Offset: disabled
[  119.555282] CPU features: 0x0002,20006008
[  119.555283] Memory Limit: none

이 정도로 정리하면 SError 익셉션을 만나도 크게 당황하지 않을 것 같네요.

SError(System Error) 익셉션 핸들러 분석: EL0(Aarch64)에서 SError 익셉션 유발

이번에는 유저 애플리케이션이 구동되는 EL0에서 SError 익셉션이 유발했을 때 처리되는 익셉션 핸들러를 분석하겠습니다.

역시 VBAR_EL1(익셉션 벡터 베이스 주소)은 0xFFFFFFC010082800라는 점을 기억합시다.

EL0(Aarch64)에서 SError 익셉션이 발생하면 0xFFFFFFC010082D80(VBAR_EL1 + 0x580) 주소로 프로그램 카운터를 브랜치합니다.

01 MX:FFFFFFC010082800|D10503FF    vectors:  sub     SP,SP,#0x140     ; SP,SP,#320
02 MX:FFFFFFC010082804|8B2063FF              add     SP,SP,x0
03 MX:FFFFFFC010082808|CB2063E0              sub     x0,SP,x0
04 MX:FFFFFFC01008280C|37700080              tbnz    x0,#0x0E,0xFFFFFFC01008281C   ; x0,#14,0xFFFFFFC01008281C
...
05 MX:FFFFFFC010082D80|D503201F                  nop
06 MX:FFFFFFC010082D84|D503201F                  nop
...
07 MX:FFFFFFC010082DD8|14000778                  b       0xFFFFFFC010084BB8   ; el0_error

0xFFFFFFC010082D80 주소 아랫 부분을 보면 07번 째 줄과 같이 el0_error 레이블을 브랜치하는 명령어를 볼 수 있습니다.

이어서 분석할 el0_error 레이블의 코드의 구현부는 다음과 같습니다.

https://elixir.bootlin.com/linux/v5.4.30/source/arch/arm64/kernel/entry.S
el0_error:
kernel_entry 0
el0_error_naked:
mrs x25, esr_el1
gic_prio_kentry_setup tmp=x2
ct_user_exit_irqoff
enable_dbg
mov x0, sp
mov x1, x25
bl do_serror
enable_da_f
b ret_to_user
ENDPROC(el0_error)

역시나 do_serror() 함수를 호출합니다. do_serror() 함수는 이미 분석했으니 넘어 가겠습니다.

이어서 EL0에서 SError 익셉션이 유발됐을 때 콜 스택을 확인해 봅시다.
아래 링크에 보이는 로그에서 SError 익셉션이 유발됐을 때 함수 흐름을 확인할 수 있습니다.

[  209.234890]  dump_backtrace+0x0/0x180
[  209.234892]  show_stack+0x24/0x30
[  209.234894]  dump_stack+0x90/0xb4
[  209.234895]  panic+0x128/0x290
[  209.234897]  nmi_panic+0x7c/0x80
[  209.234899]  arm64_serror_panic+0x80/0x8c
[  209.234901]  is_valid_bugaddr+0x0/0x1c
[  209.234903]  el0_error_naked+0x10/0x18
[  209.234935] SMP: stopping secondary CPUs

EL0에서 어떤 함수를 실행하는 도중에 SError 익셉션이 발생해 EL1로 익셉션 레벨을 변경한 후
el0_error_naked 레이블이 호출된 것입니다.



.

1 2 3 4 5 6 7 8 9 10 다음