Pintos Project 2 - Argument Passing

이후띵·2022년 1월 10일
2

PintOS

목록 보기
20/31

gitbook project2 argument passing에서 x86-64 Calling Convention을 보자.

https://casys-kaist.github.io/pintos-kaist/project2/argument_passing.html

1 ~ 5번까지 한글자 한글자가 소중하게 읽어야한다.
일단, x86-64는 함수의 인자를 넘길 때 정수형 레지스터에 저장해서 넘긴다.

  • 예시 f(1, 2, 3)

처음에 의문점이 있었다. 커맨드라인에 /bin/ls, -l, foo, bar을 치면,

rdi <- /bin/ls
rsi <- -l
rdx <- foo
rcs <- bar

이렇게 저장되야 하는 것인가? 답은 NO

Program Startup Detail에서 해당 의문에 대한 해답을 찾을 수 있다.
유저 프로그램이 실행되면, lib/user/entry.c 의 _start() 함수가 실행되며, 이것이 유저프로그램의 시작점이다.

  • 첫 번째 매개변수가 int argc, 두 번째가 char *argv[]이다.

따라서 우리가 해야할 일은, 커맨드 라인에서 입력한 우리의 argument 들 (/bin/ls -l foo bar)을 메인함수의 매개변수에 알맞게 넣어준다.

argc = 4 (커맨드라인에서 파일이름 제외하고 공백기준으로 입력된 스트링의 개수)
argv = ["/bin/ls\0", "-l\0", "foo\0", "bar\0"] + 센티넬

  1. Push the address of each string plus a null pointer sentinel, on the stack, in right-to-left order. These are the elements of argv. The null pointer sentinel ensures that argv[argc] is a null pointer, as required by the C standard. The order ensures that argv[0] is at the lowest virtual address. Word-aligned accesses are faster than unaligned accesses, so for best performance round the stack pointer down to a multiple of 8 before the first push.

주의할 것은, 각 스트링마다 1바이트씩 사이즈를 추가해줘야 한다. ("\0"을 통해 스트링이 끝났다는 것을 C standard가 알 수 있음.)
마찬가지 이유로 argv[argc] 위치에 sentinel을 넣어줘야됨.

밑에 테이블처럼 넣어주면 된다.

동작 순서는 다음과 같다. (내 코드 기준)

  1. 유저프로그램에서 exec 실행 -> lib/user/syscall.c -> ..
    -> 이 부분이 명확하지 않다.
    다만, exec은 *file 매개변수 하나만 받으니까 syscall1을 호출하고, rax에 시스템콜 넘버(SYZS_EXEC = 3)과 첫 번째 인자인 *file를 rdi 에 넣어주는 것으로 사려된다.

  2. syscall_handler가 struct intr_frame *f를 넘겨 받는다.
    -> 유저 스페이스에서 넘겨받은 것. (syscall-entry.S에서 만들어지는 것으로 사려됨), intr_frame *f는 exec을 호출할 때의 유저프로세스의 문맥을 저장한다고 생각했다.

현재 rdi 에는 커맨드라인에 입력한 그대로 들어가있다.
f->R.rdi : "/bin/ls -l foo bar"

  1. rax에 입력되어있는 값(시스템콜 넘버)으로 switch&case문을 활용하여 exec을 드감.
    (참고로, include/syscall-nr.h에 시스템콜 넘버가 enum으로 정의되어있음.)
        case SYS_EXEC:
            check_address(f->R.rdi);
            if (exec(f->R.rdi) == -1) exit(-1);
            break;

check_address는 rdi에 입력되어있는 주소가 valid 주소인지 (logical address) 즉, user memory에 제대로 access 하고 있는지 확인함수이며 아닐 시 exit(-1) 비정상 종료한다.

무튼 체크하고 exec을 한다.

int exec(const char *cmd_line){
    int size = strlen(cmd_line) + 1;
    char *fn_copy = palloc_get_page(PAL_ZERO);

    if (!fn_copy) exit(-1);

    strlcpy(fn_copy, cmd_line, size);

	if (process_exec(fn_copy) == -1) return -1;

	NOT_REACHED();
	return 0;
}

argument passing이 주제인걸 깜빡했다.
바로 process_exec() 함수를 보겠다.

어쨌든, 위의 과정을 거쳐서 힘들게 process_exec()함수가 실행되는데, 아직 filename을 따로 파싱을 하지 않았고, argument또한 passing 되지 않았다.

현재 rdi 에는 커맨드라인에 입력한 그대로 들어가있다.
f->R.rdi : "/bin/ls -l foo bar"

process_exec()과 load함수를 통해, 현재 rdi에 저장되어있는 애들을 parsing 해서 밑에 표에있는 것처럼 넣어줘야한다.

보자.. 어디서부터 설명해야할지 갑갑하다.
일단, argc, *argv[64]를 선언해주었고 첫 strtok_r를 통해 file_name을 parsing 하였다. (load함수에 들어가는 file_name = /bin/ls)
while문을 통해서

argc = 4
argv = ["/bin/ls","-l","foo","bar"]

로 만들어주고,
argument_stack_for_user 함수에서 위에 테이블처럼 넣는 과정을 구현하였다.

argument_stack_for_user 함수를 보면, 일단 스택이 위에서 아래로 쌓이기 넣을 사이즈만큼 포인터 이동시키고 memcpy나 memset함수로 필요한 데이터를 넣어주는 방식으로 통일시켜서 구현하였다. 구체적으로 보면,

287~292라인

  • 287 : argc가 string 갯수 이므로, 1빼줘야함(나중에 sentinel(for 스트링 "배열") 넣을꺼임 cf) argv[argc] = 빈값))
  • 288 : 스트링이 끝나는것을 식별하기위한 C standard ... size + 1해줌.(sentinel(for 스트링))
  • 289 : 사이즈만큼 빼줌
  • 290 : 뒤에서부터 정보 넣어줌 (맨처음에 bar\0 들어감)
  • 291 : 306에서 넣어줄 string의 주소를 기억해둠. (argv[i]를 넣어준 순간 argv[i]는 쓸모없으므로 밑에서 사용할 정보를 기억하기위해 재활용)

296~299라인

  • 8의 배수로 맞춰주기 위해 padding을 넣어줘야한다.
  • 스택이 위에서 밑으로 쌓이기 때문에, padding은 8 - (if_->rsp % 8)이 아니고 if_->rsp % 8 이 맞다.

302~303라인

  • argv[argc]는 비어있어야하고, 이유는 배열이 끝나는 곳이 어딘지 파악하기위한 C standard.
  • 8바이트만큼 0으로 채워줌

304~마지막 라인

  • 스트링이 저장되어있는 주소를 넣어주고 페이크리턴을 넣어줌.
  • rdi에 argc값, rsi에 argv 시작점의 주소를 넣어준다.

아래의 귀여운 명령어를 통해 hex_dump 테스트를 해보았다.
cd pintos-kaist-team02; source ./activate; cd userprog; make clean; make; cd build; pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'

hex_dump를 활용해서 찍어보니,

GG는 16진수로 47이라는 숫자가 아스키코드로 G라서 저렇게 찍히고, 이런식으로 출력되는것이 정상적인 출력이다.

profile
이후띵's 개발일지

0개의 댓글