gitbook project2 argument passing에서 x86-64 Calling Convention을 보자.
https://casys-kaist.github.io/pintos-kaist/project2/argument_passing.html
1 ~ 5번까지 한글자 한글자가 소중하게 읽어야한다.
일단, x86-64는 함수의 인자를 넘길 때 정수형 레지스터에 저장해서 넘긴다.
처음에 의문점이 있었다. 커맨드라인에 /bin/ls, -l, foo, bar을 치면,
rdi <- /bin/ls
rsi <- -l
rdx <- foo
rcs <- bar
이렇게 저장되야 하는 것인가? 답은 NO
Program Startup Detail에서 해당 의문에 대한 해답을 찾을 수 있다.
유저 프로그램이 실행되면, lib/user/entry.c 의 _start() 함수가 실행되며, 이것이 유저프로그램의 시작점이다.
따라서 우리가 해야할 일은, 커맨드 라인에서 입력한 우리의 argument 들 (/bin/ls -l foo bar)을 메인함수의 매개변수에 알맞게 넣어준다.
argc = 4 (커맨드라인에서 파일이름 제외하고 공백기준으로 입력된 스트링의 개수)
argv = ["/bin/ls\0", "-l\0", "foo\0", "bar\0"] + 센티넬
- 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을 넣어줘야됨.
밑에 테이블처럼 넣어주면 된다.
동작 순서는 다음과 같다. (내 코드 기준)
유저프로그램에서 exec 실행 -> lib/user/syscall.c -> ..
-> 이 부분이 명확하지 않다.
다만, exec은 *file 매개변수 하나만 받으니까 syscall1을 호출하고, rax에 시스템콜 넘버(SYZS_EXEC = 3)과 첫 번째 인자인 *file를 rdi 에 넣어주는 것으로 사려된다.
syscall_handler가 struct intr_frame *f를 넘겨 받는다.
-> 유저 스페이스에서 넘겨받은 것. (syscall-entry.S에서 만들어지는 것으로 사려됨), intr_frame *f는 exec을 호출할 때의 유저프로세스의 문맥을 저장한다고 생각했다.
현재 rdi 에는 커맨드라인에 입력한 그대로 들어가있다.
f->R.rdi : "/bin/ls -l foo bar"
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함수로 필요한 데이터를 넣어주는 방식으로 통일시켜서 구현하였다. 구체적으로 보면,
아래의 귀여운 명령어를 통해 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라서 저렇게 찍히고, 이런식으로 출력되는것이 정상적인 출력이다.