Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

8200
629
98815


[리눅스커널] 시스템 콜: ret_fast_syscall 레이블 복귀 과정 알아보기 11장. 시스템 콜

시스템 콜 실행 완료 후 무슨 일을 할까?
시스템 콜을 수행하면 시스템 콜 핸들러를 통해 가상 파일시스템이나 커널 프로세스 함수를 실행합니다. 이후 시스템 콜 종류에 따라 시스템 콜 핸들러 하부 루틴을 수행하고 유저 공간 복귀합니다. 이번 절에서는 이 과정에서 실행하는 ret_fast_syscall 레이블 처리 과정에 대해 살펴보겠습니다. 

ret_fast_syscall 레이블 복귀 과정 알아보기 

이전 절까지 유저 공간에서 시스템 콜을 발생하면 어떤 과정으로 시스템 콜 핸들러를 호출하는지 살펴봤습니다.
  - 유저 공간: 시스템 콜 발생
  - 커널 공간: 시스템 콜 테이블 접근 후 시스템 콜 핸들러 함수 호출 

이렇게 시스템 콜 핸들러를 호출해 시스템 콜 종류별 세부 동작을 처리를 하면 다시 유저 공간으로 복귀합니다. 이 때 ret_fast_syscall 레이블을 실행하며 다음 동작을 수행합니다.
  - 시그널 받기
  - 선점 스케줄링 시작 
  - 유저 공간 복귀 

시스템 콜이 유저 공간에서 실행하는 과정만큼 시스템 콜 핸들러 서브 함수 실행을 마치고
유저 공간으로 복귀하기 전 ret_fast_syscall 레이블 동작은 중요합니다. 그 이유는 ret_fast_syscall 레이블에서 다음과 같은 동작을 수행하기 때문입니다. 

   "프로세스가 자신에게 전달된 시그널이 있는지 체크한다."
   "프로세스가 선점 스케줄링 될 조건인지 점검한다."

그런데 여기서 한 가지 의문이 생깁니다.

   "시스템 콜 핸들러 실행을 마무리한 후 왜 ret_fast_syscall 레이블로 복귀할까?" 

언제나 정답은 소스 코드에 있는 것 같습니다.
시스템 콜 테이블을 통해 시스템 콜 핸들러를 분기하기 시점 코드를 볼까요? 
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/arch/arm/kernel/entry-common.S]
1 badr lr, ret_fast_syscall @ return address
2 ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine

위에 있는 1번째 줄 코드를 실행하면 다음과 같이 처리합니다.

   " 복귀 레지스터(r14, lr)에 ret_fast_syscall 레이블을 지정한다."

시스템 콜 핸들러 함수하기 직전에 복귀할 함수 주소를 ret_fast_syscall 레이블로 지정하는 동작입니다. 다음 2번째 줄 코드를 실행하면 시스템 콜 핸들러 함수가 호출됩니다. 시스템 콜 핸들러 함수 코드 앞 부분에서 복귀 레지스터를 스택에 푸시합니다.

이해를 돕기 위해 write() 시스템 콜 핸들러인 sys_write() 함수 코드를 같이 볼까요?
01 NSR:80283D74|sys_write:   cpy     r12,r13
02 NSR:80283D78|             push    {r11-r12,r14,pc}
03 NSR:80283D88|             bl      0x80283CD4       ; ksys_write

위 코드는 sys_write() 함수를 어셈블리 코드로 본 것입니다. 02번째 줄 코드를 보면 {r11-r12,r14,pc} 레지스터 세트를 프로세스 스택 공간에 저장합니다. 여기서 r14 레지스터는 ret_fast_syscall 레이블 주소가 저장돼 있습니다.  
위에서 분석한 내용은 다음과 같이 정리할 수 있습니다.
  - 시스템 콜 핸들러를 호출하기 직전에 r14 복귀 레지스터에 ret_fast_syscall 레이블 주소를 저장한다.
  - 시스템 콜 핸들러는 r14 레지스터를 스택에 푸시한다.

그러면 이렇게 r14 레지스터를 ret_fast_syscall 레이블 주소로 저장하는 이유는 무엇일까요? 여기엔 그럴만한 이유가 있습니다.

   " 시스템 콜 핸들러가 실행을 마치면 스택에 저장된 r14 주소로 복귀한다."

그래서 시스템 콜 핸들러에서 시스템 콜 종류에 따라 시스템 콜 핸들러에서 실행을 마친 후 ret_fast_syscall 레이블로 복귀하는 것입니다. 

그러면 시스템 콜 핸들러 함수 실행 후 ret_fast_syscall 레이블이 콜스택에서 보여야 하지 않을까요? 이해를 돕기 위해 다음 ftrace 로그를 같이 볼까요?
1 chromium-browse-1200 [001] 952.125229: _raw_spin_lock+0x10/0x54 <-__schedule+0xc0/0xa50
2 chromium-browse-1200 [001] 952.125238: <stack trace>
3 => futex_wait_queue_me+0x10c/0x1a8
4 => futex_wait+0xf8/0x234
5 => do_futex+0x10c/0xc58
6 => SyS_futex+0xec/0x194
7 => ret_fast_syscall+0x0/0x28

위 ftrace에서 보이는 콜스택에서 __raw_spin_lock() 함수가 실행 중입니다. 그런데 이 함수 실행을 마무리하면 3번째 줄 함수에서 6번째 줄 함수 방향으로 되돌아 갑니다. 6번째 줄에 있는 시스템 콜 핸들러인 sys_futex() 함수가 실행을 끝낸 후 복귀하는 주소는 ret_fast_syscall 레이블인 것입니다.

이번 소절에서는 시스템 콜 핸들러를 처리한 다음 실행하는 ret_fast_syscall를 소개했습니다. 다음 소절에서는 ret_fast_syscall 레이블 전체 실행 흐름도를 알아보겠습니다.

ret_fast_syscall 레이블 전체 흐름도
이어서 ret_fast_syscall 레이블 전체 실행 흐름도를 살펴보겠습니다.
다음 그림은 ret_fast_syscall 레이블에서 시작해서 no_work_pending 레이블까지 동작 흐름도입니다. 

위 그림에서 보이듯 ret_fast_syscall 레이블의 핵심 동작은 시스템 콜 실행을 종료하고 유저 공간으로 복귀하는 것입니다. 

각 단계 별로 어떤 동작을 하는지 살펴봅시다.

1 단계: ret_fast_syscall 레이블 실행
프로세스 최상단 주소에 있는 struct thread_info 구조체 flag 필드가 _TIF_WORK_MASK 인지 점검합니다. 아닐 경우 다음 조건으로 처리합니다.
  1> TIF_SYSCALL_WORK 이면 ?
    ;__sys_trace_return_nosave 레이블 실행으로 ftrace sys_exit 이벤트 로그 실행 후 
     유저 공간 복귀
  2> TIF_SYSCALL_WORK 아니면 ?
     ; no_work_pending 레이블을 실행해서 유저 공간으로 복귀

2 단계: slow_work_pending 레이블 실행
 do_work_pending() 함수를 호출해서 시그널 및 스케줄링 처리를 한 후  no_work_pending 레이블을 호출합니다. 리눅스 커널 관점에서 눈여겨봐야 할 레이블 코드입니다.
  
3 단계: no_work_pending 레이블 실행
 restore_user_regs 매크로를 실행해 유저 공간으로 복귀합니다.

ret_fast_syscall 레이블 전체 흐름도를 살펴봤으니 다음 절에서는 각 레이블이 어떤 동작을 하는지 어셈블리 코드 분석으로 알아보겠습니다.

"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 

Thanks,
Austin Kim(austindh.kim@gmail.com)


#Reference: 시스템 콜
시스템 콜 주요 개념 소개
유저 공간에서 시스템 콜은 어떻게 발생할까
시스템 콜 핸들러는 어떤 동작을 할까? 
시스템 콜 실행 완료 후 무슨 일을 할까?
시스템 콜 관련 함수  
시스템 콜 디버깅  



핑백

덧글

댓글 입력 영역