Requirement
- thread.h에 #define USERPROG 선언
process_exec() 함수 수정
/* Switch the current execution context to the f_name.
* Returns -1 on fail. */
int
process_exec (void *f_name) {
/* ... */
/* for project 2
* todo: implementation argument passing */
int argc = 0;
char *argv[64];
char *ret_ptr, *next_ptr;
ret_ptr = strtok_r(file_name, " ", &next_ptr);
while(ret_ptr) {
argv[argc++] = ret_ptr;
ret_ptr = strtok_r(NULL, " ", &next_ptr);
}
/* And then load the binary */
success = load (file_name, &_if);
/* for project 2 */
argument_stack(argv, argc, &_if);
hex_dump(_if.rsp, _if.rsp, USER_STACK - _if.rsp, true);
/* ... */
}
전달받은 file_name에서 argument parsing을 수행한다. strtok_r() 함수를 사용해서 공백으로 분리된 토큰을 파싱한다.
# strtok_r() 함수에 대해
strtok_r() 함수는 문자열을 지정된 구분자(delimiter)를 기준으로 토큰(token)으로 분리하는 역할을 한다. 주어진 문자열을 순회하면서 구분자를 기준으로 토큰을 추출하고, 추출된 토큰을 변환한다. strtok_r() 함수는 다음과 같은 형식을 가지고 있다.
char *strtok_r(char *str, const char *delim, char **saveptr);
file_name 문자열을 공백을 구분자로 사용하여 토큰으로 분리한다. 첫 번째 호출 시에는 file_name 문자열을 전달하고, 이후 호출에서는 NULL을 전달하여 이전에 분리되지 않은 나머지 부분을 계속해서 분리한다. strtok_r() 함수는 호출할 때마다 구분자로부터 첫 번째로 발견된 토큰을 반환하고, 내부적으로 이전 상태를 유지하여 다음 호출 시에는 그 다음 토큰을 반환한다. 따라서 반복문을 사용하여 문자열을 모두 분리할 수 있다.
argument_stack() 함수 구현
/* argument parsing for project 2*/
void
argument_stack(char **argv, int argc, struct intr_frame *if_) {
/* argument stack downwards using rsp stack pointer */
for (int i = argc-1; i >= 0; i--) {
int N = strlen(argv[i]) + 1;
if_->rsp -= N;
memcpy(if_->rsp, argv[i], N);
argv[i] = (char *)if_->rsp;
}
/* word-alignment*/
if (if_->rsp%8) {
int padding = if_->rsp%8;
if_->rsp -= padding;
memset(if_->rsp, 0, padding);
}
/* for null sentinel */
if_->rsp -= 8;
memset(if_->rsp, 0, 8);
for (int i = argc-1; i >= 0; i--) {
if_->rsp -= 8;
memcpy(if_->rsp, &argv[i], 8);
}
/* fake return address */
if_->rsp -= 8;
memset(if_->rsp, 0, 8);
if_->R.rdi = argc;
if_->R.rsi = if_->rsp + 8;
}
if_->rsp: struct intr_frame 구조체의 rsp 멤버 변수는 스택 포인터를 나타낸다.
if_->R: struct intr_frame 구조체의 R 멤버 변수는 레지스터의 값을 나타낸다.
if_->R.rdi: R.rdi는 함수 호출 시 첫 번째 인수를 전달하는 레지스터이다.
if_->R.rsi: R.rsi는 함수 호출 시 두 번째 인수를 전달하는 레지스터이다.
argument_stack() 함수는 전달받은 argument를 반복문으로 스택 포인터에 역순으로 저장하는 역할을 수행한다. 먼저 argv의 요소를 하나씩 역순으로 스택에 저장한다. 이 때, 문자열의 마지막을 식별하기 위한 sentinel('\0')을 고려한다. 해당 크기만큼 스택(if_->rsp)를 줄이고, argv의 요소의 값을 스택에 저장한다. argv를 스택을 참조하도록 바꿔준다.
그 다음, 스택을 word_alignment(워드 정렬)한다. 스택 포인터(if_->rsp)가 8의 배수가 아닌 경우, padding을 추가하여 워드 정렬을 수행한다. memset 함수를 사용하여 패딩 영역을 0으로 초기화한다. 워드 정렬은 데이터가 메모리에 저장될 때 효율성과 성능을 향상시키는 데 도움을 준다.
그 다음, argv 배열의 끝을 표시하기 위해 null pointer sentinel을 추가한다. argv 배열을 일반적으로 널 포인터(NULL)로 끝나야 한다. 널 센티널이 없다면, 인수 배열의 크기를 명시적으로 전달해야 하거나 다른 방식으로 배열의 끝을 알려주어야 한다. 따라서, 널 센티널을 추가하는 것은 인수 배열의 끝을 명확하게 표시하여 인수 처리의 편의성과 안정성을 높이는데 도움을 준다.
그 다음, 반복문을 통해 인수 포인터들을 스택에 역순으로 저장한다.
그 다음, 함수 호출을 위한 fake return address를 추가한다. 이를 추가하는 이유는 함수 호출의 흐름을 조작하고, 호출된 함수가 정상적으로 반환되었다고 속이는 데 있다. 이는 프로그램의 실행 흐름을 제어하거나 보안상의 목적을 달성하는 데 사용될 수 있다.
마지막으로 레지스터(if_->R)를 설정하여 함수 호출 시 인수를 전달한다. R.rdi는 함수 호출 시 첫 번째 인수를 전달하는 레지스터이다. if_->R.rdi에 argc 값을 할당함으로써 argc 값을 함수의 첫 번째 인수로 설정한다. R.rsi는 함수 호출 시 두 번째 인수를 전달하는 레지스터이다. if_->R.rsi에 if_->rsp+8의 값을 할당함으로써, 두 번째 인수로서 스택 포인터 다음에 위치한 주소(&argv[0])를 설정한다.
# fake return address를 추가하는 이유
Finally, push a fake "return address": although the entry function will never return, its stack frame must have the same structure as any other.
호출된 함수가 절대로 return 되지 않을 것이라도, 스택 프레임이 어떤 경우에도 일반적인 구조를 가지도록 하기 위함이다. 이러한 접근을 사용하는 이유는 코드의 일관성과 호환성을 유지하기 위함이다. 프로그램에서 함수를 호출할 때마다 스택에 새로운 프레임이 생성되고, 호출된 함수의 local variable와 return address 등이 해당 스택 프레임에 저장된다. 이러한 구조는 함수 호출 및 반환의 일관성을 유지하기 위해 필요하다.
실제로, return address는 호출된 함수가 종료된 후에 호출자로 돌아갈 때 사용된다. 그러나 몇몇 함수는 return되지 않을 수 있다. 예를 들어 프로그램의 진입점(main 함수)이나 운영체제의 특정 기능을 수행하는 함수 등이 될 수 있다. 이러한 함수들은 return되지 않을 수 있지만, 함수의 스택 프레임은 여전히 일반적인 구조를 가지고 있어야 한다.
따라서, fake return address를 추가함으로써 호출된 함수의 스택 프레임이 일반적인 구조를 가지도록 할 수 있다.
'프로젝트 > Pint OS' 카테고리의 다른 글
[Pint OS] System Calls (2) (0) | 2023.06.11 |
---|---|
[Pint OS] 에러: missing "begin" message (0) | 2023.06.09 |
[Pint OS] System Calls (1) (0) | 2023.06.07 |
[Pint OS] User Memory Access (0) | 2023.06.07 |
[Pint OS] 에러: Kernel panic... thread_yield() (0) | 2023.06.05 |