시스템콜

시스템콜이란?

  • 유저모드에서 커널모드로 진입하는 동작
  • 유저모드와 커널모드 사이의 가상 계층이자 인터페이스

커널공간

  • 커널 코드가 실행될 때는 커널 내부의 모든 함수 호출이 가능
  • 제약 없이 메모리 공간에 접근해서 하드웨어를 제어

유저공간

  • 유저 애플리케이션 코드가 구동하는 동작과 상태
  • 유저 애플리케이션은 유저공간에서 실행되며 메모리 공간접근에 제한이 있어 하드웨어에 직접 접근이 불가능
  • 접근시 커널은 오류를 감지해서 해당 프로세스를 종료시킴

시스템콜은 누가 언제 발생시키나?

  • 파일 시스템에 접근해서 파일을 읽거나 쓰고싶을때
  • PID 프로세스 정보를 얻으려 할때
  • 시스템 정보를 얻고싶을때

Armv8 기반 시스템콜

__libc_read() 함수

유저 공간에서 시스템 콜은 어떻게 발생시키나?
0000000000000000 <__libc_read>: // 내부 라이브러리
0: a9bd7bfd stp x29, x30, [sp, #-48]!
4: 90000003 adrp x3, 0 <__libc_multiple_threads>
8: 910003fd mov x29, sp
c: a90153f3 stp x19, x20, [sp, #16]
10: 93407c13 sxtw x19, w0
14: b9400060 ldr w0, [x3]
18: 35000160 cbnz w0, 44 <__libc_read+0x44>
1c: aa1303e0 mov x0, x19
20: d28007e8 mov x8, #0x3f // #63 // x0~x30 : 범용 레지스터
24: d4000001 svc #0x0 // svc ==> 시스템콜 발생 트리거

시스템 콜을 발생시키는 주인공은?

  • 유저 프로세스

시스템 콜을 유발하려면 어떤 Arm 어셈블리 명령어를 실행해야 할까요?

  • X8 레지스터에 시스템 콜 번호를 저장하고 'svc 0x0' 명령어를 실행

시스템 콜을 유발하는 어셈블리 명령어는 CPU 아키텍처마다 같나요?

  • CPU 아키텍처별로 각기 다른 명령어로 시스템 콜을 발생시킴

주요 레이블 함수

  • 익셉션 핸들러: VBAR_EL1 + 0x400
  • el0t_64_sync 레이블
  • el0t_64_sync_handler() 함수
  • el0_svc() 함수
  • do_el0_svc() 함수
  • el0_svc_common() 함수
  • invoke_syscall 레이블: 시스템 콜 핸들러 분기

시스템 콜 커널 코드 분석

EL0(유저 애플리케이션)에서 SVC 명령어를 실행

  • PSTATE.EL = EL1; // 익셉션레벨 스위칭
  • PC = VBAR_EL1 + 0x400; // 브랜치

익셉션 핸들러 분기

  • VBAR_EL1 + 0x400: 익셉션 핸들러
NSX:FFFFFFD174210800|vectors: sub sp,sp,#0x150 ; sp,sp,#336 // 익셉션 핸들러의 베이스주소 저장
NSX:FFFFFFD174210804| add sp,sp,x0
NSX:FFFFFFD174210808| sub x0,sp,x0
NSX:FFFFFFD17421080C| tbnz x0,#0x0E,0xFFFFFFD17421081C ; x0,#14,0xFFFFFFD17421081C
...
NSX:FFFFFFD174210C00| b 0xFFFFFFD174210C0C // + 0x400
NSX:FFFFFFD174210C04| mrs x30,#0x3,#0x3,c13,c0,#0x3 ; x30, TPIDRRO_EL0
NSX:FFFFFFD174210C08| msr #0x3,#0x3,c13,c0,#0x3,xzr ; TPIDRRO_EL0,xzr
NSX:FFFFFFD174210C0C| sub sp,sp,#0x150 ; sp,sp,#336
NSX:FFFFFFD174210C10| add sp,sp,x0
NSX:FFFFFFD174210C14| sub x0,sp,x0
NSX:FFFFFFD174210C18| tbnz x0,#0x0E,0xFFFFFFD174210C28 ; x0,#14,0xFFFFFFD174210C28
NSX:FFFFFFD174210C1C| sub x0,sp,x0
NSX:FFFFFFD174210C20| sub sp,sp,x0
NSX:FFFFFFD174210C24| b 0xFFFFFFD174211470 ; el0t_64_sync

el0t_64_sync 레이블 구현부

서브 루틴: el0t_64_sync 레이블
NSX:FFFFFFD174211470|el0t_64_sync: stp x0,x1,[sp]
NSX:FFFFFFD174211474| stp x2,x3,[sp,#0x10] ; x2,x3,[sp,#16]
NSX:FFFFFFD174211478| stp x4,x5,[sp,#0x20] ; x4,x5,[sp,#32]
NSX:FFFFFFD17421147C| stp x6,x7,[sp,#0x30] ; x6,x7,[sp,#48]
NSX:FFFFFFD174211480| stp x8,x9,[sp,#0x40] ; x8,x9,[sp,#64]
NSX:FFFFFFD174211484| stp x10,x11,[sp,#0x50] ; x10,x11,[sp,#80]
NSX:FFFFFFD174211488| stp x12,x13,[sp,#0x60] ; x12,x13,[sp,#96]
NSX:FFFFFFD17421148C| stp x14,x15,[sp,#0x70] ; x14,x15,[sp,#112]
NSX:FFFFFFD174211490| stp x16,x17,[sp,#0x80] ; x16,x17,[sp,#128]
...
NSX:FFFFFFD174211600| nop
NSX:FFFFFFD174211604| nop
NSX:FFFFFFD174211608| mov x0,sp
NSX:FFFFFFD17421160C| bl 0xFFFFFFD174D4D980 ; el0t_64_sync_handler
NSX:FFFFFFD174211610| b 0xFFFFFFD174212218 ; ret_to_user

el0t_64_sync_handler() 함수 구현부

익셉션 핸들러 서브 루틴
https://elixir.bootlin.com/linux/v5.15.30/source/arch/arm64/kernel/entry-common.c
asmlinkage void noinstr el0t_64_sync_handler(struct pt_regs *regs)
{
unsigned long esr = read_sysreg(esr_el1); // 익셉션 신드롬 레지스터(싱크로노스 익셉션 유발시 세부유발인자를 arm코어가 31번째~27번째 비트에 업데이트, 그 정보를 읽는다.
switch (ESR_ELx_EC(esr)) { 
case ESR_ELx_EC_SVC64:
el0_svc(regs);
break;
case ESR_ELx_EC_DABT_LOW:
el0_da(regs, esr);
break;
case ESR_ELx_EC_IABT_LOW:
el0_ia(regs, esr);
break;
case ESR_ELx_EC_FP_ASIMD:
el0_fpsimd_acc(regs, esr);
break;
...
default:
el0_inv(regs, esr);
}
}

el0_svc() 함수

시스템 콜 핸들러 관련 분기 전처리 루틴
https://elixir.bootlin.com/linux/v5.15.30/source/arch/arm64/kernel/entry-common.c
static void noinstr el0_svc(struct pt_regs *regs)
{
enter_from_user_mode(regs);
cortex_a76_erratum_1463225_svc_handler();
do_el0_svc(regs);
exit_to_user_mode(regs);
}

do_el0_svc() 함수

https://elixir.bootlin.com/linux/v5.15.30/source/arch/arm64/kernel/entry-common.c
void do_el0_svc(struct pt_regs *regs)
{
sve_user_discard();
el0_svc_common(regs, regs->regs[8], __NR_syscalls, sys_call_table); // reg8 : 유저공간에서 시스템콜을 유발할 때, svc명령어를 실행할때 x8레지스터의 시스템 콜번호 정보를 지정하고있다., NR : 맥스시스템콜 번호, 시스템콜 테이블을 담고있는 심볼 스타트 주소,, reg 레지스터, regs[8] : x1레지스터에 실려서 전달된다. 
}

el0_svc_common() 함수

시스템 콜 핸들러 관련 분기 처리 루틴
https://elixir.bootlin.com/linux/v5.15.30/source/arch/arm64/kernel/syscall.c
static void el0_svc_common(struct pt_regs *regs, int scno, int sc_nr,
const syscall_fn_t syscall_table[])
{
unsigned long flags = current_thread_info()->flags;
regs->orig_x0 = regs->regs[0];
regs->syscallno = scno;
...
invoke_syscall(regs, scno, sc_nr, syscall_table); // scno:시스템콜 번호, 

invoke_syscall 레이블

시스템 콜 핸들러 분기 루틴
NSX:FFFFFFD174227CC8|invoke_syscall: hint #0x19 ; #25
NSX:FFFFFFD174227CCC| stp x29,x30,[sp,#-0x30]! ; x29,x30,[sp,#-48]!
NSX:FFFFFFD174227CD0| mov x29,sp
...
NSX:FFFFFFD174227CE4| cmp w1,w2 ; scno,sc_nr
...
NSX:FFFFFFD174227D00| and w1,w1,w0 ; scno,scno,_mask
NSX:FFFFFFD174227D04| mov x0,x19 ; _mask,regs
NSX:FFFFFFD174227D08| ldr x1,[x3,x1,lsl #0x3] ; x1,[syscall_table,x1,lsl #3] // x1시스템콜번호 전달, x3 : 시스템콜 테이블의 베이스주소,ldr x1 :시스템콜 번호에따라 시스템콜 핸들러의 시작주소를 로딩해서 x1레지스터에 업데이트
NSX:FFFFFFD174227D0C| blr x1 //브랜치

sys_call_table의 정체

시스템 콜 테이블
$ d.v %y.ll sys_call_table
________________address|value_____________|symbol
NSD:FFFFFFD174D716F0|0xFFFFFFD17455F3C0 \\vmlinux\Global\__arm64_sys_io_setup
NSD:FFFFFFD174D716F8|0xFFFFFFD17455F5D0 \\vmlinux\Global\__arm64_sys_io_destroy
NSD:FFFFFFD174D71700|0xFFFFFFD1745606B4 \\vmlinux\Global\__arm64_sys_io_submit
NSD:FFFFFFD174D71708|0xFFFFFFD17455E900 \\vmlinux\Global\__arm64_sys_io_cancel
NSD:FFFFFFD174D71710|0xFFFFFFD17455EB74 \\vmlinux\Global\__arm64_sys_io_getevents
NSD:FFFFFFD174D71718|0xFFFFFFD17452B320 \\vmlinux\Global\__arm64_sys_setxattr
NSD:FFFFFFD174D71720|0xFFFFFFD17452B360 \\vmlinux\Global\__arm64_sys_lsetxattr
NSD:FFFFFFD174D71728|0xFFFFFFD17452B3A0 \\vmlinux\Global\__arm64_sys_fsetxattr
...
NSD:FFFFFFD174D718D8|0xFFFFFFD17450D1D4 \\vmlinux\Global\__arm64_sys_getdents64
NSD:FFFFFFD174D718E0|0xFFFFFFD1744F06B0 \\vmlinux\Global\__arm64_sys_lseek
NSD:FFFFFFD174D718E8|0xFFFFFFD1744F36E0 \\vmlinux\Global\__arm64_sys_read
NSD:FFFFFFD174D718F0|0xFFFFFFD1744F3810 \\vmlinux\Global\__arm64_sys_write
NSD:FFFFFFD174D718F8|0xFFFFFFD1744F19E0 \\vmlinux\Global\__arm64_sys_readv
NSD:FFFFFFD174D71900|0xFFFFFFD1744F2250 \\vmlinux\Global\__arm64_sys_writev
NSD:FFFFFFD174D71908|0xFFFFFFD1744F3904 \\vmlinux\Global\__arm64_sys_pread64
NSD:FFFFFFD174D71910|0xFFFFFFD1744F3A04 \\vmlinux\Global\__arm64_sys_pwrite64
NSD:FFFFFFD174D71918|0xFFFFFFD1744F1AF4 \\vmlinux\Global\__arm64_sys_preadv
NSD:FFFFFFD174D71920|0xFFFFFFD1744F2364 \\vmlinux\Global\__arm64_sys_pwritev
  • 오른쪽이 심볼 정보

시스템 콜 핸들러 구현부

시스템 콜 핸들러 분석
https://elixir.bootlin.com/linux/v5.15.30/source/fs/read_write.c
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, 
size_t, count) // 첫번째 : 시스템콜 이름, 이후는 전달되는 아규먼트, 전달되는 인자가 3개다
{
return ksys_write(fd, buf, count);
}
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
return ksys_read(fd, buf, count);
}

라즈베리 파이

시스템 콜 번호 확인하기
/usr/include/asm-generic/unistd.h
#define __NR_io_setup 0
__SC_COMP(__NR_io_setup, sys_io_setup, compat_sys_io_setup)
#define __NR_io_destroy 1
__SYSCALL(__NR_io_destroy, sys_io_destroy)
...
#define __NR_read 63
__SYSCALL(__NR_read, sys_read)
#define __NR_write 64
__SYSCALL(__NR_write, sys_write)
...
/* kernel/exit.c */
#define __NR_exit 93
__SYSCALL(__NR_exit, sys_exit)
#define __NR_exit_group 94
__SYSCALL(__NR_exit_group, sys_exit_group)
..
#define __NR_clone 220
__SYSCALL(__NR_clone, sys_clone)

63번 __NR_read 시스템 콜

시스템 콜 종류 별 stack trace 확인하기
rpi_proc_exit-1759 [002] ..... 7760.760695: sys_enter: NR 63 (3, 7fc8327bd7, 1, 7f8a301040, 7f8a308640, 0) 
rpi_proc_exit-1759 [002] ..... 7760.760699: vfs_read+0x4/0x2c0 <-ksys_read+0xe0/0xf8
rpi_proc_exit-1759 [002] ..... 7760.760701: <stack trace>
=> vfs_read+0x8/0x2c0
=> ksys_read+0xe0/0xf8
=> __arm64_sys_read+0x24/0x30
=> invoke_syscall+0x4c/0x110
=> el0_svc_common.constprop.3+0x98/0x120
=> do_el0_svc+0x34/0xd0
=> el0_svc+0x30/0x88
=> el0t_64_sync_handler+0x98/0xc0
=> el0t_64_sync+0x18c/0x190
rpi_proc_exit-1759 [002] ..... 7760.760708: sys_exit: NR 63 = 0

64번 __NR_write 시스템 콜

시스템 콜 종류 별 stack trace 확인하기
rpi_proc_exit-1759 [002] ..... 7760.763829: sys_enter: NR 64 (1, 55791742a0, d, 7f94a55c40, ffffffff, fbad2a84) // 시스템콜에 전달되는 아규먼트
rpi_proc_exit-1759 [002] ..... 7760.763832: vfs_write+0x4/0x430 <-ksys_write+0x70/0xf8
rpi_proc_exit-1759 [002] ..... 7760.763835: <stack trace>
=> vfs_write+0x8/0x430
=> ksys_write+0x70/0xf8
=> __arm64_sys_write+0x24/0x30
=> invoke_syscall+0x4c/0x110
=> el0_svc_common.constprop.3+0x98/0x120
=> do_el0_svc+0x34/0xd0
=> el0_svc+0x30/0x88
=> el0t_64_sync_handler+0x98/0xc0
=> el0t_64_sync+0x18c/0x190
rpi_proc_exit-1759 [002] ..... 7760.763879: sys_exit: NR 64 = 13 // 유저공간으로 복귀, 시스템콜 번호, 리턴하는 값

§ 220번 __NR_clone 시스템 콜

시스템 콜 종류 별 stack trace 확인하기
bash-1090 [001] ..... 7760.759706: sys_enter: NR 220 (1200011, 0, 0, 0, 7f8a301110, 0)
bash-1090 [001] ..... 7760.759710: copy_process+0x4/0x14f8 <-kernel_clone+0x98/0x3f8
bash-1090 [001] ..... 7760.759713: <stack trace>
=> copy_process+0x8/0x14f8
=> kernel_clone+0x98/0x3f8
=> __do_sys_clone+0x6c/0x98
=> __arm64_sys_clone+0x28/0x38
=> invoke_syscall+0x4c/0x110
=> el0_svc_common.constprop.3+0x98/0x120
=> do_el0_svc+0x34/0xd0
=> el0_svc+0x30/0x88
=> el0t_64_sync_handler+0x98/0xc0
=> el0t_64_sync+0x18c/0x190
bash-1090 [001] ..... 7760.760251: sys_exit: NR 220 = 1759 // 클론에서는 pid 리턴

0개의 댓글