'어셈블리어'에 해당되는 글 2건

  1. 2010.03.16 [MASM 강좌] 튜토리얼 1 : 기본 (5)
  2. 2010.03.15 MASM - DLL 만들기 (3)

 

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

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


 

튜토리얼 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

신고

Introduction


지난 시간에 약속한대로 이번 시간에는 MASM 을 이용하여 DLL을 만들고, DLL을 이용하는 방법에 대해서 설명하도록 하겠습니다. 물론 Visual Studio 2008 환경에서 설명하도록 하겠습니다.


DLL 만들기

이 예제에서는 DllTest.dll 을 생성하고, 이를 사용하도록 하겠습니다. DllTest.dll 안에는 MsgBoxFunction 이라는 Funcion 하나가 들어 있습니다. 소스 파일명은 Test.asm 입니다. DLL을 생성하기 위해서는 Def 파일을 추가로 작성해 주셔야 합니다.

Test.asm 코드


Test.Def 코드


Test.Def 안에 LIBRARY는 *.asm 파일명을 적어 주시면 됩니다. 그리고 Exports 에는 말 그대로 Export 시킬 함수이름을 적으면 되죠. 매우 간단하죠? 자, 이제 빌드를 하면 아무 문제없이 DLL 파일이 만들어 집니다. Test.DLL 이면 좋으련만, 아무 생각없이 DllTest.dll 로 만들어 버렸습니다 =_=; 물론 마음에 들지 않으시는 분은 프로젝트 속성에서 output 을 새로 설정해 주시면 됩니다. 전 그냥 rename 하지 않고, 쭉~ 설명 하겠습니다.

빌드 설정

빌드에 앞서 설정해 주어야 할 것들이 있습니다. 그냥 빌드한다면 exe 파일이 만들어지겠죠. C++ 프로젝트에 익숙하신 분들은 별 무리없이 setting 하실 수 있을거라고 생각합니다.
자, 프로젝트 속성에서 Configuration Properties -> General 에 들어가시면 상단 화면처럼 'Configuration Type' 이라는 메뉴가 있습니다. 이곳의 타입을 화면처럼 "Dynamic Library(.dll)"로 변경해 줍니다. 우린 dll을 만들 것이기 때문이죠.


그리고 이어서 Configuration Properties -> Linker -> Input 에 "Module Definition File" 에 앞서 작성한 Def 파일명을 입력합니다. 화면 하단에 설명되어 있는 것처럼 Linker 에 /DEF: 옵션을 주기 위한 것이죠.

마지막으로 Configuration Properties -> Linker -> System 에서 SubSystem 을 "Windows (/SUBSYSTEM:WINDOWS)" 로 선택합니다.

이제 build 를 위한 모든 준비가 끝났습니다. Ctrl + Alt + B 를 눌러서 프로젝트를 Build 하면 별 무리없이 수행 되는 것을 보실 수 있습니다. 그리고 폴더로 이동하면 .dll 파일이 만들어져 있을 것입니다. ^-^

 

DLL 사용하기

앞에서 만든 DLL을 사용하는 방법에 대해서 설명하도록 하겠습니다. 사실 이것은 MASM 에 대한 이야기라기보다는 Win32에 더 가까울 것입니다. 어려운 부분이 없기 때문에, 따로 설명 드리지 않도록 하겠습니다.

DLL 사용



마무리

개인적으로 저는 Assembly 를 매우 좋아합니다. 그 어떤 언어보다도 군더더기 없이 깔끔한 바이너리 파일을 만들어 주기 때문이죠. 저는 찝찝한 것을 매우 싫어합니다. 내가 만든 파일인데 실제로 binary 가 어떻게 되어 있는지 확신할 수 없죠. 하지만 assembly 는 그런 면에서 결코 프로그래머를 배신하지 않습니다. ^-^
앞으로는 MASM 프로그래밍에 대해서 포스팅을 하려고 합니다. 물론 Language 에 대해서 포스팅 한다는게 우스운 일이긴 합니다만, Assembly 프로그래밍에 대한 자료를 찾기 어려운 것이 사실입니다. 인터넷 서점을 뒤져봐도 대부분이 2000년 이전의 책들이지요. 이런 현실이 안타까운 1인으로서 assembly 를 조금이나마 알리고자 노력할 생각입니다. :) 아, Assembly 로만 완전한 프로그램을 만든다는것은 어리석은 이야기라는데 동의합니다. 하지만 많은 연산이 필요한 부분에 대해서는 assembly 만큼 좋은 언어는 없겠죠. 그리고 결정적으로 Reverse Engineering 을 위해서는 반드시 알아야 하는 언어랍니다. :)

신고