Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

15192
888
89788


[라즈베리파이] 시스템 콜 - 유저 공간 시스템 콜 발생 어셈블리 코드 분석 11장. 시스템 콜

아키텍처별로 시스템 콜을 실행하는 동작은 다릅니다. 시스템 콜을 실행하는 동작은 어셈블리 코드로 구현되어 있습니다.

라즈베리파이가 탑재한 ARM(ARM32) 아키텍처에서는 시스템 콜은 다음과 같이 구현돼 있습니다.
1. r0 ~ r5 레지스터에 시스템 콜로 전달할 인자 지정
2. r7 레지스터에 시스템 콜 번호 저장
3. "svc 0x00000000" 명령어 실행

GNU C 라이브러리 파일에서 실제 시스템 콜을 실행하는 어셈블리 코드를 봅시다.

write() 함수에 대한 시스템 콜을 실행하는 코드를 소개합니다.
00000020 <__libc_write>:
1  20: e59fc060  ldr ip, [pc, #96] ; 88 <__libc_write+0x68>
2  24: e79fc00c  ldr ip, [pc, ip]
3  28: e33c0000  teq ip, #0
4  2c: e52d7004  push {r7} ; (str r7, [sp, #-4]!)
5  30: 1a000005  bne 4c <__libc_write+0x2c>
6  34: e3a07004  mov r7, #4
7  38: ef000000  svc 0x00000000
6번째 줄 코드를 보면 r7 레지스터에 write() 함수에 대응하는 시스템 콜 번호인 4를 저장합니다.


라즈베리파이에서 다음 해더 파일에서 __NR_write 매크로를 보면 인덱스가 4임을 알 수 있습니다.
[/usr/include/arm-linux-gnueabihf/asm/unistd.h]
#define __NR_write (__NR_SYSCALL_BASE+  4)

이 인덱스가 시스템 콜 번호입니다.

이번에는 open() 함수를 호출하면 GNU C 라이브러리에서 시스템 콜을 실행하는 코드를 보겠습니다.
00000020 <__libc_open>:
1  20: e59fc060  ldr ip, [pc, #96] ; 88 <__libc_open+0x68>
2  24: e79fc00c  ldr ip, [pc, ip]
3  28: e33c0000  teq ip, #0
4  2c: e52d7004  push {r7} ; (str r7, [sp, #-4]!)
5  30: 1a000005  bne 4c <__libc_open+0x2c>
6  34: e3a07005  mov r7, #5
7  38: ef000000  svc 0x00000000

6번째 줄 코드를 보면 r7 레지스터에 5를 저장합니다. 이후 7번째 줄 코드에서 “svc 0x0” 명령어를 실행해서 Supervisor Call 즉 소프트웨어 인터럽트를 유발합니다.

라즈베리파이에서 다음 해더 파일에서 __NR_open 매크로를 확인하면 인덱스가 5임을 알 수 있습니다.
[/usr/include/arm-linux-gnueabihf/asm/unistd.h]
#define __NR_open (__NR_SYSCALL_BASE+  5)

r7 레지스터에 저장하는 5란 정수는 open() 함수에 대한 시스템 콜 번호입니다.

write(), open() 함수를 호출하면 시스템 콜이 실행되고 커널 공간에서 sys_write() 와 sys_open() 함수를 호출할 것이라 예상합니다.

대부분 리눅스 저수준 표준 함수 이름과 시스템 콜 핸들러 함수는 일치하는 경우가 많지만,
모든 함수가 이런 규칙으로 동작하는 것은 아닙니다.

exit() 함수를 생각해 봅시다. exit() 함수를 호출하면 우리는 당연히 sys_exit() 함수로 시스템 콜 번호를 지정할 것이라 예상할 수 있습니다.

exit() 함수를 호출하면 libc.a 라이브러리 파일 내 다음 경로에 있는 _exit 레이블을 실행합니다.
[glibc/sysdeps/unix/sysv/linux/_exit.c]
00000000 <_exit>:
1    0: e92d4080  push {r7, lr}
2    4: e1a02000  mov r2, r0
3    8: e3a070f8  mov r7, #248 ; 0xf8
4    c: ef000000  svc 0x00000000

3~4번째 줄 코드를 보겠습니다.
3    8: e3a070f8  mov r7, #248 ; 0xf8
4    c: ef000000  svc 0x00000000

r7 레지스터에 시스템 콜 번호인 248를 저장합니다.
라즈베리파이에서 다음 해더 파일을 보면 248에 대한 시스템 콜 함수는 매크로는 __NR_exit_group입니다.
[/usr/include/arm-linux-gnueabihf/asm/unistd.h]
#define __NR_exit_group (__NR_SYSCALL_BASE+248)

리눅스 커널 기준으로 sys_call_table 심볼에 있는 시스템 콜 데이블 248번에 해당하는 시스템 콜 핸들러 함수는 sys_exit_group() 입니다. 프로세스를 종료할 때 프로세스가 속한 스레드 그룹까지 종료하는 경우가 많아서 sys_exit_group() 함수를 호출하는 것입니다.

참고로, GNU C 라이브러리 코드는 다음 경로에서 확인할 수 있습니다.
[https://code.woboq.org/userspace/glibc/stdlib/exit.c.html]
1 void
2 _exit (int status)
3 {
4  while (1)
5    {
6 #ifdef __NR_exit_group
7      INLINE_SYSCALL (exit_group, 1, status);
8 #endif
9      INLINE_SYSCALL (exit, 1, status);
10 #ifdef ABORT_INSTRUCTION
11      ABORT_INSTRUCTION;
12 #endif
13    }
14 }

7번째 줄 코드를 실행하면 r7 레지스터에 __NR_exit_group 시스템 콜 번호인 248을 저장하고 소프트웨어 인터럽트를 유발합니다. 

코드를 분석하고 실행 흐름을 예측하는 것은 중요합니다. 그런데 개발 도중 만나는 여러 문제를 해결하기 위해서는 리눅스 시스템에서 실제 어떤 동작을 하는지 점검할 필요가 있습니다. 저도 exit() 함수를 호출하면 248번 시스템 콜을 실행한다는 사실은 라즈베리파이에서 GDB 디버깅을 통해 확인했습니다.


이번 시간에 유저 공간에서 시스템 콜을 발생하는 어셈블리 코드를 분석했습니다. 
다음 시간에 유저 공간에서 발생한 시스템 콜을 커널 공간에서 어떻게 처리하는지 살펴보겠습니다.

#Reference 시스템 콜


Reference(워크큐)
워크큐(Workqueue) Overview

.


핑백

덧글

댓글 입력 영역