반응형

http://sol9501.blog.me/70102942944


|DLL 분석 ①|DLL파일 생성과 설명

 

* 이번 포스팅은 실제 DLL파일이란 무엇인지 알아보고 직접 생성하는 방법을 알아보겠습니다.

 

 

 1. DLL(Dynamic Link Library)이란?

   1)정의

     - 어플리케이션에서 동적으로 링크하여 사용할 수 있는 라이브러리를 말하며 확장자로는 .dll, .fon, .drv, .exe 등이 사용된다.

구조는 간단히 코드부분과 데이터로 구성되어있으며, 스택이 없다는 것이 특징이다. 스택은 어플이케이션의 것을 사용한다. 스택이 없으므로 독립적인 프로세스가 될 수 없으며 운영체제로 부터의 메세지를 받을 수 없다. 내부에 Exported Function Table을 가지고 있으며 서수 + 기호이름 + 실제함수를 가리키는 포인터로 구성되어 있다.

 

   2) 종류

    - Regular DLL (Statically linked MFC DLL) : 배포시 자신의 DLL만 제공가능

    - Regular DLL (using Shared MFC DLL) : 배포시 자신의 DLL과 MFC공유DLL을 같이 제공

      -> 어플리케이션이 MFC이외의 경우에도 사용가능

      C언어 기반의 인터페이스제공 필요.

     - MFC Extension DLL (using Shared MFC DLL) : MFC로 작성된 어플리케이션에서만 사용가능

 

    3) 로딩방법

     ㄱ) Implicit Loading (암시적 로딩)

        - DLL + Lib + header file 필요. 

- 어플리케이션 로딩때 같이 로딩.

     ㄴ) Explicit Loading (명시적 로딩)

         - 로딩타임 마음대로 결정가능

- 주요함수

- LoadLibrary : DLL모듈 로딩

- GetProcAddress : 함수의 포인터를 얻어옴

- FreeLibrary : DLL 종료 (reference counter를 1감소시킨다)

 

* 이번 포스팅은 DLL 파일 생성 방법만 알아보고 로딩방법이나 기타 자세한 상항은 PE파일 (DLL 로딩과 임포트 섹션편)을 참고해 주시길 바랍니다.

DLL 로딩과 임포트 섹션 - 포스팅중..^^

 

 

  2. DLL 생성

    - DLL을 만드는 방법에는 크게 두 가지가 있다. 하나는 고전적인 DLL정의 방법인 모듈 정의 파일을 이용하는 것이고 다른 하나는 내장 Visulal c++ 지시자인 __declspec(dllexport)를 이용하는 것이다. 우선은 고전적인 방법인 모듈정의 파일을 이용해 먼저 DLL 파일을 생성해 보겠습니다. 소스는 아래와 같습니다. VS2010기준으로 DLL 파일을 생성하는 방법은 아래의 그림 1-1부터 1-3과 같습니다.

 

그림 1-1) win32 project

 

그림 1-2) Application Setting

 

- 그림 1-2에서처럼 어플리케이션 타입은 DLL을 클릭해 줍니다. 프로젝트가 생성되었으면 빈 cpp소스파일을 하나 생성하고 아래의 예제소스를 복사해 붙여 넣고 빌드합니다. 빌드가 성공하면 그림 1-3과 같이 dll 파일이 생성됩니다.

 

Ex1) 예제소스 1

#include <Windows.h>

 

void WINAPI DrawTextPos04(HDC hDC, LPTSTR pszText, POINT ptPos)

{

TextOut(hDC, ptPos.x, ptPos.y, pszText, lstrlen(pszText));

}

 

int IntSum06(int a, int b)

{

    return a+b;

}

 

BOOL WINAPI IsPointInRect11(RECT rcRgn, POINT ptPos)

{

    return PtInRect(&rcRgn, ptPos);

}

 

 

그림 1-3) Application Setting

 

- 여기서 잠깐 우리는 저 DLL을 왜 만드는지 짚고 넘어가야 한다. DLL을 사용하는 근본적인 이유는 반복해서 사용하는 코드를(위의 경우 3가지 함수들) 매번 작성할 것이 아니라 따로 바이너리 모듈로 만들어두고 필요할 때마다 링크해서 사용하고자 하는 목적이다. 여기서 확장자가 .lib라는 파일인 정적 라이브러리와 .DLL인 동적 라이브러리로 나뉘는데 이 둘의 차이는 정적 라이브러리는 코드나 데이터가 링크 시에 pe파일 내에 병합되어 그만큼 PE파일의 사이즈가 커지지만 동적 라이브러리의 경우(.DLL) 라이브러리의 코드나 데이터는 해당 PE가 로드 될 때 그 PE의 가상 주소 공간에 병합된다는 것입니다. 즉 링크된 EXE의 PE 파일에는 단지 해당 라이브러이에서 사용할 함수에 대한 간단한 정보만 기록됩니다.(이 부분의 확인은 위에서 링크한 PE파일 익스포트 섹션분석편을 참고하세요.)

모듈 자체가 정적 라이브러리처럼 EXE의 PE이미지 내에 병합되는 것이 아니기 때문에 DLL의 경우 이렇게 실행 이미지에 병합하고자 하는 함수나 변수는 미이 병합될 함수라는 것을 컴파일러나 링커에게 알려주어야 합니다. 이렇게 알려주는 것을 "익스포트"한다라고 하는데 익스포트시키지 않으면 해당 DLL 내의 어떤 함수나 변수를 사용할 수 있는 지 판단한 수 없습니다.

위의 과정은 단지 함수만 정의했을 뿐 그 외 어떠한 것도 지정하지 않았습니다. 즉 익스포트시키지 않은 것입니다. 그 결과 그림 2와 같이 dumbin 으로 익스포트 정보를 요구해도 아무런 정보가 나타나지 않는 것입니다.

이러한 익스포트 하는 방법으로 위에서 언급한 모듈 정의 파일을 정의하거나 __declspec(dllexport) 지시어를 사용하는 방법이 있습니다. 우리는 우선 고전적이고 지금은 사용빈도가 적지만 기본이 되는 모듈 정의 파일을 이용하는 방법을 알아보겠습니다. 제가 항상 말씀드리지만 고수와 하수의 차이는 기본기에 있습니다. ^^ 기초가 튼튼해야죵..^^

 

그림 2) 익스포트 되지 않은 dll파일

 

 

1) 모듈정의 파일 사용

- 모듈 정의 파일의 장점은 서수를 지정할 수 있다는 것이다. 서수를 지정할 수 있다는 이야기는 서수가 익스포트 함수들에 대한 인덱스 역할을 하게 되기 때문에 문자열 비교 없이 빠른 속도로 해당 함수를 링크할 수 있다는 장점이 있다. 또한 DLL에 익스포트 함수를 새로 추가할 때 이 함수에 해당하는 서수를 그 DLL내에서 익스포트된 다른 모든 함수의 서수 값들보다 더 높은 값으로 할달 할 수 있다. 이렇게 되면 암시적 링크를 사용하는 응용 프로그램에서 새로운 함수가 추가된 DLL에 해당하는 LIB파일을 다시 링크할 필요가 없어진다. 이 경우 기존의 DLL에 새로운 기능을 추가하여 계속 업그레이드시키면서도 기존 응용 프로그램이 업그레이드된 DLL과 올바르게 작동하도록 할 수 있다. 또한 만약 익스포트시킬 함수가 많을 경우 모듈 정의 파일을 통해 "NONAME"의 속성을 지정하게 되면 함수 이름을 저장하지 않고 서수만 저장하기 때문에 DLL의 사이즈를 최적화할 수 있다.

아래 그림 3은 모듈정의 파일을 정의하는 방법을 보여주고 있다. 모듈정의 파일은 노트패드나 문서화 도구를 이용해서 아래 그림 3과 같이 만들어 주면 된다.

 

그림 3)모듈 정의 파일(.DEF)

 

- 모듈 정의 파일은 말 그대로 모듈을 정의한다는 의미이다. 이 말은 곧 굳이 작성하는 PE가 DLL이 아니라 exe이더라도 링크 시에 모듈정의 파일에 정의된 지시어들이 유효함을 말하며 링커는 모듈 정의 파일이 존재한다면 이 파일에 지정된 지시어들을 링크 스위치로 해석하게 된다. 일반적으로 모듈 정의문 대신에 사용할 수 있는 링커 옵션이 있기 때문에 일반적으로는 모듈 정의 파일이 필요하지 않다. 하지만 함수 익스포트의 경우 위의 그림 3처럼 EXPORTS 키워드 아래에 익스포트 함수들을 한 번에 정의할 수 있기 때문에 DLL 작성시 모듈 정의 파일을 사용하는 것이다. 각 키워드는 표 1을 참고하세요.

 

[표 1] – 모듈정의 파일

키워드

의미

LIBRARY

형식

LIBRARY [library][BASE=address]

library

* library 옵션은 DLL의 이름을 지정한다. 링커 옵션 /OUT을 사 용하여 DLL의 출력 이름을 지정 할 수도 있다.

BASE=address

* BASE= ADDRESS 옵션은 운영체제가 해당 DLL을 로드하는데 사용하는 기본 주소를 설정한다. 정의되지 않았다면 DLL 매핑의 디폴트 주소인 0x10000000으로 설정한다.

EXPORTS

형식

EXPORTS

definition

definition = entryname[=internalname] [@ordinal [NONAME]] [PRIVATE] [DATA]

entryname

* entryname은 익스포트하고자 하는 함수 또는 변수의 이름으로서 반드시 지정되어야 하는 요소이다. internalname의 경우는 익스포트하고자 하는 이름이 DLL에 있는 이름과 다른 경우에 이 요소를 통해 실제 이름을 지정하기 위해 사용된다.

@ordinal

* @ordinal을 사용하면 함수 이름이 아닌 서수가 DLL의 익스포트 테이블에 들어가도록 지정된다. 이렇게 서수를 지정하게 하면 후에 DLL을 링크하는 응용 프로그램에서는 이름 대신 서수를 통해 해당 함수를 링크하게 된다.

NONAME

* 선택적 키워드인 NONAME을 사용하면 함수를 서수로만 익스포트시키기 때문에 해당 DLL이 많은 익스포트 함수를 정의할 경우에 익스포트 테이블의 크기를 줄일 수 있다. 하지만 링크하고자 할 때 이름이 유효하지 않게 되므로 반드시 서수를 알아야 한다.

PRIVATE

* 선택적 키워드인 PRIVATE는 LINK로 생성된 LIB파일에 ENTRYNAME이 포함되지 않도록 한다.

DATA

* 선택적 키워드인 DATA는 익스포트 대상이 코드가 아니라 데이터임을 알려준다.

EXPORTS

DllExam @1 PRIVATE, DATA

DllName = ValName DATA

DllObject @4 NONAME, PRIVATE

DllRegi

 

 

- 위의 그림 3과 같이 모듈정의 파일을 정의했으면 이젠 다음 그림4와 같이 정의 파일을 프로젝트에 포함시켜주자.

 

그림 4) 모듈정의 파일 추가

 

 

- 위의 그림 4와 같이 모듈정의 파일을 추가했으면 다시 한 번 빌드해줍니다. 그러면 이번에는 lib파일과 exp파일이 생성되었음을 알 수 있습니다. 아래의 그림은 dumpbin으로 다시 한 번 DLL의 익스포트 정보를 확인해 보겠습니다.

 

그림 5) DLL 익스포트 정보

 

 

- 그림 2와 비교하여 이 번에는 예제소스의 함수들이 제대로 익스포트되어 출력됨을 알 수 있습니다. 실행방법은 생성된 lib파일을 실행할 프로젝트에 포함시키고 컴파일 시키시면 됩니다. 아래 그림 6은 실행화면 설명입니다. 소스는 실행파일 소스입니다.

 

 

Ex2) 실행파일 소스 1

#include <windows.h>

 

#define DLL_NAME "DllExam_01.dll"

 

void ChkPrint(LPCTSTR str);

int IntSum06(int a, int b);

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst,LPSTR lpCmdLine,intnShowCmd)

{

    HMODULE hModule;

    TCHAR buf[512];

 

    hModule = LoadLibrary(DLL_NAME);

 

    

    wsprintf(buf,"hModule = 0x%8x\n",hModule);

    ChkPrint(buf);

    

 

    wsprintf(buf,"hModule = %d\n",IntSum06(5,6));

    ChkPrint(buf);

 

 

    return 0;

}

 

    void ChkPrint(LPCTSTR str)

{

    MessageBox(HWND_DESKTOP,str,"OK",MB_OK);

}

 

 

그림 6) 모듈정의 파일 방법실행

 

 

2) __dlclspec(dllexport) 지시자 사용

- MS에서는 컴파일러에서 익스포트할 함수나 변수의 이름을 자동으로 생성한 다음에 ,LIB 파일에 포함시킬 수 있도록 하였는데 그 때 쓰이는 지시자가 __declspec이다. dexlspec(dllexport) 지시자를 사용하여 dll에서 데이터, 함수, 클래스 또는 클래스 멤버 함수를 내보낼 수 있도록 변경할 수 있다. 그리고 __declspec(dllexport)와 쌍으로 사용되는 키워드가 __declspec(dllimport)이다. __declspec(dllexport)은 컴파일러로 하여금 해당 함수가 익스포트될 것임을 알려준다. 반대로 __declspec(dllimport)는 컴파일러로 하여금 해당 함수가 임포트되는 함수임을 미리 알려주는 역할을 한다. 소스파일과 헤더파일의 소스는 아래와 같습니다.

 

 

Ex3) 소스 1

#include "Windows.h"

 

#ifdef __cplusplus

#define DLLBASIC_API extern "C" __declspec(dllexport)

#else

#define DLLBASIC_API __declspec(dllexport)

#endif

#include "Dll_declspec.h"

 

void WINAPI DrawTextPos04(HDC hDC, LPTSTR pszText, POINT ptPos)

{

    TextOut(hDC, ptPos.x, ptPos.y, pszText, lstrlen(pszText));

}

 

int WINAPI IntSum06(int a, int b)

{

    return a+b;

}

 

BOOL WINAPI IsPointInRect11(RECT rcRgn, POINT ptPos)

{

    return PtInRect(&rcRgn, ptPos);

}

 

 

Ex4) 헤더 1

#ifndef __DLLBASIC_H__

#define __DLLBASIC_H__

 

#ifndef DLLBASIC_API

#ifdef __cplusplus

#define DLLBASIC_API extern "C" __declspec(dllimport)

#else

#define DLLBASIC_API __declspec(dllimport)

#endif

#endif

 

DLLBASIC_API void WINAPI DrawTextPos04(HDC hDC, LPTSTR pszText, POINT ptPos);

DLLBASIC_API int WINAPI IntSum06(int a, int b);

DLLBASIC_API BOOL WINAPI IsPointInRect11(RECT rcRgn, POINT ptPos);

 

#endif    //__DLLBASIC_H__

 

- 위의 소스가 정의문들이 많아 약간 어려워 하실 분들도 있을 수 있겠지만 실상은 상당히 간단한 문장입니다..

__cplusplus와 extrn "C"는 함수 호출 방법에 따른 스택정리와 이름 데코레이션의 관계때문에 정의해 준 부분입니다. 이 부분을 설명하자면 이번 포스팅의 범위를 넘어가니 이 부분은 따로 관계된 문서를 찾아 보시길 바랍니다.

아래 그림과 같이 소스 파일과 헤더파일을 추가 시키고 빌드 하면 위이 파일정의 모듈방식과 동일하게 LIB파일이 생성됨을 알 수 있습니다. 이 파일을 dumpbin으로 출력해보면 위의 그림 5와 같이 익스포트 정보들이 출력됨을 알 수 있습니다.

 

그림 9) declspec 빌드

 

그림 10) dumpbin 출력화면

 

반응형

+ Recent posts