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해야 한다.
02 · CONTRACT
공통 계약과 architecture 구현
| architecture | 핵심 mechanism | 실패 형태 | 확인할 상태 |
|---|---|---|---|
| arm64 | ADRP/ADD, CALL26/JUMP26와 module PLT | immediate 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-64 | R_X86_64_64, PC32/PLT32와 alternatives | 32-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-V | HI20/LO12 pair, CALL_PLT, JAL/BRANCH와 relaxation | HI20 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
세 그림으로 먼저 읽기
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을 본다.
apply_relocate_add()가 S+A-P 등의 식으로 location을 patch한다. 이후 permission을 RX/RO/RW로 강화한다.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개 줄에 각각 설명을 붙였습니다.
insn = aarch64_insn_gen_branch_imm((u64)place, val,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
AARCH64_INSN_BRANCH_NOLINK);이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
WRITE_PLACE(place, cpu_to_le32(insn), me);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
return 0;이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
int apply_relocate_add(Elf64_Shdr *sechdrs,이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
const char *strtab,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned int symindex,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned int relsec,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct module *me)이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
unsigned int i;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
int ovf;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
bool overflow_check;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
Elf64_Sym *sym;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
void *loc;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
u64 val;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
Elf64_Rela *rel = (void *)sechdrs[relsec].sh_addr;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
for (i = 0; i < sechdrs[relsec].sh_size / sizeof(*rel); i++) {각 RELA entry를 순회하며 symbol, addend와 patch location을 계산한다.
/* loc corresponds to P in the AArch64 ELF document. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
loc = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
+ rel[i].r_offset;이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* sym is the ELF symbol we're referring to. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
sym = (Elf64_Sym *)sechdrs[symindex].sh_addr계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
+ ELF64_R_SYM(rel[i].r_info);helper 또는 architecture operation을 실행한다. arm64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* val corresponds to (S + A) in the AArch64 ELF document. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
val = sym->st_value + rel[i].r_addend;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Check for overflow by default. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
overflow_check = true;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Perform the static relocation. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
switch (ELF64_R_TYPE(rel[i].r_info)) {ABS, PREL, CALL/JUMP, ADRP 등 AArch64 relocation별 encoding을 선택한다.
/* Null relocations. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
case R_ARM_NONE:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
case R_AARCH64_NONE:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
ovf = 0;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Data relocations. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
case R_AARCH64_ABS64:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
overflow_check = false;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
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를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case R_AARCH64_ABS32:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
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를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case R_AARCH64_ABS16:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
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를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case R_AARCH64_PREL64:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
overflow_check = false;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
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를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case R_AARCH64_PREL32:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
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를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case R_AARCH64_PREL16:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
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를 연결해 본다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
(blank)빈 줄은 arm64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* MOVW instruction relocations. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
case R_AARCH64_MOVW_UABS_G0_NC:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
overflow_check = false;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
fallthrough;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
case R_AARCH64_MOVW_UABS_G0:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 0,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
AARCH64_INSN_IMM_MOVKZ, me);이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case R_AARCH64_MOVW_UABS_G1_NC:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
overflow_check = false;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
fallthrough;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
case R_AARCH64_MOVW_UABS_G1:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 16,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
AARCH64_INSN_IMM_MOVKZ, me);이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case R_AARCH64_MOVW_UABS_G2_NC:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
overflow_check = false;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
fallthrough;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
case R_AARCH64_MOVW_UABS_G2:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
ovf = reloc_insn_movw(RELOC_OP_ABS, loc, val, 32,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
AARCH64_INSN_IMM_MOVKZ, me);이 줄이 arm64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
break;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
case R_AARCH64_MOVW_UABS_G3:hardware reason, flag 또는 operation 종류를 개별 처리 경로로 분기한다. 각 case가 공통 cleanup과 completion 지점에 도달하는지 확인한다.
/* We're using the top bits so we can't overflow. */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
overflow_check = false;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
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개 줄에 각각 설명을 붙였습니다.
if (!early) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
smp_text_poke_sync_each_cpu();helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
mutex_unlock(&text_mutex);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return ret;저장된 실행 문맥으로 돌아가는 제어 이전이다. PC뿐 아니라 privilege, interrupt mask, stack과 architecture status가 함께 복원된다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
int apply_relocate_add(Elf64_Shdr *sechdrs,이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
const char *strtab,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned int symindex,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned int relsec,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct module *me)이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
return write_relocate_add(sechdrs, strtab, symindex, relsec, me, true);이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#ifdef CONFIG_LIVEPATCHKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
void clear_relocate_add(Elf64_Shdr *sechdrs,이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
const char *strtab,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned int symindex,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned int relsec,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct module *me)이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
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를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
#endifKconfig와 compiler feature에 따라 최종 object에 남는 경로가 달라지는 전처리 경계다. 대상 .config와 disassembly로 실제 선택을 확인한다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
int module_finalize(const Elf_Ehdr *hdr,이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
const Elf_Shdr *sechdrs,선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
struct module *me)이 줄이 x86-64의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
const Elf_Shdr *s, *alt = NULL, *locks = NULL,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
*orc = NULL, *orc_ip = NULL,Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*retpolines = NULL, *returns = NULL, *ibt_endbr = NULL,Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
*calls = NULL, *cfi = NULL;Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
if (!strcmp(".altinstructions", secstrings + s->sh_name))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
alt = s;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (!strcmp(".smp_locks", secstrings + s->sh_name))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
locks = s;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (!strcmp(".orc_unwind", secstrings + s->sh_name))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
orc = s;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (!strcmp(".orc_unwind_ip", secstrings + s->sh_name))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
orc_ip = s;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (!strcmp(".retpoline_sites", secstrings + s->sh_name))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
retpolines = s;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (!strcmp(".return_sites", secstrings + s->sh_name))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
returns = s;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (!strcmp(".call_sites", secstrings + s->sh_name))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
calls = s;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (!strcmp(".cfi_sites", secstrings + s->sh_name))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
cfi = s;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (!strcmp(".ibt_endbr_seal", secstrings + s->sh_name))이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
ibt_endbr = s;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
its_init_mod(me);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (retpolines || cfi) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
void *rseg = NULL, *cseg = NULL;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
unsigned int rsize = 0, csize = 0;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (retpolines) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
rseg = (void *)retpolines->sh_addr;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
rsize = retpolines->sh_size;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (cfi) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
cseg = (void *)cfi->sh_addr;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
csize = cfi->sh_size;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
apply_fineibt(rseg, rseg + rsize, cseg, cseg + csize);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
if (retpolines) {이 조건이 x86-64 fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
void *rseg = (void *)retpolines->sh_addr;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
apply_retpolines(rseg, rseg + retpolines->sh_size);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
its_fini_mod(me);helper 또는 architecture operation을 실행한다. x86-64에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 x86-64 Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
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개 줄에 각각 설명을 붙였습니다.
if (!*relocation_hashtable)이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return 0;이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
__hash_init(*relocation_hashtable, hashtable_size);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
return hashtable_bits;이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
int apply_relocate_add(Elf_Shdr *sechdrs, const char *strtab,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned int symindex, unsigned int relsec,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
struct module *me)이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
{C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
Elf_Rela *rel = (void *) sechdrs[relsec].sh_addr;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
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를 연결해 본다.
Elf_Sym *sym;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
void *location;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned int i, type;이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
unsigned int j_idx = 0;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
Elf_Addr v;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
int res;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
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를 연결해 본다.
struct hlist_head *relocation_hashtable;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
unsigned int hashtable_bits;선언 또는 macro 확장 일부다. type의 폭과 signedness, per-CPU/task/object 중 어느 수명을 따르는 값인지 확인한다.
LIST_HEAD(used_buckets_list);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
hashtable_bits = initialize_relocation_hashtable(num_relocations,계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
&relocation_hashtable);이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!relocation_hashtable)이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
return -ENOMEM;이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
pr_debug("Applying relocate section %u to %u\n", relsec,이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
sechdrs[relsec].sh_info);이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
for (i = 0; i < num_relocations; i++) {range, CPU mask, relocation 또는 descriptor를 반복 처리한다. 반복 상한과 중간 실패 때 이미 처리한 항목을 되돌리는 경로를 함께 본다.
/* This is where to make the change */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
location = (void *)sechdrs[sechdrs[relsec].sh_info].sh_addr계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
+ rel[i].r_offset;이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
/* This is the symbol it is referring to */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
sym = (Elf_Sym *)sechdrs[symindex].sh_addr계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
+ ELF_RISCV_R_SYM(rel[i].r_info);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
if (IS_ERR_VALUE(sym->st_value)) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
/* Ignore unresolved weak symbol */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (ELF_ST_BIND(sym->st_info) == STB_WEAK)이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
continue;정상 직선 경로를 벗어나 cleanup, retry 또는 다음 항목으로 이동한다. 이동 대상에서 해제하는 resource와 현재까지 획득한 ownership을 맞춘다.
pr_warn("%s: Unknown symbol %s\n",이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
me->name, strtab + sym->st_name);이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
return -ENOENT;이 함수가 Kernel module relocation과 instruction range 단계의 결과 또는 오류를 상위 계층에 전달한다. 반환 전에 lock, interrupt state, reference와 hardware active state가 정리됐는지 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
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를 연결해 본다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (type < ARRAY_SIZE(reloc_handlers))이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
handler = reloc_handlers[type].reloc_handler;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
else앞 조건이 성립하지 않았을 때의 대체 경로다. fast path와 같은 ownership, ordering과 반환 계약을 제공해야 한다.
handler = NULL;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (!handler) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
pr_err("%s: Unknown relocation type %u\n",불가능해야 하는 상태 또는 복구 가능한 오류를 외부에 드러내는 줄이다. 직전 register/object 값을 함께 남겨 재현 가능한 failure signature를 만든다.
me->name, type);이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
return -EINVAL;지원하지 않는 type, 잘못된 pair 또는 overflow relocation을 거부한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
v = sym->st_value + rel[i].r_addend;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
if (type == R_RISCV_PCREL_LO12_I || type == R_RISCV_PCREL_LO12_S) {이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
unsigned int j = j_idx;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
bool found = false;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
do {최소 한 번 실행되는 retry 또는 순회 구간이다. 탈출 조건이 다른 CPU나 hardware의 진행에 의존하는지 확인한다.
unsigned long hi20_loc =이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
sechdrs[sechdrs[relsec].sh_info].sh_addr이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
+ rel[j].r_offset;이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
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를 연결해 본다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Find the corresponding HI20 relocation entry */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
if (hi20_loc == sym->st_value이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
&& (hi20_type == R_RISCV_PCREL_HI20이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
|| hi20_type == R_RISCV_GOT_HI20)) {이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
s32 hi20, lo12;이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
Elf_Sym *hi20_sym =이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(Elf_Sym *)sechdrs[symindex].sh_addr이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
+ ELF_RISCV_R_SYM(rel[j].r_info);helper 또는 architecture operation을 실행한다. RISC-V에서 이 호출이 register write, cache/TLB operation, callback 또는 object lifetime 중 무엇을 바꾸는지 call site와 callee를 연결해 본다.
unsigned long hi20_sym_val =이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
hi20_sym->st_value이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
+ rel[j].r_addend;이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
(blank)빈 줄은 RISC-V Kernel module relocation과 instruction range 경로에서 한 상태 묶음이 끝나는 위치다. 위쪽에서 만든 값이 아래쪽에서 소비되는지 구간을 나눠 읽는다.
/* Calculate lo12 */Linux 원본 주석이다. 바로 아래 코드의 호출 조건, hardware 제약 또는 예외 처리를 설명하므로 실행 줄과 함께 읽는다.
size_t offset = hi20_sym_val - hi20_loc;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
if (IS_ENABLED(CONFIG_MODULE_SECTIONS)이 조건이 RISC-V fast path와 fallback/error path를 가른다. 조건에 쓰인 flag가 어느 CPU 또는 object의 상태인지, 동시에 바뀔 수 있는지 확인한다.
&& hi20_type == R_RISCV_GOT_HI20) {이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
offset = module_emit_got_entry(계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
me, hi20_sym_val);이 줄이 RISC-V의 현재 상태에서 읽는 register와 memory, 그리고 다음 줄에 남기는 값을 적는다. Kernel module relocation과 instruction range의 공통 kernel 계약과 architecture 전용 side effect를 분리해 해석한다.
offset = offset - hi20_loc;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
}C block의 시작 또는 끝이다. lock, RCU, preemption과 interrupt-disabled 범위를 이 중괄호 바깥 호출까지 넘겨 추정하지 않는다.
hi20 = (offset + 0x800) & 0xfffff000;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
lo12 = offset - hi20;계산한 pointer, flag, register image 또는 generation을 다음 단계가 읽을 위치에 저장한다. 값의 단위, address space와 publication ordering을 확인한다.
05 · WORKED EXAMPLE
숫자로 검산하기
PC-relative branch displacement 계산
patch 위치 P=0xffffffffc0201000, target S=0xffffffff81002000, addend A=0이라고 가정한다.
- formuladisplacement S+A-P = -0x3f1ff000, 약 -1009.996MiB다.
- arm64CALL26 범위 ±128MiB를 벗어나므로 직접 BL encoding이 불가능하고 가까운 module PLT가 필요하다.
- x86-64signed rel32 ±2GiB에는 들어가므로 displacement를 32-bit field에 encode할 수 있다.
- RISC-VJAL ±1MiB를 훨씬 벗어나므로 AUIPC+JALR long call 또는 PLT sequence가 필요하다.
결론relocation 가능 여부는 64-bit 주소를 저장할 수 있느냐가 아니라 해당 instruction immediate의 범위와 alignment로 판단한다.
06 · DEEP DIVE
경계별 상세 분석
공통 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해야 한다.
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를 확인한다.
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을 확인한다.
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을 본다.
객체 수명과 소유권을 먼저 고정한다
module text/data와 symbol/unwind/ftrace metadata는 refcount와 RCU grace period가 끝날 때까지 유지된다. 실행 중인 CPU의 return address가 module text를 가리키면 unload할 수 없다.
주소나 register 값이 맞는지만 확인하면 stale state를 놓친다. producer, publication, consumer와 폐기 지점을 같은 표에 기록한다.
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
실패를 어떤 증거로 나눌 것인가
| 분류 | 관찰되는 결과 | 첫 확인값 |
|---|---|---|
| arm64 | immediate 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-64 | 32-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-V | HI20 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
재현과 계측 절차
- 작은 test module을 architecture별로 빌드해
readelf -r,objdump -dr와 load 후 runtime address를 비교한다. - target 거리를 인위적으로 늘려 PLT/thunk 생성 또는 relocation overflow 오류를 관찰한다.
- 동일한 workload에서 세 architecture의 tracepoint 이름, CPU 번호, PC, stack pointer와 address-space identifier를 같은 열로 기록한다.
- 소스만 읽고 끝내지 않고 최종
vmlinux의objdump -dr,readelf -SW결과로 선택된 alternative와 section 배치를 확인한다.
09 · REFERENCES
원문 좌표
- arm64arch/arm64/kernel/module.c:223-315
- x86-64arch/x86/kernel/module.c:208-294
- RISC-Varch/riscv/kernel/module.c:759-855
Linux kernel source: GPL-2.0-only. 이 글의 코드 발췌는 Linux v6.18.37 원문을 기준으로 하며, 분석 문장은 해당 코드의 실행 조건과 상태 경계를 설명합니다.