이 파일은 왜 필요한가요?
커널이 펌웨어 함수를 부를 때에도 커널이 돌아올 자리는 남아 있어야 합니다. 이 래퍼는 원래 커널 스택의 위치를 기록하고 EFI 전용 스택으로 바꿉니다. 펌웨어가 정상적으로 반환하면 원래 스택으로 돌아오며, 실행 중 예외가 발생한 경우에는 별도의 복구 코드가 저장 기록을 사용합니다.
명령·지시문·라벨을 원본 줄 번호에 맞춰 설명합니다. 주석과 빈 줄을 포함한 전체 원문은 아래에 따로 두었습니다. 매크로 정의와 조건부 코드는 모두 차례대로 실행되는 명령 목록이 아닙니다.
기존 코드 분석과 함께 읽기 · 원본 파일 · 설명 원고
먼저 알아둘 개념: SP와 스택 공간, 스택 프레임과 복귀 기록, 호출 규약, 호출 주소와 복귀 주소, 예외 레벨과 복귀
처음 읽을 때
SP를 바꾸면 앞으로 메모리에 저장할 위치가 달라집니다. 기존 스택 내용이 사라지는 것은 아닙니다. 돌아올 주소를 어디에 남겼는지 찾으세요.
더 깊이 살펴볼 때
정상 경로는 callee-saved 규약을 믿고, 예외 복구 경로는 저장한 레지스터를 명시적으로 다시 읽습니다. x18처럼 플랫폼 규약의 의미를 가진 레지스터가 왜 별도 검사를 받는지도 살펴보세요.
한 줄씩 레지스터와 메모리 상태 보기
각 코드 아래의 ‘행 그림’을 펼치면, 그 줄에서 읽는 값과 실행 전·후의 상태를 나란히 볼 수 있습니다. 여기서 ‘명령 직전’은 지금 보고 있는 명령이 실행되기 바로 전을 뜻합니다. 함수 진입 때나 소스의 바로 윗줄을 가리키는 말은 아닙니다. 앞서 실행한 명령이 값을 바꿨다면 그 결과가 들어 있습니다.
소스로 정해지는 상수, 호출자가 넘기는 값, 실제 실행에서 정해지는 주소를 구분해서 읽어야 합니다. 주소 숫자를 몰라도 무엇을 가리키는지와 어디서 받아 오는지는 설명할 수 있습니다. ‘명령 직전 x0의 값’처럼 일반 표기로 남은 곳은 한 명령의 동작을 나타내며, 특정 호출 경로의 값까지 확인했다는 뜻은 아닙니다.
일반 명령은 그 한 줄의 효과를, 매크로는 여러 명령을 묶은 주요 결과를 표시합니다. 함수 호출은 호출 직후의 PC와 LR까지만 나타냅니다. 메모리 접근은 정상적으로 끝나는 경우를 가정합니다. 생략된 항목까지 모두 보존된다는 뜻은 아닙니다.
화살표 →는 같은 항목의 전후 비교입니다. 메모리 칸에는 주소와 크기를 따로 적었습니다. SP가 줄어들면 낮은 주소 쪽을 사용하며, 화면의 위아래와 메모리 주소 방향은 별개입니다. 전처리·어셈블·링크 지시문은 CPU 실행과 구분합니다.
‘이전·다음 행’은 소스에 적힌 순서입니다. 분기·반복·호출이 있으므로 파일 위에서 아래로 실제 실행된다고 가정하지 않습니다. 값이 여러 줄에 걸쳐 이어지는 모습은 cpu-reset.S의 단계별 예제에서 보실 수 있습니다.
총 48개 줄에 그림을 붙였습니다. 기존 코드와 설명은 그 아래에서 이어집니다.
먼저 그림으로 살펴보기
두 스택의 연결
#include <linux/linkage.h>C 호출용 EFI 래퍼와 예외 복구 코드의 시작·끝을 선언할 linkage 매크로를 가져옵니다. 두 경로의 진입 방식이 달라 FUNC와 CODE 선언을 구분합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
6행 그림 · 전처리 단계
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
#include <asm/assembler.h>전역 변수 값을 PC 상대 방식으로 읽는 ldr_l 등 ARM64 공통 매크로를 가져옵니다. 이 파일은 EFI 스택 상단 변수의 값을 찾을 때 이를 사용합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
7행 그림 · 전처리 단계
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
SYM_FUNC_START(__efi_rt_asm_wrapper)커널에서 UEFI 런타임 서비스를 호출할 때 스택과 보존 레지스터를 관리하는 진입점입니다. x0는 펌웨어 함수 주소, x1은 오류 메시지에 쓸 함수 이름이며 x2~x6이 실제 서비스 인자입니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.
9행 그림 · 어셈블·링크 단계
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 함수 시작 매크로는 심벌·정렬 정보와 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
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- !는 전위 갱신입니다. 오프셋을 더한 주소로 접근하고 그 주소를 기준 레지스터에도 남깁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
mov x29, sp새 커널 스택 프레임 주소를 x29에 남깁니다. 잠시 EFI 전용 스택으로 바꿨다가 돌아올 때 이 값으로 원래 스택을 찾습니다. x29에 담기는 것은 SP의 주소값이며, SP와 스택 메모리의 내용은 그대로입니다.
11행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
stp x1, x18, [sp, #16]오류를 알릴 때 사용할 함수 이름 x1과 호출 전 x18을 보관합니다. x18은 플랫폼 전용 레지스터이므로 펌웨어가 바꾸면 안 되며, 호출 뒤 비교할 기준이 됩니다. 메모리에서는 sp + (16) 위치가 첫 레지스터 x1에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x18에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
첫 슬롯은 오류 처리용 x1, 두 번째 슬롯은 원래 x18입니다.
18행 그림 · 메모리 쓰기
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
stp x19, x20, [sp, #32]커널의 x19·x20를 저장 프레임 +32에 보관합니다. 정상적인 펌웨어는 이를 보존하지만, 실행 도중 예외가 나면 정상 복귀 절차를 건너뛰므로 별도 복구 경로가 이 저장본을 사용합니다. 메모리에서는 sp + (32) 위치가 첫 레지스터 x19에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x20에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
26행 그림 · 메모리 쓰기
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
stp x21, x22, [sp, #48]커널의 x21·x22를 저장 프레임 +48에 보관합니다. 정상적인 펌웨어는 이를 보존하지만, 실행 도중 예외가 나면 정상 복귀 절차를 건너뛰므로 별도 복구 경로가 이 저장본을 사용합니다. 메모리에서는 sp + (48) 위치가 첫 레지스터 x21에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x22에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
27행 그림 · 메모리 쓰기
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
stp x23, x24, [sp, #64]커널의 x23·x24를 저장 프레임 +64에 보관합니다. 정상적인 펌웨어는 이를 보존하지만, 실행 도중 예외가 나면 정상 복귀 절차를 건너뛰므로 별도 복구 경로가 이 저장본을 사용합니다. 메모리에서는 sp + (64) 위치가 첫 레지스터 x23에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x24에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
28행 그림 · 메모리 쓰기
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
stp x25, x26, [sp, #80]커널의 x25·x26를 저장 프레임 +80에 보관합니다. 정상적인 펌웨어는 이를 보존하지만, 실행 도중 예외가 나면 정상 복귀 절차를 건너뛰므로 별도 복구 경로가 이 저장본을 사용합니다. 메모리에서는 sp + (80) 위치가 첫 레지스터 x25에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x26에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
29행 그림 · 메모리 쓰기
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
stp x27, x28, [sp, #96]커널의 x27·x28를 저장 프레임 +96에 보관합니다. 정상적인 펌웨어는 이를 보존하지만, 실행 도중 예외가 나면 정상 복귀 절차를 건너뛰므로 별도 복구 경로가 이 저장본을 사용합니다. 메모리에서는 sp + (96) 위치가 첫 레지스터 x27에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x28에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
30행 그림 · 메모리 쓰기
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 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
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 이 매크로의 임시 레지스터는 내부에서 사용됩니다.
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, x16SP를 EFI 전용 스택으로 바꿉니다. 이후 펌웨어 함수와 그 내부 호출은 이 공간에 프레임을 만듭니다. 바뀌는 것은 SP가 담은 주소입니다. 원래 스택의 데이터가 새 주소로 복사되는 것은 아닙니다.
33행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
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
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- !는 전위 갱신입니다. 오프셋을 더한 주소로 접근하고 그 주소를 기준 레지스터에도 남깁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
정상 호출과 복귀
mov x8, x0펌웨어 함수 주소를 x8에 보관합니다. 곧 x0를 첫 번째 서비스 인자로 바꿔야 하므로 분기 주소를 미리 옮겨 둡니다. MOV는 x8의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
41행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
mov x0, x2래퍼의 x2를 펌웨어의 첫 인자 x0로 옮깁니다. 함수 포인터와 이름이라는 래퍼 전용 인자 두 개를 제외한 실제 인자를 전달합니다. MOV는 x0의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
42행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
mov x1, x3래퍼의 x3를 펌웨어의 두 번째 인자 x1로 옮깁니다. MOV는 x1의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
43행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
mov x2, x4래퍼의 x4를 펌웨어의 세 번째 인자 x2로 옮깁니다. MOV는 x2의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
44행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
mov x3, x5래퍼의 x5를 펌웨어의 네 번째 인자 x3로 옮깁니다. MOV는 x3의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
45행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
mov x4, x6래퍼의 x6를 펌웨어의 다섯 번째 인자 x4로 옮깁니다. 이 구현이 다루는 런타임 서비스는 인자가 다섯 개 이하라 추가 스택 인자 이동이 필요하지 않습니다. MOV는 x4의 기존 값을 대체합니다. 원본이 주소값이어도 그 주소의 메모리를 읽거나 복사하지 않습니다.
46행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
blr x8x8의 UEFI 런타임 서비스를 EFI 전용 스택에서 호출합니다. 서비스의 efi_status_t 결과는 x0로 돌아오며, 래퍼는 이후 검사에서도 이 값을 보존합니다. BLR은 다음 명령의 주소를 LR(x30)에 기록하고 레지스터에 든 목적지로 분기합니다.
47행 그림 · 호출과 실행 위치
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 그림은 분기 명령 자체의 효과입니다. 호출된 함수가 실행된 뒤의 레지스터 값은 그 함수의 반환·보존 규칙을 따라야 합니다.
mov x16, sp돌아온 EFI 스택 주소를 x16에 남깁니다. 원래 태스크 스택으로 돌아간 뒤 EFI 스택에 기록한 복구용 포인터를 지우기 위해 사용합니다. x16에 담기는 것은 SP의 주소값이며, SP와 스택 메모리의 내용은 그대로입니다.
49행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
mov sp, x29x29에 보관했던 커널 프레임 주소로 SP를 복원합니다. 이후 프레임 해제와 함수 이름 읽기는 원래 태스크 스택에서 수행합니다. 바뀌는 것은 SP가 담은 주소입니다. 원래 스택의 데이터가 새 주소로 복사되는 것은 아닙니다.
ABI에 따라 보존된 x29가 112바이트 커널 프레임의 시작을 가리킵니다.
50행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
str xzr, [x16, #8] // clear recorded task SP valueEFI 스택에 남긴 태스크 SP 값을 0으로 지웁니다. 현재 태스크가 아직 EFI 펌웨어를 실행 중이라는 잘못된 판정을 막기 위한 정리입니다. 실제로 x16 + (8) 주소의 메모리 8바이트를 xzr의 값으로 씁니다. 이 주소 형식은 기준 레지스터 x16 자체를 갱신하지 않습니다.
51행 그림 · 메모리 쓰기
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 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
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
cmp x2, x18호출 전 x18과 펌웨어에서 돌아온 x18을 비교합니다. 플랫폼 레지스터 보존 규약을 어긴 펌웨어인지 확인합니다. CMP는 두 피연산자의 뺄셈 결과로 NZCV 조건 플래그만 갱신하고 원래 레지스터 값은 유지합니다.
54행 그림 · 조건 플래그 계산
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 일반 레지스터에 연산 결과를 쓰지 않고 조건 플래그만 바꿉니다.
- 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를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
b.ne 0fx18이 바뀌었다면 복원·오류 보고 경로로 갑니다. 같으면 바로 펌웨어 결과 x0를 반환합니다. 앞서 계산한 NZCV 중 Z=0 조건으로 분기합니다. 이 줄은 두 값을 새로 비교하지 않으며 조건이 맞지 않으면 다음 명령으로 진행합니다.
56행 그림 · 조건별 실행 위치
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 조건이 맞는 한 경로만 실행합니다. 이 분기 명령은 NZCV를 새로 계산하지 않습니다.
ret펌웨어의 반환 상태 x0를 바꾸지 않고 커널 호출자에게 돌려줍니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.
57행 그림 · 호출과 실행 위치
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 새 복귀 주소를 LR에 기록하지 않습니다. RET 역시 메모리의 스택 값을 자동으로 꺼내는 명령은 아닙니다.
x18이 바뀐 경우
0:펌웨어가 x18을 손상시킨 경우 들어오는 오류 보고 경로의 시작입니다. shadow call stack을 사용하는 빌드는 먼저 x18을 복구합니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.
58행 그림 · 위치에 이름 붙이기
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
#ifdef CONFIG_SHADOW_CALL_STACKShadow Call Stack은 함수의 복귀 주소를 일반 스택과 별도 공간에도 보관하여 덮어쓰기 공격에 대비하는 기능입니다. ARM64에서는 x18이 그 별도 스택의 포인터이므로, 이 설정이 켜졌다면 C 오류 보고 코드로 가기 전에 손상된 x18부터 복구해야 합니다. 이 조건은 빌드 전처리 단계에서 포함할 코드를 고릅니다. CPU가 실행 중 전처리 조건을 검사하지는 않습니다.
66행 그림 · 전처리 단계
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
ldr_l x18, efi_rt_stack_topx18이 손상된 경로에서 EFI 전용 스택 상단 주소를 다시 읽습니다. 기존 x18 값은 믿을 수 없으므로 저장본을 찾을 주소부터 새로 구합니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.
67행 그림 · 주소·값 구분
함수·코드 범위: __efi_rt_asm_wrapper
이 줄을 포함하는 조건: #ifdef CONFIG_SHADOW_CALL_STACK
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 이 매크로의 임시 레지스터는 내부에서 사용됩니다.
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
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
#endifShadow Call Stack용 포인터 복구는 여기까지입니다. 설정과 관계없이 x18 보존 규약 위반에 대한 오류 보고는 이어서 수행합니다.
69행 그림 · 전처리 단계
함수·코드 범위: __efi_rt_asm_wrapper
이 줄을 포함하는 조건: #ifdef CONFIG_SHADOW_CALL_STACK
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
b efi_handle_corrupted_x18 // tail callx0의 펌웨어 상태와 x1의 함수 이름으로 efi_handle_corrupted_x18을 꼬리 호출합니다. 이 함수는 펌웨어가 x18을 손상시켰다는 메시지를 남기고 원래 상태값을 그대로 반환합니다. B를 사용하므로 래퍼로 재복귀하지 않습니다. 이 분기는 LR에 새 복귀 주소를 기록하지 않습니다.
x0의 EFI 결과와 복구한 x1을 유지한 tail call입니다.
71행 그림 · 호출과 실행 위치
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 새 복귀 주소를 LR에 기록하지 않습니다. RET 역시 메모리의 스택 값을 자동으로 꺼내는 명령은 아닙니다.
SYM_FUNC_END(__efi_rt_asm_wrapper)도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.
72행 그림 · 어셈블·링크 단계
함수·코드 범위: __efi_rt_asm_wrapper
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 끝 표기 자체가 RET나 분기를 실행하지는 않습니다.
동기 예외 복구
SYM_CODE_START(__efi_rt_asm_recover)펌웨어 실행 중 동기 예외를 커널이 복구하기로 결정했을 때 들어오는 별도 경로입니다. efi_runtime_fixup_exception이 PC를 여기로 바꾸고 x0에 EFI_ABORTED, x30에 저장된 태스크 SP를 마련합니다. 이 매크로는 정렬된 코드 시작 심벌을 정의하고 다른 오브젝트에서도 참조할 수 있게 합니다.
74행 그림 · 어셈블·링크 단계
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
mov sp, x30여기서 x30은 보통의 복귀 주소가 아니라 예외 처리 코드가 넣어 준 태스크 스택 프레임 주소입니다. 이를 SP에 넣어 펌웨어 스택을 벗어납니다. 바뀌는 것은 SP가 담은 주소입니다. 원래 스택의 데이터가 새 주소로 복사되는 것은 아닙니다.
efi.c가 예외 프레임의 x30에 원래 커널 SP를 넣어 이 경로로 보냅니다. 여기의 x30은 정상 LR 용도가 아닙니다.
75행 그림 · 값 복사
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 화살표는 값의 변화를 뜻합니다. 주소값을 옮겨도 그 주소의 메모리 내용은 복사하지 않습니다.
ldr_l x16, efi_rt_stack_top // clear recorded task SP valueEFI 런타임 스택 상단을 다시 찾아, 이제 펌웨어 실행을 벗어났다는 표시를 정리할 준비를 합니다. ldr_l은 ADRP로 심벌이 속한 페이지를 구한 뒤 LDR로 그 위치의 값을 읽는 매크로입니다. 주소를 구하는 adr_l과 달리 실제 메모리 접근이 있습니다.
77행 그림 · 주소·값 구분
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 이 매크로의 임시 레지스터는 내부에서 사용됩니다.
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
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
ldp x19, x20, [sp, #32]정상적인 펌웨어 복귀를 거치지 못한 경로이므로, 호출 전에 프레임 +32에 보관한 x19·x20를 직접 되살립니다. 커널 호출자가 기대하는 callee-saved 상태를 복원하는 단계입니다. 메모리에서는 sp + (32) 위치가 첫 레지스터 x19에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x20에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
80행 그림 · 메모리 읽기
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
ldp x21, x22, [sp, #48]정상적인 펌웨어 복귀를 거치지 못한 경로이므로, 호출 전에 프레임 +48에 보관한 x21·x22를 직접 되살립니다. 커널 호출자가 기대하는 callee-saved 상태를 복원하는 단계입니다. 메모리에서는 sp + (48) 위치가 첫 레지스터 x21에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x22에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
81행 그림 · 메모리 읽기
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
ldp x23, x24, [sp, #64]정상적인 펌웨어 복귀를 거치지 못한 경로이므로, 호출 전에 프레임 +64에 보관한 x23·x24를 직접 되살립니다. 커널 호출자가 기대하는 callee-saved 상태를 복원하는 단계입니다. 메모리에서는 sp + (64) 위치가 첫 레지스터 x23에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x24에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
82행 그림 · 메모리 읽기
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
ldp x25, x26, [sp, #80]정상적인 펌웨어 복귀를 거치지 못한 경로이므로, 호출 전에 프레임 +80에 보관한 x25·x26를 직접 되살립니다. 커널 호출자가 기대하는 callee-saved 상태를 복원하는 단계입니다. 메모리에서는 sp + (80) 위치가 첫 레지스터 x25에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x26에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
83행 그림 · 메모리 읽기
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
ldp x27, x28, [sp, #96]정상적인 펌웨어 복귀를 거치지 못한 경로이므로, 호출 전에 프레임 +96에 보관한 x27·x28를 직접 되살립니다. 커널 호출자가 기대하는 callee-saved 상태를 복원하는 단계입니다. 메모리에서는 sp + (96) 위치가 첫 레지스터 x27에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x28에 대응합니다. 이 주소 형식은 기준 레지스터 sp 자체를 갱신하지 않습니다.
84행 그림 · 메모리 읽기
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 주소 계산에 의한 기준 레지스터 갱신은 없습니다. 다만 LDR/LDP의 목적 레지스터가 기준 레지스터와 같으면 읽은 값이 그 레지스터를 덮어씁니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
ldp x29, x30, [sp], #112예외 전 커널 호출자의 프레임 포인터와 LR을 복원하고 전체 저장 프레임을 해제합니다. EFI 내부의 복귀 주소로 돌아가는 것이 아닙니다. 메모리에서는 sp 위치가 첫 레지스터 x29에 대응하고, 그보다 8바이트 높은 위치가 두 번째 레지스터 x30에 대응합니다. 대괄호 밖의 오프셋 112은 후위 갱신입니다. 기존 sp 주소로 접근한 뒤 sp에 이 값을 더합니다.
85행 그림 · 메모리 읽기
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 대괄호 밖의 오프셋은 후위 갱신입니다. 이전 주소로 접근한 뒤 기준 레지스터를 갱신합니다.
- 메모리 칸은 낮은 주소부터 나열했습니다. SP를 줄이면 더 낮은 주소의 공간을 사용합니다. 화면의 아래쪽이 곧 낮은 주소라는 뜻은 아닙니다.
ret예외 처리 코드가 준비한 EFI_ABORTED를 x0에 둔 채 원래 커널 호출자로 돌아갑니다. 커널의 C 예외 처리 쪽에서는 이후 EFI 런타임 서비스 사용도 비활성화합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.
86행 그림 · 호출과 실행 위치
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 새 복귀 주소를 LR에 기록하지 않습니다. RET 역시 메모리의 스택 값을 자동으로 꺼내는 명령은 아닙니다.
SYM_CODE_END(__efi_rt_asm_recover)도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.
87행 그림 · 어셈블·링크 단계
함수·코드 범위: __efi_rt_asm_recover
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 끝 표기 자체가 RET나 분기를 실행하지는 않습니다.
설명한 뒤 함께 생각해 볼 질문
다른 스택으로 가면 원래 함수로 못 돌아오지 않나요?
원래 프레임 주소를 x29와 EFI 스택에 남겨 둡니다. 그 값을 SP로 되돌리면 원래 프레임을 다시 읽을 수 있습니다.
복구 함수에서 x30을 SP로 쓰는 것은 이상하지 않나요?
C 예외 처리 코드가 복구용 원래 SP를 예외 프레임의 x30에 넣어 보냅니다. 이 경로에서 x30은 잠시 다른 의미로 사용됩니다.
정상 복귀에도 x19–x28을 모두 다시 읽나요?
이 래퍼의 정상 경로는 ABI에 따른 펌웨어의 보존을 전제로 합니다. 저장본을 모두 읽는 경로는 동기 예외 복구 부분입니다.
주석을 포함한 전체 원문
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)10stp x29, x30, [sp, #-112]!11mov 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*/18stp 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*/26stp x19, x20, [sp, #32]27stp x21, x22, [sp, #48]28stp x23, x24, [sp, #64]29stp x25, x26, [sp, #80]30stp x27, x28, [sp, #96]3132ldr_l x16, efi_rt_stack_top33mov sp, x1634stp 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*/41mov x8, x042mov x0, x243mov x1, x344mov x2, x445mov x3, x546mov x4, x647blr x84849mov x16, sp50mov sp, x2951str xzr, [x16, #8] // clear recorded task SP value5253ldp x1, x2, [sp, #16]54cmp x2, x1855ldp x29, x30, [sp], #11256b.ne 0f57ret580: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_STACK67ldr_l x18, efi_rt_stack_top68ldr x18, [x18, #-16]69#endif7071b efi_handle_corrupted_x18 // tail call72SYM_FUNC_END(__efi_rt_asm_wrapper)7374SYM_CODE_START(__efi_rt_asm_recover)75mov sp, x307677ldr_l x16, efi_rt_stack_top // clear recorded task SP value78str xzr, [x16, #-8]7980ldp x19, x20, [sp, #32]81ldp x21, x22, [sp, #48]82ldp x23, x24, [sp, #64]83ldp x25, x26, [sp, #80]84ldp x27, x28, [sp, #96]85ldp x29, x30, [sp], #11286ret87SYM_CODE_END(__efi_rt_asm_recover)

