Adventure Time - Finn 3

새소식

운영체제

[Pintos-Kaist] Project2 - Argument Passing

  • -

과제 목표

  • Command line Parsing 기능 구현

 

과제 설명

  • Pintos는 프로그램과 인자를 구분하지 못하는 구조로 되어있습니다.
    • ex) /bin/ls -l foo bar 의 경우 null 포인터를 인식하지못하고 문자열 전체로 인식.
    • 명령어를 널포인터 기준으로 단어를 분할해서 /bin/ls, -l, foo, bar로 나눠서 스택에 푸시한다.
  • 스택에 저장된 인자를 응용 프로그램에 전달하는 기능을 구현합니다.

 

 

수정 부분

 

  1. process_create_initd() 함수에서 프로그램을 실행 할 프로세스를 생성합니다.
  2. process_exec() 함수에서 프로그램을 메모리에 탑재하고 응용 프로그램을 실행합니다.
  3. argument_stack() 함수에서 함수 호출 규약에 따라 유저 스택에 프로그램 이름과 인자들을 저장합니다.

 

 

 

💡 어셈블리어 레지스터 변수란?

 

레지스터

 

- 레지스터는 CPU가 요청을 처리하는데 필요한 데이터(명령어의 종류, 연산결과, 복귀주소 등)을 일시적으로 저장하는 기억장치이다.

 

 

✔️ 범용 레지스터

 

  • RAX(Accumulator) : 더하기, 빼기 등 산술/논리 연산을 수행하며 함수의 return값이 저장된다.
    • 시스템콜 함수를 사용하려면 RAX에 함수의 syscall 번호를 넣어준다.
  • RBX(Base) : 메모리 주소를 저장하기 위한 용도로 사용된다.
  • RCX (Count) : 반복문에서 카운터로 사용되는 레지스터. 고급언어 for문의 i 와 같은 역할이지만, 다만 ECX는 미리 반복 값을 정해두고 명령어를 사용할 때마다 값이 하나씩 줄어든다는 점이 다르다.
    • syscall을 호출했던 사용자프로그램의 return 주소를 가진다.
  • RDX (Data) : 다른 레지스터를 서포트하는 여분의 레지스터. 큰 수의 곱셈이나 나눗셈 연산에서 EAX와 함께 사용된다.

 

✔️ 인덱스 레지스터

 

  • RSI (Source Index) : 데이터를 복사할 때 src데이터, 즉 복사할 데이터의 주소가 저장된다.
  • RDI (Destination Index) : 데이터를 복사할 때 복사된 dest데이터의 주소가 저장된다.

 

 

✔️ 포인터 레지스터

 

  • RSP (Stack Point) : 스택프레임에서 스택의 끝 지점 주소(현재 스택 주소)가 저장된다.
    • 즉, 데이터가 계속 쌓일 때 스택의 가장 높은 곳을 가리킨다.
    • push, pop 명령을 통해 RSP 값이 위아래로 8바이트씩 이동하면서 스택프레임의 크기를 변경하게 된다.
  • RBP (Base Point) : 함수가 호출되면 스택프레임이 형성 되는데 이 스택스레임의 시작 지점 주소(스택 복귀 주소)가 저장된다.

 

 

 

 

구현

1️⃣  tid_t process_create_initd(const char *file_name)

 

  •  file_name 문자열 파싱하기
  • 인자로 들어오는 file_name을 널 포인터 기준으로 파싱해준다.

 

tid_t process_create_initd (const char *file_name) {
	char *fn_copy;
	tid_t tid;

	/* Make a copy of FILE_NAME.
	 * Otherwise there's a race between the caller and load(). */
	fn_copy = palloc_get_page (0);
	if (fn_copy == NULL)
		return TID_ERROR;
	strlcpy (fn_copy, file_name, PGSIZE);

	
	char *parsing;
	/*file_name을 받아와서 null 기준으로 문자열 파싱*/
	strtok_r(file_name," ", &parsing);

	/* Create a new thread to execute FILE_NAME. */
	tid = thread_create (file_name, PRI_DEFAULT, initd, fn_copy);
	if (tid == TID_ERROR)
		palloc_free_page (fn_copy);
	return tid;
}

 

 

2️⃣ int process_exec(void *f_name)

  • parsing한 이자를 담을 argu배열을 생성하고 배열의 길이는 pintos에서 command line 길이를 128바이트로 제한했기에 128바이트로 생성.
  • strtok_r 함수를 활용해서 반복문을 만들어서 argu배열에 token을 저장.
  • 스택에 마지막 추가한 fake address를 담기 직전의 주소가 argv의 시작 주소이므로, rsi에 현재 스택 포인터 rsp +8을 저장한다.       (아래 사진에서는 argc 무시, 4바이트 기준일때 주소값입니다.)

stack의 구성

  • 결과 확인을 위한 메모리를 16진수 형태로 출력해주는 hex_dump()함수활용.

 

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 ();

	/*parsing한 인자를 담을 argu배열의 길이는 pintos제한 128바이트*/
	char *argu[128];
	char *token, *parsing;
	int cnt = 0;
	/* strtok_r 함수를 통해서 첫 번째로 얻은 값을 token에 저장, 나머지를 parsing에 저장 */
	/* token이 NULL일때까지 반복 */
	/* strtok_r 함수에 NULL값을 받아온다면 이전 호출 이후의 남은 문자열에서 토큰을 찾음, 따라서 token에는 다음 문자 저장*/
	for(token=strtok_r(file_name," ",&parsing);token!=NULL; token=strtok_r(NULL," ",&parsing))
	{
		argu[cnt++]=token;
	}

	/* And then load the binary */
	success = load (file_name, &_if);

	
	argument_stack(argu,cnt,&_if.rsp);
	/*if 구조체의 필드값 갱신*/
	_if.R.rdi=cnt;
	_if.R.rsi=(char*)_if.rsp+8;
	/*_if.rsp를 시작 주소로하여 메모리 덤프를 생성. 메모리 덤프의 크기는 16진수로*/
	hex_dump(_if.rsp, _if.rsp, USER_STACK - (uint64_t)_if.rsp, true);

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

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

 

 

 

3️⃣ void argument_stack(char **parse ,int count ,void **esp)

  • 프로그램 이름, 인자 push
  • 주의해야할 점, stack의 bottom에 오른쪽 인자부터 쌓아 나간다. 즉 argu의 끝부터 rsp를 감소시키면서 stack에 저장한다.
  • stack을 word정렬해주기 위해 패딩 해준다. -> rsp의 값이 8의 배수가 될 때까지 계속해서 stack에 0 추가.
  • 문자열 종료를 나타내는 0 push
  • 문자열의 주소 push

 

 

void argument_stack(char **argu, int count, void **rsp)
{
    // 프로그램 이름, 인자 문자열 push
    for (int i = count - 1; i > -1; i--)
    {
        for (int j = strlen(argu[i]); j > -1; j--)
        {
            (*rsp)--;                      // 스택 주소 감소
            **(char **)rsp = argu[i][j]; // 주소에 문자 저장
        }
        argu[i] = *(char **)rsp; 
    }

    /* 정렬 패딩 push
	rsp의 값이 8의 배수가 될 때까지 스택에 0 넣어서 패딩맞춰주기*/
    int padding = (int)*rsp % 8;
    for (int i = 0; i < padding; i++)
    {
        (*rsp)--;
        **(uint8_t **)rsp = 0; // rsp 직전까지 값 채움
    }

    // 인자 문자열 종료를 나타내는 0 push
    (*rsp) -= 8;
    **(char ***)rsp = 0; // char* 타입의 0 추가

    // 각 인자 문자열의 주소 push
    for (int i = count - 1; i > -1; i--)
    {
        (*rsp) -= 8; // 다음 주소로 이동
        **(char ***)rsp = argu[i]; // char* 타입의 주소 추가
    }

    // return address push
    (*rsp) -= 8;
    **(void ***)rsp = 0; // void* 타입의 0 추가
}

 

 

 

테스트 결과

- userprog/build에서 아래의 명령어 입력

pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'

 

 

Argument-parsing 구현 전

 

 

Argument-parsing 구현 후

 

중간에 CG의 개수와 command_line이 출력된 부분을 확인할 수 있습니다.

위와 같은 출력물이 나오면 parsing부분은 테스트 성공입니다!

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.