오늘은 어셈블리로 사칙연산 명령어를 공부할 것이다.

1. 덧셈
명령어 : add
형식
add     dst,     src
:dst에 더한값을 저장한다.
dst에는 레지스터, 메모리가 올 수 있고
src에는 레지스터 메모리 상수가 올 수 있다.

*주의 한번에 두개의 메모리를 참조할 수 없다.
ex) add 메모리, 메모리
(이건 mov도, 기타등등 여러 명령어에서도 마찬가지이다.)

그러면 add명령어를 사용해보겠다.



(add 코드)



eax에 2를 저장하고 add 명령어를 이용해 3을 더했다.

실행하게되면


(실행화면)



결과는 5가 나온다.

이번에는 레지스터가 아닌 메모리를 사용해 덧셈을 해보겠다.



(메모리 사용)



sum 주소에 2를 저장하고 3을 더해주었다.
대신 주소값을 쓰는게 아니라 주소에 있는 데이터값을 쓰는것이므로 
[ ] 브라켓을 씌워준다.


(실행결과)



실행 결과 역시 5가 나왔다.

2. 뺄셈
명령어 : sub
형식
sub     dst,     src
내용은 덧셈과 같다.

그렇다면 뺄셈을 사용해보겠다.



(뺄셈 코드)



(실행화면)



실행화면을 보면 10-5의 결과로 5가 제대로 나온 것을 볼 수 있다.

* 실습 문제
 - 다음과 같은 두 개의 값의 덧셈 결과와 뺄셈 결과를 출력
segment  .data
 num1     dd     20
 num2     dd     10



(실습 코드)




코드를 보면 알겠지만 조금 오바해봤다.

실제 계산부분만 보면 되겠다.
실행해보면



(실행결과)




덧셈과 뺄셈이 잘 되는 걸 확인 할 수 있다.

이번에는 덧셈은 레지스터, 뺄셈은 (꾸이꾸이) 메모리를 사용해 해보겠다.



(실습 코드2)




(실행결과)



실행 결과가 같다.

이번에는 주소값과 주소에 있는 데이터와의 차이를 보겠다.

아래의 코드를 먼저 본다.



(어셈블 코드)



위에는 그냥 num 아래는 [num] 을 사용했다.
실행하여 결과를 확인해보자.



(실행결과)



실행 결과를 보니 그냥 num을 쓴 것은 주소값이 출력되었고
[num]은 그 주소에 있는 데이터 10의 값이 출력되었다.

여기서 이제 mov와 lea를 비교하면서 lea가 뭔지 살펴보겠다.
lea는 mov와 거의 비슷하다. 다만 조금 다른점이 있는데 직접 눈으로 확인해보겠다.

아래에 lea와 mov를 추가해서 확인해본다.



(mov와 lea 추가)




(실행결과)




실행 결과를 보니 mov로는 [num]이기 때문에 우리가 알던대로
num의 주소에 있는 데이터인 10이 출력되었다.

근데 이상한점이 있다. lea로 [num]을 넣어주었는데
주소값이 출력되었다.
바로 이게 차이점이다.
lea는 [ ] 브라켓을 꼭 써야하고
그 안에있는 값 그대로 출력해준다. 그래서 num의 주소값이 그대로 출력된 것이다.

바로 이 점을 이용해 덧셈을 할 수 있는데
아래 코드에서 마지막 부분을 보면 된다.



(lea를 통한 덧셈)




(실행 결과)



실행 결과를 보면 14로 덧셈이 잘 된것을 확인 할 수 있다.
lea eax, [ebx+4]
이렇게 하면 우리가 ebx에 10을 넣어주었으니 ebx는 10이되고 10+4로 14의 값이 그대로 전달되는 것이다.

3. 곱셈
명령어 : mul, imul
형식
mul     피연산자
-> 부호없는 곱셈

imul 피연산자
 imul 피연산자, 피연산자, 피연산자
 -> 최대 3개까지 올 수 있다.
 -> 부호 있는 곱셈

mul 는 a 레지스터를 쓰게 되어있다.
무조건 a레지스터에 있는 값과 곱하게 된다.

그렇다면 곱셈을 사용해보자!



(곱셈 코드)



(실행화면)



실행화면을 보면 곱셈의 결과 6이 나온 것을 확인할 수 있다.

이제 곱셈의 이상한?점을 볼 차례이다.
바로 사이즈가 커진다는 것인데
곱셈은 결과가 원래 사이즈를 넘어갈 수 가 있다.
그렇기 때문에 결과를 큰 사이즈에 저장하게된다.
위에서도 보다시피 1바이트 곱셈인데 결과는 우리가 eax를 푸쉬해서 출력했다.

아래처럼 255로 꽉채워서 곱셈을 하게되면 사이즈가 초과되기 때문에 결과는 eax로 저장이 된다.



(초과 곱셈)



(실행결과)



그렇기 때문에 곱셈의 결과가 정확하게 나올 수 있는 것이다.

그러면 이제 imul을 사용해보겠다. 이 명령어는 부호가 있는 곱셈이라고 했다.
부호가 있을때 사용하라는 말이 아니라 비트중 MSB를 인식한다는 뜻이다.

우리가 255 255 곱셈을 하게되면 어떻게 될까? 왜냐하면 MSB가 모두 1이기 때문에 궁금하다.



(imul)



(실행결과)



실행결과 255를 -1로 인식하여
-1 곱하기 -1 을 하여 1이 나왔다.

imul은 mul처럼 사용할 수 도 있지만
인자를 2개 3개까지 받을 수 있는데 살펴보겠다.

2개를 쓸 때



(2개 사용 코드)




(실행 모습)



2개를 쓸 때는 이렇게 사용한다.

3개를 쓸 때는?



(3개 사용)




(결과)



2번째 3번째 곱셈의 결과를 1번째 인자에 저장한다.

곱셈은 결과가 사이즈가 초과할 수 있어 그보다 더 큰 레지스터에 저장한다고 했다.
그렇다면 eax 4바이트 단위의 곱셈은 어디다 저장이 되는 걸까?

해보자!



(초과 결과 코드)



(실행 모습)



eax를 출력해보니 뭔가 완전하지 않은듯한 느낌이든다.

원래 곱셈의 결과라면
0x11111111  *  0x22222222 는 아래와 같다.



(결과)



그렇다면 앞 부분은 어디갔다는 말인가?!

바로 edx에 저장된다.
edx도 함께 출력해보겠다.



(edx 추가 출력)




(실행화면)



실행화면을 보면 eax와 edx에 나누어서 결과가 저장이 된 것을 확인 할 수 있다.

나머지와 mod 연산은 다음 글에서 이어서 써보겠다. :)




어제까지 배운 지식으로 토끼모양을 출력하는 프로그램을 어셈블 언어로 작성해보자!

코드는 다음과 같다.



(어셈블 코드)




(출력 결과)



오늘은 레지스터에 대해 알아볼 것이다.

* 레지스터



1. 범용 레지스터

-> 이름이 범용이 들어가는 것 처럼 이곳저곳에서 많이 쓰이는 레지스터이다. 레지스터의 크기에 따라 명칭이 달라진다.


레지스터 크기 1, 2, 4, 8 바이트
8바이트 : RAX, RBX, RCX, RDX
4바이트 : EAX, EBX, ECX, EDX, ...
2바이트 : AX, BX, CX, ...
1바이트 : AH, AL, BH, BL, CH, CL ...


종류가 다르기보다는 크기를 나타내는 명칭이라고 생각하면된다. 같은 레지스터의 영역이다.


내용을 확인할 수 있는 프로그램을 만들면서 확인해보겠다.



(어셈블 코드)



위 코드는 레지스터 eax에 0101010101010101 이 들어간 값을 출력하는 프로그램이다.
레지스터의 내부구조를 살펴보기위해 만든 코드이다.
출력은 16진수로 표현된다. 그러므로 출력결과는 55555555 로 될것이다.



(출력 결과)



출력 결과가 55555555이 나왔다. 여기까지는 크게 다른 내용은 없다.
계산기에서 55555555을 입력하여 비트단위로 보면 0101010101010101인것을 확인 할 수 있다.



(55555555 (16))



여기서 eax 그대로에 ax를 사용하려 했으나 eax가 여기서 함수호출로 인해 사용되므로 ebx로 바꿔서 계속 진행해보겠다.
ebx 레지스터에 55555555을 넣고 bx 레지스터에 2222를 넣었다.
그리고 ebx를 출력해보면 출력결과가 어떻게 나올까?



(어셈블코드)



그 내용을 담은게 위의 코드이다.
실행해보겠다.



(실행결과)




(비트단위)



ebx 결과를 보면 ebx 내부 2바이트가 2222로 덮여쓰여진 것을 볼 수 있다.

계속해서 1바이트를 표현하는 bl, bh를 사용해보겠다. 이 둘은 상위1바이트 하위 1바이트를 가리키는 레지스터이다. ebx는 그대로 사용한 채 bh에 44 bl에 33을 넣어보겠다.



(어셈블코드)



실행하게 되면



(실행 모습)




(실행결과)




상위 1바이트는 44로 표현이 되었고 하위 1바이트는 33으로 표현되었다.

이렇게 범용레지스터는 크기를 나타내는 용어가 다르다. 즉 1바이트를 사용하고싶거나 2바이트, 4바이트 이렇게 사용하고 싶은 크기에 따라 다른 레지스터를 사용하면 된다.

2. 포인터 레지스터
 - 주소를 표현하는 레지스터이다. 이 레지스터는 용도가 분명하게 있다.
 - 용도가 정확하게 정해져 있기 때문에 다른용도로쓰면 세그먼트 폴트등 오류가 난다.



1) 스택 메모리에서 사용되는 레지스터
 - EBP ( Extended Base Pointer )
 - ESP ( Extended Stack Pointer )
 - EIP ( Extended Instruction Pointer ) : 다음 명령을 가리키는 주소


2) 문자열 복사등에 사용되는 레지스터
 - ESI ( Extended Source Index )
 - EDI ( Extended Destination Index )
-> 다른용도로 쓰이기도한다.. 중요성이 덜해서 다른용도로 쓰기도한다.
 
3) 플래그 레지스터
 - EFLAGS
 0: CF (Carry Flag ) : 올림수가 발생한 경우 비트가 1로 셋팅된다.
 6 :ZF ( Zero Flag ) : 연산의 결과가 0인 경우 비트가 1로 셋팅된다.
 7 : SF ( Sign Flag ) : 부호가 발생한 경우(음수) 비트가 1로 셋팅된다.
 11 : OF ( Overflow Flag ) : 오버플로우가 발생한 경우 비트가 1로 셋팅된다.
-> 나중에 디버거에서 직접 확인해보겠다.



+ Recent posts