Armv8 아키텍처는 PL0 ~ PL3까지 4개의 privilege level(접근 권한)을 정의합니다. privilege levels은 Armv7 아키텍처에서 다룬 내용과 거의 유사합니다.
privilege levels은 Arm 아키텍처에서만 지원할까요? 그렇지는 않습니다.
x86을 포함한 대부분의 CPU 아키텍처는 privilege levels과 같은 기능을 지원합니다. 그 이유는 무엇일까요? 주로 리눅스와 같은 범용 운영체제를 설계할 때 시스템에 결함을 일으킬 수 있는 유저 애플리케이션으로부터 시스템을 보호하기 위해서입니다. 리눅스 커널과 같은 운영체제 커널은 privilege level에서 실행되며 privilege level에서만 주요 시스템 설정(시스템 레지스터, 인터럽트, 캐시 접근)을 할 수 있게 권한을 부여합니다. 유저 애플리케이션이 실행될 때는 privilege level 레벨이 아닌 unprivileged level로 실행해, 시스템의 세부 설정을 할 수 없게 설계합니다.
이처럼 privilege level 레벨로 권한을 부여하는 이유는 무엇일까요? 운영체제 커널의 세부 동작을 잘 모르는 유저 애플리케이션 개발자가 privilege level이 없는 환경에서 애플리케이션을 개발한다고 가정해봅시다. 유저 애플리케이션에서 메모리를 제대로 관리하면 괜찮겠지만 실수로 코드를 잘못 작성해서 커널 자료구조나 함수가 위치한 메모리 공간을 오염시키면 어떻게 될까요? 혹은 해커들이 고의로 시스템을 오동작시키기 위한 명령어를 실행하는 애플리케이션을 제작하면 어떤 결과를 초래할까요? 커널 패닉으로 시스템이 리부팅되거나 예상치 못한 시스템 오동작이 일어날 가능성이 있습니다.
따라서 유저 애플리케이션은 unprivileged level로 실행되도록 시스템을 설계합니다. 즉, 유저 애플리케이션에서 시스템 메모리 공간에 직접 접근하지 못하게 제약을 둡니다. 만약 유저 애플리케이션에서 시스템 메모리에 접근을 해야 하는 상황이라면 어떤 방법을 사용하면 될까요? 시스템 콜을 통해 privilege level로 실행되는 운영체제 커널로 이동해 실행 흐름을 스위칭한 다음 메모리에 접근하도록 시스템을 설계할 수 있습니다.
익셉션 레벨과 Privilege Level
EL0에서는 유저 애플리케이션이 unprivileged 레벨 혹은 PL0로 실행됩니다. EL0에서 소프트웨어(유저 애플리케이션)가 실행되면 PL0 권한으로 실행되는데 다음과 같은 특징을 지닙니다.
EL0에서는 인터럽트, MMU, 캐시 기능을 직접 설정할 수 없다.
EL0에서는 메모리 접근 권한에 제약이 있다.
IRQ나 MMU를 설정할 수 없다.
EL0에서는 시스템 레지스터를 직접 설정할 수 없다.
만약 EL0에서 시스템 레지스터나 메모리 주소와 하드웨어 리소스를 직접 설정하면 익셉션이 유발됩니다.
이어서 운영체제의 커널이 구동되는 EL1은 PL1 권한으로 실행되는 익셉션 레벨입니다. EL1에서 구동되는 커널에서는 인터럽트, MMU, 캐시와 같은 하드웨어를 직접 설정할 수 있습니다. EL1은 EL0보다 privilege level이 높으므로 EL0에서 실행되는 코드나 데이터에 접근할 수 있습니다.
PL2는 EL2에게 부여되는 권한 레벨로, EL1으로 실행되는 게스트 OS의 시스템 리소스에 접근할 수 있습니다. 일반적으로 EL2에서 하이퍼바이저가 실행되며, 하이퍼바이저는 PL2 권한 레벨로 실행됩니다. EL2는 EL0 혹은 EL1보다 privilege level이 높아 EL0 혹은 EL1에서 실행되는 코드나 데이터에 직접 접근할 수 있습니다.
PL2 권한이 있는 EL2는 자신보다 낮은 EL1(PL1)에서 실행되는 각각의 게스트 OS에서 설정된 레지스터에 접근할 수 있습니다. 이를 통해 EL2에서 실행되는 하이퍼바이저는 2개 이상의 게스트 OS의 리소스를 관리할 수 있습니다.
[용어] #하이퍼바이저란?
익셉션 레벨에 대한 설명을 하이퍼바이저 같은 낯선 용어와 함께 읽으면 무슨 내용인지 이해하기 어려울 수 있습니다. 익셉션 레벨에서 EL2를 설명하면 하이퍼바이저라는 용어가 등장합니다. 하이퍼바이저는 데스크톱 PC에서 윈도우와 리눅스를 사용하듯이, 2개 이상의 운영체제를 동시에 실행할 수 있는 아키텍처 혹은 플랫폼입니다.
다음 그림을 보면 EL2에서 하이퍼바이저가 실행됩니다.
그림 6.1 EL2에서 실행되는 #하이퍼바이저
하이퍼바이저에 대한 자세한 설명은 ‘16장. 가상화’를 참고하세요.
익셉션 레벨별로 접근할 수 있는 레지스터와 Privilege Level
이번에는 익셉션 레벨별로 접근할 수 있는 레지스터에 대해 알아봅시다. 이번 절에서 알아봤듯이 익셉션 레벨은 privilege level과 비례합니다. EL0는 PL0, EL1은 PL1 실행 권한을 지닙니다.
그런데 각 익셉션 레벨에서 접근할 수 있는 레지스터 범위가 한정돼 있습니다. EL1에서는 EL0에서 설정된 레지스터에 접근할 수 있고, EL2에서는 EL1, EL0에서 사용되는 레지스터에 모두 접근할 수 있습니다. 반대로 EL1에서는 EL2에서만 사용되는 레지스터에 접근할 수 없고 EL0에서는 EL1의 레지스터에 접근할 수 없습니다. 자신보다 높은 익셉션 레벨에서 정의된 레지스터에 접근하면 엑세스 권한 위반(Access Violation)으로 감지해 폴트를 유발합니다.
이처럼 각 익셉션 레벨에 부여된 privilege level 권한 레벨만큼 레지스터에 접근할 수 있는 권한이 있습니다. 이런 특징을 활용해 보안 관점에서 시스템의 리소스를 체계적으로 관리할 수 있습니다.
다음은 EL2에서 실행되는 XEN 하이퍼바이저에서 EL1 레지스터에 직접 접근하는 어셈블리 명령어입니다.
출처: https://github.com/xen-project/xen/blob/stable-4.15/xen/arch/arm/arm64/entry.S
01 0000000000269ba4 <guest_sync_slowpath>:
02 0x269ba4: 0xd10083ff sub sp, sp, #0x20
03 0x269ba8: 0xa9bf77fc stp x28, x29, [sp, #-16]!
...
04 0x269c00: 0xd53c4116 mrs x22, sp_el1
05 0x269c04: 0xd5384037 mrs x23, elr_el1
06 0x269c08: 0xa9005eb6 stp x22, x23, [x21]
...
07 0x269c68: 0xa9405eb6 ldp x22, x23, [x21]
08 0x269c6c: 0xd51c4116 msr sp_el1, x22
09 0x269c70: 0xd5184037 msr elr_el1, x23
10 0x269c74: 0x14000174 b 26a244 <return_from_trap>
[정보] guest_sync_slowpath 함수 소개
guest_sync_slowpath 함수는 EL2에서 실행되는 XEN 하이퍼바이저에서 게스트 OS를 관리하는 목적으로 구현된 함수입니다. guest_sync_slowpath 함수는 게스트 OS에서 하이퍼바이저에게 어떤 서비스를 요청할 때 서브루틴으로 호출됩니다. guest_sync 레이블을 통해 guest_sync_slowpath 함수가 호출되는데, XEN 하이퍼바이저 관점의 더 자세한 코드 분석은 15.5 절 'XEN 하이퍼바이저 코드 리뷰'를 참고하세요.
앞에서 소개한 코드는 어느 익셉션 레벨에서 실행될까요? 하이퍼바이저가 실행되는 EL2입니다. 이 점을 염두에 둡시다.
04 ~ 05번째 줄은 sp_el1과 elr_el1 레지스터를 읽는 명령어이고, 08 ~ 09번째 줄은 sp_el1과 elr_el1 레지스터를 설정하는 명령어입니다. 여기서 sp_el1은 EL1에서 접근하는 스택 포인터 레지스터, elr_el1은 EL1에서 접근하는 익셉션 링크 레지스터입니다. 그런데 PL2 권한으로 실행되는 EL2에서는 EL1에서 설정된 sp_el1 혹은 elr_el1 레지스터에 접근할 수 있습니다.
PL3 권한으로 실행되는 EL3
마지막으로 PL3는 EL3에 부여되는 권한 레벨로, 가장 높은 특권 레벨(highest privilege level)이라고도 합니다. 시스템 리소스를 모두 설정할 수 있고, 익셉션 레벨에 존재하는 모든 레지스터에 엑세스할 수 있습니다.
[정보] 시스템이 부팅할 때 가장 먼저 어떤 익셉션 레벨을 설정할까?
시스템에 전원을 인가하면 가장 먼저 부트로더가 실행됩니다. 시스템이 부팅하면 가장 먼저 실행되는 소프트웨어인 부트로더는 EL3로 익셉션 레벨을 설정합니다. 실행 권한이 가장 높은 EL3에서 시스템을 구성하는 주요 기능(MMU, 시스템 레지스터)을 설정합니다.
부트로더에서 익셉션 레벨을 EL3로 설정하는 다른 이유는 EL2에서 EL3로 진입하려면 익셉션을 유발하면서 EL2에서 실행한 정보를 담고 있는 레지스터 세트를 어딘가에 백업하는 추가 루틴이 실행되어야 하기 때문입니다. 결국 소프트웨어 복잡도가 늘어납니다. 그래서 EL3에서 시스템 리소스를 충분히 설정한 다음 EL3에서 EL2로 익셉션 레벨을 변경합니다. 이어서 EL2에서 EL1으로 익셉션 레벨을 변경하면서 부팅이 진행됩니다.
시스템이 부팅하는 과정에서 실행되는 소프트웨어 스택은 SoC 칩마다 다르므로 구체적인 내용은 SoC에서 배포한 데이터 시트 문서를 참고하세요.
익셉션 레벨과 Privilege Level 정리
지금까지 설명한 익셉션 레벨별 특권 레벨은 다음 표로 정리할 수 있습니다.
표 6.2 Armv8 아키텍처의 익셉션 레벨과 특권 레벨
익셉션 레벨과 권한 레벨은 비례하고 EL0는 가장 낮은 특권 레벨, EL3는 가장 높은 특권 레벨인 익셉션 레벨입니다.
다음 그림에서 익셉션 레벨과 특권 레벨의 관계를 확인할 수 있습니다.
그림 6.2 익셉션 레벨과 특권 레벨
그림을 보면 익셉션 레벨과 Privilege level이 비례한다는 사실을 알 수 있습니다.
익셉션 레벨의 실행 흐름과 소프트웨어 스택
이번에는 다음 그림을 보면서 각 익셉션 레벨에서 실행되는 소프트웨어 스택과 실행 흐름을 알아봅시다.
그림 6.3 Armv8 익셉션 레벨과 주요 실행 흐름
그림 6.3은 멀티 운영체제가 동시다발적으로 실행되는 하이퍼바이저와 트러스트존 기능이 구현된 아키텍처입니다. 각 익셉션 레벨에서 어떤 소프트웨어가 실행되는지 살펴봅시다.
먼저 그림의 가장 윗부분에 보이는 EL0부터 분석합시다. EL0는 EL0를 나타내며 유저 애플리케이션이 실행됩니다. 이어서 EL0의 아랫부분에 있는 EL1을 보겠습니다. EL1에는 운영체제의 커널이 구동되며, EL1은 PL1 권한으로 실행됩니다. EL1의 아래에는 EL2가 보입니다.
EL2에는 2개 이상 게스트 OS를 제어하는 하이퍼바이저가 구동되며, EL2는 PL2 권한으로 실행됩니다. 그림에서는 게스트 OS1과 게스트 OS2가 존재하는데, 하이퍼바이저는 2개 이상의 게스트 OS가 동시다발적으로 실행되도록 게스트 OS의 리소스(예: VCPU, 가상 인터럽트)를 관리하는 역할을 수행합니다.
마지막으로 EL2 아래에 있는 EL3를 봅시다. EL3는 가장 높은 특권 레벨(highest privilege) 권한, 즉 시스템의 모든 기능(레지스터, 메모리, 캐시)을 설정할 수 있는 가장 높은 권한으로 실행됩니다. 보통 부팅 과정에서 실행되는 부트로더에서 EL3로 설정한 다음에 시스템의 여러 리소스(메모리, 시스템 레지스터)를 설정합니다.
여기까지 Armv8 아키텍처의 근간인 익셉션 레벨을 살펴봤습니다. 이번 절에서 소개한 익셉션 레벨은 Armv8 아키텍처에서 가장 중요한 내용이니 잘 기억해 둡시다. 이어지는 절에서는 익셉션 레벨로 어떻게 진입하는지 살펴보겠습니다.
< 관련 강의 영상 >
07.05.2024
'시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리 > 6장: Armv8 - 익셉션 레벨' 카테고리의 다른 글
[Arm프로세서] Armv8: 익셉션 레벨은 어떻게 변경될까? (0) | 2024.01.01 |
---|---|
[Arm프로세서] 익셉션 레벨 소개 (0) | 2024.01.01 |
[ARMv8]ARM64 - 각 익셉션(Exception) 레벨 소개 (0) | 2023.06.10 |