실행 가능 목적파일을 실행하기 위해서, 쉘은 로더 loader라고 알려진 메모리 상주 운영체제 코드를 호출해서 이 프로그램을 실행한다. 모든 리눅스 프로그램은 execve 함수를 호출해서 로더를 호출할 수 있으며, 로더는 디스크로부터 실행 가능한 목적파일 내의 코드와 데이터를 메모리로 복사하고 이 프로그램의 첫 번째 인스트럭션, 즉 엔트리 포인트로 점프해서 프로그램을 실행한다. 이와 같은 프로그램을 메모리로 복사하고 실행하는 과정을 로딩이라고 부른다.
- CSAPP 7장 링커 참조
왼쪽 그림의 바이너리 데이터는 오른쪽그림의 아래부분에 채워진다. 이 과정을 로딩이라고 한다.
로딩은 pintos 코드에서 지원을 한다고 하지만, User stack 영역을 채워주는 것이 project 2 의 첫 번째 과제였다.
/bin/ls -l foo bar 을 space를 기준으로 자르면 '/bin/ls' , '-l', 'foo', 'bar' 4 개로 나눌 수 있다. 따라서 User stack에 아래 표와 같은 방법으로 채워주면 된다. 인자값 넣은 블록, rsp를 위한 8의 배수 align padding블록, 인자값과 인자포인터 구분 블록, 인자포인터, 가짜 리턴 주소 순서로 채워주면 된다. 가짜 리턴 주소를 사용하는 이유는 나중에 다른 리턴 값으로 덮어쓰기 위함과 해킹(?)의 위험이 있다고 한다.
test에 args-single 이라는 문제가 있으니 이것을 통해 argument passing 을 하려고 했다.
인자로 들어오는 값은 'args-single onearg' 이기 때문에 'args-single\0' 와 'onearg\0' 로 나누어야 했다. 위의 표대로 User stack 을 어떻게 배치해야 할까 그려봤다.
이렇게 코드를 작성해야 하는데, User stack 에 데이터를 추가할 때마다 rsp를 데이터의 크기만큼 내려줘야 한다. 따라서 코드에서는 rsp에 해당하는 블록에 데이터를 넣어주고 rsp를 데이터의 크기만큼 빼주는 작업을 같이 진행해야 한다. 예를 들어서 onearg\0는 7 바이트 이므로 rsp = rsp - 7 을 해주어야 한다.
코드구현
argument parsing 작업
// string.c에 인자를 자르는 함수은 strtok_r 이 있으므로
// 이것을 사용해서 인자를 parsing하면 된다.
// argc : 인자의 개수
// argv : 인자를 담아 둘 리스트
char *argv_list[64]; // pintos에서 command line의 길이에는 128바이트 제한이므로 인자를 담을 배열을 64로 지정
char *token, *save_ptr;
int agrc_num = 0;
token = strtok_r(file_name, " ", &save_ptr);
argv_list[agrc_num] = token; // arg_list[0] = file_name_first
while (token != NULL)
{
token = strtok_r(NULL, " ", &save_ptr);
agrc_num++;
argv_list[agrc_num] = token;
}
process_exec 함수에서 load() 후 User stack 배치하는 함수를 작성하여 추가하면 된다.
void setup_argument(char **argv_list, int argc_num, struct intr_frame *if_)
{
/*
1. parsing 된 인자를 argv_list로 받음
2. 리스트의 각 요소의 size를 계산하여 rsp를 1칸 씩 내리며 문자열 push
3. 스택 영역 8의 배수로 맞춰주기 padding
4. 문자열 종료 알려주기 위한 memset
5. 문자열의 주소 push
6. fake return addr
x = (x + 7) & ~7; 8의 배수
*/
// 프로그램 이름, 인자 문자열 push
for (int i = argc_num - 1; i > -1; i--)
{
for (int j = strlen(argv_list[i]); j > -1; j--)
{
if_->rsp -= 1;
*(char *)if_->rsp = argv_list[i][j];
}
argv_list[i] = (char *)if_->rsp;
}
// 스택 영역 정렬 패딩 push
int padding = (int)if_->rsp % 8;
if_->rsp -= padding;
memset(if_->rsp, '\0', padding);
// 인자 문자열 종료를 나타내는 0 push
if_->rsp -= 8;
memset(if_->rsp, '\0', 8);
// 각 인자 문자열의 주소 push
for (int i = argc_num - 1; i > -1; i--)
{
if_->rsp -= 8;
memcpy(if_->rsp, &argv_list[i], sizeof(char *));
}
// return address push
if_->rsp -= 8;
memset(if_->rsp, '\0', sizeof(void *));
if_->R.rdi = argc_num;
if_->R.rsi = if_->rsp + 8; // fake_address 바로 위: arg_address 맨 앞 가리키는 주소값!
}
argument parsing이 잘 되었는지 확인을 하기 위해서 아래와 같은 코드를 setup_argument() 가 끝난 후 확인할 수 있다.
hex_dump(if_->rsp, if_->rsp, USER_STACK - (uint64_t)if_->rsp, true); // user stack을 16진수로 프린트
// terminal에서 /userprog/build 경로에서 다음 코드를 실행시키기
pintos --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'
다음과 같은 결과를 얻었으면 argument passing이 되었다는 것을 알 수 있다. 프로젝트를 통과하기 위해서는 hex_dump를 주석처리 해야한다.
'PintOS' 카테고리의 다른 글
PintOS Project 3 : Virtual Memory (1) - supplemetal page table 보조 페이지 테이블 (0) | 2024.10.01 |
---|---|
PintOS Project 2 : system call (2) (1) | 2024.09.18 |
PintOS Project 2 : system call (1) (2) | 2024.09.18 |
PintOS Project 1 : Thread (Priority) (0) | 2024.09.01 |
PintOS Project 1 : Thread(Alarm Clock) (0) | 2024.08.27 |