x86-64 호출 규약 (Calling Convention)
이 부분은 리눅스 기반 64비트(x86-64) 환경에서 함수 호출 규약을 요약한 것. 간단하게 설명하기 위해 일부 세부 사항은 생략되었음. 더 자세한 내용은 System V AMD64 ABI 문서를 참고
호출 규약은 다음과 같이 동작 :
1.
사용자 수준 애플리케이션은 인자를 레지스터 순서대로 전달 :
%rdi, %rsi, %rdx, %rcx, %r8, %r9
2.
호출자(caller)는 자신의 다음 명령어 주소(리턴 주소)를 스택에 푸시한 뒤, 피호출자(callee)의 첫 번째 명령어로 점프 :
CALL 명령어가 이 두 동작을 동시에 수행
3.
피호출자(callee)가 실행됨
4.
만약 피호출자가 반환 값을 가지면, 그것은 RAX 레지스터에 저장
5.
피호출자는 스택에서 반환 주소를 꺼내서, RET 명령어로 해당 위치로 점프하며 복귀
예시 : f(1,2,3)
•
int 인자 3개를 받는 함수 f()가 있는 상황 가정
•
호출 시점: f(1, 2, 3)
이때 피호출자 입장에서의 레지스터와 스택 상태는 다음과 같음 :
스택 포인터 → 0x4747fe70 | return address |
+----------------+
RDI: 0x0000000000000001
RSI: 0x0000000000000002
RDX: 0x0000000000000003
C
복사
프로그램 시작 과정 (Program Startup Details)
Pintos의 사용자 프로그램 라이브러리(lib/user/entry.c)는 _start() 함수를 프로그램 진입점(entry point)으로 지정
_start()는 main()을 호출하고, 만약 main()이 반환되면 자동으로 exit()를 호출 :
void
_start (int argc, char *argv[]) {
exit (main (argc, argv));
}
C
복사
즉, 커널은 사용자 프로그램이 시작되기 전에 레지스터에 인자를 적절히 세팅 필요. 이 인자 전달 규칙은 일반적인 함수 호출 규약과 동일하게 적용
예시: /bin/ls -l foo bar
처리 과정
1.
명령어를 공백 기준으로 분리
•
/bin/ls, -l, foo, bar
2.
이 문자열들을 스택 최상단(top of stack)에 저장
•
문자열 순서는 상관없음(포인터를 통해 접근하므로)
3.
각 문자열의 주소를 스택에 푸시
•
추가로 NULL 포인터(sentinel)를 넣어서 argv[argc] == NULL이 되도록 함
•
포인터들은 오른쪽에서 왼쪽으로 차례로 푸시
•
이렇게 해야 argv[0]이 가장 낮은 주소에 위치
4.
성능 최적화를 위해 스택 포인터를 8바이트 단위(word align)로 맞춤
5.
레지스터 세팅 :
•
%rdi ← argc (인자의 개수)
•
%rsi ← argv (argv[0] 주소)
6.
마지막으로 가짜 "return address"를 푸시
•
프로그램이 리턴하지 않더라도 스택 프레임 형식은 일반 함수와 동일해야 하기 때문
최종 스택/레지스터 상태 예시
주소 | 이름 | 데이터 | 타입 |
0x4747fffc | argv[3] | "bar\0" | char[4] |
0x4747fff8 | argv[2] | "foo\0" | char[4] |
0x4747fff5 | argv[1] | "-l\0" | char[3] |
0x4747ffed | argv[0] | "/bin/ls\0" | char[8] |
0x4747ffe8 | word-align | padding | uint8_t[] |
0x4747ffe0 | argv[4] | 0 (NULL) | char * |
0x4747ffd8 | argv[3] | 0x4747fffc | char * |
0x4747ffd0 | argv[2] | 0x4747fff8 | char * |
0x4747ffc8 | argv[1] | 0x4747fff5 | char * |
0x4747ffc0 | argv[0] | 0x4747ffed | char * |
0x4747ffb8 | return addr | 0 | void (*)() |
레지스터 상태 :
•
RDI = 4 (argc = 4)
•
RSI = 0x4747ffc0 (argv[0]의 주소)
이 경우, 스택 포인터 초기값은 0x4747ffb8
구현 지침
•
코드에서는 USER_STACK(include/threads/vaddr.h에 정의됨)을 스택 시작 위치로 사용
•
hex_dump() 함수를 사용하면 스택 초기화 과정을 디버깅하기 좋음(<stdio.h>에 선언되어 있음)
과제 요구사항
현재 process_exec()은 프로그램 파일 이름만 인자로 받아 실행 → 이를 확장해서, 인자를 공백 기준으로 분리하고 프로그램에 전달하기
예시 : process_exec("grep foo bar") 실행
•
grep 실행
•
argv = {"grep", "foo", "bar", NULL}
조건 :
•
연속된 여러 개의 공백은 하나로 취급
•
전체 인자 길이는 한 페이지(4KB)로 제한 가능
•
pintos 유틸리티가 넘겨줄 수 있는 인자 길이 제한은 128바이트
팁 :
•
strtok_r() 사용 가능 (include/lib/string.h, 구현은 lib/string.c)
•
man strtok_r 참고 가능
정리
process_exec()을 수정해서 프로그램 이름 + 인자들을 파싱하고, 스택에 올린 후, argc/argv를 레지스터에 세팅해 사용자 프로그램을 실행하도록 만드는 게 이 과제의 목표

