본문 바로가기

무언가 만들기 위한 지식/SPARC Assembler

SubRoutine Structure Point

지난번 포스팅에서 언급한 SPARC Stack의 쌓이는 구조를 보면 다음과 같다.

 sp+n  sp  Register Window Saving Area (64 byte)
 Return Structure Point (4 byte)
 First 6 Parameters (24 byte)
 Rest of Parameters (as needed)
 Callee
 fp-n  sp  Locals (as needed)  Callee
 fp+n  fp  Register Window Saving Area (64 byte)
 Return Structure Point (4 byte)
 First 6 Parameters (24 byte)
 Rest of Parameters (as needed)
 Caller

위의 표에서 Structure Point라는 것을 볼 수 있을 것이다.
이 Structure Point에는 해당 Structure의 포인터 값, 즉 주소값이 저장되어 있다.
쌓인 스택에서 아래 스택의 Structure Point의 값을 얻어오면 아래 스택의 메모리 부분에 쉽게 접근이 가능하다.



그림으로 확인하면 왼쪽과 같다.
Pointer(주소값)를 이용하여 아래 Stack의 값에 접근하는 것이다.

즉, 위 스택(Callee)에서 아래 스택(Caller)의 메모리에 접근하여 값을 가져오는 것이다.

이 방법은 structure에서 뿐만아니라,
6개 이상의 매개변수가 사용될때에도 비슷하게 사용된다.

주소에 관한 접근은 물론 fp와 sp를 이용하여 접근하기 때문에 이를 쉽게 이해하기 위해서는 전 포스팅에서 언급했던 스택 쌓일때의 fp와 sp 변화를 잘 알고 있어야 한다.

왼쪽 표 하단의 X2, X1은 그냥 자신이 설정한 메모리상 데이터 공간이라고 보면 된다.

중요한 것은 fp영역의 struct에는 아래 x2의 가장 끝, 즉 가장 낮은 주소값이 저장되어 있다는 것이다. 이를 이용하여 아래의 모든 메모리 접근이 가능하다. 변수가 더 많아도 전스택에서 공간만 할당해 놓았다면 자유롭게 접근이 가능하다.


테스팅한 프로그램으로 확인해 보자.

※ 사실 return structure point는 설계자들이 구조상 스트럭쳐를 리턴하기 위해 할당된 임의의 공간이다. 이 공간을 포인터를 이용하여 인자전달등 다른 기능으로 사용해도 무방하다고 한다.

[작성환경 : solaris sparc machine  작성툴 : Vi editor   컴파일러 : gcc]

위 코드는 SubRoutine 호출시,
main에서의 스택과 func 서브루틴 상에서 접근한 값을 비교하기 위해서 작성하였다.
참고로 5, 6번줄의 global은 꼭 선언해야만 하는 것은 아니다.

일단 main에서하는 역할을 보자.

21, 22번줄에서는 테스트할 값인 4와 8을 Register인 %l0와 %l1에 넣어주고,
23, 24번줄에서는 메모리상에 각 값을 저장하고 있다.
이때의 fp는 main Stack의 하단부로부터 각각 -4, -8인 위치(즉 스택 위 방향으로 4칸 8칸)인 부분에 4byte짜리를 저장한다.
그후 main Stack에서의 메모리 값을 직접 접근하여 출력해 본다.
후에 func함수 내에서 이 메모리에 접근하여 다시 출력해 보고, 그 값을 비교하는 것이 이 프로그램의 의도이다.

30~32번 줄에서는 위에서 정의한 func함수를 호출한다.
호출하기 전에 main Stack에서의 값(4, 8-우리가 출력할 값)의 주소값을 Return Structure Point 영역에 저장한다.
Return Structure Point로의 접근은 
st %o0, [%sp+64]  
을 이용하여 이루어진다. main Stack의 sp에서 아래쪽 방향( 높은 주소방향)으로 64다음의 4byte공간이다.

이제 func 함수의 루틴으로 실행이 넘어가게 된다.
8에서 save를 이용하여 새롭게 Stack의 영역을 확보하고 이는 main Stack위에 쌓이게 된다.
그후 fp+64를 통해 이전 Stack(즉 main)의 Return Structure 영역에 접근한다.
그곳에는 주소값이 저장되어 있는데, 그 주소값을 따라가면 우리가 처음 설정하였던 4, 8의 최상위 주소값이 정해져 있다.
접근하여 얻은 값은 %o0에 넣어지게 되는데,
후에 %o0, %o0+4를 이용하여 그 값(value)에 접근할 수 있다.
이렇게 주소를 이용하여 이전 Stack상의 메모리 영역에 자유롭게 접근이 가능해 진다.

그리고 값을 가져와서 func Stack상에서 출력을 해 본다.
출력 값을 보면 다음과 같다.


위에서처럼 main내 메모리 접근과 func에서의 main상 메모리에 접근이 무사히 되어 같은 값을 출력했음을 확인할 수 있다.

※ 참고사항 :
     명령어들중에 함수를 종료시 ret와 restore의 명령어를 볼 수 있다.
     restore는 레지스터를 전 스택의 레지스터와 맵핑 시켜주는 역활과 sp의 조절역할을 한다.(save와 반대) 즉 메모리 해제의 역할을 하고 ret의 경우 돌아갈 주소로 jump하게 된다.
     사실 ret은 jmpl %i7+8, %g0 로 구성되어 있다.
     전에 언급되었듯이 %i7은 return Register로 call을 호출했던 지점의 주소가 저장되어 있다. (%o7으로부터 받은 값이다.)
     +8을 하는 이유는 call한 부분으로 부터 다음다음 명령어를 수행하기 위해 해준다.(call의 다음명령은 nop이거나 파이프라인 특성으로 다른 명령어로 처리해줘야 하는부분이기 때문이다.)
     또한 ret은 pipeline과 연관되어 다음에 하나의 명령어를 처리해주고 이동하게 되는데
    ret
    restore

     이 두줄의 문장은 사실
    restore
    ret

     nop
     으로 구성되어 있다.
     nop제거를 위해 짧게 사용하는 것이다.