실습3
if( eax >0 && ebx < 10 || ecx >= 2){
 ebx =1;
} else{
 ebx = 0;
}

알고리즘
1. 비교 eax와 0 비교
: eax가 0보다 같거나작을경우 (eax <=0) ecx를 비교하러 가야함.
1-1. (eax <=0)
 ecx와 2 비교
: ecx가 2보다 같거나 크면 (ecx >=2) -> ebx = 1
아니면 ebx = 0
1-2. (eax >0)
-> ebx를 10과 비교
: ebx가 10보다 작으면 (ebx <10) -> ebx = 1
아니면 ecx 비교해야한다.
-> ecx 비교해서 2보다 크면 1
작으면 0

위의 C코드를 어셈블리 언어로 표현하면 아래와 같다.


(실습코드)



이 코드를 조금 더 간략하게 표현하자면 아래와 같이도 표현할 수 있다.



(실습 코드2)



그러면 우리가 만들었던 어셈블리 코드가 실제로 C코드를 컴파일한 코드와 비교해보자.



(C코드)



컴파일 후 어셈블리어 코드를 확인해보면



(어셈블리어 코드)



코드를 보면 우리가 만들었던 것과 조금 차이는 있지만 이 코드를 보고 우리가 어떤 식으로 동작하는지 이해할 수 있는 정도는 되었다.

이번에는 switch 문을 사용한 C코드를 컴파일한 어셈블리어를 if 코드와 비교해보겠다.



(switch문 사용)




컴파일 후 어셈블리어 코드 확인



(switch문)



코드를 보면 if문과 조금의 차이는 있지만 구조적으로 보면 jmp를 사용하기에
똑같다고 볼 수 있다.

이제 반복문을 알아보겠다.
반복문을 이용한 C코드를 컴파일해서 어떤식으로 이루어지는지 먼저 확인해보겠다.



(for문)



컴파일 후 실행해보면



(실행결과)



실행결과 우리가 생각하는 반복문 실행이 되는 것을 확인 할 수 있다.

이 코드를 어셈블리어를 확인해보면



(어셈블리어 코드)



반복문 또한 우리가 공부했던 jmp 분기문을 이용해 이루어지는 것을 볼 수 있다.

그렇다면 같은 내용을 while로 구현해보겠다.



(while 사용)




(어셈블리어코드)



어셈블리어 코드에서 보면 for문과 while 문의 차이는 없었다.
반복문은 jmp 분기문을 이용해 똑같이 만들어지는 것을 확인 할 수 있다.

그렇다면 직접 반복문을 만들어보자.!

아래와 같은 반복문을 만들것이다.
ebx = 0;
eax = 1;
while(eax <= 10){
 ebx += eax;
 eax++;
}


(실습 어셈블리어 코드)



분기문 하기에 앞서 사칙연산을 정리해보겠다.

사칙연산하는 C코드의 어셈블리어는 어떻게 구성될까?



(C코드)




컴파일 후 확인해본다!




(어셈블 코드)



저번 글에서 만들었던 형태가 비슷하게 나오는 것을 확인 할 수 있다.

오늘은 분기문을 공부 할 것이다.
jmp는 해당 주소로 이동하는 것이다.

이것을 이용하면 조건문도 만들 수 있고 반복문도 만들 수 있다.
실제로 C코드에서 if와 switch는 jmp 명령어로 이루어져있고
어셈블 차원에서 보면 똑같다.

사용 형식은
jmp addr
이런식으로 써준다.

그렇다면 jmp로 무한루프를 만들어보겠다.




(무한루프 코드)



jmp 명령에 의해 _start로 돌아간다.
_start는 레이블 이름으로 주소값이다. 컴파일 전에 주소값을 우리는 모르니 레이블 이름을 달아주어 사용한다.
컴파일한다.



(컴파일)



컴파일 후 실행해보면



(실행 모습)



실행되는 모습이다.

그렇다면 C코드의 기본적인 if문을 어셈블러로 구현해보자!
먼저 C코드의 기본적인 if문 코드이다.



(if문 코드)




(실행모습)



여기서는 조건 분기를 사용한다. 위의 jmp는 무조건 분기로 다른 조건없이 바로 해당 주소로 넘어가지만 조건분기는 조금 다르다.

조건 분기
 - EFLAGS 레지스터를 참조해서 분기할지 말지를 결정한다.
조건 분기문을 쓰기전에 cmp를 해줘야 EFLAGS에 기록하고 이걸 보고 조건문기문이
동작한다.

 - cmp

조건분기 종류를 살펴보면
 - je (jmp equal) = jz
 - jne (jmp not equal) = jnz
 - jl (jmp less)
 - jg (jmp greater)
 - jnl
 - jng
 - jle
 - jge
 - ja (jmp above) 초과
 - jb (jmp below) 미만
 - jna
 - jnb
 ...

3. cmp : 비교명령어
 cmp vleft vright
 -> 두개의 값이 같은지 비교
- vleft 값과 vright 값의 차를 구한다.
- 그 결과에 따라서 EFLAGS 레지스터의 플래그를 조절한다.
cmp에서 사용하는 flg
- ZF, SF
1) vleft - vright의 결과가 0인 경우
 : ZF = 1, SF=0 -> 두 값이 같다고 판단
2) vleft - vright의 결과가 음수인 경우
 : ZF=0, SF=1 -> 오른쪽이 더 크다.
3) vleft - vright의 결과가 양수인 경우
 : ZF=0,SF=0
조건분기문 사용 직전에 cmp를 해줘야한다.

이를 이용해 기본적인 if C코드를 어셈블러 코드로 구현하면 아래와 같다.



(조건분기문 사용)




(실행결과)



실행 결과 잘 나오는 듯하다.
(하지면 이 코드에는 문제점이 있다.)

값을 5보다 작게 2로 설정하여 확인해보겠다.



(작은 값 입력)




(실행결과)



실행결과를 보니 2가 작음에도 크다고 출력되었다.

왜 이럴까??
원인은 바로 점프하지 않고도 그 아래에 출력해주는 명령문이 있기 때문에 아래 코드가 순차적으로 실행 되었던 것이다. 이것을 막아주기 위해서는 아래 처럼 추가해주면된다.



(추가 코드)




(실행결과)



실행한 결과 작을 때는 이제 출력되지 않는 우리가 원하던 C코드의 동작을 하게 되었다.

다르게 표현을 하자면 jmp문 하나로도 완성 가능하다.



(다른 표현)



C코드를 컴파일 한 내용과 비교해보겠다.
상수끼리 비교하면 컴파일러가 알아서 계산을 해놓아서...
변수를 따로 선언을 해주었다.



(C코드 수정)



컴파일 후 어셈블러를 확인해보면



(어셈블 코드)



우리가 만든 코드와 비슷하게 나온 것을 확인 할 수 있다.

그렇다면 실습으로 아래와 같은 코드를 어셈블 코드로 만들어보자!



(실습 코드)



실습 결과는 아래와 같이 만들 수 있다.



(실습 결과)



jmp 문에 대한 내용이었다.




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

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 연산은 다음 글에서 이어서 써보겠다. :)



+ Recent posts