https://mm5-gnap.tistory.com/60


ESP - Stack pointer register


ESP 레지스터 : 스택의 크기를 조정할 때 사용되는 레지스터. 스택의 최상단 주소값을 가진다.

 -> 스택의 크기를 나타냄


Intel CPU에서는 스택이 거꾸로 자란다.


ESP는 다음 번 DATA를 PUSH할 위치가 아닌, 다음에 POP 했을 때 뽑아낼 데이터의 위치를 가리킨다.

 -> std::stack::top()의 주소라고 생각하면 될듯.


어셈블리에서 esp에 PUSH를 하면 esp 값이 n감소한다.


 n? : msvc 2017 기준, 0C0h(192) 감소한다. 밑의 디스어셈블리 참고



감소하는 이유는 다음과 같다. 높은주소 → 낮은주소


EBP - Base pointer register


EBP 레지스터 : 스택프레임 형태로 저장된 함수의 지역변수, 전달 인자를 참조 & 값의 수정 시 사용되는 레지스터.


현재 스택의 가장 바닥을 가리키는 포인터.


새로운 함수 B가 호출되면, EBP 레지스터 값은 지금까지 사용했던 스택 A의 위를 가리킨다. 그리고 새로운 스택(함수 공간)이 시작


따라서 EBP는 새로운 함수가 호출되거나, 현재 실행중인 함수가 종료되면 값이 달라진다.


새로운 함수를 호출할 때, EBP 레지스터 값

전달 인자를 ESP 레지스터로 참조할 수는 있지만 어셈블리 코드 유지가 힘들다.


EBP는 고정적이지만 ESP는 명령을 수행 시 값이 변하기 때문에 매번 수정해주어야 하기 때문.



sp(Stack Pointer register) 는 esp다.

fp(Frame Pointer register) 는 ebp다.


fp 는 sp의 백업 포인터다.


sp가 반환을 알리면 fp 위치로 돌아간다. 이 때 메모리는 반환작업을 거치지 않고 그냥 덮어쓰는 방식으로 작동한다.



fp의 중복 문제는 스택에 fp를 백업하는 방식으로 해결한다





PUSH & POP 명령어 디자인



기존 STORE 명령어는


STORE 대상(레지스터), 목적지(메모리 주소)

이지만


문제는

STORE 7(숫자), sp(레지스터)

이다.


해결책은 7을 레지스터에 넣고 sp를 메모리에 넣는것



첫 번째 문제점 7을 레지스터에 넣는것은


ADD r1, 7, 0

으로 해결한다.


보통 MOV 명령어가 있거나 그렇던데 그렇더라.


두번째 문제는 sp를 메모리에 STORE 한다


STORE sp, 0x40


그 후 레지스터와 메모리 주소를 STORE한다


STORE r1, [0x40]


[] 기호는 인다이렉트, 포인터 연산이다.


0x40에는 sp메모리 위치 0x10이 저장되어 있다


[0x40]이 아니라 0x40에다가 r1을 STORE 해버리면 포인터 변수의 위치에 값을 넣어버리는 것과 같은 꼴이다.


[] 기호로 0x10에 접근하자.


그리고 ADD로 sp 메모리 위치를 4바이트 올리자. 만약 64비트라면 8을 더해줘야 한다.


POP을 만드는 두가지 방법



호출 규약 실행 이동




CPU 내부에선 IR(Instruction) register에 코드영역 내부에 존재하는 명령어를 하나씩 fetch한다.


PC(Program Counter) register는 fetch해야할 명령어, Command n을 가리킨다.


즉 CPU는 PC에 저장된 값을 Fetch Decode Execution 사이클을 죙일 반복한다.



pc 레지스터도 스택포인터, 프레임포인터와 같이 함수호출이 되면 돌아갈 위치를 저장해야 하는데 이를 LR(Link Register)라고 부른다.


LR도 똑같이 Stack에 쌓는다.




32비트의 함수호출규약은 한계가 있어서 하나도 빠짐없이 암기할 필요까진 없다


Parameter order는 C스타일이다. <-

(파스칼스타일은 -> 이다 근데 안중요)


스택을 비우는게 32비트에선 Caller와 Callee가 각각 다른 것을 볼 수 있는데 64비트에선 Caller 통일댓다


중요한 점은 64비트에서 Parameters in registers 카테고리의 레지스터 사용 방식이 레지스터에 상당히 의존하는 것을 볼 수 있다.


32비트에서 빠르다는 __fastcall이 레지스터 두개를 쓴다. 64비트에선 기본적으로 4개를 쓴다. 리눅스에선 지존많이쓴다.


그래서 64비트는 클럭수준으로 속도만 빠른게 아니라 이러한 함수호출규악까지 포함해서 32비트보다 속도가 빠르다.





+ Recent posts