← KernelDUJINLABS.COM

Linux 6.18.37 LTS · x86-64 boot entry

x86-64 커널의 32비트 진입과 long mode·가상 주소 전환

startup_32, compressed startup_64, vmlinux startup_64를 한 경로로 연결해 boot protocol, KASLR 배치, 4/5-level paging과 CR3 전환을 읽습니다.

Series
01 / 4
Baseline
Linux 6.18.37 LTS
Architecture
x86-64
Source
arch/x86/boot/compressed/head_64.S

부트로더가 넘긴 CPU와 boot_params가 언제부터 x86-64 커널의 정상 C 실행 환경이 되는가?

x86-64 부팅에는 이름이 같은 startup_64가 두 개 있다. arch/x86/boot/compressed/head_64.S의 함수는 압축 해제기 실행 환경을 만들고, arch/x86/kernel/head_64.S의 함수는 압축이 풀린 vmlinux의 page table과 high virtual address 실행을 준비한다. 두 symbol을 같은 함수로 보면 relocation이 두 번 등장하는 이유를 설명할 수 없다.

32비트 boot protocol entry의 offset 0과 64비트 entry의 offset 0x200은 외부 부트로더가 의존하는 ABI다. 첫 경로는 4GiB 아래의 임시 주소 제약을 받고 직접 PAE와 long mode를 준비한다. 두 번째 경로는 이미 long mode와 identity mapping을 받은 대신 loader가 제공한 mapping 범위를 검증해야 한다.

핵심 상태는 세 가지다. %rsi가 가리키는 boot_params, 압축 해제 source와 destination이 겹치지 않는 메모리 배치, 그리고 CR3가 바뀐 뒤에도 다음 instruction fetch와 stack access가 성공하는 page-table overlap이다. 마지막 UART 문자열보다 이 세 값을 먼저 기록하면 초기 무출력 정지를 좁힐 수 있다.

들어오는 상태와 내보내는 상태

경계입력이 경계가 완성하는 상태
32-bit entryprotected mode, %esi=boot_paramsGDT, PAE, temporary page table, long mode
compressed 64-bit entrylong mode, identity map, %rsi=boot_paramsdecompress destination과 4/5-level paging
vmlinux startup_64압축 해제된 image, identity mapearly_top_pgt와 kernel virtual address
C entry정상 stack, 초기 page table, 보존된 boot_paramsx86_64_start_kernel()

주소와 register를 먼저 그려 본다

그림 1. 두 개의 startup_64가 맡는 서로 다른 일

bzImage / compressed

startup_32boot protocol 32-bit entry
startup_64decompressor의 stack·page table
extract_kernelvmlinux를 목적지에 해제

decompressed vmlinux

startup_64물리/가상 offset 계산
early_top_pgtCR3에 새 root 적재
common_startup_64high virtual address에서 계속

압축 image의 entry와 압축 해제된 vmlinux entry를 주소 공간 경계로 분리한다.

그림 2. 압축 해제 전후의 대표 메모리 배치
높은 주소
decompressed kernel destination2MiB 정렬·KASLR 후보
decompression output growth아래 주소부터 확장
compressed payload copybuffer 끝으로 역방향 복사
boot stack / page tables출력과 비중첩
boot_params / cmdlineloader가 넘긴 입력
낮은 주소

source를 buffer 끝으로 옮기는 이유는 출력이 커지면서 아직 읽지 않은 압축 입력을 덮는 일을 막기 위해서다.

그림 3. CR3 전환 때 동시에 유효해야 하는 주소
identity mapping현재 RIP와 early page table 접근
early_top_pgt fixup실제 물리 load offset 반영
mov %rax, %cr3활성 translation root 교체
high mappingcommon_startup_64의 링크 VA
x86_64_start_kernelC 초기화 진입

CR3 write 한 줄의 전후에서 PC, SP와 page-table page 자체가 모두 접근 가능해야 한다.

32비트 boot protocol 진입과 실제 load base 계산

startup_32는 loader의 DF와 interrupt 상태를 정리하고, call/pop으로 현재 실행 물리 주소를 구한 뒤 long mode에 필요한 GDT와 PAE를 준비한다.

원본: arch/x86/boot/compressed/head_64.S 82-170줄

82SYM_FUNC_START(startup_32)
83	/*
84	 * 32bit entry is 0 and it is ABI so immutable!
85	 * If we come here directly from a bootloader,
86	 * kernel(text+data+bss+brk) ramdisk, zero_page, command line
87	 * all need to be under the 4G limit.
88	 */
89	cld
90	cli
91 
92/*
93 * Calculate the delta between where we were compiled to run
94 * at and where we were actually loaded at.  This can only be done
95 * with a short local call on x86.  Nothing  else will tell us what
96 * address we are running at.  The reserved chunk of the real-mode
97 * data at 0x1e4 (defined as a scratch field) are used as the stack
98 * for this calculation. Only 4 bytes are needed.
99 */
100	leal	(BP_scratch+4)(%esi), %esp
101	call	1f
1021:	popl	%ebp
103	subl	$ rva(1b), %ebp
104 
105	/* Load new GDT with the 64bit segments using 32bit descriptor */
106	leal	rva(gdt)(%ebp), %eax
107	movl	%eax, 2(%eax)
108	lgdt	(%eax)
109 
110	/* Load segment registers with our descriptors */
111	movl	$__BOOT_DS, %eax
112	movl	%eax, %ds
113	movl	%eax, %es
114	movl	%eax, %fs
115	movl	%eax, %gs
116	movl	%eax, %ss
117 
118	/* Setup a stack and load CS from current GDT */
119	leal	rva(boot_stack_end)(%ebp), %esp
120 
121	pushl	$__KERNEL32_CS
122	leal	rva(1f)(%ebp), %eax
123	pushl	%eax
124	lretl
1251:
126 
127	/* Setup Exception handling for SEV-ES */
128#ifdef CONFIG_AMD_MEM_ENCRYPT
129	call	startup32_load_idt
130#endif
131 
132	/* Make sure cpu supports long mode. */
133	call	verify_cpu
134	testl	%eax, %eax
135	jnz	.Lno_longmode
136 
137/*
138 * Compute the delta between where we were compiled to run at
139 * and where the code will actually run at.
140 *
141 * %ebp contains the address we are loaded at by the boot loader and %ebx
142 * contains the address where we should move the kernel image temporarily
143 * for safe in-place decompression.
144 */
145 
146#ifdef CONFIG_RELOCATABLE
147	movl	%ebp, %ebx
148	movl	BP_kernel_alignment(%esi), %eax
149	decl	%eax
150	addl	%eax, %ebx
151	notl	%eax
152	andl	%eax, %ebx
153	cmpl	$LOAD_PHYSICAL_ADDR, %ebx
154	jae	1f
155#endif
156	movl	$LOAD_PHYSICAL_ADDR, %ebx
1571:
158 
159	/* Target address to relocate to for decompression */
160	addl	BP_init_size(%esi), %ebx
161	subl	$ rva(_end), %ebx
162 
163/*
164 * Prepare for entering 64 bit mode
165 */
166 
167	/* Enable PAE mode */
168	movl	%cr4, %eax
169	orl	$X86_CR4_PAE, %eax
170	movl	%eax, %cr4

82-170줄 주석

82SYM_FUNC_START(startup_32)

linker와 assembler가 진입 symbol, section 또는 정렬을 확정하는 지시자다. runtime 명령은 아니지만 entry address와 배치를 결정한다.

83/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

84* 32bit entry is 0 and it is ABI so immutable!

offset 0은 Linux x86 boot protocol의 고정 ABI다. 코드를 앞에 삽입해 이 위치를 바꾸면 부트로더는 다른 byte를 entry로 실행한다.

85* If we come here directly from a bootloader,

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

86* kernel(text+data+bss+brk) ramdisk, zero_page, command line

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

87* all need to be under the 4G limit.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

88*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

89cld

문자열 명령의 방향을 증가 방향으로 고정한다. 펌웨어가 남긴 DF 상태를 C ABI가 기대하는 값으로 되돌리는 줄이다.

90cli

외부 비동기 진입을 막는다. 이후 코드가 예외 벡터와 stack을 완성하기 전에는 interrupt handler가 실행되어서는 안 된다.

91(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

92/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

93* Calculate the delta between where we were compiled to run

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

94* at and where we were actually loaded at. This can only be done

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

95* with a short local call on x86. Nothing else will tell us what

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

96* address we are running at. The reserved chunk of the real-mode

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

97* data at 0x1e4 (defined as a scratch field) are used as the stack

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

98* for this calculation. Only 4 bytes are needed.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

99*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

100leal (BP_scratch+4)(%esi), %esp

boot_params의 scratch 영역을 4-byte 임시 stack으로 사용한다. 정상 kernel stack이 아직 없다는 뜻이다.

101call 1f

다음 instruction 주소를 stack에 넣어 현재 load base를 역산한다. 32비트 모드에서는 일반 RIP-relative addressing을 쓸 수 없기 때문이다.

1021: popl %ebp

stack에서 값을 회수하고 SP를 이동한다. 앞서 쌓인 순서와 폭이 정확히 맞아야 한다.

103subl $ rva(1b), %ebp

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

104(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

105/* Load new GDT with the 64bit segments using 32bit descriptor */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

106leal rva(gdt)(%ebp), %eax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

107movl %eax, 2(%eax)

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

108lgdt (%eax)

descriptor table의 base와 limit를 CPU에 적재한다. 메모리에 표가 존재하는 것과 CPU register가 그 표를 가리키는 것은 별개다.

109(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

110/* Load segment registers with our descriptors */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

111movl $__BOOT_DS, %eax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

112movl %eax, %ds

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

113movl %eax, %es

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

114movl %eax, %fs

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

115movl %eax, %gs

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

116movl %eax, %ss

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

117(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

118/* Setup a stack and load CS from current GDT */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

119leal rva(boot_stack_end)(%ebp), %esp

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

120(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

121pushl $__KERNEL32_CS

현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.

122leal rva(1f)(%ebp), %eax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

123pushl %eax

현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.

124lretl

일반 분기가 아니라 privilege 또는 code segment 상태를 함께 복원하는 제어 이전이다. frame 형식이 틀리면 다음 명령에 도달하지 못한다.

1251:

분기 대상 label이다. 이 지점에 들어올 수 있는 모든 선행 경로가 같은 register와 stack 조건을 만족하는지 비교한다.

126(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

127/* Setup Exception handling for SEV-ES */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

128#ifdef CONFIG_AMD_MEM_ENCRYPT

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

129call startup32_load_idt

하위 루틴으로 진입한다. 호출 직전 argument register, stack 정렬, 보존 register가 해당 ABI를 만족하는지 확인한다.

130#endif

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

131(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

132/* Make sure cpu supports long mode. */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

133call verify_cpu

하위 루틴으로 진입한다. 호출 직전 argument register, stack 정렬, 보존 register가 해당 ABI를 만족하는지 확인한다.

134testl %eax, %eax

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

135jnz .Lno_longmode

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

136(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

137/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

138* Compute the delta between where we were compiled to run at

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

139* and where the code will actually run at.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

140*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

141* %ebp contains the address we are loaded at by the boot loader and %ebx

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

142* contains the address where we should move the kernel image temporarily

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

143* for safe in-place decompression.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

144*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

145(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

146#ifdef CONFIG_RELOCATABLE

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

147movl %ebp, %ebx

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

148movl BP_kernel_alignment(%esi), %eax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

149decl %eax

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

150addl %eax, %ebx

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

151notl %eax

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

152andl %eax, %ebx

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

153cmpl $LOAD_PHYSICAL_ADDR, %ebx

직전에 만든 값이나 flag를 검사해 경로를 나눈다. 실패 분기가 어디에서 멈추는지까지 따라가야 조건의 의미가 완성된다.

154jae 1f

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

155#endif

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

156movl $LOAD_PHYSICAL_ADDR, %ebx

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

1571:

분기 대상 label이다. 이 지점에 들어올 수 있는 모든 선행 경로가 같은 register와 stack 조건을 만족하는지 비교한다.

158(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

159/* Target address to relocate to for decompression */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

160addl BP_init_size(%esi), %ebx

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

161subl $ rva(_end), %ebx

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

162(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

163/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

164* Prepare for entering 64 bit mode

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

165*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

166(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

167/* Enable PAE mode */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

168movl %cr4, %eax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

169orl $X86_CR4_PAE, %eax

long mode page translation이 요구하는 PAE bit를 먼저 켠다. 이후 EFER.LME와 CR0.PG 순서가 이어진다.

170movl %eax, %cr4

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

64비트 compressed entry, paging level 조정과 안전한 source 이동

64비트 loader가 직접 들어오거나 startup_32에서 넘어온 두 경로를 합친다. 목적지 정렬을 다시 계산하고, 4/5-level paging 차이를 trampoline으로 정리한 뒤 압축 입력을 안전한 위치로 옮긴다.

원본: arch/x86/boot/compressed/head_64.S 278-442줄

278SYM_CODE_START(startup_64)
279	/*
280	 * 64bit entry is 0x200 and it is ABI so immutable!
281	 * We come here either from startup_32 or directly from a
282	 * 64bit bootloader.
283	 * If we come here from a bootloader, kernel(text+data+bss+brk),
284	 * ramdisk, zero_page, command line could be above 4G.
285	 * We depend on an identity mapped page table being provided
286	 * that maps our entire kernel(text+data+bss+brk), zero page
287	 * and command line.
288	 */
289 
290	cld
291	cli
292 
293	/* Setup data segments. */
294	xorl	%eax, %eax
295	movl	%eax, %ds
296	movl	%eax, %es
297	movl	%eax, %ss
298	movl	%eax, %fs
299	movl	%eax, %gs
300 
301	/*
302	 * Compute the decompressed kernel start address.  It is where
303	 * we were loaded at aligned to a 2M boundary. %rbp contains the
304	 * decompressed kernel start address.
305	 *
306	 * If it is a relocatable kernel then decompress and run the kernel
307	 * from load address aligned to 2MB addr, otherwise decompress and
308	 * run the kernel from LOAD_PHYSICAL_ADDR
309	 *
310	 * We cannot rely on the calculation done in 32-bit mode, since we
311	 * may have been invoked via the 64-bit entry point.
312	 */
313 
314	/* Start with the delta to where the kernel will run at. */
315#ifdef CONFIG_RELOCATABLE
316	leaq	startup_32(%rip) /* - $startup_32 */, %rbp
317	movl	BP_kernel_alignment(%rsi), %eax
318	decl	%eax
319	addq	%rax, %rbp
320	notq	%rax
321	andq	%rax, %rbp
322	cmpq	$LOAD_PHYSICAL_ADDR, %rbp
323	jae	1f
324#endif
325	movq	$LOAD_PHYSICAL_ADDR, %rbp
3261:
327 
328	/* Target address to relocate to for decompression */
329	movl	BP_init_size(%rsi), %ebx
330	subl	$ rva(_end), %ebx
331	addq	%rbp, %rbx
332 
333	/* Set up the stack */
334	leaq	rva(boot_stack_end)(%rbx), %rsp
335 
336	/*
337	 * At this point we are in long mode with 4-level paging enabled,
338	 * but we might want to enable 5-level paging or vice versa.
339	 *
340	 * The problem is that we cannot do it directly. Setting or clearing
341	 * CR4.LA57 in long mode would trigger #GP. So we need to switch off
342	 * long mode and paging first.
343	 *
344	 * We also need a trampoline in lower memory to switch over from
345	 * 4- to 5-level paging for cases when the bootloader puts the kernel
346	 * above 4G, but didn't enable 5-level paging for us.
347	 *
348	 * The same trampoline can be used to switch from 5- to 4-level paging
349	 * mode, like when starting 4-level paging kernel via kexec() when
350	 * original kernel worked in 5-level paging mode.
351	 *
352	 * For the trampoline, we need the top page table to reside in lower
353	 * memory as we don't have a way to load 64-bit values into CR3 in
354	 * 32-bit mode.
355	 */
356 
357	/* Make sure we have GDT with 32-bit code segment */
358	leaq	gdt64(%rip), %rax
359	addq	%rax, 2(%rax)
360	lgdt	(%rax)
361 
362	/* Reload CS so IRET returns to a CS actually in the GDT */
363	pushq	$__KERNEL_CS
364	leaq	.Lon_kernel_cs(%rip), %rax
365	pushq	%rax
366	lretq
367 
368.Lon_kernel_cs:
369	/*
370	 * RSI holds a pointer to a boot_params structure provided by the
371	 * loader, and this needs to be preserved across C function calls. So
372	 * move it into a callee saved register.
373	 */
374	movq	%rsi, %r15
375 
376	call	load_stage1_idt
377 
378#ifdef CONFIG_AMD_MEM_ENCRYPT
379	/*
380	 * Now that the stage1 interrupt handlers are set up, #VC exceptions from
381	 * CPUID instructions can be properly handled for SEV-ES guests.
382	 *
383	 * For SEV-SNP, the CPUID table also needs to be set up in advance of any
384	 * CPUID instructions being issued, so go ahead and do that now via
385	 * sev_enable(), which will also handle the rest of the SEV-related
386	 * detection/setup to ensure that has been done in advance of any dependent
387	 * code. Pass the boot_params pointer as the first argument.
388	 */
389	movq	%r15, %rdi
390	call	sev_enable
391#endif
392 
393	/* Preserve only the CR4 bits that must be preserved, and clear the rest */
394	movq	%cr4, %rax
395	andl	$(X86_CR4_PAE | X86_CR4_MCE | X86_CR4_LA57), %eax
396	movq	%rax, %cr4
397 
398	/*
399	 * configure_5level_paging() updates the number of paging levels using
400	 * a trampoline in 32-bit addressable memory if the current number does
401	 * not match the desired number.
402	 *
403	 * Pass the boot_params pointer as the first argument. The second
404	 * argument is the relocated address of the page table to use instead
405	 * of the page table in trampoline memory (if required).
406	 */
407	movq	%r15, %rdi
408	leaq	rva(top_pgtable)(%rbx), %rsi
409	call	configure_5level_paging
410 
411	/* Zero EFLAGS */
412	pushq	$0
413	popfq
414 
415/*
416 * Copy the compressed kernel to the end of our buffer
417 * where decompression in place becomes safe.
418 */
419	leaq	(_bss-8)(%rip), %rsi
420	leaq	rva(_bss-8)(%rbx), %rdi
421	movl	$(_bss - startup_32), %ecx
422	shrl	$3, %ecx
423	std
424	rep	movsq
425	cld
426 
427	/*
428	 * The GDT may get overwritten either during the copy we just did or
429	 * during extract_kernel below. To avoid any issues, repoint the GDTR
430	 * to the new copy of the GDT.
431	 */
432	leaq	rva(gdt64)(%rbx), %rax
433	leaq	rva(gdt)(%rbx), %rdx
434	movq	%rdx, 2(%rax)
435	lgdt	(%rax)
436 
437/*
438 * Jump to the relocated address.
439 */
440	leaq	rva(.Lrelocated)(%rbx), %rax
441	jmp	*%rax
442SYM_CODE_END(startup_64)

278-442줄 주석

278SYM_CODE_START(startup_64)

linker와 assembler가 진입 symbol, section 또는 정렬을 확정하는 지시자다. runtime 명령은 아니지만 entry address와 배치를 결정한다.

279/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

280* 64bit entry is 0x200 and it is ABI so immutable!

offset 0x200 역시 외부 bootloader가 직접 사용할 수 있는 고정 entry ABI다.

281* We come here either from startup_32 or directly from a

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

282* 64bit bootloader.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

283* If we come here from a bootloader, kernel(text+data+bss+brk),

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

284* ramdisk, zero_page, command line could be above 4G.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

285* We depend on an identity mapped page table being provided

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

286* that maps our entire kernel(text+data+bss+brk), zero page

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

287* and command line.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

288*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

289(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

290cld

문자열 명령의 방향을 증가 방향으로 고정한다. 펌웨어가 남긴 DF 상태를 C ABI가 기대하는 값으로 되돌리는 줄이다.

291cli

외부 비동기 진입을 막는다. 이후 코드가 예외 벡터와 stack을 완성하기 전에는 interrupt handler가 실행되어서는 안 된다.

292(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

293/* Setup data segments. */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

294xorl %eax, %eax

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

295movl %eax, %ds

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

296movl %eax, %es

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

297movl %eax, %ss

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

298movl %eax, %fs

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

299movl %eax, %gs

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

300(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

301/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

302* Compute the decompressed kernel start address. It is where

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

303* we were loaded at aligned to a 2M boundary. %rbp contains the

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

304* decompressed kernel start address.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

305*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

306* If it is a relocatable kernel then decompress and run the kernel

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

307* from load address aligned to 2MB addr, otherwise decompress and

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

308* run the kernel from LOAD_PHYSICAL_ADDR

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

309*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

310* We cannot rely on the calculation done in 32-bit mode, since we

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

311* may have been invoked via the 64-bit entry point.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

312*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

313(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

314/* Start with the delta to where the kernel will run at. */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

315#ifdef CONFIG_RELOCATABLE

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

316leaq startup_32(%rip) /* - $startup_32 */, %rbp

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

317movl BP_kernel_alignment(%rsi), %eax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

318decl %eax

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

319addq %rax, %rbp

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

320notq %rax

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

321andq %rax, %rbp

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

322cmpq $LOAD_PHYSICAL_ADDR, %rbp

직전에 만든 값이나 flag를 검사해 경로를 나눈다. 실패 분기가 어디에서 멈추는지까지 따라가야 조건의 의미가 완성된다.

323jae 1f

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

324#endif

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

325movq $LOAD_PHYSICAL_ADDR, %rbp

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

3261:

분기 대상 label이다. 이 지점에 들어올 수 있는 모든 선행 경로가 같은 register와 stack 조건을 만족하는지 비교한다.

327(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

328/* Target address to relocate to for decompression */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

329movl BP_init_size(%rsi), %ebx

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

330subl $ rva(_end), %ebx

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

331addq %rbp, %rbx

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

332(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

333/* Set up the stack */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

334leaq rva(boot_stack_end)(%rbx), %rsp

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

335(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

336/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

337* At this point we are in long mode with 4-level paging enabled,

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

338* but we might want to enable 5-level paging or vice versa.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

339*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

340* The problem is that we cannot do it directly. Setting or clearing

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

341* CR4.LA57 in long mode would trigger #GP. So we need to switch off

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

342* long mode and paging first.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

343*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

344* We also need a trampoline in lower memory to switch over from

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

345* 4- to 5-level paging for cases when the bootloader puts the kernel

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

346* above 4G, but didn't enable 5-level paging for us.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

347*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

348* The same trampoline can be used to switch from 5- to 4-level paging

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

349* mode, like when starting 4-level paging kernel via kexec() when

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

350* original kernel worked in 5-level paging mode.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

351*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

352* For the trampoline, we need the top page table to reside in lower

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

353* memory as we don't have a way to load 64-bit values into CR3 in

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

354* 32-bit mode.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

355*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

356(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

357/* Make sure we have GDT with 32-bit code segment */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

358leaq gdt64(%rip), %rax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

359addq %rax, 2(%rax)

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

360lgdt (%rax)

descriptor table의 base와 limit를 CPU에 적재한다. 메모리에 표가 존재하는 것과 CPU register가 그 표를 가리키는 것은 별개다.

361(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

362/* Reload CS so IRET returns to a CS actually in the GDT */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

363pushq $__KERNEL_CS

현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.

364leaq .Lon_kernel_cs(%rip), %rax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

365pushq %rax

현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.

366lretq

일반 분기가 아니라 privilege 또는 code segment 상태를 함께 복원하는 제어 이전이다. frame 형식이 틀리면 다음 명령에 도달하지 못한다.

367(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

368.Lon_kernel_cs:

분기 대상 label이다. 이 지점에 들어올 수 있는 모든 선행 경로가 같은 register와 stack 조건을 만족하는지 비교한다.

369/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

370* RSI holds a pointer to a boot_params structure provided by the

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

371* loader, and this needs to be preserved across C function calls. So

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

372* move it into a callee saved register.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

373*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

374movq %rsi, %r15

loader가 넘긴 boot_params pointer를 callee-saved %r15에 보존한다. 이후 C 함수를 호출해도 살아 있어야 한다.

375(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

376call load_stage1_idt

하위 루틴으로 진입한다. 호출 직전 argument register, stack 정렬, 보존 register가 해당 ABI를 만족하는지 확인한다.

377(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

378#ifdef CONFIG_AMD_MEM_ENCRYPT

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

379/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

380* Now that the stage1 interrupt handlers are set up, #VC exceptions from

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

381* CPUID instructions can be properly handled for SEV-ES guests.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

382*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

383* For SEV-SNP, the CPUID table also needs to be set up in advance of any

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

384* CPUID instructions being issued, so go ahead and do that now via

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

385* sev_enable(), which will also handle the rest of the SEV-related

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

386* detection/setup to ensure that has been done in advance of any dependent

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

387* code. Pass the boot_params pointer as the first argument.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

388*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

389movq %r15, %rdi

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

390call sev_enable

하위 루틴으로 진입한다. 호출 직전 argument register, stack 정렬, 보존 register가 해당 ABI를 만족하는지 확인한다.

391#endif

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

392(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

393/* Preserve only the CR4 bits that must be preserved, and clear the rest */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

394movq %cr4, %rax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

395andl $(X86_CR4_PAE | X86_CR4_MCE | X86_CR4_LA57), %eax

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

396movq %rax, %cr4

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

397(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

398/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

399* configure_5level_paging() updates the number of paging levels using

현재 CR4.LA57과 kernel이 원하는 paging level이 다르면 long mode를 잠시 벗어나는 trampoline이 이를 맞춘다.

400* a trampoline in 32-bit addressable memory if the current number does

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

401* not match the desired number.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

402*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

403* Pass the boot_params pointer as the first argument. The second

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

404* argument is the relocated address of the page table to use instead

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

405* of the page table in trampoline memory (if required).

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

406*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

407movq %r15, %rdi

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

408leaq rva(top_pgtable)(%rbx), %rsi

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

409call configure_5level_paging

현재 CR4.LA57과 kernel이 원하는 paging level이 다르면 long mode를 잠시 벗어나는 trampoline이 이를 맞춘다.

410(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

411/* Zero EFLAGS */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

412pushq $0

현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.

413popfq

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

414(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

415/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

416* Copy the compressed kernel to the end of our buffer

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

417* where decompression in place becomes safe.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

418*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

419leaq (_bss-8)(%rip), %rsi

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

420leaq rva(_bss-8)(%rbx), %rdi

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

421movl $(_bss - startup_32), %ecx

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

422shrl $3, %ecx

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

423std

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

424rep movsq

DF를 세운 상태에서 높은 주소에서 낮은 주소 방향으로 compressed image를 복사한다. 아직 읽지 않은 source를 보호하는 핵심 구간이다.

425cld

문자열 명령의 방향을 증가 방향으로 고정한다. 펌웨어가 남긴 DF 상태를 C ABI가 기대하는 값으로 되돌리는 줄이다.

426(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

427/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

428* The GDT may get overwritten either during the copy we just did or

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

429* during extract_kernel below. To avoid any issues, repoint the GDTR

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

430* to the new copy of the GDT.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

431*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

432leaq rva(gdt64)(%rbx), %rax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

433leaq rva(gdt)(%rbx), %rdx

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

434movq %rdx, 2(%rax)

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

435lgdt (%rax)

descriptor table의 base와 limit를 CPU에 적재한다. 메모리에 표가 존재하는 것과 CPU register가 그 표를 가리키는 것은 별개다.

436(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

437/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

438* Jump to the relocated address.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

439*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

440leaq rva(.Lrelocated)(%rbx), %rax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

441jmp *%rax

새 위치의 .Lrelocated로 비복귀 분기한다. GDT도 새 copy를 가리키도록 바꾼 뒤여야 한다.

442SYM_CODE_END(startup_64)

linker와 assembler가 진입 symbol, section 또는 정렬을 확정하는 지시자다. runtime 명령은 아니지만 entry address와 배치를 결정한다.

압축 해제된 vmlinux의 startup_64와 high virtual address 진입

두 번째 startup_64는 vmlinux의 실제 load offset을 page table에 반영하고 early_top_pgt를 CR3에 넣은 뒤 링크된 kernel virtual address로 이동한다.

원본: arch/x86/kernel/head_64.S 38-141줄

38SYM_CODE_START_NOALIGN(startup_64)
39	UNWIND_HINT_END_OF_STACK
40	/*
41	 * At this point the CPU runs in 64bit mode CS.L = 1 CS.D = 0,
42	 * and someone has loaded an identity mapped page table
43	 * for us.  These identity mapped page tables map all of the
44	 * kernel pages and possibly all of memory.
45	 *
46	 * %RSI holds the physical address of the boot_params structure
47	 * provided by the bootloader. Preserve it in %R15 so C function calls
48	 * will not clobber it.
49	 *
50	 * We come here either directly from a 64bit bootloader, or from
51	 * arch/x86/boot/compressed/head_64.S.
52	 *
53	 * We only come here initially at boot nothing else comes here.
54	 *
55	 * Since we may be loaded at an address different from what we were
56	 * compiled to run at we first fixup the physical addresses in our page
57	 * tables and then reload them.
58	 */
59	mov	%rsi, %r15
60 
61	/* Set up the stack for verify_cpu() */
62	leaq	__top_init_kernel_stack(%rip), %rsp
63 
64	/*
65	 * Set up GSBASE.
66	 * Note that on SMP the boot CPU uses the init data section until
67	 * the per-CPU areas are set up.
68	 */
69	movl	$MSR_GS_BASE, %ecx
70	xorl	%eax, %eax
71	xorl	%edx, %edx
72	wrmsr
73 
74	call	__pi_startup_64_setup_gdt_idt
75 
76	/* Now switch to __KERNEL_CS so IRET works reliably */
77	pushq	$__KERNEL_CS
78	leaq	.Lon_kernel_cs(%rip), %rax
79	pushq	%rax
80	lretq
81 
82.Lon_kernel_cs:
83	ANNOTATE_NOENDBR
84	UNWIND_HINT_END_OF_STACK
85 
86#ifdef CONFIG_AMD_MEM_ENCRYPT
87	/*
88	 * Activate SEV/SME memory encryption if supported/enabled. This needs to
89	 * be done now, since this also includes setup of the SEV-SNP CPUID table,
90	 * which needs to be done before any CPUID instructions are executed in
91	 * subsequent code. Pass the boot_params pointer as the first argument.
92	 */
93	movq	%r15, %rdi
94	call	__pi_sme_enable
95#endif
96 
97	/* Sanitize CPU configuration */
98	call verify_cpu
99 
100	/*
101	 * Derive the kernel's physical-to-virtual offset from the physical and
102	 * virtual addresses of common_startup_64().
103	 */
104	leaq	common_startup_64(%rip), %rdi
105	subq	.Lcommon_startup_64(%rip), %rdi
106 
107	/*
108	 * Perform pagetable fixups. Additionally, if SME is active, encrypt
109	 * the kernel and retrieve the modifier (SME encryption mask if SME
110	 * is active) to be added to the initial pgdir entry that will be
111	 * programmed into CR3.
112	 */
113	movq	%r15, %rsi
114	call	__pi___startup_64
115 
116	/* Form the CR3 value being sure to include the CR3 modifier */
117	leaq	early_top_pgt(%rip), %rcx
118	addq	%rcx, %rax
119 
120#ifdef CONFIG_AMD_MEM_ENCRYPT
121	mov	%rax, %rdi
122 
123	/*
124	 * For SEV guests: Verify that the C-bit is correct. A malicious
125	 * hypervisor could lie about the C-bit position to perform a ROP
126	 * attack on the guest by writing to the unencrypted stack and wait for
127	 * the next RET instruction.
128	 */
129	call	sev_verify_cbit
130#endif
131 
132	/*
133	 * Switch to early_top_pgt which still has the identity mappings
134	 * present.
135	 */
136	movq	%rax, %cr3
137 
138	/* Branch to the common startup code at its kernel virtual address */
139	ANNOTATE_RETPOLINE_SAFE
140	jmp	*.Lcommon_startup_64(%rip)
141SYM_CODE_END(startup_64)

38-141줄 주석

38SYM_CODE_START_NOALIGN(startup_64)

linker와 assembler가 진입 symbol, section 또는 정렬을 확정하는 지시자다. runtime 명령은 아니지만 entry address와 배치를 결정한다.

39UNWIND_HINT_END_OF_STACK

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

40/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

41* At this point the CPU runs in 64bit mode CS.L = 1 CS.D = 0,

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

42* and someone has loaded an identity mapped page table

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

43* for us. These identity mapped page tables map all of the

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

44* kernel pages and possibly all of memory.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

45*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

46* %RSI holds the physical address of the boot_params structure

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

47* provided by the bootloader. Preserve it in %R15 so C function calls

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

48* will not clobber it.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

49*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

50* We come here either directly from a 64bit bootloader, or from

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

51* arch/x86/boot/compressed/head_64.S.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

52*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

53* We only come here initially at boot nothing else comes here.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

54*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

55* Since we may be loaded at an address different from what we were

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

56* compiled to run at we first fixup the physical addresses in our page

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

57* tables and then reload them.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

58*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

59mov %rsi, %r15

물리 주소인 boot_params를 C 호출 사이에 보존한다.

60(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

61/* Set up the stack for verify_cpu() */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

62leaq __top_init_kernel_stack(%rip), %rsp

검증 C 함수를 호출할 수 있도록 boot CPU의 초기 stack top을 선택한다.

63(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

64/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

65* Set up GSBASE.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

66* Note that on SMP the boot CPU uses the init data section until

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

67* the per-CPU areas are set up.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

68*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

69movl $MSR_GS_BASE, %ecx

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

70xorl %eax, %eax

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

71xorl %edx, %edx

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

72wrmsr

ECX로 선택한 model-specific register에 EDX:EAX 값을 기록한다. MSR 종류와 CPU feature 조건을 함께 확인한다.

73(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

74call __pi_startup_64_setup_gdt_idt

하위 루틴으로 진입한다. 호출 직전 argument register, stack 정렬, 보존 register가 해당 ABI를 만족하는지 확인한다.

75(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

76/* Now switch to __KERNEL_CS so IRET works reliably */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

77pushq $__KERNEL_CS

현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.

78leaq .Lon_kernel_cs(%rip), %rax

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

79pushq %rax

현재 stack에 값을 쌓는다. 이 줄을 읽을 때는 SP가 가리키는 메모리가 실제로 쓰기 가능한지와 ABI 정렬을 함께 확인한다.

80lretq

일반 분기가 아니라 privilege 또는 code segment 상태를 함께 복원하는 제어 이전이다. frame 형식이 틀리면 다음 명령에 도달하지 못한다.

81(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

82.Lon_kernel_cs:

분기 대상 label이다. 이 지점에 들어올 수 있는 모든 선행 경로가 같은 register와 stack 조건을 만족하는지 비교한다.

83ANNOTATE_NOENDBR

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

84UNWIND_HINT_END_OF_STACK

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

85(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

86#ifdef CONFIG_AMD_MEM_ENCRYPT

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

87/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

88* Activate SEV/SME memory encryption if supported/enabled. This needs to

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

89* be done now, since this also includes setup of the SEV-SNP CPUID table,

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

90* which needs to be done before any CPUID instructions are executed in

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

91* subsequent code. Pass the boot_params pointer as the first argument.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

92*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

93movq %r15, %rdi

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

94call __pi_sme_enable

하위 루틴으로 진입한다. 호출 직전 argument register, stack 정렬, 보존 register가 해당 ABI를 만족하는지 확인한다.

95#endif

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

96(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

97/* Sanitize CPU configuration */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

98call verify_cpu

하위 루틴으로 진입한다. 호출 직전 argument register, stack 정렬, 보존 register가 해당 ABI를 만족하는지 확인한다.

99(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

100/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

101* Derive the kernel's physical-to-virtual offset from the physical and

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

102* virtual addresses of common_startup_64().

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

103*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

104leaq common_startup_64(%rip), %rdi

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

105subq .Lcommon_startup_64(%rip), %rdi

runtime RIP와 link-time virtual address 차이로 물리-가상 offset을 계산한다.

106(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

107/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

108* Perform pagetable fixups. Additionally, if SME is active, encrypt

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

109* the kernel and retrieve the modifier (SME encryption mask if SME

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

110* is active) to be added to the initial pgdir entry that will be

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

111* programmed into CR3.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

112*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

113movq %r15, %rsi

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

114call __pi___startup_64

position-independent C helper가 초기 page table entry를 실제 load address에 맞게 고친다.

115(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

116/* Form the CR3 value being sure to include the CR3 modifier */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

117leaq early_top_pgt(%rip), %rcx

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

118addq %rcx, %rax

주소, 정렬 또는 bit field를 계산한다. overflow와 정렬 단위, inclusive/exclusive end를 앞뒤 줄과 함께 본다.

119(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

120#ifdef CONFIG_AMD_MEM_ENCRYPT

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

121mov %rax, %rdi

주소 또는 상태를 다음 계산에 사용할 register로 옮긴다. 이 시점의 값이 물리 주소인지 가상 주소인지 구분해야 한다.

122(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

123/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

124* For SEV guests: Verify that the C-bit is correct. A malicious

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

125* hypervisor could lie about the C-bit position to perform a ROP

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

126* attack on the guest by writing to the unencrypted stack and wait for

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

127* the next RET instruction.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

128*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

129call sev_verify_cbit

하위 루틴으로 진입한다. 호출 직전 argument register, stack 정렬, 보존 register가 해당 ABI를 만족하는지 확인한다.

130#endif

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

131(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

132/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

133* Switch to early_top_pgt which still has the identity mappings

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

134* present.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

135*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

136movq %rax, %cr3

고쳐진 early_top_pgt를 활성화한다. 아직 identity mapping을 유지하므로 바로 다음 fetch가 끊기지 않는다.

137(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

138/* Branch to the common startup code at its kernel virtual address */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

139ANNOTATE_RETPOLINE_SAFE

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

140jmp *.Lcommon_startup_64(%rip)

이제 link-time kernel virtual address를 사용해 공통 시작 코드로 이동한다.

141SYM_CODE_END(startup_64)

linker와 assembler가 진입 symbol, section 또는 정렬을 확정하는 지시자다. runtime 명령은 아니지만 entry address와 배치를 결정한다.

extract_kernel이 실제 출력 크기와 KASLR 목적지를 확정하는 구간

assembly에서 계산한 임시 위치는 최종 kernel 위치가 아니다. extract_kernel()이 boot parameter를 보존하고 필요한 전체 범위를 PMD 크기로 올림한 뒤 KASLR 물리·가상 목적지를 선택하고 정렬을 다시 검증한다.

원본: arch/x86/boot/compressed/misc.c 407-499줄

407asmlinkage __visible void *extract_kernel(void *rmode, unsigned char *output)
408{
409	unsigned long virt_addr = LOAD_PHYSICAL_ADDR;
410	memptr heap = (memptr)boot_heap;
411	unsigned long needed_size;
412	size_t entry_offset;
413 
414	/* Retain x86 boot parameters pointer passed from startup_32/64. */
415	boot_params_ptr = rmode;
416 
417	/* Clear flags intended for solely in-kernel use. */
418	boot_params_ptr->hdr.loadflags &= ~KASLR_FLAG;
419 
420	parse_mem_encrypt(&boot_params_ptr->hdr);
421 
422	sanitize_boot_params(boot_params_ptr);
423 
424	if (boot_params_ptr->screen_info.orig_video_mode == 7) {
425		vidmem = (char *) 0xb0000;
426		vidport = 0x3b4;
427	} else {
428		vidmem = (char *) 0xb8000;
429		vidport = 0x3d4;
430	}
431 
432	lines = boot_params_ptr->screen_info.orig_video_lines;
433	cols = boot_params_ptr->screen_info.orig_video_cols;
434 
435	init_default_io_ops();
436 
437	/*
438	 * Detect TDX guest environment.
439	 *
440	 * It has to be done before console_init() in order to use
441	 * paravirtualized port I/O operations if needed.
442	 */
443	early_tdx_detect();
444 
445	early_sev_detect();
446 
447	console_init();
448 
449	/*
450	 * Save RSDP address for later use. Have this after console_init()
451	 * so that early debugging output from the RSDP parsing code can be
452	 * collected.
453	 */
454	boot_params_ptr->acpi_rsdp_addr = get_rsdp_addr();
455 
456	debug_putstr("early console in extract_kernel\n");
457 
458	free_mem_ptr     = heap;	/* Heap */
459	free_mem_end_ptr = heap + BOOT_HEAP_SIZE;
460 
461	/*
462	 * The memory hole needed for the kernel is the larger of either
463	 * the entire decompressed kernel plus relocation table, or the
464	 * entire decompressed kernel plus .bss and .brk sections.
465	 *
466	 * On X86_64, the memory is mapped with PMD pages. Round the
467	 * size up so that the full extent of PMD pages mapped is
468	 * included in the check against the valid memory table
469	 * entries. This ensures the full mapped area is usable RAM
470	 * and doesn't include any reserved areas.
471	 */
472	needed_size = max_t(unsigned long, output_len, kernel_total_size);
473#ifdef CONFIG_X86_64
474	needed_size = ALIGN(needed_size, MIN_KERNEL_ALIGN);
475#endif
476 
477	/* Report initial kernel position details. */
478	debug_putaddr(input_data);
479	debug_putaddr(input_len);
480	debug_putaddr(output);
481	debug_putaddr(output_len);
482	debug_putaddr(kernel_total_size);
483	debug_putaddr(needed_size);
484 
485#ifdef CONFIG_X86_64
486	/* Report address of 32-bit trampoline */
487	debug_putaddr(trampoline_32bit);
488#endif
489 
490	choose_random_location((unsigned long)input_data, input_len,
491				(unsigned long *)&output,
492				needed_size,
493				&virt_addr);
494 
495	/* Validate memory location choices. */
496	if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1))
497		error("Destination physical address inappropriately aligned");
498	if (virt_addr & (MIN_KERNEL_ALIGN - 1))
499		error("Destination virtual address inappropriately aligned");

407-499줄 주석

407asmlinkage __visible void *extract_kernel(void *rmode, unsigned char *output)

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

408{

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

409unsigned long virt_addr = LOAD_PHYSICAL_ADDR;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

410memptr heap = (memptr)boot_heap;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

411unsigned long needed_size;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

412size_t entry_offset;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

413(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

414/* Retain x86 boot parameters pointer passed from startup_32/64. */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

415boot_params_ptr = rmode;

assembly가 %rsi로 보존해 온 zero-page pointer의 소유권을 decompressor C 코드가 넘겨받는다. 이 4KiB가 출력 범위와 겹치면 뒤의 memory map과 command line 해석이 동시에 무너진다.

416(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

417/* Clear flags intended for solely in-kernel use. */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

418boot_params_ptr->hdr.loadflags &= ~KASLR_FLAG;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

419(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

420parse_mem_encrypt(&boot_params_ptr->hdr);

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

421(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

422sanitize_boot_params(boot_params_ptr);

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

423(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

424if (boot_params_ptr->screen_info.orig_video_mode == 7) {

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

425vidmem = (char *) 0xb0000;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

426vidport = 0x3b4;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

427} else {

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

428vidmem = (char *) 0xb8000;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

429vidport = 0x3d4;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

430}

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

431(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

432lines = boot_params_ptr->screen_info.orig_video_lines;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

433cols = boot_params_ptr->screen_info.orig_video_cols;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

434(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

435init_default_io_ops();

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

436(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

437/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

438* Detect TDX guest environment.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

439*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

440* It has to be done before console_init() in order to use

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

441* paravirtualized port I/O operations if needed.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

442*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

443early_tdx_detect();

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

444(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

445early_sev_detect();

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

446(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

447console_init();

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

448(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

449/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

450* Save RSDP address for later use. Have this after console_init()

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

451* so that early debugging output from the RSDP parsing code can be

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

452* collected.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

453*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

454boot_params_ptr->acpi_rsdp_addr = get_rsdp_addr();

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

455(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

456debug_putstr("early console in extract_kernel\n");

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

457(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

458free_mem_ptr = heap; /* Heap */

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

459free_mem_end_ptr = heap + BOOT_HEAP_SIZE;

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

460(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

461/*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

462* The memory hole needed for the kernel is the larger of either

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

463* the entire decompressed kernel plus relocation table, or the

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

464* entire decompressed kernel plus .bss and .brk sections.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

465*

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

466* On X86_64, the memory is mapped with PMD pages. Round the

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

467* size up so that the full extent of PMD pages mapped is

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

468* included in the check against the valid memory table

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

469* entries. This ensures the full mapped area is usable RAM

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

470* and doesn't include any reserved areas.

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

471*/

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

472needed_size = max_t(unsigned long, output_len, kernel_total_size);

압축 해제된 ELF byte 수만 보지 않고 relocation table, BSS와 brk까지 포함한 두 후보 중 큰 값을 선택한다. KASLR hole 검사는 이 전체 수명을 수용해야 한다.

473#ifdef CONFIG_X86_64

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

474needed_size = ALIGN(needed_size, MIN_KERNEL_ALIGN);

x86-64 early mapping이 PMD 단위이므로 마지막 byte가 속한 2MiB frame까지 usable RAM인지 검사하기 위해 범위를 올림한다.

475#endif

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

476(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

477/* Report initial kernel position details. */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

478debug_putaddr(input_data);

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

479debug_putaddr(input_len);

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

480debug_putaddr(output);

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

481debug_putaddr(output_len);

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

482debug_putaddr(kernel_total_size);

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

483debug_putaddr(needed_size);

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

484(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

485#ifdef CONFIG_X86_64

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

486/* Report address of 32-bit trampoline */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

487debug_putaddr(trampoline_32bit);

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

488#endif

빌드 설정에 따라 실제 binary에 포함되는 경로가 달라지는 전처리 경계다. 분석 대상의 .config와 objdump 결과로 어느 가지가 남았는지 확인한다.

489(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

490choose_random_location((unsigned long)input_data, input_len,

입력 payload 범위와 필요한 출력 크기를 함께 넘겨 물리 destination과 kernel virtual address를 고른다. 반환된 두 주소는 같은 값일 필요가 없다.

491(unsigned long *)&output,

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

492needed_size,

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

493&virt_addr);

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

494(빈 줄)

빈 줄은 명령 묶음의 경계를 보존한다. 위에서 만든 상태와 아래에서 소비하는 상태를 나누어 읽는 자리다.

495/* Validate memory location choices. */

원본 주석이다. 현재 진입 계약이나 바로 아래 명령 묶음의 이유를 밝히므로 실행 코드와 같은 비중으로 읽는다.

496if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1))

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

497error("Destination physical address inappropriately aligned");

선택 결과를 신뢰하지 않고 assembly contract와 같은 MIN_KERNEL_ALIGN 제약을 C 경계에서 다시 검증한다.

498if (virt_addr & (MIN_KERNEL_ALIGN - 1))

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

499error("Destination virtual address inappropriately aligned");

이 줄이 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 기록한다. 단독으로 해석하지 말고 바로 앞의 생산자와 뒤의 소비자를 연결해 본다.

숫자를 넣어 끝까지 계산한다

01

compressed image를 16MiB에 적재한 경우의 주소 계산

예를 들어 compressed image runtime base가 0x01000000, link 기준 startup_32 offset이 0x200, 필요한 출력 범위가 0x01734000이라고 가정한다.

  1. runtime basecall/pop으로 얻은 다음 RIP에서 link-time label offset을 빼 0x01000000을 복원한다.
  2. output holeALIGN(0x01734000, 0x00200000) = 0x01800000, 즉 24MiB를 usable RAM으로 확보해야 한다.
  3. destination후보 0x02000000은 2MiB 정렬이고 끝 주소는 0x03800000이다.
  4. source protectioncompressed source 끝과 [0x02000000, 0x03800000)이 겹치면 source를 buffer 상단으로 역복사한 뒤 해제한다.

판정GDB에는 %rbp=destination, %rbx=decompression stack/buffer 기준, %r15=boot_params와 destination end를 함께 기록한다. 시작 주소 하나만 맞는 것으로는 overlap 안전성을 증명할 수 없다.

02

CR3에 넣는 값과 high virtual jump를 따로 검산한다

early page-table root의 physical address가 0x0019a000이면 x86 CR3의 base field는 그대로 0x0019a000이다. 하위 bit에는 PCID 또는 control flag만 들어간다.

  1. 전환 전 RIP현재 RIP가 0x03801234라면 새 root에도 이 물리 범위를 identity-map한 PTE가 있어야 한다.
  2. 전환 전 RSPRSP가 0x037ff000이면 같은 이유로 stack page도 새 root에서 접근 가능해야 한다.
  3. high alias링크 VA ffffffff81001234가 같은 physical text frame을 가리켜야 high-address jump가 성공한다.
  4. root self accesspage-table walk가 사용하는 0x0019a000과 하위 table page들은 usable RAM이며 덮어쓰이지 않아야 한다.

판정mov %rax,%cr3 직후 fault는 instruction page만 볼 문제가 아니다. RIP, RSP, page-table page 세 범위를 동시에 walk해야 한다.

코드에서 놓치기 쉬운 경계

01

boot_params는 단순 인자가 아니라 부트 프로토콜의 기록지다

struct boot_params에는 memory map, command line, initrd, loader type, kernel alignment가 들어 있다. %esi/%rsi 자체만 보존해도 그 pointer가 가리키는 zero page가 decompression destination이나 scratch에 덮이면 같은 결과가 난다. 주소와 함께 4KiB 범위를 memory map에 표시해야 한다.

32비트 entry에서는 zero page와 command line이 4GiB 아래에 있어야 하지만 64비트 direct entry는 그 제약이 완화된다. 같은 bzImage라도 loader가 어느 entry를 사용했는지에 따라 유효한 배치가 달라진다.

02

RIP-relative와 rva macro의 역할을 구분한다

compressed kernel은 PIE로 link될 수 있으므로 symbol reference가 불필요한 runtime relocation을 만들지 않게 rva(X)startup_32 기준 offset을 만든다. register에는 현재 runtime base를 넣고 instruction에는 offset만 남기는 방식이다.

disassembly에서 operand 숫자만 보지 말고 relocation section도 같이 본다. source의 rva()가 최종 binary에서 base+offset addressing으로 굳었는지 확인해야 한다.

03

KASLR 위치 선택과 decompression 안전성은 다른 문제다

KASLR은 실행 물리 위치 후보를 고르는 정책이고, source를 buffer 끝으로 옮기는 코드는 chosen destination에서 in-place decompression이 source를 덮지 않게 하는 안전 조건이다. KASLR을 꺼도 후자의 overlap 계산은 남는다.

destination은 alignment와 minimum load address를 만족해야 하고 init_size 전체가 RAM, reserved range, initrd와 겹치지 않아야 한다.

04

4-level과 5-level paging 변경은 CR4 bit 하나로 끝나지 않는다

long mode가 켜진 상태에서 CR4.LA57을 직접 바꾸면 #GP가 발생한다. 그래서 lower memory trampoline, 32비트로 표현 가능한 CR3, 임시 GDT가 필요하다. 이 구간에서 page fault가 나면 일반 kernel IDT와 console을 기대할 수 없다.

QEMU에서 -d int,cpu_reset을 사용하거나 GDB hardware breakpoint로 trampoline 전후 CR0, CR3, CR4, EFER를 기록하면 mode 전환을 확인할 수 있다.

05

identity map은 임시 다리이며 최종 kernel map과 같지 않다

entry 직후에는 현재 물리 RIP를 그대로 fetch하는 identity mapping과 링크된 high virtual address mapping이 겹쳐 존재한다. CR3를 바꾸고 high address로 분기한 뒤 identity mapping은 더 이상 일반 실행의 기반이 아니다.

초기 page table을 디버깅할 때 virtual address만 출력하지 말고 PTE가 가리키는 physical frame과 encryption C-bit까지 같이 적는다.

06

초기 fault를 마지막 출력 문자열로 판단하지 않는다

serial driver와 console이 완성되기 전에는 fault가 아무 문자열도 남기지 않을 수 있다. POST code, port 0x80, early debug register, QEMU trace, GDB로 RIP와 control register를 잡는 편이 더 직접적이다.

가장 먼저 비교할 checkpoint는 startup_32, compressed startup_64, .Lrelocated, vmlinux startup_64, common_startup_64다.

멈춘 위치보다 먼저 볼 값

조건관찰되는 증상첫 확인 값
boot_params 훼손memory map이나 command line이 무작위 값으로 보이거나 decompressor가 잘못된 크기를 사용한다.%rsi와 zero-page 4KiB 범위
destination/source overlapextract 중 정지하거나 압축 해제 뒤 instruction stream이 손상된다.%rbp, %rbx, _bss, init_size
잘못된 paging levelconfigure_5level_paging 또는 직후 #GP/triple fault가 발생한다.CR4.LA57, CR3, trampoline 주소
early page table fixup 오류CR3 write 직후 또는 high-address jump에서 무출력 reset이 난다.RIP/SP의 identity와 high mapping

직접 확인할 실험

  1. 01
    QEMU -S -s로 멈춘 뒤 startup_32, 두 startup_64, common_startup_64에 breakpoint를 건다.

    명령을 실행한 시점의 PC, stack, address-space root와 CPU/hart 번호를 함께 기록한다.

  2. 02
    각 checkpoint에서 CR0, CR3, CR4, EFER, RIP, RSP, RSI/R15를 기록해 표로 비교한다.

    명령을 실행한 시점의 PC, stack, address-space root와 CPU/hart 번호를 함께 기록한다.

  3. 03
    objdump -dr arch/x86/boot/compressed/vmlinux에서 rva() reference와 runtime relocation 유무를 확인한다.

    명령을 실행한 시점의 PC, stack, address-space root와 CPU/hart 번호를 함께 기록한다.

  4. 04
    readelf -lW vmlinux/proc/iomem을 대조해 실제 load range와 kernel text range를 확인한다.

    명령을 실행한 시점의 PC, stack, address-space root와 CPU/hart 번호를 함께 기록한다.

기준 원문