※ 질문/내용오류/공유할 내용이 있다면 jinkilee73@gmail.com으로 메일 주세요 :-)


이번 포스팅에서는 레지스터와 메모리를 통해 어떻게 데이터를 표현하는지부터 실제로 어셈블리언어에 존재하는 명령어들을 공부할 예정이다. 


명령어를 통해서 연산을 하다보면 사용할 데이터들이 메모리에 있을 때도 있고 레지스터에 있을 때도 있을 것이다. 각각의 경우, 어떤 식으로 접근하는지 살펴보자.


레지스터에 접근하는 방법은 매우 간단하다. 그냥 해당 레지스터의 이름을 써주면 된다. 가령


add        %eax, %ebx

이와 같은 명령어가 있다면, %eax에 있는 값을 %ebx의 값과 더해서 %ebx에 저장하라는 명령어가 된다. 레지스터의 값에 접근하는 것은 이와 같이 매우 쉽다. 그냥 적기만 하면 되니까. 그렇다면 메모리의 값들은 레지스터를 통해서 어떻게 접근할까?


정답은 괄호를 통해서 접근할 수 있다. 가령 다음과 같이 예를 들어보자.


add        (%ebx), %eax


위의 명령어는 다음과 같은 뜻이다. %ebx가 가리키는 값에 %eax값을 더해서 %eax에 저장하라는 뜻이다. 따라서 %ebx 자체에는 가상메모리 주소 값이 저장되어 있다는 뜻이 된다. 어려운가? 그림으로 설명해보겠다. 다음과 같다.



결국 포인터 개념이다. %ebx에 주소값이 저장되어있어서 %ebx가 가리키는 곳의 값을 쓰겠다는 것이다. 이 add 명령의 결과는 11이 된다. %ebx가 가리키는 주소가 가지고 있는 값이 8이고 %eax가 가지고 있는 값이 3이고, 그 두 개를 더해서 %eax에 더해주는 것이니까 %eax는 11로 업데이트 된다. 


다음과 같이 괄호를 쓸 수도 있다.



M[ %eax + %ebx ]는 주소값 %eax + %ebx 이 가리키는 값을 의미한다. 또한 괄호에서 3번째 인자 값이 있다면 그것은 두번째 인자값에 곱하라는 뜻이다. 헛깔리지 말아야할 것 중에 하나가 $Imm와 Imm의 차이이다. (Imm는 변수가 아닌 상수를 의미한다. 즉, 변하지 않는 고정젹인 숫자를 의미한다.) $가 붙으면 일반 상수를 의미하지만, 없으면 그 값이 가리키는 메모리 값이 된다. 헛깔리지 말도록 하자.


이제 본격적으로 실제 어셈블리 언어에 대해서 이야기 해보자.

먼저 data movement를 위한 명령어로 mov가 있다. 하지만 실제로는 mov가 아닌 movl과 같이 뒤에 무언가가 붙어서 사용되곤 한다. 그 의미는 무엇일까? 바로 다루는 데이터의 크기를 의미한다.



밑에 보면 push 또는 popl 명령어도 있는데 이는 그냥 무시하자. 나중에 매우 자세하게 공부할 예정이다. 위의 명령어에서 가장 자주 쓰이는 것은 아무레도 movl이 아닐까 싶다. 기본적으로 word 사이즈의 데이터를 가장 많이 사용하지 않을까? 그렇기 위해서는 당연히 movl 명령어를 사용해야한다. 


그림의 왼쪽을 자세히 봐보자. MOV    S, D라고 되어있다. source와 destination을 의미한다. 출발지와 도착지. 즉, 출발지에 해당하는 데이터의 값을 도착지에 복사하라는 뜻이다. 따라서 도착지에 해당하는 값이 업데이트가 된다는 뜻이다. 


※ 프로그래밍 언어도 여러 가지가 있듯이, 어셈블리언어도 종류가 여러 가지가 있다. MIPS Architecture 같은 어셈블리언어에서는 출발지와 도착지가 반대이다. 매우 혼동될 여지가 있으니 조심하기 바란다. 이 블로그에서는 특별한 이야기가 없다면 무조건 도착지가 뒤에 온다.


이제는 산술연산 명령어를 살펴보기로 하자. 



우선 leal 명령어를 살펴보자. leal 명령어는 mov명령어와 헛까릴수 있으니 조심해야 한다. 예를 들어 설명하는게 젤 빠를 것 같다.


위와 같은 상태에서 아래의 명령어의 차이를 이해해보자.


leal        (%ebx), %eax

movl       (%ebx), %eax


movl       (%ebx), %eax는 %ebx가 가리키는 값(8)을 %eax에 복사하기 때문에 %eax의 값은 8로 업데이트 된다. 

반면, 

leal        (%ebx), %eax는 그냥 %ebx의 값이 %eax에 복사되기 때문에 %eax의 값은 0x8048040으로 업데이트 된다.


leal 명령어의 이러한 특성 때문에 leal 명령어는 실제로 산술연산을 효율적으로 끝내버리는데 사용하기도 한다. %eax에 3이라는 값이 들어있을 때, 다음의 명령어를 보자.


leal        (%eax, %eax, 4), %ebx


이는 %eax의 값(3)을 5배 해서 %ebx에 저장하라는 명령어가 된다. leal 명령어의 특성상 %eax의 실제 값을 접근하기 때문에(%eax가 가리키는 값이 아닌) 


%eax값 + %eax값*4 = %eax값 * 5


이와 같은 계산이 가능해지고 %eax가 3이기 때문에 결국 하나의 산술연산을 하게 된 샘이 된다. 왜 굳이 add 명령어나 imul 명령어를 사용하지 않는가? 왜 일까? 훨씬 빠르기 때문이다. 기본적으로 imul 명령어는 곱하기 명령어인데, CPU가 이 명령어를 실행하려면 몇 clock cycle을 소모한다. 반면 add명령어는 1 clock cycle을 소모하지만 5번이나 더해야 하기 때문에 결국 오래 걸리기는 매한가지이다. 반면 leal 명령어는 단 1 clock cycle에 끝낼 수 있다. 따라서 빠른 정수 연산이 가능해진다.


곱하기 5를 하는 연산은 다음과 같이 수행할 수 도 있다. C언어로 표현해보자.


x = x<<2 + x


이 값 역시 x에 곱하기 5를 한 셈이 된다. 왜냐하면 x<<2가 결국 x*4와 같기 때문이다. 이것이 이해가 안가면 2진수를 다시 공부하면 된다. <<k 는 k만큼 left shift 하라는 의미인데 한 칸 left shift하는 효과는 결국 그 숫자의 두배를 하라는 효과이기 때문에 2칸 left shift 하는 것은 4배 하라는 의미가 되기 때문이다. 거기에 원래의 x를 한번만 더 더해주면 결국 5*x가 되어버린다. 곱하기 명령어를 수행하면 몇 cycle이 걸리는 것을 shift 연산을 사용하면 2 clock cycle(shift가 한 cycle, add가 한 cycle) 만에 끝낼 수 있다. 즉 빠르다.


shift 연산을 통해 조금 더 빠른 곱셈 연산이 가능하다는 것 정도는 알아두자.


물론 leal 명령어를 통해 계산하는 것이 가장 빠르다. 실제로 요즘 컴파일러들은 level 01 optimization에서도 저 정도의 최적화는 수행한다고 한다.


나머지 add, sub, sal 등의 명령어는 그냥 각자 공부해보자. 저 위의 표만 잘 봐도 충분히 이해 가능하다고 생각해서 굳이 따로 설명하지는 않는다.


다음 포스팅에서는 지금까지 배운 명령어들을 예제를 통해서 활용하면서 공부해보도록 하자.

'System Programming' 카테고리의 다른 글

[SP] Procedure Call  (0) 2013.05.04
[SP] Assembly Language Example  (0) 2013.04.30
[SP] Assembly Language Basic  (0) 2013.04.29
[SP] Purpose for Virtual Memory  (0) 2013.04.29
[SP] Virtual Memory  (3) 2013.04.29
Posted by 빛나유
,