# reloc_test_syms.S — 말로 풀어 읽기
Linux v6.18.37 · arch/arm64/kernel/reloc_test_syms.S

프로그램을 링크하거나 다른 주소에 배치하면 명령과 데이터 안에 든 주소 표현도 맞춰야 합니다. 이 파일은 다양한 방식으로 같은 심볼을 읽게 해 재배치 결과를 시험합니다. 실제 새 커널을 복사하는 코드는 아닙니다.

## 처음 읽을 때
주소를 직접 저장한 경우와 “여기서 몇 바이트 떨어져 있다”를 저장한 경우를 비교하세요. 상대값은 기준 주소를 더해야 실제 주소가 됩니다.

## 더 깊이 살펴볼 때
ADRP의 4 KiB 계산 단위, signed MOVW 재배치, 16·32비트 값의 확장 방식과 최종 링크 배치를 따로 검토해 보세요.

![단계별 개념 그림](../media/reloc_test_syms-overview.png)
화살표는 도구 사이 정보 전달과 주소 구성 관계입니다. 링크 단계와 CPU 실행 단계를 구분해 읽습니다.

## 16·32·64비트 절대 데이터
원본 1–25행

### 6행
```asm
#include <linux/linkage.h>
```
C의 테스트 배열에서 호출할 작은 어셈블리 함수들의 시작·끝·정렬을 선언하는 linkage 매크로를 가져옵니다. 실제 재배치 검사는 아래 명령과 리터럴 데이터를 적재한 뒤 수행합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.

### 8행
```asm
SYM_FUNC_START(absolute_data64)
```
모듈 로더의 R_AARCH64_ABS64 재배치가 올바른지 검사하는 작은 함수입니다. 주소의 메모리를 읽는 테스트가 아니라 절대 심볼값을 상수 데이터로 받아 x0에 반환합니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 9행
```asm
	ldr	x0, 0f
```
뒤의 64비트 리터럴을 x0에 읽습니다. 모듈 재배치가 끝났다면 그 자리에는 sym64_abs의 실제 테스트 상수 0xffff880000cccccc가 들어 있어야 합니다. 이 LDR 리터럴 형식은 PC 기준으로 레이블 위치를 찾아 그곳의 메모리를 읽습니다. 레이블 주소 자체를 구하는 ADR과 구분합니다.

### 10행
```asm
	ret
```
읽은 절대값을 테스트 호출자에게 반환합니다. reloc_test_core.c는 기대 상수와 비교하여 통과 여부를 출력합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 11행
```asm
0:	.quad	sym64_abs
```
이 .quad은 CPU 명령이 아니라 항목당 8바이트 데이터와 필요한 재배치 정보를 내보내는 어셈블러 지시문입니다. 아직 확정되지 않은 심벌 값은 링크 또는 모듈 적재 시의 재배치 처리로 채웁니다. 이 테스트 함수가 실행될 때에는 앞의 load 명령이 채워진 데이터를 읽습니다. 64비트 절대 심볼 재배치가 적용될 데이터 자리를 만듭니다. sym64_abs는 C 파일에서 .set으로 정의한 숫자 심볼이며 일반 변수의 저장 내용을 읽는 것은 아닙니다.

### 12행
```asm
SYM_FUNC_END(absolute_data64)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 14행
```asm
SYM_FUNC_START(absolute_data32)
```
R_AARCH64_ABS32 재배치를 검사하는 함수입니다. 32비트 리터럴을 읽은 결과가 C 쪽의 기대값 0xf800cccc와 같은지 확인합니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 15행
```asm
	ldr	w0, 0f
```
32비트 절대 심볼 리터럴을 w0에 읽습니다. W 레지스터에 쓰면 x0 상위 32비트가 0이 되므로 64비트 C 반환값도 0x00000000f800cccc가 됩니다. 이 LDR 리터럴 형식은 PC 기준으로 레이블 위치를 찾아 그곳의 메모리를 읽습니다. 레이블 주소 자체를 구하는 ADR과 구분합니다.

### 16행
```asm
	ret
```
32비트 절대 심볼을 읽은 결과를 x0로 반환하여 C 쪽 기대값과 비교하게 합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 17행
```asm
0:	.long	sym32_abs
```
이 .long은 CPU 명령이 아니라 항목당 4바이트 데이터와 필요한 재배치 정보를 내보내는 어셈블러 지시문입니다. 아직 확정되지 않은 심벌 값은 링크 또는 모듈 적재 시의 재배치 처리로 채웁니다. 이 테스트 함수가 실행될 때에는 앞의 load 명령이 채워진 데이터를 읽습니다. sym32_abs 값을 32비트 데이터 필드에 채우도록 ABS32 재배치를 만듭니다.

### 18행
```asm
SYM_FUNC_END(absolute_data32)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 20행
```asm
SYM_FUNC_START(absolute_data16)
```
R_AARCH64_ABS16 재배치를 검사하는 함수입니다. 16비트 테스트 상수 0xf8cc를 읽어 C 코드의 기대값과 비교합니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 21행
```asm
	adr	x0, 0f
```
16비트 리터럴에는 LDRH 리터럴 형식이 없으므로 먼저 뒤의 데이터 주소를 x0에 구합니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다.

### 22행
```asm
	ldrh	w0, [x0]
```
그 주소에서 16비트 값을 0으로 확장하여 w0에 읽습니다. 결과 x0는 테스트 상수 0xf8cc가 됩니다.

### 23행
```asm
	ret
```
16비트 절대 심볼값을 테스트 호출자에게 돌려줍니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 24행
```asm
0:	.short	sym16_abs, 0
```
이 .short은 CPU 명령이 아니라 항목당 2바이트 데이터와 필요한 재배치 정보를 내보내는 어셈블러 지시문입니다. 아직 확정되지 않은 심벌 값은 링크 또는 모듈 적재 시의 재배치 처리로 채웁니다. 이 테스트 함수가 실행될 때에는 앞의 load 명령이 채워진 데이터를 읽습니다. ABS16 재배치로 채울 sym16_abs와 뒤따르는 16비트 0을 배치합니다. 앞의 halfword가 검사 대상이고 뒤의 0은 데이터 크기를 맞추는 공간입니다.

### 25행
```asm
SYM_FUNC_END(absolute_data16)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## MOVW 재배치 조합
원본 26–41행

### 27행
```asm
SYM_FUNC_START(signed_movw)
```
부호 있는 MOVW 재배치를 통해 64비트 절대값을 만드는 테스트입니다. 명령의 즉시값뿐 아니라 부호에 따른 MOVZ/MOVN 선택이 올바르게 적용되는지도 확인합니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 28행
```asm
	movz	x0, #:abs_g2_s:sym64_abs
```
sym64_abs의 비트 32~47에 해당하는 그룹을 부호 있는 재배치로 넣습니다. 이 테스트 상수는 음수로 해석되는 값이므로 모듈 로더가 MOVN 형식도 선택할 수 있습니다. 소스에 MOVZ라고 적힌 것만 보고 나머지 상위 비트가 항상 0이라고 설명하면 안 됩니다. 이 재배치는 명령어의 즉시값 비트를 채우는 것이며, 실행 중 심벌이 가리키는 메모리 데이터를 읽는 작업은 아닙니다.
:abs_g2_s:는 signed MOVW 재배치입니다. 음수 값 구성에는 링커가 MOVN 형태를 선택할 수 있어 원문 MOVZ만으로 상위 비트를 단정하지 않습니다.

### 29행
```asm
	movk	x0, #:abs_g1_nc:sym64_abs
```
비트 16~31 그룹을 MOVK로 덮어씁니다. 이미 만든 나머지 비트는 유지하며 _nc는 이 부분 재배치에서 별도 overflow 검사를 하지 않는다는 뜻입니다.

### 30행
```asm
	movk	x0, #:abs_g0_nc:sym64_abs
```
하위 16비트를 채워 테스트용 64비트 절대값을 완성합니다. MOVK는 선택한 16비트 조각만 덮어쓰고 나머지 비트는 유지합니다.

### 31행
```asm
	ret
```
조립한 값을 C 테스트에 돌려줍니다. 기대값은 absolute_data64와 같은 0xffff880000cccccc입니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 32행
```asm
SYM_FUNC_END(signed_movw)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 34행
```asm
SYM_FUNC_START(unsigned_movw)
```
부호 없는 MOVW 재배치 네 개로 64비트 절대값을 만드는 테스트입니다. 상위 그룹부터 각 16비트 조각을 채웁니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 35행
```asm
	movz	x0, #:abs_g3:sym64_abs
```
sym64_abs의 최상위 비트 48~63을 넣고 다른 비트는 초기화합니다. 뒤의 세 MOVK가 나머지 48비트를 채웁니다. 이 재배치는 명령어의 즉시값 비트를 채우는 것이며, 실행 중 심벌이 가리키는 메모리 데이터를 읽는 작업은 아닙니다.

### 36행
```asm
	movk	x0, #:abs_g2_nc:sym64_abs
```
비트 32~47을 채웁니다. 앞서 넣은 최상위 16비트는 그대로 보존합니다. MOVK는 선택한 16비트 조각만 덮어쓰고 나머지 비트는 유지합니다.

### 37행
```asm
	movk	x0, #:abs_g1_nc:sym64_abs
```
비트 16~31을 채워 절대값을 계속 구성합니다. MOVK는 선택한 16비트 조각만 덮어쓰고 나머지 비트는 유지합니다.

### 38행
```asm
	movk	x0, #:abs_g0_nc:sym64_abs
```
하위 16비트를 채워 64비트 테스트 상수를 완성합니다. MOVK는 선택한 16비트 조각만 덮어쓰고 나머지 비트는 유지합니다.

### 39행
```asm
	ret
```
부호 없는 MOVW 재배치로 구성한 값이 기대 상수와 같은지 C 코드가 확인하도록 반환합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 40행
```asm
SYM_FUNC_END(unsigned_movw)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## 페이지 경계 근처의 ADRP
원본 42–62행

### 42행
```asm
	.align	12
```
다음 테스트를 4KiB 경계 기준으로 배치합니다. ADRP는 커널의 PAGE_SIZE 설정과 별개로 4KiB 단위 페이지 주소를 계산하므로 이 경계가 중요합니다. AArch64의 .align 인자는 바이트 수가 아니라 2의 지수입니다. 빌드할 때 필요한 패딩을 넣어 그 경계를 맞춥니다.

### 43행
```asm
	.space	0xff8
```
0xff8바이트를 채워 다음 테스트를 4KiB 경계 가까이에 놓습니다. 재배치 명령의 위치가 페이지 경계에 가까울 때도 주소 계산이 맞는지 시험하기 위한 배치입니다.

### 44행
```asm
SYM_FUNC_START(relative_adrp)
```
같은 테스트 모듈의 sym64_rel 주소를 ADRP+ADD로 만드는 함수입니다. 결과는 변수의 값이 아니라 변수 자체의 주소여야 합니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 45행
```asm
	adrp	x0, sym64_rel
```
PC 상대 페이지 재배치로 sym64_rel이 속한 4KiB 페이지 주소를 x0에 만듭니다. 모듈이 어느 주소에 적재됐는지에 따라 명령의 즉시값이 조정됩니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다. ADRP가 만드는 주소는 4KiB 경계까지만입니다. 심벌의 하위 12비트가 0이면 그 자체가 심벌 주소이고, 그렇지 않으면 별도 오프셋 계산이 필요합니다.

### 46행
```asm
	add	x0, x0, #:lo12:sym64_rel
```
심볼 주소의 하위 12비트를 더해 페이지 안의 정확한 바이트 주소를 완성합니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.

### 47행
```asm
	ret
```
구한 sym64_rel 주소를 반환합니다. C 테스트는 &sym64_rel과 일치하는지 확인합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 48행
```asm
SYM_FUNC_END(relative_adrp)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 50행
```asm
	.align	12
```
다음 ADRP 테스트의 배치 기준도 4KiB 경계에 맞춥니다. AArch64의 .align 인자는 바이트 수가 아니라 2의 지수입니다. 빌드할 때 필요한 패딩을 넣어 그 경계를 맞춥니다.

### 51행
```asm
	.space	0xffc
```
0xffc바이트를 채워 다음 테스트를 페이지 경계 가까이에 놓습니다. 명령 위치의 페이지 계산과 먼 심볼 재배치 처리를 확인하기 위한 공간입니다.

### 52행
```asm
SYM_FUNC_START(relative_adrp_far)
```
모듈 내부 심볼 대신 커널의 memstart_addr 주소를 대상으로 ADRP 재배치를 시험합니다. 모듈과 커널 사이 거리에 따른 처리도 검증 대상입니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 53행
```asm
	adrp	x0, memstart_addr
```
memstart_addr가 있는 4KiB 페이지 주소를 만듭니다. 모듈 로더는 이 외부 심볼의 최종 주소를 이용해 ADRP 재배치를 적용합니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다. ADRP가 만드는 주소는 4KiB 경계까지만입니다. 심벌의 하위 12비트가 0이면 그 자체가 심벌 주소이고, 그렇지 않으면 별도 오프셋 계산이 필요합니다.

### 54행
```asm
	add	x0, x0, #:lo12:memstart_addr
```
memstart_addr의 페이지 내부 오프셋을 더해 변수의 주소를 완성합니다. memstart_addr에 저장된 물리 메모리 시작값을 읽는 명령은 아닙니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.

### 55행
```asm
	ret
```
&memstart_addr와 비교할 주소를 x0로 반환합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 56행
```asm
SYM_FUNC_END(relative_adrp_far)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 58행
```asm
SYM_FUNC_START(relative_adr)
```
ADRP의 페이지 계산 없이 ADR 한 명령으로 PC 상대 주소를 만드는 재배치 테스트입니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 59행
```asm
	adr	x0, sym64_rel
```
R_AARCH64_ADR_PREL_LO21 재배치를 통해 sym64_rel의 주소를 x0에 만듭니다. 주소 차이가 ADR이 표현할 수 있는 범위에 들어가는지도 로더가 처리합니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다.

### 60행
```asm
	ret
```
ADR로 계산한 주소를 C 테스트의 &sym64_rel과 비교하도록 반환합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 61행
```asm
SYM_FUNC_END(relative_adr)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## 상대 데이터를 절대 주소로 복원
원본 63–85행

### 63행
```asm
SYM_FUNC_START(relative_data64)
```
64비트 PC 상대 데이터 재배치를 검사하는 함수입니다. 리터럴에는 주소 자체가 아니라 그 리터럴 위치에서 심볼까지의 차이가 들어갑니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 64행
```asm
	adr	x1, 0f
```
상대 오프셋이 저장된 리터럴의 주소를 x1에 구합니다. 이 주소가 나중에 오프셋을 더할 기준점이 됩니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다.

### 65행
```asm
	ldr	x0, [x1]
```
리터럴의 64비트 상대 오프셋을 x0에 읽습니다. 실제로 x1 주소의 메모리 8바이트를 x0에 읽습니다. 이 주소 형식은 기준 레지스터 x1 자체를 갱신하지 않습니다.

### 66행
```asm
	add	x0, x0, x1
```
리터럴 위치 x1에 오프셋을 더해 sym64_rel의 실제 주소를 복원합니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.

### 67행
```asm
	ret
```
PREL64로 복원한 주소가 &sym64_rel과 같은지 검사하도록 반환합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 68행
```asm
0:	.quad	sym64_rel - .
```
이 .quad은 CPU 명령이 아니라 항목당 8바이트 데이터와 필요한 재배치 정보를 내보내는 어셈블러 지시문입니다. 아직 확정되지 않은 심벌 값은 링크 또는 모듈 적재 시의 재배치 처리로 채웁니다. 이 테스트 함수가 실행될 때에는 앞의 load 명령이 채워진 데이터를 읽습니다. sym64_rel 주소에서 현재 데이터 위치를 뺀 64비트 값을 놓도록 PREL64 재배치를 만듭니다. 점(.)은 이 데이터가 놓이는 위치입니다.
이 .quad가 놓인 주소를 기준으로 한 차이를 저장하므로 실행 시 같은 위치 x1을 더하면 sym64_rel 주소가 됩니다.

### 69행
```asm
SYM_FUNC_END(relative_data64)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 71행
```asm
SYM_FUNC_START(relative_data32)
```
32비트 PC 상대 데이터 재배치를 검사합니다. 상대값을 저장한 위치의 주소에 읽어 온 값을 더해 심볼 주소를 만드는 방식입니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 72행
```asm
	adr	x1, 0f
```
32비트 상대 오프셋 리터럴의 주소를 x1에 구합니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다.

### 73행
```asm
	ldr	w0, [x1]
```
32비트 상대값을 w0에 읽습니다. 이 명령은 x0 상위 비트를 0으로 만듭니다. 따라서 모든 부호 있는 32비트 오프셋을 64비트로 부호 확장하는 일반 해법으로 읽어서는 안 되며 이 테스트의 배치와 함께 봐야 합니다. 이 주소 형식은 기준 레지스터 x1 자체를 갱신하지 않습니다.

### 74행
```asm
	add	x0, x0, x1
```
읽은 상대값을 리터럴의 실제 주소에 더하여 테스트 심볼 주소를 만듭니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.

### 75행
```asm
	ret
```
PREL32 결과를 C 코드가 예상한 sym64_rel 주소와 비교하도록 반환합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 76행
```asm
0:	.long	sym64_rel - .
```
이 .long은 CPU 명령이 아니라 항목당 4바이트 데이터와 필요한 재배치 정보를 내보내는 어셈블러 지시문입니다. 아직 확정되지 않은 심벌 값은 링크 또는 모듈 적재 시의 재배치 처리로 채웁니다. 이 테스트 함수가 실행될 때에는 앞의 load 명령이 채워진 데이터를 읽습니다. 심볼과 이 리터럴 사이의 차이를 32비트 필드에 기록하도록 PREL32 재배치를 만듭니다.

### 77행
```asm
SYM_FUNC_END(relative_data32)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

### 79행
```asm
SYM_FUNC_START(relative_data16)
```
16비트 PC 상대 데이터 재배치를 검사하는 함수입니다. 작은 폭의 상대 데이터도 로더가 올바르게 기록하는지 확인합니다. 이 선언은 함수 심볼과 정렬을 정하고, ARM64 정의에서는 간접 호출이 도착할 BTI C 명령도 진입부에 넣습니다. 여기서는 전역 심벌로 정의하므로 다른 오브젝트에서도 이 함수 이름을 참조할 수 있습니다. 모듈용 EXPORT_SYMBOL 등록은 별도입니다.

### 80행
```asm
	adr	x1, 0f
```
16비트 상대 오프셋이 놓인 주소를 x1에 구합니다. 여기서 얻는 것은 레이블의 주소이며, 그 위치의 메모리 내용은 아직 읽지 않습니다.

### 81행
```asm
	ldrsh	w0, [x1]
```
16비트 상대값을 부호 확장하여 w0에 읽습니다. 다만 목적지가 W 레지스터이므로 x0 전체를 64비트 부호 확장한 것은 아닙니다. 이 소스의 실제 읽기 폭을 그대로 구분해야 합니다. 이 주소 형식은 기준 레지스터 x1 자체를 갱신하지 않습니다.

### 82행
```asm
	add	x0, x0, x1
```
리터럴 기준 주소와 읽은 값을 더하여 테스트 심볼의 주소를 계산합니다. 이 ADD 형식은 NZCV 조건 플래그를 바꾸지 않습니다.

### 83행
```asm
	ret
```
PREL16 주소 계산 결과를 C 쪽 기대값과 비교하도록 반환합니다. 피연산자가 없는 RET는 LR(x30)에 들어 있는 주소에서 실행을 이어갑니다.

### 84행
```asm
0:	.short	sym64_rel - ., 0
```
이 .short은 CPU 명령이 아니라 항목당 2바이트 데이터와 필요한 재배치 정보를 내보내는 어셈블러 지시문입니다. 아직 확정되지 않은 심벌 값은 링크 또는 모듈 적재 시의 재배치 처리로 채웁니다. 이 테스트 함수가 실행될 때에는 앞의 load 명령이 채워진 데이터를 읽습니다. sym64_rel과 현재 위치의 차이를 16비트 데이터로 재배치하고, 뒤에 16비트 0을 추가합니다. 검사 대상은 앞의 상대값 필드입니다.

### 85행
```asm
SYM_FUNC_END(relative_data16)
```
도구에 이 코드 범위의 끝을 알려 크기와 심볼 정보를 기록합니다. CPU가 이 줄 때문에 자동으로 반환하지는 않습니다.

## 설명한 뒤 함께 생각해 볼 질문

### RET 뒤에 데이터가 있어도 되나요?
정상 실행이 RET에서 다른 곳으로 이동하므로 뒤의 리터럴을 실행하지 않습니다. 앞의 LDR 등이 그 위치를 데이터로 읽습니다.

### 상대 데이터에 x1을 더하는 이유는 무엇인가요?
리터럴에는 목표 심볼과 리터럴 위치의 차이가 있습니다. 그 위치를 다시 더해 목표 주소를 복구합니다.

### 테스트가 한 번 통과하면 모든 배치도 보장되나요?
아닙니다. 이 코드의 값, 거리, 정렬 조건에서 해당 재배치 처리를 확인한 결과입니다.
