
그동안 TIL 이 뜸했죠...?
PintOS에 딥다이브 하느라 잠자기 바빴습니다...
process_exec() 에 Argument Passing 구현
%rdi, %rsi, %rdx, %rcx, %r8, %r9 레지스터를 순서대로 사용 +----------------+
스택 포인터 --> 0x4747fe70 | 반환 주소 |
+----------------+
RDI: 0x0000000000000001 | RSI: 0x0000000000000002 | RDX: 0x0000000000000003요약하자면 저기 레지스터 순서를 잘 알아놓고 일단 넘어가자
https://casys-kaist.github.io/pintos-kaist/project2/argument_passing.html
/bin/ls, -l, foo, bar위와 같은 순서가 GitBook에 잘 작성되어 있었다.
입력받은 문자열을 delimiters 기준으로 구분한다.
Step 1인, 명령어를 단어들로 나누는 단계에서 사용할 수 있다.
char *
strtok_r (char *s, const char *delimiters, char **save_ptr) {
char *token;
ASSERT (delimiters != NULL);
ASSERT (save_ptr != NULL);
/* If S is nonnull, start from it.
If S is null, start from saved position. */
if (s == NULL)
s = *save_ptr;
ASSERT (s != NULL);
/* Skip any DELIMITERS at our current position. */
while (strchr (delimiters, *s) != NULL) {
/* strchr() will always return nonnull if we're searching
for a null byte, because every string contains a null
byte (at the end). */
if (*s == '\0') {
*save_ptr = s;
return NULL;
}
s++;
}

깃북에 있는 딱 이그림대로 들어간다.
유의해야할 것은 중간에 word-align이라는 이름 갖는 Padding 과
argv[4] 에 들어가는 NULL 값이다.
특히 Padding 같은 경우 8배수로 맞추는 역할을 하기 때문에, 여기서 삐끗하면 스택이 터진다.
strtok_r 의 주석을 참고하여 명령어를 공백 기준으로 분리한다.
// 1.Break the command
char *token;
char *save_ptr;
// file name에서 공백을 만나면 문자열 자르고 save_ptr에 다음 문자열 주소 저장
// 첫번째 호출
token = strtok_r(file_name, " ", &save_ptr); // args-single onearg 일경우 args-single
char *argv[99];
int argc = 0;
while (token != NULL)
{
argv[argc] = token; // 자른 문자열 저장
argc++;
token = strtok_r(NULL, " ", &save_ptr); // args-single onearg 일경우 args-single
}
/* And then load the binary */
success = load(file_name, &_if); // setup_stack 을 통해 rsp를 초기화 => argv 쌓기
Padding 같은 경우 나머지 연산자를 활용, 8의 배수가 될때까지 0을 집어넣고
Top of Stack을 가리키는 포인터인 rsp를 그만큼 이동 시켰다.
// 2. Place the words at the top of Stack
// first push 전 8의 배수로 round 후 push => 더 좋은 성능보장 목적
// argv의 마지먁 idx부터 들어가야함.
char *arg_stack_addrs[argc]; // 문자열의 스택 주소를 저장하기 위함
for (int i = 0; i < argc; i++)
{
uint16_t length = strlen(argv[i]);
_if.rsp = _if.rsp - (length + 1); // str의 길이 + \0 만큼 stack top pointer 이동(높은 주소 -> 낮은 주소)
arg_stack_addrs[i] = _if.rsp; // TOS 주소 저장
memcpy(arg_stack_addrs[i], argv[i], length); // 주소에 argv 값 복사해서 저장(Stack에 push)
}
// Padding(8의 배수로 round up)
uintptr_t addrval = _if.rsp; // 현재 rsp (TOS) 주소값
// 8로 나눈 나머지가 0으로 채울 공간
while (_if.rsp % 8 != 0)
{
_if.rsp--; // 낮은 주소로 1바이트씩 이동
*((char *)_if.rsp) = 0;
}
// NULL 삽입
_if.rsp -= sizeof(char *); // char * 사이즈 만큼 이동
*((char **)_if.rsp) = 0; // NULL 로 채우기
// 3. Push the address of each string + null pointer(\0)
for (int i = argc - 1; i >= 0; i--)
{
_if.rsp -= sizeof(char *);
*((char **)_if.rsp) = arg_stack_addrs[i];
}
_if.R.rdi = argc;
_if.R.rsi = arg_stack_addrs;
// 5. 가짜 return address push
_if.rsp -= sizeof(void *);
*((void **)_if.rsp) = 0;
이제 hex_dump(_if.rsp, _if.rsp, USER_STACK - _if.rsp, true); 를 넣고
테스트 코드를 돌려서 확인 해보면

아래와 같이 나오면 성공!
단, 실제 테스트를 돌릴때는 저기 hex_dump를 빼주어야 PASS가 되니 유의 하자.
args-single 테스트를 돌리는데 FAIL 과함께 Kernel Panic 이 와서 Call Stack을 찍어보았다.
Keyjungle@6e70038fa8f8:/workspaces/pintos_lab_docker/pintos-kaist/userprog/build$ backtrace 0x8004217d6c 0x800421c563 0x421c6e2 0x8004208cae 0x80042090cc 0x800421b3a4 0x80042076fc
0x0000008004217d6c: debug_panic (lib/kernel/debug.c:32)
0x000000800421c563: kill (userprog/exception.c:103)
0x000000800421c6e2: page_fault (userprog/exception.c:159 (discriminator 12))
0x0000008004208cae: intr_handler (threads/interrupt.c:352)
0x00000080042090cc: intr_entry (threads/intr-stubs.o:?)
0x000000800421b3a4: initd (userprog/process.c:73)
0x00000080042076fc: kernel_thread (threads/thread.c:559)
원인을 찾기 위해 gdb 디버거를 활용하였다.
PintOS에서 GDB를 활용하는 법은 간단하다.
make를 통해 build 파일이 존재한 상태를 전제로 한다.
터미널에 gdb 옵션을 달아서 테스트 코드를 실행한다.

pintos --gdb -m 20 --fs-disk=10 -p tests/userprog/args-single:args-single -- -q -f run 'args-single onearg'
gdb.kernel.o를 입력

아래와 같이 나오면 gdb 디버깅 준비가 모두 완료된다.
이후 break process_exec 을 통해 중단점을 잡고 n을 누르면서 한줄 씩 실행해보았다.
(gdb) n
196 while (token != NULL)
(gdb) n
198 argv[argc] = token; // 자른 문자열 저장
(gdb) n
199 argc++;
(gdb) n
200 token = strtok_r(file_name, " ", &save_ptr); // args-single onearg 일경우 args-single
(gdb) n
196 while (token != NULL)
(gdb) n
198 argv[argc] = token; // 자른 문자열 저장
(gdb) p argc
$14 = 60
내가 넘겨준건 args-single onearg 였다.
따라서 arg의 개수를 담는 변수인 argc가 2가 되어야 맞는데
60까지 더해지는, 즉 무한 루프를 돌고 있음을 발견 했다.
원인을 보니 args-single 로 잘랐는데 이걸 갱신한다는게 계속해서 넘겨주어서
무한 루프를 돌면서 계속 argc가 1씩 증가했던 것....
while (token != NULL)
{
argv[argc] = token; // 자른 문자열 저장
argc++;
token = strtok_r(NULL, " ", &save_ptr); // args-single onearg 일경우 args-single
}
넘겨주는 인자를 수정했고, 에러를 해결할 수 있었다.
우리 모두 GDB를 애용합시다