리눅스 드라이버 및 리눅스 커널 코드가 실제 실행되는 공간은 어디일까요?
커널에서 어떤 코드던 프로세스가 실행되는 운동장은 스택이에요.
프로그램이 실행되면서 스택에 Push/Pop 동작을 엄청나게 자주하는데요.
예전 실리콘 벨리에서 어떤 스타트업 회사가 기획했던 아이디어라고 하는데요.
리눅스 커널 API를 하드웨어로 처리하는 아이디어를 구현하려고 했다고 하네요.
그 아이디어의 근거는 리눅스 커널에서 자주 사용하는 API는 몇 개로 한정되어 있다는 거에요.
그럼 너무 자주 실행되는 함수면 어떤 동작을 자주 할까요?
스택 Push/Pop이 겠죠. 그래서 inline을 함수를 쓰는 거에요. inline 함수는 컴파일러가 호출되는 함수 내에 inline 함수를
추가하기 때문에 스택 Push/Pop 동작을 줄 일 수 있어요. 대신 inline 함수의 심볼 정보는 찾을 수 없죠.
대부분의 Generic Linux Kernel API는 inline 함수 타입이랍니다.
그럼 함수가 호출될 때 파라미터와 지역 변수를 스택에 Push/Pop하는 동작(유식한 말로 Calling Convention이라고 하죠)은 다른 글에서 다루기로 하고요.
쉽게 Stack Push을 소개하려고 해요.
User 공간에서 Kernel 공간으로 이동하고 싶을 때, 시스템 콜을 호출해야 한다고 하죠.
(와이파이 콜과 시스템 콜을 혼돈해서는 안되요. 시스템 콜을 와이파이 콜로 이해하고 있는 돌대가리 관리자가 갑자기 생각나네요.)
아래 콜 스택은 바인더 "Binder:410_1:790" 프로세스가 ioctl 시스템 콜을 하는 동작이에요.
crash> bt 790
PID: 790 TASK: da175040 CPU: 0 COMMAND: "Binder:410_1"
bt: WARNING: stack address:0xda233da0, program counter:0xc0fd96a0
#0 [<c0fd96a0>] (__schedule) from [<c09d31d8>]
#1 [<c09d31d8>] (binder_thread_read) from [<c09d8460>]
#2 [<c09d8460>] (binder_ioctl) from [<c0247cc4>]
#3 [<c0247cc4>] (do_vfs_ioctl) from [<c0247dfc>]
#4 [<c0247dfc>] (sys_ioctl) from [<c0106b00>]
pc : [<b4551998>] lr : [<b452515f>] psr: 200d0010
sp : b382e888 ip : 00000000 fp : 00000000
r10: 00000008 r9 : b40b126c r8 : b40b12a0
r7 : 00000036 r6 : b40b1240 r5 : c0186201 r4 : b382e8a0
r3 : 00000000 r2 : b382e8a0 r1 : c0186201 r0 : 00000020
Flags: nzCv IRQs on FIQs on Mode USER_32 ISA ARM
오라 그런데, Crash Tool에서 래지스터를 찍어주네요? 이게 뭘까요?
crash> task 790
PID: 790 TASK: da175040 CPU: 0 COMMAND: "Binder:410_1"
struct task_struct {
state = 1,
stack = 0xda232000,
"Binder:410_1" 프로세스의 스택이 자라는 주소(0xda232000+0x2000)를 가보면, 그 비밀을 알 수 있어요.
참고로 ARM32 비트 아키텍쳐에서는 스택 사이즈가 0x2000, ARM64 비트는 0x4000으로 고정되어 있어요.
자, 0xda234000 주소로 가 볼까요?
crash> eval 0xda232000+0x2000
hexadecimal: da234000 (3573968KB)
crash> rd 0xda233f80 -e 0xda234000
da233f80: 00000000 c0247dfc 00000000 b382e8a0 .....}$.........
da233f90: c0186201 b40b1240 00000036 c0106cc4 .b..@...6....l..
da233fa0: da232000 c0106b00 b382e8a0 c0186201 . #..k.......b..
da233fb0: 00000020(R0) c0186201(R1) b382e8a0(R2) 00000000(R3) ....b..........
da233fc0: b382e8a0(R4) c0186201(R5) b40b1240(R6) 00000036(R7) .....b..@...6...
da233fd0: b40b12a0(R8) b40b126c(R9) 00000008(R10) 00000000(R11) ....l...........
da233fe0: 00000000(R12) b382e888(R13) b452515f(R14) b4551998(PC) ........_QR...U.
da233ff0: 200d0010 00000020 00000000 00000000 ... ...........
유저 공간에서 커널 공간으로 이동하기 직전에 실행되었던 레지스터를 커널 스택 공간에 Push를 하는 거죠?
Crash Tool로 스택 덤프를 보니 눈에 잘 안들어온다고요? 그럼 T32로 볼까요?
아래 명령어를 써 봅시다. 아래 T32 명령어는 메모리 공간의 덤프를 심볼 정보와 함께 표현해주죠. 참 유용한 명령어죠.
d.v %y.l 0xda233f80
위 Crash Tool 보다 좀 눈에 잘 들어오죠?
________address|_data________|value_____________|symbol
NSD:DA233F80| 00 00 00 00 0x0
NSD:DA233F84| FC 7D 24 C0 0xC0247DFC \\vmlinux\fs/ioctl\sys_ioctl+0x4C
NSD:DA233F88| 00 00 00 00 0x0
NSD:DA233F8C| A0 E8 82 B3 0xB382E8A0
NSD:DA233F90| 01 62 18 C0 0xC0186201 \\vmlinux\time/timer\perf_trace_hrtimer_init+0x75
NSD:DA233F94| 40 12 0B B4 0xB40B1240
NSD:DA233F98| 36 00 00 00 0x36
NSD:DA233F9C| C4 6C 10 C0 0xC0106CC4 \\vmlinux\Global\sys_call_table
NSD:DA233FA0| 00 20 23 DA 0xDA232000
NSD:DA233FA4| 00 6B 10 C0 0xC0106B00 \\vmlinux\Global\ret_fast_syscall
NSD:DA233FA8| A0 E8 82 B3 0xB382E8A0
NSD:DA233FAC| 01 62 18 C0 0xC0186201 \\vmlinux\time/timer\perf_trace_hrtimer_init+0x75
NSD:DA233FB0| 20 00 00 00 0x20 //<<-- R0
NSD:DA233FB4| 01 62 18 C0 0xC0186201 //<<-- R1
NSD:DA233FB8| A0 E8 82 B3 0xB382E8A0 //<<-- R2
NSD:DA233FBC| 00 00 00 00 0x0 //<<-- R3
NSD:DA233FC0| A0 E8 82 B3 0xB382E8A0 //<<-- R4
NSD:DA233FC4| 01 62 18 C0 0xC0186201 //<<-- R5
NSD:DA233FC8| 40 12 0B B4 0xB40B1240 //<<-- R6
NSD:DA233FCC| 36 00 00 00 0x36 //<<-- R7
NSD:DA233FD0| A0 12 0B B4 0xB40B12A0 //<<-- R8
NSD:DA233FD4| 6C 12 0B B4 0xB40B126C //<<-- R9
NSD:DA233FD8| 08 00 00 00 0x8 //<<-- R10
NSD:DA233FDC| 00 00 00 00 0x0 //<<-- R11
NSD:DA233FE0| 00 00 00 00 0x0 //<<-- R12
NSD:DA233FE4| 88 E8 82 B3 0xB382E888 //<<-- R13(SP)
NSD:DA233FE8| 5F 51 52 B4 0xB452515F //<<-- R14
NSD:DA233FEC| 98 19 55 B4 0xB4551998 //<<-- PC
NSD:DA233FF0| 10 00 0D 20 0x200D0010
NSD:DA233FF4| 20 00 00 00 0x20 \\vmlinux\Global\up_b_offset+0x18
NSD:DA233FF8| 00 00 00 00 0x0
NSD:DA233FFC| 00 00 00 00 0x0
NSD:DA234000| 00 00 00 00 0x0 //<<-- stack bottom 주소
Userspace 레지스터 중에서 주목해야 할 놈이 있어요. 바로, R7이죠.
R7는 시스템 콜 번호를 저장하고 있어요.
Crash Tool로 시스템 콜 번호를 확인할 수 있거든요. 아래 "sys -c" 명령어로 시스템 콜 번호와 시스템 콜 함수 위치를 함께 확인할 수 있어요.
crash> sys -c
NUM SYSTEM CALL FILE AND LINE NUMBER
0 sys_restart_syscall ../home001/AustinKim/kernel/signal.c: 2503
1 sys_exit
2 sys_fork ../home001/AustinKim/kernel/msm-3.18/kernel/fork.c: 1729
3 sys_read
4 sys_write
5 sys_open ../home001/AustinKim/fs/open.c: 1064
6 sys_close ../home001/AustinKim/fs/open.c: 1125
7 sys_ni_syscall
8 sys_creat ../home001/AustinKim/fs/open.c: 1089
9 sys_link
a sys_unlink
b sys_execve
// 생략
34 sys_umount
35 sys_ni_syscall
36 sys_ioctl
37 sys_fcntl
역시 sys_ioctl의 시스템 콜 번호는 0x36이네요. 물론 R7도 0x36이지만요.
Reference: ARM 프로세서의 주요 기능
ARM 프로세서는 왜 배워야 할까
ARM 프로세서 학습하는 방법의 문제점
ARM 프로세서 소개
ARM 아키텍처를 구성하는 주요 기능
● 어셈블리 명령어란
● ARM의 동작 모드와 익셉션 레벨
Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자
# Reference (ARM Processor)
'시스템 소프트웨어 개발을 위한 Arm 아키텍처의 구조와 원리' 카테고리의 다른 글
ARM64(Aarch64) - 함수 호출시 Stack Push(스택 푸쉬) 규약 (0) | 2023.06.09 |
---|---|
ARM64- 스택 푸쉬(Stack Push) Userspace -> Kernel Space (0) | 2023.06.09 |
ARM64(Aarch64) - Special Register 설정(Trace32) (0) | 2023.06.09 |
[Linux][ARM] char buf[32]; vs char buf[32]={0}; (0) | 2023.06.09 |
[T32] Cortex A53: 프로세스 별 Call Stack 복원하는 방법 (0) | 2023.06.09 |