[yummyHitOS] 13 day (2017.07.23)

9 분 소요

우와~ 2일동안 4개의 챕터를 진행했던 야미였네여!! 날짜를 보니 12장부터 15장까지 이틀걸렸대여!

(글을 읽어보셨다면 아셨겠지만... 야미는 집중력이 무지 안좋아서 저렇게 많은 양의 공부를 하기엔 역부족이에여ㅜㅠ)

얼마나 재밌었길래 이렇게 쭉쭉쭉 진행했는지 저도 참 궁금한걸요!


오... 쭉쭉쭉 진행했을법하네여... 이번 챕터는 다름아닌 대망의 "콘솔 Shell 만들기" 라니요!! 여러분은 데스크탑으로 윈도우를 사용하시나여? 저는 리눅스를 사용한지 이제 약 2년이 되어가요. 그래서 그런지 더더욱 콘솔 Shell에 흥미를 느끼고 즐거움을 느끼는데(여윾시 변태 야미인가여?) 이번 챕터가 콘.솔.Shell.만.들.기 라고 하네요ㅜㅠㅜ

하... 그동안 OS 만들기에 대한 희망이 없었는데 이렇게나 감격스러운 순간이 올 줄이야... 감동 감격 눙물 왈칵 ㅜㅠㅜ




그럼 우리 얼른 콘솔 Shell 만들러 가볼까요? 넘나 기대되는 챕터예여 하앍...


우리가 구현할 Shell은 첫 째로 키보드에서 키를 입력받아 커맨드 버퍼를 관리하고, 커맨드에 해당하는 프로그램 실행하는 부분과, 둘 째로 텍스트 화면을 관리하는 부분, 마지막으로 Shell에 의해 실행될 프로그램 작성 부분이 있다고 해요.


C언어를 조금 써보신 분이라면 printf() 함수 외에(솔직히 printf()는 그냥 C언어 입문 하자마자 "Hello, World!\n"과 함께 배우시니 가볍게 무시를...) fprintf() 함수와 sprintf() 함수를 사용해보셨으리라 믿어 의심치 않아요!

파일 입출력 프로그램을 만들 때, fprintf()함수를 이용해 파일에 직접 쓰고, 채팅 프로그램 구현시 sprintf()함수를 이용해 char * 변수에 입력값을 넣어 그대로 send() 하거나, recv() 한 것을 sprintf()를 이용해 출력해주는 방식이 있지요!

그 중 우리는 sprintf()를 배운다고 합니다! 방금 아주 스쳐 지나가는 인연처럼 char * 변수를 말씀드렸는데, sprintf() 함수는 파라미터로 char * 변수를 받아요! 음? 파라미터? 매개변수? 아규먼트? 뭐가 맞는 말이징..?


Argument / Parameter

책에도 설명되어있지만, 인자(Argument)와 매개변수(Parameter)는 사용되어지는 것은 똑같지만, Caller와 Callee의 입장으로 나뉘는 개념이예요. 먼저 매개변수(Parameter)는 함수 등과 같은 서브루틴의 인풋으로 제공되는 여러 데이터 중 하나를 가리키기 위해 사용되어지며, 서브루틴의 인풋으로 제공되는 여러 데이터들을 인자(Argument)라고 해요.

즉, 함수 정의 부분에서의 변수를 매개변수라고 하며, 필요한 변수이자 값을 받아오는 개념이고, 함수 사용하는 입장에서 넘겨주는 변수를 인자라고 하며, 값을 넘겨주어 다른 행동을 하도록 하는 개념이예요!

int printf(const char *format, ...) 에서 format은 매개변수 / printf("Hello, World!\n"); 에서 "Hello, World!\n" 문자열이 인자가 되는 거지요!


저도 매 번 말하다보면 그냥 입에서 튀어나오고 손가락으로 쳐지는 대로 매개변수라고 얘기했다가 파라미터라고 얘기했다가 아규먼트라고 얘기했다가.. 이렇게 정리해 두었으니 까먹지 말고 맞는 용어를 사용해야겠어요!


그 다음으로 넘어가니.. 어!? 함수 호출 규약에 대해서 나오네요!? 이거 예전에 리버싱 공부할 때 했던 건데!! 제가 테스트하고 정리해둔 사진으로 설명드릴게요!


Calling convention



좌측이 stdcall 방식 / 가운데가 cdecl 방식 / 우측이 fastcall 방식이예요(fastcall이라면서 함수부분은 가장 긴 듯..)


cdecl

먼저 cdecl 방식이 우리가 코딩할 때 기본적인 방식으로, C Declaration 의 약자예요. 함수를 호출하기 전, 스택에 Argument를 넘기고 함수를 호출해줘요. 그리고 함수 내부에서 넘어온 Parameter를 이용해 함수를 수행하는 방식이예요. 그리고 함수를 빠져나온 후 메인으로 돌아오면 함수실행시 8Byte만큼 스택을 차지했던 것을 다시 빼주게 되어요! 그래야 완벽히 함수가 반환되기 때문이죠!


자 그럼 이제 가운데의 cdecl 방식을 기준으로 좌측의 stdcall 방식과 비교해볼까요?!


stdcall

stdcall 방식은 cdecl 방식과 아주 흡사하지만, 다른 점은 딱 한 가지예요! 함수를 호출하기 전, 스택에 Argument를 넘겨서 함수에서 Parameter로 받아오는 것 까지는 똑같아요. 그리고 함수 내부에서 넘어온 Parameter를 이용해 함수를 수행하는데, 마지막으로 반환하여 메인으로 넘어가기 전! 스스로 함수를 완벽히 반환시켜버려요! RETN 8 부분이 바로 cdecl 방식에서의 ADD ESP, 8의 역할을 하는 것이죠! 어멋 그러고보니 메인함수에서도 함수가 수행된 후 ADD ESP, 8을 해주지 않는 것이 보이시죠?!

cdecl 방식과 stdcall 방식의 차이점은 Caller가 함수를 반환시켜 정리해주느냐, Callee가 스스로 함수를 반환시켜 끝내느냐의 차이예요!


어디서 반환한들 그냥 반환만 되면 되는거 아니냐구요? 저 두 가지가 무슨 차이가 있는지 잘 모르시겠다구요? 넹 저도 잘 몰라서 찾아봤어영ㅎㅎㅎㅎ헿헤


첫 번째로, Caller가 함수를 반환시켜 정리해주는 것은 ADD ESP, 8 이라는 명령을 1회 더 수행해야하기 때문에 시간이 아주 조금 더 걸린다는 것이예요. Callee가 직접 RETN 8을 해주는 것이 좋다는 것은 어차피 cdecl 방식이나 stdcall 방식이나 둘 다 함수에서 RETN할텐데, 그 때 8Byte만큼 기존 주소로 이동시켜 주는 것이 효율적이라는 것이죠! 그래서 DLL을 만들 때엔 stdcall 방식의 함수를 사용한다고 하네요!


두 번째로, cdecl 방식의 장점은 가변 인자를 사용할 수 있는 점이라고 해요. 가변 인자란, printf(const char *format, ...) 에서 "..." 부분을 일컬어요.(방금 예를 들어드린 printf()의 ... 부분은 가변 파라미터가 맞는 용어예요!! 사용하는 입장의 printf("Hello, %d %s\n", num, "World!"); 에서 num 변수와 "World!" 문자열이 가변 인자가 되는 것이구요!!)


그렇다면 가변 인자(가변 파라미터)라는 것은 어떻게 사용되는지 궁금하시지 않으신가요!? 안물안궁이시라구여?! 헿헤헤헤헿괜찮아여~~ 어차피 몇 페이지 뒤에 가변 인자를 또 배우거든요!! 그러니까 미리 궁금해 하자구요 우리!!


variadic argument

가변 인자를 사용하려면, stdarg.h 라는 헤더파일이 필요해요.(참 여러분은 stdio.h 읽으실 때 스트디오라고 읽으시나요?! 컴퓨터를 하시는 분이라면 영문 약어에 익숙해지셔야 하는데, stdio는 standard input output이기 때문에 std라는 용어가 나오면 standard 겠구나! 라고 먼저 생각하시면 좋을 것 같아요! 즉 stdarg.h 파일도 standard argument 겠거니 싶어지지 않나요?! 아님 말구요... 쭈굴...)

이 헤더 파일에 가변 인자 함수들의 매크로가 정의되어 있기 때문에, 뭐.. 직접 만드셔도 되겠지만 어느정도 이미 있는건 사용하는 것도 나쁘지 않다고 생각합니다!!

가변 인자 함수는 먼저 고정 파라미터가 최소 1개 있어야하며(아까 printf() 함수에서 const char *format 과 같은) 파라미터의 마지막 순서에 "..." 이 위치해야 한다고 해요.

그러면 이제 stdarg.h 헤더파일에 포함되어있는 가변 인자를 살펴보아요!!


va_list : char * 로 정의되어있으며, 이 포인터가 각 인자의 시작주소를 가리켜준다고 해요.
va_start : va_list로 만들어진 포인터에게 가변 인자 중 첫 번째 인자의 주소를 가르쳐주는 중요한 매크로예요!!!

_crt_va_start 라는 매크로를 va_start 로 한 번 더 정의해 주었는데, 그럼 원형인 _crt_va_start 매크로를 한 번 볼까요!?


#define _crt_va_start(ap,v) (ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v))


으으 뭔가 매크로는 익숙하지 않고 극혐스럽지만... 우리도 곧 파일시스템을 만들고 멀티코어프로세서로 넘어가면서 매크로를 잔뜩 만들텐데, 미리 많이 봐둬야 익숙해지지 않을까여? ㅠㅜㅠ하지만 야미는 아직도 익숙하지 않다능... 저런 매크로 언제든지 사양해주겠다능... 오덕오덕쿰척쿰척




_crt_va_start 매크로에서 첫 번째 인자는 va_list로 만든 포인터가, 두 번째 인자는 마지막 고정 인수가 담겨야 한다고 해요.

첫 번째 인자를 살펴보면 * _ADDRESSOF(v) 인데, 이것은 &(v) 와 같으며 주소로 바꿔주는 매크로예요.

두 번째 인자를 살펴보면 * _INTSIZEOF(n) 인데, 이것은 ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)) 를 의미해요. 막 비트연산도 있고 으으 보기만해도 머리아프지만, 이것은 마지막 고정인수의 size를 구해서 가변 인자의 시작주소까지의 메모리상의 "거리" 를 구해주는 매크로이죠!!


va_arg : 특정 가변 인자를 가리키고 있는 va_list의 포인터를 다음 가변 인자로 이동시켜 주는 매크로예요.

C++에서 vector나 linkedlist같이 next element 하는 것 같은 느낌이랄까요!? 요 매크로는 _crt_va_arg 매크로를 한 번 더 정의해 준 매크로인데, 그렇다면!! 야미의 성격상 _crt_va_arg 매크로를 살펴보아야겠죠!?


#define _crt_va_arg(ap,t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))


이 매크로의 첫 번째 인자로는 va_list로 만든 포인터가, 두 번째 인자로는 자료형 타입의 이름이 들어가요.(자료형 타입은 다들 아시다시피 기본 자료형인 int, long, double을 의미해요!! 오잉? 왜 char, short 같은 다른 타입은 안말씀해주시나여 야미님!! 한 말씀 해주시져!! 그 이유는 바로 char, short는 int로 대신 쓰고, float는 double로 대신 쓴 후 형 변환을 해주어야 한다고 하네영!! char ch = (char) va_arg(ap, int); 같은 식으로 말이예요.)


va_end : 마지막으로 가변 인자를 끝내는 역할을 하는 매크로예요. 아까 주소와 크기를 받던 va_start 매크로를 이용해 가변 인자를 갖는 함수를 리턴해주는 매크로라고 해요!

이 매크로도 역시 _crt_va_end 매크로를 한 번 더 재정의 해준 것이므로 한 번 살펴보아요!


#define _crt_va_end(ap) (ap = (va_list)0)


뭔가 va_list 가변 인자를 통해 ap를 0으로 초기화시켜버리네요!? 음~ 초기화 작업을 시켜주는 녀석인것인가...


문득 생각난건데(뭐 맨날 문득문득 생각나면서 글 쓰니 이제 익숙해 지시나여? 헤헿) OS개발을 하는건지 C언어 뜯어보기를 하는건지 잘 모르겠네여... 하.지.만!! 웹/앱 개발이 아닌 시스템이나 네트워크를 하신다면 이렇게 다양하게 요고 조고 다 만져보셔야 한다는 것!!

가변 인자를 포함하는 헤더파일인 stdarg.h 파일을 더 자세~~하게 파고 싶으신 분께서는 아래 출처를 남겨둘게요!! 요기 참 좋은 사이트예요!!

출처 : https://www.tutorialspoint.com/c_standard_library/stdarg_h.htm

사실 간혹 페이스북하다보면 이 사이트 공유된 글 많이 봤는데.. 별로 안땡기게 생겼었는데 이렇게 땡기게 되는 사이트일줄은 1도 몰랐네영ㅎㅎ


드디어 다음으로 넘어갈 수 있게 되었어요!! 그런데 그 다음이 아직도 함수 호출 규약이라는 것은 함☆정~



선정이.. 예나 딸이예요... (아닛!? 뭐라곳!? 쥬르르륵)

fastcall

cdecl 방식과 stdcall 방식을 살펴보았는데, 마지막으로 fastcall 이라는 방식이 있어요!! 이제는 저 멀리 남겨진 저의 캡쳐사진이지만, 가운데 cdecl 방식을 기준으로 우측이 fastcall 방식이지요!! 좀 더 길어보이지만 무슨이유에서인지 fast하다고 이름부터 남다르네여!?

어떤 차이가 있는지 보이시나여!? 바로 매개변수의 처리 방식이 달라요!! cdecl 방식과 stdcall 방식은 PUSH 4 → PUSH 5 → CALL Function 순서로 처리하는데 반해, fastcall 방식은 MOV EDX, 4 → MOV ECX, 5 → CALL Function 으로 되어있지 않나요? 너무 사진이 멀리있어서 보기 불편하시다구요? 죄송해여... 얼른 cdecl 방식과 fastcall 방식 두 가지만 떼어가져 올게여...



뭔가 아까사진보다 더 잘보이는 것 같은 느낌은 저만 느끼는 걸까요..? 맥뿍이라 화질이 좋아서 그런가... 헿헤 넘나 맥뿍자랑하는것~

참 혹시 제가 편집하는 방법이나 툴 등을 궁금해 하시는 분이 있지 않을까(전혀 없지만) 생각되어 말씀드리자면... 보통은 그림판으로 한땀 한땀 작업해요!! 그림판 장인이 되어버려씀... 게다가 전 마우스 없이 매직트랙패드를 이용하기 때문에 손가락의 섬세한 움직임을 필요로 해여ㅜㅠ 파워포인트는 아주 가끔 스택그릴때!? gif는 youtube 동영상을 mp4 파일로 변환시켜주는 flvto 사이트를 이용한 후, 동영상파일을 gif 파일로 변환시켜주는 ezgif 사이트를 이용하고, gif 파일에 자막을 넣거나 포스팅시 10MB 크기제한에 맞게 하기 위해서는 gifcom 이라는 프로그램과 ALSEE 프로그램을 이용해요!

복잡하지요..? 이거 참 제가 복잡하게 하는건지, 원래 이렇게 짤 하나 생성하는게 어려운건지 잘 모르겠어여 ㅜㅠㅜ 그래서 포스팅 하는 시간이 오래걸리기두 해여...쥬륵...ㅜㅜ 다시 본론으로 넘어갈게요!!


fastcall 방식은 직접 스택에 PUSH하여 매개변수에 접근하는 방식이 아닌, 레지스터를 이용해 레지스터의 값에 저장한 후 함수에서 직접 접근하여 사용할 수 있기 때문에 속도가 빠른 방식이라고 해요! 또한, stdcall 방식과 비슷하게 메인함수로 돌아왔을 때 ADD ESP, 8이 없는 것으로 보아 Callee에서 스택을 정리하고 나오는 듯 하지 않나요? 사실 따로 스택을 정리할 필요 없이, PUSH 된 매개변수의 크기만큼 스택을 정리해주는 stdcall 방식과 cdecl 방식과는 달리 레지스터를 이용하기 때문에 스택을 정리해줄 필요가 전혀 없다는 점!!

정말 매력적인 호출 규약이예요... fastcall 너란 녀석.. 핳하☆


드디어 함수 호출 규약과 가변 인자에 대해 다 알아보았어요!! 아직 챕터 15에 들어온지 3페이지밖에 안된 것은 실화입니까?!

그 다음으로 넘겨보니 위에서 설명드린 가변 인자에 대해 설명해주셨네여! 뭔가 제가 설명한 것은 쭈굴이같은 느낌...쭈굴...


printf() 함수나 sprintf() 함수 둘 다 결국 종착점은 vsprintf() 라는 함수라고 해요. va_list 변수를 파라미터로 갖고있기 때문에, 직접 접근할 수 있기 때문이에요!


이 vsprintf() 함수를 구현한 후, 자주 쓰이며 정말 중요한 itoa() 함수 / atoi() 함수를 구현하네요!


itoa() 함수는 integer to alphabet 이라는 의미로, 정수를 문자열로 치환해주는 함수예요.(문자열이면 좀 친근하게 const char * 혹은 string 의 앞글자를 따서 itoc 나 itos 라고 좀 해주지... 멍충이 황소그누...) 네트워크에서 개발을 하다보면 inet_ntoa() / inet_aton() 함수가 있는데(최근에 보안성 문제로 inet_ntop() / inet_pton() 함수를 이용해요! 음.. 보안성 문제라면 역시 scanf() → scanf_s() / strcpy() → strcpy_s() 같은 이유겠지요?!) 이 함수들은 Little Endian 방식과 Big Endian 방식의 치환 함수예요. 처음에 네트워크 개발을 시작했다보니 itoa() / atoi() 와 같은 함수들이 친숙하더라구여!!

역시 atoi() 함수는 그러면 alphabet to integer 로써 문자열을 정수로 치환해주는 함수겠지요?!


이번 챕터는 여담도 길지 않았는데ㅜㅠ 스압이네여... 약스압이 아니라 처음으로 강스압이 되어버려따... ㅋㅋㅋㅋㅋ여러분 예전에 나는 가수다에서 가수 박미경씨의 레전드 짤 아세여?! 여윾시 백수단 선배님들~ 무대를 뒤집어 놓으셔따! ㅋㅋㅋ아 넘나 웃겨서 전 또 짤제작하러갔다올게여!!(아직 할 일이 많지만 이 짤만큼은 제작해 버리겠닷+____+)



백두산 선배님을 긴장하셔서 백수단이라고 말씀하신 것...ㅜㅠ 재미를 선사해주셔서 감사합니다 (___ ^ ___) 꾸벅


이제 itoa() / atoi() 함수를 지나 decimalToString() / hexToString() 함수가 나오는데, 이 함수들은 나중에 네트워크 개발에선 요긴하게 쓰이겠지만 그 외 따로 쓰일 일은 없을 것 같아요! 그냥 이번 챕터에서 stringConverter라는 명령어를 만들 때 사용하기 위한 함수라고 생각하고 있어여 ㅇㅅㅇ

그리고 이제서에 챕터 15의 2절로 넘어왔네여... 콘솔 매크로를 만들고, 키보드 값을 입력받을 때 화면 크기에 맞게 버퍼를 이동시키는 아주아주 중요한 함수를 구현하네요! 커서까지 제어하구요!! 드디어드디어 ㅜㅠ 눙물나는 리눅스에 가까워진 것인가... 흐 감동...

그 다음에 나오는 getch() 함수는 C언어 프로그래밍시 자주 쓰이는 함수일 거예요! getchar() 함수와 함께 가끔 대기상태를 위해 / 혹은 fflush()를 대체해서 buffer 지우는 용도로도 사용하더라구요!


그 다음 챕터 15의 꽃 !!! 3절의 Shell.구.현.☆ 오늘 이 날만을 위해 달려온 우리들이 아입니꺼!? Shell 코드에서 "MINT64>"로 되어있는 프롬프트를 자신만의 것으로 바꿔보아여☆ 뭔가 깐지나면서도 나의 OS라니 감격스럽지 않나여!? 하앍...


더 이상 뒤에는 제가 알려드릴 것은 없는 것 같아여.. 넘나 완벽도가 높은 책이라 저의 부연설명은 여기까지겠네여!! 항상 앞에서 힘 다쓰고 뒤에서 빈약한 것 같은 포스팅이지만, 실제로 ㅜㅠ 책을 한 페이지 한 글자 읽어가며 설명이 필요한 것은 무엇이 있을지 찾아가며 포스팅을 하는 것이기 때문에 너무 비난하지는 말아주세여!!! 물론 비난과 비평은 피드백의 중요 요소가 되긴 하지만요!!


이렇게 유닉스 초기 버전에 가까워진 우리의 OS를 축하하며 야미는 다음 포스팅에서 뵐게여!! 오늘 강스압 하루 수고하셨습니당!!


Check out the yummyhit’s website for more info on who am i. If you have questions, you can ask them on E-mail.

카테고리:

업데이트:

댓글남기기