[ 카드캡터 핀토스 ] ep.2💬 말의 카드 : argv의 속삭임, 스택 위의 인자들

김쟇연·2025년 6월 2일
5

PintOS Project 2

목록 보기
3/4
post-thumbnail

episode 2 – 💬 말의 카드 : argv의 속삭임, 스택 위의 인자들

나는 또 하나의 카드를 발견했어요.

고요한 밤, 유저 프로그램의 심연 속에서 조용히 깨어난 작은 목소리들.

“args-multiple... args-dbl-space... 그 이름은 나를 호출하는 주문…”

🌸 그건 마치 인자(argument)들이 서로 이야기를 나누듯,

차곡차곡 쌓여 스택 위에서 조용히 기다리는 것 같았어요.


💭 "단지 실행만 된다면 충분할까?"

‘args-single’을 봉인하고 나서도,

무언가 아쉬운 기분이 마음 한구석에 남아 있었어요.

유저 프로그램은 분명히 뜨긴 했지만,

그 속에서 전해져오는 인자의 속삭임은 너무 희미했죠.

그때였어요. 케로가 내 곁에 다가와 말했어요.

“사쿠라, argv[0]만 알면 되는 건 아니야~

argv[1], argv[2], … 거기 담긴 진짜 의미까지,

정확하게 전달돼야 ‘진짜 마법’인 거지~”


📖 말의 카드가 다루는 마법

이 카드의 마법은 ‘정확하게 쌓아 올리는 것’이에요.

스택이라는 공간에 단 하나의 인자도 빠뜨리지 않고,

순서대로, 예쁘게, 그리고 규칙에 맞게 정렬해야 하죠.

그게 바로 argument passing의 마법!

🌟 작지만, 가장 기본이 되는 신뢰의 마법이에요.


🗂️ 메모 위의 약속 – gitbook에 적힌 규칙

🧠 내가 참고한 마법서, 즉 운영체제의 지침서에는

이 마법에 대해 이렇게 적혀 있었어요:


# 스택 구조 예시

[rsp ↓]
[ fake return address ]
[ argc              ]  ← %rdi
[ argv              ]  ← %rsi
[ argv[0] 주소      ]
[ argv[1] 주소      ]
[ ...              ]
[ NULL sentinel     ]
[ word-align padding]
[ "onearg\0"       ]
[ "args-single\0"  ]
요소설명
문자열 복사인자 문자열들을 스택에 넣어요. (역순!)
주소 저장각 문자열의 주소를 다시 스택에 push
NULL sentinelargv[argc]가 NULL이 되도록 0을 push
정렬스택 포인터는 항상 8바이트 정렬이 되어야 해요
%rdi, %rsi각각 argc, argv의 시작 주소를 저장

🌸 이 구조를 정확히 따라야

유저 프로그램의 main(int argc, char **argv)

진짜로 인자들을 제대로 받을 수 있어요!


🧪 도장깨기 마법 테스트들

args-multiple


(args) argc = 4
(args) argv[0] = 'args-multiple'
(args) argv[1] = 'one'
(args) argv[2] = 'two'
(args) argv[3] = 'three'
(args) argv[4] = null

args-dbl-space


(args) argc = 5
(args) argv[0] = 'args-dbl-space'
(args) argv[1] = 'too'
(args) argv[2] = 'many'
(args) argv[3] = 'spaces'
(args) argv[4] = null

이 테스트들은 마치

“인자가 정말 모두 정확히 잘 도착했는가?”를 묻는 시련 같았어요.


✨ 마법진을 짜듯 쌓은 스택

🌸 나는 다시 setup_stack() 함수를 꺼내 들었어요.

이번엔 마법처럼 더 정밀하게 조율해서,

각 인자의 위치와 의미까지 모두 담아내기로 했죠.

c

static void
setup_stack(struct intr_frame *if_, char **argv, int argc) {
  char *arg_addr[64];
  uint64_t *rsp = (uint64_t *)if_->rsp;

  for (int i = argc - 1; i >= 0; i--) {
    rsp = (void *)rsp - (strlen(argv[i]) + 1);
    memcpy(rsp, argv[i], strlen(argv[i]) + 1);
    arg_addr[i] = (char *)rsp;
  }

  rsp = (void *)rsp - ((uint64_t)rsp % 8);
  *(--rsp) = 0;

  for (int i = argc - 1; i >= 0; i--)
    *(--rsp) = (uint64_t)arg_addr[i];

  uint64_t argv_addr = (uint64_t)rsp;
  *(--rsp) = argv_addr;
  *(--rsp) = argc;
  *(--rsp) = 0;

  if_->rsp = (uint64_t)rsp;
  if_->R.rdi = argc;
  if_->R.rsi = argv_addr;
}

마치 블록을 맞추듯,

문자열을 스택에 복사하고

그 주소들을 차례로 쌓아가면서

나는 진짜로 argv의 구조를 이해하게 되었어요.


🐛 시험은 이어졌어요

처음엔 스택이 계속 망가졌어요.

  • 문자열보다 먼저 argv 포인터를 넣으면 순서가 뒤바뀌고,
  • NULL sentinel을 빼먹으면 argv[argc] != NULL이 되고,
  • 정렬을 안 하면 프로그램이 죽고,
  • argv[0]보다 argv[1] 주소가 높으면 프로그램이 혼란에 빠지고…

🌙 밤을 지새워 스택을 디버깅하면서,

나는 점점 마법의 정렬을 깨우쳤어요.


📷 그 순간, 마법진은 완성되었어요

🌸 그리고 드디어 테스트를 통과한 순간,

토모요가 말했어요.

“모든 인자가 제자리야… 너, 정말 멋져 사쿠라!”

프로그램은 또렷하게 모든 인자를 출력했고,

argv는 정확하게 정렬돼 있었어요.

그건 조용하지만,

가장 확실한 승리의 선언이었어요.


💬 말의 카드… 봉인 완료!!

나는 스택 위의 조용한 속삭임들을 귀 기울여 듣고,

하나도 빠뜨리지 않고 받아 적었어요.

“한 마디도, 틀리지 않게…”

🌸 그리고 마침내,

🎴 말의 카드, 봉인 완료!


✅ 정상인 버전 정리

📌 테스트

  • args-multiple, args-dbl-space
  • argv의 개수, 문자열, NULL, 순서 모두 검증

🧠 개념 요약

요소설명
argv[]각 문자열의 주소 배열
NULL sentinelargv[argc] = NULL
word-align%rsp는 항상 8의 배수
레지스터%rdi = argc, %rsi = argv 포인터

🛠️ 구현 포인트

  • 문자열은 뒤에서부터 push
  • 그 주소를 역순으로 다시 push
  • 마지막엔 argv 포인터, argc, fake return address까지

⚠️ 실수 포인트

  • argv 포인터 순서 꼬임
  • NULL 누락
  • word-align 안 맞춤
  • 주소가 높은 순으로 정렬되면 실패

💯 테스트 결과

  • 모든 인자 테스트 통과!
  • 💬 말의 카드, 성공적으로 봉인 완료 🎀

profile
그래도 해야지 어떡해

0개의 댓글