까지만 알고 있었는데, 정확히 증가하는 시점.? 이 갑자기 궁금해짐.
얘는 왜 궁금해졌나?
사실 이야기 할 것들이 많은데, 이유를 다 설명하기에는 너무 장황해져서 생략함.
편의상 rip라고 설명함. (x64에서의 레지스터)
명령어가 실행될 때 마다 특정 크기만큼 증가하는데, 그 증가하는 크기는 현재 실행중인 명령어의 크기에 해당한다. 예를 들면, MOV rax, rbx라고 하면 MOV 명령어는 5bytes 이므로 rip가 5씩 늘어난다.
-> 그러므로 rip의 값을 증가시켜주는 하드웨어가 따로 존재한다.
OK, 평소에 rip가 어떤 식으로 증가하면서 코드가 실행되는지 알겠다. 그러면 jmp의 경우는?
올바른 코드는 아니겠지만, 일단 예제코드로 보자.
정리 : rip의 증가는 명령 실행 전에 일어나게 된다.
위의 경우에는 offset이 1바이트만으로 표현이 가능한 경우의 jmp에 해당하고, "jmp short 주소"의 형태로도 사용이 가능하다.
offset이 1바이트보다 더 큰 경우가 궁금해서 온라인 어셈블러로 코드를 쳐 봤더니, 이 경우에도 절대 주소로 jmp가 아닌 offset을 통해서 jmp를 하더라.
사실 jmp에 대해서 찾아보게 된 이유가 ret의 세부 동작이 궁금해서 그랬던 것이긴 한데, 일반적으로 검색하면 나오는 말들이 "ret는 pop rip; jmp rip와 동일하다"는 것이었다.
그런데 생각해 보면 jmp rip는 어차피 rip 값으로 rip값을 이동시키는 거니까 안 붙여줘도 되지 않나? 왜 굳이 jmp rip를 넣어주는 거지? 라는 의문이 들기 시작함.
chat gpt한테 물어보니까 pop rip는 특수연산이라서 스택을 정리를 안 해준다고 하는데, 이거는 말이 안 되는 것 같고, 그냥 pop rip만 하면 보기에 안 이쁘니까 실제로 jmp한다는 의미로써 jmp rip라고 추가적으로 적어놓은 것 같기도..?
pop rax; jmp rax; 해도 되잖아 이제보니깐..
그러면 도대체가 왜 ret, call, jmp같은 명령어가 존재하는가? 그냥 일반 연산으로 하면 되잖아.
일단 먼저 정리하고 시작해야 될 것은, rip는 특수 목적 레지스터이므로 ADD, MOV 등과 같은 일반적인 연산이 허용되지 않는다. (but, x86에서는 가능..!)
정리 : 일단 자세한거는 컴퓨터구조 공부하면서 알아보는 것으로 하고, 일단 지금은 ret의 역할 : 스택의 제일 위의 값을 pop하고, 그 pop한 값으로 rip를 옮겨준다. 정도로 이해하면 될 듯. 애초에 얘 자체가 하나의 기계어라서 그냥 하나의 동작으로 보는 것일 수도 있음. 당연히 call의 경우에는 rip값을 push해주고 그 다음에 jmp하는 거겠지.