DUJINLABS.COM

Linux v6.18.37 · ARM64

어셈블리 표기와 매크로 읽는 법

head.S에서 사용한 표기 설명입니다. 각 파일의 새로운 매크로는 해당 문서 하단에 정의와 치환 예를 추가했습니다.

표기 1. SYM_INNER_LABEL부터: 이름·범위·ELF 타입

SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)은 init_kernel_el 내부의 현재 위치에 init_el1이라는 이름을 붙입니다. 첫 인자는 이름, 둘째 인자는 심볼을 다른 오브젝트 파일에 공개할지 정하는 매크로입니다. 호출 명령이 아니며, 새 함수 프레임을 만들지도 않습니다. 여기의 LOCAL은 “이 위치로 분기할 수 없다”가 아니라 외부 오브젝트에 전역 심볼로 공개하지 않는다는 의미입니다.

v6.18.37 include/linux/linkage.h의 정의를 따라가면 다음과 같습니다. ASM_NL은 어셈블리 문장을 나누는 구분자이며 여기서는 세미콜론입니다. SYM_L_LOCAL(name)과 SYM_A_NONE은 별도의 어셈블리 코드를 생성하지 않습니다.

/* 원문 매크로의 확장 관계 */
SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)
    -> .type init_el1 STT_NOTYPE
       SYM_ENTRY(init_el1, SYM_L_LOCAL, SYM_A_NONE)
    -> .type init_el1 STT_NOTYPE
       init_el1:

.type은 ELF 심볼 정보를 지정하고 init_el1:은 위치를 정의합니다. 이 둘은 실행 명령 바이트를 추가하지 않습니다. 정렬도 요구하지 않습니다. 그러므로 275행 다음에는 바로 INIT_SCTLR 값을 준비하는 명령이 이어집니다. init_el2도 같은 종류의 내부 라벨이며, 두 경로 모두 init_kernel_el의 시작과 끝 사이에 속합니다.

표기이 버전에서 하는 일하지 않는 일
SYM_CODE_START(name)전역 이름 + CONFIG_FUNCTION_ALIGNMENT 정렬 + label자동 stack frame 생성 없음
SYM_CODE_START_LOCAL(name)비전역 이름 + 정렬 + label외부 공개를 위한 .globl 없음
SYM_CODE_END(name)STT_NOTYPE 및 .size 설정RET를 넣지 않음
SYM_FUNC_START(name)ARM64 override: 전역 이름 + 정렬 + label + bti c레지스터 push나 SP 조정 없음
SYM_FUNC_START_LOCAL(name)비전역 함수 시작 + 정렬 + bti cstatic C 함수 본체를 생성하는 것은 아님
SYM_FUNC_END(name)STT_FUNC 및 .size 설정호출자에게 돌아가는 명령 없음
SYM_INNER_LABEL(name, linkage)함수/코드 범위 안의 STT_NOTYPE label별도 함수 크기나 BTI를 자동 추가하지 않음

특히 SYM_FUNC_START는 include/linux/linkage.h의 일반 정의만 읽으면 불완전합니다. 그보다 먼저 포함되는 arch/arm64/include/asm/linkage.h가 정의를 덮어씁니다. ARM64에서는 다음과 같이 bti c까지 나옵니다. 이 헤더는 ARM64용 정의를 항상 적용합니다. 실제 BTI 보호의 강제 여부는 CPU 기능과 매핑/제어 설정에 따르지만 명령의 삽입 자체와 구분해야 합니다.

/* init_kernel_el 시작 부분의 구조적 확장 */
.globl init_kernel_el
.balign CONFIG_FUNCTION_ALIGNMENT
init_kernel_el:
    bti c
    mrs x1, CurrentEL

/* SYM_FUNC_END(init_kernel_el)의 역할 */
.type init_kernel_el STT_FUNC
.set .L__sym_size_init_kernel_el, .-init_kernel_el
.size init_kernel_el, .L__sym_size_init_kernel_el

위의 CONFIG_FUNCTION_ALIGNMENT는 특정 .config에서 결정되는 바이트 정렬 값입니다. 여기서는 .config를 지정해 빌드하지 않았으므로 정렬 값은 기호로 표기합니다. 점 .은 assembler의 현재 위치이고 .-init_kernel_el은 함수 시작부터 현재 위치까지의 바이트 수입니다. 실제 제어 이동은 코드에 적힌 B, BR, ERET 등의 명령이 담당합니다.

SYM_L_LOCAL은 .local name을 출력하는 구현이 아니라 아무것도 출력하지 않는 구현입니다. 전역 선언이 없는 이 심볼은 local binding을 유지합니다. 이것과 .Lfoo라는 assembler의 임시 라벨 이름 관례는 별개입니다. LOCAL 심볼은 디버깅/심볼 정보에 남을 수 있지만 .L로 시작하는 임시 라벨은 보통 최종 심볼 테이블에 남기지 않습니다.

표기 2. 전처리·어셈블·링크·실행은 서로 다른 단계

head.S의 대문자 S는 C 전처리기를 먼저 거친다는 뜻입니다. #include는 헤더의 정의를 가져오고, #if/#ifdef/#ifndef는 빌드 설정에 따라 소스를 남기거나 제거합니다. #error는 빌드를 중단합니다. CPU가 실행 중에 이 줄을 읽고 판단하는 것이 아닙니다.

단계이 파일에서 보는 예결과
C 전처리SYM_* / CPU_LE / #ifdef텍스트 치환과 조건별 소스 선택
어셈블.macro / .if / adrp / b매크로 전개, 명령 인코딩, relocation 정보
링크_text / early_init_stack / :lo12:섹션 배치와 심볼 주소 확정
부팅 중 패치alternative_ifcapability에 맞는 명령열 선택·교체
CPU 실행mrs / str / b.eq / eret레지스터·메모리·PC 상태 변화

CPU_LE(code...)와 CPU_BE(code...)는 CONFIG_CPU_BIG_ENDIAN에 따라 인자 명령을 남기거나 없앱니다. runtime endian 검사와 이 전처리 선택은 다릅니다. little-endian 빌드에서는 아래 두 줄 중 첫 번째의 TBNZ만 남습니다. 그 TBNZ가 실행될 때 실제 SCTLR.EE를 검사합니다.

CPU_LE( tbnz x19, #SCTLR_ELx_EE_SHIFT, 1f )
CPU_BE( tbz  x19, #SCTLR_ELx_EE_SHIFT, 1f )

/* little-endian 빌드 후 */
tbnz x19, #SCTLR_ELx_EE_SHIFT, 1f

매크로의 확장 결과는 빌드 설정에 따라 달라집니다. 아래 예시는 헤더 정의를 풀어 쓴 것으로, 특정 .config의 빌드 결과는 아닙니다.

표기 3. __HEAD·__INIT와 데이터 지시문

__HEAD.section ".head.text","ax", __INIT.section ".init.text","ax"로 확장됩니다. section은 이후 출력할 코드·데이터를 어느 입력 섹션에 놓을지 지정합니다. ELF flag에서 a는 메모리에 적재되는 allocatable, x는 executable, w는 writable입니다. 이 문자열이 즉시 SCTLR이나 페이지 테이블 권한을 바꾸지는 않습니다.

head.S의 .section ".idmap.text","a"는 원문 그대로 a만 적혀 있습니다. 링커 스크립트가 이 입력 섹션을 모으고, 초기 페이지 테이블 코드가 해당 주소 범위의 실제 실행 권한을 설정합니다. identity mapping의 VA=PA 관계 역시 .section 한 줄이 만드는 것이 아닙니다.

지시문크기/작용head.S에서의 예
.text일반 text 입력 섹션으로 전환__secondary_switched 앞
.quad 08바이트 값 출력Image offset 및 예약 필드
.long symbol4바이트 값 출력PE header offset
.ascii string문자열 바이트 출력, 자동 NUL 없음ARM64_IMAGE_MAGIC
.balign n다음 위치를 n바이트 경계로 정렬SYM_* 시작 매크로
.type / .sizeELF 심볼의 타입/크기 기록SYM_* 끝과 내부 라벨
.set name, exprassembler 심볼에 식의 값 지정함수 크기 및 매크로 보조 심볼
.pushsection / .popsection다른 섹션을 잠시 사용한 뒤 복원BUG table 및 alternative metadata

AArch64 GAS의 .align 2는 2바이트가 아니라 2의 2제곱, 즉 4바이트 정렬입니다. .balign 4와 단위가 다릅니다. .quad.long은 명령이 아니라 데이터 바이트를 내보냅니다. Image의 B primary_entry가 이 데이터를 건너뛰므로 CPU가 크기 필드를 명령으로 실행하지 않습니다.

표기 4. 0f·1b·.macro·역슬래시를 읽는 법

숫자 라벨 1:은 여러 번 사용할 수 있습니다. 1f는 현재 위치에서 앞으로 만나는 가장 가까운 1번 라벨, 1b는 뒤로 거슬러 가장 가까운 1번 라벨입니다. 여기 f/b는 16진수 접미사나 CPU flag 이름이 아닙니다. 반면 pen:은 이름 있는 라벨이고 b pen이 그 위치로 분기합니다.

1:  wfe
    wfi
    b 1b           // 바로 위 1:로 되돌아감

    cbz x0, 2f     // 아래쪽 2:로 이동
    ...
2:  ret

.macro init_cpu_task tsk,tmp1,tmp2는 assembler 매크로 정의입니다. 정의 자체를 호출해서 실행하는 것이 아니라 사용 위치에 본문을 펼칩니다. \tsk는 매크로 인자를 대입합니다. init_cpu_task x4,x5,x6이라면 msr sp_el0,\tskmsr sp_el0,x4가 됩니다. .endm은 정의의 끝입니다. 매크로를 펼치는 행위는 BL도 아니고 LR을 바꾸지도 않습니다.

init_cpu_task 안의 w\tmp2는 눈여겨봐야 합니다. tmp2=x6을 넣으면 문자열은 wx6이 됩니다. assembler.h 앞부분이 .irp n,0,...,30 반복과 wx\n .req w\n으로 wx6을 w6의 alias로 미리 정의합니다. 그래서 실제로는 32비트 CPU 번호를 w6으로 읽고 뒤에서 x6을 인덱스로 사용할 수 있습니다. 단순한 오타가 아닙니다.

매크로 내부의 \@는 매크로 확장마다 고유한 번호를 만들 때 사용합니다. 같은 매크로를 여러 번 펼쳐도 .Lskip_\@ 라벨들이 충돌하지 않습니다. \sym\()_lo32\()는 인자 이름과 뒤 문자열을 구분하는 빈 구분자입니다. sym=_kernel_size_le이면 _kernel_size_le_lo32가 됩니다.

.req는 레지스터 별명을 만들고 .unreq는 그 별명을 해제합니다. 이 파일에서 쓰는 lr은 assembler.h에서 x30으로 정한 별명입니다. .if/.else/.endif는 assembler 단계의 조건부 처리로, #if 계열의 C 전처리와 시점이 다르며 실행 중 분기 명령도 아닙니다.

표기 5. adr_l·str_l·mov_q·le64sym의 실제 확장

adr_l은 심볼 주소를 만드는 두 개의 명령으로 확장되는 매크로입니다. ADRP는 현재 PC의 4 KiB 경계를 기준으로 대상의 4 KiB page 주소를 만들고 ADD가 하위 12비트를 더합니다. 여기 명령의 4 KiB 단위는 커널의 CONFIG_ARM64_64K_PAGES와 별개입니다. 앞 본문의 “페이지 정렬”은 ADRP 명령의 주소 생성 단위와 실제 MMU granule을 구별해서 읽어야 합니다.

adr_l x0, boot_args
/* 확장 */
adrp x0, boot_args
add  x0, x0, :lo12:boot_args

str_l x21, __fdt_pointer, x5
/* 확장 */
adrp x5, __fdt_pointer
str  x21, [x5, :lo12:__fdt_pointer]

:lo12:는 assembler/linker에 심볼 주소의 하위 12비트를 이 위치의 relocation으로 사용하도록 지시하는 표기입니다. CPU가 실행할 때 문자열을 해석하는 것이 아닙니다. str_l의 세 인자는 저장할 값, 목적 심볼, 주소를 만들 임시 레지스터입니다. x5의 기존 값은 주소 계산 과정에서 바뀌지만, x21의 값은 그대로 유지됩니다. 대상 레지스터가 x이면 8바이트, w이면 4바이트 store다.

ldr x8,=__primary_switched의 등호 표기는 주소 상수를 로드하는 assembler 의사 명령입니다. ldr x8,[x0]처럼 x0가 가리키는 데이터를 읽는 문법과 다릅니다. head.S에서는 최종 분기 대상 주소를 literal로 얻고 BR합니다. ADRP의 PC-relative 계산과 절대 주소 literal의 재배치가 서로 다른 이유입니다.

mov_q는 64비트 상수를 만들기 위해 16비트 조각을 MOVZ/MOVK 계열로 구성합니다. source의 .if가 상수의 크기와 부호를 검사하여 2–4개 wide-immediate 명령을 만듭니다. 메모리에서 8바이트를 읽는 함수가 아닙니다. 다음은 양의 64비트 상수에 대한 의미상 확장 예입니다.

mov_q x0, 0x123456789abcdef0
/* 16비트 조각으로 구성하는 확장 의미 */
movz x0, #0x1234, lsl #48
movk x0, #0x5678, lsl #32
movk x0, #0x9abc, lsl #16
movk x0, #0xdef0

실제 매크로는 :abs_g3:, :abs_g2_nc: 등의 relocation modifier를 사용합니다. g0/g1/g2/g3은 하위부터 16비트 묶음이며, MOVK는 다른 비트를 유지하고 해당 조각만 바꿉니다. 위 예시는 실제 빌드 결과를 캡처한 disassembly가 아니라 이 상수에 대한 확장 의미입니다.

le64sym은 이름 뒤에 _lo32와 _hi32를 붙인 두 심볼을 .long으로 출력합니다. 예를 들어 le64sym _kernel_size_le_kernel_size_le_lo32_kernel_size_le_hi32를 차례로 씁니다. endian 처리는 kernel/image.h의 DEFINE_IMAGE_LE64/DATA_LE32가 링크 시 값에 수행합니다. 그래서 빅 엔디언 커널에서도 Image 규약의 little-endian 필드를 만들 수 있고, 런타임 재배치 대상 64비트 포인터와도 구분됩니다.

표기 6. alternative_if는 매번 실행되는 if문이 아니다

alternative 매크로는 기본 명령열, 교체 명령열, 두 위치와 CPU capability를 연결하는 metadata를 만듭니다. 커널의 alternative 패치 단계가 capability에 맞춰 명령을 바꿉니다. 실행할 때마다 이 자리에서 C의 if처럼 기능 레지스터를 검사하는 코드가 자동으로 붙는 것은 아닙니다.

alternative_if ARM64_HAS_VA52
    bl __cpu_secondary_check52bitva
alternative_else_nop_endif

이 경우 VA52가 선택되는 쪽은 BL이고 다른 쪽은 같은 길이의 NOP다. alternative_if는 해당 capability가 있는 쪽의 명령을 먼저 적는 형식이고, alternative_if_not은 없는 쪽의 명령을 먼저 적는 형식입니다. 이름의 if/not을 무시하면 기본 경로와 교체 경로를 반대로 읽게 됩니다.

alternative_else_nop_endif는 내부에서 alternative_else, 앞쪽 길이에 맞춘 nops, alternative_endif를 이어 붙입니다. AArch64 명령 한 개는 4바이트이므로 여기 BL 한 개의 대안은 NOP 한 개입니다. metadata의 orig/alt offset, capability, 길이를 통해 교체 범위를 찾습니다. 외부에서 교체 블록 내부로 분기하는 것은 원본 매크로의 제약에도 금지되어 있습니다.

367행의 #ifdef CONFIG_ARM64_VA_BITS_52는 이 블록 자체를 빌드에 넣는지 결정하고, 안쪽 ARM64_HAS_VA52는 실행할 커널이 선택한 capability와 연결됩니다. 빌드할 때 코드를 포함할지 결정하는 과정과 부팅 중 명령을 교체하는 과정을 구분해야 합니다. 패치가 적용되는 정확한 시점은 alternative 초기화 함수를 호출하는 코드에서 확인해야 합니다.

표기 7. 시스템 레지스터·MMU 매크로

MRS는 시스템 레지스터에서 일반 레지스터로 읽고, MSR는 반대 방향으로 씁니다. mrs x1,CurrentELmsr sctlr_el1,x0에서 인자 순서가 반대인 이유입니다. 일반 메모리 load/store와 다른 명령이며, 접근 가능한 EL과 trap 설정의 제한을 받습니다.

mrs_s/msr_s는 SYS_*의 수치 인코딩과 일반 레지스터 번호를 조합해 MRS/MSR 명령을 직접 출력하는 assembler 매크로입니다. 도구가 모든 새 시스템 레지스터의 이름을 알지 못해도 인코딩으로 쓸 수 있게 합니다. _s를 플래그 갱신 접미사나 secure 전용 명령으로 해석하지 않습니다. msr_s SYS_SCTLR_EL12,x1 역시 그 레지스터를 대상으로 하는 MSR 한 개의 인코딩입니다.

매크로입력·임시 레지스터확장되는 명령
phys_to_ttbr dst,phys결과 dst, 물리 주소 physPA_BITS_52이면 ORR 및 BADDR mask, 아니면 MOV
load_ttbr1 pgtbl,tmp1,tmp2루트 PA와 임시 두 개phys_to_ttbr → offset_ttbr1 → MSR TTBR1 → ISB
set_sctlr_el1 regSCTLR 값MSR → ISB → IC IALLU → DSB NSH → ISB
pre_disable_mmu_workaround인자 없음해당 Falkor erratum 설정에서 ISB, 아니면 빈 매크로
set_sctlr_el1 x0
/* assembler.h의 확장 */
msr sctlr_el1, x0
isb
ic iallu
dsb nsh
isb

phys_to_ttbr의 52비트 경로는 orr dst,phys,phys,lsr #46 후 TTBR_BADDR_MASK_52로 마스크합니다. 높은 PA 비트를 TTBR 형식에 배치하는 연산입니다. load_ttbr1의 tmp 인자가 원래 pgtbl 레지스터와 같은 경우도 허용하여 head.S는 load_ttbr1 x1,x1,x3을 씁니다. 레지스터 이름이 반복된다고 세 번의 독립적인 주소가 들어온다는 뜻이 아닙니다.

offset_ttbr1의 48/52비트 보정은 VA_BITS_52이면서 비 LPA2인 구성에서 조건부로 들어갑니다. 이런 조건은 실제 빌드 설정에 따라 명령 수를 바꿉니다. 매크로 한 줄을 항상 단일 CPU 명령으로 세면 코드 크기와 실행 순서를 틀리게 계산합니다.

표기 8. EFI·태스크·EL2·실패 매크로의 구분

표기정의·입력실제 효과와 본문의 연결
efi_signature_nopefi-header.S, 인자 없음EFI이면 MZ 인코딩을 만드는 CCMP, 아니면 NOP; 본문 02
__EFI_PE_HEADERefi-header.S, 인자 없음CONFIG_EFI에 따른 PE/COFF header 데이터 출력; 함수 호출 아님
init_cpu_task tsk,tmp1,tmp2head.S 자체의 .macroSP_EL0/current, 태스크 스택, 최종 frame, SCS, per-CPU 설치; 본문 06
scs_load_currentscs.hSHADOW_CALL_STACK이면 current에서 SCS 포인터 로드, 아니면 빈 확장
set_this_cpu_offset srcassembler.halternative에 따라 TPIDR_EL1 또는 TPIDR_EL2에 src 기록
ptrauth_keys_init_cpu tsk,tmp1,tmp2,tmp3asm_pointer_auth.hcapability/ID 확인, SCTLR 인증 enable 및 태스크 kernel key 설치, ISB
init_el2_hcr valel2_setup.hHCR 설정과 VHE-only 판별; x0/x1 사용; 본문 09
init_el2_stateel2_setup.h여러 EL2 초기화 매크로를 사용 위치에서 펼침; x0/x1/x2의 기존 값 변경; 부록 I
update_early_cpu_boot_status status,tmp1,tmp2head.S 자체의 .macroSTR → DMB → IVAC; 본문 14
ASM_BUG()asm-bug.hBUG metadata와 BRK trap; 반환 불가 지점 검사

ASM_BUG()는 ASM_BUG_FLAGS(0)이고 마지막 실행 명령은 brk BUG_BRK_IMM, 이 버전에서 즉시값은 0x800입니다. CONFIG_GENERIC_BUG이면 __bug_table에 상대 위치와 flag를 기록하고, DEBUG_BUGVERBOSE이면 파일·행 정보도 넣습니다. metadata와 BRK는 별개입니다. “함수를 끝내는 장식”이 아니라 도달하면 예외를 일으키는 실제 명령이 포함됩니다.

init_cpu_task는 사용 위치에 코드가 펼쳐지는 매크로입니다. bl __cpu_setup, bl __pi_create_init_idmap, bl finalise_el2는 실제 호출입니다. __pi_는 PI 빌드에서 심볼 이름에 붙인 접두사이지 ISA 명령 접두사가 아닙니다. 각 매크로의 세부 동작은 본문과 부록에서 설명합니다.

표기 9. #상수, 구조체 오프셋, 링크 심볼

명령 인자의 #는 즉시값 표기입니다. #16은 숫자 16이고 #TSK_STACK은 빌드에서 결정된 숫자 상수입니다. 소스 줄 맨 앞의 #include/#if와 같은 용법이 아닙니다. 또한 .quad 0에서 0은 데이터 값이며, adr_l x0,boot_args의 boot_args는 주소를 가진 심볼입니다.

종류값이 정해지는 근거
구조체 필드 오프셋TSK_STACK, TSK_TI_CPU, CPU_BOOT_TASKasm-offsets.c의 offsetof
구조체 크기/위치PT_REGS_SIZE, S_STACKFRAME, S_STACKFRAME_TYPEsizeof(pt_regs), offsetof(pt_regs,...)
빌드 설정 크기THREAD_SIZE, PAGE_SIZE, CONFIG_FUNCTION_ALIGNMENT설정과 헤더 정의
bit maskSCTLR_ELx_M, SCTLR_ELx_C각각 bit 0, bit 2
bit indexSCTLR_ELx_EE_SHIFTTBNZ/TBZ에서 몇 번째 비트인지 지정
상태 조합INIT_PSTATE_EL1D/A/I/F mask와 EL1h 모드
메모리/링크 심볼_text, vectors, init_task, early_init_stack오브젝트와 링커 스크립트의 배치

TSK_STACK은 stack의 크기가 아니라 task_struct 안에서 stack 필드가 놓인 바이트 오프셋입니다. PT_REGS_SIZE는 반대로 구조체 전체 크기입니다. 둘을 같은 종류의 주소로 읽으면 LDR와 SP 계산이 이해되지 않습니다. asm-offsets.c에서 DEFINE(...)으로 C의 레이아웃을 출력하여 생성 헤더에 반영합니다. 특정 .config의 생성된 asm-offsets.h를 빌드하지 않은 상태에서 숫자 오프셋을 고정해 적지 않습니다.

INIT_PSTATE_EL1은 이 버전 ptrace.h에서 PSR_D_BIT + PSR_A_BIT + PSR_I_BIT + PSR_F_BIT + PSR_MODE_EL1h에 해당하는 비트 OR 조합입니다. D/A/I/F는 debug/SError/IRQ/FIQ 마스크입니다. EL1h는 EL1의 SP를 사용하는 모드입니다. SPSR에 이 값을 넣고 ERET하므로 복귀할 때의 실행 상태가 커널이 요구하는 값으로 설정됩니다. 단지 EL 번호만 바꾸는 상수가 아닙니다.

BOOT_CPU_MODE_EL1=0xe11, BOOT_CPU_MODE_EL2=0xe12는 커널의 소프트웨어 표식이며 CurrentEL 시스템 레지스터 인코딩과 동일하지 않습니다. BOOT_CPU_FLAG_E2H는 bit 32이므로 mov x20,x0는 보존하지만 str w0는 저장하지 않습니다. 비트 위치 SHIFT와 이미 이동된 mask를 구분하는 습관도 필요합니다. TBNZ는 비트 번호를 받고, TST는 mask를 받습니다.

CPU_STUCK_IN_KERNEL은 실패 상태의 기본 부분이고 CPU_STUCK_REASON_52_BIT_VA와 CPU_STUCK_REASON_NO_GRAN은 원인 부분입니다. 원본의 OR 연산자로 둘을 합쳐 status 즉시값으로 전달합니다. 이는 CPU 명령이 아니라 커널의 상태 코드 조합입니다. INIT_SCTLR_EL1_MMU_ON/OFF 및 EL2_MMU_OFF도 같은 방식으로 sysreg.h에 정의된 제어 비트 조합입니다. ON에는 M/C/I가 포함되고 OFF는 단순히 정수 0이 아닙니다. 초기 엔디언·필수 제어 비트를 포함하므로 이름만 보고 mov x0,0으로 치환하지 않습니다.

표기 10. 이 파일에 나오는 명령과 NZCV

명령읽는 법이 파일에서 주의할 점
mov dst,src값 복사/상수 구성의 aliassrc가 주소인지 데이터인지는 문맥으로 결정
add / sub덧셈 / 뺄셈여기처럼 S 없는 형식은 NZCV 유지
and / orr / eor / bicAND / OR / XOR / 비트 지우기이 형식은 NZCV 유지; BIC는 두 번째 mask를 반전하여 AND
cmp a,ba-b 결과의 flags만 남김결과 값을 일반 레지스터에 쓰지 않음
tst a,maskAND 결과의 flags만 남김record_mmu_state에서 C=0인지 Z로 전달
csel dst,a,b,cond조건 참이면 a, 아니면 b 선택자체는 NZCV를 갱신하지 않음
ubfx dst,src,lsb,width비트 필드를 0 확장하여 추출TGRAN 4비트 추출
sbfx dst,src,lsb,width같은 필드를 부호 확장하여 추출LPA2 기능값 비교에서 부호 의미 유지
ldr / str레지스터 1개 load/storex면 8 B, w면 4 B
ldp / stp레지스터 2개 load/storex 두 개면 16 B; 주소 갱신은 별도 문법
b label / br reg직접 / 레지스터 간접 분기LR을 새로 만들지 않음
bl label / blr reg직접 / 레지스터 간접 호출x30에 다음 명령 주소 저장
ret보통 x30으로 복귀SPSR을 복원하지 않음
eretELR와 SPSR을 사용하는 예외 복귀init_kernel_el의 상태 전환
cbz / cbnz레지스터가 0 / 비0이면 분기NZCV 변경 없음
tbz / tbnz지정 비트가 0 / 1이면 분기NZCV 변경 없음

조건부 B는 기존 NZCV를 검사합니다. EQ는 Z=1, NE는 Z=0입니다. LT/GE는 signed 비교로 각각 N≠V / N=V이고, GT는 Z=0이면서 N=V다. helper에서 보이는 LO는 unsigned lower로 C=0을 검사합니다. CMP 뒤의 LT와 주소 순회 뒤의 LO를 무조건 같은 “작다”로 해석하지 않습니다.

cmp x19, #CurrentEL_EL2   // Z는 EL2 여부
mrs x19, sctlr_el1       // NZCV 유지
b.ne 0f                 // 위 CMP의 결과 사용
...
tst x19, #SCTLR_ELx_C    // 여기 도달하면 Z의 의미가 C==0으로 바뀜
and x19, x19, #SCTLR_ELx_M  // NZCV 유지
csel x19, xzr, x19, eq   // 바로 위 AND가 아니라 TST의 Z를 사용

N은 음수, Z는 zero, C는 carry/unsigned 비교, V는 signed overflow flag다. 플래그를 마지막으로 바꾼 명령이 무엇인지 추적해야 합니다. 코드에서 가까운 줄이 항상 조건 플래그를 설정한 명령인 것은 아닙니다. record_mmu_state의 엔디언 불일치 분기는 TST를 거치지 않아서 더 앞의 CMP를 계속 사용합니다.

표기 11. [주소]·!·LSL·SP·XZR

표기정확한 의미실제 예
[x0]x0 값이 가리키는 메모리stp x21,x1,[x0]
[x0,#16]x0+16에서 접근, x0는 그대로두 번째 boot_args STP
[sp,#-16]!sp를 16 감소시킨 새 주소에서 접근FP/LR 저장의 pre-index
[sp],#16원래 sp에서 접근한 뒤 16 증가FP/LR 복원의 post-index
[base,index,lsl #3]base + index×8__per_cpu_offset[cpu] 읽기
xN / wN같은 레지스터의 64/32비트 이름wN에 쓰면 상위 32비트 0
xzr / wzr읽으면 0, 쓰기는 버림mov x29,xzr, str wzr,...
sp현재 선택된 stack pointerSP_EL0와 언제나 같은 저장소가 아님
lrx30의 assembler 별명BL/BLR의 복귀 주소

STP 자체가 push라는 이름의 명령은 아닙니다. [sp,#-16]!와 조합하여 stack push 효과를 만드는 것입니다. head.S의 boot_args STP는 같은 명령이지만 SP를 전혀 사용하지 않습니다. lsl #3도 이 주소식에서는 별도의 LSL 명령이 아니라 scaled register addressing의 일부입니다.

주소 레지스터를 읽는 것과 그 주소의 메모리를 읽는 것을 나눠야 합니다. mov x21,x0는 주소 값을 복사하고, ldr x2,[x0,#CPU_BOOT_TASK]는 x0+offset의 메모리에서 태스크 포인터를 읽습니다. 둘 다 최종 결과가 포인터여도 접근하는 메모리와 오류가 발생할 수 있는 위치이 다릅니다.

표기 12. DMB·DSB·ISB·DC·대기 명령

명령/인자역할사용 위치
dmb sy지정 범위의 메모리 접근과 캐시 관리 작업 사이의 순서 보장MMU off 쓰기 후 invalidate 전
dsb sy / dsb nsh지정 범위·종류의 선행 작업 완료를 기다리는 동기화캐시 helper 끝, MMU 전환
isb뒤 명령이 갱신된 실행 문맥을 보도록 동기화SCTLR/TCR/VBAR 변경 주변
dc ivac,addr주소의 D-cache line을 PoC까지 invalidate실패 상태 및 캐시 helper
dc cvac,addr주소의 D-cache line을 PoC까지 cleanMMU on 진입 코드 정리
dc civac,addrclean + invalidate부분 cache line 경계
ic iallu로컬 I-cache 전체 무효화set_sctlr 매크로
tlbi vmalle1해당 번역 체계의 TLB invalidate__cpu_setup/테이블 교체
wfeevent를 기다릴 수 있는 hintholding pen
wfiinterrupt를 기다릴 수 있는 hint실패 CPU 대기
bti c간접 호출이 도착해도 되는 위치를 표시ARM64 SYM_FUNC_START 확장

SY는 full-system 범위, NSH는 non-shareable 범위입니다. helper C 코드의 ISHST는 inner-shareable store 범위를 가리킵니다. DMB가 자동으로 dirty data를 clean하지 않고, ISB가 페이지 테이블 데이터를 메모리에 쓰지도 않습니다. DC/TLBI는 캐시·TLB를 관리하고, 배리어는 필요한 실행 순서와 완료 시점을 보장합니다.

WFE/WFI는 다음 명령이 없다는 뜻의 CPU 종료 명령이 아닙니다. event/interrupt나 구현상 허용된 이유로 실행이 이어질 수 있으므로 head.S는 비교를 다시 하거나 B로 대기 루프를 만듭니다. 반대로 ASM_BUG가 내는 BRK는 의도적으로 동기 예외를 일으키는 명령이며 대기와 다릅니다.

맨 위로 ↑