외로운 Nova의 작업실

Assembly 언어 공부 - 10(어셈블리어의 기본요소) 본문

Programming/Assembly

Assembly 언어 공부 - 10(어셈블리어의 기본요소)

Nova_ 2022. 5. 29. 15:50

안녕하세요. 저번 시간까지 tutorial을 마치고 1장에서 공부하려고했던 어셈블리어 책을 공부해보겠습니다.

항상 저는 책을 읽고 중요한 내용만 포스팅을 합니다. 따라서 책을 읽지않아도 해당 포스팅만 이해하신다면 큰 도움이 될실겁니다. 이번 10장에서는 어셈블리언어의 기본 구성요소에 대해 정리해보도록 하겠습니다.

 

<어셈블리언어의 기본 구성요소>

 

<정수상수>

기본적으로 어셈블리어에는 c에서와 마찬가지로 정수 상수를 가지고있습니다.

예를 들어 1, 3, 5와 같습니다.

또한, 진수를 표기하여 해당 숫자가 무슨 진법을 사용하는지 알수 있습니다.

아래는 진법에대한 표기방법입니다.

h 16진수(Hexdecimal)
q/o 8진수(Octal)
d 10진수(Decimal)
b 2진수
r 부호화실수
t 10진수
y 2진수

예시를 한번 들어보도록 하겠습니다.

0A3h - 16진수 A3입니다.

42q - 8진수 42입니다.

11010011b - 2진수 11010011입니다.

26d - 10진수 26입니다.

23 - 10진수 23입니다.

//진법 표시가 아무것도 없는 경우 10진수 입니다.

//또한 16진수 표기할때, 맨앞자리가 문자이면 식별자로 해석되지않게 0을 넣어줍니다.

 

<산술 연산자>

이번에는 산술 연산자에대해서 정리해보도록 하겠습니다.

연산자 이름 우선순위
() 괄호 1
+,- 단항+, 단항= 2
*,/ 곱셈, 나눗셈 3
MOD 나머지 3
+,- 덧셈, 뺄셈 4

예시를 들어보겠습니다.

괄호 : (2+3)

단항 +,- : 3++, 2--

곱셈 나눗셈 : 3*2, 3/1

나머지 : 7 MOD 3 

덧셈,뺄셈 : 3+1, 3-2

우선순위같은경우엔, c언어를 아시는분들은 다 아실 거라고 생각합니다.

 

<실수 상수>

이번에는 실수 상수에대해서 정리해보도록 하겠습니다.

c언어에서 마찬가지로 정수부분과 소수점은 필수사항입니다.

예시를 들어보겠습니다.

2.

+3.0

-44.2E+05

26.E5

 

<문자, 문자열 상수>

이번에는 문자 상수와 문자열 상수를 정리해보도록 하겠습니다.

c언어와 마찬가지로 문자 상수의 경우 '(작은따옴표)나"(큰따옴표)로 둘러싸인 문자입니다.

예시) 'A' , "q"

 

문자열 상수는 따옴표로 둘러싸인 문자입니다.

예시) 'ABC', "this is a test"

 

<예약어>

예약어란 c언어에서와 같게 이미 특별한 목적이 있어서 정해놓은 것들입니다.

c언어에서는 int나 double 등이 있었죠.

어셈블리어에서 예약어의 종류는 총 6가지가 있습니다.

1. 명령어 니모닉(mnemonucs) : MOV, ADD, MUL 등의 명령어

2. 레지스터 이름 : EAX, ECX등

3. 디렉티브(directive) : MASM에게 프로그램을 어떻게 어셈블하는지 알려줌 ex)include

4. 속성 : 변수와 피연산자의 크기와 사용정보를 제공함, ex) BYTE, WORD등

5.연산자 : 수식에 사용됨

6. 미리 정의된 기호 : 어셈블할때 상수 정수 값을 반환하는 @data와 같은 기호

 

<식별자>

식별자란 프로그래머가 변수, 상수, 프로시저, 코드 레이블등에 붙이는 이름입니다.

ex) main proc - main이란 식별자를 가지고있는 프로시저입니다.

 

<레이블>

레이블이란 명령어 또는 데이터의 위치를 표시하는 식별자입니다.

결국 모든 식별자들은 어셈블러가 주소값으로 바꾸게됩니다.

예를들어 아래와 같은 주소에 코드가 있다고 했을때,

0x00000012 count DWORD 100(0x00000012주소에 100을 저장하고 레이블은 count로 함과 같음)

아래와 같이 사용이 됩니다.

0x00000022 add eax, count 

이 코드는 어셈블러가 아래와 같이 바꿉니다.

0x0000022 add eax, 0x00000012

이를 컴퓨터가 인식하면 0x000000012 주소로가서 값을 빼온다음에 eax에 더히게됩니다.

또한 위처럼 데이터의 레이블도 있지만 코드 자체에 레이블을 둘 수 도 있습니다.

아래와같이 사용됩니다.

0x00000012 start : ~~~~

~~

0x00000030 ENDstart

나중에 start를 사용하면 어셈블러가 0x00000012로 바꾸게됩니다.

 

<세그먼트>

세그먼트란 프로그램을 크게 나누는 하나의 공간입니다.

예를 들어 data 세그먼트는 프로그램에서 사용하는 data들이 모여있는 공간입니다.

결국 이 공간들은 0x00000000 ~ 0x00001212 까지 이런식으로 표현할 수 있습니다.

또한 이러한 세그먼트는 디렉티브를 사용하여 구분하게됩니다.

.data는 프로그램의 데이터 영역을 표시합니다.

.code는 프로그램의 실행 가능한 명령어를 포함하는 영역을 표시합니다.

.stact은 프로그램의 스택의 크기를 설정하면서, 실행 스택을 가진 프로그램 영역을 표시합니다.

 

<주석>

주석을 쓰는 방법에대해서 알아보겠습니다.

한줄 주석 : c언어에서는 //를 사용하지만 어셈블리어에서는 ;을 사용하며 세미콜론 뒤에 있는 같은 줄의 모든 문자를 무시합니다.

블록 주석 : COMMENT 디렉티브와 사용자 정의 기호로 시작되며 어셈블러는 같은 사용자 정의 기호가 나타날때까지 이어지는 모든 줄을 무시합니다. 예시는 아래와 같습니다.

COMMENT ! 

this line is a comment.

!

 

<NOP 명령어>

NOP명령어란 1바이트 프로그램 저장공간을 차지하며 아무런 동작을 하지않는 명령어입니다.

NOP명령어는 때때로 코드를 짝수 주소 결계로 정렬시키기위해서 사용합니다.

예시는 아래와 같습니다.

0x00000000 66 8B C3 ;mov ax, bx

0x00000003 90 ; NOP명령어

0x00000004 4B D1 ; mov edx, ecx

 

<정수의 덧셈과 뺄셈 예제>

이제 배운것들을 사용해서 덧셈과 뺄셈을 하는 예제를 만들어보겠습니다.

;this program add and subtract 32-bit integer.

.386
.model flat, stdcall
option casemap :none

include \masm32\macros\macros.asm

; -----------------------------------------------------------------
; include files that have MASM format prototypes for function calls
; -----------------------------------------------------------------
include \masm32\include\masm32.inc

; ------------------------------------------------
; Library files that have definitions for function
; exports and tested reliable prebuilt code.
; ------------------------------------------------
includelib \masm32\lib\masm32.lib
includelib \masm32\lib\kernel32.lib


.code
    main proc

    mov eax, 10000h ;eax = 10000h
    add eax, 40000h ;eax = 50000h
    sub eax, 20000h ;eax - 30000h

    print str$(eax)
    
    main ENDP ; tell MASM end of main proc
END main ;tell MASM end of code and start main

위 코드는 간단하게 eax 레지스터를 이용해서 덧셈과 뺄셈 연산을 하는 코드입니다.

print 명령어는 macros.asm에 메크로 되어있고, 이는 masm32.lib에서 돌아갑니다. 또한 masm32.lib이 돌아가기위해선 kernel32.lib이 필요합니다. 만약 kernel32.lib을 포함하지않고 masm32.lib과 macros.asm만 포함하게된다면 아래와 같은 오류문구가 뜹니다.

이 문구를 본다면 kerneal32.lib을 포함해주시면 됩니다.

또한 print 명령어 다음에 chr$, str$을 쓸 수있는데, chr$디렉티브 같은경우 문자열을 받아서 출력하고, str$ 디렉티브 같은경우 정수를 받아서 10진수로 변환한다음 문자열 형태로 출력합니다.

예를들어 print chr$("hello") 를 쓰면 콘솔에 hello가 출력됩니다.

print str$(20h)를 출력하면 32를 출력합니다.(16진수 20은 10진수 32와 같으니까..)

그래서 해당 코드를 실행시켜보면,

16진수로 30000인 10진수 196608이 출력됩니다.

 

<데이터 정의>

이번에는 자료형에대해서 알아보겠습니다.

자료형 용도
BYTE 8비트 부호없는 정수
SBYTE 8비트 부호있는 정수
WORD  16비트 부호없는 정수
SWORD 16비트 부호있는 정수
DWORD 32비트 부호없는 정수
SDWORD 32비트 부호있는 정수
FWORD 64비트 부호없는 정수
QWORD 4비트 정수
TBYTE 80비트 정수
REAL4 32비트 IEEE 짧은 실수
REAL8 64비트  IEEE 긴 실수
REAL10 80비트 IEEE 확장 실수

아래는 구형 데이터의 자료형입니다.

자료형 용도
DB 8비트 정수
DW 16비트 정수
DD 32비트 정수 또는 실수
DQ 64비트 정수 또는 실수
dt 80비트 정수를 정의

<DUP연산자>

DUP연산자는 여러개의 저장공간을 할당하는 연산자입니다.

예시부터 봅시다.

BYTE 20 DUP(0)

위 명령은 1바이트 크기의 변수를 20개 만들고 0으로 초기화하라는 명령줄입니다.

또한 다양하게 응용도 가능합니다.

BYTE 20 DUP(?) ;1바이트 크기의 변수를 20개 만들고 초기화하지말라는 명령줄입니다.

BYTE 20 DUP("STACK") ; 1바이트 크기의 변수를 20개만들고 STACK으로 초기화하라는 명령줄입니다.

마지막 STACK의 경우 STACKSTACKSTACKSTACKSTACK 이렇게 20바이트가 초기화됩니다.

또한 여러개의 초기화경우, 주소는 연속됩니다.

예를들어

0x00000000 BYTE 20 DUP("STACK")이라면

0x00000001 'S'

0x00000002 'T'

0x00000003 'A'

0x00000004 'C'

0x00000005 'K'

.

.

.0x00000020 K 이런식으로 저장됩니다.

만약 BYTE 크기가 아닌 BYTE보다 2배 더큰 WORD 크기라면, 주소가 2씩 띄어집니다.

예를들어,

0x00000000 DOWRD 20 DUP("STACK") 이라면

0x00000001 'S'

0x00000003 'T'

0x00000005 'A'

.

.

.0x0000039 'K' 이런식으로 저장이됩니다.

 

<기호 상수>

기호 상수란 변수와 달리 실행시에 값이 변하지 않는 상수를 말합니다.

기호상수를 만드는 여러가지 방법에대해 알아봅시다.

 

등호 디렉티브

=(등호)기호를 사용하여 만들수 있습니다.

아래는 예시입니다.

count = 500

mov eax, count

위 처럼 쓰게되면, 어셈블러(전처리단계에서)는 아래와 같이 마지막 문장을 바꿉니다.

mov eax, 500

기호상수를 쓰게되면 해당 값이 여러개일 경우 바꾸기 편합니다.

 

배열과 문자열의 크기계산

배열과 문자열을 손쉽게 계산할 수 있는 법을 알아봅시다.

먼저 현재 위치 카운터 $에대해서 설명해보겠습니다.

$기호는 현재 주소값을 반환합니다.

예를들어, 0x00001234 eax, $ 를하게되면 eax엔 00001234가 들어가게됩니다.

이를 이용하여 list 배열의 크기를 계산해보겠습니다.

list BYTE 10, 20, 30, 40

ListSize = ($ - list)

위와 같이 list 배열의 크기를 쉽게 계산할 수 있습니다.

하지만 list배열의 원소의 크기가 WORD라면 ListSize의 값이 달라집니다.

List WORD 10, 20, 30, 40

ListSize = ($ - List) / 2

위처럼 2로 한번 나눠줘야합니다. 만약 DWORD라면 4로 나눠줘야합니다.

그 이유는 WORD는 2바이트 DWORD는 4바이트이기 때문입니다.

 

EQU 디렉티브

EQU디렉티브는 기호를 정수 수식이나 임의의 텍스트와 연관시킵니다.

사용법에는 3가지종류가 있습니다.

한번 알아보겠습니다.

먼저 수식으로 사용할 수 있습니다.

PI EQU 3 * 10

이렇게 쓰게되면 어셈블러(전처리 단계)는 PI를 30으로 모두 변환 시킵니다.

 

두번쨰로는  symbole로 사용할 수 있습니다.

StdOut proc 

StdOut ENDP

print EQU StdOut

위와 같이 StdOut이 레이블과 같은 symbol 일때, 어셈블러(전처리단계)는 print를 StdOut으로 변환시킵니다.

 

세번째로는 문자열로 사용할 수 있습니다.

PI EQU <3 * 10>

print chr$(PI)

위 문장을 실행시키면 3 * 10이 출력될 것입니다.

 

또한 EQU로 정의된문장은 정의 문장을 제외하고 어떠한 문장으로도 재정의 할 수 없습니다.

예를들어 

PI EQU 3*10 을 했다면 그다음에

PI EQU 410 은 안된다는 뜻입니다.

 

TEXTEQU 디렉티브

TEXTEQU 디렉티브는 EQU와 비슷하지만 쓰임이 조금 다릅니다.

아래의 예제를 통해서 알아보겠습니다.

rowSize = 5

count TEXTEQU %(reowSize * 2)

move TEXTEQU <mov>

setupAL TEXTEQU <move al, count>

그러므로 다음문장은

setupAL

다음과 같이 어셈블 될 것입니다.

mov al,10

 

또한 TEXTEQU는 정의한것을 또다시 정의하는 구문에 사용할 수 있습니다.

move TEXTEQU <mov>

move2 TEXTEQU <move>

이런식으로 말입니다.

만약 move2를 사용하면 어셈블러(전처리기)는 mov로 바꿀것입니다.

또한 TEXTEQU는 EQU와 다르게 정의문장을 포함하여 다른 문장으로도 정의를 바꿀 수 있습니다.

 

이음줄 기호

어셈블리어에서 다음과 같은 문장은 줄이음 기호 \를 사용하여 동일한 문장입니다.

1. mov eax,3

2. mov \  

         eax, 3

 

이로써 어셈블리언어의 기본요소를 마치겠습니다.

다음시간에는 데이터 전송과 주소 지정과 산술연산에대해 정리해보도록 하겠습니다.

Comments