DUJINLABS.COM

Linux v6.18.37 · ARM64

efi-rt-wrapper.S — 말로 풀어 읽기

이 파일은 왜 필요한가요?

커널이 펌웨어 함수를 부를 때에도 커널이 돌아올 자리는 남아 있어야 합니다. 이 래퍼는 원래 커널 스택의 위치를 기록하고 EFI 전용 스택으로 바꿉니다. 펌웨어가 정상적으로 반환하면 원래 스택으로 돌아오며, 실행 중 예외가 발생한 경우에는 별도의 복구 코드가 저장 기록을 사용합니다.

명령·지시문·라벨을 원본 줄 번호에 맞춰 설명합니다. 주석과 빈 줄을 포함한 전체 원문은 아래에 따로 두었습니다. 매크로 정의와 조건부 코드는 모두 차례대로 실행되는 명령 목록이 아닙니다.

기존 코드 분석과 함께 읽기 · 원본 파일 · 설명 원고

먼저 알아둘 개념: SP와 스택 공간, 스택 프레임과 복귀 기록, 호출 규약, 호출 주소와 복귀 주소, 예외 레벨과 복귀

처음 읽을 때

SP를 바꾸면 앞으로 메모리에 저장할 위치가 달라집니다. 기존 스택 내용이 사라지는 것은 아닙니다. 돌아올 주소를 어디에 남겼는지 찾으세요.

더 깊이 살펴볼 때

정상 경로는 callee-saved 규약을 믿고, 예외 복구 경로는 저장한 레지스터를 명시적으로 다시 읽습니다. x18처럼 플랫폼 규약의 의미를 가진 레지스터가 왜 별도 검사를 받는지도 살펴보세요.

한 줄씩 레지스터와 메모리 상태 보기

각 코드 아래의 ‘행 그림’을 펼치면, 그 줄에서 읽는 값과 실행 전·후의 상태를 나란히 볼 수 있습니다. 여기서 ‘명령 직전’은 지금 보고 있는 명령이 실행되기 바로 전을 뜻합니다. 함수 진입 때나 소스의 바로 윗줄을 가리키는 말은 아닙니다. 앞서 실행한 명령이 값을 바꿨다면 그 결과가 들어 있습니다.

소스로 정해지는 상수, 호출자가 넘기는 값, 실제 실행에서 정해지는 주소를 구분해서 읽어야 합니다. 주소 숫자를 몰라도 무엇을 가리키는지와 어디서 받아 오는지는 설명할 수 있습니다. ‘명령 직전 x0의 값’처럼 일반 표기로 남은 곳은 한 명령의 동작을 나타내며, 특정 호출 경로의 값까지 확인했다는 뜻은 아닙니다.

일반 명령은 그 한 줄의 효과를, 매크로는 여러 명령을 묶은 주요 결과를 표시합니다. 함수 호출은 호출 직후의 PC와 LR까지만 나타냅니다. 메모리 접근은 정상적으로 끝나는 경우를 가정합니다. 생략된 항목까지 모두 보존된다는 뜻은 아닙니다.

화살표 →는 같은 항목의 전후 비교입니다. 메모리 칸에는 주소와 크기를 따로 적었습니다. SP가 줄어들면 낮은 주소 쪽을 사용하며, 화면의 위아래와 메모리 주소 방향은 별개입니다. 전처리·어셈블·링크 지시문은 CPU 실행과 구분합니다.

‘이전·다음 행’은 소스에 적힌 순서입니다. 분기·반복·호출이 있으므로 파일 위에서 아래로 실제 실행된다고 가정하지 않습니다. 값이 여러 줄에 걸쳐 이어지는 모습은 cpu-reset.S의 단계별 예제에서 보실 수 있습니다.

총 48개 줄에 그림을 붙였습니다. 기존 코드와 설명은 그 아래에서 이어집니다.

먼저 그림으로 살펴보기

EFI 호출용 스택 전환과 예외 복구
화살표: 처리 흐름입니다. 조건이 적힌 단계에서는 해당 조건에 따라 경로가 갈립니다.
EFI 호출 전후의 두 스택
화살표는 SP의 전환을 뜻합니다. 아래 칸의 숫자는 각 프레임 시작을 기준으로 한 바이트 오프셋입니다.

두 스택의 연결

원본 1–35행

펌웨어 호출 중에는 별도의 스택을 사용합니다

펌웨어 호출 중에는 별도의 스택을 사용합니다의 세 단계 비교
화살표는 상태 저장·복원 및 스택 선택 관계입니다. 두 스택은 별도 메모리이며 높낮이가 실제 주소 순서는 아닙니다.

움직임으로 따라가기 · 재생하거나 한 단계씩 고정해 보실 수 있습니다.

펌웨어 호출 중에는 별도의 스택을 사용합니다 · 1단계

1단계 고정

GIF 원본 열기

그림의 각 단계를 글로 읽기

1. 호출 준비

SP를 바꾸어도 원래 스택의 내용이 자동으로 복사되지는 않습니다.

2. 펌웨어 실행

어느 스택이 선택됐는지와 그 안의 데이터가 무엇인지 구분합니다.

3. 커널로 복귀

필요한 레지스터와 호출 규약을 맞춘 뒤 원래 흐름을 계속합니다.

#include <linux/linkage.h>

C 호출용 EFI 래퍼와 예외 복구 코드의 시작·끝을 선언할 linkage 매크로를 가져옵니다. 두 경로의 진입 방식이 달라 FUNC와 CODE 선언을 구분합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

6행 그림 · 전처리 단계
소스
처리 전전처리 전
이 줄의 효과#include <linux/linkage.h>
CPU 레지스터
처리 전해당 없음
이 줄의 효과실행되는 CPU 명령이 없습니다.

→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

#include <asm/assembler.h>

전역 변수 값을 PC 상대 방식으로 읽는 ldr_l 등 ARM64 공통 매크로를 가져옵니다. 이 파일은 EFI 스택 상단 변수의 값을 찾을 때 이를 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

7행 그림 · 전처리 단계
소스
처리 전전처리 전
이 줄의 효과#include <asm/assembler.h>
CPU 레지스터
처리 전해당 없음
이 줄의 효과실행되는 CPU 명령이 없습니다.

→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

SYM_FUNC_START(__efi_rt_asm_wrapper)

커널에서 UEFI 런타임 서비스를 호출할 때 스택과 보존 레지스터를 관리하는 진입점입니다. x0는 펌웨어 함수 주소, x1은 오류 메시지에 쓸 함수 이름이며 x2~x6이 실제 서비스 인자입니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

9행 그림 · 어셈블·링크 단계

함수·코드 범위: __efi_rt_asm_wrapper

빌드 도구
처리 전소스의 명령·배치 표기
이 줄의 효과SYM_FUNC_START(__efi_rt_asm_wrapper)
CPU 레지스터
처리 전해당 없음
이 줄의 효과지시문 자체는 레지스터를 쓰지 않습니다.

→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 함수 시작 매크로는 심벌·정렬 정보와 BTI 진입 명령 등을 배치할 수 있습니다. 생성된 명령의 실행과 심벌 선언을 구분합니다.
	stp	x29, x30, [sp, #-112]!

112바이트의 커널 스택 프레임을 만들고 x29·LR을 저장합니다. 나머지 공간에는 함수 이름, x18, x19~x28을 보관하여 펌웨어 예외가 발생해도 커널 호출자로 복귀할 수 있게 합니다. 메모리에서는 sp + (-112) 위치가 첫 레지스터 x29에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x30에 대응합니다. 대괄호 뒤의 !는 주소 갱신을 뜻합니다. sp에 오프셋 -112을 더한 주소로 접근하고 그 주소를 sp에도 남깁니다.

확보한 112바이트의 시작에 FP/LR을 저장합니다. SP는 낮은 주소로 112 이동합니다.

10행 그림 · 메모리 쓰기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값spx29x30
메모리[이전 sp + (-112)] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x29의 값
메모리[이전 sp + (-112) + 8] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x30의 값
sp
이 명령을 실행하기 직전명령 직전 sp의 값
이 명령을 실행한 직후명령 직전 sp의 값 + (-112)

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • !는 전위 갱신입니다. 오프셋을 더한 주소로 접근하고 그 주소를 기준 레지스터에도 남깁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	mov	x29, sp

새 커널 스택 프레임 주소를 x29에 남깁니다. 잠시 EFI 전용 스택으로 바꿨다가 돌아올 때 이 값으로 원래 스택을 찾습니다. x29에 담기는 것은 SP의 주소값이며, SP와 스택 메모리의 내용은 그대로입니다.

11행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값sp
x29
이 명령을 실행하기 직전명령 직전 x29의 값
이 명령을 실행한 직후명령 직전 sp의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	stp	x1, x18, [sp, #16]

오류를 알릴 때 사용할 함수 이름 x1과 호출 전 x18을 보관합니다. x18은 플랫폼 전용 레지스터이므로 펌웨어가 바꾸면 안 되며, 호출 뒤 비교할 기준이 됩니다. 메모리에서는 sp + (16) 위치가 첫 레지스터 x1에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x18에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

첫 슬롯은 오류 처리용 x1, 두 번째 슬롯은 원래 x18입니다.

18행 그림 · 메모리 쓰기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값spx1x18
메모리[이전 sp + (16)] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x1의 값
메모리[이전 sp + (16) + 8] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x18의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	stp	x19, x20, [sp, #32]

커널의 x19·x20를 저장 프레임 +32에 보관합니다. 정상적인 펌웨어는 이를 보존하지만, 실행 도중 예외가 나면 정상 복귀 절차를 건너뛰므로 별도 복구 경로가 이 저장본을 사용합니다. 메모리에서는 sp + (32) 위치가 첫 레지스터 x19에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x20에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

26행 그림 · 메모리 쓰기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값spx19x20
메모리[이전 sp + (32)] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x19의 값
메모리[이전 sp + (32) + 8] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x20의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	stp	x21, x22, [sp, #48]

커널의 x21·x22를 저장 프레임 +48에 보관합니다. 정상적인 펌웨어는 이를 보존하지만, 실행 도중 예외가 나면 정상 복귀 절차를 건너뛰므로 별도 복구 경로가 이 저장본을 사용합니다. 메모리에서는 sp + (48) 위치가 첫 레지스터 x21에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x22에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

27행 그림 · 메모리 쓰기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값spx21x22
메모리[이전 sp + (48)] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x21의 값
메모리[이전 sp + (48) + 8] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x22의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	stp	x23, x24, [sp, #64]

커널의 x23·x24를 저장 프레임 +64에 보관합니다. 정상적인 펌웨어는 이를 보존하지만, 실행 도중 예외가 나면 정상 복귀 절차를 건너뛰므로 별도 복구 경로가 이 저장본을 사용합니다. 메모리에서는 sp + (64) 위치가 첫 레지스터 x23에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x24에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

28행 그림 · 메모리 쓰기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값spx23x24
메모리[이전 sp + (64)] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x23의 값
메모리[이전 sp + (64) + 8] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x24의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	stp	x25, x26, [sp, #80]

커널의 x25·x26를 저장 프레임 +80에 보관합니다. 정상적인 펌웨어는 이를 보존하지만, 실행 도중 예외가 나면 정상 복귀 절차를 건너뛰므로 별도 복구 경로가 이 저장본을 사용합니다. 메모리에서는 sp + (80) 위치가 첫 레지스터 x25에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x26에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

29행 그림 · 메모리 쓰기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값spx25x26
메모리[이전 sp + (80)] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x25의 값
메모리[이전 sp + (80) + 8] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x26의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	stp	x27, x28, [sp, #96]

커널의 x27·x28를 저장 프레임 +96에 보관합니다. 정상적인 펌웨어는 이를 보존하지만, 실행 도중 예외가 나면 정상 복귀 절차를 건너뛰므로 별도 복구 경로가 이 저장본을 사용합니다. 메모리에서는 sp + (96) 위치가 첫 레지스터 x27에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x28에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

30행 그림 · 메모리 쓰기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값spx27x28
메모리[이전 sp + (96)] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x27의 값
메모리[이전 sp + (96) + 8] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x28의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	ldr_l	x16, efi_rt_stack_top

부팅 때 할당한 EFI 런타임 전용 스택의 최상단 주소를 읽습니다. 펌웨어의 스택 사용을 현재 태스크의 일반 커널 스택에서 분리합니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.

심볼의 주소가 아니라 efi_rt_stack_top 변수에 저장된 포인터 값을 읽습니다.

32행 그림 · 주소·값 구분

함수·코드 범위: __efi_rt_asm_wrapper

x16
이 명령을 실행하기 직전명령 직전 x16의 값
이 명령을 실행한 직후메모리[주소(efi_rt_stack_top)]의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 이 매크로의 임시 레지스터는 내부에서 사용됩니다.
ldr_l 매크로 본문 · 187행

arch/arm64/include/asm/assembler.h · Linux v6.18.37

정의 선택 조건: #ifndef __ASM_ASSEMBLER_H

.macro ldr_l, dst, sym, tmp= .ifb \tmp adrp \dst, \sym ldr \dst, [\dst, :lo12:\sym] .else adrp \tmp, \sym ldr \dst, [\tmp, :lo12:\sym] .endif .endm
	mov	sp, x16

SP를 EFI 전용 스택으로 바꿉니다. 이후 펌웨어 함수와 그 내부 호출은 이 공간에 프레임을 만듭니다. 바뀌는 것은 SP가 담은 주소입니다. 원래 스택의 데이터가 새 주소로 복사되는 것은 아닙니다.

33행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x16
sp
이 명령을 실행하기 직전명령 직전 sp의 값
이 명령을 실행한 직후명령 직전 x16의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	stp	x18, x29, [sp, #-16]!

EFI 스택 상단에 원래 x18과 커널 프레임 주소 x29를 기록합니다. 예외 처리 코드는 이 두 값을 통해 원래 태스크 스택과 shadow call stack 상태를 되찾습니다. 메모리에서는 sp + (-16) 위치가 첫 레지스터 x18에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x29에 대응합니다. 대괄호 뒤의 !는 주소 갱신을 뜻합니다. sp에 오프셋 -16을 더한 주소로 접근하고 그 주소를 sp에도 남깁니다.

EFI 스택의 top−16에 x18, top−8에 커널 스택 프레임 주소 x29를 기록합니다.

34행 그림 · 메모리 쓰기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값spx18x29
메모리[이전 sp + (-16)] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x18의 값
메모리[이전 sp + (-16) + 8] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후명령 직전 x29의 값
sp
이 명령을 실행하기 직전명령 직전 sp의 값
이 명령을 실행한 직후명령 직전 sp의 값 + (-16)

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • !는 전위 갱신입니다. 오프셋을 더한 주소로 접근하고 그 주소를 기준 레지스터에도 남깁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.

정상 호출과 복귀

원본 36–57행

	mov	x8, x0

펌웨어 함수 주소를 x8에 보관합니다. 곧 x0를 첫 번째 서비스 인자로 바꿔야 하므로 분기 주소를 미리 옮겨 둡니다. MOV는 x8의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

41행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x0
x8
이 명령을 실행하기 직전명령 직전 x8의 값
이 명령을 실행한 직후명령 직전 x0의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	mov	x0, x2

래퍼의 x2를 펌웨어의 첫 인자 x0로 옮깁니다. 함수 포인터와 이름이라는 래퍼 전용 인자 두 개를 제외한 실제 인자를 전달합니다. MOV는 x0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

42행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x2
x0
이 명령을 실행하기 직전명령 직전 x0의 값
이 명령을 실행한 직후명령 직전 x2의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	mov	x1, x3

래퍼의 x3를 펌웨어의 두 번째 인자 x1로 옮깁니다. MOV는 x1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

43행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x3
x1
이 명령을 실행하기 직전명령 직전 x1의 값
이 명령을 실행한 직후명령 직전 x3의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	mov	x2, x4

래퍼의 x4를 펌웨어의 세 번째 인자 x2로 옮깁니다. MOV는 x2의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

44행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x4
x2
이 명령을 실행하기 직전명령 직전 x2의 값
이 명령을 실행한 직후명령 직전 x4의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	mov	x3, x5

래퍼의 x5를 펌웨어의 네 번째 인자 x3로 옮깁니다. MOV는 x3의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

45행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x5
x3
이 명령을 실행하기 직전명령 직전 x3의 값
이 명령을 실행한 직후명령 직전 x5의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	mov	x4, x6

래퍼의 x6를 펌웨어의 다섯 번째 인자 x4로 옮깁니다. 이 구현이 다루는 런타임 서비스는 인자가 다섯 개 이하라 추가 스택 인자 이동이 필요하지 않습니다. MOV는 x4의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.

46행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x6
x4
이 명령을 실행하기 직전명령 직전 x4의 값
이 명령을 실행한 직후명령 직전 x6의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	blr	x8

x8의 UEFI 런타임 서비스를 EFI 전용 스택에서 호출합니다. 서비스의 efi_status_t 결과는 x0로 돌아오며, 래퍼는 이후 검사에서도 이 값을 보존합니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다.

47행 그림 · 호출과 실행 위치

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x8
PC
이 명령을 실행하기 직전명령 직전 PC의 값
이 명령을 실행한 직후명령 직전 x8의 값
LR (x30)
이 명령을 실행하기 직전명령 직전 LR의 값 (x30)
이 명령을 실행한 직후이 BL/BLR 다음 명령 주소

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 그림은 분기 명령 자체의 효과입니다. 호출된 함수가 실행된 뒤의 레지스터 값은 그 함수의 반환·보존 규칙을 따라야 합니다.
	mov	x16, sp

돌아온 EFI 스택 주소를 x16에 남깁니다. 원래 태스크 스택으로 돌아간 뒤 EFI 스택에 기록한 복구용 포인터를 지우기 위해 사용합니다. x16에 담기는 것은 SP의 주소값이며, SP와 스택 메모리의 내용은 그대로입니다.

49행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값sp
x16
이 명령을 실행하기 직전명령 직전 x16의 값
이 명령을 실행한 직후명령 직전 sp의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	mov	sp, x29

x29에 보관했던 커널 프레임 주소로 SP를 복원합니다. 이후 프레임 해제와 함수 이름 읽기는 원래 태스크 스택에서 수행합니다. 바뀌는 것은 SP가 담은 주소입니다. 원래 스택의 데이터가 새 주소로 복사되는 것은 아닙니다.

ABI에 따라 보존된 x29가 112바이트 커널 프레임의 시작을 가리킵니다.

50행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x29
sp
이 명령을 실행하기 직전명령 직전 sp의 값
이 명령을 실행한 직후명령 직전 x29의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	str	xzr, [x16, #8]			// clear recorded task SP value

EFI 스택에 남긴 태스크 SP 값을 0으로 지웁니다. 현재 태스크가 아직 EFI 펌웨어를 실행 중이라는 잘못된 판정을 막기 위한 정리입니다. 실제로 x16 + (8) 주소의 메모리 8바이트를 xzr의 값으로 씁니다. 이 주소 형식은 기준 레지스터 x16 자체를 갱신하지 않습니다.

51행 그림 · 메모리 쓰기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x16xzr
메모리[이전 x16 + (8)] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후0

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
	ldp	x1, x2, [sp, #16]

원래 함수 이름을 x1에, 호출 전 x18을 x2에 읽습니다. x0에는 펌웨어 결과를 그대로 남겨 오류를 보고하더라도 반환할 수 있게 합니다. 메모리에서는 sp + (16) 위치가 첫 레지스터 x1에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x2에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

저장한 x1과 원래 x18을 x1/x2로 읽습니다. EFI 반환값 x0는 건드리지 않습니다.

53행 그림 · 메모리 읽기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값sp
메모리[이전 sp + (16)] · 8바이트
이 명령을 실행하기 직전M0 (이 주소에 저장된 값)
이 명령을 실행한 직후M0 (이 주소에 저장된 값)
x1
이 명령을 실행하기 직전명령 직전 x1의 값
이 명령을 실행한 직후M0
메모리[이전 sp + (16) + 8] · 8바이트
이 명령을 실행하기 직전M1 (이 주소에 저장된 값)
이 명령을 실행한 직후M1 (이 주소에 저장된 값)
x2
이 명령을 실행하기 직전명령 직전 x2의 값
이 명령을 실행한 직후M1

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	cmp	x2, x18

호출 전 x18과 펌웨어에서 돌아온 x18을 비교합니다. 플랫폼 레지스터 보존 규약을 어긴 펌웨어인지 확인합니다. CMP는 두 피연산자의 뺄셈 결과로 NZCV 조건 플래그만 갱신하고 원래 레지스터 값은 유지합니다.

54행 그림 · 조건 플래그 계산

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값x2x18
NZCV
이 명령을 실행하기 직전명령 직전 NZCV의 값
이 명령을 실행한 직후명령 직전 x2의 값 − 명령 직전 x18의 값의 조건 결과

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 일반 레지스터에 연산 결과를 쓰지 않고 조건 플래그만 바꿉니다.
  • N은 결과의 부호 비트, Z는 결과가 0인지 나타냅니다. C는 빌림이 없으면 1, V는 부호 있는 뺄셈의 오버플로를 나타냅니다.
	ldp	x29, x30, [sp], #112

호출자의 프레임 포인터·LR을 복원하고 112바이트 스택 프레임을 해제합니다. 정상 반환과 오류 보고 양쪽이 같은 호출자에게 돌아갈 준비를 합니다. 메모리에서는 sp 위치가 첫 레지스터 x29에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x30에 대응합니다. 대괄호 밖의 오프셋 112은 후위 갱신입니다. 기존 sp 주소로 접근한 뒤 sp에 이 값을 더합니다.

LDP는 CMP의 NZCV를 바꾸지 않아 다음 B.NE는 54행 비교 결과를 사용합니다.

55행 그림 · 메모리 읽기

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값sp
메모리[이전 sp] · 8바이트
이 명령을 실행하기 직전M0 (이 주소에 저장된 값)
이 명령을 실행한 직후M0 (이 주소에 저장된 값)
x29
이 명령을 실행하기 직전명령 직전 x29의 값
이 명령을 실행한 직후M0
메모리[이전 sp + 8] · 8바이트
이 명령을 실행하기 직전M1 (이 주소에 저장된 값)
이 명령을 실행한 직후M1 (이 주소에 저장된 값)
x30
이 명령을 실행하기 직전명령 직전 x30의 값
이 명령을 실행한 직후M1
sp
이 명령을 실행하기 직전명령 직전 sp의 값
이 명령을 실행한 직후명령 직전 sp의 값 + (112)

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 대괄호 밖의 오프셋은 후위 갱신입니다. 이전 주소로 접근한 뒤 기준 레지스터를 갱신합니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	b.ne	0f

x18이 바뀌었다면 복원·오류 보고 경로로 갑니다. 같으면 바로 펌웨어 결과 x0를 반환합니다. 앞서 계산한 NZCV 중 Z=0 조건으로 분기합니다. 이 줄은 두 값을 새로 비교하지 않으며 조건이 맞지 않으면 다음 명령으로 진행합니다.

56행 그림 · 조건별 실행 위치

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값NZCV
PC
이 명령을 실행하기 직전명령 직전 PC의 값
이 명령을 실행한 직후Z=0이면 0f (L58), 아니면 다음 명령

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 조건이 맞는 한 경로만 실행합니다. 이 분기 명령은 NZCV를 새로 계산하지 않습니다.
	ret

펌웨어의 반환 상태 x0를 바꾸지 않고 커널 호출자에게 돌려줍니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

57행 그림 · 호출과 실행 위치

함수·코드 범위: __efi_rt_asm_wrapper

읽는 값lr
PC
이 명령을 실행하기 직전명령 직전 PC의 값
이 명령을 실행한 직후명령 직전 lr의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 새 복귀 주소를 LR에 기록하지 않습니다. RET 역시 메모리의 스택 값을 자동으로 꺼내는 명령은 아닙니다.

x18이 바뀐 경우

원본 58–72행

0:

펌웨어가 x18을 손상시킨 경우 들어오는 오류 보고 경로의 시작입니다. shadow call stack을 사용하는 빌드는 먼저 x18을 복구합니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.

58행 그림 · 위치에 이름 붙이기

함수·코드 범위: __efi_rt_asm_wrapper

레이블
처리 전현재 배치 위치
이 줄의 효과0
CPU 레지스터
처리 전그대로
이 줄의 효과레이블 자체가 값을 바꾸지 않습니다.

→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

#ifdef CONFIG_SHADOW_CALL_STACK

Shadow Call Stack은 함수의 복귀 주소를 일반 스택과 별도 공간에도 보관하여 덮어쓰기 공격에 대비하는 기능입니다. ARM64에서는 x18이 그 별도 스택의 포인터이므로, 이 설정이 켜졌다면 C 오류 보고 코드로 가기 전에 손상된 x18부터 복구해야 합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.

66행 그림 · 전처리 단계

함수·코드 범위: __efi_rt_asm_wrapper

소스
처리 전전처리 전
이 줄의 효과#ifdef CONFIG_SHADOW_CALL_STACK
CPU 레지스터
처리 전해당 없음
이 줄의 효과실행되는 CPU 명령이 없습니다.

→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

	ldr_l	x18, efi_rt_stack_top

x18이 손상된 경로에서 EFI 전용 스택 상단 주소를 다시 읽습니다. 기존 x18 값은 믿을 수 없으므로 저장본을 찾을 주소부터 새로 구합니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.

67행 그림 · 주소·값 구분

함수·코드 범위: __efi_rt_asm_wrapper

이 줄을 포함하는 조건: #ifdef CONFIG_SHADOW_CALL_STACK

x18
이 명령을 실행하기 직전명령 직전 x18의 값
이 명령을 실행한 직후메모리[주소(efi_rt_stack_top)]의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 이 매크로의 임시 레지스터는 내부에서 사용됩니다.
ldr_l 매크로 본문 · 187행

arch/arm64/include/asm/assembler.h · Linux v6.18.37

정의 선택 조건: #ifndef __ASM_ASSEMBLER_H

.macro ldr_l, dst, sym, tmp= .ifb \tmp adrp \dst, \sym ldr \dst, [\dst, :lo12:\sym] .else adrp \tmp, \sym ldr \dst, [\tmp, :lo12:\sym] .endif .endm
	ldr	x18, [x18, #-16]

EFI 스택 상단의 16바이트 아래에 저장한 원래 x18을 되찾습니다. 이후 계측된 C 함수가 올바른 shadow call stack에 복귀 주소를 기록하도록 합니다.

68행 그림 · 메모리 읽기

함수·코드 범위: __efi_rt_asm_wrapper

이 줄을 포함하는 조건: #ifdef CONFIG_SHADOW_CALL_STACK

읽는 값x18
메모리[이전 x18 + (-16)] · 8바이트
이 명령을 실행하기 직전M0 (이 주소에 저장된 값)
이 명령을 실행한 직후M0 (이 주소에 저장된 값)
x18
이 명령을 실행하기 직전명령 직전 x18의 값
이 명령을 실행한 직후M0

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
#endif

Shadow Call Stack용 포인터 복구는 여기까지입니다. 설정과 관계없이 x18 보존 규약 위반에 대한 오류 보고는 이어서 수행합니다.

69행 그림 · 전처리 단계

함수·코드 범위: __efi_rt_asm_wrapper

이 줄을 포함하는 조건: #ifdef CONFIG_SHADOW_CALL_STACK

소스
처리 전전처리 전
이 줄의 효과#endif
CPU 레지스터
처리 전해당 없음
이 줄의 효과실행되는 CPU 명령이 없습니다.

→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

	b	efi_handle_corrupted_x18	// tail call

x0의 펌웨어 상태와 x1의 함수 이름으로 efi_handle_corrupted_x18을 꼬리 호출합니다. 이 함수는 펌웨어가 x18을 손상시켰다는 메시지를 남기고 원래 상태값을 그대로 반환합니다. B를 사용하므로 래퍼로 재복귀하지 않습니다. 이 분기는 LR에 새 복귀 주소를 기록하지 않습니다.

x0의 EFI 결과와 복구한 x1을 유지한 tail call입니다.

71행 그림 · 호출과 실행 위치

함수·코드 범위: __efi_rt_asm_wrapper

PC
이 명령을 실행하기 직전명령 직전 PC의 값
이 명령을 실행한 직후efi_handle_corrupted_x18

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 새 복귀 주소를 LR에 기록하지 않습니다. RET 역시 메모리의 스택 값을 자동으로 꺼내는 명령은 아닙니다.
SYM_FUNC_END(__efi_rt_asm_wrapper)

도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

72행 그림 · 어셈블·링크 단계

함수·코드 범위: __efi_rt_asm_wrapper

빌드 도구
처리 전소스의 명령·배치 표기
이 줄의 효과SYM_FUNC_END(__efi_rt_asm_wrapper)
CPU 레지스터
처리 전해당 없음
이 줄의 효과지시문 자체는 레지스터를 쓰지 않습니다.

→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 끝 표기 자체가 RET나 분기를 실행하지는 않습니다.

동기 예외 복구

원본 73–87행

SYM_CODE_START(__efi_rt_asm_recover)

펌웨어 실행 중 동기 예외를 커널이 복구하기로 결정했을 때 들어오는 별도 경로입니다. efi_runtime_fixup_exception이 PC를 여기로 바꾸고 x0에 EFI_ABORTED, x30에 저장된 태스크 SP를 마련합니다. 이 매크로는 정렬된 코드 시작 심벌을 정의하고 다른 오브젝트에서도 참조할 수 있게 합니다.

74행 그림 · 어셈블·링크 단계

함수·코드 범위: __efi_rt_asm_recover

빌드 도구
처리 전소스의 명령·배치 표기
이 줄의 효과SYM_CODE_START(__efi_rt_asm_recover)
CPU 레지스터
처리 전해당 없음
이 줄의 효과지시문 자체는 레지스터를 쓰지 않습니다.

→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

	mov	sp, x30

여기서 x30은 보통의 복귀 주소가 아니라 예외 처리 코드가 넣어 준 태스크 스택 프레임 주소입니다. 이를 SP에 넣어 펌웨어 스택을 벗어납니다. 바뀌는 것은 SP가 담은 주소입니다. 원래 스택의 데이터가 새 주소로 복사되는 것은 아닙니다.

efi.c가 예외 프레임의 x30에 원래 커널 SP를 넣어 이 경로로 보냅니다. 여기의 x30은 정상 LR 용도가 아닙니다.

75행 그림 · 값 복사

함수·코드 범위: __efi_rt_asm_recover

읽는 값x30
sp
이 명령을 실행하기 직전명령 직전 sp의 값
이 명령을 실행한 직후명령 직전 x30의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
	ldr_l	x16, efi_rt_stack_top		// clear recorded task SP value

EFI 런타임 스택 상단을 다시 찾아, 이제 펌웨어 실행을 벗어났다는 표시를 정리할 준비를 합니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.

77행 그림 · 주소·값 구분

함수·코드 범위: __efi_rt_asm_recover

x16
이 명령을 실행하기 직전명령 직전 x16의 값
이 명령을 실행한 직후메모리[주소(efi_rt_stack_top)]의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 이 매크로의 임시 레지스터는 내부에서 사용됩니다.
ldr_l 매크로 본문 · 187행

arch/arm64/include/asm/assembler.h · Linux v6.18.37

정의 선택 조건: #ifndef __ASM_ASSEMBLER_H

.macro ldr_l, dst, sym, tmp= .ifb \tmp adrp \dst, \sym ldr \dst, [\dst, :lo12:\sym] .else adrp \tmp, \sym ldr \dst, [\tmp, :lo12:\sym] .endif .endm
	str	xzr, [x16, #-8]

EFI 스택 끝의 복구용 태스크 SP 기록을 0으로 지웁니다. 정상 반환 경로와 동일하게 EFI 실행 중 상태를 해제합니다. 실제로 x16 + (-8) 주소의 메모리 8바이트를 xzr의 값으로 씁니다. 이 주소 형식은 기준 레지스터 x16 자체를 갱신하지 않습니다.

78행 그림 · 메모리 쓰기

함수·코드 범위: __efi_rt_asm_recover

읽는 값x16xzr
메모리[이전 x16 + (-8)] · 8바이트
이 명령을 실행하기 직전기존 메모리 값
이 명령을 실행한 직후0

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
	ldp	x19, x20, [sp, #32]

정상적인 펌웨어 복귀를 거치지 못한 경로이므로, 호출 전에 프레임 +32에 보관한 x19·x20를 직접 되살립니다. 커널 호출자가 기대하는 callee-saved 상태를 복원하는 단계입니다. 메모리에서는 sp + (32) 위치가 첫 레지스터 x19에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x20에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

80행 그림 · 메모리 읽기

함수·코드 범위: __efi_rt_asm_recover

읽는 값sp
메모리[이전 sp + (32)] · 8바이트
이 명령을 실행하기 직전M0 (이 주소에 저장된 값)
이 명령을 실행한 직후M0 (이 주소에 저장된 값)
x19
이 명령을 실행하기 직전명령 직전 x19의 값
이 명령을 실행한 직후M0
메모리[이전 sp + (32) + 8] · 8바이트
이 명령을 실행하기 직전M1 (이 주소에 저장된 값)
이 명령을 실행한 직후M1 (이 주소에 저장된 값)
x20
이 명령을 실행하기 직전명령 직전 x20의 값
이 명령을 실행한 직후M1

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	ldp	x21, x22, [sp, #48]

정상적인 펌웨어 복귀를 거치지 못한 경로이므로, 호출 전에 프레임 +48에 보관한 x21·x22를 직접 되살립니다. 커널 호출자가 기대하는 callee-saved 상태를 복원하는 단계입니다. 메모리에서는 sp + (48) 위치가 첫 레지스터 x21에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x22에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

81행 그림 · 메모리 읽기

함수·코드 범위: __efi_rt_asm_recover

읽는 값sp
메모리[이전 sp + (48)] · 8바이트
이 명령을 실행하기 직전M0 (이 주소에 저장된 값)
이 명령을 실행한 직후M0 (이 주소에 저장된 값)
x21
이 명령을 실행하기 직전명령 직전 x21의 값
이 명령을 실행한 직후M0
메모리[이전 sp + (48) + 8] · 8바이트
이 명령을 실행하기 직전M1 (이 주소에 저장된 값)
이 명령을 실행한 직후M1 (이 주소에 저장된 값)
x22
이 명령을 실행하기 직전명령 직전 x22의 값
이 명령을 실행한 직후M1

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	ldp	x23, x24, [sp, #64]

정상적인 펌웨어 복귀를 거치지 못한 경로이므로, 호출 전에 프레임 +64에 보관한 x23·x24를 직접 되살립니다. 커널 호출자가 기대하는 callee-saved 상태를 복원하는 단계입니다. 메모리에서는 sp + (64) 위치가 첫 레지스터 x23에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x24에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

82행 그림 · 메모리 읽기

함수·코드 범위: __efi_rt_asm_recover

읽는 값sp
메모리[이전 sp + (64)] · 8바이트
이 명령을 실행하기 직전M0 (이 주소에 저장된 값)
이 명령을 실행한 직후M0 (이 주소에 저장된 값)
x23
이 명령을 실행하기 직전명령 직전 x23의 값
이 명령을 실행한 직후M0
메모리[이전 sp + (64) + 8] · 8바이트
이 명령을 실행하기 직전M1 (이 주소에 저장된 값)
이 명령을 실행한 직후M1 (이 주소에 저장된 값)
x24
이 명령을 실행하기 직전명령 직전 x24의 값
이 명령을 실행한 직후M1

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	ldp	x25, x26, [sp, #80]

정상적인 펌웨어 복귀를 거치지 못한 경로이므로, 호출 전에 프레임 +80에 보관한 x25·x26를 직접 되살립니다. 커널 호출자가 기대하는 callee-saved 상태를 복원하는 단계입니다. 메모리에서는 sp + (80) 위치가 첫 레지스터 x25에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x26에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

83행 그림 · 메모리 읽기

함수·코드 범위: __efi_rt_asm_recover

읽는 값sp
메모리[이전 sp + (80)] · 8바이트
이 명령을 실행하기 직전M0 (이 주소에 저장된 값)
이 명령을 실행한 직후M0 (이 주소에 저장된 값)
x25
이 명령을 실행하기 직전명령 직전 x25의 값
이 명령을 실행한 직후M0
메모리[이전 sp + (80) + 8] · 8바이트
이 명령을 실행하기 직전M1 (이 주소에 저장된 값)
이 명령을 실행한 직후M1 (이 주소에 저장된 값)
x26
이 명령을 실행하기 직전명령 직전 x26의 값
이 명령을 실행한 직후M1

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	ldp	x27, x28, [sp, #96]

정상적인 펌웨어 복귀를 거치지 못한 경로이므로, 호출 전에 프레임 +96에 보관한 x27·x28를 직접 되살립니다. 커널 호출자가 기대하는 callee-saved 상태를 복원하는 단계입니다. 메모리에서는 sp + (96) 위치가 첫 레지스터 x27에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x28에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.

84행 그림 · 메모리 읽기

함수·코드 범위: __efi_rt_asm_recover

읽는 값sp
메모리[이전 sp + (96)] · 8바이트
이 명령을 실행하기 직전M0 (이 주소에 저장된 값)
이 명령을 실행한 직후M0 (이 주소에 저장된 값)
x27
이 명령을 실행하기 직전명령 직전 x27의 값
이 명령을 실행한 직후M0
메모리[이전 sp + (96) + 8] · 8바이트
이 명령을 실행하기 직전M1 (이 주소에 저장된 값)
이 명령을 실행한 직후M1 (이 주소에 저장된 값)
x28
이 명령을 실행하기 직전명령 직전 x28의 값
이 명령을 실행한 직후M1

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	ldp	x29, x30, [sp], #112

예외 전 커널 호출자의 프레임 포인터와 LR을 복원하고 전체 저장 프레임을 해제합니다. EFI 내부의 복귀 주소로 돌아가는 것이 아닙니다. 메모리에서는 sp 위치가 첫 레지스터 x29에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x30에 대응합니다. 대괄호 밖의 오프셋 112은 후위 갱신입니다. 기존 sp 주소로 접근한 뒤 sp에 이 값을 더합니다.

85행 그림 · 메모리 읽기

함수·코드 범위: __efi_rt_asm_recover

읽는 값sp
메모리[이전 sp] · 8바이트
이 명령을 실행하기 직전M0 (이 주소에 저장된 값)
이 명령을 실행한 직후M0 (이 주소에 저장된 값)
x29
이 명령을 실행하기 직전명령 직전 x29의 값
이 명령을 실행한 직후M0
메모리[이전 sp + 8] · 8바이트
이 명령을 실행하기 직전M1 (이 주소에 저장된 값)
이 명령을 실행한 직후M1 (이 주소에 저장된 값)
x30
이 명령을 실행하기 직전명령 직전 x30의 값
이 명령을 실행한 직후M1
sp
이 명령을 실행하기 직전명령 직전 sp의 값
이 명령을 실행한 직후명령 직전 sp의 값 + (112)

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 대괄호 밖의 오프셋은 후위 갱신입니다. 이전 주소로 접근한 뒤 기준 레지스터를 갱신합니다.
  • 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
	ret

예외 처리 코드가 준비한 EFI_ABORTED를 x0에 둔 채 원래 커널 호출자로 돌아갑니다. 커널의 C 예외 처리 쪽에서는 이후 EFI 런타임 서비스 사용도 비활성화합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

86행 그림 · 호출과 실행 위치

함수·코드 범위: __efi_rt_asm_recover

읽는 값lr
PC
이 명령을 실행하기 직전명령 직전 PC의 값
이 명령을 실행한 직후명령 직전 lr의 값

→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 새 복귀 주소를 LR에 기록하지 않습니다. RET 역시 메모리의 스택 값을 자동으로 꺼내는 명령은 아닙니다.
SYM_CODE_END(__efi_rt_asm_recover)

도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

87행 그림 · 어셈블·링크 단계

함수·코드 범위: __efi_rt_asm_recover

빌드 도구
처리 전소스의 명령·배치 표기
이 줄의 효과SYM_CODE_END(__efi_rt_asm_recover)
CPU 레지스터
처리 전해당 없음
이 줄의 효과지시문 자체는 레지스터를 쓰지 않습니다.

→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.

  • 끝 표기 자체가 RET나 분기를 실행하지는 않습니다.

설명한 뒤 함께 생각해 볼 질문

다른 스택으로 가면 원래 함수로 못 돌아오지 않나요?

원래 프레임 주소를 x29와 EFI 스택에 남겨 둡니다. 그 값을 SP로 되돌리면 원래 프레임을 다시 읽을 수 있습니다.

복구 함수에서 x30을 SP로 쓰는 것은 이상하지 않나요?

C 예외 처리 코드가 복구용 원래 SP를 예외 프레임의 x30에 넣어 보냅니다. 이 경로에서 x30은 잠시 다른 의미로 사용됩니다.

정상 복귀에도 x19–x28을 모두 다시 읽나요?

이 래퍼의 정상 경로는 ABI에 따른 펌웨어의 보존을 전제로 합니다. 저장본을 모두 읽는 경로는 동기 예외 복구 부분입니다.

주석을 포함한 전체 원문

Linux stable v6.18.37 · arch/arm64/kernel/efi-rt-wrapper.S

1/* SPDX-License-Identifier: GPL-2.0-only */2/*3 * Copyright (C) 2018 Linaro Ltd <[email protected]>4 */56#include <linux/linkage.h>7#include <asm/assembler.h>89SYM_FUNC_START(__efi_rt_asm_wrapper)10	stp	x29, x30, [sp, #-112]!11	mov	x29, sp1213	/*14	 * Register x18 is designated as the 'platform' register by the AAPCS,15	 * which means firmware running at the same exception level as the OS16	 * (such as UEFI) should never touch it.17	 */18	stp	x1, x18, [sp, #16]1920	/*21	 * Preserve all callee saved registers and preserve the stack pointer22	 * value at the base of the EFI runtime stack so we can recover from23	 * synchronous exceptions occurring while executing the firmware24	 * routines.25	 */26	stp	x19, x20, [sp, #32]27	stp	x21, x22, [sp, #48]28	stp	x23, x24, [sp, #64]29	stp	x25, x26, [sp, #80]30	stp	x27, x28, [sp, #96]3132	ldr_l	x16, efi_rt_stack_top33	mov	sp, x1634	stp	x18, x29, [sp, #-16]!3536	/*37	 * We are lucky enough that no EFI runtime services take more than38	 * 5 arguments, so all are passed in registers rather than via the39	 * stack.40	 */41	mov	x8, x042	mov	x0, x243	mov	x1, x344	mov	x2, x445	mov	x3, x546	mov	x4, x647	blr	x84849	mov	x16, sp50	mov	sp, x2951	str	xzr, [x16, #8]			// clear recorded task SP value5253	ldp	x1, x2, [sp, #16]54	cmp	x2, x1855	ldp	x29, x30, [sp], #11256	b.ne	0f57	ret580:59	/*60	 * With CONFIG_SHADOW_CALL_STACK, the kernel uses x18 to store a61	 * shadow stack pointer, which we need to restore before returning to62	 * potentially instrumented code. This is safe because the wrapper is63	 * called with preemption disabled and a separate shadow stack is used64	 * for interrupts.65	 */66#ifdef CONFIG_SHADOW_CALL_STACK67	ldr_l	x18, efi_rt_stack_top68	ldr	x18, [x18, #-16]69#endif7071	b	efi_handle_corrupted_x18	// tail call72SYM_FUNC_END(__efi_rt_asm_wrapper)7374SYM_CODE_START(__efi_rt_asm_recover)75	mov	sp, x307677	ldr_l	x16, efi_rt_stack_top		// clear recorded task SP value78	str	xzr, [x16, #-8]7980	ldp	x19, x20, [sp, #32]81	ldp	x21, x22, [sp, #48]82	ldp	x23, x24, [sp, #64]83	ldp	x25, x26, [sp, #80]84	ldp	x27, x28, [sp, #96]85	ldp	x29, x30, [sp], #11286	ret87SYM_CODE_END(__efi_rt_asm_recover)
맨 위로 ↑