Krafton_Jungle/pintOS

Project 2 User program - argument_stack

전낙타 2024. 5. 23. 13:02

구현 목표


  1. f_name을 통째로 file_name으로 바꿔주고 있다.
    • [[strtok_r]] 를 사용해 토큰별로 쪼개준다 (공백 여러개 있을 때도 처리해야 함)
  2. 그렇게 쪼개진 토큰을 list에 담아 intr_frameregister에 세팅해준다.
int
process_exec (void *f_name) {
    char *file_name = f_name;
    bool success;

    /* We cannot use the intr_frame in the thread structure.
     * This is because when current thread rescheduled,
     * it stores the execution information to the member. */
    struct intr_frame _if;
    _if.ds = _if.es = _if.ss = SEL_UDSEG;
    _if.cs = SEL_UCSEG;
    _if.eflags = FLAG_IF | FLAG_MBS;

    /* We first kill the current context */
    process_cleanup ();

    /* And then load the binary */
    // f_name을 그대로 load에 넣어줘 file_load에 실패하고 있다.
    success = load (file_name, &_if); 

    /* If load failed, quit. */
    palloc_free_page (file_name);
    if (!success)
        return -1;

    /* Start switched process. */
    do_iret (&_if);
    NOT_REACHED ();
}

수정해야 할 함수


process_exec

string token으로 분할

    char *token_arr[128];
    char *save_ptr, *token;
    int count = 0;

    for (token = strtok_r(file_name, " ", &save_ptr); 
         token != NULL; 
         token = strtok_r(NULL, " ", &save_ptr))
        token_arr[count++] = token;

    /* And then load the binary 
      * 2. 디스크에서 해당 바이너리 파일을 메모리로 로드한다 -> load() */
    success = load (file_name, &_if);
  1. token을 저장할 token_arr를 선언해준다. 배열의 크기는 깃북을 참고함
  2. buffer 역할을 해줄 save_ptrtoken을 선언, 인자의 개수를 저장할 count를 초기화해준다.
  3. 분할 후 token_arr에 담아주고, file_name으로 load

추가해야 할 함수


argument_stack

전체 코드

static void
argument_stack(char *parse[], int count, struct intr_frame *_if) {

    for (int i = count - 1; i >= 0; i--) {
        size_t len = strlen(parse[i]) + 1;
        _if->rsp -= len;
        memcpy(_if->rsp, parse[i], len);
        parse[i] = _if->rsp;
    }

    uint8_t padding = _if->rsp % 8;
    if (padding) {
        memset(_if->rsp -= (sizeof(uint8_t) * padding), 0, sizeof(uint8_t) * padding);
    }

    _if->rsp -= sizeof(uintptr_t);
    memset(_if->rsp, 0, sizeof(uintptr_t));

    for (int i = count - 1; i >= 0; i--) {
        _if->rsp -= sizeof(uintptr_t);
        _if->rsp = memcpy(_if->rsp, &parse[i], sizeof(uintptr_t));
    }

    _if->R.rsi = _if->rsp;
    _if->R.rdi = count;

    _if->rsp -= sizeof(uintptr_t);
    memset(_if->rsp, 0, sizeof(uintptr_t));
}

개요


args-single라는 file을 load하고, onearg인자를 전달하는 테스트를 진행할것이다.
우리가 원하는 동작은 위 그림과 같이 전달받은 인자, 파일의 이름을 [[stack]]에 적재하는 것인데, 이를 코드를 따라가며 한줄 한줄 정리해보자.

인수의 메모리 배치 & 8byte 정렬

for (int i = count - 1; i >= 0; i--) {
    size_t len = strlen(parse[i]) + 1;
    _if->rsp -= len;
    memcpy(_if->rsp, parse[i], len);
    parse[i] = _if->rsp;
}

uint8_t padding = _if->rsp % 8;
if (padding) {
    memset(_if->rsp -= (sizeof(uint8_t) * padding), 0, sizeof(uint8_t) * padding);
}
  1. rsp 주소를 token의 사이즈 + 1(null 종단문자)만큼 감소시키면서 stack에 데이터를 할당한다.
  2. 해당 token이 저장되어 있는 주소를 buffer로 사용될 parse에 저장시켜둔다.
  3. 이 과정이 종료되면 추가로 할당해줘야 할 padding의 크기를 구하고 할당해준다.

인자의 마지막을 알려주는 null pointer sentinel

_if->rsp -= sizeof(uintptr_t);
memset(_if->rsp, 0, sizeof(uintptr_t));

그 다음, 스택에 NULL 포인터를 추가합니다. 이는 인수 목록의 끝을 표시하기 위해 사용됩니다.

인수 포인터 배열

for (int i = count - 1; i >= 0; i--) {
    _if->rsp -= sizeof(uintptr_t);
    _if->rsp = memcpy(_if->rsp, &parse[i], sizeof(uintptr_t));
}

그 후, 각 인수의 주소를 스택에 저장합니다. 이를 통해 인수 포인터 배열을 구성합니다.

레지스터 설정

_if->R.rsi = _if->rsp;
_if->R.rdi = count;

마지막으로, 레지스터에 인수 포인터 배열의 시작 주소와 인수 개수를 설정합니다. 이는 프로그램 시작 시 argcargv를 설정하는 역할을 합니다.

_if->rsp -= sizeof(uintptr_t);
memset(_if->rsp, 0, sizeof(uintptr_t));

마지막으로, 스택에 NULL 포인터를 추가하여 함수가 종료됩니다.

요약


이 과정은 스택에 명령줄 인수들을 저장하고, 인수 배열을 구성하여, 프로그램이 실행될 때 인수들을 올바르게 접근하고 사용할 수 있게 합니다. 스택에 저장된 주소들을 통해 프로그램은 인수들을 올바르게 참조하고 처리할 수 있습니다. 이는 명령줄 인수 전달 규약에 따른 표준적인 처리 방식입니다.