이 파일은 왜 필요한가요?
시그널 핸들러가 끝난 뒤에는 핸들러를 호출하기 전의 프로그램 상태로 돌아가야 합니다. 일반적인 RET만으로는 중단된 레지스터와 마스크를 모두 복구할 수 없습니다. 이 작은 사용자 코드는 r7에 시스템 호출 번호를 넣고 SVC로 커널에 복구를 요청합니다.
명령·지시문·라벨을 원본 줄 번호에 맞춰 설명합니다. 주석과 빈 줄을 포함한 전체 원문은 아래에 따로 두었습니다. 매크로 정의와 조건부 코드는 모두 차례대로 실행되는 명령 목록이 아닙니다.
기존 코드 분석과 함께 읽기 · 원본 파일 · 설명 원고
먼저 알아둘 개념: 예외 레벨과 복귀, 실행 상태의 저장과 복원, 어셈블러 지시문, 호출 규약, 전처리·어셈블·링크·실행
처음 읽을 때
여기의 .byte는 레지스터 복원 명령이 아니라 사용자에게 제공할 명령 바이트를 만드는 줄입니다. 실제 복원은 요청을 받은 커널이 수행합니다.
더 깊이 살펴볼 때
ARM과 Thumb의 명령 폭, 리틀 엔디언 바이트 순서, EABI에서 r7이 맡는 역할을 함께 읽어 보세요. 원본 Thumb 주석은 바이트와 반대여서 실제 인코딩을 확인해야 합니다.
한 줄씩 레지스터와 메모리 상태 보기
각 코드 아래의 ‘행 그림’을 펼치면, 그 줄에서 읽는 값과 실행 전·후의 상태를 나란히 볼 수 있습니다. 여기서 ‘명령 직전’은 지금 보고 있는 명령이 실행되기 바로 전을 뜻합니다. 함수 진입 때나 소스의 바로 윗줄을 가리키는 말은 아닙니다. 앞서 실행한 명령이 값을 바꿨다면 그 결과가 들어 있습니다.
소스로 정해지는 상수, 호출자가 넘기는 값, 실제 실행에서 정해지는 주소를 구분해서 읽어야 합니다. 주소 숫자를 몰라도 무엇을 가리키는지와 어디서 받아 오는지는 설명할 수 있습니다. ‘명령 직전 x0의 값’처럼 일반 표기로 남은 곳은 한 명령의 동작을 나타내며, 특정 호출 경로의 값까지 확인했다는 뜻은 아닙니다.
일반 명령은 그 한 줄의 효과를, 매크로는 여러 명령을 묶은 주요 결과를 표시합니다. 함수 호출은 호출 직후의 PC와 LR까지만 나타냅니다. 메모리 접근은 정상적으로 끝나는 경우를 가정합니다. 생략된 항목까지 모두 보존된다는 뜻은 아닙니다.
화살표 →는 같은 항목의 전후 비교입니다. 메모리 칸에는 주소와 크기를 따로 적었습니다. SP가 줄어들면 낮은 주소 쪽을 사용하며, 화면의 위아래와 메모리 주소 방향은 별개입니다. 전처리·어셈블·링크 지시문은 CPU 실행과 구분합니다.
‘이전·다음 행’은 소스에 적힌 순서입니다. 분기·반복·호출이 있으므로 파일 위에서 아래로 실제 실행된다고 가정하지 않습니다. 값이 여러 줄에 걸쳐 이어지는 모습은 cpu-reset.S의 단계별 예제에서 보실 수 있습니다.
총 14개 줄에 그림을 붙였습니다. 기존 코드와 설명은 그 아래에서 이어집니다.
먼저 그림으로 살펴보기
EABI 번호와 코드 범위
#include <asm/unistd_compat_32.h>AArch32 호환 시스템 호출 번호를 가져옵니다. 뒤의 sigreturn·rt_sigreturn 기계어는 AArch64 x8 ABI가 아니라 AArch32 r7 번호를 사용하므로 호환 번호 정의가 필요합니다. #include는 전처리 단계에서 헤더 내용을 가져옵니다. CPU가 이 줄에서 함수를 호출하는 동작은 없습니다.
16행 그림 · 전처리 단계
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
.section .rodata이후 출력할 바이트를 어느 섹션에 넣을지 바꿉니다. 빌드 도구가 코드를 분류하는 작업이며 현재 CPU의 PC나 SP를 옮기는 줄은 아닙니다. .rodata
18행 그림 · 어셈블·링크 단계
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
.globl __aarch32_sigret_code_startAArch32 시그널 복귀 명령 모음의 시작 기호를 다른 커널 코드가 찾을 수 있게 내보냅니다. 여기 있는 바이트를 사용자 복귀 트램펄린에 배치할 때 시작과 끝 기호로 범위를 계산합니다. 이 표기는 심벌을 다른 오브젝트에서도 참조할 수 있게 하며 CPU가 실행하는 명령을 추가하지 않습니다.
19행 그림 · 어셈블·링크 단계
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
__aarch32_sigret_code_start:여기에 __aarch32_sigret_code_start라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.
20행 그림 · 위치에 이름 붙이기
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
sigreturn의 ARM·Thumb 바이트
.byte __NR_compat32_sigreturn, 0x70, 0xa0, 0xe3 // mov r7, #__NR_compat32_sigreturn이 .byte 자체는 CPU 명령이 아니라, 어셈블 단계에서 각 값을 1바이트씩 내보내는 지시문입니다. 이 파일에서는 그 바이트열이 명령어 인코딩이며, 시그널 복귀 시 CPU가 이를 AArch32 명령으로 실행합니다. 리틀 엔디언 ARM 명령 MOV r7,#sigreturn을 바이트로 만듭니다. r7은 AArch32 EABI 시스템 호출 번호 레지스터이며, 뒤의 SVC가 사용자 시그널 프레임에 보관한 문맥을 커널에서 검사·복원하도록 요청하게 합니다.
ARM MOV r7,#119의 바이트입니다. 119는 syscall_32.tbl의 sigreturn 번호입니다.
25행 그림 · 바이트에 담긴 AArch32 명령
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 이 .byte 자체가 CPU 명령은 아닙니다. 그림은 생성된 ARM 명령을 실행할 때의 효과입니다.
.byte __NR_compat32_sigreturn, 0x00, 0x00, 0xef // svc #__NR_compat32_sigreturn이 .byte 자체는 CPU 명령이 아니라, 어셈블 단계에서 각 값을 1바이트씩 내보내는 지시문입니다. 이 파일에서는 그 바이트열이 명령어 인코딩이며, 시그널 복귀 시 CPU가 이를 AArch32 명령으로 실행합니다. ARM 상태의 SVC를 바이트로 만듭니다. 직전 r7의 sigreturn 번호와 함께 커널에 들어가 시그널 이전 상태를 복원합니다. 일반 함수의 LR로 단순 RET하는 동작과 다릅니다.
ARM SVC 명령입니다. EABI는 r7의 시스템 호출 번호를 사용합니다.
26행 그림 · 바이트에 담긴 AArch32 명령
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 시스템 호출 번호는 r7에 준비되어 있습니다. 복귀 시 복원할 사용자 레지스터는 시그널 프레임의 내용에 따릅니다.
- 이 .byte 자체가 CPU 명령은 아닙니다. 그림은 생성된 ARM 명령을 실행할 때의 효과입니다.
.byte __NR_compat32_sigreturn, 0x27 // svc #__NR_compat32_sigreturn이 .byte 자체는 CPU 명령이 아니라, 어셈블 단계에서 각 값을 1바이트씩 내보내는 지시문입니다. 이 파일에서는 그 바이트열이 명령어 인코딩이며, 시그널 복귀 시 CPU가 이를 AArch32 명령으로 실행합니다. 0x27xx는 Thumb MOVS r7,#imm 인코딩이므로 sigreturn 번호를 r7에 넣습니다. 이 줄의 원문 주석 SVC는 다음 줄과 뒤바뀌어 있어 실제 바이트를 기준으로 해석해야 합니다.
실제 바이트는 0x2777, 즉 Thumb MOVS r7,#119입니다. 원문 svc 주석은 바이트와 반대입니다.
31행 그림 · 바이트에 담긴 AArch32 명령
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- Thumb의 0x27xx는 MOVS r7,#imm입니다. 이 원본의 Thumb MOV/SVC 주석 두 줄은 서로 바뀌어 있어 바이트 인코딩을 기준으로 표시합니다.
- 이 .byte 자체가 CPU 명령은 아닙니다. 그림은 생성된 Thumb 명령을 실행할 때의 효과입니다.
.byte __NR_compat32_sigreturn, 0xdf // mov r7, #__NR_compat32_sigreturn이 .byte 자체는 CPU 명령이 아니라, 어셈블 단계에서 각 값을 1바이트씩 내보내는 지시문입니다. 이 파일에서는 그 바이트열이 명령어 인코딩이며, 시그널 복귀 시 CPU가 이를 AArch32 명령으로 실행합니다. 0xdfxx는 Thumb SVC 인코딩입니다. 앞에서 설정한 r7의 sigreturn 번호로 커널 복귀 서비스를 요청합니다. 원문에 MOV라고 적혀 있어도 실제 명령은 SVC입니다.
실제 바이트는 0xdf77, 즉 Thumb SVC #119입니다. 원문 mov 주석은 바이트와 반대입니다.
32행 그림 · 바이트에 담긴 AArch32 명령
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 시스템 호출 번호는 r7에 준비되어 있습니다. 복귀 시 복원할 사용자 레지스터는 시그널 프레임의 내용에 따릅니다.
- 이 .byte 자체가 CPU 명령은 아닙니다. 그림은 생성된 Thumb 명령을 실행할 때의 효과입니다.
rt_sigreturn과 범위 끝
.byte __NR_compat32_rt_sigreturn, 0x70, 0xa0, 0xe3 // mov r7, #__NR_compat32_rt_sigreturn이 .byte 자체는 CPU 명령이 아니라, 어셈블 단계에서 각 값을 1바이트씩 내보내는 지시문입니다. 이 파일에서는 그 바이트열이 명령어 인코딩이며, 시그널 복귀 시 CPU가 이를 AArch32 명령으로 실행합니다. ARM 상태의 MOV r7,#rt_sigreturn을 만듭니다. rt_sigreturn은 확장된 시그널 프레임·마스크를 포함한 복귀 ABI를 사용하며 일반 sigreturn과 별도 호출 번호입니다.
ARM MOV r7,#173입니다. rt_sigreturn 번호는 173입니다.
37행 그림 · 바이트에 담긴 AArch32 명령
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 이 .byte 자체가 CPU 명령은 아닙니다. 그림은 생성된 ARM 명령을 실행할 때의 효과입니다.
.byte __NR_compat32_rt_sigreturn, 0x00, 0x00, 0xef // svc #__NR_compat32_rt_sigreturn이 .byte 자체는 CPU 명령이 아니라, 어셈블 단계에서 각 값을 1바이트씩 내보내는 지시문입니다. 이 파일에서는 그 바이트열이 명령어 인코딩이며, 시그널 복귀 시 CPU가 이를 AArch32 명령으로 실행합니다. ARM SVC로 rt_sigreturn을 요청합니다. 커널이 사용자 프레임의 레지스터·시그널 상태를 검사하고 복원하는 경로로 들어갑니다.
38행 그림 · 바이트에 담긴 AArch32 명령
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 시스템 호출 번호는 r7에 준비되어 있습니다. 복귀 시 복원할 사용자 레지스터는 시그널 프레임의 내용에 따릅니다.
- 이 .byte 자체가 CPU 명령은 아닙니다. 그림은 생성된 ARM 명령을 실행할 때의 효과입니다.
.byte __NR_compat32_rt_sigreturn, 0x27 // svc #__NR_compat32_rt_sigreturn이 .byte 자체는 CPU 명령이 아니라, 어셈블 단계에서 각 값을 1바이트씩 내보내는 지시문입니다. 이 파일에서는 그 바이트열이 명령어 인코딩이며, 시그널 복귀 시 CPU가 이를 AArch32 명령으로 실행합니다. Thumb MOVS r7,#rt_sigreturn의 바이트입니다. 0x27xx 인코딩과 호출 번호를 기준으로 해석하며 이 줄의 SVC 주석은 맞지 않습니다.
실제 바이트는 0x27ad, 즉 Thumb MOVS r7,#173입니다.
43행 그림 · 바이트에 담긴 AArch32 명령
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- Thumb의 0x27xx는 MOVS r7,#imm입니다. 이 원본의 Thumb MOV/SVC 주석 두 줄은 서로 바뀌어 있어 바이트 인코딩을 기준으로 표시합니다.
- 이 .byte 자체가 CPU 명령은 아닙니다. 그림은 생성된 Thumb 명령을 실행할 때의 효과입니다.
.byte __NR_compat32_rt_sigreturn, 0xdf // mov r7, #__NR_compat32_rt_sigreturn이 .byte 자체는 CPU 명령이 아니라, 어셈블 단계에서 각 값을 1바이트씩 내보내는 지시문입니다. 이 파일에서는 그 바이트열이 명령어 인코딩이며, 시그널 복귀 시 CPU가 이를 AArch32 명령으로 실행합니다. Thumb SVC의 0xdfxx 인코딩입니다. rt_sigreturn 복귀 서비스를 요청하고, 원래 응용 프로그램의 PC·SP 등은 커널 복원 경로에서 결정됩니다.
실제 바이트는 0xdfad, 즉 Thumb SVC #173입니다.
44행 그림 · 바이트에 담긴 AArch32 명령
→는 같은 항목의 실행 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
- 시스템 호출 번호는 r7에 준비되어 있습니다. 복귀 시 복원할 사용자 레지스터는 시그널 프레임의 내용에 따릅니다.
- 이 .byte 자체가 CPU 명령은 아닙니다. 그림은 생성된 Thumb 명령을 실행할 때의 효과입니다.
.globl __aarch32_sigret_code_end__aarch32_sigret_code_end를 다른 오브젝트에서도 찾을 수 있는 이름으로 공개합니다. 이름을 공개하는 것만으로 함수가 실행되지는 않습니다.
46행 그림 · 어셈블·링크 단계
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
__aarch32_sigret_code_end:여기에 __aarch32_sigret_code_end라는 위치 이름을 붙입니다. 다른 줄의 분기나 주소 계산이 이 위치를 찾아올 수 있습니다. 이름 자체가 CPU 작업을 추가하지는 않습니다.
47행 그림 · 위치에 이름 붙이기
→는 같은 항목의 처리 전과 후를 비교하는 표시입니다. 메모리 주소의 증가 방향이나 데이터 전송 경로를 뜻하지 않습니다.
설명한 뒤 함께 생각해 볼 질문
SVC 즉시값만 보면 syscall 번호를 알 수 있나요?
이 코드가 사용하는 EABI에서 커널은 r7의 시스템 호출 번호를 사용합니다.
Thumb의 0x27xx는 어떤 명령인가요?
MOVS r7,#imm8입니다. 0xdfxx는 SVC입니다. 이 파일의 해당 주석 순서는 뒤바뀌어 있습니다.
이 파일이 사용자 레지스터를 모두 복원하나요?
아닙니다. sigreturn 요청을 보내고 커널의 시그널 복귀 처리가 프레임을 해석해 복원합니다.
주석을 포함한 전체 원문
1/* SPDX-License-Identifier: GPL-2.0 */2/*3* AArch32 sigreturn code.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* For ARM syscalls, the syscall number has to be loaded into r7.10* We do not support an OABI userspace.11*12* For Thumb syscalls, we also pass the syscall number via r7. We therefore13* need two 16-bit instructions.14*/1516#include <asm/unistd_compat_32.h>1718.section .rodata19.globl __aarch32_sigret_code_start20__aarch32_sigret_code_start:2122/*23* ARM Code24*/25.byte __NR_compat32_sigreturn, 0x70, 0xa0, 0xe3 // mov r7, #__NR_compat32_sigreturn26.byte __NR_compat32_sigreturn, 0x00, 0x00, 0xef // svc #__NR_compat32_sigreturn2728/*29* Thumb code30*/31.byte __NR_compat32_sigreturn, 0x27 // svc #__NR_compat32_sigreturn32.byte __NR_compat32_sigreturn, 0xdf // mov r7, #__NR_compat32_sigreturn3334/*35* ARM code36*/37.byte __NR_compat32_rt_sigreturn, 0x70, 0xa0, 0xe3 // mov r7, #__NR_compat32_rt_sigreturn38.byte __NR_compat32_rt_sigreturn, 0x00, 0x00, 0xef // svc #__NR_compat32_rt_sigreturn3940/*41* Thumb code42*/43.byte __NR_compat32_rt_sigreturn, 0x27 // svc #__NR_compat32_rt_sigreturn44.byte __NR_compat32_rt_sigreturn, 0xdf // mov r7, #__NR_compat32_rt_sigreturn4546.globl __aarch32_sigret_code_end47__aarch32_sigret_code_end:

