티스토리 뷰

[전광성의 어셈블리어 이해하기:4회] 프로그래밍에 필요한 명령어와 디렉티브 (3)
저자: 전광성 |  날짜: 2005년 02월 16일  

/
1 .0 여러가지 명령어(MOV, 다이렉트-오프셋 오퍼랜드, ADD, SUB)
2 .0 플래그와 연산자(OFFSET, PTR, TYPE, 인다이렉트 오퍼랜드) 및 배열
0 JMP, LOOP 명령과 예제
/
  • JMP 명령

    JMP는 jump의 약자로, 코드의 특정 위치로 점프를 할 수 있다. C언어에서의 goto문과 정확히 일대일 대응이 되는 명령이다. "jmp 목적지의 코드레이블"와 같이 사용한다. 다음의 예를 보아라.

    L1: jmp L1

    지금 보여주는 것은 무한루프가 된다. jmp 앞에 붙은 L1:은 코드상에 붙인 레이블이다. jmp L1 이렇게 하게 되면 L1으로 가서 코드를 수행하게 된다. 그런데 L1으로 갔는데 또 jmp L1이 있다. 그렇다면 그 다음에도, 다음 다음에도 계속 jmp L1이라는 명령만 수행할 것이다. 따라서 무한루프를 돌게 된다.

    지금까지 배운 명령만 사용한다면 jmp명령어를 사용한다면 무조건 무한루프를 돌게 된다. 그것은 아직 우리가 조건에 따라 점프하는 명령을 배우지 않았기 때문이다. 후에 배울 것이니 일단은 jmp가 어떤 일을 하는 지만 알아 두기 바란다.

  • LOOP 명령

    LOOP명령은 말 그대로 루프를 돌리는데, 특정한 조건이 만족될 때 까지만 루프를 돌린다. LOOP역시 jmp처럼 코드 레이블을 인자로 받는다. LOOP명령이 수행되는 과정은 다음과 같다.

    1.일단 ecx를 1 감소시킨다.
    2.ecx가 0인가? 그렇다면 다음 명령을 계속 수행한다.
                       아니라면, 인자로 받은 레이블로 점프한다.

    간단한 사용예는 다음과 같다.

         mov ax, 0
         mov ecx, 5
    L1:
         inc ax
         loop L1

    위와같이 하면 루프를 빠져 나온 후 ax는 정확히 5가 되어있을 것이다. ecx에 5를 넣고 하나씩 감소시켜가며 0이 되었는지 확인하였기 때문이다. ecx는 여기서 한마디로 말해, 루프카운터의 역할을 하는 것이다. 잘 모르겠다면 LOOP명령 수행 과정을 다시 한번 훑어봐 주기 바란다.

    참고로, 코드가 수행되고 있을 때는 다음번에 수행할 명령에 대한 포인터가 레지스터 변수 EIP(Extended Instruction Pointer)에 저장되어있다. L1이라는 곳으로 점프를 한다는 것은 다음 수행할 명령에 대한 포인터가 L1레이블이 붙은 명령을 가리키도록 하는 것이다. 그래서 EIP는 프로그래머가 직접 건드릴 수 없도록 되어있다. EIP를 잘못 건드렸다가는 프로그램의 흐름 자체가 엉망이 되어버리기 때문이다.

  • 예제

    Include Irvine32.inc


    .data

    source BYTE "I love internet.com", 0

    target BYTE SIZEOF source DUP(0)


    .code

    main PROC

      mov esi, 0

      mov ecx, SIZEOF source  ; 루프 카운터

    L1:

      mov al, [source + esi]   ; source에서 한 글자를 읽어들인다.

      mov [target + esi], al     ; target에 저장한다.

      inc esi      ; 다음 글자를 가리키게 한다.

      loop L1


      ;target스트링을 출력하는 함수를 호출한다.

      mov edx, OFFSET target

      call WriteString


      exit

    main ENDP

    END main



  • 출력결과


  • 설명

    일단 코드의 맨 윗줄의 Include Irvine32.inc는 예전에도 설명했듯이 필자가 주로 참고하는 교재에서 제공하는 기본적인 프로시져들을 사용할 수 있도록 원형을 포함하고자 적어준 것이다. C에서 #include 를 하는 것과 같다고 생각하면 편할 것이다.

    그 다음에 .data는 디렉티브로서 이 부분에 변수를 적어주는 것이라고 설명했었다. 위에서는 source라는 배열에 "I love internet.com"을 집어넣고, 맨 마지막에 널문자를 넣어주었다. 그리고 target이라는 배열에 source와 같은 길이로 0을 채워주었다. 여기서 SIZEOF라는 것이 쓰였는데 이것은 연산자로서, source데이터 레이블로부터 시작되는 연속된 자료의 총 사이즈를 바이트로 리턴해준다. "N DUP(0)"은 괄호안의 수를 'N'개만큼 연속으로 선언한다는 뜻이다.

    다음에는 .code디렉티브로, 이 부분에 실제 수행할 코드를 적어준다. 그 아래는 main프로시져를 정의하는 부분이다. 여기서 일단 esi를 초기화 해 주는데, esi는 배열의 인덱스를 적어주게 될 것이다. C에 비유하자면, int ary[5]; 라고 선언한 후 esi = 0;한 다음 esi변수를 증가시켜가며 ary[esi]에 접근하는 것과 같은 역할을 한다. ecx는 루프를 돌리기 위한 카운터로, source배열의 전체 사이즈 만큼 루프를 돌릴 것이다. 따라서 mov ecx, SIZEOF source라고 값을 복사해 주었다.

    L1:은 코드 레이블로써 루프를 사용하기 위해 적어준 것이다. 루프의 구조는 간단하다. source + esi에서 한 바이트를 읽어 al에 저장하고, 이를 target + esi에 적어준다. 그리고 나서 esi를 하나 증가시켜 주는데, INC라는 명령을 사용하였다. INC는 increase의 약자로, 변수의 값을 하나 증가시켜준다.

    C에서 esi++;이라고 쓰는 것과 같은 의미이다. 그 다음 loop L1을 만나는데, 여기서 ecx를 하나 감소시킨 후 0인지 확인하고, 0이 아니라면 L1:레이블이 달린 곳으로 점프한다. ecx가 0이 되었다면 바로 다음 명령을 계속해서 수행하게 될 것이다. 나머지 코드는, 화면에 target배열에 들어있는 문자열을 찍어주기위한 프로시져호출이다. 뒤에서 프로시져를 배울 때 자세히 설명해 줄 테니, 지금은 어떤 역할을 하는 지만 알아두어라.

  • 마치는 글

    점점 설명이 어려워 지는 느낌이라 죄송한 마음 뿐이다. 쉽게 쓰려고 좀더 노력하겠다. 다음 회에서는 어셈블리어에서 프로시져를 사용하는 방법을 배울 것이다. 프로시져를 배우고 나면 고급언어에서 함수호출이 어떻게 런타임 스택을 이용하는지에 대해 이해할 수 있을 것이다. 또 약 3~4회 정도 후에 나올 고급 프로시져를 배운다면 좀더 완벽히 이해할 수 있다. 지난회를 읽지 않았다면 본회를 읽는데 어려웠을 것이고, 이번회를 읽지 않았다면 다음회를 읽는 데 어려움이 있을 것이다. 그럼 다음회에서 다시 뵙길 바란다.
  • 댓글
    안내
    궁금한 점을 댓글로 남겨주시면 답변해 드립니다.