본문 바로가기

PINTOS 2주차 Argument Parsing

이번 주차에서는 Argument Parsing, User Memory Access(Check Address), System Call 과제를 수행했다.
이 중에서 이번 글에서는 Argument Parsing에 대해서만 먼저 정리하겠다.
 
Argument Parsing 
 
Argument Parsing 관련 테스트 코드를 실행시키면 process.c 파일에 있는 process_create_initd() 함수가 호출되는데, 이때 파라미터로 테스트용 명령어(프로그램 이름 + 인자들)이 들어오게 된다. 그러면 process_create_initd() 함수는 이 파라미터를 이용하여 thread_create()라는 함수를 호출하여 스레드를 생성한다. thread_create에는 파라미터로 initd라는 function가 전달되는데 그 함수의 인자로 쓸 전체 명령어 또한 같이 전달된다.

thread_create의 일부

 
thread_create 함수를 보면 스레드를 생성하면서 trap frame(interrupt frame)의 %rip(다음에 수행할 instruction의 주소를 담고 있는 레지스터)에 kernel_thread를 저장하고 있다. 해당 스레드가 다음에 수행할 동작은 커널 모드에서 수행될 것임을 알 수 있다. 그리고 trap_frame의 R(general register) 중에서 %rdi(첫 번째 인자를 저장하는 레지스터)에는 파라미터로 전달받은 function, 우리의 경우에 initd를 저장하고 있다. %rsi(두 번째 인자를 저장하는 레지스터)에는 initd라는 function의 인자로 사용할 aux, 우리의 경우에 전체 명령어를 저장하고 있다. 이제 thread_create를 통해 생성된 스레드는 커널 모드에서 수행될 것이고, initd라는 함수를 수행할 것이다.
 
process.c 파일에 있는 initd 함수는 process_exec() 함수를 호출하고 파라미터로 자신이 파라미터로 전달받은 전체 명령어를 process_exec()에게 그대로 전달한다.
 
process_exec() 함수에서는
1. 명령어에서 프로그램 이름과 각 인자들을 모두 토큰화해서 배열에 저장한다. 
2. 사용자 모드에서 사용할 interrupt frame에 ds, cs, eflags 정보를 설정한다.
3. process_cleanup() 함수를 통해 이전에 실행 중이던 스레드의 자원을 정리한다.
4. load() 함수를 호출하여 위에서 파싱한 프로그램 이름을 가진 프로그램을 메모리로 로드하고, 런타임 스택을 준비한다.
5. argument_stack() 함수를 호출하여 런타임 스택에 프로그램 이름, 인자들, 그리고 커널 모드에서의 작업이 끝나고 유저 모드의 작업으로 돌아갈 때 돌아갈 주소(return address)를 push한다.
6. 사용자 모드에서 사용할 interrupt frame의 rdi에 argc를, rsi에 런타임 스택에서 리턴 주소가 들어 있는 칸 바로 위를 가리키는 포인터를 저장해준다.
7. do_iret() 함수를 호출하여 커널 모드에서 유저 모드로 전환한다.
 
위 단계 중에서 5번째 단계, argument_stack() 부분을 구현하는 것이 주요 과제였다. 우리 조가 짠 코드는 다음과 같다.

void 
argument_stack(char **parse, int count, void **rsp) {
    char* argv_addr[30];  									// array to save addresses of argument strings
    int len;																// integer to save string legth of each argument string

    // 1. push program name
    for (int i = count-1; i >= 0; i--) {
        len = strlen(parse[i]) + 1;  				// get string length including NULL(\0)
        *rsp -= len;  							 				// lower stack pointer

        for (int j = 0; j < len; j++) {     // copy each character to stack (can replaced by memcpy)
            ((char *)(*rsp))[j] = parse[i][j];
        }

        argv_addr[i] = (uintptr_t)(*rsp);   // save the address of string to array
    }

    // 2. push padding for 8 bytes address alignment
		size_t padding_size = (uintptr_t)(*rsp) & 0x7;  
		*rsp -= padding_size;
		memset(*rsp, 0, padding_size);

    // 3. push NULL pointer which separates argument strings with addresses of them
    *rsp -= sizeof(uint8_t*);
    *(char **)(*rsp) = 0;

    // 4. push addresses of argument strings
    for (int i = count - 1; i >= 0; i--) {
        *rsp -= sizeof(char *);
        *(char **)(*rsp) = (char *)argv_addr[i];
    }
    // 5. push fake return address
    *rsp -= sizeof(void *);
    *(void **)(*rsp) = 0;
}

 
이 코드를 짜면서 스택에 데이터가 정확하게 쌓이지 않는 오류가 발생했다. 오류가 발생했던 주요 원인은 argument_stack에서 **rsp를 void ** 타입으로 받고 있어서 포인터 타입 캐스팅을 정확하게 해주어야 했는데 잘못된 포인터 타입으로 캐스팅해주는 부분이 많았기 때문이었다.

'정글' 카테고리의 다른 글