← Architecture 비교DUJINLABS.COM

Linux 6.18.37 LTS · Architecture comparison 20/21

Kernel module relocation과 instruction range

ELF module의 section을 vmalloc 영역에 배치하고 architecture relocation을 적용해 symbol address와 instruction immediate를 고치는 방법을 비교합니다.

비교 대상
arm64 / x86-64 / RISC-V
실제 원본
3 files · 277 annotated lines
기준 tag
Linux v6.18.37
분석 축
state · ordering · lifetime · latency

01 · QUESTION

무엇을 확인할 것인가

같은 R_*_CALL relocation이라도 target이 branch range를 벗어날 때 왜 PLT, thunk, trampoline이 필요하며 text를 쓴 뒤 instruction cache를 어떻게 동기화하는가?

module loader가 ELF section, symbol과 relocation table을 검증하고 core/init layout에 memory를 할당한 뒤 architecture apply_relocate_add()가 S+A-P 등의 식으로 location을 patch한다. 이후 permission을 RX/RO/RW로 강화한다.

untrusted module ELF parsing, writable text patch 구간, instruction-cache publication과 module live state 전환을 분리한다. relocation 실패 시 module init을 호출하기 전에 모든 allocation을 unwind해야 한다.

지연 시간 관점load 시간은 section allocation, relocation 수, symbol lookup, signature/ORC validation과 icache synchronization에 비례한다. runtime hot path에는 직접 branch 또는 PLT indirection 비용이 남는다.

02 · CONTRACT

공통 계약과 architecture 구현

architecture핵심 mechanism실패 형태확인할 상태
arm64ADRP/ADD, CALL26/JUMP26와 module PLTimmediate alignment/range overflow, BTI/PAC landing 요구 또는 stale icache는 load 실패와 이전 instruction 실행을 만든다.relocation type, S/A/P 값, encoded immediate, PLT address, module text range와 icache flush를 확인한다.
x86-64R_X86_64_64, PC32/PLT32와 alternatives32-bit displacement overflow, CET IBT ENDBR 누락 또는 metadata 불일치는 load error, control-protection fault와 잘못된 unwind를 만든다.relocation type, 64-bit result 또는 s32 displacement, P/S/A, module region, ENDBR와 ORC table을 확인한다.
RISC-VHI20/LO12 pair, CALL_PLT, JAL/BRANCH와 relaxationHI20 rounding과 LO12 sign 처리, paired relocation 위치 또는 compressed instruction width 오류가 완전히 다른 target을 만든다.relocation pair, S/A/P, HI20 rounding, LO12 sign extension, PLT entry와 remote icache synchronization을 본다.

03 · DIAGRAMS

세 그림으로 먼저 읽기

그림 1. 같은 목적, 서로 다른 mechanism각 ISA에서 실제로 추적할 state와 checkpoint를 한 줄에 맞췄습니다.

arm64

mechanism
ADRP/ADD, CALL26/JUMP26와 module PLT
state
AArch64 relocation은 instruction bitfield에 page-relative 또는 PC-relative immediate를 encode한다. ±128MiB branch range를 벗어나면 module PLT entry가 absolute target을 load해 branch한다.
checkpoint
relocation type, S/A/P 값, encoded immediate, PLT address, module text range와 icache flush를 확인한다.

x86-64

mechanism
R_X86_64_64, PC32/PLT32와 alternatives
state
absolute 64-bit와 signed 32-bit PC-relative relocation이 중심이다. module allocation을 kernel text와 가까운 범위에 두어 rel32 call/jump의 ±2GiB 제한을 만족시키며 필요 시 PLT/thunk 성격의 stub을 사용한다.
checkpoint
relocation type, 64-bit result 또는 s32 displacement, P/S/A, module region, ENDBR와 ORC table을 확인한다.

RISC-V

mechanism
HI20/LO12 pair, CALL_PLT, JAL/BRANCH와 relaxation
state
AUIPC+JALR 또는 LUI+ADDI처럼 여러 instruction이 하나의 symbol address를 구성하므로 paired relocation을 일관되게 적용한다. JAL range 밖 target은 PLT나 long-call sequence를 사용한다.
checkpoint
relocation pair, S/A/P, HI20 rounding, LO12 sign extension, PLT entry와 remote icache synchronization을 본다.
그림 2. 공통 kernel과 architecture hook의 소유권공통 정책이 hardware state를 직접 소유하지 않는 경계를 표시합니다.
Linux common contractmodule loader가 ELF section, symbol과 relocation table을 검증하고 core/init layout에 memory를 할당한 뒤 architecture apply_relocate_add()가 S+A-P 등의 식으로 location을 patch한다. 이후 permission을 RX/RO/RW로 강화한다.
arm64ADRP/ADD, CALL26/JUMP26와 module PLTtext patch를 완료한 뒤 D-cache clean과 I-cache invalidate를 수행하고 permission을 executable/read-only로 바꾼다.
x86-64R_X86_64_64, PC32/PLT32와 alternativeswritable alias/text_poke와 instruction serialization 뒤 executable permission을 적용하고 ORC/ftrace metadata도 relocated address에 맞춰 등록한다.
RISC-VHI20/LO12 pair, CALL_PLT, JAL/BRANCH와 relaxationinstruction pair patch와 icache sync가 끝난 뒤 모든 hart가 새 code를 보도록 remote fence.i 경로를 사용한다.
lifetime boundarymodule text/data와 symbol/unwind/ftrace metadata는 refcount와 RCU grace period가 끝날 때까지 유지된다. 실행 중인 CPU의 return address가 module text를 가리키면 unload할 수 없다.
그림 3. publication과 관찰 순서state를 준비한 뒤 architecture ordering을 거쳐 관찰 가능한 checkpoint가 됩니다.
arm64state 준비text patch를 완료한 뒤 D-cache clean과 I-cache invalidate를 수행하고 permission을 executable/read-only로 바꾼다.관찰: relocation type, S/A/P 값, encoded immediate, PLT address, module text range와 icache flush를 확인한다.
x86-64state 준비writable alias/text_poke와 instruction serialization 뒤 executable permission을 적용하고 ORC/ftrace metadata도 relocated address에 맞춰 등록한다.관찰: relocation type, 64-bit result 또는 s32 displacement, P/S/A, module region, ENDBR와 ORC table을 확인한다.
RISC-Vstate 준비instruction pair patch와 icache sync가 끝난 뒤 모든 hart가 새 code를 보도록 remote fence.i 경로를 사용한다.관찰: relocation pair, S/A/P, HI20 rounding, LO12 sign extension, PLT entry와 remote icache synchronization을 본다.

04 · SOURCE

Linux 6.18.37 원본 코드와 줄별 설명

소스 위치를 고정된 숫자로 복사하지 않고 Linux v6.18.37 tree에서 함수 선언을 다시 찾아 발췌했습니다. 아래 코드와 각 줄의 설명은 1:1로 대응합니다.

arm64 · Linux 6.18.37

ADRP/ADD, CALL26/JUMP26와 module PLT

AArch64 relocation은 instruction bitfield에 page-relative 또는 PC-relative immediate를 encode한다. ±128MiB branch range를 벗어나면 module PLT entry가 absolute target을 load해 branch한다.

원본 코드: arch/arm64/kernel/module.c:223-315

223		insn = aarch64_insn_gen_branch_imm((u64)place, val,
224						   AARCH64_INSN_BRANCH_NOLINK);
225	}
226 
227	WRITE_PLACE(place, cpu_to_le32(insn), me);
228	return 0;
229}
230 
231int apply_relocate_add(Elf64_Shdr *sechdrs,
232		       const char *strtab,
233		       unsigned int symindex,
234		       unsigned int relsec,
235		       struct module *me)
236{
237	unsigned int i;
238	int ovf;
239	bool overflow_check;
240	Elf64_Sym *sym;
241	void *loc;
242	u64 val;
243	Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr;
244 
245	for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {
246		/* loc corresponds to P in the AArch64 ELF document. */
247		loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr
248			+ rel[i].r_offset;
249 
250		/* sym is the ELF symbol we're referring to. */
251		sym = (Elf64_Sym *)sechdrs[symindex].sh_addr
252			+ ELF64_R_SYM(rel[i].r_info);
253 
254		/* val corresponds to (S + A) in the AArch64 ELF document. */
255		val = sym->st_value + rel[i].r_addend;
256 
257		/* Check for overflow by default. */
258		overflow_check = true;
259 
260		/* Perform the static relocation. */
261		switch (ELF64_R_TYPE(rel[i].r_info)) {
262		/* Null relocations. */
263		case R_ARM_NONE:
264		case R_AARCH64_NONE:
265			ovf = 0;
266			break;
267 
268		/* Data relocations. */
269		case R_AARCH64_ABS64:
270			overflow_check = false;
271			ovf = reloc_data(RELOC_OP_ABS, loc, val, 64, me);
272			break;
273		case R_AARCH64_ABS32:
274			ovf = reloc_data(RELOC_OP_ABS, loc, val, 32, me);
275			break;
276		case R_AARCH64_ABS16:
277			ovf = reloc_data(RELOC_OP_ABS, loc, val, 16, me);
278			break;
279		case R_AARCH64_PREL64:
280			overflow_check = false;
281			ovf = reloc_data(RELOC_OP_PREL, loc, val, 64, me);
282			break;
283		case R_AARCH64_PREL32:
284			ovf = reloc_data(RELOC_OP_PREL, loc, val, 32, me);
285			break;
286		case R_AARCH64_PREL16:
287			ovf = reloc_data(RELOC_OP_PREL, loc, val, 16, me);
288			break;
289 
290		/* MOVW instruction relocations. */
291		case R_AARCH64_MOVW_UABS_G0_NC:
292			overflow_check = false;
293			fallthrough;
294		case R_AARCH64_MOVW_UABS_G0:
295			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 0,
296					      AARCH64_INSN_IMM_MOVKZ, me);
297			break;
298		case R_AARCH64_MOVW_UABS_G1_NC:
299			overflow_check = false;
300			fallthrough;
301		case R_AARCH64_MOVW_UABS_G1:
302			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 16,
303					      AARCH64_INSN_IMM_MOVKZ, me);
304			break;
305		case R_AARCH64_MOVW_UABS_G2_NC:
306			overflow_check = false;
307			fallthrough;
308		case R_AARCH64_MOVW_UABS_G2:
309			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 32,
310					      AARCH64_INSN_IMM_MOVKZ, me);
311			break;
312		case R_AARCH64_MOVW_UABS_G3:
313			/* We're using the top bits so we can't overflow. */
314			overflow_check = false;
315			ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 48,

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 93개 줄에 각각 설명을 붙였습니다.

L223 insn = aarch64_insn_gen_branch_imm((u64)place, val,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L224 AARCH64_INSN_BRANCH_NOLINK);

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L225 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L226(blank)

빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L227 WRITE_PLACE(place, cpu_to_le32(insn), me);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L228 return 0;

이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L229}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L230(blank)

빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L231int apply_relocate_add(Elf64_Shdr *sechdrs,

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L232 const char *strtab,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L233 unsigned int symindex,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L234 unsigned int relsec,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L235 struct module *me)

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L236{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L237 unsigned int i;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L238 int ovf;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L239 bool overflow_check;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L240 Elf64_Sym *sym;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L241 void *loc;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L242 u64 val;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L243 Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L244(blank)

빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L245 for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {

각 RELA entry를 순회하며 symbol, addend와 patch location을 계산한다.

L246 /* loc corresponds to P in the AArch64 ELF document. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L247 loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L248 + rel[i].r_offset;

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L249(blank)

빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L250 /* sym is the ELF symbol we're referring to. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L251 sym = (Elf64_Sym *)sechdrs[symindex].sh_addr

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L252 + ELF64_R_SYM(rel[i].r_info);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L253(blank)

빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L254 /* val corresponds to (S + A) in the AArch64 ELF document. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L255 val = sym->st_value + rel[i].r_addend;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L256(blank)

빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L257 /* Check for overflow by default. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L258 overflow_check = true;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L259(blank)

빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L260 /* Perform the static relocation. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L261 switch (ELF64_R_TYPE(rel[i].r_info)) {

ABS, PREL, CALL/JUMP, ADRP 등 AArch64 relocation별 encoding을 선택한다.

L262 /* Null relocations. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L263 case R_ARM_NONE:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L264 case R_AARCH64_NONE:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L265 ovf = 0;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L266 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L267(blank)

빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L268 /* Data relocations. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L269 case R_AARCH64_ABS64:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L270 overflow_check = false;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L271 ovf = reloc_data(RELOC_OP_ABS, loc, val, 64, me);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L272 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L273 case R_AARCH64_ABS32:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L274 ovf = reloc_data(RELOC_OP_ABS, loc, val, 32, me);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L275 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L276 case R_AARCH64_ABS16:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L277 ovf = reloc_data(RELOC_OP_ABS, loc, val, 16, me);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L278 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L279 case R_AARCH64_PREL64:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L280 overflow_check = false;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L281 ovf = reloc_data(RELOC_OP_PREL, loc, val, 64, me);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L282 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L283 case R_AARCH64_PREL32:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L284 ovf = reloc_data(RELOC_OP_PREL, loc, val, 32, me);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L285 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L286 case R_AARCH64_PREL16:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L287 ovf = reloc_data(RELOC_OP_PREL, loc, val, 16, me);

helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L288 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L289(blank)

빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L290 /* MOVW instruction relocations. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L291 case R_AARCH64_MOVW_UABS_G0_NC:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L292 overflow_check = false;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L293 fallthrough;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L294 case R_AARCH64_MOVW_UABS_G0:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L295 ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 0,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L296 AARCH64_INSN_IMM_MOVKZ, me);

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L297 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L298 case R_AARCH64_MOVW_UABS_G1_NC:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L299 overflow_check = false;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L300 fallthrough;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L301 case R_AARCH64_MOVW_UABS_G1:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L302 ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 16,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L303 AARCH64_INSN_IMM_MOVKZ, me);

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L304 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L305 case R_AARCH64_MOVW_UABS_G2_NC:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L306 overflow_check = false;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L307 fallthrough;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L308 case R_AARCH64_MOVW_UABS_G2:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L309 ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 32,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L310 AARCH64_INSN_IMM_MOVKZ, me);

이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L311 break;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L312 case R_AARCH64_MOVW_UABS_G3:

hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.

L313 /* We're using the top bits so we can't overflow. */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L314 overflow_check = false;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L315 ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 48,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

x86-64 · Linux 6.18.37

R_X86_64_64, PC32/PLT32와 alternatives

absolute 64-bit와 signed 32-bit PC-relative relocation이 중심이다. module allocation을 kernel text와 가까운 범위에 두어 rel32 call/jump의 ±2GiB 제한을 만족시키며 필요 시 PLT/thunk 성격의 stub을 사용한다.

원본 코드: arch/x86/kernel/module.c:208-294

208	if (!early) {
209		smp_text_poke_sync_each_cpu();
210		mutex_unlock(&text_mutex);
211	}
212 
213	return ret;
214}
215 
216int apply_relocate_add(Elf64_Shdr *sechdrs,
217		   const char *strtab,
218		   unsigned int symindex,
219		   unsigned int relsec,
220		   struct module *me)
221{
222	return write_relocate_add(sechdrs, strtab, symindex, relsec, me, true);
223}
224 
225#ifdef CONFIG_LIVEPATCH
226void clear_relocate_add(Elf64_Shdr *sechdrs,
227			const char *strtab,
228			unsigned int symindex,
229			unsigned int relsec,
230			struct module *me)
231{
232	write_relocate_add(sechdrs, strtab, symindex, relsec, me, false);
233}
234#endif
235 
236#endif
237 
238int module_finalize(const Elf_Ehdr *hdr,
239		    const Elf_Shdr *sechdrs,
240		    struct module *me)
241{
242	const Elf_Shdr *s, *alt = NULL, *locks = NULL,
243		*orc = NULL, *orc_ip = NULL,
244		*retpolines = NULL, *returns = NULL, *ibt_endbr = NULL,
245		*calls = NULL, *cfi = NULL;
246	char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;
247 
248	for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
249		if (!strcmp(".altinstructions", secstrings + s->sh_name))
250			alt = s;
251		if (!strcmp(".smp_locks", secstrings + s->sh_name))
252			locks = s;
253		if (!strcmp(".orc_unwind", secstrings + s->sh_name))
254			orc = s;
255		if (!strcmp(".orc_unwind_ip", secstrings + s->sh_name))
256			orc_ip = s;
257		if (!strcmp(".retpoline_sites", secstrings + s->sh_name))
258			retpolines = s;
259		if (!strcmp(".return_sites", secstrings + s->sh_name))
260			returns = s;
261		if (!strcmp(".call_sites", secstrings + s->sh_name))
262			calls = s;
263		if (!strcmp(".cfi_sites", secstrings + s->sh_name))
264			cfi = s;
265		if (!strcmp(".ibt_endbr_seal", secstrings + s->sh_name))
266			ibt_endbr = s;
267	}
268 
269	its_init_mod(me);
270 
271	if (retpolines || cfi) {
272		void *rseg = NULL, *cseg = NULL;
273		unsigned int rsize = 0, csize = 0;
274 
275		if (retpolines) {
276			rseg = (void *)retpolines->sh_addr;
277			rsize = retpolines->sh_size;
278		}
279 
280		if (cfi) {
281			cseg = (void *)cfi->sh_addr;
282			csize = cfi->sh_size;
283		}
284 
285		apply_fineibt(rseg, rseg + rsize, cseg, cseg + csize);
286	}
287	if (retpolines) {
288		void *rseg = (void *)retpolines->sh_addr;
289		apply_retpolines(rseg, rseg + retpolines->sh_size);
290	}
291 
292	its_fini_mod(me);
293 
294	if (returns) {

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 87개 줄에 각각 설명을 붙였습니다.

L208 if (!early) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L209 smp_text_poke_sync_each_cpu();

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L210 mutex_unlock(&text_mutex);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L211 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L212(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L213 return ret;

저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.

L214}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L215(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L216int apply_relocate_add(Elf64_Shdr *sechdrs,

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L217 const char *strtab,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L218 unsigned int symindex,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L219 unsigned int relsec,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L220 struct module *me)

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L221{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L222 return write_relocate_add(sechdrs, strtab, symindex, relsec, me, true);

이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L223}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L224(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L225#ifdef CONFIG_LIVEPATCH

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L226void clear_relocate_add(Elf64_Shdr *sechdrs,

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L227 const char *strtab,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L228 unsigned int symindex,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L229 unsigned int relsec,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L230 struct module *me)

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L231{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L232 write_relocate_add(sechdrs, strtab, symindex, relsec, me, false);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L233}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L234#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L235(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L236#endif

Kconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.

L237(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L238int module_finalize(const Elf_Ehdr *hdr,

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L239 const Elf_Shdr *sechdrs,

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L240 struct module *me)

이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L241{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L242 const Elf_Shdr *s, *alt = NULL, *locks = NULL,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L243 *orc = NULL, *orc_ip = NULL,

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L244 *retpolines = NULL, *returns = NULL, *ibt_endbr = NULL,

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L245 *calls = NULL, *cfi = NULL;

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L246 char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L247(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L248 for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L249 if (!strcmp(".altinstructions", secstrings + s->sh_name))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L250 alt = s;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L251 if (!strcmp(".smp_locks", secstrings + s->sh_name))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L252 locks = s;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L253 if (!strcmp(".orc_unwind", secstrings + s->sh_name))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L254 orc = s;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L255 if (!strcmp(".orc_unwind_ip", secstrings + s->sh_name))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L256 orc_ip = s;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L257 if (!strcmp(".retpoline_sites", secstrings + s->sh_name))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L258 retpolines = s;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L259 if (!strcmp(".return_sites", secstrings + s->sh_name))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L260 returns = s;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L261 if (!strcmp(".call_sites", secstrings + s->sh_name))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L262 calls = s;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L263 if (!strcmp(".cfi_sites", secstrings + s->sh_name))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L264 cfi = s;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L265 if (!strcmp(".ibt_endbr_seal", secstrings + s->sh_name))

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L266 ibt_endbr = s;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L267 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L268(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L269 its_init_mod(me);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L270(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L271 if (retpolines || cfi) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L272 void *rseg = NULL, *cseg = NULL;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L273 unsigned int rsize = 0, csize = 0;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L274(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L275 if (retpolines) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L276 rseg = (void *)retpolines->sh_addr;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L277 rsize = retpolines->sh_size;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L278 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L279(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L280 if (cfi) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L281 cseg = (void *)cfi->sh_addr;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L282 csize = cfi->sh_size;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L283 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L284(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L285 apply_fineibt(rseg, rseg + rsize, cseg, cseg + csize);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L286 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L287 if (retpolines) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L288 void *rseg = (void *)retpolines->sh_addr;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L289 apply_retpolines(rseg, rseg + retpolines->sh_size);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L290 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L291(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L292 its_fini_mod(me);

helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L293(blank)

빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L294 if (returns) {

이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

RISC-V · Linux 6.18.37

HI20/LO12 pair, CALL_PLT, JAL/BRANCH와 relaxation

AUIPC+JALR 또는 LUI+ADDI처럼 여러 instruction이 하나의 symbol address를 구성하므로 paired relocation을 일관되게 적용한다. JAL range 밖 target은 PLT나 long-call sequence를 사용한다.

원본 코드: arch/riscv/kernel/module.c:759-855

759	if (!*relocation_hashtable)
760		return 0;
761 
762	__hash_init(*relocation_hashtable, hashtable_size);
763 
764	return hashtable_bits;
765}
766 
767int apply_relocate_add(Elf_Shdr *sechdrs, const char *strtab,
768		       unsigned int symindex, unsigned int relsec,
769		       struct module *me)
770{
771	Elf_Rela *rel = (void *) sechdrs[relsec].sh_addr;
772	int (*handler)(struct module *me, void *location, Elf_Addr v);
773	Elf_Sym *sym;
774	void *location;
775	unsigned int i, type;
776	unsigned int j_idx = 0;
777	Elf_Addr v;
778	int res;
779	unsigned int num_relocations = sechdrs[relsec].sh_size / sizeof(*rel);
780	struct hlist_head *relocation_hashtable;
781	unsigned int hashtable_bits;
782	LIST_HEAD(used_buckets_list);
783 
784	hashtable_bits = initialize_relocation_hashtable(num_relocations,
785							 &relocation_hashtable);
786 
787	if (!relocation_hashtable)
788		return -ENOMEM;
789 
790	pr_debug("Applying relocate section %u to %u\n", relsec,
791	       sechdrs[relsec].sh_info);
792 
793	for (i = 0; i < num_relocations; i++) {
794		/* This is where to make the change */
795		location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr
796			+ rel[i].r_offset;
797		/* This is the symbol it is referring to */
798		sym = (Elf_Sym *)sechdrs[symindex].sh_addr
799			+ ELF_RISCV_R_SYM(rel[i].r_info);
800		if (IS_ERR_VALUE(sym->st_value)) {
801			/* Ignore unresolved weak symbol */
802			if (ELF_ST_BIND(sym->st_info) == STB_WEAK)
803				continue;
804			pr_warn("%s: Unknown symbol %s\n",
805				me->name, strtab + sym->st_name);
806			return -ENOENT;
807		}
808 
809		type = ELF_RISCV_R_TYPE(rel[i].r_info);
810 
811		if (type < ARRAY_SIZE(reloc_handlers))
812			handler = reloc_handlers[type].reloc_handler;
813		else
814			handler = NULL;
815 
816		if (!handler) {
817			pr_err("%s: Unknown relocation type %u\n",
818			       me->name, type);
819			return -EINVAL;
820		}
821 
822		v = sym->st_value + rel[i].r_addend;
823 
824		if (type == R_RISCV_PCREL_LO12_I || type == R_RISCV_PCREL_LO12_S) {
825			unsigned int j = j_idx;
826			bool found = false;
827 
828			do {
829				unsigned long hi20_loc =
830					sechdrs[sechdrs[relsec].sh_info].sh_addr
831					+ rel[j].r_offset;
832				u32 hi20_type = ELF_RISCV_R_TYPE(rel[j].r_info);
833 
834				/* Find the corresponding HI20 relocation entry */
835				if (hi20_loc == sym->st_value
836				    && (hi20_type == R_RISCV_PCREL_HI20
837					|| hi20_type == R_RISCV_GOT_HI20)) {
838					s32 hi20, lo12;
839					Elf_Sym *hi20_sym =
840						(Elf_Sym *)sechdrs[symindex].sh_addr
841						+ ELF_RISCV_R_SYM(rel[j].r_info);
842					unsigned long hi20_sym_val =
843						hi20_sym->st_value
844						+ rel[j].r_addend;
845 
846					/* Calculate lo12 */
847					size_t offset = hi20_sym_val - hi20_loc;
848					if (IS_ENABLED(CONFIG_MODULE_SECTIONS)
849					    && hi20_type == R_RISCV_GOT_HI20) {
850						offset = module_emit_got_entry(
851							 me, hi20_sym_val);
852						offset = offset - hi20_loc;
853					}
854					hi20 = (offset + 0x800) & 0xfffff000;
855					lo12 = offset - hi20;

라인 바이 라인 주석

빈 줄과 전처리 경계도 생략하지 않았습니다. 원본의 97개 줄에 각각 설명을 붙였습니다.

L759 if (!*relocation_hashtable)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L760 return 0;

이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L761(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L762 __hash_init(*relocation_hashtable, hashtable_size);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L763(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L764 return hashtable_bits;

이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L765}

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L766(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L767int apply_relocate_add(Elf_Shdr *sechdrs, const char *strtab,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L768 unsigned int symindex, unsigned int relsec,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L769 struct module *me)

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L770{

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L771 Elf_Rela *rel = (void *) sechdrs[relsec].sh_addr;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L772 int (*handler)(struct module *me, void *location, Elf_Addr v);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L773 Elf_Sym *sym;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L774 void *location;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L775 unsigned int i, type;

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L776 unsigned int j_idx = 0;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L777 Elf_Addr v;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L778 int res;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L779 unsigned int num_relocations = sechdrs[relsec].sh_size / sizeof(*rel);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L780 struct hlist_head *relocation_hashtable;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L781 unsigned int hashtable_bits;

선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.

L782 LIST_HEAD(used_buckets_list);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L783(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L784 hashtable_bits = initialize_relocation_hashtable(num_relocations,

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L785 &relocation_hashtable);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L786(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L787 if (!relocation_hashtable)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L788 return -ENOMEM;

이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L789(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L790 pr_debug("Applying relocate section %u to %u\n", relsec,

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L791 sechdrs[relsec].sh_info);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L792(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L793 for (i = 0; i < num_relocations; i++) {

range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.

L794 /* This is where to make the change */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L795 location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L796 + rel[i].r_offset;

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L797 /* This is the symbol it is referring to */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L798 sym = (Elf_Sym *)sechdrs[symindex].sh_addr

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L799 + ELF_RISCV_R_SYM(rel[i].r_info);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L800 if (IS_ERR_VALUE(sym->st_value)) {

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L801 /* Ignore unresolved weak symbol */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L802 if (ELF_ST_BIND(sym->st_info) == STB_WEAK)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L803 continue;

정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.

L804 pr_warn("%s: Unknown symbol %s\n",

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L805 me->name, strtab + sym->st_name);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L806 return -ENOENT;

이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.

L807 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L808(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L809 type = ELF_RISCV_R_TYPE(rel[i].r_info);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L810(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L811 if (type < ARRAY_SIZE(reloc_handlers))

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L812 handler = reloc_handlers[type].reloc_handler;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L813 else

앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.

L814 handler = NULL;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L815(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L816 if (!handler) {

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L817 pr_err("%s: Unknown relocation type %u\n",

불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.

L818 me->name, type);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L819 return -EINVAL;

지원하지 않는 type, 잘못된 pair 또는 overflow relocation을 거부한다.

L820 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L821(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L822 v = sym->st_value + rel[i].r_addend;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L823(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L824 if (type == R_RISCV_PCREL_LO12_I || type == R_RISCV_PCREL_LO12_S) {

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L825 unsigned int j = j_idx;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L826 bool found = false;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L827(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L828 do {

최소 한 번 실행되는 retry 또는 순회 구간이다. 탈출 조건이 다른 CPU나 hardware의 진행에 의존하는지 확인한다.

L829 unsigned long hi20_loc =

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L830 sechdrs[sechdrs[relsec].sh_info].sh_addr

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L831 + rel[j].r_offset;

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L832 u32 hi20_type = ELF_RISCV_R_TYPE(rel[j].r_info);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L833(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L834 /* Find the corresponding HI20 relocation entry */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L835 if (hi20_loc == sym->st_value

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L836 && (hi20_type == R_RISCV_PCREL_HI20

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L837 || hi20_type == R_RISCV_GOT_HI20)) {

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L838 s32 hi20, lo12;

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L839 Elf_Sym *hi20_sym =

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L840 (Elf_Sym *)sechdrs[symindex].sh_addr

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L841 + ELF_RISCV_R_SYM(rel[j].r_info);

helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.

L842 unsigned long hi20_sym_val =

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L843 hi20_sym->st_value

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L844 + rel[j].r_addend;

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L845(blank)

빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.

L846 /* Calculate lo12 */

Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.

L847 size_t offset = hi20_sym_val - hi20_loc;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L848 if (IS_ENABLED(CONFIG_MODULE_SECTIONS)

이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.

L849 && hi20_type == R_RISCV_GOT_HI20) {

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L850 offset = module_emit_got_entry(

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L851 me, hi20_sym_val);

이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.

L852 offset = offset - hi20_loc;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L853 }

C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.

L854 hi20 = (offset + 0x800) & 0xfffff000;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

L855 lo12 = offset - hi20;

계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.

05 · WORKED EXAMPLE

숫자로 검산하기

01

PC-relative branch displacement 계산

patch 위치 P=0xffffffffc0201000, target S=0xffffffff81002000, addend A=0이라고 가정한다.

  1. formuladisplacement S+A-P = -0x3f1ff000, 약 -1009.996MiB다.
  2. arm64CALL26 범위 ±128MiB를 벗어나므로 직접 BL encoding이 불가능하고 가까운 module PLT가 필요하다.
  3. x86-64signed rel32 ±2GiB에는 들어가므로 displacement를 32-bit field에 encode할 수 있다.
  4. RISC-VJAL ±1MiB를 훨씬 벗어나므로 AUIPC+JALR long call 또는 PLT sequence가 필요하다.

결론relocation 가능 여부는 64-bit 주소를 저장할 수 있느냐가 아니라 해당 instruction immediate의 범위와 alignment로 판단한다.

06 · DEEP DIVE

경계별 상세 분석

01

공통 kernel core와 architecture hook의 경계

module loader가 ELF section, symbol과 relocation table을 검증하고 core/init layout에 memory를 할당한 뒤 architecture apply_relocate_add()가 S+A-P 등의 식으로 location을 patch한다. 이후 permission을 RX/RO/RW로 강화한다.

untrusted module ELF parsing, writable text patch 구간, instruction-cache publication과 module live state 전환을 분리한다. relocation 실패 시 module init을 호출하기 전에 모든 allocation을 unwind해야 한다.

02

arm64: ADRP/ADD, CALL26/JUMP26와 module PLT

AArch64 relocation은 instruction bitfield에 page-relative 또는 PC-relative immediate를 encode한다. ±128MiB branch range를 벗어나면 module PLT entry가 absolute target을 load해 branch한다.

text patch를 완료한 뒤 D-cache clean과 I-cache invalidate를 수행하고 permission을 executable/read-only로 바꾼다. 디버깅할 때는 relocation type, S/A/P 값, encoded immediate, PLT address, module text range와 icache flush를 확인한다.

03

x86-64: R_X86_64_64, PC32/PLT32와 alternatives

absolute 64-bit와 signed 32-bit PC-relative relocation이 중심이다. module allocation을 kernel text와 가까운 범위에 두어 rel32 call/jump의 ±2GiB 제한을 만족시키며 필요 시 PLT/thunk 성격의 stub을 사용한다.

writable alias/text_poke와 instruction serialization 뒤 executable permission을 적용하고 ORC/ftrace metadata도 relocated address에 맞춰 등록한다. 디버깅할 때는 relocation type, 64-bit result 또는 s32 displacement, P/S/A, module region, ENDBR와 ORC table을 확인한다.

04

RISC-V: HI20/LO12 pair, CALL_PLT, JAL/BRANCH와 relaxation

AUIPC+JALR 또는 LUI+ADDI처럼 여러 instruction이 하나의 symbol address를 구성하므로 paired relocation을 일관되게 적용한다. JAL range 밖 target은 PLT나 long-call sequence를 사용한다.

instruction pair patch와 icache sync가 끝난 뒤 모든 hart가 새 code를 보도록 remote fence.i 경로를 사용한다. 디버깅할 때는 relocation pair, S/A/P, HI20 rounding, LO12 sign extension, PLT entry와 remote icache synchronization을 본다.

05

객체 수명과 소유권을 먼저 고정한다

module text/data와 symbol/unwind/ftrace metadata는 refcount와 RCU grace period가 끝날 때까지 유지된다. 실행 중인 CPU의 return address가 module text를 가리키면 unload할 수 없다.

주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.

06

latency upper bound는 hardware instruction 하나가 아니다

load 시간은 section allocation, relocation 수, symbol lookup, signature/ORC validation과 icache synchronization에 비례한다. runtime hot path에는 직접 branch 또는 PLT indirection 비용이 남는다.

평균값 외에 interrupt-off 구간, remote CPU 응답, firmware 호출과 retry 횟수를 분리해야 최악 지연의 원인을 찾을 수 있다.

07 · FAILURE

실패를 어떤 증거로 나눌 것인가

분류관찰되는 결과첫 확인값
arm64immediate alignment/range overflow, BTI/PAC landing 요구 또는 stale icache는 load 실패와 이전 instruction 실행을 만든다.relocation type, S/A/P 값, encoded immediate, PLT address, module text range와 icache flush를 확인한다.
x86-6432-bit displacement overflow, CET IBT ENDBR 누락 또는 metadata 불일치는 load error, control-protection fault와 잘못된 unwind를 만든다.relocation type, 64-bit result 또는 s32 displacement, P/S/A, module region, ENDBR와 ORC table을 확인한다.
RISC-VHI20 rounding과 LO12 sign 처리, paired relocation 위치 또는 compressed instruction width 오류가 완전히 다른 target을 만든다.relocation pair, S/A/P, HI20 rounding, LO12 sign extension, PLT entry와 remote icache synchronization을 본다.

08 · LAB

재현과 계측 절차

  1. 작은 test module을 architecture별로 빌드해 readelf -r, objdump -dr와 load 후 runtime address를 비교한다.
  2. target 거리를 인위적으로 늘려 PLT/thunk 생성 또는 relocation overflow 오류를 관찰한다.
  3. 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
  4. 소스만 읽고 끝내지 않고 최종 vmlinuxobjdump -dr, readelf -SW 결과로 선택된 alternative와 section 배치를 확인한다.

09 · REFERENCES

원문 좌표

Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.