IT_Programming/Assembly

[전광성의 어셈블리어 이해하기] 어셈블리언어 기초

JJun ™ 2007. 7. 2. 10:30
LONG

 

 

  • 레이블(Label)

    레이블이란 인스트럭션이나 데이터의 위치를 표시해 주는 것이다. 인스트럭션의 위치를 표시해 주는 것이라는 설명에 대해서는 C에서 goto문을 생각하면 가장 쉬울 것이다. C에서는 특정 문장 앞에 L1: 과같이 쓰고, 나중에 그 위치로 옮겨가고 싶을 때 goto L1;이라고 썼다. 어셈블리에서는 jmp이나 loop와 같은 인스트럭션을 쓸 때 사용하게 될 것이다. 이러할 때 쓰이는 레이블을 코드 레이블(Code Label)이라고 한다.

    데이터의 위치를 표시해 준다는 것은 무엇일까? 바로 변수의 이름이다. 변수의 이름이란, 데이터 영역에 일종의 레이블을 붙여 그 위치를 기억했다가 그 변수를 사용할 때 아까 기억해놓은 레이블을 이용해서 그곳에 값을 저장하거나 불러오는데 쓰인다.

    만약 데이터 레이블이 없다면 우리는 특정 주소에 변수를 사용하기로 약속하고는 그 주소를 계속 외우고 다녀야 할 것이다. 또 그 변수의 역할, 갖게될 값의 형태까지 기억하려면 얼마나 불편한지 짐작이 갈 것이다. 이렇게 데이터의 위치를 기억하는 레이블을 데이터 레이블(Data Label)이라고 한다.

  • 인스트럭션 니모닉(Instruction Mnemonic)

    아까 인스트럭션 니모닉에 대해 잠시 언급한 바가 있다. 좀더 자세히 설명해 보자면, Mnemonic의 사전적 의미는 "기억을 도와주는 장치"이다. 1회에서 필자가 말했던 것 처럼 CPU에게 내릴 명령코드를 의미있는 글자로 치환시켜주는 것이 어셈블리어의 기본 모토라고 하였다. 복잡한 명령 코드 대신 "기억을 도와주는 것"이 바로 이 니모닉이다. 대표적인 것으로 mov, add, sub와 같은 것이 있다. mov는 move의 약자이고, sub는 subtract의 약자이니까 어떤 일을 하는 인스트럭션인지는 짐작이 갈 것이다.

  • 주석(Comment)

    주석은 코드 자체가 수행할 일들과는 무관하지만, 보는 이로 하여금 좀더 쉽게 이해할 수 있도록 도와주는 것이다. 즉, 이부분에서 어떤 일을 하는지, 주의할 점은 무엇인지 등이다. 특히 어셈블리어는 프로시져가 어떤일을 하는지, 매개변수는 어떤 것을 받는지, 리턴값은 어떤지에 대해 주석을 달지 않으면 해석하기가 정말 힘들다. 게다가 매개변수로 레지스터 메모리를 사용하기라도 한다면 더더욱 이해하기 힘들어진다.

    그래서 주석은 아무리 강조해도 지나침이 없다. 아까 설명했듯이 대표적인 주석은 세미콜론(;)인데, ;로 시작해서 그 줄의 끝까지가 주석이 된다. (이것을 가장 많이 쓰게 될 것이다.) 또다른 형태의 주석은, C에서 /* */와 같이 일종의 블럭안을 모두 주석으로 처리해 주는 것이다. 이는 다음과 같이 사용할 수 있다.

        COMMENT    ! 
               This line is a comment. 
               Welcome to Internet.com 
        !
    

    !대신에 &와 같이 사용자가 하고싶은 기호를 사용해도 된다. 이렇게 하면 COMMENT  ! 와 !로 둘러싸인 부분이 모두 주석이 된다.

  • 자료 정의하기

    자료를 정의하는 방법을 배워보자. 쉽게 말해서 변수를 만드는 것이다. 그러기 위해서는 자료형을 알아야 한다. C에 int, long, float, double이 있는 것처럼, 어셈블리어에도 자료형이 있다. 다음의 표에 열거해 놓았으니 참고바란다.

     Type

    Usage

     BYTE

    8비트 부호없는 정수

     SBYTE

    8비트 부호있는 정수

     WORD

    16비트 부호없는 정수

     SWORD

    16비트 부호있는 정수

     DWORD

    32비트 부호없는 정수

     SDWORD

    32비트 부호있는 정수

     FWORD

    48비트 정수

     QWORD

    64비트 정수

     TBYTE

    80비트 정수

     REAL4

    IEEE표준의 32비트 실수

     REAL8

    IEEE표준의 64비트 실수

     REAL10

    IEEE표준의 80비트 실수

    <표 2 : 자료형>

  • 자료 정의하기

     C로 치면 변수 선언 방법이라고 할 수 있고, 다음과 같은 형식을 같는다.

          이름    디렉티브    초기값, 초기값...
    
    이름은 생략될 수도 있다. 디렉티브는 표 2에 나온 것들을 말한다. 각각을 설명하자면, 먼저 맨 앞의 이름은 앞에서 이야기한 데이터 레이블이다. 레이블은 없을 수도 있다. 초기값이 여러개인 것이 궁금할 것이다. 초기값을 두 개 이상 쓰게 되면 그것은 메모리에 연속적으로 초기값의 개수만큼 공간이 할당되며 해당 초기값으로 초기화된다. 메모리에 연속적으로 위치하게 되므로, 메모리 주소 연산을 이용하여 일차원 배열처럼 사용할 수 있을 것이다. 이에 대한 예는 나중에 설명하도록 하겠다. 자료 정의에 대한 예는 다음과 같다.
         value1 BYTE 'A'	; 문자 상수
         value2 BYTE 0
         value3 SBYTE ?
    디렉티브를 대문자로 쓴 것은 단지 보기 좋으라고 한 것이니까 신경쓰지말자. 대소문자는 구분하지 않는다고 했으니 소문자로 써도 상관없다. 값을 굳이 초기화하지 않아도 된다면 value3와 같이 초기값 자리에 ?를 사용할 수 있다.

  • 문자열 정의하기

    C언어에서 배열에 문자열 넣을 때는 char string[25] = "abcdef"; 이런식의 사용이 허용된다는 것을 기억하는가? 어셈블리어도 문자열 정의에 있어서는 많은 배려를 해주고 있다. 다음을 보라.

        greeting1 BYTE "Hi~>.<;",0		; 1번
        greeting2 BYTE 'H', 'i', '~','>', '.', '<', 0 	; 2번
    
    위의 1번과 2번은 완전히 같은 것이다. 맨 뒤에 붙은 0은 무엇을 의미할까? 바로 문자열의 끝을 나타내는 것으로서 널문자라고 한다. 개행문자를 넣고 싶다면 해당 위치에 0Ah, 0Dh를 넣어야 한다. 도스에서는 캐리지리턴과 라인피드가 모여서 하나의 개행문자를 이루기 때문이다.
  •  

    =============================================================================================

     

  • DUP 연산자

    연속된 여러 값들을 한꺼번에 초기화할 수 있게 해주는 연산자이다. 아까 한 데이터 레이블에 뒤이어 연속적으로 자료를 정의했던 것을 상기시켜보라. 만약 연속된 자료가 1000개 필요하다면 0을 1000개 써야 하므로 매우 귀찮을 것이다. 하지만 DUP연산자를 사용한다면 다음과같이 쓸 수 있다.

    
           BYTE 1000 DUP(0)      ; 20 bytes, 모두 다 0이다.
           BYTE 4 DUP("ABC")     ; 12 bytes: "ABCABCABCABC"
    
    위와 같이 사용한다. DUP앞에 쓰는 숫자는 '몇개'를 복사할 것인지 이고, 괄호 안의 숫자는 초기값이다. '괄호 안의 것'이 DUP앞에 써있는 '숫자' 만큼 복사된다. 쉽게 말해서 어셈블리어가 코드를 복사해 준다고 생각하면 쉬울 것이다.

  • 리틀엔디안 오더(Little Endian Order)

    위의 용어는 메모리에 각 바이트가 저장되는 순서를 의미한다. 앞장에서 우리는 데이터를 비트를 쭉 늘어놓았을 때, 맨 왼쪽이 최상위비트, 맨 오른쪽이 최하위비트라고 배웠다. 마찬가지로 16비트, 즉 2바이트를 늘어놓았을 때에도 왼쪽에 있는 바이트는 상위 바이트, 오른쪽에 있는 바이트는 하위 바이트라고 할 수 있다.

    리틀 엔디안 오더는 "하위 바이트가 하위 주소에 저장되는 방식"이다. 주소에 상위와 하위가 있다고? 상위라는 것은 단지 메모리 주소를 나타내는 숫자가 크다는 뜻이므로 헷갈리지 않기 바란다. 다음 그림을 참고하면 더욱 이해하기 쉬울 것이다.

    <그림 1 : 리틀 엔디안 오더 / 빅 엔디안 오더>

    왼쪽이 리틀 엔디안 오더이고, 오른쪽이 빅 엔디안 오더이다. 빅 엔디안 오더란, 리틀 엔디안 오더의 반대 개념으로서 "하위 바이트가 상위 주소에 저장되는 방식"이다. IA-32는 리틀 엔디안 오더를 사용하고 있다. 단, 레지스터 메모리에는 적용되지 않는 개념이니 주의하기 바란다.

  • 등호 디렉티브(Equal-Sign Directive)

    C언어에서 #define을 쓰는 것과 같은 효과를 낸다. 다음을 보아라.

    
        COUNT = 500
        mov al, COUNT
    
    COUNT = 500이라는 것은 앞으로 COUNT라고 쓰면 그것이 500과 같은 효과를 낸다는 뜻이다. C언어에서 #define COUNT 500 라고 쓴 것과 같은 효과를 낸다. mov라는 인스트럭션은 나중에 배우게 될 것이지만 간단히 설명하면, C에서 대입연산자와 같다고 보면 된다. C언어에서 al = COUNT; 라고 쓴것과 비슷하다. 자료를 전송(move)하는 것인데, al레지스터에 COUNT에 해당되는 값을 넣게 될 것이다.

  • 간단한 예제 - 세 수를 더하는 프로그램

    TITLE Add and Subtract              (AddSub.asm)
    ; This program adds and subtracts 32-bit integers.
    INCLUDE Irvine32.inc
    .data
    val1 DWORD 10000h
    val2 DWORD 40000h
    val3 DWORD 20000h
    finalVal DWORD ?
    .code
    main PROC
      	mov eax,val1	    	; EAX = 10000h
      	add eax,val2		    ; EAX = 50000h
      	sub eax,val3		    ; EAX = 30000h
      	mov finalVal, eax	; 결과를 finalVal에 저장한다.(30000h)
      	call DumpRegs
      	exit
    main ENDP
    END main
    

  • 출력화면

    <그림 2 : 출력화면>

  • 분석

    위의 프로그램이 하는 일은 우선 eax에 10000h(16진수로 10000)을 넣고, eax에 들어있는 값에 40000h를 더한 후 20000h를 빼는 것이다. 그리고 나서 레지스터 메모리에 있는 내용을 화면에 출력시킨 후 프로그램을 종료한다. 한줄한줄 천천히 보자.

    먼저, 맨 위에 TITLE부터 설명하겠다. TITLE은 디렉티브이며, 큰 의미는 없고, 단지 이름이 타이틀이기 때문에 소스코드에는 아무런 영향을 안미치며, 해당 줄에 쓰는 것들(Add and Subtract....)은 모두 주석처리가 되는 것이다. 있어도 되고 없어도 되는 부분이지만 만약 없다면 프로그램을 읽기가 매우 힘들 것이다.

    다음은 ;로 시작하는 부분인데 지난번에 주석이라고 배웠다.
    INCLUDE Irvine32.inc은 C에 비유하자면 #include <irvine32.inc>와 같다. 필자가 참고도서로 언급한 책에서 제공되는 라이브러리를 사용하기 위해서는 이와같이 써야한다. 또한 링크 할 때도 irvine32.lib를 붙여주어야 한다. 혹시 필요한 독자가 있다면
    deltakam@hanmail.net로 연락주기 바란다. 필요한 라이브러리와 어셈블러를 보내드리겠다.

    .data라는 부분이 나오는데, .data는 데이터 세그먼트의 시작 부분을 나타내는 디렉티브이다. 본 프로그램이 실행될 때 메모리의 데이터 영역에 올라가게 될 내용을 이곳에 적게 된다. 본 예제에서는 네가지 DWORD(더블워드)의 변수를 정의하여 놓았다.

    .code라는 부분역시 디렉티브이고, 코드 세그먼트의 시작 부분을 나타낸다. 본 프로그램이 실행될 때 메모리의 코드 영역에 올라가게 될 내용을 이곳에 적으며, 실질적인 프로그램 제어 등이 이곳에서 이루어진다.
     main PROC라는 것은 이름이 main인 프로시져의 정의가 이곳부터 작성될 것이라는 것을 알린다. C에서 함수의 헤더를 정의 앞에 적어주는 것에 비유해서 이해할 수 있다.

    mov는 아까도 잠깐 설명했지만, 왼쪽에 있는 변수에 오른쪽에 있는 값을 저장하라는 인스트럭션이다. 코드의 첫 문장이 지났다면 이제 주석에서 보이는 대로 EAX에 10000h가 들어가게 될 것이다.

    add는 왼쪽에 있는 변수의 값에 오른쪽에 있는 변수의 값을 더하라는 뜻이다. sub또한 마찬가지이다.

    call DumpRegs는 irvine32라이브러리에 포함되어있는 프로시져 DumpRegs를 호출하는 것이다. 역할은 레지스터 메모리에 있는 내용을 화면에 출력시켜준다.

    main ENDP는 이름이 main인 프로시져의 정의가 이곳까지라는 것을 나타낸다.
    자세한 것은 나중에 다시 설명해 줄테니 일단은 이렇게 생긴 것이라고 이해를 해두기 바란다.

  • 마치는 글

    이번 회에서는 자세히 알지는 못하더라도, 개략적으로 어셈블리어가 어떻게 이루어져 있는지를 공부해 보았다. 디테일한 부분이 집착하지 않길 바라며, 혹시 레지스터 메모리가 헷갈린다 싶으면, 지난회를 다시 보기 바란다. 다음 회에서는 본격적으로 하나하나 인스트럭션과 유용한 디렉티브를 공부해 볼 것이다. 고급언어와의 연계를 생각하면서 강좌를 읽는다면 흥미를 유발할 수 있을 것이다. 그럼 다음 회에 건강한 모습으로 다시 뵐수 있길 빈다.

  • ARTICLE
  • 강좌를 시작하며

    이번 강좌에서는 어셈블리어로 프로그램을 작성하기 위해 필요한 기본 지식들을 배우게 될 것이다. 즉, 자료형과 간단한 명령어, 코딩할 때 알아야할 기반 지식들을 배우게 될 것이다. C언어를 처음 배울 때 #include 부터 배우듯이, 차근차근 밟아나갈 것이다. 참고로 우리는 앞으로 매크로 어셈블러를 사용할 것이다.

    여기에 나올 간단한 예제들을 직접 해보고 싶다면, 최신버젼을 구해서 해보기 바란다. 그리고 이 강좌 소개및 목차부분에서 언급한 책에서 제공하는 라이브러리도 사용하게 될 것이다. 라이브러리라고 해서 특별한 것은 없다. 단지, 우리가 수행한 것이 제대로 되었나 출력해 봐야하는데, 많이 배우지 못한 상태에서 복잡한 출력 루틴을 만드는 것보다는 다른 라이브러리를 이용해 일단은 기본개념을 배우는 것이 좋기 때문이다. 그럼 이제 시작해 보자.

  • 어셈블리어의 기본적인 구성요소

    어셈블리의 기본적인 구성요소로는, 정수 상수, 정수 수식, 실수 상수, 문자 상수, 문자열 상수, 예약어, 식별자, 디렉티브(Directives), 인스트럭션(Instructions), 레이블(Label)등이 있다.

  • 상수

    상수란 결국에 어떤 한 값인데, 코드에 숫자 그대로 입력되어 실행중에 값을 변경할 수 없거나, 변수이지만 값을 변경시킬수 없는 것을 의미한다. 먼저 정수 상수에 대해 알아보자.

    정수 상수는 우리가 흔히 쓰는 표기대로 부호는 있거나 없어도 되고, 없다면 양수로 취급된다. 단지 다른 것이 있다면, 숫자 뒤에 radix라는 것을 붙여주는데 쉽게 이야기하면 진법을 이야기 하는 것이다. 우리가 이진수를 1010(2)라고 쓰고, 8진수를 777(8)이라고 수학에서 사용하듯이, 이곳에서도 뒤에 진법을 표기해 준다. 물론 뒤에 진법을 표기하지 않는다면 십진수로 취급된다.

    다음 표1을 보면 쉽게 이해가 갈 것이다. 참고로 radix에는 실수를 표현하기 위한 것으로 r도 올 수 있는데, 이는 실수가 메모리에 저장되는 방식을 직접 우리가 기술하는 것이다. 그다지 본 강좌의 목적과 부합하지 않으므로 자세한 설명은 생략하겠다.

    h

    16진수

    q 또는 o

    8진수

    d 또는 t

    10진수

    b 또는 y

    이진수

    <표 1 : radix >

    이번에는 실수 상수에 대해 알아보자. 실수 역시 부호 규칙을 정수와 같다. 하지만 26과 26.은 다르다는 사실을 아는가? 실수는 반드시 .을 포함하고 있어야 한다. 26은 정수이고 26.은 실수이다. 정수와 실수는 컴퓨터에서 저장하는 형식이 다르다는 것은 알고 있으리라 믿는다. 또, 과학적 표기법을 사용할 수 있는데, 다음을 보아라.

             -44.2E+5

    C에서도 이런 것을 본 적이 있을 것이다. E는 exponent를 뜻하고 그냥 과학적 표기법을 표시하기 위해서 사용한다고 생각하면된다. 저 위에 있는 것을 풀어 이야기하면 "-44.2 곱하기 10의 +5승"이다. 뒤에 +5대신 -2가 온다면 "-44.2 곱하기 10의 -2승"이 될 것이다.

    다음은 문자 상수이다. 어셈블리어에 내가 한 글자를 표현하고 싶다면 작은따옴표(')나 큰 따옴표를 이용해 묶으면 된다. 이 둘의 차이는 없다. C처럼 큰 따옴표를 사용한다고 문자열인 것은 아니니, 헷갈리지 않길 바란다.

    'A'라고 쓰면 사실 코드에는 그렇게 보이지만 어셈블할 때(즉, 컴파일할 때) 이는 A에 해당되는 아스키코드로 변환된다. 즉, 정수와 같다는 이야기가 된다. 달리 특별한 것으로 착각하지 않길 바란다.

    이번엔 문자열 상수에 대해 알아보자. 문자열 상수 역시 문자상수처럼 작은 따옴표 또는 큰 따옴표로 표기된다. 혹시라도 문자열 안에 따옴표를 쓰고 싶다면 다음과 같이 하면 된다.

          'I said "Hi, internet.com.".'

    즉, 안쪽에 큰 따옴표를 쓰고싶다면 문자열의 시작과 끝을 나타내는 기호로 작은 따옴표를 사용하면 된다.

  • 예약어

    예약어란, 어셈블리언어가 먼저 사용하고 있는 단어들을 의미한다. C언어에서 예를 든다면 int같은 것이 있을 것이다.

    - 인스트럭션 니모닉(Instruction mnemonics) : CPU에게 직접 내리는 명령(Instruction)이다.
    - 디렉티브(Directives) : 어셈블리어에게 어떻게 어셈블할 것인지 지시해준다.
    - 속성(Attributes) : BYTE와 WORD같이 변수등이 어떤 크기에 어떤정보를 담고 있을지를 알려주는 단어이다.
    - 연산자(Operators) : 상수 수식에 사용되는 연산기호(*, +, -, / 같은 것들)를 뜻한다.
    - 미리정의된 기호(Predefined Symbols) : @data와 같이 어셈블할 때 특정 값을 갖게되는 기호이다.

    갑자기 이런 개념들이 나와 헷갈릴지 모르겠지만, 조금있다가 설명할 것이니 이런 것이 있구나, 하고 넘어가주길 바란다.

  • 식별자(Identifiers)

    식별자는 프로그래머가 선택하는 이름이다. 변수이름, 상수이름, 프로시져(쉽게 말해서 함수)이름, 코드 레이블(label) 와 같은 것들이며, 다음의 규칙을 갖는다.

    - 길이는 1~247글자이다.
    - 대소문자를 구분하지 않는다. 즉, A와 a는 같다.
    - 첫번째 글자는 반드시 (A~Z, a~z, _, @, $)중의 하나가 와야한다. 두번째 이상의 글자부터는 숫자가 올 수도 있다.
    - 식별자는 예약어와 같아서는 안된다(위에 예약어에 대해 설명해 놓았다).

    참고로, @로 시작하는 식별자는 되도록 쓰지 않는 것이 좋다. 어셈블러가 미리정의해 놓은 기호로 자주 사용되기 때문이다.

  • 디렉티브(Directives)

    디렉티브(Directive)는 CPU에 직접 프로그래머가 명령을 내리는 것이 아닌, 어셈블러가 어셈블 할 때(컴파일 할 때) 해석하여 특정 일을 수행해 주는 명령을 이야기한다. 디렉티브는 주로 세그먼트, 메모리 모델 설정, 변수 정의, 프로시져(함수)만들 때 사용된다. 즉, 일종의 어셈블러 문법인 셈이다. 여기서도 대소문자는 구분하지 않는다.

    .data와 .DATA는 같은 것으로 인식된다. 다음에 나올 인스트럭션(Instruction)과 비교해 보면 그 차이를 짐작할 수 있을 것이다.

  • 인스트럭션(Instructions)

    인스트럭션은 네 부분으로 구성된다.

           레이블:       니모닉            피연산자            ; 주석
           Label:        Mnemonic       Operand(s)        ; Comment

    흔히 하나의 인스트럭션은 위와같이 구성되는데, Label과 Comment는 선택 사항이다. 단, Mnemonic은 반드시 있어야 하며, Mnemonic에 따라 Operand의 개수와 형(위에서말한 속성(attributes))이 결정된다. Instruction Mnemonic은 단지 CPU에 내리는 명령코드를 이해하기 쉽게 영문약자로 표시해준 것이고, Instruction은 피연산자와 부수적인 것들을 포함한 완전한 명령을 의미한다.

    사소한 것이지만, 헷갈리지 않도록 한다. 레이블은 반드시 그 뒤에 콜론(:)이 붙어야 하며 주석은 반드시 그 앞에 (;)가 붙어야 한다. 이는 C++에서 // 와 같은 기능을 한다.

    이제 이 네가지를 하나하나 살펴보기로 한다. 다음 페이지를 보라!

  • 8051-실전 어셈블리 언어의 기초 이해와 문법.pdf
    0.31MB