먼저, 이전 글에서 다루지 못했던 것이 두 가지였다.
일단 기억을 상기하기 위해 코드 및 실행결과와 그림을 다시 불러오자!
프로그램 코드
void hello(char *tag)
{
char inp[16];
printf("Enter value for %s: ", tag); gets(inp);
printf("Hello your %s is %s\n", tag, inp);
}
hello()
를 한 번만 호출한다hello()
의 virtual address는 008048394
이다.실행결과
$ perl - e 'print pack("H*", "414243444546474851525354555657586162636465666768e8ffffbf948304080a4e4e4e4e0a"); | ./buffer2
Enter value for name:
Hello your Re?pyy]uEA is ABCDEFGHQRSTUVWXabcdefguyu
Enter value for Kyyu:
Hello your Kyyu is NNNN
Segmentation fault (core dumped)
펄스크립트로 hex 값을 유니코드 문자로 변환해서 buffer2의 입력으로 넣어줬는데, 이 때 스택에 어떻게 들어가는지 자세히 살펴보자.
414243444546474851525354555657586162636465666768e8ffffbf948304080a4e4e4e4e0a
41 42 ... 68까지는 아스키코드표에서 확인할 수 있듯, 알파벳으로 변환이 됐었다. 저번 글에서 다루었듯, 0a가 엔터로 들어가서 gets()의 입력으로 들어가고, 그 바로 뒤에 있는 tag포인터의 1byte가 우리 입력의 끝, NULL로 값이 바뀌어서 tag가 가리키는 주소에 있는 문자열 값이 Re?pyy]uEA
, 쓰레기 값이 되었었다.
그림을 보면 이해가 쉬웠었다😀
그렇다면 tag 포인터가 가리키는 문자열이 Kyyu
로 한 번 더 바뀐 이유는 뭘까?
그걸 알기 위해서는 c언어의 함수 호출과정을 이해해야한다.
stack frame의 구조는 다음과 같다.
유튜브에서 stack frame이 쌓이고 사라지는 과정에 대해 친절하게 설명해주는 동영상을 찾을 수 있었다! 여기
그림을 설명해보자!!
stack frame은 함수가 호출될 때마다 함수당 한 프레임씩 쌓인다. 이 때 함수가 모든 명령을 수행하고 return 된 후에는 이전의 프로시저(함수)가 스택에 남아있는 이전 함수의 아이템들을 스택에서 pop시킨다.🤩
현재 수행중인 함수를 P, 호출할 함수를 Q라고 하면(그림처럼) P가 Q를 호출하면
Q가 종료되면(returning control)
실행결과와 스택 그림을 다시 보면서 해보자~
$ perl - e 'print pack("H*", "414243444546474851525354555657586162636465666768e8ffffbf948304080a4e4e4e4e0a"); | ./buffer2
Enter value for name:
Hello your Re?pyy]uEA is ABCDEFGHQRSTUVWXabcdefguyu
Enter value for Kyyu:
Hello your Kyyu is NNNN
Segmentation fault (core dumped)
hello()가 처음 실행되고 나서 return하기 전에, hello()의 local variable 이나 old base ptr은 모두 스택에서 빠져나온다는 걸 잊으면 안된다. 특히 old base ptr
는 그냥 아무 접근 가능한 주소값으로 채워넣을 수밖에 없기 때문에 이후에 control을 가지게 되는 프레임(? 혹은 프로시저)가 이상한 위치가 되어버린다.
return addr가 hello()의 시작 주소였기 때문에 hello()함수는 한번 더 실행이 되지만, 스택 프레임 자체가 원래 있지도 않았고, 지금은 엉뚱한 공간(e8ffffbf
)을 가리키기 때문에 함수에서 사용되는 매개변수나, 지역변수가 이상한 값을 보일 수 밖에 없다. 그래서 tag
가 가리키는 문자열이 Kyyu
와 같은 쓰레기값으로 한 번 더 바뀐 것이다.
그리고 나서 Segmentation fault가 뜨는 이유는 두번째 hello()
가 모두 실행되고 나서 pop되는 return addr가 접근 불가능한 영역이었기 때문일 것 같다.(당연히 stack frame 포인터 자체가 엉뚱한 곳을 가리키고 있었기 때문에 쓰레기값으로 차있었을 것.) 이 부분에 대해서는 정확히는 말할 수 없을 것 같다. 실습을 해볼 수 있는 영역도 아니고..ㅜ 나중에 자세히 찾아봐야 할 것 같다.
2020.06.22
사실 저번 글이 시험 공부를 하다가 쓴 거 였는데 이해가 안돼서 뒷부분을 미뤄놨다가 시험 전 날 이해를 해서 시험이 끝나고 좀 쉰 후에 뒷부분을 쓰게 됐다 ! 😀 시험에 나오진 않았지만.. 잊어버리기 싫어서 쓰려고 한다. 뭐 잊어버리긴 하겠지만 다시보면 되니까 히히.
2020.06.24
하 진짜 내가 이걸 오늘까지 질질 끌 줄은 몰랐다. 물어볼 사람이 없는 공부는 정말 어렵다😇 이번에 구글링하면서 나중에 정말 잘하는 사람이 되려면 영어에 많이 익숙해져야겠다고 생각했다. 전보다야 나아졌지만 아직도 효율적으로 모르는 것에대한 답을 얻는 방법을 모르겠다. 흠 너무 왕도를 찾으려고 하는 건가 싶기도 하네