argument passing
- 기존코드에서는 파일을 실행하면, 파일이름과 인자가 모두 파일이름에 문자열로 같이 들어온다.
- ex) gcc -o hello.out hello.c
를 실행하면 gcc -o hello.out hello.c
전체가 file_name
변수에 문자열로 잡힘.
- 따라서, 문자열을 공백을 separator로 하여 문자열을 parsing하고, ABI 규약에 맞게 인자를 유저 프로그램이 전달하는 작업이 필요함.
strtok_r
함수를 사용하여, 문자열 parsing을 보다 간편하게 할 수 있었다.
- parsing된 문자열은 유저스택에 쌓는다.
- interrupt frame을 세팅하고
do_iret
함수를 실행하여, 유저프로그램으로 분기한다.
system call
왜 system call을 사용하는가?
- Protection
- 시스템 콜이 없다면, 유저 프로그램이 하드웨어를 직접 조작하게 됨.
- 안정적인 시스템 운영이 아니다.
- 하나의 프로세스가 다른 프로세스의 입출력 작업을 하는 등 시스템에서 의도하지 않은 동작을 하는 것을 방지함.
- 제한적 직접 실행 원리(LDE)
- Privilege Escalation Attack
- 권한 상승 공격
- 공격자가 시스템 콜을 실행할 수 있는 권한을 얻음
- Kernel Exploit
시스템 콜의 종류
- 파일 시스템 접근
- 프로세스 생성 및 제거
- 다른 프로세스와의 통신
- 메모리 할당
- 기타 등등...
상세
- User 모드와 Kernel 모드를 분리함
- 시스템 콜은 Kernel 모드에서만 실행됨
- Trap 루틴을 통해 진입
- 대부분의 운영체제는 수백개의 시스템 콜을 제공한다.
- 유저 모드
- 접근할 수 있는 영역이 제한적
- 유저 어플리케이션이 실행되는 모드
- Intel Architecture 기준 Protection Ring 3
- 커널 모드
- 컴퓨터의 모든 자원에 접근할 수 있음
- 커널이 실행되는 모드
- Intel Architecture 기준 Protection Ring 0
예시
stdio
의 printf()
호출 User mode
glibc
의 printf()
함수에서 write()
wrapper 함수 호출 User mode
write()
구현부에서 트랩을 발생 User mode
- Architecture의 호출부 규약에 맞에 인자를 맞춘다
int 0x80
(asm: 0x80 인터럽트 발생)
- 커널에서 트랩 핸들러
system_call()
호출 Kernel mode
- IDT(Interrupt Descripter Table) 에서 트랩번호인
0x80
을 참조하여 system_call()
호출
system_call()
- EAX
의 값을 인덱스로 함(시스템 콜 번호)
- 시스템 콜 테이블에서 적절한 시스템콜 함수를 호출 (여기서는 sys_write()
)
- 실제
write()
함수인 sys_write()
호출 Kernel mode
- 시스템 콜은
sys_
로 시작함
EAX
에 반환할 값 저장
- 역으로 수행하여 결과 return
왜 시스템 콜은 rcx 대신 r10을 사용하는가?
- 일반적인 procedure call
- rdi, rsi, rdx, rcx, r8, r9
system call
- rdi, rsi, rdx, r10, r8, r9
일반적인 procedure call은 되돌아갈 rip
를 stack에 push 하지만,
시스템 콜은 트랩 루틴을 마친 후 되돌아갈 주소를 rcx
에 저장하기 때문.
rcx
← rip
rip
← IA32_LSTAR
IA32_LSTAR
는 MSR(Model-Specific Register)이며, 범용 레지스터가 아님
syscall
명령어는 rflags
를 r11
에 저장하기 때문에, r11
도 사용할 수 없음.
Task
- 리눅스 커널에서는 프로세스와 쓰레드를 구분하지 않는다.
- 태스크로 동등하게 관리함
- 쓰레드 그룹의 종속여부, 자원 할당 차이
- 프로세스와 쓰레드를 생성하는 모든 함수는
do_fork()
함수를 거침
do_fork()
함수의 결과로 task_struct
구조체가 생성됨
- 태스크가 생성되면, 커널 스택 과
task_struct
가 할당됨
- 자식 프로세스를 생성하는
fork()
함수
- 데이터, 코드, 힙, 스택이 새로 할당됨
- 쓰레드를 생성하는
pthread_create()
함수
- 스택을 제외한 대부분의 자원을 부모쓰레드와 공유함
- 커널 입장에서는 둘을 구분하지 않음
- 커널 스택과
task_struct
할당함