본문 바로가기

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

기본적인 명령어 및 코드


일단 기본적인 개념을 설명하면서 진행하는 것도 좋겠지만,
처음 어셈블리언어를 접하는 사람은 없을 것이고 어느정도 컴퓨터 언어를 다뤄봤다는 전제하게 코드를 봐가며 설명하려한다.



위 코드를 처음본다면 눈에 익숙한 것들이 여러가지 보일 것이다.
main, global, sub, add등이 왠지 무슨뜻인지 알것이다.

[어셈블러의 주석처리는 !로 한다. 마지막 줄에 보면 !로 End를 주석처리한 것을 볼 수 있다.]

이 소스는 (x-1)*(x-7)/(x-11)을 연산하는 프로그램이다.
x는 %l0 레지스터를 통하여 제어되고 결과 값인 y는 %l1레지스터에 의하여 저장된다.
이 어셈블리어는 정해진 32개의 레지스터에 의하여 값을 주고 받는다.
레지스터란 임시적으로 빠르게 파일을 주고받기 위한 공간이다. 메모리와는 다르게 취급되며 메모리는 실제 다른 명령어를 통해 제어된다.(그부분은 후반에 자세이 다루겠다.)

.global은 말 그대로 글로벌 영역을 정해준다고 보면된다. 즉 2번줄은 main을 글로벌영역, 가장 먼저 실행되는 부분으로 정해준다. C언어의 main처럼 사용하겠다고 선언하는 것이다.

4번 줄의 save영역에서는 사용할 메모리 영역을 설정하는 것이다.
-96은 크기를 말하는데, 왜 음수 이고 96인가에 대해서는 스택 챕터 부근에서 자세히 언급될 것이다.
-96의 경우 언제나 8의 배수로 오게되는데, 이것은 명령어의 주소가 4단위로 증가하고, 가장 큰 자료형인 double이 8byte이기 때문이다.

6, 7번째 줄의 sub는 가장 원초적인 기계어이다.
[sub]- 2의 보수 뺄샘을 실행.
Format(형식) : sub rs1, rs2, rd  (rs는 레지스터 소스, rd는 레지스터 목적지)
Operation(연산) : rd= rs1 - rs2

즉, sub %l0, 7, %o1 이 코드는
%l0의 값에서 7을 빼서 %o1에 집어 넣으라는 것이다. %는 레지스터를 가리킬때 사용된다.

그다음 8번줄에 call이라는 것이 있는데 이것은 System call의 일종으로 어셈블러에서 손쉬운 연산을 위해 미리 만들어 놓은 프로시져 또는 함수이다.

나중에 가면 printf, .div, .mul 등 여러가지를 call을 이용하여 호출하게 된다.
여기에서는 .div를 사용하였는데 이것은 나눗셈을 쉽게 사용하기 위해 연산을 정의한 것이다.
사실상 기계어중 나눗셈이란 명령어는 없다. 나눗셈이란 결국 뺄셈을 여러번 한 것이기 때문에 이를 정의해 놓았다.
우리는 규칙에 맞게 가져다 쓰면 되는 것이다.

.div를 사용할때 주의점은 나눌 제수와 피제수를 각각 %o1, %o0에 넣은 후 .div를 call해야 한다는 것이다.(인자로 사용된다.)
call이 끝나면 그 결과값은 자동으로 %o0에 저장된다. 단 %o1에는 혹시나 Overflow된 값이 저장이 되므로 연산후 %o1의 값을 그대로 사용하면 절대 않된다. 나중에 연속으로 call을 사용하거나 %o0,o1 레지스터를 연속으로 다룰때 %o1의 값을 고려하지 않는다면 낭패를 볼것이다. 자세한 이유는 역시 Binary Arithmetic 부근에서 다시 언급하겠다.

그 다음에는 신기하게도 nop이라는 짧은 명령어를 볼 수 있을 것이다.
이 nop이라는 의미는 한칸을 쉬어간다라는 뜻이다.
어셈블러에서 공백인 줄은 그냥 아무의미없이 처리되지만(수행 칸으로도 보지 않고 무시하고 지나간다는 의미), nop을 사용하게 되면 한줄을 쉬었다가 넘어간다는 것을 말한다.
즉 한줄씩 코드가 실행이 될때, 아무 명령어도 실행하지 않고 그냥 nop으로 넘어간다면 비효율적이지 않을 수 없다.

그러나 이 nop은 분기문, 함수호출 즉 Branch나 jmpl시 반드시 사용하게 된다. (이유는 pipelining 부분에서 언급)
그러나 반복문같은 경우 nop이 많아지면 반복을 많이 할수록 점점 비효율이 되므로 큰 지장을 초래한다.
이를 대비하여 nop을 제거할 수 있는 방법이 여러가지 존재한다. (그 방법도 pipelining 부분에서 언급예정 ㅠ.ㅜ)
보통 코드를 짜다보면 처음에는 nop이 있는 상태로 프로그램을 짜고, 코드를 조금 씩 수정하면서 nop을 없애간다.
그러면서 효율성을 높여가는 것이다.

14, 15번 줄에서 mov 1, %g1             ta 0를 볼 수 있을 것이다.
이 두코드는 프로그램의 종료를 나타낸다.
ta를 trap service의 명령어이다. ta명령어는 %g1에 있는 요청된 숫자대로 OS에 전달해 준다.

 %g1   Service Request
 1  exit
 2  fork
 3  read
 4  write
 5  open
 6  close
 7  x
 8  create

위는 g1레지스터에 저장되는 값에 따른 요청 서비스이다.
가장 기본적인 종료 방법이다. 종료시에는 ret, restore도 사용하는데 그 부분은 다른 예문을 다룰때 알아보자.
그리고 마지막으로 중요한 개념은 어셈블러는 레이블 단위로 움직인다는 것이다.
소스에서 main: 이라는 부분이 있다.
여기서 main: 같은 부분을 레이블이라고 한다.
나중에 반복문이나 함수를 만들어 사용할 경우 이 Lable을 단위로 움직이고, 처리하기 때문에 이 레이블의 설정은 상당히 중요하다.
이 어셈블러는 다분히도 순차적으로만 명령어 코드가 해석되고 실행되므로 특정 부분을 반복하거나 이동하여야 할때 바로 Label단위로 이동하게 된다.

기본적인 코드를 분석해 보았다.
다음은 작성시 유의하여할 기본 개념에 대하여 알아보자.



※ 위코드를 컴파일하여 실행시키면 아무런 출력도 없다. 다만 결과 값만 레지스터에 저장되는데, 출력을 따로 설정해 주거나 - gdb를 이용하여 순간의 값을 확인하여 잘 돌아가는지 확인할 수 있다.