프로세스 생성 분석 fork(콜 스택) - 1

2025. 9. 28. 23:10·Linux

fork

 

↓ call

 

__sys_ret(sys_fork())

 

↓ call

 

sys_fork()에서 __NR_clone일때, return my_syscall5(__NR_clone, SIGCHLD, 0, 0, 0, 0); 반환

(ARM64는 __NR_fork가 아니라, __NR_clone임)

 

my_syscall5는 이렇게 정의되어있음.

#define my_syscall5(num, arg1, arg2, arg3, arg4, arg5)                        \
({                                                                            \
         register long _num  __asm__ ("x8") = (num);                           \
         register long _arg1 __asm__ ("x0") = (long)(arg1);                    \
         register long _arg2 __asm__ ("x1") = (long)(arg2);                    \
         register long _arg3 __asm__ ("x2") = (long)(arg3);                    \
         register long _arg4 __asm__ ("x3") = (long)(arg4);                    \
         register long _arg5 __asm__ ("x4") = (long)(arg5);                    \
                                                                               \
         __asm__ volatile (                                                    \
                 "svc #0\n"                                                    \
                 : "=r" (_arg1)                                                \
                 : "r"(_arg1), "r"(_arg2), "r"(_arg3), "r"(_arg4), "r"(_arg5), \
                   "r"(_num)                                                   \
                 : "memory", "cc"                                              \
         );                                                                    \
         _arg1;                                                                \
})

시스템 콜 요청. 인자 num은 시스템콜 번호를 의미함.

__NR_clone은 220번이므로 x8레지스터에 220이 저장될 것으로 예상할 수 있다.

svc #0 실행 : 220번 시스템호출 실행 요청.

 

코드에서, __NR_clone은

include/uapi/asm-generic/unistd.h에서, 220번으로 시스템콜에 등록되어있음.

#define __NR_clone 220
__SYSCALL(__NR_clone, sys_clone);

 

sys_clone은 __NR_clone시스템콜 번호가 호출되면 실행 될 핸들러.

 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
                  int __user *, parent_tidptr,
                  unsigned long, tls,
                  int __user *, child_tidptr)
 #elif defined(CONFIG_CLONE_BACKWARDS2)
 SYSCALL_DEFINE5(clone, unsigned long, newsp, unsigned long, clone_flags,
                  int __user *, parent_tidptr,
                  int __user *, child_tidptr,
                  unsigned long, tls)
 #elif defined(CONFIG_CLONE_BACKWARDS3)
 SYSCALL_DEFINE6(clone, unsigned long, clone_flags, unsigned long, newsp,
                 int, stack_size,
                 int __user *, parent_tidptr,
                 int __user *, child_tidptr,
                 unsigned long, tls)
 #else
 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
                  int __user *, parent_tidptr,
                  int __user *, child_tidptr,
                  unsigned long, tls)
 #endif
 {
         struct kernel_clone_args args = {
                 .flags          = (lower_32_bits(clone_flags) & ~CSIGNAL),
                 .pidfd          = parent_tidptr,
                 .child_tid      = child_tidptr,
                 .parent_tid     = parent_tidptr,
                 .exit_signal    = (lower_32_bits(clone_flags) & CSIGNAL),
                 .stack          = newsp,
                 .tls            = tls,
         };
 
         return kernel_clone(&args);
 }

SYSCALL_DEFINE5 매크로에 의해 clone는 이름이 바뀌는데 마지막에 확인해보자.

결국 220번 시스템콜이 발생하면 kernel_clone호출하는데,

ftrace에 등록되어있다.

 

간단한 테스트 프로그램을 작성하고 의도적으로 fork를 발생시켜 보았다.

ftrace로 kernel_clone를 추적시켜 콜스택을 추적해보았다.

여기까지 분석 후 흐름을 대강 정리해보자.

fork는 결국 my_syscall5를 호출하고, svc명렁으로 시스템콜 호출을 요청한다.

invoke_syscall -> __arm64_sys_clone -> __do_sys_clone -> kernel_clone의 순서로 함수가 호출된다.

그러면 __arm64_sys_clone은 어디서 나오나?

 

나머지 invoke_syscall을 분석해보자.

  static long __invoke_syscall(struct pt_regs *regs, syscall_fn_t syscall_fn)
  {
          return syscall_fn(regs);
  }
  
  static void invoke_syscall(struct pt_regs *regs, unsigned int scno,
                             unsigned int sc_nr,
                             const syscall_fn_t syscall_table[])
  {
          long ret;
  
          add_random_kstack_offset();
  
          if (scno < sc_nr) {
                  syscall_fn_t syscall_fn;
                  syscall_fn = syscall_table[array_index_nospec(scno, sc_nr)];
                  ret = __invoke_syscall(regs, syscall_fn);
          } else {
                  ret = do_ni_syscall(regs, scno);
          }
  
          syscall_set_return_value(current, regs, 0, ret);
  
          /*
           * This value will get limited by KSTACK_OFFSET_MAX(), which is 10
           * bits. The actual entropy will be further reduced by the compiler
           * when applying stack alignment constraints: the AAPCS mandates a
           * 16-byte aligned SP at function boundaries, which will remove the
           * 4 low bits from any entropy chosen here.
           *
           * The resulting 6 bits of entropy is seen in SP[9:4].
           */
          choose_random_kstack_offset(get_random_u16());
  }

 

typedef long (*syscall_fn_t)(const struct pt_regs *regs);

참고로 syscall_fn_t는 포인터로 long 형 포인터로 정의되어있고,

syscall_fn과 syscall_table은 syscall_fn_t형 변수이다.

 

syscall_table은 do_el0_svc -> el0_svc_common에서 인자로 전달하는 sys_call_table이고, 핸들러 함수의 포인터가 담긴 포인터 배열이라고 예상한다.

즉, invoke_syscall에서 syscall_table에서 핸들러 함수의 포인터를 syscall_fn에 저장한다.

그리고 __invoke_syscall을 호출한다.

 

그래서, sys_call_table을 찾아보았다.

  const syscall_fn_t sys_call_table[__NR_syscalls] = {
          [0 ... __NR_syscalls - 1] = __arm64_sys_ni_syscall,
  #include <asm/syscall_table_64.h>
  };

예상했듯 sys_call_table은 핸들러 테이블임을 알 수 있고,

호출스택에서 보았던 __arm64_sys_clone이 여기서 나왔음을 볼 수 있다.

 

__arm64_sys_clone은 __do_sys_clone을 호출하는데,

__do_sys_clone은 매크로에 의해 생긴다.

 SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
                  int __user *, parent_tidptr,
                  unsigned long, tls,
                  int __user *, child_tidptr)

여기서 clone이 인자로 들어가고,

SYSCALL_DEFINE5는 __SYSCALL_DEFINEx의 alias이다.

__SYSCALL_DEFINEx 매크로를 찾아보자.

 

  #ifndef __SYSCALL_DEFINEx
  #define __SYSCALL_DEFINEx(x, name, ...)                                 \
          __diag_push();                                                  \
          __diag_ignore(GCC, 8, "-Wattribute-alias",                      \
                        "Type aliasing is used to sanitize syscall arguments");\
          asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))       \
                  __attribute__((alias(__stringify(__se_sys##name))));    \
          ALLOW_ERROR_INJECTION(sys##name, ERRNO);                        \
          static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
          asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
          asmlinkage long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__))  \
          {                                                               \
                  long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
                  __MAP(x,__SC_TEST,__VA_ARGS__);                         \
                  __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));       \
                  return ret;                                             \
          }                                                               \
          __diag_pop();                                                   \
          static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
  #endif /* __SYSCALL_DEFINEx */

완벽하게 해석하지는 못해도,

인자로 전달받은 clone을 __do_sys_clone으로 바꾼다는 것을 알 수 있다.

 

방금전의 SYSCALL_DEFINE5의 코드를 전체적으로 다시 봐보자.

 {
         struct kernel_clone_args args = {
                 .flags          = (lower_32_bits(clone_flags) & ~CSIGNAL),
                 .pidfd          = parent_tidptr,
                 .child_tid      = child_tidptr,
                 .parent_tid     = parent_tidptr,
                 .exit_signal    = (lower_32_bits(clone_flags) & CSIGNAL),
                 .stack          = newsp,
                 .tls            = tls,
         };
 
         return kernel_clone(&args);
 }

clone이 __do_sys_clone으로 바뀐다고 했으므로, clone은 __do_sys_clone과 동일하다.

그러면, __do_sys_clone에서는 kernel_clone을 호출한다.

 

이로써, ftrace로 kernel_clone을 찍어보고

모든 콜스택을 조사해보았다.

 

이제 kernel_clone_args같은 구조체들을 파보자.

'Linux' 카테고리의 다른 글

메모리 프로파일러 (시스템 콜 후킹)  (0) 2025.10.05
프로세스 생성 분석 fork (태스크 복사 및 초기화 부분) - 2  (0) 2025.09.30
커널에서 static inline 함수선언과 wrapping  (0) 2025.09.20
linux/drivers/char/mem.c 드라이버 분석  (0) 2025.09.20
objdump활용 vmlinux 분석시 start address의미  (0) 2025.09.11
'Linux' 카테고리의 다른 글
  • 메모리 프로파일러 (시스템 콜 후킹)
  • 프로세스 생성 분석 fork (태스크 복사 및 초기화 부분) - 2
  • 커널에서 static inline 함수선언과 wrapping
  • linux/drivers/char/mem.c 드라이버 분석
Jminu
Jminu
  • Jminu
    뇌 구조가 바이너리
    Jminu
  • 전체
    오늘
    어제
    • 분류 전체보기
      • C프로그래밍
        • 오류해결
        • 개인 공부
        • Programming Lab(학교수업)
        • MemoryTracker
      • C++
        • 개인 공부
      • 자료구조(Data Structure)
      • ARM arch
        • Cortex-M
        • FreeRTOS
      • 컴퓨터 공학(Computer Science)
        • OS
        • 컴퓨터 구조
      • Qualcomm 기업과제
      • Linux
      • Web
      • 똥글
      • 백준
      • Git 학습
        • 오류해결
        • 학습중
      • Python
        • 오류해결
        • 개인 공부
  • 블로그 메뉴

    • 태그
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    토발즈
    yolo
    앤드류모튼
    INIT
    리눅스
    commit
    피보나치
    Qualcomm
    버퍼
    파일 입출력
    Git
    동적메모리
    커널 기여
    c언어
    순환
    포인터
    소수
    rubikpi3
    커널
    Branch
    시스템콜
    C++
    백준
    스택
    파이썬
    자료구조
    rubik pi
    드라이버 분석
    arm
    이진 트리
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Jminu
프로세스 생성 분석 fork(콜 스택) - 1
상단으로

티스토리툴바