티스토리 뷰

[전광성의 어셈블리어 이해하기:5회] 프로시져(Procedure) (1)
저자: 전광성 |  날짜: 2005년 02월 23일  

/
0 함수의 동작 원리
2 .0 프로시져(Procedure)
3 .0 USES 연산자
/
  • 시작하기에 앞서...

    아무리 저급언어라고 해도, 함수는 존재한다. 함수는 특정 코드가 반복될때, 이를 따로 떼어내어 만들어 두고 필요할 때 호출함으로써 코드의 길이를 줄일 수 있다. 또한 함수는 지나치게 긴 코드를 보기좋고 깔끔하게 만들어 주기도 하며, 함수의 이름을 잘 짓는다면 문서화에도 도움이 된다. 어셈블리어에서는 함수를 이용할 수 있는 직접적인 명령(Instruction)은 없지만, 디렉티브(Directive)를 갖고 있다. 하지만 그 이전에, 먼저 런타임 스택(Runtime Stack)을 알아야 한다.

    본 강좌의 목적은 어셈블리어 활용보다는 '이해'에 중점을 두고 있으므로, 어떻게 함수 호출이 이루어지는지를 이해하는데에 많은 분량을 할당하려고 한다.

  • 함수의 동작 원리

    함수의 동작 원리라고 하니 뭔가 거창해 보이지만 사실 별 것 아니다. 그저 스택이라는 자료구조를 이용해 함수 호출이 가능하다는 것을 이야기 하고 싶을 뿐이다. 함수를 호출하게 되면 제어가 그 곳으로 넘어가야 하니까, 이전의 인스트럭션 포인터(EIP)레지스터를 어딘가에 저장시켜놨다가 나중에 다시 불러와야 할 것이다. 그런데 함수 여러개가 중첩되어 호출되었다면, 인스트럭션 포인터를 저장하는 자료구조는 가장 나중에 들어간 자료가 가장 먼저 나와야 올바르게 수행될 것이다. 즉, LIFO(Last-In, First-Out)여야 한다. 그렇다면 스택을 사용하여야 한다는 것은 두말할 필요도 없을 것이다.

  • 런타임 스택(Runtime Stack)

    런타임 스택이란, 메모리를 이용하여 CPU에 의해 직접 관리되는 스택이다. 이는 프로시져(Procedure)를 호출하고 리턴하는데 있어서 필수적인 메커니즘이다. 위에서는 쉬운 이해를 위해 함수라고 하였지만, 개념이 비슷할 뿐 어셈블리어에서는 프로시져(Procedure)라고 한다. 런타임 스택은 항상 ESP(Extended Stack Pointer)레지스터를 이용하여 top을 가리키게 된다. 우리는 보통 스택의 개념을 설명할때 '쌓음'의 원리를 이용하여 아래서부터 위로 쌓여가는 것으로 설명하곤 한다. 하지만 IA-32아키텍쳐에서 스택은 아래로 자란다. 즉, 스택을 거꾸로 뒤집어 놓은 것이다. 별다른 이유없이 그렇게 된 것이니 너무 깊이 생각할 필요는 없다. 다음의 그림을 보면 이해가 빠를 것이다

    <그림 1 : push동작>


  • PUSH, POP 인스트럭션

    독자가 스택이라는 자료구조에 알고 있다는 가정하에 설명하겠다. 어셈블리어에서는 다른 고급언와 달리 직접 런타임 스택에 PUSH/POP할 수 있다. 사용방법은 매우 간단한다. push의 피연산자로는 16비트나 32비트 메모리, 레지스터, 그리고 임의의 상수가 올 수 있다. 임의의 상수를 사용한다면 32bit로 스택에 저장된다. 이 인스트럭션은 피연산자를 스택에 집어넣게 될 것이다.

    pop인스트럭션의 피연산자로 올 수 있는 것에는 16비트나 32비트 메모리, 레지스터가 있다. pop인스트럭션은 스택에서 하나를 빼와 피연산자에 집어넣는다. 피연산자가 16bit면 스택에서 16bit만 빼올 것이고, 피연산자가 32bit면 스택에서 32bit만 빼올 것이다. 다음의 사용예들을 참고하여라.

      .data
      value DWORD 0h
      .code
            mov eax, 3000h
            push eax
            push 0FFFFFFFFh
            pop value ; 0FFFFFFFFh
            pop edx ; 3000h


  • PUSHFD, POPFD 인스트럭션

    이름에 push, pop이 들어가 있으니 뭔가 런타임 스택과 관련있는 인스트럭션이라는 감이 올 것이다. f는 flags라는 뜻이고, d는 더블워드(32bit)라는 뜻이다. push doubleword flags 라고 풀어보면 무슨 뜻인지 이해가 갈 것이다. 32bit의 EFLAGS레지스터의 내용을 모두 스택에 push하라는 뜻이다. popfd는 마찬가지로 스택에서 32bit를 pop하여 EFLAGS레지스터에 넣으라는 뜻이다. 두 인스트럭션 모두 피연산자는 받지 않는다.

    이런 인스트럭션이 어디에 사용될까? 어셈블리어에서는 어떤 인스트럭션을 수행한 후 세팅되는 플래그에 따라 제어가 분기되는 경우가 많다. 허나 그 인스트럭션과 제어가 분기되는 곳 사이에 다른 인스트럭션이 들어가서 플래그를 변경시켜 버린다면 어떻게 될까? 프로그래머가 의도하지 않은 결과가 나올 것이다. 이럴 때, pushfd해 두었다가 제어를 분기하기 전에 popfd하면 문제가 해결될 것이다.
  • 댓글
    안내
    궁금한 점을 댓글로 남겨주시면 답변해 드립니다.