안녕하세요? 언더입니다.

요즘들어 어셈블리 강좌를 쓰면 좋을 것 같다는 생각을 많이 하였습니다. 그리고 어디서부터 어떻게 시작하면 좋을까 생각했지요. 그러던 중 매우 훌륭한 글을 웹에서 발견하였습니다. 그리하여 무능한 제가 어설픈 강좌를 하는 것 보다는 이것을 번역하여 올리는 것으로 대체할까 합니다. 원문 자체가 워낙 잘 되어 있어서, 그대로 번역하려고 많이 노력할 것입니다. 더불어 번역에 대한 허가를 받으려고 연락을 시도하였지만, 연락이 닿지 않았습니다. 혹시 문제가 되면 연재를 중지하도록 하겠습니다. 원문은 매번 게시글 하단에 게재하도록 하겠습니다.


 

튜토리얼 1 : 기본

본 튜토리얼에서는 여러분이 MASM 사용법을 알고 있다고 가정합니다. 만약 MASM에 대해서 잘 알지 못한다면 본 튜토리얼에 앞서 win32asm.exe을 다운로드 받아, 안에 포함되어 있는 문서를 공부하시기 바랍니다. 자, 이제 여러분은 준비가 되었습니다. 시작해 봅시다!

이론 :

Win32 프로그램은 80286부터 도입된 보호모드(protected mode)에서 실행됩니다. 하지만 80286은 너무 옛날 이야기입니다. 그렇기 때문에 우린 80386이나 그 이상에 대해서만 고려하도록 할 것입니다. Windows 는 Win32 프로그램을 각각 가상공간(virtual space)라는 분리된 공간에서 실행합니다. 이것은 각각의 Win32 프로그램은 자신만의 4GB라는 주소공간을 갖게 된다는 이야기입니다. 그렇다고해서 모든 win32 프로그램이 물리적인 4GB 메모리를 갖는다는 이야기는 아닙니다. 단지 프로그램이 그 범위 내에서 어떤 주소든 가질 수 있다는 이야깁니다. Windows 는 프로그램이 올바른 메모리 주소를 참조할 수 있도록 해 줍니다. 물론, 프로그램은 Windows 가 정한 규칙을 지켜야 합니다. 그렇지 않을 경우 일반적 보호 오류(General Protection Falut)의 원인이 됩니다. 각각의 프로그램은 고유한 자신만의 메모리 공간에는 독립적으로 존재합니다. 이것은 Win16 때와 대조적입니다. 모든 Win16 프로그램은 서로의 프로그램을 볼 수 있었습니다. Win32 에서는 그렇지 않습니다. 이러한 특징은 프로그램이 다른 프로그램의 코드/데이타를 덮어쓰는 오류를 줄이는데 큰 도움이 됩니다.

메모리 모델 역시 16비트 시절과 매우 다릅니다. Win32 에서는  메모리 모델과 세그먼트를 더 이상 고민할 필요가 없습니다! 'Flat memory'라는 단 하나의 메모리 모델만 있습니다. 더이상 64K 세그먼트가 없습니다. 4GB의 연속적이고 커다란 메모리가 있습니다. 다시 말해, 더 이상 세그먼트 레지스터를 만질 필요가 없다는 것입니다. 어떤 세그먼트 레지스터를 가지고도 메모리의 어떤 곳이든 가리킬 수 있습니다. 이것은 프로그래머에게 엄청난 축복입니다. Win32 어셈블리 프로그래밍을 C처럼 쉽게 할 수 있는 것이 바로 이것 덕분입니다.

Win32 에서 프로그래밍을 할 때, 반드시 알아야 하는 중요한 규칙이 있습니다. 그 중 하나가 이런 것입니다. Windows 는 내부적으로 esi, edi, ebp, ebx 레지스터를 사용하며, 레지스터 안에 값이 변경되는 것을 고려하지 않습니다. 그래서 이 규칙을 우선적으로 기억해야 합니다. 만약 여러분의 콜백 함수에서 앞서 언급한 레지스터를 사용했다면, Windows 에게 제어권을 넘기기 전에 반드시 다시 복구시켜 놓아야 합니다. 콜백함수는 Windows 가 호출하는 여러분이 만든 함수입니다. 확실한 예는 windows 프로시져입니다. 그렇다고 해서 여러분이 이 네가지 레지스터를 사용할 수 없다는 이야기는 아닙니다. 사용할 수 있습니다. 단지 Windows 에게 제어권을 돌려주기 전에 반드시 이전값으로 되돌려 놓아야 한다는 것입니다.

 

내용 :

이것은 프로그램의 뼈대입니다. 설사 이해하지 못하는 코드가 있다고 하더라도 좌절할 필요가 없습니다. 나중에 자세히 설명해 드릴 것입니다.

.386
.MODEL Flat, STDCALL
.DATA
    <Your initialized data>
    ......
.DATA?
   <Your uninitialized data>
   ......
.CONST
   <Your constants>
   ......
.CODE
   <label>
    <Your code>
   .....
    end <label>


이것이 전부입니다. 그럼 프로그램 뼈대를 살펴보도록 하죠.

.386
이것은 어셈블러에게 80386 명령어 집합을 사용하겠다고 알려주는 어셈블러 지시자입니다. 여러분은 이외에 .486, .586 도 사용할 수 있지만 .386 을 사용하는 것이 가장 안정적입니다. 사실 각각의 CPU 모델에는 비슷한 두가지 양식이 존재합니다. .386/.386p, .486/.486p. 여기 "p" 버젼들은 프로그램 안에서 특별한 명령을 사용할 때만 필요합니다. 특별한 명령어란 보호모드(protected mode)에서 CPU/OS에 의해 예약된 명령어를 의미합니다. 이들은 가상 디바이스 드라이버와 같이 특별한 코드에 의해서만 사용될 수 있습니다. 대부분 여러분의 프로그램은 비 특별모드(non-privileged mode)에서 실행될 것이기 때문에, p 가 없는 버젼을 사용하시는게 안전합니다.

.MODEL FLAT, STDCALL
.MODEL 은 프로그램의 메모리 모델을 명시하는 어셈블러 지시자입니다. Win32 에서는 FLAT 이라고 하는 단 하나의 모델만 있습니다.


STDCALL 은 MASM 에게 파라미터 전달방식에 대해서 말해 줍니다. 파라미터 전달 방식이란 왼쪽에서 오른쪽 순서로 전달 또는 오른쪽에서 왼쪽 순서로 전달과 같이 파라미터 전달 순서, 그리고 함수 호출 후에 스택 프레임을 어디서 되돌려 놓아야 하는지에 대해 명시합니다.


Win16에서 C 와  P, 두 가지 호출방식(calling convention)이 있습니다.

C 호출방식은 가장 오른쪽에 있는 파라미터를 가장 먼저 입력(push)하는 순서로, 오른쪽에서 왼쪽으로 파라미터를 전달합니다.  호출자는 호출 후에 스택 프레임을 되돌려 놓아야 합니다. 예를 들어, c 호출방식(calling convention)에서 foo(int first_param, int second_param, int third_param)라는 함수를 호출하기 위해한 어셈 코드는 아래와 같습니다.

push  [third_param]               ; 세번째 파라미터를 입력
push  [second_param]           ; 이이서 두번째 파라미터
push  [first_param]                ; 그리고 첫번째 파라미터
call    foo
add    sp, 12                         ; 호출자가 스택 프레임을 복구시킨다

PASCAL 호출방식은 C 호출방식과 반대입니다. 파라미터를 왼쪽에서 오른쪽으로 전달하고, 호출 후에는 호출받은 쪽에서 스택을 되돌려 놓아야 합니다.

Win16 은 코드의 길이가 짧은 PASCAL 호출방식을 채택하였습니다. C 방식은 wsprintf()처럼 얼마나 많은 파라미터를 함수로 전달해야 할지 예측할 수 없을 때, 유용합니다. wsprintf()에서는 함수가 얼마나 많은 파라미터가 스택으로 입력될지 알 수 없기 때문에, 스택을 되돌려 놓는 것 역시 불가능합니다.

STDCALL 은 C 와 PASCAL 방식의 혼합입니다. 파라미터는 오른쪽에서 왼쪽 순서로 전달하지만, 호출 후에 호출받은 쪽에서 스택을 되돌려 놓아야 합니다. Win32 플렛폼은  STDCALL 를 베타적으로 사용합니다. 하지만 wsprintf()는 예외입니다. wsprintf()는 C 호출방식을 사용해야만 합니다.

.DATA
.DATA?
.CONST
.CODE

다음 네 지시자는 섹션(section)이라고 불립니다. Win32에서는 세그먼트가 없습니다. 기억하시죠? 하지만 우린 전체 주소 공간을 논리적인 섹션으로 나눌 수 있습니다. 한 섹션의 시작은 이전 섹션의 마침을 의미합니다. 섹션에는 데이타와 코드라는 두개의 분류가 있습니다. 데이타 섹션은 세개의 카테고리로 나누어 집니다.

  • .DATA   이 섹션 안에는 프로그램에서 초기화된 데이터들을 넣습니다.
  • .DATA? 이 섹션 안에는 프로그램에서 초기화된 데이터를 넣습니다. 간혹 메모리 공간은 필요한데, 초기화 하지 않을 경우가 있습니다. 이런 경우를 위한 섹션입니다. 데이터를 초기화하지 않았을 경우 장점은 다음과 같습니다. 이 데이타는 실행파일 안에 공간을 차지하지 않습니다. 예를들어, 만약 10,000 bytes 를 .DATA? 안에 할당받았다고 하더라도 실행파일이 10,000 bytes 만큼 증가하지는 않습니다. 더 커다란 크기도 마찬가지입니다. 단지 어셈블러에게 프로그램이 메모리에 로드될 때, 얼마나 많은 공간을 필요로 하는지 말해주는 것 뿐입니다.
  • .CONST  이 섹션 안에는 프로그램이 사용할 상수 선언을 넣습니다. 이 섹션 안의 상수는 프로그램에서 절대 변경될 수 없습니다. 이것은 말 그대로 상수입니다.

반드시 프로그램 안에 이 세가지 섹션이 모두 있어야 하는 것은 아닙니다. 필요한 섹션만 선언하면 됩니다.

코드를 위한 섹션은 .CODE 단 하나입니다.  여러분의 코드가 있는 곳이 바로 이곳입니다.
<label>
end <label>

<label> 은 여러분의 코드 확장을 명시하기 위해서 사용되는 표시로 어떤것이든 가능합니다. 한 쌍의 label 은 반드시 같아야 합니다. 여러분의 코드는 모두 <label>end <label> 사이에 있어야 합니다.
 

----------------------------------------------------------------------------------------
원문 : http://win32assembly.online.fr/tut1.html