역할과 입력
- cmpxchg: r0=기존 값, r1=새 값, r2=대상 주소
- cmpxchg64: r0=기존 값의 주소, r1=새 값의 주소, r2=대상 주소
원본 .S · 빌드 조건: Makefile · 명령·주소 표기 읽는 법
65줄 · SHA-256: e6d0cfe899adb2cf119da8ddcfea83461044042d723aed7c3166870ac23c57a3
구조와 흐름
데이터로 포함한 사용자 코드
각 helper는 32바이트 정렬을 사용하며, 64비트 cmpxchg helper는 그보다 긴 공간을 차지합니다. 원본 주석의 0xffff0f60 등은 사용자에게 보이는 주소이며 현재 커널 링크 주소가 아닙니다.
1/* SPDX-License-Identifier: GPL-2.0 */
2/*
3 * AArch32 user helpers.
4 * Based on the kuser helpers in arch/arm/kernel/entry-armv.S.
5 *
6 * Copyright (C) 2005-2011 Nicolas Pitre <nico@fluxnic.net>
7 * Copyright (C) 2012-2018 ARM Ltd.
8 *
9 * The kuser helpers below are mapped at a fixed address by
10 * aarch32_setup_additional_pages() and are provided for compatibility
11 * reasons with 32 bit (aarch32) applications that need them.
12 *
13 * See Documentation/arch/arm/kernel_user_helpers.rst for formal definitions.
14 */
15
16#include <asm/unistd.h>
17
18 .section .rodata
19 .align 5
20 .globl __kuser_helper_start
21__kuser_helper_start:
22
- L16
- ARM64의 시스템 호출 관련 정의를 포함합니다. 다만 아래 helper는 syscall 명령을 수행하는 래퍼가 아니라 사용자 공간에 매핑된 AArch32 명령으로 비교·교환, 장벽, TLS 조회를 직접 수행합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
- L18
- 32비트 호환 프로그램에 제공할 명령 바이트를 커널의 읽기 전용 데이터 영역에 둡니다. ARM64 커널이 이곳을 직접 실행하는 대신, 나중에 AArch32 사용자용 고정 주소 페이지로 복사하여 실행 가능하게 매핑합니다. 이는 빌드 중 출력할 바이트의 섹션을 선택하는 지시문이며, 실행 중 PC나 SP를 옮기지 않습니다.
- L19
- 첫 helper를 32바이트 경계에 맞춥니다. 이 파일은 각 helper의 고정 주소와 마지막 버전 필드 위치를 맞춰야 하므로 정렬이 ABI의 일부입니다. AArch64의 .align 인자는 바이트 수가 아니라 2의 지수입니다. 빌드할 때 필요한 패딩을 넣어 그 경계를 맞춥니다.
- L20
- helper 블록의 시작 심볼을 다른 파일에서도 참조할 수 있게 합니다. vdso.c가 복사할 바이트 범위를 계산할 때 끝 심볼과 함께 사용합니다. 이 표기는 심벌을 다른 오브젝트에서도 참조할 수 있게 하며 CPU가 실행하는 명령을 추가하지 않습니다.
- L21
- AArch32 사용자 helper 바이트 블록의 시작입니다. 시작·끝 사이의 데이터를 사용자에게 매핑할 helper 페이지에 복사합니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.
64비트 비교 후 교환
r4/r5에 기존 값, r6/r7에 새 값을 읽습니다. LDREXD로 읽은 두 워드가 모두 기존 값과 같을 때만 STLEXD를 실행합니다. 배타적 저장 결과가 1이면 다시 읽고 재시도합니다. 비교가 다르면 저장하지 않습니다. DMB 뒤 RSBS는 반환값과 조건 플래그를 만들고, 보존했던 r4–r7을 복원합니다.
23__kuser_cmpxchg64: // 0xffff0f60
24 .inst 0xe92d00f0 // push {r4, r5, r6, r7}
25 .inst 0xe1c040d0 // ldrd r4, r5, [r0]
26 .inst 0xe1c160d0 // ldrd r6, r7, [r1]
27 .inst 0xe1b20f9f // 1: ldrexd r0, r1, [r2]
28 .inst 0xe0303004 // eors r3, r0, r4
29 .inst 0x00313005 // eoreqs r3, r1, r5
30 .inst 0x01a23e96 // stlexdeq r3, r6, [r2]
31 .inst 0x03330001 // teqeq r3, #1
32 .inst 0x0afffff9 // beq 1b
33 .inst 0xf57ff05b // dmb ish
34 .inst 0xe2730000 // rsbs r0, r3, #0
35 .inst 0xe8bd00f0 // pop {r4, r5, r6, r7}
36 .inst 0xe12fff1e // bx lr
37
- L23
- 0xffff0f60에서 호출하는 64비트 비교 후 교환 helper입니다. r0는 기대한 64비트 값의 주소, r1은 새 값의 주소, r2는 변경할 메모리 주소를 받습니다. 명령은 .inst로 넣은 AArch32 코드입니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.
- L24
- r4~r7을 사용자 스택에 저장합니다. 기대값과 새 값의 64비트 쌍을 이 레지스터들에 보관하지만, 호출자에게 돌아갈 때는 원래 값을 보존해야 합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L25
- r0가 가리키는 기대값을 하위·상위 32비트 r4·r5에 읽습니다. 이후 실제 메모리 값과 두 부분을 모두 비교합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L26
- r1이 가리키는 새 64비트 값을 r6·r7에 읽습니다. 비교가 성공한 경우에만 이 쌍을 대상 메모리에 저장합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L27
- 대상 r2의 64비트 값을 r0·r1에 exclusive load합니다. 이 읽기 이후 다른 접근 때문에 배타적 저장이 실패할 수 있으므로 뒤에서 저장 결과를 검사합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L28
- 읽은 값의 하위 32비트 r0와 기대값 r4를 XOR하여 같은지 검사합니다. 같으면 r3=0과 Z=1이 되어 상위 부분 비교를 이어갑니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L29
- 하위 부분이 같을 때만 상위 r1과 r5를 비교합니다. 두 부분이 모두 같아야 다음 조건부 64비트 저장을 시도합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L30
- 비교가 일치하면 r6·r7의 새 값을 r2 주소에 release 성격의 exclusive store로 기록하려고 합니다. r3에는 저장 성공 시 0, 배타적 접근 실패 시 1이 들어갑니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L31
- 실제 저장을 시도한 경우 그 결과 r3가 1인지 검사합니다. 값 불일치는 즉시 실패로 돌려주지만, 다른 접근과의 충돌로 저장만 실패한 경우는 다시 시도할 수 있습니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L32
- exclusive store 결과가 1이면 앞의 exclusive load로 돌아갑니다. 메모리가 바뀌었을 수 있으므로 비교까지 다시 수행합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L33
- Inner Shareable 범위의 메모리 접근 순서를 보장하여 helper가 제공하는 비교·교환의 메모리 장벽 의미를 마무리합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L34
- 0-r3를 r0에 만들고 조건 플래그도 갱신합니다. 성공하면 반환값 0과 carry 설정, 실패하면 0이 아닌 값과 carry 해제가 되어 helper ABI에 맞게 결과를 돌려줍니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L35
- 작업용으로 사용한 r4~r7을 호출 전 값으로 복원합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L36
- LR로 복귀합니다. BX이므로 AArch32 호출자의 ARM/Thumb 실행 상태에 맞춰 돌아갑니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
메모리 배리어와 32비트 비교 후 교환
barrier helper는 DMB ISH 뒤 BX LR로 복귀합니다. cmpxchg는 LDREX로 값을 읽고 SUBS로 r0와 비교합니다. 값이 같으면 STLEXEQ로 r1을 저장하며 경쟁 때문에 저장이 실패했을 때만 반복합니다. 성공 반환값은 0이며 실패에서는 비0과 플래그를 사용합니다.
38 .align 5
39__kuser_memory_barrier: // 0xffff0fa0
40 .inst 0xf57ff05b // dmb ish
41 .inst 0xe12fff1e // bx lr
42
43 .align 5
44__kuser_cmpxchg: // 0xffff0fc0
45 .inst 0xe1923f9f // 1: ldrex r3, [r2]
46 .inst 0xe0533000 // subs r3, r3, r0
47 .inst 0x01823e91 // stlexeq r3, r1, [r2]
48 .inst 0x03330001 // teqeq r3, #1
49 .inst 0x0afffffa // beq 1b
50 .inst 0xf57ff05b // dmb ish
51 .inst 0xe2730000 // rsbs r0, r3, #0
52 .inst 0xe12fff1e // bx lr
53
- L38
- 앞의 64비트 비교·교환 코드 뒤를 32바이트 경계까지 채워 memory barrier helper의 고정 위치를 맞춥니다. AArch64의 .align 인자는 바이트 수가 아니라 2의 지수입니다. 빌드할 때 필요한 패딩을 넣어 그 경계를 맞춥니다.
- L39
- 0xffff0fa0의 메모리 장벽 helper입니다. 예전 AArch32 프로그램이 이 고정 주소를 호출하여 공유 메모리 접근 순서를 보장할 수 있습니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.
- L40
- DMB ISH를 실행하여 Inner Shareable 영역의 앞뒤 메모리 접근 순서를 보장합니다. 데이터를 복사하거나 캐시 전체를 지우는 helper가 아닙니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L41
- 메모리 장벽을 수행한 뒤 AArch32 호출자의 LR로 돌아갑니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L43
- 다음 32비트 비교·교환 helper가 지정된 32바이트 슬롯 경계에서 시작하도록 맞춥니다. AArch64의 .align 인자는 바이트 수가 아니라 2의 지수입니다. 빌드할 때 필요한 패딩을 넣어 그 경계를 맞춥니다.
- L44
- 0xffff0fc0의 32비트 비교 후 교환 helper입니다. r0는 기대값, r1은 새 값, r2는 변경할 주소입니다. 현재 값이 기대값과 같을 때만 메모리를 바꿉니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.
- L45
- 대상 주소 r2에서 현재 32비트 값을 r3에 exclusive load합니다. 뒤의 배타적 저장과 함께 동시 접근을 감지하는 읽기·수정·쓰기 시도를 만듭니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L46
- 현재 값에서 기대값 r0를 빼 두 값이 같은지 검사합니다. 같으면 r3=0과 Z=1이 되어 저장을 시도하고, 다르면 저장을 생략합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L47
- 비교가 같을 때만 새 값 r1을 r2 주소에 release 성격의 exclusive store로 기록합니다. r3는 성공 여부가 되어 0이면 성공, 1이면 재시도 대상입니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L48
- 저장을 시도한 경우 r3가 실패 코드 1인지 검사합니다. 값 자체의 불일치와 exclusive store 충돌을 구분합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L49
- 저장 충돌이면 다시 읽기부터 시작합니다. 다른 실행 주체가 값을 바꾸었을 수 있으므로 같은 기대값인지 재검사합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L50
- DMB ISH로 공유 메모리 접근 순서를 정리합니다. 비교·교환 결과만 반환하는 것에 더해 helper의 장벽 동작을 제공하는 줄입니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L51
- 0-r3를 반환값 r0에 넣으면서 carry 플래그를 갱신합니다. 성공은 0과 carry 설정, 실패는 0이 아닌 값과 carry 해제로 호출자에게 전달합니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L52
- 32비트 비교·교환 결과를 남긴 채 LR의 호출 지점으로 돌아갑니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
TLS와 버전 슬롯
MRC는 TPIDRURO의 값을 r0로 읽어 TLS 포인터를 제공합니다. 뒤의 .rep 5는 0 워드 다섯 개를 채웁니다. 마지막 버전 값은 전체 helper 길이를 32바이트 단위로 나눈 식이며, 현재 배치에서는 5입니다.
54 .align 5
55__kuser_get_tls: // 0xffff0fe0
56 .inst 0xee1d0f70 // mrc p15, 0, r0, c13, c0, 3
57 .inst 0xe12fff1e // bx lr
58 .rep 5
59 .word 0
60 .endr
61
62__kuser_helper_version: // 0xffff0ffc
63 .word ((__kuser_helper_end - __kuser_helper_start) >> 5)
64 .globl __kuser_helper_end
65__kuser_helper_end:
- L54
- TLS helper의 고정 주소를 맞추기 위해 다음 코드를 32바이트 경계에 배치합니다. AArch64의 .align 인자는 바이트 수가 아니라 2의 지수입니다. 빌드할 때 필요한 패딩을 넣어 그 경계를 맞춥니다.
- L55
- 0xffff0fe0의 TLS 조회 helper입니다. AArch32 프로그램이 현재 스레드의 thread pointer를 얻기 위해 사용하는 고정 주소 진입점입니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.
- L56
- AArch32의 TPIDRURO에 해당하는 CP15 스레드 식별 레지스터를 읽어 r0로 돌려줄 값을 준비합니다. ARM64 커널이 호환 태스크를 위해 유지하는 사용자 TLS 값입니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L57
- TLS 포인터를 r0에 둔 채 호출자에게 돌아갑니다. 여기서 .inst는 어셈블 단계에 숫자로 적힌 명령어 인코딩을 내보냅니다. 이 파일의 바이트열은 32비트 사용자 모드에서 AArch32 명령으로 실행됩니다.
- L58
- 뒤의 32비트 0을 다섯 번 배치하여 TLS helper 뒤의 고정 크기 공간을 채웁니다. CPU가 다섯 번 반복 실행하는 루프는 아닙니다.
- L59
- 이 .word는 어셈블 단계에서 4바이트의 0을 내보내는 데이터 지시문입니다. CPU가 0을 계산하거나 저장하는 실행 명령은 아닙니다. 패딩용 4바이트 0을 놓습니다. 위 .rep 5와 합쳐 총 20바이트를 차지하여 버전 필드가 0xffff0ffc 위치에 오게 합니다.
- L60
- 패딩용 0을 다섯 번 생성하는 범위를 마칩니다. 결과적으로 뒤의 버전 필드가 정해진 페이지 끝 위치에 놓입니다.
- L62
- 0xffff0ffc의 helper 버전 필드 위치입니다. 프로그램은 이 값을 읽어 제공되는 helper ABI 버전을 확인할 수 있습니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.
- L63
- 이 .word는 같은 섹션의 두 레이블 사이 크기를 어셈블 단계에서 계산하여 4바이트 정수로 기록합니다. 실행 중 CPU가 이 뺄셈과 시프트를 수행하는 줄은 아닙니다. helper 블록 크기를 32바이트 단위로 나눈 값을 버전 필드에 기록합니다. 이 배치에서는 블록이 0xa0바이트이므로 값은 5입니다. helper 함수 개수를 세는 표현으로 읽으면 안 됩니다.
- L64
- 복사할 helper 블록의 끝 주소를 다른 파일에서도 참조할 수 있게 합니다. 이 표기는 심벌을 다른 오브젝트에서도 참조할 수 있게 하며 CPU가 실행하는 명령을 추가하지 않습니다.
- L65
- helper 블록의 끝을 표시합니다. 시작 주소와의 차이로 복사 크기와 버전 필드 값을 결정합니다. 레이블은 이 위치에 붙인 이름이며, 이름을 적는 것만으로 CPU 명령이 추가되지는 않습니다.
매크로 정의와 확장
각 정의는 v6.18.37 원문입니다. 역슬래시 인자는 사용 위치의 값으로 치환되며, 안쪽 매크로와 조건부 블록은 이후 단계에서 다시 처리됩니다.
상수·구조체 오프셋
S_*·THREAD_*·FREGS_* 등의 구조체 오프셋은 빌드한 C 구조체 배치에서 생성됩니다. 숫자를 고정하지 않고 원래 기호를 사용했습니다.