Dynamic API Hooking Architecture

Written by X-Type

User Level (ring 3)

 

목차

머리말

API Hooking…?

Dynamic API Hooking…?

API 후킹 방식

동적 API 후킹의 구현

문제 해결하기

코딩하기

결론

참고 자료

 

머리말

글에서는 Windows NT 9x 대해 정적(Static) 아닌 동적(Dynamic) API Hooking 하는 방법에 대해 자세히 알아보고자 한다.

 

API Hooking?

본론으로 들어가기 전에 먼저 API Hooking 무엇인지 짚고 넘어가자.

API Hooking 어떤 특정한 프로그램 또는 시스템 전역에서 API 호출되는 것을 가로채는 것을 말한다. (그러나 반드시 API 대상으로 하지는 않는다) 개발자들이 문제에 도전하는 이유는, API Hooking 해서 얻는 것이 많기 때문이다. API 호출을 가로챈다면, 주어진 인자 또는 리턴값을 수정하여 프로그램의 동작을 제어할 있게 된다. 또한 API 호출을 로그(Log) 남긴다면, 프로그램이 어떤 식으로 동작하는지 있다. (의외로 효과는 크다)

그렇다면, 정적 API Hooking 무엇이고 동적 API Hooking 무엇인가? 둘의 차이는 API 어느 정도 후킹할 있는지의 차이이다. 정적 API 후킹의 경우, 특정한 코드로 짜여져서 특정 API 후킹하는 것이다. 동적 API 후킹은 런타임(Run-Time)에서 사용자의 요구에 따라 API 상황에 따라 다르게 후킹하도록 것이다.

 

Static API Hooking(정적 API 후킹)

Dynamic API Hooking(동적 API 후킹)

구현이 쉽다.

구현이 다소 어렵다.

후킹할 API 관리하기가 까다롭다.

(코드를 작성하고 다시 컴파일해야 )

유저에 의해 후킹할 API 관리된다.

(, 런타임상에서 관리된다)

API 따라서 가로챈 후의 동작이 다양하게 구현되는 경우가 많다.

API 후킹하는 목적에 따라 가로챈 후의 동작이 고정되어있는 경우가 많다.

<동적 API 후킹과 정적 API 후킹의 차이>

 

동적 API 후킹은 불안전한 면을 다소 가지고 있다. 예를 들어, 유저가 인자 개수를 틀리게 작성해주는 경우가 있다. 이럴 경우에는 스택이 잘못 정리되므로, 후킹 도중에 프로그램이 튕기게 된다. (디스어셈블러를 이용하여 보정해 수는 있다. 그러나 완벽하지는 않다.)

 

Dynamic API Hooking?

이번에는 동적 API 후킹이 무엇인지 알아보자. 정의에 관해서는 앞에서 말했기 때문에, 여기서는 구현 방식에 대해 간단하게 설명하도록 하겠다.

우선 정적 API 후킹 방식에 대해 이해해야 한다. 정적 API 후킹은 이런 방식으로 작성된다:

 

typedef int (WINAPI *API_MESSAGEBOX)(HWND, LPCSTR, LPCSTR, UINT);

int WINAPI MessageBoxAProc(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);

API_MESSAGEBOX pfnOriginalMessageBoxA;

 

LPVOID HookAPI(PROC Api, PROC ApiProc, DWORD Argc){

// API 후킹 구현

}

 

void main(){

             pfnOriginalMessageBoxA = HookAPI(

                                                                  GetProcAddress(LoadLibrary(“User32.dll”), “MessageBoxA”),

                                                                  (PROC)&MessageBoxAProc,

                                                                  4

                                                                  );

}

 

int WINAPI MessageBoxAProc(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType){

            

             return pfnOriginalMessageBoxA(hWnd, lpText, lpCaption, uType);

}

 

이런 식으로 API 후킹 프로시저(API 후킹을 가로챈 뒤에 어떠한 작업을 해주는 함수) 선언, 정의하고 API 후킹을 시도하는 것을 있다. 그렇다면 API 프로시저를 런타임에서 생성할 있다면 동적 API 후킹이 되는 것인가? 가능은 하겠지만 비효율적인 면이 있다. C/C++ 코드는 옮기면 깨질 가능성이 크기 때문에 어셈블리로 짜야 한다. 또한 스택 정리를 위해 함수가 약간씩 수정되어야 한다. ( 과정에서 디스어셈블러가 필요하다.) 1)API 프로시저의 크기가 크다면 이러한 동적 API 후킹 방식은 매우 비효율적인 방법이 수밖에 없다. 그렇다면 어떤 식으로 구현되어야 하는가? 필자는 동적 API 후킹을 하려면 함수의 동적인 생성(복사) 필연적이라고 생각한다. 2) 만약에 함수를 런타임에서 생성시킨다면 크기를 최소화하는 것이 좋을 것이다. 그러나, API 후킹 프로시저를 복사하는 것은 앞에서도 말했듯이 비효율적이다. 그렇다면 어떻게 해야 할까? 필자는 API 프로시저를 하나만 두고도 여러 가지 API 후킹이 가능하도록, 동적으로 생성된 여러 작은 함수들과, 1개의 내부3) API 프로시저로 외부4) API 프로시저를 연결시켰다. 과정이 약간 어렵다. 왜냐하면 순수 C/C++로는 구현이 불가능하기 때문이다. , 어셈블리를 써야 한다. 자세한 것은 다음에 말하도록 하겠다.

 

1)          이것을 구현하기에는 문제가 많다. C/C++에서 함수를 호출하면 보통 컴파일러가 절대 주소 참조를 하지 않고 상대 주소 참조를 한다. 이러한 코드는 코드의 위치를 옮기게 되면 모두 깨진다. Volatile 키워드가 붙은 함수 포인터를 사용하여 절대 주소 참조로 만들 수는 있다. (그러나 방법으로는 속도, 용량에 모두 손해를 본다.) 그리고 컴파일러에 따라서 같은 함수라도 어셈블리 상의 구현이 다를 있다. , 함수의 크기가 일정하지 않기 때문에 주의를 요한다.

2)          다수의 API 가로채어 같은 API 프로시저에 오게 한다면 API 구분할 없다. API 구분할 있게 하는 루틴이 API마다 필요하다.

3)          필자는 API 후킹이 쉽도록 API 후킹 객체를 작성하고 있다. , 객체 내부에서 구현하고 있는 API 프로시저를 말한다.

4)          객체 바깥에서 구현한 API 프로시저를 말한다.

 

API 후킹 방식

API 후킹하는 방법에 대해 간단히 알아보자. 동적인 후킹이든 정적인 후킹이든 모두 일정한 API 후킹 방법을 기초로 하여 작성된 것들이기 때문에 알아둘 필요가 있다.

DLL 필요한 방법은 DLL 이용하여 다른 프로세스의 메모리에 침투할 필요가 있는 방법들이다.

 

a. Hook Imported Function

방법은 임포트 테이블(Import Table)이라는 것을 수정하는 방법이다. 프로그램이 특정 API1) 호출하는 코드를 작성했다면, 보통 API 주소를 담고 있는 테이블을 이용하여 API 호출하는데 테이블을 수정하여 API 후킹을 시도하는 것이다. 방법은 특정 모듈2) 대해서만 API 후킹이 가능하다. 또한 임포트 테이블을 통하지 않고 API 주소를 알아내어 함수를 호출할 있으므로, 3) 모든 API 호출이 후킹되지 않는다. 그러나 방법은 다른 방법에 비해 비교적 간단하고 안정적으로 동작한다.

 

1) API뿐만 아니라 외부 DLL에서 export 모든 함수들이 대상이다.

2) 모듈(Module): 프로세스에 로드(load) 프로그램(exe, dll, …) 말한다.

3) LoadLibrary, GetProcAddress API 이용하면 API 주소를 알아낼 있다.

 

예를 들어 보자.

 

GetModuleHandleA(NULL);

00404663 6A 00                push        0

00404665 FF 15 50 12 42 00    call        dword ptr [__imp__GetModuleHandleA@4 (00421250)]

 

여기서 __imp__GetModuleHandleA@4 , 메모리 주소 0x421250 값을 읽어서 호출하는 것을 있다. 0x421250 값을 수정한다면, 함수의 호출을 가로챌 있을 것이다. (자세한 구현 방법은 찾아보면 나온다. 찾기가 귀찮다면 이곳(1) 참고하기 바란다.)

 

Windows에서는 각각의 프로세스의 메모리가 분리되어 있기 때문에, 다른 프로세스의 임포트 테이블을 건드리려면 DLL 대상 프로세스에 삽입(Inject)하여야 한다. DLL 삽입하지 않게 수도 있지만 구현이 훨씬 복잡하게 된다. 여기서는 DLL 삽입(DLL Injection) 관한 방법은 다루지 않는다. 이것에 관한 부분은 API Hooking Revealed(2)에서 찾아볼 있고, CreateRemoteThread( API NT 계열에서만 구현되어 있다. 그러나 9x에서도 가능하다) 이용한 DLL Injection 기법은 Remote Library(3)에서 모든 Windows 대해(9x, NT, …) 다루고 있다.

 

b. Trampoline

방법은 API 함수 자체의 코드를 변경시키는 방법이다. 그래서 구현이 까다롭다. 방법은 API 함수의 부분에 jmp 명령어를 넣어서 API 프로시저로 오게 하여 호출을 가로채는 방식이다. 원본 API 호출하기 위해서 복잡한 과정을 필요로 한다. 이렇게 복잡하지만 프로세스 내의 모든 API 호출이 후킹되는 장점이 있다. 타겟 API jmp 명령어가 들어갈 있는 충분한 크기를 가져야 하기 때문에, 함수의 크기가 jmp 명령어의 크기인 5 바이트보다 작을 경우 후킹할 없다는 단점이 있다.

 

예를 들어 보자.

 

// FindWindowA API 원본 (편의상 뒷부분을 생략했다)

77CFE8FF 33 C0                xor         eax,eax

77CFE901 50                   push        eax

77CFE902 FF 74 24 0C          push        dword ptr [esp+0Ch]

77CFE906 FF 74 24 0C          push        dword ptr [esp+0Ch]

// FindWindowA API 후킹

77CFE8FF E9 DC 2F C1 88       jmp         009118E0

77CFE904 90                   nop

77CFE905 90                   nop

77CFE906 FF 74 24 0C          push        dword ptr [esp+0Ch]

// Trampoline 함수 (원본 API 이것을 통해 호출)

00911920 33 C0                xor         eax,eax

00911922 50                   push        eax

00911923 FF 74 24 0C          push        dword ptr [esp+0Ch]

00911927 E9 DA CF 3E 77       jmp         77CFE906

 

어떤 방식인지 감이 것이다. 주의할 점이 있다면 jmp 명령어를 삽입할 원본 명령어 크기를 고려하여 nop 넣어주어야 한다는 것이다. Jmp 명렁어의 크기인 5바이트만을 복사하게 되면 위의 함수와 같은 경우에는 명령어가 깨질 수가 있다.

 

77CFE8FF E9 DC 2F C1 88       jmp         009118E0

77CFE904 24 0C                 and al, 0Ch (잘못됨)

77CFE906 FF 74 24 0C          push dword ptr [esp+0Ch]

 

함수의 경우, 이렇게 잘못 수정되면 인수 하나가 넘겨지지 못하게 된다. Access Violation(엑세스 위반) 발생할 것은 뻔하다. 그러나 디스어셈블러를 쓴다면 문제를 해결할 있다. 디스어셈블러 없이 구현하려면 원본 API 호출시 잠깐 API 복원했다가 다시 패치해주는 방법이 있으나 API 복원되었을 , 다른 쓰레드에서 API 호출한다면 후킹이 되지 않는 문제가 발생한다. 이런 방법은 멀티쓰레드 환경에 맞지 않다.

 

방법도 마찬가지로 방법으로 다른 프로세스의 API 호출을 잡아내려면 DLL 삽입해야 한다.

( 방법의 자세한 구현 방법은 뒤에서 말할 것이다.)

 

c. Debugging

방법은 디버거를 이용하는 것이다. 방법은 DLL 사용하지 않는 방법이다. 우선, 대상 프로세스에 디버거를 활성(Active)시킨다. 다음에 함수 부분에 브레이크 포인트(BreakPoint, 중단점) 심어둔다. 함수가 호출되면 EXCEPTION_BREAKPOINT 디버그 이벤트가 발생하며 모든 쓰레드가 중지된다. 제어권이 넘어오게 되는데 이때 각종 작업을 해주는 것이다. 방법의 경우 프로세스 통신 구현이 따로 필요가 없다. 단점은 많은 편이다. 우선 느리고, 디버거를 종료시키면 디버깅되는 프로세스도 같이 종료되어 버린다. 중단점에 이르면 모든 쓰레드가 멎어버리기 때문에 예기치 않은 상황이 발생할 가능성을 배제할 없다.

 

대상 프로세스를 디버그시킨다. -> 중단점(int 3) 심는다. -> 중단점 디버그 메시지를 받는다. -> (작업) -> API 복원 -> 중단점(int 3) 다시 심는다.

 

요약하자면 이정도가 된다.

 

(참고) Win9x API Hooking

Windows 9x(95, 98, Me)에서는 API 후킹이 더더욱 어렵다. System Dll(Kernel32.Dll, User32.dll, Gdi32.dll, …)들은 Windows 9x에만 존재하는항상 공유되는 메모리 영역 맵핑되어있기 때문에 문제가 발생한다. Windows 9x에서는 가상 메모리 0x80000000 ~ 0xC0000000 영역은 모든 프로세스에서 동일하다. , System Dll API 코드를 수정하였을 경우에 문제가 된다. 모든 프로세스에 적용되기 때문에, 기존의 Trampoline API 후킹을 사용하면 에러가 난다. 기존의 디버거를 이용한 방법은 모든 프로세스에 중단점이 존재하지만 디버그하는 프로세스는 하나이다. 만약에 디버그 중이 아닌 프로세스에서 해당 API 호출하게 되면, 중단점에서 프로그램이 튕겨버린다. 중단점도 인터럽트1) 중의 하나이기 때문에, 핸들링되어있지2) 않으면 기본 핸들러3) 프로세스를 강제 종료시켜 버린다. 7) 이런 이유로, 필자가 아는 바로는 9x에서 안전한 동작을 보장할 있는 API 후킹 방법은 Hook Imported Function 하나밖에 없다.

 

참고로, Windows 9x에서는 공유 메모리 영역의 Protection 변경할 없다. 공유 메모리에 위치한 System Dll 코드는 Protection 읽기 전용이기 때문에 수정이 불가능한 것처럼 보인다. 그러나 int 0x2E4), VxD Service5) 이용하면 수정할 있다. (자세한 내용은 뒤에 언급하겠다)

 

1) 인터럽트(Interrupt): 예외 상황이 발생하더라도 계속해서 작업이 가능하도록 하는 운영체제의 기능. 예외 처리 외에도 다른 기능이 있다.

2) 핸들링(Handling): 예외 상황을 계속해서 작업이 가능하도록 유도하는 작업.

3) 핸들러(Handler): 예외 상황에 대응하는 루틴. 기본 핸들러(Default Handler) 핸들링이 되어있지 않을 때에 호출된다.

4) Int 0x2E: 인터럽트 번호 0x2E 인터럽트를 말한다. NT 2000 ring 06) 서브루틴을 호출할 사용된다. (9x에서도 일부가 지원된다)

5) VxD Service: 운영체제 내부 구현이나 드라이버 구현에서 쓰인다. VxD(Virtual x Device) 제공하는 서비스를 말한다.

6) Ring 0: Intel 계열의 CPU 지원하는특권 레벨” 0 말한다. 특권 레벨은 0부터 3까지 있으며, 0 가장 높고, 3 가장 낮다. Windows 0 3만을 사용하는데, 흔히 ring 0 커널 모드, ring 3 유저 모드라고 부른다.

7) 아직 시험해 적이 없어서 확신할 없다.

 

동적 API 후킹의 구현

이번에는 동적 API 후킹이 어떤 식으로 구현되는지 알아보자. 앞으로 말할 내용은 동적 API 후킹 방법 중에 가지 방법일 뿐이며, 다른 방법도 있다는 것을 명심하기 바란다. 필자는 Trampoline API 후킹 방식을 기초로 한다. (하지만 다른 후킹 방법을 있다) 다수의 API 후킹하되 하나의 후킹 프로시저로 연결되도록 하는 것이 핵심이다. 그렇다면, 후킹 프로시저는 호출된 API 무엇인지 구분할 있어야 한다. 따라서 동적 API 후킹에는 후킹된 API 관한 정보를 저장해둘 필요가 있다. 하지만, 어떻게 자료를 넘겨야 할까? 필자는 API 호출된 직후에 eax 레지스터를 이용하여 자료의 포인터를 넘기는 방법을 선택했다. (필자는 역할을 하는 코드를 API Specifier라고 명명했다) 다음에는 필자는 유저 프로시저에 직접 데이터의 포인터를 넘기는 방법을 택했다. (유저 프로시저를 C/C++ 코드로 작성하기 위해서는 방법이 가장 안전하다) 그러면 유저 프로시저를 연결시켜주는 과정에 대해 생각해 보자. 필자는 과정 작성하기 전에 미리 가지를 정해두었다.

 

-1. 구현하는 필요한 어셈블리 코드를 최소화한다.

- 2. 유저 프로시저에는 최소한의 데이터만을 넘긴다.

- 3. 함수의 복사는 최대한 사용하지 않는다.

 

어셈블리 코드는 크기가 커지면 유지/보수가 곤란하다. 유저 프로시저에 데이터를 많이 넘기면 넘길수록 속도에 손해를 보게 되며, 함수의 복사는 안전하지 못하다. (구현하기도 어렵고 메모리를 쓸데없이 먹는다.)

API Specifier 유저 프로시저로 연결시켜주기 위해 특정한 함수(Connector) 점프한다. Connector에서는 일단 데이터 포인터(eax) 스택에 백업한다. Eax 레지스터는 차후 리턴값으로 쓰이게 되기 때문이다. 다음으로, 유저 프로시저를 실제로 호출하는 함수(Linker) 따로 두게 되면 함수(Linker) 구현을 좀더 자유롭게 있게 되고, Connector 함수의 코드의 양을 어느 정도 줄일 수가 있다. 따라서 Connector Linker 함수를 호출한다. Connector Linker에서 반환한 값으로 리턴을 한다.

Connector API 넘겨진 인수를 쓰지 않는다. User API Procedure API 넘겨진 인수를 쉽게 조작할 있도록, 이를 위해 Connector 번째 인수의 포인터를 후킹 데이터와 함께 Linker 넘겨주는 방법을 택했다. (어셈블리 코드를 줄이기 위해서)

문제는 스택 정리이다. API stdcall 호출 규약1) 사용하며 API 마다 인수의 개수가 약간씩 다르다. 그러므로 API마다 맞게 스택을 정리해줘야 한다. 유저 프로시저에 연결시켜주는 함수를 복사해서 스택 정리 부분만 수정시켜주는 방법은 곤란하다. (, Connector 복사하는 방법) 메모리 효율이 떨어지기 때문이다. 필자는 스택을 정리하는 부분만을 API마다 따로 두고(필자는 API Specifier 두었다) Connector 사용한 스택을 모두 정리한 , 데이터에 포인터로 기록되어 있는 스택을 정리하는 부분으로 점프한다. (이것 때문에 Connector C/C++ 구현될 없다) 이렇게 위의 조건을 최대한 따르면서 API 후킹을 구현해 보았다. 지금까지의 과정을 요약하면 다음과 같다.

 

(구현 과정을 알아보기 쉽도록 C 코드와 어셈블리를 섞어 썼다.)

참고: cdecl 호출 규약1) 가진 모든 함수에서의 스택 정리는 ret 0이나 ret 하면 된다.

 

API Specifier, Connector 모두 어셈블리로 작성되며, Linker C/C++ 작성해도 무방하다.

참고로 리턴 과정 이러하다.

User Proc -> Linker -> Connector -> API Specifier -> (End)

위에서는 원본 API 호출하는 것이 보이지 않는다. 그것은 User API Procedure 몫이다. 데이터를 통해 Trampoline 함수의 포인터가 제공되며, User API Procedure에서는 그것을 이용하여 원본 API 호출할 있다. ( 과정은 인라인 어셈블리를 필요로 하므로 따로 함수를 만들어 두는 것이 좋다.)

 

주의: User API Procedure에서 후킹된 API 호출할 경우에도 위의 순서대로 수행되어 무한 루프 빠질 있다. 해결책은 뒤에 언급하겠다.

 

1) 호출 규약: 함수를 어떻게 호출할 것인지 정해놓은 일종의 약속이다. stdcall 호출 규약은 함수에 인수를 넘기는 스택을 함수 내에서 정리하는 호출 규약을 말한다. 반대로 cdecl 호출 규약은 함수를 호출한 쪽에서 스택을 정리하는 것이다.

 

여기까지의 구현으로는 Windows NT에서만 동작한다. Windows 9x에서 System DLL API 호출을 가로채려면 복잡한 구현이 필요하다.

그렇다면 Windows 9x에서는 어떻게 해야 하는지 알아보자.

 

우선, System DLL API 수정하는 방법에 대해 알아보자.

Windows 9x VxD 서비스 중에 RtlCopyMemory라는 것이 있다. 이것을 이용하면 읽기 전용의 메모리 영역이라도 메모리 입출력을 있다.

 

push nSize                                    // 원본, 대상의 크기

push pSrc                                      // 원본 (Source) 메모리 주소

push pDest                                   // 대상 (Destination) 메모리 주소

mov edx, esp                                // edx: 인수 포인터

mov eax, 0x10A                            // eax: RtlCopyMemory 서비스 번호

int 0x2E                                        // RtlCopyMemory(pDest, pSrc, nSize)

add esp, 12                                  // 스택 정리

 

간단한 어셈블리 코드이다. 우선 인수들을 넘기고 가장 첫번째 인수의 주소를 edx 레지스터에, 서비스 번호를 eax 레지스터에 넘기고 인터럽트 0x2E 발생시키면 nSize만큼의 pSrc pDest 복사된다. 참고로, 원본이나 대상의 메모리를 읽을 없을 경우 무서운 블루스크린 뜨니 코드를 실행하기 전에 VirtualQuery함수로 해당 메모리가 존재하는지 체크하는 것이 좋다. 또한 코드는 NT에서 동작하지 않는다. (NT에서는 필요가 없겠지만)

 

하지만 공유 영역에 있는 System DLL API 수정하면 모든 프로세스에 적용되기 때문에 추가로 구현해주어야 것이 있다. 일단은 현재 프로세스가후킹되었는지검사해야 한다. 만약 후킹되었으면 API Specifier jump하고, 후킹되지 않았으면 Trampoline 함수로 jump하면 된다. 일단 후킹된 프로세스 리스트(9x Stub Data), API Specifier, Trampoline 함수를 공유 영역에 할당할 필요가 있다.

 

주의: System DLL 공유 메모리 영역에 위치하기 때문에 수정에 신중을 기하여야 한다. 잘못된 코드를 실행하게 되면 실행중인 모든 프로세스가 튕기는 현상이 발생한다. 또한 모든 작업을 마친 코드를 원래대로 돌려놓지 않을 경우에도 문제가 발생할 있다.

 

메모리를 직접 할당하는데 쓰이는 VirtualAlloc함수의 fAllocationType에는 VA_SHARED라는 Flag 있어서 이것을 쓰면 공유 영역에 메모리를 할당할 있다. (NT에서는 당연히 안된다) VA_SHARED 값은 0x8000000이다. 이렇게 할당해주면 것이다.

 

#define VA_SHARED 0x8000000

LPVOID pSharedData = ::VirtualAlloc(NULL, dwSize, fAllocationType | VA_SHARED, flProtect);

 

이렇게 공유 영역에 할당한 데이터를 프로세스 검사 루틴에 넘겨주기 위해서는 마찬가지로 eax 레지스터를 통해 넘겨주면 된다. 1) 이렇게 넘겨진 데이터로 후킹되었는지를 검사해서 적절히 jump하면 된다. 주의할 점은 Trampoline 함수는 후킹되지 않은 프로세스에서도 쓰이므로 공유 영역에 할당3)하고, API Specifier 프로세스별로 다르게 할당2)되어 있으므로 프로세스 리스트와 묶어서 관리해야 한다. 그리고 이렇게 할당한 데이터를 해제하기 위해서는 개의 프로세스가 후킹되었는지 저장해둘 필요가 있다. 후킹을 해제할 마지막 후킹된 프로세스일 경우에만 공유 메모리에 할당된 데이터를 최종적으로 해제해야 한다.

 

1) 필자는 이러한 역할을 수행하는 코드를 9x Specifier라고 이름 붙였다. 9x Specifier 모든 프로세스에서 유효해야 하므로 공유 메모리에 할당되어야 한다. 또한 프로세스 검사 루틴 역시 공유 메모리에 위치해야 한다)

2) 이것도 공유 메모리에 할당할 필요는 없다. 공유 메모리에 할당하는 것은 좁은 공유 영역을 낭비하는 것이다.

3) Trampoline 함수, 9x Specifier 함수는 공유 영역에 위치하기 때문에 한번만 할당해도 여러 프로세스에서 써먹을 있다.

 

정리하자면, 공유 메모리에 할당되어야 것들은 9x Specifier, Trampoline 함수, 프로세스 검사 루틴, 프로세스 리스트이다.

프로세스 리스트에 포함될 데이터는 Trampoline 함수 주소 ( 프로세스 공통), 후킹된 프로세스 개수, API Specifier 포함된 프로세스 리스트이다.

프로세스 검사 루틴은 공유 메모리에 할당되어야 하기 때문에 함수를 복사해야 한다. 안전한 복사를 하려면, 모두 어셈블리로 짜야 하고, 직접적인 jmp, call 구문을 쓰지 않아야 한다. 주소값을 담고 있는 포인터나 레지스터를 이용해서 jmp, call 해야 한다.

 

프로세스 리스트에서, Windows 9x에서는 프로세스 ID 대신 프로세스 Database 포인터를 있다. 번거롭게 GetCurrentProcessId 호출하는 것보다 훨씬 구현이 쉽다.

 

LPVOID pCurrentProcessDatabase;

__asm{

mov eax, fs:[0x30]                                   // Current Process Database = fs:[0x30]

mov [pCurrentProcessDatabase], eax

}

 

이런 식으로 현재 프로세스의 데이터베이스 주소를 구할 있다.

지금까지의 과정을 그림으로 나타내 보면 이러하다.

위의 그림에서는 빠뜨렸는데, 프로세스 체크 함수도 공유되어야 한다. (위에도 언급했었지만)

 

주의: 프로그램이 예기치 못한 에러로 종료될 경우, API 제대로 복원되지 않아 문제를 발생시킬 있다. (메모리 누수, 전체 프로세스 종료 현상) 따라서 SetUnhandledExceptionFilter 써서 예외 상황에서 API 복원해 주는 것이 좋다. 방식으로 예외 처리를 , API 후킹을 객체로 경우, 인스턴스를 개로 제한할 필요가 있다.

 

문제 해결하기

이번에는 위에서 언급했던 문제에 관해 알아보자.

첫번째로 API 코드 덮어쓰기에 문제가 있었다. 명령어가 깨지는 문제이다. 디스어셈블러를 이용하여 API 명령어 단위로 분석하여 최소 5 바이트를 확보하면 문제는 해결된다. 디스어셈블러는 어떻게 써야 하는 것인가? ……물론 직접 만들거나 다른 디스어셈블러 소스를 쓰면 된다. 디스어셈블러를 만들려면 기계어의 해독 방법을 알아야 한다. 별로 어렵지는 않다. CPU 제조 회사 홈페이지에 찾아가면 어렵지 않게 관련 문서를 구할 있다. (모두 영어지만) 필자가 하나 번역해 (4) 있으니 참고하기 바란다.

두번째로 User API Procedure 의해 무한 루프에 빠져버리는 문제이다. 먼저 생각해 있는 방법은 API 후킹되었는지 확인하고, 후킹되었으면 Trampoline 함수를 호출하고, 그렇지 않으면 원본 API 바로 호출하는 것이다. API 후킹 데이터와 일일이 비교하는 방법도 있으나, 필자의 후킹 방식을 경우에, API 부분에 jmp 있는지 확인하고, jmp 있으면 주소를 추적한다. 주소가 Specifier라면, (mov 시작하고, 다음 명령어가 jmp인지 체크해본다.) 데이터 주소를 얻어낼 있다. 이때 데이터를 이용하여 Trampoline 함수의 주소를 알아내고 그것을 호출하는 것이다. 물론 후킹된 API 아니면 그대로 호출해주면 된다. 모두 인라인 어셈블리를 필요로 한다. 그러나 방법으로는 겉으로 보이는 호출만 막을 있지 다른 라이브러리를 통한 API 호출은 막을 없다.1) 자세한 방법은 뒤에 언급할 것이다. 다른 방법으로는 리턴 주소를 체크하여 API 프로시저에서 호출한 것이라면 바로 원본 API 호출하고 리턴하는 방법이 있다. 2)

 

1)          예를 들어서 malloc같은 함수가 있다. 내부적으로 HeapAlloc API 호출한다. 만약에 API 후킹 프로시저에서 malloc 호출하고 HeapAlloc 함수가 후킹되어 있다면 방법으로는 막을 없다.

2)          방법도 마찬가지다. 라이브러리를 정적 링크하고 DLL 전체에서 검사한다면 막을 수는 있다.

3)          그러나 위의 방법 모두 Win9x에서 디버깅되는 프로세스에서는 통하지 않는다. Win9x에서 디버그중인 프로세스에서는 직접적으로 System DLL API 호출하지 않고 간접적으로 API 호출하도록 Windows에서 조치를 취하기 때문이다.

 

코딩하기

이제 코딩을 보자. 일단 누구나 쉽게 API 후킹을 있도록, 객체로 짜는 것이 좋겠다.

우선 필요한 데이터들을 알아보자. (1 바이트 구조체 정렬을 했다)

 

typedef struct _API{

             api_hook *HookObj;                     // 00      Hooking Object

             const _APIHOOK *HookInfo;         // 04      Hooking Information

             void *ApiCaller;                             // 08      Return Address               

}API;

 

API 프로시저에 넘겨질 데이터이다. 후킹 객체, API 정보, 리턴 주소(함수 수행이 끝났을 리턴되는 ) 있다.

후킹 객체는 API 정보에도 담겨 있지만, 편의상 제공했다.

 

typedef struct _APIHOOK{

             PROC fnTrampoline;         // 00      trampoline 함수

             PROC fnApi;                     // 04      원본 api 함수

             PROC fnSpecifer;             // 08      API Specifier

             APIPROC fnProc;              // 12      User API procedure

             DWORD fnArgc;                // 16      API 인수 개수

             DWORD fnApiOffset;        // 20      API 코드에 덮어쓴 크기

             LPVOID UserData;           // 24      user data

             api_hook *HookObj;        // 28      후킹 객체

             _9XSTUB *pStub;            // 32      stub data (9x)

             DWORD idxDat;               // 36      pStub->Data[idxDat]

             PROC fn9xSpecifier;         // 40      9x Specifier

}APIHOOK;

 

API 후킹 정보이다.

 

typedef struct __9XDATA{

             LPVOID pPDB;                  // 00      Process DataBase

             LPVOID pSpec;                // 04      API Specifier

}_9XDATA;

 

typedef struct __9XSTUB{

             LPVOID pTrampoline;       // 00      Trampoline Function (공유)

             DWORD nProcHooked;     // 04      후킹된 프로세스 개수

             _9XDATA Data[1];            // 08(+8n)          프로세스 리스트 본체

}_9XSTUB;

 

9x 데이터이다.

9x data 프로세스를 구분하는데 쓰일 프로세스 데이터베이스(PDB) API Specifier 있다. 여기서 주의할 것은 API Specifier 공유 메모리에 할당되지 않는다는 것이다. (, 프로세스마다 따로 할당)

 

class api_hook{

private:

             static const int procchk_len;         // Process Checker 길이

             static const int stubdata_len;       // Stub Data 크기

             list<APIHOOK> m_data;               // APIHOOK DATA

             disasm m_disObj;                         // Disassembler

            

             // Skeleton Codes

             static BYTE ApiPatchCode[];          // API 덮어쓸 코드

             static BYTE ApiSpecifer[];              // api specifier

             static BYTE Api9xSpecifier[];          // 9x api specifier

 

             PROC m_ProcChecker;                  // 9x process checker (공유)

 

             // 예외 처리

             static LPTOP_LEVEL_EXCEPTION_FILTER pTopFilter;

             static api_hook *pInstance;

             static LONG __stdcall UnhandledExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo);

public:

             static ah_osver osver;                  // Operating System Version Checker

             // simple VirtualProtect

             static inline DWORD VirtualProtect(void *lpAddress, DWORD dwSize, DWORD flNewProtect){

                           DWORD flOldProtect = 0;

                           ::VirtualProtect(lpAddress, dwSize, flNewProtect, &flOldProtect);

                           return flOldProtect;

             }

            

             // Intercept Function Calls (stdcall, cdecl)

             const APIHOOK *InterceptApi(PROC fnApi, APIPROC fnProc, WORD fnArgc, LPVOID UserData = 0);

             const APIHOOK *InterceptCdecl(PROC fnApi, APIPROC fnProc, WORD fnArgc, LPVOID UserData = 0);

 

             // stop Intercepting (stdcall, cdecl)

             int RestoreApi(PROC fnApi);

 

             // api hook data

             const list<APIHOOK> &GetData() const{return m_data;}

 

             // call trampoline function

             int CallTrampolineAPI(const API *api, LPVOID pFirstArg);                  // stdcall

             int CallTrampolineCdecl(const API *api, LPVOID pFirstArg);  // cdecl

 

             // copy memory (9x, NT)

             static void _stdcall _RtlCopyMemory(void *pDest, const void *pSrc, unsigned int nSize);

 

             api_hook();

             virtual ~api_hook();

};

 

클래스 선언문이다. 지금까지 설명한 내용이 모두 들어가 있음을 있다. 부분은 별로 어렵지 않으니 넘어가도록 하겠다. 이제 connector, linker, Process Checker 보자.

 

//////////////////////////////////////////////////////////////////////////

// 내부 함수 정의 부분 (헤더 파일에 포함)

 

// connector에서 데이터를 받아 API 프로시저를 호출하는 linker 함수.

static int __stdcall api_hook_linker(APIHOOK *pSpecApi, PVOID pStackObject){

             // pStackObject -> 첫번째 인수의 주소이다. [첫번째 인수의 주소]-4 리턴 주소를 나타낸다.

             API api = {pSpecApi->HookObj, pSpecApi, (PVOID)(*((DWORD *)((BYTE *)pStackObject-4)))};

 

             return pSpecApi->fnProc(&api, pStackObject);

}

 

#define pos_spec 8                      // APIHOOK에서의 Specifier 위치

#define pos_spec_2 10                // Specifier에서의 ret xx위치

 

// connector 함수 (Specifier->Linker)

static int __declspec(naked) __stdcall api_hook_connector(){

             __asm{

                           // API Hooking 데이터 백업

                           push eax

 

                           // api_hook_linker(pSpecApi, pStackObject);

                           lea ecx, [esp+8]

                           push ecx

                           push eax

                           call api_hook_linker

 

                           // Specifier ret xx 위치 추적

                           mov ecx, dword ptr [esp]

                           mov ecx, dword ptr [ecx+pos_spec]

                           add ecx, pos_spec_2

 

                           // 스택 정리 점프

                           add esp, 4

                           jmp ecx

             }

}

#undef pos_spec

#undef pos_spec_2

 

// Process Checker 함수

// 지역 변수

#define pCurPDB ecx

#define idx edx

#define i edi

#define pScanPDB esi

#define pStub ebp-4

#define local_size 4        // 지역 변수 크기

 

static int __declspec(naked) __stdcall api_hook_proc_checker(){

             __asm{

                           // EAX: APIHOOK::pStub

                           // 레지스터 백업

                           push edx

                           push edi

                           push esi

                           push ebp

                           mov ebp, esp

                           sub esp, local_size

 

                           // pStub = APIHOOK::pStub;

                           mov [pStub], eax

 

                           // pCurPDB = fs:[0x30];

                           mov pCurPDB, fs:[0x30]

 

                           // idx = -1;

                           mov idx, -1

 

                           // i = 0;

                           xor i, i

 

                           // pScanPDB = &pStub->Data[0].pPDB;

                           mov esi, [pStub]

             }

MAIN_LOOP:

             __asm{

                           // to next pPDB

                           add pScanPDB, 8

 

                           // if (pStub->Data[i].pPDB == pCurPDB) goto MATCH_PROC;

                           cmp pCurPDB, dword ptr [pScanPDB]

                           je MATCH_PROC

 

                           // i++

                           inc i

                           // if (i<1024) goto MAIN_LOOP;

                           cmp i, 1024

                           jl MAIN_LOOP

                           // else goto LOOP_END;

                           jmp LOOP_END

             }

MATCH_PROC:

             __asm{

                           // idx = I;

                           mov idx, i

             }

LOOP_END:

             __asm{

                           cmp idx, 0xFFFFFFFF                      // cmp idx, -1

                           je TRAMPOLINE

                           jmp SPECIFIER

                           // if (idx == -1) goto TRAMPOLINE

                           // else goto SPECIFIER

             }

TRAMPOLINE:

             __asm{

                           // eax = TRAMPOLINE FUNCTION

                           mov eax, [pStub]

                           mov eax, [eax]

                           jmp END

             }

SPECIFIER:

             __asm{

                           // eax = API SPECIFIER FUNCTION

                           mov eax, [pScanPDB+4]

             }

END:

             __asm{

                           add esp, local_size

                           pop ebp

                           pop esi

                           pop edi

                           pop edx

                           // jump TRAMPOLINE or SPECIFIER

                           jmp eax

             }

}

 

#undef pCurPDB

#undef idx

#undef i

#undef pStub

#undef pScanPDB

#undef local_size

 

여기까지가 헤더 파일(.h) 들어갈 내용들이다. 주석에 설명이 되어있으니 다음으로 넘어가자.

이제 .cpp 작성해 보자.

 

#define MakePtr( cast, ptr, addValue ) (cast)( (DWORD)(ptr)+(DWORD)(addValue))

// 후킹될 있는 최대 프로세스 개수 ( 수정시 Process Checker 수정 필요)

#define STUB_DATA_COUNT 1024

// 공유 메모리 영역에 메모리를 할당하도록 하는 Flag (VirtualAlloc)

#define VA_SHARED 0x8000000

 

// 운영체제 버전 체크. IsWinxx 형식의 함수들이 있음.

ah_osver api_hook::osver;

// 9x Stub Data 크기

const int api_hook::stubdata_len = sizeof(_9XSTUB)+(sizeof(_9XDATA)*(STUB_DATA_COUNT)-1);

// 9x Process Checker 크기 (직접 측정. VC++ Disassembly 썼다.)

const int api_hook::procchk_len = 75;

 

// 예외 처리

LPTOP_LEVEL_EXCEPTION_FILTER api_hook::pTopFilter = NULL;

api_hook *api_hook::pInstance = NULL;

 

// Api 쓰여질 코드. 명령어 길이를 맞추기 위해서 0x90 (nop-아무 동작도 수행하지 않음) 넣었다.

BYTE api_hook::ApiPatchCode[]={

             0xE9, 0x00, 0x00, 0x00, 0x00,                   // jmp rel32 (Api Specifier)

             0x90, 0x90, 0x90, 0x90, 0x90

};

 

// Api Specifier

BYTE api_hook::ApiSpecifer[]={

             0xB8, 0x00, 0x00, 0x00, 0x00,                   // mov eax, imm32 (data ptr)

             0xE9, 0x00, 0x00, 0x00, 0x00,                   // jmp rel32 (connector)

             0xC2, 0x00, 0x00                                                    // ret xx

};

 

// 9x Api Specifier

BYTE api_hook::Api9xSpecifier[]={

             0xB8, 0x00, 0x00, 0x00, 0x00,                   // mov eax, imm32 (9x stub ptr)

             0xE9, 0x00, 0x00, 0x00, 0x00                    // jmp rel32 (proc_checker)

};

 

여기까지가 선언문이다. 이제 본격적으로 코딩에 들어가 보자.

 

// 예외 처리. 예외 발생시 파괴자 호출 프로세스 종료

LONG api_hook::UnhandledExceptionFilter(struct _EXCEPTION_POINTERS *ExceptionInfo){

    pInstance->~api_hook();

 

    return EXCEPTION_CONTINUE_SEARCH;

}

 

// 생성자

api_hook::api_hook(){

    // 인스턴스 제한

    ASSERT(!pInstance);

 

    // UnhandledExceptionFilter 설정

    pInstance = this;

    pTopFilter = SetUnhandledExceptionFilter(&api_hook::UnhandledExceptionFilter);

 

    // Win9x: Process Checker 공유 메모리에 할당한다.

    if (osver.IsWin9x()){

        m_ProcChecker = (PROC)

                           VirtualAlloc(NULL, procchk_len, MEM_COMMIT | VA_SHARED, PAGE_EXECUTE_READWRITE);

        memcpy(m_ProcChecker, &api_hook_proc_checker, procchk_len);

    }else{

        // WinNT: Process Checker 없음

        m_ProcChecker = 0;

    }

}

 

// 파괴자

api_hook::~api_hook(){

    // UnhandledExceptionFilter 복원

    SetUnhandledExceptionFilter(pTopFilter);

 

    list<APIHOOK>::iterator it = m_data.begin();

 

    // 후킹된 API 복원한다

    while(m_data.size()){

        const APIHOOK &data = *it;

        RestoreApi(data.fnApi);

        it = m_data.begin();

    }

 

    // Win9x: Process Checker 해제한다.

    if (m_ProcChecker){

        VirtualFree(m_ProcChecker, NULL, MEM_RELEASE);

        m_ProcChecker = 0;

    }

}

 

// __stdcall 호출 규약의 함수 호출을 후킹한다.

// fnApi: 후킹할 함수 주소, fnProc: API 후킹 프로시저, fnArgc: 후킹될 함수의 인수 개수, UserData: 사용자 정의 데이터

// 리턴값: API 후킹 데이터

const APIHOOK *api_hook::InterceptApi(PROC fnApi, APIPROC fnProc, WORD fnArgc, LPVOID UserData){

    // 함수가 모두 주어졌는가?

    if (!fnApi || !fnProc) return NULL;

 

    // Win9x: '진짜' API 함수 주소를 얻어 온다.

    // Windows 9x에서는 System DLL 디버깅되는 것을 방지하기 위해,

    // 디버거가 실행중일 때에는 GetProcAddress()

    // 동적 할당된 코드의 주소를 리턴하며, 임포트 테이블의 내용도 모두 바뀐다.

    // 코드의 가장 부분은 API 주소를 push 하는 명령으로 되어 있다.

    if (osver.IsWin9x()){

        if (*(PBYTE)fnApi == 0x68)

        {

            fnApi = (PROC)*(DWORD *)((PBYTE)fnApi + 1);

        }

    }

 

    // 증분 링크 체크

    // Microsoft Visual C++ 실행중에도 코드를 수정할 있게끔

    // 증분 링크를 사용하여 함수의 주소가 실제 함수를 가리키지 않고, 실제 함수로 jmp 하는

    // 명령의 주소로 되어 있다.

    // 참고: System DLL 경우에 무시

    if ((DWORD)fnApi < 0x80000000 && *(PBYTE)fnApi == 0xE9){

        fnApi = (PROC)((int)(fnApi) + (*(int *)((PBYTE)fnApi+1)) + 5);

    }

 

    // 중복 후킹은 에러의 원인 하나다. (중복 후킹 막기)

    // fnApi 이미 후킹되었는가? (데이터 검색)

    list<APIHOOK>::iterator it;

    list<APIHOOK>::iterator end = m_data.end();

    for (it=m_data.begin(); it!=end; it++){

        if (it->fnApi == fnApi)

            return 0;

    }

 

    // list 추가할 데이터

    APIHOOK data;

 

    // 데이터 셋팅

    data.fnArgc = fnArgc;       // 인수 개수

    data.fnApi = fnApi;         // API 주소

    data.fnProc = fnProc;       // API Procedure

    data.UserData = UserData;       // 사용자 정의

    data.HookObj = this;        // 후킹 객체

 

    // API 덮어쓸 코드의 크기를 측정한다.

    // 디스어셈블러를 사용하여 명령 크기 단위로 5 바이트 이상의 공간을 확보한다.

    int WriteSize = 0;

    while (WriteSize < 5)

        WriteSize += m_disObj.GetLineSize(((BYTE *)fnApi)+WriteSize);

 

    // 0x90 (nop) 모자랄 경우.

    // 이론상 에러는 나지 않지만 예외 상황을 대비하여 0 리턴함.

    if (WriteSize > sizeof(ApiPatchCode)) return 0;

    // API 복원을 위해 덮어쓴 코드 크기를 저장해 둔다.

    data.fnApiOffset = WriteSize;

 

    BYTE *TrampCode = new BYTE [WriteSize+5];       // Trampoline 함수 할당

    BYTE *SpecCode = new BYTE [sizeof(ApiSpecifer)];    // api specifier 할당

    BYTE *PatchCode = new BYTE [WriteSize]; // API 덮어쓸 코드 할당

 

    // 메모리 할당 실패

    if (!PatchCode || !TrampCode || !SpecCode){

        delete [] PatchCode;

        delete [] TrampCode;

        delete [] SpecCode;

        return 0;

    }

 

    // PatchCode: jmp SpecCode

    memcpy(PatchCode, ApiPatchCode, WriteSize);

    *((int *)&PatchCode[1]) = SpecCode -((BYTE *)fnApi) -5;

   

    // SpecCode:        mov eax, data

    //          jmp api_hook_connector

    //          ret argc*sizeof(int)

    memcpy(SpecCode, ApiSpecifer, sizeof(ApiSpecifer));

    *((WORD *)(SpecCode+11)) = fnArgc * sizeof(int);

    *((int *)(SpecCode+6)) = ((BYTE *)(void *)&api_hook_connector) -(&SpecCode[6]) -4;

 

    // TrampCode:           <원본 API 코드> (API 복원시에도 활용)

    //              jmp <덮어써지지 않은 API 코드 시작 부분>

    memcpy(TrampCode, fnApi, WriteSize+5);

    TrampCode[WriteSize] = 0xE9;

    *((int *)&TrampCode[WriteSize+1]) = ((BYTE *)fnApi+WriteSize+1) -(&TrampCode[WriteSize+1]) -5;

 

    // 동적 할당한 함수들의 메모리 Protection 재설정

    VirtualProtect(PatchCode, WriteSize, PAGE_EXECUTE_READWRITE);

    VirtualProtect(SpecCode, sizeof(SpecCode), PAGE_EXECUTE_READWRITE);

    VirtualProtect(TrampCode, WriteSize+5, PAGE_EXECUTE_READWRITE);

 

    // 함수가 System DLL 속해 있지 않거나 WinNT 경우

    if (osver.IsWinNT() || (DWORD)fnApi < 0x80000000){

        // 메모리 Protection 변경 코드를 덮어씀. (안정성을 위해 가장 마지막에 덮어씀)

        // DWORD oldProtect = VirtualProtect(fnApi, WriteSize, PAGE_EXECUTE_READWRITE);

        // memcpy(fnApi, PatchCode, WriteSize);

        // VirtualProtect(fnApi, WriteSize, oldProtect);

    // Win9x에서 함수가 System DLL 속해 있는 경우

    }else{

        PVOID pfn9xSpec = 0;    // 9x Specifier

        _9XSTUB *p9xStub = 0;   // 9x Stub Data

        PVOID pfn9xTramp = 0;   // 9x Shared Trampoline Function

       

        // System DLL 함수는 모든 프로세스에 적용된다.

        // 프로세스 별로 API 후킹하려면, Stub 데이터를 한번만 할당해도 된다.

        // 따라서 중복 할당을 막기 위해 Stub 데이터 주소를 추적한다.

        // 9x Specifier 추적 (API 첫부분에 jmp 코드가 있을 경우)

        if (*(PBYTE)fnApi == 0xE9){

            // 9x Specifier

            pfn9xSpec = (PROC)((int)(fnApi) + (*(int *)((PBYTE)fnApi+1)) + 5);

 

            // 올바른 9x Specifier인가? ( 명령이 mov인가?)

            if (*(BYTE *)pfn9xSpec != 0xB8)

                pfn9xSpec = 0;

 

            // 9x Stub Data 추적

            p9xStub = (_9XSTUB *)(*(DWORD *)((BYTE *)pfn9xSpec+1));

        }

        // 9x Specifier 존재하지 않는 경우, 아직 후킹되지 않은 API이다.

        if (!pfn9xSpec){

            // 9x Specifier 할당 (공유)

            pfn9xSpec = VirtualAlloc(NULL, sizeof(Api9xSpecifier),

                                                     MEM_COMMIT | VA_SHARED, PAGE_EXECUTE_READWRITE);

            memcpy(pfn9xSpec, Api9xSpecifier, sizeof(Api9xSpecifier));

 

            // 9x stub data 할당 (공유)

            p9xStub = (_9XSTUB *)VirtualAlloc(NULL, stubdata_len,

                                                     MEM_COMMIT | VA_SHARED, PAGE_EXECUTE_READWRITE);

            memset(p9xStub, 0, stubdata_len);

 

            // Trampoline 함수 할당 (공유)

            // 기존 힙에 할당한 코드를 해제하고 다시 공유 메모리에 할당한다.

            pfn9xTramp = VirtualAlloc(NULL, WriteSize+5,

                                                     MEM_COMMIT | VA_SHARED, PAGE_EXECUTE_READWRITE);

            memcpy(pfn9xTramp, TrampCode, WriteSize+5);

            delete [] TrampCode;

            TrampCode = (BYTE *)pfn9xTramp;

 

            // Trampoline 코드가 옮겨졌으므로 jmp 명령어가 다시 설정되어야 한다.

            *((int *)&TrampCode[WriteSize+1]) = ((BYTE *)fnApi+WriteSize+1) -(&TrampCode[WriteSize+1]) -5;

 

            // 9x Stub Data 설정

            *((DWORD *)(((BYTE *)pfn9xSpec)+1)) = (DWORD)p9xStub;

            // Process Checker 설정

            *((DWORD *)(((BYTE *)pfn9xSpec)+6)) = (int)m_ProcChecker -(int)((BYTE *)pfn9xSpec+6) -4;

        }

 

        int i, idx=-1;

 

        // 프로세스 리스트에서 공간 찾음

        for (i=0; i<STUB_DATA_COUNT; i++){

            if (p9xStub->Data[i].pPDB == 0){ idx = i; break; }

        }

 

        // 프로세스 리스트가

        if (idx == -1) return 0;

 

        // 현재 Process DB 읽어와서 항목에 셋팅한다.

        PVOID PDB;

        __asm{

            mov eax, fs:[0x30]

            mov [PDB], eax

        }

 

        p9xStub->Data[i].pPDB = PDB;

 

        // API Specifier 설정

        p9xStub->Data[i].pSpec = SpecCode;

 

        // API 후킹 데이터에 저장

        data.fn9xSpecifier = (PROC)pfn9xSpec;

        data.pStub = p9xStub;

 

        // 첫번째 후킹 => TrampCode 공유 메모리에 있음 (위에서 할당함)

        if (p9xStub->nProcHooked < 1){

            data.pStub->pTrampoline = TrampCode;

        // 두번째 이상 => TrampCode 힙에 할당됨.

        // 공유된 Trampoline 함수로 설정

        }else{

            delete [] TrampCode;

            TrampCode = (BYTE*)(data.pStub->pTrampoline);

        }

 

        // 프로세스 리스트의 index 저장

        data.idxDat = idx;

 

        // 후킹 횟수 증가

        p9xStub->nProcHooked++;

    }

   

    // API 후킹 데이터에 저장한다.

    data.fnTrampoline = (PROC)TrampCode;

    data.fnSpecifer = (PROC)SpecCode;

 

    // 데이터 추가

    m_data.push_back(data);

   

    // API Specifier 해당 데이터 적용

    *((DWORD *)(SpecCode+1)) = (DWORD)&(m_data.back());

 

    // API 코드를 패치해 준다.

    if (osver.IsWinNT() || (DWORD)fnApi < 0x80000000){

        DWORD oldProtect = VirtualProtect(fnApi, WriteSize, PAGE_EXECUTE_READWRITE);

        memcpy(fnApi, PatchCode, WriteSize);

        VirtualProtect(fnApi, WriteSize, oldProtect);

    }else{

        // Win9x: 첫번째 후킹일때만 패치시켜준다.

        if (data.pStub->nProcHooked <= 1){

            *((int *)&PatchCode[1]) = (BYTE *)(data.fn9xSpecifier) -((BYTE *)fnApi) -5;

            _RtlCopyMemory(fnApi, PatchCode, WriteSize);

        }

    }

   

    // API 패치 PatchCode 해제해 준다.

    delete [] PatchCode;

 

   

    // 새로 패치한 명령어가 적용되도록 명령어 캐쉬를 비운다.

    FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

 

    // API Hooking 성공!

    return (APIHOOK *)(*((DWORD *)(SpecCode+1)));

}

 

// cdecl 호출 규약의 함수 후킹

// 아래와 같이 구현하면 안전하지 않다.

// InterceptAPI 똑같이 구현하되 Specifier 마지막 스택 정리 부분을 ret 0으로 하면 된다.

// 코드의 길이가 길어져서 생략했다.

const APIHOOK *api_hook::InterceptCdecl(PROC fnApi, APIPROC fnProc, WORD fnArgc, LPVOID UserData){

    APIHOOK *pHookDat = (APIHOOK *)InterceptApi(fnApi, fnProc, fnArgc, UserData);

 

    if (!pHookDat) return 0;

 

    // Specifier 스택 정리 부분을 수정한다 -> ret 0

    *((WORD *)((BYTE *)(pHookDat->fnSpecifer)+11)) = 0;

    return (APIHOOK *)(pHookDat);

}

 

// 후킹한 API 복원

int api_hook::RestoreApi(PROC fnApi){

    list<APIHOOK>::iterator it_data;

    list<APIHOOK>::iterator it_end = m_data.end();

   

    // list에서 맞는 데이터를 찾는다.

    for (it_data = m_data.begin(); it_data != it_end; it_data++){

        const APIHOOK &data = *it_data;

        // 데이터가 주어진 함수의 주소와 맞는가?

        if (data.fnApi == fnApi){

            // Win9x System API 아니거나 NT 경우

            if (osver.IsWinNT() || (DWORD)data.fnApi < 0x80000000){

                // API 코드 복원

                DWORD oldp = VirtualProtect(data.fnApi, data.fnApiOffset, PAGE_EXECUTE_READWRITE);

                memcpy(data.fnApi, data.fnTrampoline, data.fnApiOffset);

                VirtualProtect(data.fnApi, data.fnApiOffset, oldp);

 

                // 메모리 해제

                delete [] (BYTE *)data.fnTrampoline;

                delete [] (BYTE *)data.fnSpecifer;

                m_data.erase(it_data);

            }else{

                // 다른 프로세스가 후킹하고 있는 중일 경우

                if (data.pStub->nProcHooked > 1){

                    // 쓰고 있는 프로세스 리스트를 초기화시킨다.

                    memset(&data.pStub->Data[data.idxDat], 0, sizeof(_9XDATA));

                    // 후킹 횟수를 줄인다.

                    data.pStub->nProcHooked--;

 

                    // 메모리를 해제한다.

                    delete [] (BYTE *)data.fnSpecifer;

                    m_data.erase(it_data);

                }

                // 다른 프로세스가 후킹중이지 않은 경우

                else{

                    // API 복원한다.

                    _RtlCopyMemory(data.fnApi, data.fnTrampoline, data.fnApiOffset);

 

                    // 공유 메모리를 해제한다.

                    VirtualFree(data.pStub, NULL, MEM_RELEASE);

                    VirtualFree(data.fn9xSpecifier, NULL, MEM_RELEASE);

                    VirtualFree(data.fnTrampoline, NULL, MEM_RELEASE);

 

                    // 메모리를 해제한다.

                    delete [] (BYTE *)data.fnSpecifer;

                    m_data.erase(it_data);

                }

            }

            break;

        }

    }

   

    return 1;

}

 

// Trampoline 함수 호출 (__stdcall)

// API Procedure 인수를 그대로 넘겨주면 된다.

int api_hook::CallTrampolineAPI(const API *api, LPVOID pFirstArg){

    // 후킹 데이터를 읽는다.

    const APIHOOK *data = api->HookInfo;

 

    // 인수를 차례로 push Trampoline 함수를 호출한다.

    // (push 순서는 거꾸로다. 가장 마지막 인수 -> 가장 첫번째 인수로 push)

    int *pCurParam = ((int *)pFirstArg)+(data->fnArgc)-1;

 

    int _var, _proc, _ret;

 

    for (int i=0; i<data->fnArgc; i++){

        _var = (*(pCurParam-i));

        __asm push _var

    }

    _proc = (int)(data->fnTrampoline);

    __asm{

        call _proc

        mov _ret, eax

    }

 

    // 원본 API 함수의 리턴값

    return _ret;

}

 

// Trampoline 함수 호출 (cdecl, stdcall 공용)

int api_hook::CallTrampolineCdecl(const API *api, LPVOID pFirstArg){

    const APIHOOK *data = api->HookInfo;

    int *pCurParam = ((int *)pFirstArg)+(data->fnArgc)-1;

 

    int _var, _proc, _ret, _esp;

 

    __asm mov _esp, esp

   

    for (int i=0; i<data->fnArgc; i++){

        _var = (*(pCurParam-i));

        __asm push _var

    }

    _proc = (int)(data->fnTrampoline);

    __asm{

        call _proc

        mov esp, _esp

        mov _ret, eax

    }

    return _ret;

}

 

// 참고: Hook Imported Function 방식의 API Hooking.

// 수정한 부분의 주소를 반환한다. (, 함수의 주소가 기록되어 있는 메모리 주소)

// System DLL Import Table 수정할 없다.

/*PDWORD api_hook::HookImportedFunction(HMODULE hModule, PROC fnApi, PROC fnProc){

    PROC pfnOriginalProc;

    PIMAGE_DOS_HEADER pDosHeader;

    PIMAGE_NT_HEADERS pNTHeader;

    PIMAGE_IMPORT_DESCRIPTOR pImportDesc;

    PIMAGE_THUNK_DATA pThunk;

    PVOID pBaseAddr; 

 

    // Verify that a valid pfn was passed

    if ( IsBadCodePtr(fnProc) ) return 0; 

 

    // Get API Address to Hook

    pfnOriginalProc = fnApi;

    if(!pfnOriginalProc) return 0; 

 

    // Approach the address of import table

    pBaseAddr   = (PVOID)hModule;

    pDosHeader  = (PIMAGE_DOS_HEADER)pBaseAddr; 

    pNTHeader   = (PIMAGE_NT_HEADERS) ( (DWORD)pDosHeader + pDosHeader->e_lfanew); 

    pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) ( (DWORD)pBaseAddr +  

   (DWORD) (pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress)); 

 

    // Verify that the module is valid

    if ( pDosHeader->e_magic != IMAGE_DOS_SIGNATURE ) // "MZ"

        return 0;

    if ( pNTHeader->Signature != IMAGE_NT_SIGNATURE ) // "PE\0\0"

        return 0;

 

    // the end of dll chain is always NULL

    while ( pImportDesc->Name ) //Name is a DWORD (RVA, to a DLL name)

    {

        PSTR pszModName = MakePtr(PSTR, pDosHeader, pImportDesc->Name);

        

//      if ( stricmp(pszModName, szDllName) == 0 )  // find dll

        {

            pThunk = (PIMAGE_THUNK_DATA) ( (DWORD)pBaseAddr + pImportDesc->FirstThunk); 

 

            // the end of API address chain in dll is always NULL

            while ( pThunk->u1.Function )

            {

                PDWORD pImpFunction = pThunk->u1.Function;

 

                if (osver.IsWin9x()){

                    if (*(PBYTE)pImpFunction == 0x68){

                        pImpFunction = (PDWORD)*(DWORD *)((PBYTE)pImpFunction + 1);

                    }

                }

               

                if ( (DWORD)pImpFunction == (DWORD)pfnOriginalProc ) // find api

                {  // FOUND!

                    DWORD oldp = 0;

                    ::VirtualProtect(&pThunk->u1.Function, sizeof(PDWORD), PAGE_EXECUTE_READWRITE, &oldp);

                    pThunk->u1.Function = (PDWORD)fnProc; // intercept target api address field

                    ::VirtualProtect(&pThunk->u1.Function, sizeof(PDWORD), oldp, &oldp);

                    return (PDWORD)&pThunk->u1.Function;

                }

                pThunk++;  

            }

        }

        pImportDesc++;

    }

 

    return 0;

}*/

 

// 메모리 복사

// 메모리 Protection 무시하고 복사한다.

void _stdcall api_hook::_RtlCopyMemory(void *pDest, const void *pSrc, unsigned int nSize){

    // 메모리가 실제로 존재하는 부분인가?

    MEMORY_BASIC_INFORMATION mbi={0};

    if (VirtualQuery(pDest, &mbi, sizeof(mbi)) == sizeof(mbi)){

        if (mbi.State == MEM_FREE) return;

    }

    if (VirtualQuery(pSrc, &mbi, sizeof(mbi)) == sizeof(mbi)){

        if (mbi.State == MEM_FREE) return;

    }

 

    // WinNT: 메모리 Protection 잠시 바꾸어서 복사

    if (osver.IsWinNT()){

        DWORD oldp = VirtualProtect(pDest, nSize, PAGE_EXECUTE_READWRITE);

        DWORD oldp_2 = VirtualProtect((void *)pSrc, nSize, PAGE_EXECUTE_READWRITE);

        memcpy(pDest, pSrc, nSize);

        VirtualProtect((void *)pSrc, nSize, oldp_2);

        VirtualProtect(pDest, nSize, oldp);

        return;

    }

    // Win9x: int 2E 이용

    __asm{

        push nSize

        push pSrc

        push pDest

        mov edx, esp

        mov eax, 0x10A          // int2E_RtlCopyMemory

        int 0x2E                // RtlCopyMemory(pDest, pSrc, nSize)

        add esp, 12

    }

}

 

결론

API 후킹은 API 호출을 가로채는 것이고 동적 API 후킹은 API 후킹 기법을 활용하여 여러 API 호출을 하나의 프로시저로 연결시키는 것이다.

 

참고 자료

1. API Hooking 기초강좌

2. API Hooking Revealed (한글 번역)

3. Remote Library (한글 번역)

4. IA-32 Intel Architecture Software Developer’s Manual Volume 2A: Instruction Set Reference, A-M; CHAPTER 2, Instruction Format (한글 번역)


출처 : http://www.devpia.com/Maeul/Contents/Detail.aspx?BoardID=51&MAEULNo=20&no=7267&ref=7267#IDynAPIHook

'C/C++언어 > 후킹' 카테고리의 다른 글

Hooking 을 사용하는 프로그램 내의 구현  (0) 2007.11.11
Global Hooking in Win32  (0) 2007.11.11
[본문스크랩] 메세지 후킹  (0) 2007.09.06
[본문스크랩] Knowledge Base API  (0) 2007.09.06
API Hooking Revealed  (0) 2007.09.06
 

파일 입.출력


파일 입.출력은 앞으로도 매우 중요하게 쓰이는 부분이다.

어떤 파일로부터 데이터를 읽어들이고 또 데이터를 어떤 파일에 저장하는 것을 말한다.


데이터 저장과 불러오기 기능을 구현한것

읽기 / 쓰기 전용 , 읽기 / 쓰기 혼합 모드 기능

파일을 열고 닫는 작업이 쌍으로 존재한다.


[파일 오픈]


* FILE *fopen(const char *filename, const char *mode) ;    // 성공시 해당파일의 포인터. 실패시 NULL 포인터 리턴


- filename : 문자형 포인터로 사용할 파일 이름을 지정

- mode     : 파일에 대한 접근 방식

- 리턴값     : open 한 파일을 가리키는 파일 포인터


mode 에는 기본적으로 r, w, a 가 있고 세부적으로 바이너리 코드(이진모드)와 텍스트 모드로 분리



[파일 오픈 모드]


r   텍스트 모드이며 읽기 모드로 파일을 오픈

w  텍스트 모드이며 쓰기 모드로 파일을 오픈. 단 파일을 없으면 자동으로 생성. 있으면 내용을 전부 삭제하고 새로운 파일을 생성

a   텍스트 모드이며 쓰기 모드로 파일을 오픈. 단 파일이 없으면 자동으로 생성. 있으면 파일의 가장 끝부분에 이어 쓰기를 실행

b   바이너리 모드를 의미

+   읽기 / 쓰기의 혼합 모드가 가능함을 의미



[데이터 입.출력 모드]


t   텍스트 모드(text mode)

b   2진 모드(binary mode)


데이터의 읽기 / 쓰기를 텍스트 모드로 할건지 2진 모드로 할건지 정해주는 거이다.

문자열과 같은 텍스트 기반의 데이터는 텍스트 모드로 입. 출력 하는 것이 좋고 데이터 변환이 발생하면 안되는 경우 2진 모드로 데이터 입. 출력 하는 것이 좋다.


파일 접근 모드 + 데이터.입출력모드 = 파일 개방 모드     rt  ( r + t )  : 텍스트 모드의 읽기 전용



[일반적인 파일 열기 방법과 파일 오픈시 주의 사항]


FILE *fp ;                                                 // 파일을 오픈하기전에 반드시 파일포인터 선언

.......

fp = fopen( "test.txt" , "r" ) ;                        // 파일이름과 모드는 "와" 사이에 입력   test.txt 파일을 읽기 모드로 읽는 것이다.  

.......                                                        // 반드시 test.txt 파일이 폴더내에 있어야 한다.

if( fp == NULL )                                          // 파일이 제대로 열렸는지 반드시 체크

{

     ........

     return -1 ;

}



[파일 종결]


* int fclose(FILE *stream) ;


- stream : fopen() 함수를 통해 생성된 파일 포인터

- 현재 열린 파일을 닫을때 사용

- 출력 버퍼에 남아있는 데이터를 파일에 기록한 후 파일을 닫음


FILE *fp ;

.......

fp = fopen( "test.txt", "r" ) ;             // 파일 오픈

.......

fclose( fp ) ;                                  // 파일 닫음


동적할당에서 malloc 과 free 가 짝이 듯이 항상 파일을 오픈하면 종료를 해주어야 한다. fopen -> fclose



* 파일을 읽기 와 쓰기 에서 사용되는 함수들은 기존의 함수와 비슷하다. f 만 들어가면 된다.















<fputc() 함수>


하나의 문자를 파일에 저장

int fputc(int c, FILE *fp) ;

- c : 파일에 저장할 데이터

- fp : open 한 파일을 가리키는 포인터

- 리턴값 : fputc() 함수가 파일에 기록한 데이터


#include <stdio.h>

void main()

{

     FILE *fp ;

     fp = fopen( "text.txt", "w" ) ;                  // w 생성한다. test.txt   기존에 파일이 있으면 데이터 삭제 새로 사용.

     for( int i = 0 ; i < 128 ; i++ ){

          fputc( '\n', fp ) ;                           

          fputc( i, fp ) ;

     }

     fclose( fp ) ;

}


실행하면 아무것도 안나올 것이다.. 폴더에 가보면 test.txt 란 파일이 생기고  아스키 코드값 0번부터 128번의 문자들이 들어있다.


<fgetc() 함수>


* int fgetc( FILE *fp ) ;

- fp : fopen() 함수를 통해 생성된 파일 포인터

- 리턴값 : 문자

- fp 는 항상 NULL 이 될수 없음


폴더에 text.txt 안에 a b c 를 작성하고 종료.(저장. 공백 주의)


#include <stdio.h>

#include <stdlib.h>


void main()

{

     FILE* fp ;

     fp = fopen( "test.txt" , "r" ) ;

     if( fp == NULL){

          printf("Error\n") ;

          exit( 1 ) ;

     }

    

     while( !feof(fp) )                                  // 끝이 아니라면 실행

          printf( "%c", fgetc(fp) ) ;                   // 문자를 읽어들여 출력  a b c 출력

     

     fclose( fp ) ;

}


<feof() 함수>


* int feof( FILE *filepointer )

- fileopinter : fopen() 함수를 통해 생성된 파일 포인터

- 현재 가리키는 위치가 파일의 끝인가를 검사한다.

- 리턴값 : 현재 가리키는 위치가 파일의 끝인 경우 0 을. 아닌 경우에는 0 이 아닌 값을 리턴


<fputs() 함수>


문자열을 대상파일( 지정한 파일 포인터의 파일) 에 기록


* int *fputs( const char *buffer, FILE *fp ) ;

- buffer : 출력할 문자열의 포인터

- fp : 출력할 파일을 가리키는 포인터


#include <stdio.h>

#include <stdlib.h>


void main()

{

     FILE *fp ;

     fp = fopen( "test.txt", "w" ) ;


     fputs( "Hello" , fp ) ;                              // test.txt 에 Hello 가 저장된다.

     fclose( fp ) ;

}


<fgets() 함수>


파일 포인터로부터 원하는 양 만큼의 문자열 데이터를 읽어옴


* char *fgets( char *buffer, int n, FILE *fp ) ;

- buffer : 문자열을 저장하게 되는 영역

- n : 읽어드릴 양

- fp : open 한 파일을 가리키는 포인터


현재 test.txt 파일에는 Hello 가 저장되어 있다.


#include <stdio.h>

#include <stdlib.h>


void main()

{

     FILE *fp ;

     char str[20] ;

     fp = fopen( "test.txt", "r" ) ;

   

     fgets( str, sizeof(str), fp ) ;

     printf( "str : %s\n", str ) ;                     // Hello 출력

     fclose( fp ) ;

}


<fread() 함수>


파일로 부터 바이트 단위로 데이터를 읽어들인다.


* size_t fread( void *buffer, size_t size, FILE *fp ) ;

- buffer : 읽어올 데이터를 저장할 메몰리 영역의 포인터

- size : 읽어올 사이즈

- fp : 파일 포인터

- 파일 포인터 fp 에서 size 만큼의 데이터 n 개를 buffer 에 저장


<fwrite() 함수>


파일에 바이트 단위로 데이터 기록


* size_t fwrite( void *buffer, size_t size, size_t n, FILE *fp ) ;

- buffer : 파일에 기록할 데이터가 저장되어 있는 메모리 영역의 포인터

- size : 개별적인 데이터 항목의 크기

- n : 기록할 수

- fp : 파일 포인터

- buffer 에 있는 size 만큼의 데이터 n 개를 파일 포인터 fp 에 기록


#include <stdio.h>

#include <stdlib.h>


struct ADDS{

     char name[30] ;

     char number[30] ;

} ;


void main()

{

     FILE *fp ;

     int input, cnt ;

     ADDS *Juso ;


     while(1)

     {

          printf( "* [1] 입력  [2] 출력  [3] 종료 *\n" ) ;

          printf( "입력  : " ) ;

          scanf( "%d", &input ) ;


          if( input == 3 ){

               printf( "-Good Bye-\n" ) ;

               exit( 1 ) ;

          }

          if( input == 1 ){

               printf( "총 몇명 의 주소록을 입력할까요 ?" ) ;

               scanf( "%d", &cnt ) ;


               Juso = (ADDS*)malloc(sizeof(ADDS)*cnt) ;         

               fp = fopen( "test.txt", "w" ) ;


               for( int i = 0 ; i < cnt ; i++ ){

                    printf( "이름 : " ) ;

                    scanf( "%s", Juso[i].name ) ;

                    printf( "전화번호 : " ) ;

                    scanf( "%s", Juso[i].number ) ;

               }

               fwrite( &cnt, sizeof(int), 1, fp ) ;                       // fwrite cnt 변수값

               fwrite( Juso, sizeof(ADDS), cnt, fp ) ;                // fwrite Juso 구조체

               printf( "저장 완료\n" ) ;

               fclose( fp ) ;

               free( Juso ) ;

          }

          if( input == 2 ){

               fp = fopen( "test.txt", "r" ) ;            

               fread( &cnt, sizeof(int), 1, fp ) ;

          

               Juso = (ADDS*)malloc(sizeof(ADDS)*cnt)  ;          

               fread( Juso, sizeof(ADDS), cnt, fp ) ;

               printf( "\n" ) ;

     

               for( int i = 0 ; i < cnt ; i++ )

                    printf( "이름 : %s  전화 : %s\n", Juso[i].name, Juso[i].number ) ;


               printf( "\n" ) ;

               free( Juso ) ;

               fclose( fp ) ;

          }

     }

}


간단한 주소록 프로그램이다.  소스가 그다지 어렵지 않기 때문에 쉽게 이해 할수 있을 것이다.


<fprintf 와 fscanf>


fprintf 와 fscanf 함수는 printf 와 scanf 함수와 똑같다. 단지 파일이냐 모니터냐의 차이이다.  그리고 앞에 FILE 포인터 변수가 들어간다.


#include <stdio.h>

#include <stdlib.h>


void main()

{

     FILE *fp ;

     int Index ;    

     int Data ;


     fp = fopen( "test.txt", "w" ) ;

     for( Index = 0 ; Index < 9 ; Index++ )                            // 0 부터 9 까지 test.txt 에 저장

          fprintf( fp, "%d\n", Index ) ;


     fclose( fp ) ;


     fp = fopen( "test.txt", "r" ) ;

     for( Index = 0 ; Index < 9 ; Index++ ){                           // test.txt 에 있는 값을 9개 불러옴.. 0 부터 9 까지 출력

          fscanf( fp, "%d", &Data ) ;                            

          printf( "%d\n", Data ) ;

     }


     fclose( fp ) ;

}

'C/C++언어' 카테고리의 다른 글

여러 C++ 컴파일러  (0) 2007.09.06
함수포인터란 ?  (0) 2007.09.06
[본문스크랩] VC++ Article  (0) 2007.09.06
[강좌] MASM 6.0 사용법  (0) 2007.09.06
어셈블리 초급 기초 #1(정태식님)  (0) 2007.09.06

출처 : http://www.gosu.net/GosuWeb/ArticleBList.aspx?CateCode=0502000

 
• General (52) • Database (0)
• Command and Function (0) • Internet & Network (8)
• API/DLLs (1) • COM/DCOM/COM+ (5)
• Threads, Processes & IPC (2) • Shell Programming (1)
• Windows forms (0) • Doc/View/Printing (1)
• Files and Folders (2) • Dialog and Windows (6)
• TextBoxs/String (0) • Button Controls (0)
• Menus (0) • Tab/Toolbars/Status Bar (0)
• Edit Controls (0) • List/Combo Controls (1)
• ListView/TreeView (1) • Static Controls (0)
• Samples (0)
 
General (52)
 
 
How to i386 32bit OS Kernel Compile in VC6 (0) / 작성자: bro / 작성일: 2004-10-14 / 평점: 4.56
본 문서는 VC6에서 어떻게 커널을 컴파일 할 수 있는가에 대한 간단한 소개를 하는 문서입니다. VC에서 OS를 만들기 위해서 생기는 특이한 사항과 간단한 컴파일 후 콘솔 커널을 띄우는 것의 데모까지 해보도록 합니다.
MFC DLL 간단 설명 (0) / 작성자: bro / 작성일: 2004-11-28 / 평점: 4.75
MFC DLL 과 관련된 여러 재미있는 내용들을 알아보았습니다. MSDN에서 본 영문 자료를 기반으로 이해를 위해 추가를 하거나 한 설명입니다.
프로그램내에서 한/영 전환하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-25 / 평점: 4.75
프로그램적으로 한영키를 제어 하는 방법으로 Imm*() 함수를 사용하시면 됩니다. 아래의 예제 코드는 한글키와 영문키로 바꾸는 두가지 함수를 설명하고 있습니다
프로그램 한번만 실행하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 4.75
프로그램의 중복 실행을 막는 방법에는 여러가지가 있겠지만, 공유 Data segment 를 사용하는 방법을 살펴보겠습니다.
MetaFile 사용 및 클립보드에 복사하기 (1) / 작성자: 하얀그림자 / 작성일: 2004-12-02 / 평점: 4.75
메타파일을 클립보드를 이용하여 다른 프로그램에 붙여넣을 수 있는 기능을 구현하자. 여기서는 윈도우메타파일이 아닌 확장 메타파일을 다루고 있다. 뭐, 그게 그것이지만,,,
트레이 아이콘(TrayIcon) 사라지는 버그 막기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-09 / 평점: 4.00
요즘 나오는 프로그램에서는 대부분 TrayIcon을 제공합니다. 단순히 프로그램 리스트(태스크 바)에 나오는 것보다는, TrayIcon이 사용자에게 보다 더 편리한 UI를 제공하기 때문입니다. 그런데, 익스플로어(인터넷 익스플로어가 아닙니다.)의 버그 때문에 <그림 1>과 같이 종종 TrayIcon이 Tray에서 사라지는 경우가 있습니다. 하지만, 몇몇의 프로그램은 사라지지 않고 계속 남아있는 경우도 볼 수 있습니다.
DLL, LIB 인텔리센스, Debug Step Trace되게 하기 (0) / 작성자: bro / 작성일: 2004-10-20 / 평점: 4.25
Static Lib나 DLL을 배포할때 DLL, LIB만 배포하면 쓰는 사람쪽(클라이언트)에서 인텔리센스와 스텝트레이스 디버깅을 제대로 할 수 없는 문제가 있습니다. 이 문서는 이를 위한 경험에 따른 팁입니다.
소스 세이프 ( Source Safe )의 간략한 소개와 설명 (1) / 작성자: 규서스 / 작성일: 2004-11-09 / 평점: 4.25
Visual Studio 에서 제공하는 공동 프로젝트 소스관리 툴인 소스세이프 (Source Safe)에 관하여 간략하게 설명합니다. 보다 세밀하고 어려운 기능은 많은 방법으로 테스트를 한 이후에 실제 프로젝트에 적용을 하면 보다 안전(Safe)한 프로젝트 소스와 리소스 관리가 되지 않을까 생각됩니다.
MFC DLL Debug/Release 구분해서 하기 (0) / 작성자: bro / 작성일: 2004-11-18 / 평점: 3.80
Release용 MFC42.dll 디버그용 MFC42d.dll 처럼 dll 만들때 디버그용 릴리즈용으로 세팅하는 방법에 대해서 알아봅니다.
CStringArray 정렬하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 4.67
CStringArray 객체를 정렬하기 위해서 아래에서 보여주는 소스코드를 사용하실 수 있습니다. main() 함수에서는 CStringArray 객체를 만들고, 내용들을 추가시킵니다. 그리고 나서 화면에 출력하고 sort() 함수를 호출하죠. 이제 sort()함수가 끝나고 난후 다시 화면에 출력해서 sort() 함수가 제대로 역할을 수행했는지 결과를 보여줍니다. sort() 함수는 Bubble Sort 알고리즘을 사용했으면 CompareAndSwap()라는 함수를 구현하여 CString 객체끼리 서로 위치를 바꿀 수 있도록 하고 있습니다.
일반적인 윈도우 소멸 순서 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 4.00
MFC framework에서, 사용자가 frame 윈도를 닫게되면, 윈도는 기본적으로 OnClose 핸들을 call 합니다. 그리고 OnClose 내부에서는 DestroyWindow를 호출하죠. 가장 마지막으로 호출되는 함수는 OnNcDestroy이다.
_ASSERTE 가 GetLastError()를 망친다 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-15 / 평점: 4.00
MFC 에서 제공하는 에러 방지용 매크로인 _ASSERTE 가 호출되면 MFC 는 Last Error 를 0 을 셋팅하게 되어 GetLastError() 가 무용지물이 되어 버립니다.
윈도우용 CVS 의 활용-클라이언트_WinCVS (1) / 작성자: nak / 작성일: 2004-11-12 / 평점: 4.00
WinCVS는 CVS명령을 쉽게 사용하게 하는 클라이언트 프로그램이다. 비슷한 프로그램으로 여러가지가 나와 있지만 Tortoise CVS 프로그램과 가장 많이 사용하는 CVS 클라이언트 이다. 역시 군더더기는 빼고 사용법만 설명하도록 하겠다.
tstring: string for TCHAR (0) / 작성자: bro / 작성일: 2004-10-20 / 평점: 3.75
MBCS 환경이나 UNICODE 환경에서도 stl::string과 stl::wstring을 자동으로 선택해서 사용할 수 있는 tstring에 대해서 알아보도록합니다.
파일 버전 구해서 비교하기... (1) / 작성자: 데미소다오렌지 / 작성일: 2004-11-16 / 평점: 4.33
요즘은 대부분의 파일을 인터넷을 통해서 배포하는 것이 일반적입니다. 이럴 경우 파일이 업데이트 되었는지 검사하기 위해서 버전 정보를 구하는 함수가 필요합니다. 이 문서에서는 Win32 PE 파일에 포함된 버전 정보를 구하는 함수와 함께 구해진 버전을 비교하는 함수의 구현 과정을 포함하고 있습니다.
자동 버전 증가 매크로 (0) / 작성자: 데미소다오렌지 / 작성일: 2004-11-28 / 평점: 4.33
이 문서는 VC++의 자동 버전 증가 매크로에 대한 설명과 좀 더 개선된 매크로의 소스를 포함하고 있습니다.
HBITMAP을 BMP 파일로 만들어 주는 함수 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-07 / 평점: 4.33
어떤 내용이 들어있는 HBITMAP 값과 파일이름을 넘겨주면 주어진 파일 이름으로 Bitmap 이미지 파일을 만드는 함수입니다.
디스크 복사 및 포맷 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 4.33
디스크 포맷 기능을 하는 API함수가 존재하는데 이건 Undocument된 내용으로 MSDN에 나오지않고 헤더에도 선언되어있지 않습니다. 함수 Prototype을 선언하면 사용할 수 있습니다.
친절한 메세지 ( benign message ) (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-23 / 평점: 3.25
특정 Thread 나 Window 에 아무런 영향을 끼치지 않으면서 단순히 그 Thread 나 Window 가 살아있는지를 확인하고 싶을때 '친절한 메세지' 를 사용하시면 됩니다.
Source Safe과 Project를 분리시키기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 3.67
Source Safe와 연결되어있는 Project는 자체적으로 이와 관련된 정보를 가지고 있습니다. 이 정보를 제거하는 방법을 알아보겠습니다.
재밌는(?) 시스템 강제로 다운시키기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 3.67
필요한 경우가 거의 없겠지만(바이러스가 아니고서야. ^^;;), 시스템을 강제로 다운시키고자 하는 경우가 있을 수 있습니다. 이런 경우 아래의 예제에서 처럼 어셈블리 코드를 사용하는 방법과 Thread 를 와장창 만들어서 thread 러쉬(?)를 통해서 다운시키는 방법이 있습니다. 아래의 예제를 참고 하세요.
SearchPath()의 리턴값 알아보기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 4.50
"Win32 Programmer's Reference"에서 "SearchPath()"는 다음과 같은 reture value를 갖고 있습니다. The length (in characters) of the string copied to the buffer. -or- 0 if the function fails [see "GetLastError()" in the "Win32 Programmer's Reference" for more information]. 하지만, 만약 찾고자 하는 파일을 찾을 수 없을때에도 0을 return 해주는데, 이때 GetLastError()로 error값을 살펴보아도 error값은 변경되어 있지 않다는 것을 알수 있습니다.
MFC UNICODE 컴파일 하기 (0) / 작성자: bro / 작성일: 2004-11-10 / 평점: 4.50
MFC에서 다국어 버전( 한프로그램에서 여러 국가의 문자를 표현하는 )을 만들때 유니코드를 사용하므로 유니코드 컴파일을 해야 합니다.
윈도우용 CVS 의 활용-서버편 (0) / 작성자: nak / 작성일: 2004-11-11 / 평점: 4.50
많은 사람들이 알지만 귀차니즘 혹은 정보의 부족으로 인해 하지 않았던 CVS를 아주 쉽게 따라하며 직접 설치하고 사용 할 수 있도록하는데 이 문서의 목적이 있다.
Hatch 가 밀려나오는 브러시 수정하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-15 / 평점: 4.50
Hatch 스타일이 적용된 브러시가 정상적으로 표시되지 않는 이유는 브러시 원점과 관련되어 있습니다.
IPicture를 이용한 JPG, GIF, BMP, WMF, EMF, ICO 보기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-15 / 평점: 4.50
JPG나 GIF 등의 이미지 파일포멧을 읽어올려면 직접구현하던가 아니면 해당 라이브러리(예, LeadTool,...)을 이용해야 됩니다. 그러나 윈도즈에서 제공하는 IPicture인터페이스로 간단하게 그림파일(JPG, GIF, BMP, WMF, EMF, ICO)을 불러오는 방법을 알아보겠습니다.
web session : MD5, RC4, base64 (0) / 작성자: bro / 작성일: 2004-12-24 / 평점: 4.50
웹 세션에 대해서알아본 후 md5, rc4, base64등을 사용하는 방법과 쉽게 사용 할 수 있는 md5, rc4, base64 소스를 얻어봅니당.
First-chance exception 에서 Stop 하도록 만들기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 4.50
가끔씩 Visaul C++의 Debug 창에 위의 문구가 출력되는 걸 볼 수 있습니다. 이 문구의 의미는 무엇이며, 어떻게 디버깅할 수 있는지 알아보겠습니다.
ISAPI필터dll Attach to Process로 디버깅하기 (0) / 작성자: bro / 작성일: 2004-11-17 / 평점: 3.00
VC의 Attach to Process를 하는 방법에 대해서 알아보고 예로 ISAPI 필터 dll을 디버깅하는 방법을 알아보았습니다.
스플리터 윈도우 위치 고정하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 4.00
스플리터 윈도우의 크기를 위치를 공정하는 방법을 설명한다. 스플릿바의 행동양식을 살펴보면 스플리터 윈도우의 크기를 고정하는 방법을 찾을 수 있다.
CDROM Insertion / Removal 감지하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-15 / 평점: 4.00
CDROM (or DVD) Insertion / Removal 감지하는 방법을 알아보겠습니다.
할당된 블록의 크기 구하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 4.00
new, malloc을 사용해서 메모리 블럭을 할당한 경우에, 포인터를 통해 메모리 블럭의 크기를 구하는 함수입니다. 물론 디버그 버전에서만 사용할 수 있습니다.
base pointer를 지정하자. (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-16 / 평점: 4.00
microsoft c++에서는 __base 키워드를 사용해서 base pointer를 지정할 수 있습니다.
CPList - CPtrList for non-MFC (0) / 작성자: bro / 작성일: 2005-01-02 / 평점: 4.00
MFC의 CPtrList를 대치할 수 있는 non-MFC 환경하의 PList 클래스를 소개합니다
Top 윈도우들의 핸들을 알수 없을까? (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-25 / 평점: 3.50
프로그램을 하다 보면, 현재 Windows System 에서 돌아가는 상위 윈도우들을 알아야 되는 경우가 있습니다. 어떻게 하면 쉬울까요?
소스로 바로가는 TRACE (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 3.50
ANSI CRT에는 TRACE와 같은 기능이 없지만 Visual C++에는 _RPTn 들과 _RPTFn 들이 있습니다. 여기서 _RPTFn 을 주목할 필요가 있는데 _RPTFn 은 다음과 같은 형태의 매크로들을 말합니다.
파일로 트레이스 하도록 바꾸기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 3.50
MFC에서 말입니다. 모든 트레이스를 파일로 하도록 바꿔봅시다. 예전에 어떻게 좀 해볼라고 하다가 실패했는데, 간단하고도 효과적인 방법이 무수히 많이 있습니다. 그중에 하나!
폴더내의 dll 한 번에 등록하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 3.50
쉘에서 for 명령을 지원하네요.
포인터 변수를 검증하기위한 매크로 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-16 / 평점: 3.50
포인터가 올바른 메모리 블럭을 가리키고 있는지 조사할 수 있는 API 들이 몇 가지 있습니다. ( IsBadWritePtr() 을 MSDN 에서 찾으시면 비슷한 친구들까지 모두 찾을실 수 있을겁니다 ) MFC 에는 이 API를 사용해서 간단한 매크로를 만들어 두었습니다.
COM 디버그 설정 (0) / 작성자: bro / 작성일: 2004-11-29 / 평점: 3.50
웹페이지에 붙은 COM 디버깅 VB 프로그램에 붙은 COM 디버깅 MTS COM+ 디버깅 참고 ISAPI DLL 디버깅
특정 파일의 Property 창 띄우기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 3.50
프로그래머가 직접 정보를 수정하지 않고 사용자가 정보를 수정하게 하고 싶은 경우에 파일의 Property 창을 띄울 수 있습니다. 그럴때 간단히 ShellExcute() 를 사용해 보시면 됩니다.
CTString - CString on non-MFC (0) / 작성자: bro / 작성일: 2005-01-02 / 평점: 5.00
CString만 사용하는 경우 MFC 라이브러리를 포함하기에는 무겁습니다. 이럴때 간단히 대치할 수 있는 클래스로 CTString을 소개합니다.
Watch 창에서 함수 실행하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 3.00
아시는 분은 다 아시겠지만, watch 창에서는 함수를 실행시킬 수 있습니다.
Floating-Point Error가 Exception을 발생하도록 만들기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 4.00
int대신 float를 사용했다면 Exception은 발생하지 않습니다. 어떻게하면 Exception이 발생하도록 만들 수 있을까요?
특정 컨트롤에서 한글을 입력받고 싶지 않을때 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-21 / 평점: 4.00
특정 컨트롤에서 영문만 입력 받고자 할때에는 IME의 사용을 중지하면 됩니다.
WaitCursor가 만들어지지 않는 경우 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-23 / 평점: 4.00
모래시계 커서를 만드는데 실패하셨던( 저처럼) 분들은 부디 유용하게 사용하시기 바랍니다.
트레이 메뉴가 사라지지 않을때 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 4.00
트레이 아이콘에서 메뉴를 띄울 때 메뉴의 특정 항목을 선택하지 않고 다른 윈도우를 선택하게 되면 메뉴가 사라지지 않습니다. 이 문제는 임의의 윈도우를 생성하여 메세지가 사라지도록 하는 것입니다.
MFC 라이브러리에 동적으로 링크된 정규 DLL (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 4.00
MFC 라이브러리에 DLL을 동적으로 링크하는 방법을 알아보도록 합니다.
연결 프로그램 찾기 다이얼로그 띄우기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 4.00
Explorer 에서 파일 아이콘을 더블클릭할 경우 파일의 확장명에 따라 연결되어 있는 프로그램이 동작하게 되어 있다. 하지만 연결되어 있는 프로그램이 없을 경우 "연결 프로그램 찾기" 다이얼로그가 화면에 나타나게 할 수 있습니다.
xmlite : simple xml parser (0) / 작성자: bro / 작성일: 2004-12-17 / 평점: 4.00
XMLite 는 DOM 기반의 사용하기 쉬운 c++ XML 파서입니다. 처음에는 간단히 엘리먼트만 파싱하도록 하였다가, 필요에 의해서 여러가지 기능이 추가되었습니다.
Capture가 되지 않는 경우 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-23 / 평점: 3.00
Capture가 되기 위해서는 마우스 버튼이 눌려져 있어야 한다는 사실을 아십니까?
간단한 LoadString (0) / 작성자: bro / 작성일: 2004-11-26 / 평점: 2.00
애플리케이션의 문자열 리소스를 가져다 쓰려면 LoadString을 호출해야 합니다. MFC에서는 그나마 편리하게 사용하라고 CString의 맴버 함수로 LoadString 맴버 함수를 제공 하고 있지만, 매번 CString 객체를 생성해서 사용해야 하므로 불편하기도 합니다. 게다가, 문자열 리소스에서 인자를 받게 하고 싶으면 더욱 불편합니다. 그래서 아래와 같은 방식으로 저는 간단한 LoadString 매크로를 만들어 사용하고 있습니다.
 

 
Internet & Network (8)
 
 
PHP나 ASP 코드 직접 호출하기 (2) / 작성자: 디버깅전문가 / 작성일: 2004-11-09 / 평점: 4.00
IE(Internet Explorer)같은 웹 브라우저를 이용하지 않고, 서버측에 있는 코드를 직접호출하는 방법에 대해서 알아보도록 합니다.
네트워크 정보 얻어오는 방법 (3) / 작성자: bro / 작성일: 2004-11-10 / 평점: 4.00
1. MAC Address 그냥 보는 방법 2. GetNetworkParams / GetAdaptersInfo 3. NIC 카드 랜카드 이름 얻기
HTML코드를 임시파일로 저장한 파일 URL얻기 (0) / 작성자: bro / 작성일: 2004-11-29 / 평점: 4.00
가끔가다보면 동적으로 HTML코드를 만들어서 이를 네비게이트해야하는 경우가 있습니다. about 을 사용하면 간단한 경우 해결되지만, 제약으로 인해 몇 K 이상되는 코드는 넣을수 없습니다.
인터넷에 연결되어 있는지 알아보기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-21 / 평점: 4.33
현재 컴퓨터가 인터넷에 연결되어 있는지를 확인하는 함수를 알아보도록 합니다.
mshtml 메모리 릭 최소화하기 (0) / 작성자: bro / 작성일: 2004-11-29 / 평점: 4.50
mshtml은 웹브라우저 컨트롤이나 CHtmlView를 사용할때 내부 HTML 다큐먼트 안의 엘리먼트 값을 보거나 수정하기 위해서 쓰는 HTML DOM Parser COM 라이브러리이다. 문제는 잘못사용하면 메모리릭이 많이 생긴다는 것이다.
HTML 스크립트 함수 ActiveX에서 호출하기 (0) / 작성자: bro / 작성일: 2004-11-30 / 평점: 4.50
IDocument 를 얻은 후 get_script로 스크립트 객체를 얻은 후 GetIDsOfNames( 함수명 ) 으로 해당 함수를 얻은 후 Invoke 할 수 있습니다.
HTML 리소스에 넣어 Navigate 하기 (0) / 작성자: bro / 작성일: 2004-11-29 / 평점: 4.00
HTML 페이지를 리소스로 EXE에 포함하여 이를 Navigate 하는 방법에 대해서 알아봅니다.
웹페이지가 이동될때 진입하는 함수 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-07 / 평점: 4.00
웹상에서 구동되는 ActiveX 컨트롤의 경우 웹페이지가 이동될때 진입하는 함수를 알아본다.
 

 
API/DLLs (1)
 
 
local folder에 있는 dll 사용하게 만들기 (2) / 작성자: 디버깅전문가 / 작성일: 2004-12-21 / 평점: 3.00
dll hell 을 방지하기 위한 방법 중 한가지로, local 폴더에 dll을 두고 혼자만 쓰는 방법을 들 수 있습니다. 그와 관련하여 재밌는 기능이 한 가지 있네요..
 

 
COM/DCOM/COM+ (5)
 
 
MS Script Control in MFC (0) / 작성자: bro / 작성일: 2004-11-20 / 평점: 5.00
MS VB Script Control을 MFC 에서 사용하는 방법에 대해서 간략하게 알아보았습니다.
Lite control에서 이벤트가 가능하게 하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 4.00
Lite control에서 이벤트가 가능하게 하기위해서는 IProvideClassInfo2Impl를 상속받게 하면 된다.
ATL Object가 한글 폴더에 있으면 등록이 안되는 버그 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 4.00
이 버그는 MS에서 인정하는 버그이고 해결책까지 나와있습니다. 아래 참조에서 MSDN 항목을 확인하실 수 있습니다. 시키는대로 했는데 잘 안되서, 독자적인 해결책을 제시합니다.
OLE를 이용한 Drag and Drop (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-25 / 평점: 4.00
Ole를 이용하여 간단하게 Window 사이에서 Drag and Drop하여 데이터를 교환하는 방법을 설명합니다.
다른 스레드에서 인터페이스 받아다 쓰기 마샬링하기 (0) / 작성자: bro / 작성일: 2004-11-30 / 평점: 2.00
같은 에러는 다른 스레드에서 인터페이스 포인터 받아와서 직접 메소드를 호출하려고 할때 나는 에러다. 이런 경우 해당 인터페이스를 스레드로 넘겨줄때는 마샬링을 해야 한다.
 

 
Threads, Processes & IPC (2)
 
 
멀티 프로세서 시스템에서 한개의 CPU만 사용하도록 하기. (3) / 작성자: nak / 작성일: 2004-11-10 / 평점: 4.80
멀티 프로세서 시스템에서 프로그램을 개발시 가장 좋은 방법은 생성한 스레드가 잘 분산되어 처리 되도록 하는것이다. 그러나 멀티 프로세서 시스템을 염두하지 않고 만들어진 프로그램에서 비정상적인 종료, dead lock 등으로 인한 pendig 현상등의 문제가 발생하면 당혹스러울 수 밖에 없다. 아주 간단한 팁이지만 실무에서 유용하게 사용할 수있는 팁을 소개한다.
Safe TerminateProcess() (0) / 작성자: snaiper / 작성일: 2004-10-20 / 평점: 4.50
Win32 API 에 보면 TerminateProcess 라는 함수가 있습니다. 함수명 그대로 Process 를 강제로 Kill 시키는 함수입니다. 하지만 권고하지 않는 함수이고, 여러가지 Side Effect 들이 존재합니다. 이를 피하고 안전하게 처리할 수 있는 방법을 소개합니다.
 

 
Shell Programming (1)
 
 
바탕화면, 즐겨찾기, 내 문서 등의 패스얻기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-09 / 평점: 4.67
Windows에서는 여러가지 특별한 폴더들이 있습니다. 내 문서도 그렇고 휴지통 등이 이에 속합니다. 이들 경로는 절대적인 경로가 아니기 때문에(대표적인 예가 Windows root 경로나 System 경로등이죠) 절대적인 경로를 사용하는 경우에는 낭패를 보기 쉽상입니다. 이 글에서는 SHGetSpecialFolderPath()를 이용하여 윈도우에서 사용하는 특별한 폴더들의 패스를 얻어오는 방법을 설명합니다.
 

 
Doc/View/Printing (1)
 
 
Doc/View 구조 없는 SDI에서 스플릿 윈도우 만들기 (0) / 작성자: bro / 작성일: 2004-11-28 / 평점: 4.00
Visual C++ 6 에서 새롭게 지원된 MFC App Wizard 옵션은 바로 다큐먼트 뷰 구조를 사용하지 않고 SDI나 MDI를 할 수 있다는 것입니다. 다큐먼트 뷰 구조가 아닐때 스플릿 윈도우를 만드는 방법을 간단히 알아보도록 합니다.
 

 
Files and Folders (2)
 
 
실행 파일(바이너리)에서 파일 이름 가져오기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-21 / 평점: 4.50
실행 파일의 헤더안에 저장되어 있는 정보를 읽는 것은 심볼릭 정보나 실행 파일에 대한 더 자세한 것을 얻을 수 있는 방법입니다.
파일 사이즈 별로 단위를 나누어서 출력하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-21 / 평점: 4.00
파일의 사이즈를 바이트 연산을 하여, 화면에 적당한 값으로 출력하는 함수를 알아보도록 합니다.
 

 
Dialog and Windows (6)
 
 
Dialog Box의 Control에 Tooltip넣기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 4.67
Tooltip은 CToolTipCtrl 클래스를 이용하면 쉽게 구현할 수 있습니다. 먼저 Create()를 호출해서 Tooltip 객체를 생성합니다. 그리고 나서 AddTool()을 호출해 각각의 컨트롤과 Tooltip 문자열을 연결해 줍니다. 여기서 Tooltip 문자열을 표시해 주는 함수는 ReplyEvent()이며, Tooltip 컨트롤 객체를 생성해 주고 컨트롤과 연결할 수 있는 가장 적당한 곳은 WM_INITDIALOG 메시지의 핸들러(OnInitDialog())입니다.
Dialog Base 프로그램을 Tray에 등록할 때 문제점 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 4.33
Dialog Base 프로그램을 Tray에 등록할 때 대부분 Window를 Hide시켜서 실행하게 된다. 그런데 Dialog Base로 프로그램 작성하면 프로그램이 처음 실행되면서 화면에 잠깐 나타났다가 사라지는 모습을 볼 수 있다. 이런 현상을 피할 수 있는 방법을 소개한다.
윈앰프처럼 TaskBar와 SystemTray 마음대로 주무루기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-15 / 평점: 4.50
윈앰프에서 보면 TaskBar와 SystemTray에 동시에 보여주는 모드 혹은 각각 보여주는 모드 그리고 모두 보여주지 않는 모드가 있습니다. 4가지의 기능을 모두 구현해 보도록 하겠습니다.
다이얼로그 리소스의 실제 크기 얻기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-15 / 평점: 5.00
VC++ 의 Resouce Editor 에서 작성한 다이얼로그의 크기는 실제 화면의 pixel 단위와 맞지 않기 때문에 종종 컨트롤 배치시에 어려움을 겪게 됩니다. 아래의 함수는 리소스에서 작성한 다이얼로그의 실제 크기를 구하는 함수입니다.
모달 다이알로그 시작시 숨기기 (0) / 작성자: 데미소다오렌지 / 작성일: 2004-11-30 / 평점: 4.00
이 문서는 MFC의 모달 다이알로그의 생성시 초기 상태를 숨김 상태로 하는 방법을 설명하고 있습니다.
Brush 의 패턴을 사용하여 스킨 구현하기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-12-19 / 평점: 4.00
브러시의 패턴을 설정할 수 있다는 점과 브러시의 원점을 설정할 수 있다는 점에서 착안하여 만들어본 스킨 예제입니다.
 

 
List/Combo Controls (1)
 
 
List Control 에 다른 Control 넣기 (0) / 작성자: 디버깅전문가 / 작성일: 2004-11-28 / 평점: 5.00
특정 Column에서 다른 Control 을 띄우는 방법 설명. 여기서 사용한 방법은 현재 Cursor가 있는 위치를 파악한후 그 위치에 적당한 크기의 Edit Control를 생성 시킨다.
 

 
ListView/TreeView (1)
 
 
리스트 컨트롤 몇가지 팁 (0) / 작성자: bro / 작성일: 2004-10-20 / 평점: 4.86
1. 특정 ROW 포커스 주기 2. 특정 ROW로 가기 3. 헤더 컬럼수 얻어오기 4. 컬럼의 너비를 이쁘게 주기 5. 한줄 쭉 선택되게 하기, 그리드 라인 주기 6. 선택한 아이템(ROW)를 지우기 7. 두 아이템을 스왑 하기 8. 기존에 선택되어있는 것을 해체하기
 

'C/C++언어' 카테고리의 다른 글

함수포인터란 ?  (0) 2007.09.06
[본문스크랩] 파일 입.출력  (0) 2007.09.06
[강좌] MASM 6.0 사용법  (0) 2007.09.06
어셈블리 초급 기초 #1(정태식님)  (0) 2007.09.06
어셈블리 초급 기초 #2(정태식님)  (0) 2007.09.06

이건 Windows API정복 -가남사 : 김상형저- 에 있던 소스 입니다..


대략 API의 정복이라 불리우는 책이죠.. 좋은책입니다.

게임을 만들기 위해.. DirectX를 해야 하지만.. 이 DirectX를 받쳐주는게 API 방식의 프로그래밍과
MFC의 프로그래밍...


물론 각각 C언어와 C++ 언어가 되야 하지만.. 이것만 어떻게든 하면 DirectX 는 상당히 쉬운편이라

생각되네요..


뭐 대략 요령을 깨우치지 못하면.. 무척 어렵지만....


----------------- 소스.. ------------------

// 밑에 부분 복사후 Alt + F8 인가로 소스 정렬후 봅시다.. 그게 편합니다..

// 나중에 자기가 짠 소스에 허우적 거리지 않기를..


#include <windows.h>

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
HWND hWndMain;
LPCTSTR lpszClass=TEXT("Class");

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance
   ,LPSTR lpszCmdParam,int nCmdShow)
{
 HWND hWnd;
 MSG Message;
 WNDCLASS WndClass;
 g_hInst=hInstance;
 
 WndClass.cbClsExtra=0;
 WndClass.cbWndExtra=0;
 WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
 WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
 WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
 WndClass.hInstance=hInstance;
 WndClass.lpfnWndProc=(WNDPROC)WndProc;
 WndClass.lpszClassName=lpszClass;
 WndClass.lpszMenuName=NULL;
 WndClass.style=CS_HREDRAW | CS_VREDRAW;
 RegisterClass(&WndClass);

 hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,
  CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
  NULL,(HMENU)NULL,hInstance,NULL);
 ShowWindow(hWnd,nCmdShow);
 hWndMain=hWnd;
 
 while(GetMessage(&Message,0,0,0)) {
  TranslateMessage(&Message);
  DispatchMessage(&Message);
 }
 return Message.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
 HDC hdc;
 PAINTSTRUCT ps;

 switch(iMessage) {
 case WM_CREATE:
  return 0;
 case WM_PAINT:
  hdc=BeginPaint(hWnd, &ps);
  EndPaint(hWnd, &ps);
  return 0;
 case WM_DESTROY:
  PostQuitMessage(0);
  return 0;
 }
 return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}


//---------------- 이하 소스 끝 -------------------


실행하면 화면에 박스(프로그램 창)가 하나 뜹니다. 이 박스를 생성할때 박스 생성 조절은...

WinMain 함수의 CreateWindow 함수에서 조정하면 되는것이고요..


이 박스는 WndProc 함수안의 switch 문 안의 여러 이벤트(환경 변화..

대략 프로그램 실행시 입력하는.. 마우스 움직임 이라던지.. 키보드 눌림 변화..)

에 동작 하게 만듭니다.

 제목: [강좌] MASM 6.0 사용법 2/8
 ------------------------------------------------------------------------------

[MASM6.0/2]인스톨, 에디터를 맘에 들게..

      이번 강좌에서는 매즘 6.0의 인스톨 방법과 에디터를 사용자 맘에 들
    게 바꾸는 것에 대해 얘기를 하겠습니다.
   
    **** 매즘 6.0의 인스톨에 대해 ****
      
    1. 인스톨 방법에 대해..
    --> 매즘 6.0의 인스톨 방법은 아주 간단합니다. SETUP디스크를 A 드라
        이브에 넣고 SETUP을 타이프 하면 다음과 같은 메뉴가 나옵니다.
        (편의상 번호를 삽입합니다.)
        1. Install the Microsoft Macro Assembler
        2. Install the Macrro Assembler using defaults
        3. Run SETUP without installing any files
        4. View important documentation notes (README.DOC)
        5. View the packing list (PACKING.LST)
        6. Copy a file from the distribution disks
        7. Exit SETUP
        만약 여러 프로그램들을 다루신 분들이라면 설명이 없이도 금방 인
        스톨하시리라 봅니다. 가장 간단한 방법은 2번을 고르면 금방 됩니
        다. 그러면 여러 결정 상황 ( SAMPLE프로그램들을 복사할 것인가의
        여부, BRIEF 에디터의 에뮬레이션을 할 것인가의 여부 등등 )
        을 보여주고, 거기서 바꿀만한 것을 바꾸고 메뉴 맨 위에 위치한
        NO CHANGE를 선택하면 곧바로 인스톨을 시작합니다.

        모든 결정상황에 대해 계속 문답식으로 내용을 결정하면서 인스톨
        을 하고자 1번을 선택하면 됩니다. 그런데 제가 하나 터보 계열의
        에디터을 쓰시는 분께 권하고 싶은 것은, 디폴트로 되어 있지는
        않지만 BRIEF 에디터의 에뮬레이션을 선택하는 것이 좋을 듯 싶습
        니다. 터보 계열과 키가 많이 비슷하므로 조금만 바꾸면 별 어려움
        없이 에디터를 사용할 수 있기 때문입니다.

    2. 인스톨 후에 해야 할 것들..
    --> 위의 방법으로 다 인스톨을 하고 난다음 해야 할 중요한 것은 바로
        AUTOEXEC.BAT과 CONFIG.SYS를 바꾸는 것입니다. 그리고 TOOLS.PRE
        라는 파일을 TOOLS.INI로 이름을 바꾸어야 합니다.
       
        AUTOEXEC.BAT와 CONFIG.SYS에 첨가할 내용이 들어있는 파일은 기본
        디렉토리인 C:/MASM/BIN에 NEW-VARS.BAT과 NEW-CONF.SYS가 있습니
        다. 이것을 각각 AUTOEXEC.BAT과 CONFIG.SYS에 삽입하면 됩니다.
        NEW-CONF.SYS는 FILES과 BUFFERS를 정하는 것으로 별로 중요하지는
        않으나 NEW-VARS는 헬프 파일과 INCLUDE파일 등의 경로가 들어 있
        으므로 꼭 AUTOEXEC.BAT에 삽입을 해 주어야 합니다.

        또,  TOOLS.PRE가 기본디렉토리인 C:/MASM/INIT에 있는데 이를
        TOOLS.INI로 바꾸어 주어야 합니다.

    **** PWB의 간략한 소개와 에디터를 맘에 들게 바꾸는 법 ****

    1. Programmer's WorkBench (PWB)에 대해서..
    --> 저번에도 말했듯이 PWB는 윈도우 방식의 통합 개발 환경으로서 거
        기서 소스 작성, 어셈블, 링킹, 디버깅, 소스 브라우져(browser)
        기능, 완벽한 온라인 헬프 시스템을 이용할 수 있습니다.
       
        PWB를 실행시키려면 간단히 PWB라고 치면 됩니다. PWB를 사용하는
        방법은 터보나 볼랜드 계열의 IDE(통합 개발 환경)과 비슷하고
        또한 온라인 헬프에 아주 자세히 나와 있으므로 생략을 합니다.

    2. 에디터 사용에 관해서...
    --> 여러 에디터를 씀에 따라 문제가 되는 것은 각각 에디터 마다 쓰는
        키가 다르다는 것입니다. 원래 pwb에디터의 키는 많이 쓰이고 있는
        터보 계열과 많이 달라서 애를 먹습니다. 가령 터보 계열에서는
        파일의 맨 처음으로 가는 키는 Ctrl+PageUp 키이나 pwb에서는
        Ctrl+Home키 입니다. 비교적 비슷한 것이 위에서 말한 Brief에디터
        입니다. 그래서 터보계열을 많이 쓰시는 분은 위에서 인스톨을 할
        때 Brief에디터 에뮬레이션을 선택하라고 권고 했습니다. 그럼
        이번에는 에디터의 키를 자기 맘대로 바꾸는 것에 대해 이야기 하
        겠습니다.

        에디터의 키를 바꾸려면 pwb의 메뉴에서 OPTION의 Key Assignments
        를 선택을 합니다. 그러면 그냥 어떤 파일이 화면에 뜨게 됩니다.
        그 내용은  함수 : 키 의 형식이 되어 있는데 단순히 키를 변환 시
        키면 됩니다.

        가령 파일의 맨 처음으로 오는 함수의 이름은 pwb에서 begfile입니
        다. 만약 Brief 에뮬레이션 모드를 선택하지 않고 디폴트로 인스톨
        을 했으면 다음과 같이 나올 것입니다.
           begfile : Ctrl+Home
        여기서 만약 그 키를 Ctrl+PgUp으로 바꾸고 싶으면 단순히 다른 에
        디터를 쓰는 것처럼 Del키를 이용해서 위의 Home을 PgUp으로 바꾸
        면 됩니다. 그러면 바뀌었다는 것을 알려 주기 위해서 바뀌어진 줄
        의 색깔이 바뀌게 됩니다. 만약 인식할 수 없는 키를 적거나 ( 가
        령  sadf처럼) 잘못된 형식으로 적거나 ( 키의 조합은 +로 연결되
        어야 하는데 -으로 연결을 할때)하면 다음줄로 내려 가지 못하게
        되어 키를 제대로 써 주어야 계속 키를 바꿀  있게 됩니다.

        그러면 많이 쓰이면서도 에디터 마다 다른 함수를 열거해보죠.

        begfile --> 파일의 맨 처음에 커서를 위치.
        endfile --> 파일의 맨 끝에 커서를 위치.
        insertmode --> insert, overwrite 상태를 토글(toggle)
        ldelete    --> 한 줄을 지움
        menukey --> 메뉴를 부름
       
        다음은 pwb기본 에디터에는 함수 인데 brief에디터를 에뮬레이션하
        기 위해 만들어지 매크로 입니다. 즉 이것들은 brief에뮬레이션을
        선택했을 경우에만 나옵니다.

        beginnig_of_line --> 줄의 맨처음으로 간다. ( 칼럼 1)
                             ( 기본 함수에 begline이라고 있는데 이것은
                               앞의 공백문자를 무시한, 맨 처음 문자로
                               가는 함수 입니다. )                    
  
        delete_next_word -->  앞의 한 단어를 지운다.
        delete_previous_word --> 뒤의 한 단어를 지운다.
        delete_to_eol        --> 커서의 위치 부터 줄 끝까지 지운다.

       
        아마 이 정도만 바꾸어도 그리 불편하지 않게 쓰실 수 있을 것입니
        다. 다른 기능들, 가령 컴파일하는 키라던지 찾기의 단축키라던지
        등을 바꾸시려면 그에 해당하는 키를 바꾸시면 됩니다. 더 능력이
        되시면 스스로 매크로를 만들어서 tools.ini에 삽입하여 자기만의
        기능을 에디터에 삽입할 수도 있습니다. 위의 delete_next_word도
        매크로로 만든 예입니다. 인스톨시 Brief 에디터 에뮬레이션을 선
        택한 후 tools.pre 혹은 tools.ini를 보시면 그 안에 매크로를 정
        의한 것을 보실 수 있을 것입니다. 함수에 대한 설명이나 매크로를
        만드는 방법은 도움말에 자세히 나오니 참조하시길 바랍니다.

        그리고 키를 바꾸실 때 어떤 함수의 이름이 나왔는데 그 함수를 잘
        모르시겠다면 커서를 그 함수 이름에다 위치하고 F1키를 누르시면
        곧바로 잘 된 설명을 보실수 있을 것입니다. 마우스로는 마우스 커
        서를 위치하고 오른쪽 버튼을 누르면 도움말이 나옵니다. 실제로
        모든 것이 이런식으로 도움말을 볼 수 있는데 가령 소스를 보다가
        inc라는 명령어가 나왔는데 이것이 어떤 명령어인지 잘 모르겠다면
        커서를 inc위에다 위치시키고 마우스를 버튼을 누르거나 F1키를 누
        르면 곧 도움말을 볼 수 있습니다. 

        !!!! 주의할 점 !!!!
        위의 키를 바꾼것을 계속 유지하려면 꼭 세이브를 해야합니다. 메
        뉴에서 세이브를 선택하던지 세이브 단축키를 이용하면 됩니다.

        그리고 그밖의 에디터에 관한 옵션들 가령 화면 색이라던지 탭을
        몇 칸 뛰울 것이라던지 하는 것은 Options에  Editor Settings을
        고르고 위의 키 바꾸는 것처럼 값을 바꾸어 주면 됩니다.

        ** 블럭지정에 관한 팁 **

        블럭을 지정하고 블럭 지우기, 버퍼에 복사하기 같은 것을 하는데
        이런 키들은 볼랜드 계열의 키와 비슷합니다. 즉 블럭을 지정하는
        것은 shift키를 누르고 커서 이동키를 누르면 되고요. 카피하는 것
        은  Ctrl+Ins 이고 지우는 것은 Ctrl+Del이고 뭐 이런 것은 다 같
        은데 하나 좋은 것은 블럭을 지정을 할 때 한글의 F4를 지정해서
        블럭을 설정을 할 때처럼 박스모양의 블럭을 지정할 수 있다는 것
        입니다. 즉 마우스로 블럭을 지정할 때 오른쪽 버튼을 누르면
        박스모양의 블럭과 일반 줄단위의 블럭을 토글하면서 지정을 할
        수 있습니다. 한 번 해보세요. 키는 Ctrl+B를 누르면 두 지정방
        식간의 토글을 할 수 있습니다.


 ───────────────────────────────────────
 제목: [강좌] MASM 6.0 사용법 3/8
 ------------------------------------------------------------------------------

[MASM6.0/3] 소스를 빌드하는 방법..

  이번 강좌에서는 소스 프로그램을 빌드(Build)를 하여 실행 파일로
만드는 방법을 알아보도록 하겠습니다.


**** 소스 프로그램을 빌드 하는 법 ****

  소스 프로그램을 빌드하는 방법은 이미 고급언어로 실행 파일을 만
어본 경험이 있으신 분은 아마 쉽게 하리라고 봅니다. 그러나 몇가지
주의할 점이 있고 또한 여러 모듈로 된 프로그램을 빌드하는 방법에
대해 잘 모르시는 분이 있으실 것 같아 간단히 설명해 보겠습니다.

1. 하나의 소스로 구성된 프로그램을 빌드하는 방법
-->어떤 소스가 파일로 저장되어 있다고 가정을 퇸襟때 그것을 어셈블

   을 시키기 전에 먼저 해주어야 될 것은 어셈블할 프로그램의 타입
   을 정해주어야 합니다. 매즘 6.0에서 제공하는 타입은 여러가지가
   있는데, 가령 크게 나누어서 도스용 실행파일과 OS용 실행파일이
   있는데 각각 실행파일의 형태가 달라지기 때문에 이러한 것들을
   정해주어야 합니다. 또한 도스용 실행 파일중에서도 확장자가 EXE
   인 것도 있고 COM인 것도 있는데 이 둘 역시 실행파일에 있어서
   차이가 나기 때문에 이러한 타입들을 정해 주는 과정이 필요합니다.

   그래서 어셈블할 프로
 ?텝타입을 정해주려면..
   (1) Options메뉴에서 Build Options을 선택합니다.
   (2) Set Main Language를 선택하여 assembler를 선택합니다.
   (3) Set Initial Build Options을 선택하여 어셈블할 파일의 타입을
       정합니다. 만약 EXE파일이라면 DOS EXE를 COM 파일이면 DOS COM
       을 선택하면 되겠습니다.

    프로그램의 타입과 함께 고려해야 할 것은 실행파일을 어떤 버젼으로
   만드는가를 결정하는 것입니다. 여기서 버젼이라함은 Release 혹은
   Debug버젼을 말하는 것으로 두 버젼의 차이점을 말하자면5  Release
   버젼은 Debug버젼과는 달리 실행 파일내에 디버그 정보를 포함하지 않
   습니다. 말 그대로 Release버젼은 프로그램이 버그가 없어서 곧 발표(?)
   해도 될 때 Release버젼으로 프로그램을 빌드하는 것입니다. 만약 어떤
   에라가 있어서 계속 디버깅을 한다면은 Debug버젼으로 빌드를 하는 것이
   좋습니다. 왜냐하면 Debug버젼으로 프로그램을 빌드하면  코드뷰라는
   디버거의 기능을 십분 발휘할 수 있기 때문입니다. 그러나 Debug버젼은
   실행 파일내에 디버그 정보가 들어가기 때문에 실행파일이 더 커지게
   됩니다. 그러므로 상황에 맞게 버젼을 선택해야 하겠습니다. 두 버젼을
   선택하는 것은 위의 Options 메뉴의 Build Options에서 정할 수 있습
   니다.

2. 여러개의 모듈로 이루어진 프로그램을 빌드하는 법
-->하나의 소스로 이루어진 프로그램과는 달리 여러개의 파일로 이루어진
   프로그램을 빌드할 때는 Program List를 만들어야 합니다. Program
   List란 어떤 프로그램을 구성하는 소스 파일의 리스트를 말하는 것으로
   PWB는 이를 가지고 make file을 만들게 됩니다. make file이란 고급언
   어에서 프로젝트 파5일과 유사한 역할을 하는 파일로 확장자가 .MAK입
   니다. 아마 볼랜드 C같은 데서 프로젝트 파일을 만들어본 경험이 있으신
   분은 쉽게 프로그램 리스트를 작성하실 것이라고 봅니다.

   그럼 실제로 얘를 들면서 여러개의 모듈로 이루어진 프로그램을 빌드
   하는 방법을 보도록 합시다.

   다음의 예제 프로그램은 화면에 Hello, world를 찍는 프로그램으로
    HELLO.ASM과 PUTSTR.ASM 두개의 파일로 이루어진 프로그램입니다. 메
   인 모듈인 HELLO.ASM에서 외부 함수인 PutStr불러서 화면에 문자를 뿌
   리게 되어 있습5니다. 소스의 곳곳에 매즘 5.1에서는 생소한 것들이 있는
   데 추후 강좌를 통해서 설명하기로 하고 일단은 소스를 살펴봅시다.

   메인 모듈 - HELLO.ASM

    ; HELLO.ASM defines a string and calls the procedure PutStr to
    ; display the text. PutStr is in a separate module PUTSTR.ASM

                .MODEL  small, c
   
    ; Tell assembler PutStr's argument type and how to call PutStr:
    PutStr      PROTO   pMsg:PTR BYTE

                .DOSSEG
                .STACK

                .DAT5A
    msg         BYTE    "Hello, world.", 13, 10, 0  ; null-terminated
                                                    ; string
                .CODE
                .STARTUP                  ; Initialize data and stack
                                          ; segments
                INVOKE  PutStr, ADDR msg  ; call external procedure
                  .ENDW
           
                ret
    PutStr      ENDP
       
                END       
                   

    그러면 위얹텝소스를 각각 HELLO.ASM과 PUTSTR.ASM로 저장을 합니다.
    자 이제부터 위의 프로그램을 빌드하는 방법을 단계를 밟아 가면서
    살펴보도록 하죠.

    (1) 빌드할 프로그램의 타입을 정한다.
        --> 위에서 설명했든이 메뉴의 Options의 Build Options을 선택
            하여 Set Main Language을 선택하여 assembler를 선택을 하고
            Set Initial Build Optins을 선택하여 DOS EXE를 선택을 합니
            다. 버젼은 아직 디버그가 안되었다고 보고 Debug버젼을 선택
            을 합니다.
    (2) Prog5ram List를 만든다.
        --> Make메뉴에서 Set Program List를 선택을 합니다. 그러면 다이
            알로그 박스가 나오는데 메인 프로그램의 파일 이름을 적습니
            다.(HELLO). 그러면 PWB가 HELLO.MAK가 있다면 그 파일을 로드
            하게 됩니다. 없을 때는 새로운 makefile을 만들것이냐고 물어
            보는데 Yes를 선택을 합니다. 그러면 다이알로그 박스가 나
            오는데 거기엔 현재 디렉토리에 있는 파일들의 리스트가 나
            오게 됩니다. 거기서 프로그램을 구성하는 파일들얹밗선택하면
            됩니다.
            위의 예에서는 HELLO.ASM, PUTSTR.ASM이 되겠죠. 선택이 끝났
            으면 Save List를 선택합니다. 그러면 자동적으로 PWB가 HELLO
            .MAK이라는 makefile을 만들게 됩니다.

    (3) 프로그램을 빌드한다.                                          
             
        --> Set Program List를 선택하여 확장자가 .MAK인 makefile을
            불러 옵니다. 위처럼 Program List를 바로 만드후면 다시 부를
            필요는 없습니다. makefile을 로드한 후5에 여러 옵션들을 정합
            니다. MASM 옵션도 있고 BROWSE 옵션, LINK, NMAKE옵션 등이
            있지만 처음엔 디폴트 옵션으로 빌드를 하고 나중에 필요가
            있다면 적절하게 옵션들을 정합니다. 옵션을 정한후에는 MAKE
            메뉴의 Build 혹은 Rebuild All을 선택하여 프로그램을 빌드
            합니다. 아무 에라가 없으면 실행 파일이 만들어집니다.


 ───────────────────────────────────────
 제목: [강좌] MASM 6.0 사용법 4/8
 ------------------------------------------------------------------------------

[MASM6.0/4] 브라우져, 코드뷰 사용법..

  그럼 디버깅에 유용한 소스 브라우져와 코드뷰의 간단한 사용
법에 알아보도록 합시다.

**** 소스 브라우져와 코드뷰의 간단한 사용법 ****

1. 소스 브라우져의 사용법
-->소스 브라우져(Source Browser)는 함수와  변수들의  관계에
   관한 정보를 보여주는 역할을 합니다. 즉  함수와  변수들의
   정의(definition)와 그것들이 어디서 참조(reference)되었는
   지를 화면에 보여줍니다. 실제로 사용해 보시면 더욱 이해가
   쉬울 것이고요 온라인 헬프에 아주 잘나와있으므로 그것을 참
   조하면 좋을것입니다. (물론 조금의 영어 실력이 필요하죠.)
   소스 브라우져는 메뉴의 Browse 명령을 통해서 이용할 수 있
   는데 먼저 이를 이용하려면 데이타 베이스를 정의하는  준비
   가 필요합니다.

   데이타 베이스를 정의하려면....
   (1) MAKE메뉴에서 Set Program List를 선택을 하여  program
       list를 만듭니다.  이 program list는  데이타 베이스에
       필요하게 됩니다.
   (2) 메뉴의 Options에서 Browse Options을 선택합니다. 거기
       서 Generate Browse Information을 꼭 선택을 해야 합니
       다.
   (3) MAKE 메뉴에서 빌드를 하면 확장자가 .BSC(Browser Source
       Cache)인 파일이 생기는데 이 파일은 브라우져에 이용되는
       자료가 저장되어 있습니다.
                                       
   위와 같이 데이타 베이스를 정의하면  다음과  Browse메뉴에서
다음과 같은 명령어를 사용할 수 있습니다.

   (1) Goto Definition
       --> 이 명령어는 변수나 함수, 매크로 등이 정의된 곳으로
           커서를 옮겨 줍니다. 먼저 다이알로그 박스에 변수,함
           수, 매크로들의 이름이 나오는 데 그 중 커서를  옮길
      곳을 선택하고 O.K 버튼을 선택하면 곧바로 그것이 정
           의된 소스로 커서를 옮겨 줍니다. 아주 긴 소스를 보거
           나 여러개의 파일로 된 소스를 볼 때 이 명령을 사용하
           면 어떤 함수나, 매크로, 변수가 정의된 곳을 쉽게 찾
           아 볼 수 있습니다.
   (2) Goto Reference
       -->이 명령어는  함수나 변수, 매크로 등이 참조된 파일과
          줄번호들을 보여 줍니다.
   (3) View Relationships
       -->이 명령어는 프로그램에  관련된 여러 정보를 자세하게
          보여줍니다.  이 명령어를 사용하는 데는 다음과 같은
          절차가 필요합니다.
          1. 오브젝트를 설정한다.
          -->여기서 오브젝트란 각종 심볼(함수, 변수..)를 말합
             니다.
          2. 오브젝트에 적용될 오퍼레이션을 정한다.
          -->오퍼레이션은 여러 가지가 있는 데 여기서 다 설명을
             하는 것은 어려우므로 실제적으로 한번 사용해보시면
             쉽게 이해를 하시리라 봅니다.
          3. 보여줄 것들을 정한다.
          -->보여줄 것이라함은 함수, 매크로, 변수등을 말하는
             데 예를 들어서 2번의 오퍼레이션을 USE를 선택하면
             어떤 함수나 매트로 등이 사용한 것들을 죽 나열하게
             되어 있는데 여기서 보여줄 것을 함수와 변수만을 선
             택하면 그 함수가 사용한 함수와 변수만을 보여주게
             됩니다.
   (4) List References
       --> 이 명령어는 함수, 매크로, 변수, 심볼 등이 어떤 파일
           어떤 함수에 의해서 참조되었나를 쭉 나열하게 됩니다.
   (5) Call Tree
       -->이 명령어는 함수간의 관계, 즉 어떤 함수가 어떤 다른
          함수를 부르는 가를 트리구조로 보여줍니다.

 이상 소스 브라우져 사용법을 간단히 알아 보았는데 실제로 한번
써보시면 쉽게 그 기능을 이해하시리라 봅니다.

 그럼 코드뷰라는 디버거에 대해서 간단히 알아보도록 합니다.

**** 코드뷰의 간단한 사용법 ****

  먼저 코드뷰를 사용하기 전에...
 
  코드뷰를 사용하려면 저번 강좌에서 말했듯이 준비해야 할 것이
있습니다. 즉 빌드를 할 때 Debug 버젼으로 프로그램을  빌드해야
합니다. 잊어버리신 분을 위하여 다시 말씀해 드리면 Options 메뉴
에 Build Options을 선택을 해서 Debug버젼을 선택을 하시면  됩니
다. 그러면 실행파일에 Debug 정보가 들어가는데 코드뷰는 이를 이
용해서 여러가지 정보를 제공해 줍니다. 물론 보통 EXE파일도 코드
뷰를 이용할 수는 있지만 소스 레벨에서의 디버깅이 안되므로 코드
뷰를 잘 사용하시려면 Debug버젼으로 빌드하시고 사용하시는  것이
좋겠습니다.

  코드뷰가 보여주는 것들...

  RUN 메뉴에서 Debug 명령어를 선택하면 곧바로 코드뷰  화면으로
넘어가는 데 화면에 코드뷰가 보여주는 것들은 다음과 같습니다.

  먼저 프로그램을 보여주는 데 다음과 같은 세가지 방법으로 보여
줄 수 있습니다. ( Options의 Source Window를 선택 )

  1. 매즘 소스 코드 (Source Code)
  -->이것은 기본 선택 사항으로 PWB에서 짠 소스 코드를 그대로 보
     여줍니다.
  2. 디스어셈블된 소스 코드
  -->소서와 같은 디스어셈블러와 같이 디스어셈블된 어셈블리 코드
     를 보여주므로 1번과 같이 심볼들을 볼 수 없게 됩니다.
  3. 1과 2의 혼합
  -->즉 매즘 소스 코드와 함께 그 소스 코드가 디스어셈블된 코드가
     같이 나오게 됩니다. 이 경우는 매크로나 지시어등이 어떻게 기
     계어로 바뀌었는지 살펴 볼때 좋습니다.

  위의 프로그램을 보여주는 것 뿐만 아니라 디버깅에 유용한 여러정
보들을 화면에 보여 줍니다.
  즉 메모리, 레지스터, 지역 변수 등을 보여주는데 이는 코드뷰 메뉴
에서 View 명령어를 선택하여 보고 싶은 것만 볼 수 있도록 합니다.
 
  기타 여러 기능을 사용하는 방법 - Watch를 사용하거나, Go, Trace
Step 하는 방법, Break Point를 지정하는 방법 등은 일반 터보 계열이
나 볼랜드 계열의 고급언어에서 제공하는 것과 비슷하므로 생략하기로
합니다.

  다음 강좌부터는 일반 환경 사용법이 아닌 언어상으로 매즘 6.0에서
나아진 점에 대해 살펴보도록 합시다.


 ───────────────────────────────────────
 제목: [강좌] MASM 6.0 사용법 5/8
 ------------------------------------------------------------------------------

[MASM6.0/5] 고급언어적 지시어들..

  먼저 강좌가 늦어진 점에대해 죄송하게 생각합니다. 그럼  요번강좌
에 서는 MASM 6.0에서 획기적으로 나아졌다고 할수  있는  고급언어적
인 지시어들에 대해 설명을 해보겠습니다. 이  기능으로 말미암아  어
셈 프로그래밍이 한결 편해졌으며   소스를  보는 것이 매우  쉬어진,
그야말로 MASM6.0에서 가장 맘에 드는   변화일  것입니다.  간단하게
기능을 설명을 하자면 마치  고급언어(특히 C)적인 명령어를 제공하게
된 것입니다. 즉 while, if, else, repeat 등의 기능과 비슷한 기능을
이용할 수 있게 된 것입니다. 그럼 하나 하나의 기능들을 살펴 보기로
합시다.

1. 비교 판단 지시어
-->비교 판단 지시어로는 .IF, .ELSEIF, .ELSE 지시어가  있습니다. 
   구문을 살펴보면..

   .IF 조건1
   명령문
   [.ELSEIF 조건2
   명령문]
   [.ELSE
   명령문]
   .ENDIF

   여기서 []는 그  안에  있는  것은  생략될  수   있음을   말합니    
   다. 예를 들어서 살펴보면..

   예제)
   .IF       cx == 20
   mov       dx, 20
   .ELSE
   mov       dx, 30
   .ENDIF

   만약 위와 같은 프로그램은 실제로 어셈 명령어로 바뀔  때 다음과
   같이 번역됩니다.
   .IF       cx == 20
             cmp      cx, 014h        ; 위의 고급언어적
             jne      @c0001          ; 지시어가 프로세서
             mov      dx, 20          ; 명령어로 바뀐 코드
   .ELSE
             jmp      @c0003          ; ""
   @c0001:
             mov      dx, 30          ; ""
   .ENDIF
   @c0003:

   비교에 보시면 아시겠지만 고급 언어적인 지시어를  사용해서  프
   로그래밍을 하면 훨씬 편하며 보기도 쉬움을 알 수  있을  것입니
   다.

2. 루프 지시어
-->고급 언어에서 처럼 루프를 실행할 수 있는 새로운 지시어가  생겼
   는데, 고급언어의 WHILE물 같은 .WHILE, .ENDW, REPEAT문의 역할
   을 하는 .REPEAT, .UNTIL, .REPEAT, .UNTILCX 등이 있습니다. 그러
   나, 이런 새로운 지시어를 사용함에 있어서 새겨두어야 할 것이 있
   는데..

   1. 위의 지시어들은 새로운 명령어가 아니라 적당하게 프로세서 명
      령어들로 바뀌어지는 것입니다.
   2. 위의 루프 지시어들은 조건을 가지고 판단하기 때문에 부호있는
      데이타의 선언에 주의를 해야 합니다.

   2번을 부가 설명을 하자면, 조건을 底은疵   평가함에 있어서 그
   조건에 있는 오퍼랜드가 부호가 있는가의 여부를 알아야 합니다.그
   것을 확실히 해주지 않으면 예상하지 않은 결과가 나올 수 있기 때
   문입니다. 구체적으로 조건이 음수를 담고 있는 메모리를 참조하고
   자 할때는 그 메모리를  할당을  할때  새로생긴  지시어인  SBYTE
   (signed bytes), SWORD (signed words),  SDWORD  (signed  double

   words)를 사용해야 하거나 PTR 오퍼레이터를 써야 합니다.

   2.1 .WHILE 문
   -->.WHILE문을 구현하려면 두 지시어 .WHILE, .ENDW을 사용합니다.
      여기서 .ENDW은 WHILE문의 끝을 알리는 데 쓰입니다.

      구문)
      .WHILE 조건
      명령문
      .ENDW

      한가지 예를 들어봅시다. 다음 예제는 한 버퍼에서 다른 버퍼까
      지 데이타를 카피하는 것인데 데이타의 끝을  나타내는데  달러
      마크($)가 사용됩니다.

                .DATA
      buf1      BYTE       "This is a string", '$'
      buf2      BYTE       100 DUP (?)
                .CODE
                sub        bx, bx               ;BX를 0으로
                .WHILE     (buf1[BX] != '$')
                mov        al, buf[bx]          ; 한문자를 읽음
                mov        buf2[bx], al         ; buf2에 카피
                inc        bx                   ; 다음 문자
                .ENDW                                    

   2.2 .REPEAT 문
   -->.REPEAT문은 C에서 do문, 파스칼에서 repeat문과  같은  역할을
      하는 것입니다. 지시어는 .REPEAT 과 .UNTIL(또는  UNTILCXZ)을
      사용합니다.

      구문)
      .REPEAT
      명령문
      .UNTIL 조건
      .REPEAT
      명령문
      .UNTILCX [조건]

      예를 보면 확실히 이해가 갈겁니다. 다음 예제는 키보드로 입력
      을 받아서 버퍼에 넣어주는 예제입니다. 엔터를 누르면  루프는
      끝이 나도록 되어 있습니다.

                .DATA
      buffer    BYTE       100 DUP (?)
                .CODE
                sub        bx, bx
                .REPEAT
                mov        ah, 01h
                int        21h             ; 키보드로 부터 입력
          mov        buffer[bx], al  ; 버퍼에 저장
                inc        bx              ; 다음 버퍼 포인트
                .UNTIL     (al == 13)      ; 엔터일때 까지 루프
      여기서  .UNTIL  지시어는  비교   점프   명령어를   생성하고
      .UNTILCXZ는 loop 명령어를 생성합니다. 다음의 예제는  지시어
      를 명령어 코드로 바꾸어 표시한 것인데 이를 살펴보면  이해가
      갈 것입니다.

      ASSUME    bx:PTR SomeStruct

                .REPEAT
      @C001:
                incax
                .UNTIL ax == 6
                cmp        ax, 06h      ;비교 점프 명령어로
                jne        @C001        ;바뀜


                .REPEAT
      @C003:
                mov        ax, 1
                .UNTILCXZ
                loop       @C003        ;루프 명령어로 바뀜


                .REPEAT
      @C004:
                .UNTILCXZ  [bx].field != 6  ; 루프 명령어로
                cmp        [bx].field, 06h  ; 바뀜
                loope      @C004            ;
      


   2.3 .BREAK, .CONTINUE 문
   -->.BREAK와 .CONTINUE문은 .REPEAT 이나 .WHILE문에서 루프를  빠
      져 나가거나 계속 루프를 돌릴 때  쓰입니다.  마치  C언어에서
      break와 continue와 비슷합니다.

      구문)
      .BREAK [.IF 조건]
      .CONTINUE [.IF 조건]

      이 구문들에서 주의할 것은 .IF다음에 .ENDIF가 쓰이지 않는 것
      입니다. 당연하죠 보통의 .IF문처럼 조건이 맞으면  그  아래에
      있는 명령어가 수행되는 것이 아니라 그냥 루프를 빠져  나오거
      나 다시 루프를 시작할 때 쓰이니까요. 그럼 예제를 보면서  살
      펴봅시다. 다음 예제는 '0' 부터 '9'사이의 키만 받아들이고 그
      문자를 화면에 내보냅니다. 그 외의 다른 문자가 들어오는 경우
      는 계속 키를 입력을 받습니다. 그리고 엔터가 들어면 끝이나게
      되어있습니다.

                .WHILE 1                ; 무한 루프
                mov        ah, 08h     
                int        21h          ; get key without echo
                .BREAK     .IF al == 13 ; 엔터면 루프를 나감
                .CONTINUE  .IF (al<'0') || (al>'9')
                                        ; 숫자가 아니면 다시 루프
                mov        dl, al      
                mov        ah, 02h
                int        21h          ; 문자를 화면에 내보냄
                .ENDW

      만약 위의 예제를  /Fl, /Sg 옵션을 써서 어셈블을 하고 리스팅
      파일을 살펴보면 다음과 같이 나옵니다.

                         .WHILE 1              ; 무한 루프
 0017          
*@C0001:
 0017  B4 08             mov        ah, 08h     
 0019  CD 21             int        21h       ; get key without echo
                         .BREAK     .IF al == 13 ;엔터면 루프를 나감
 001B  3C 0D    *        cmp    al, 00Dh
 001D  74 10    *        je     @C0002
                         .CONTINUE  .IF (al<'0') || (al>'9')
 001F  3C 30    *        cmp    al, '0'
 0021  72 F4    *        jb     @C0001
 0023  3C 39    *        cmp    al, '9'
 0025  77 F0    *        ja     @C0001
                                           ; 숫자가 아니면 다시 루프
 0027  8A D0             mov        dl, al      
 0029  B4 02             mov        ah, 02h
 002B  CD 21             int        21h      ; 문자를 화면에 내보냄
                         .ENDW
 002D  EB E8    *        jmp    @C0001
 002F          
*@C0002:
            

      주의 : 칼럼 제한으로 리스팅 파일을 일부 고쳤습니다.


3. 조건에 대해서
-->.IF, .REPEAT, .WHILE 지시어의 조건을 씀에 있어서 관계형 오퍼레
   이터나 속성을 나타내는 PTR을 쓸 수 있습니다.  여기서는  조건에


   쓰이는 각종 오퍼레이터와 오퍼랜드, 속성, 우선순위에 대해  알아
   보도록 합시다.

   3.1 오퍼레이터에 대해서
   -->조건문에 쓰이는 오퍼레이터는 C의 오퍼레이터와 똑같습니다.

      오퍼레이터             의미
      ----------             ----
          >                  크다.
         >=                  크거나 같다.
          <                  작다
         <=                  작거나 같다.
          &                  bit test
          !                  logical NOT
         &&              logical AND
         ||                  logical OR

      오퍼레이터가 없는 조건문은 c언어에서와 같이 다룹니다. 즉 예


      를 들어서 .WHILE (x)는 .WHILE(x != 0)과 같고,  .WHILE(!x)는
      .WHILE (x == 0)과 같습니다.

      또한, 플랙 이름(ZERO?, CARRY?, OVERFLOW, SIGN?,  PARITY?)을
      써서 조건을 나타 낼 수도 있는데 즉 .WHILE  (CARRY?)같이  쓸
      수 있습니다.

   3.2  Signed, Unsigned 오퍼랜드에 대해
   -->조건에 쓰일 수 있는 오퍼랜드는 레지스터, 상수, 메모리가  될
      수 있습니다. 기본적으로 조건문에 있는 오퍼랜若 unsigned로
      가정을 합니다. 그런데 만약에 비교되는 오퍼랜드가 singed라면
      문제가 생기게 됩니다. 이럴땐 PTR 오퍼레이터로 특정한 오퍼랜
      드가 singed인 것을 알려줄 수 있습니다. 예를 들어서..

                .WHILE SWORD PTR [bx] <= 0
                .IF    SWORD PTR mem1 > 0

      만약  여기서  PTR  오퍼레이터가  안쓰였다면   bx의   내용을

      unsigned로 가정하고 명령어를 생성했을 것입니다.

      위에서 말했다시피 메모리 오퍼렌드의 속성을 지정할 수도 있습

      니다. 즉..

                .DATA
      mem1      SBYTE   ?
      mem2      WORD    ?
                .IF     mem1 > 0
                .WHILE  mem2 < bx
                .WHILE  SWORD ptr ax < count

      위의 예제에서는 메모리를 할당을 할 때 미리 SBYTE라고 알려주
      었기 때문에 전의 예제처럼 SWORD PTR을 쓸 필요가 없습니다.

   3.3 우선 순위에 대해
   -->C언어처럼 &&, ||, !으로 조건들을 연결을 해서 쓸  수  있는데
      이렇게 쓸 때 우선순위는 !,&&, ||순입니다. 또한 C처럼  왼쪽

      에서 오른쪽으로 조건을 살핍니다.

  오늘 강좌에서 보았듯이 MASM 6.0에서는 마치  고급언어(특히  C)와
비슷한 구문을 제공하여 프로그래밍을 매우 편하게 그리고 소스를  보
기 쉽게 만들어 주고 있습니다. 다음 강좌에서는 프로시져에 대한  일
반적인 얘기와 MASM 6.0에서 나아진 프로시져 지시어들에 대해서 알아
보도록 합시다.

 ───────────────────────────────────────
 제목: [강좌] MASM 6.0 사용법 6/8
 ------------------------------------------------------------------------------

[MASM6.0/6] 확장된 프로시져 기능(1)

  요번 강좌에서는 프로시져 지시어를 이용해서 효과적으로  파라메터를
주고받는 방법과 파라메터의 수가 가변적인 것을 다루는 방법에  대해서
얘기해 보도록 하겠습니다.

1.1 프로시져에 인수(argument)를 전달하기
    프로시져에 인수를 전달하는 방법은 여러 가지 방법이 있는데, 예를
    들면 레지스터를 통해서 전달하는 방법과 global 변수를 통해  전달
    하는 방법 등이 있다. 또한 고급언어에서 쓰는 방법으로 가장  일반
    적인 전달 방법으로 쓰이는 것은 스택을 통해서  전달하는  것이다.
    그러나 인수를 스택에 전달하는 방법도 언어에 따라서 다른데  이렇
    게 스택을 이용하여 인수를 전달하는 관행을 calling convention(호
    출 관행)이라고 한다.
      다음의 예는 c 스타일의 호출 관행에 따라  프로시져를  정의하고
    스택을 통하여 인수를 전달하는 부분이다.
      C에서 addup이라는 3개의 인수들을 받아 그 인수들을 모두 더해서
    값으로 돌려주는 함수를 생각하자. c 프로그램에서  addup  (  arg1
    ,arg2, 10)이라고 함수를 호출하면  아래와 같은 어셈블리로 번역될
    것이다. 단 arg1, arg2를 word크기의 변수라고 생각을 하자(즉 int
    와 같이 2 byte 크기)

                mov     ax, 10   
                push    ax       ;세번째 인수를 스택에 저장
                push    arg2     ;두번째 인수를 스택에 저장
                push    arg3     ;첫번째 인수를 스택에 저장
                call    addup    ;함수를 호출
                add     sp, 6    ;인수의 스택 프레임을 없앰
                ...       
                ...
    addup       PROC    NEAR     ;near형 프로시져 이므로 스택에
                              ;어드레스 저장으로 2byte를 차지
                push    bp       ;base pointer 를 저장 (2bytes)
                mov     bp, sp   ; bp=sp
                mov     ax, [bp+4] ; 첫번째 인수를 읽어드림
                add     ax, [bp+6] ; 두번째 인수랑 더함
                add     ax, [bp+8] ; 세번째 인수랑 더함
                mov     sp, bp
                pop     bp
                ret                ;결과값을 전달해줌
    addup       ENDP
           
      위의 소스를 가지고 스택이 어떻게 변화되는가를 그림으로 설명을
    하면 다음과 같다.

    1. call addup 하기전             2. call addup 후                                      
    상위    |           |            상위    |           |
    메모리  +-----------+            메모리  +-----------+
            |세번째 인수|                    |세번째 인수|
            +-----------+                    +-----------+
            |두번째 인수|                    |두번째 인수|
            +-----------+                    +-----------+
            |첫번째 인수|<-SP                |첫번째 인수|
            +-----------+                    +-----------+
            |           |                    | 리턴 주소 |<-SP
            +-----------+                    +-----------+
     하위   |           |             하위   |           |
     메모리 +-----------+             메모리 +-----------+
            |           |                    |           |


   3. push bp                         4. pop bp 한 후 
      mov bp, sp 한후
                       
  상위    |           |               상위    |           |      
  메모리  +-----------+               메모리  +-----------+      
          |세번째 인수|<-BP+8                 |세번째 인수|
          +-----------+                       +-----------+      
          |두번째 인수|<-BP+6                 |두번째 인수|
          +-----------+                       +-----------+      
          |첫번째 인수|<-BP+4                 |첫번째 인수|
          +-----------+                       +-----------+      
          | 리턴 주소 |                       | 리턴 주소 |<-SP      
          +-----------+                       +-----------+      
   하위   |이전 BP 값 |<-BP/SP        하위    |           |
   메모리 +-----------+               메모리  +-----------+      
          |           |                       |           |


  5. ret 한 후                        6. add sp, 6 한 후
                               
  상위    |           |               상위    |           |->SP
  메모리  +-----------+               메모리  +-----------+      
          |세번째 인수|                       |           |
          +-----------+                       +-----------+      
          |두번째 인수|                       |           |
          +-----------+                       +-----------+      
          |첫번째 인수|<-SP                   |           |
          +-----------+                       +-----------+      
          |           |                       |           |      
          +-----------+                       +-----------+      
   하위   |           |                하위   |           |
   메모리 +-----------+                메모리 +-----------+     
          |           |                       |           |      


      좀더 부연 설명을 하자면, 위에서 보다시피 c의 호출  관행에서는
    인수의 마지막 것부터, 즉 위의 소스에서는 상수(10)를 먼저 스택에
    저장을 하고 그리고 두번째, 첫번째 이렇게 거꾸로  스택에  인수를
    저장을 한다. 그러나 파스칼은 이와는 달리 순행으로 스택에 인수를
    저장한다. 그리고 c에서는 위의 예제와 같이 호출한 쪽에서  인수를
    위한 스택 프레임을 없애기 때문에(위의 소스에서 add sp, 6)  인수
    의 갯수를 가변적으로 할 수 있으나 파스칼은 호출된 쪽, 즉 프로시
    져 자체에서 인수를 위한 스택 프레임을 없애기 때문에 가변적인 인
    수를 가질 수 없다. 이렇듯 언어마다 호출관행이  다르므로  나중에
    혼합 언어 프로그래밍을 할때 이러한 점을 잘 고려하여 프로그램을
    작성해야 한다.

1.2 프로시져 정의하기
    PROC 지시어를 사용해서 프로시져를 정의할 때에는 저장되어야할 레
    지스터들과 프로시져에서 받는 파라메터들을 정의할 수 있다.  이전
    버젼과 달리 파라메터들을 프로시져 정의할 때 정의함으로써 편하게
    파라메터들을 이용할 수 있다. 그럼 예를 들어서 C 형식의 프로시져
    를 정의하면 다음과 같다.

      myproc PROC FAR C PUBLIC USES di si, var1:WORD, arg1:VARARG
      |    |      |          | |        |  |       |
      +--+-+      +----+-----+ +---+----+  +---+----+
         |             |           |           |
      label        attributes    reglist    parameters

    일반적으로 프로시져의 구문을 살펴보면..

    label PROC [attributes] [USES reglist] [,parameter[:tag]...]


    구성 요소                 설    명                 
    ---------                ------------
     label                   프로시져의 이름
    
     attributes              distance와 언어 종류, visibility와 같
                             은 속성을 나타낸다.
    
     reglist                 USES 지시어에 따르는 레지스터들은 프로
                             시져의 시작부분에  저장된다.  레지스터
                             들은 빈칸이나 탭으로  구분되어야  하며
                             콤마로 구분되면 안된다.  USES 지시어가
                             오면 자동적으로 어셈블러가  프로시져의
                             앞부분(프롤로그)에는 push 명령어로  레
                             지스터들을 스택에 보관하고  프로시져의
                             끝부분(에필로그)에는 pop 명령어로 다시
                             저장된 레지스터 값을 복귀한다.  프로시
                             져가 레지스터의 내용을  바꾸게  될  때 
                             USES reglist를 사용함으로써 프로시져가
                             호출된 쪽으로 복귀해도 레지스터의 내용
                             이 바뀌지 않도록 할 때 사용한다.

     parameter               스택을 통해서 프로시져에전달되는 파라
                             메터 리스트들. 리스트가 한 줄을 넘어서
                             게되면 줄 끝에 콤마를 하고 다음줄에 연
                             결하여 나타낼 수 있다.


    1.2.1 Attributes
      프로시져의 attribute 구문은 다음과 같다.

      [distance] [langtype] [visibility] [<prologuearg>]

      구성요소        설  명
      ---------      --------
      distance        RET 명령어의 형태(retn, retf)를 결정하게 되는
                      요소이다. NEAR나 FAR 둘중의 하나가 될  수  있
                      다.  만약에  distance가   기술되지   않았다면
                      .MODEL에  의해서   결정된다.   TINY,   SMALL,
                      COMPACT, FLAT은 NEAR가  되고  MEDIUM,  LARGE,
                      HUGE는 FAR가 된다.

      langtype        위에서 언급한 호출관행(calling  convention)을
                      결정하게  되는  요소이다.   BASIC,   FORTRAN,
                      PASCAL은 프로시져의 이름을 대문자로  변환하고
           마지막 파라메터가 스택의 가장 처음에 위치하게
                      된다. (가장 낮은 메모리에 위치하게 된다.) RET
                      n 명령어를 통해서  스택프레임을  프로시져에서
                      없애게 되어 가변적인 파라메터를 구현할 수  없
                      다.
                      C나 STDCALL은 프로시져의 scope가 PUBLIC이거나
                      EXPORT일 경우는 프로시져 이름의  맨  앞부분에
                      밑줄('_')을 붙이고 첫번째  파라메터가  스택의
                      가장 처음에 위치하게 된다. (가장 낮은  메모리
                      에 위치하게 된다.) SYSCALL은  C의  호출관행과
                      똑같은데 다만 프로시져 이름의 앞부분에 밑줄을
                      붙이지 않는다. STDCALL은 프로시져를 호출한 쪽
                      에서 스택 프레임을 없애기  때문에  파라메터의
                      수가 가변적일 수 있다.

      visibility      프로시져가 다른 모듈에서 사용 가능한가를 나타
                      낸다. visibility에는 PRIVATE, PUBLIC,  EXPORT
                      가 될 수 있다. 프로시져는 PRIVATE이라고  명시
                      되지 않은 이상 기본적으로 PUBLIC로  간주된다.
                      만약에 visibility가 EXPORT인 경우는  linker가
                      프로시져의 이름을 export table에 올린다. 당연
                      히 EXPORT는 PUBLIC을 포함하게 된다. OPTION 지
                      시어를 써서 default visibility를 정할  수  있
                      다. OPTION PROC:PUBLIC 이라고 하면 프로시져의
                      기본 visibility는 PUBLIC이 된다.

      prologuearg     프로시져의 처음과 끝부분의 코드 즉 prologue와
                      epilogue 코드를 생성하는 데 영향을 주는  요소
                      이다. 나중에 설명할 것이다.


    1.2.2 Parameters
      만약 PROC 지시어에 reglist가 존재하면 레지스터 리스트와 파
      라메터는 콤마로 구분된다.

      파라메터의 구문을 살펴보면..

      parmname[:tag]

      parmnane은 파라메터의 이름이고 tag는 BYTE나 WORD와 같은 타
      입이나 가변 파라메터를 나타내는 키워드 VARARG가 될  수  있
      다. VARARG 키워드는 항상 파라메터의 리스트의 맨끝에 와야한
      다.


                           Parmname  -------+    +-- Qualifiedtype
      EX)                                   |    |
      myproc PROC FAR C PUBLIC USES di si, var1:WORD, arg1:VARARG
            레지스터 리스트와            | |       |         |
            콤마로 구분된다.  -----------+ +---+---+         |
                                               |             |
                                           Parameters        |
                                VARARG는 파라메터의   -------+
                                 맨끝에  온다.


      다음의 예는 위의 1.1의 예제 중에서 프로시져 정의부분을  파
      라메터 리스트를 나열하는 기능을 이용하여 다시 고쳐 쓴 것이
      다. 역할은 1.1.의 프로그램과 똑같으나 훨씬 보기쉽고 프로그
      래밍도 쉬워진다는 것을 알 수 있다.

      addup     PROC    NEAR C,
                arg1:WORD, arg2:WORD, count:WORD
                mov     ax, arg1
                add     ax, count
                add     ax, arg2
                ret
      addup     ENDP
        
      만약에 프로시져의 argument가 포인터 타입이라면 위의 예제와
      같이 단순히 mov 명령어 하나로 포인터가 가르키는 곳의  값을
      얻을 수  없다.  만약에  그  값을  얻으려면  포인터  타입의
      argument를 어드레스로 간주하고 값을 얻어 와야 한다. 만약에
      포인터가 near 포인터라면 mov 명령어를 두번 써서 그 값을 구
   할 수가 있다. 처음 mov 명령어는 파라메터의 어드레스를 얻어
      오고 두번째 mov 명령어는 실제 파라메터의 값을 얻어오는  것
      이다. 다음의 예제를 보면 쉽게 이해할 것이다.

      ; Call from C as a FUNCTION returning an integer

                .MODEL medium, c
                .CODE
      myadd     PROC    arg1:NEAR PTR WORD, arg2:NEAR PTR WORD

                mov     bx, arg1     ; argument의 주소를 얻는다.
                mov     ax, [bx]     ; 실제 값을 얻는다.
                mov   bx, arg2      
                add     ax, [bx]     ; 두번째 값과 더한다.
                ret

      myadd     ENDP
                END

      만약에 포인터 타입이 near인지 far인지 잘 모를 경우나  여러
      모델에 상관없이 값을 제대로 얻으려면  conditional-assembly
      지시어(directive)를 이용하여 해결할 수 있다.  아래  예제는
      위의 myadd라는 프로시져가 far나 near에  상관없이  포인터가
      가르키는 값을 얻어서 제대로 수행할 수 있게 고친것이다.

                .MODEL medium, c       ; 아무 모델이나 상관없다.
                .CODE
      myadd     PROC    arg1:PTR WORD,  arg2:PTR WORD
                IF      @DataSize
                les     bx, arg1       ; far인 파라메터
                mov     ax, es:[bx]
                les     bx, arg2
                add     ax, es:[bx]
                ELSE
                mov     bx, arg1     ; near인 파라메터
                mov     ax, [bx]    
                mov     bx, arg2      
                add     ax, [bx]
                ENDIF
                ret
      myadd     ENDP
                END

      NOTE :  위의  예제에서  @DataSize는  메모리  모델이  tiny,
              small, medium, flat일 경우는 0,  compact,  large는
              1, huge는 2의 값을 주는 predefined symbol이다.  위
              의 예제에서는 @DataSize의 값이 0인 경우는 데이타가
              near에 위치하고 그 외에는 far에 위치함을 이용한 것
              이다.


    1.2.3 파라메터의 수가 가변적인 경우( Using VARARG )
      위에서 언급한 것과 같이 호출관행이 C, SYSCALL, STDCALL인 경우
      는 PROC 지시어를 이용하여 파라메터의 맨끝에 VARARG를 이용함으
      로써 파라메터의 수가 가변적인 것도 받아 들일 수가 있다.  다음
      의 예제를 살펴보자.

      addup3    PROTO   NEAR C, argcount:WORD, arg1:VARARG

                invoke  addup3, 3, 5, 2, 4

      ddup3    PROTO   NEAR C, argcount:WORD, arg1:VARARG
                sub     ax, ax          ; ax=0
                sub     si, si          ; si=0
 
                .WHILE  argcount > 0
                add     ax, arg1[si]   
                inc     si
                inc     si
                dec     argcount                 
                .ENDW
                ret
      addup3    ENDP

        예제의 둘째줄의 invoke는 MASM 6.0에서 처음으로 생긴  프로세
      서 명령어가 아닌 어셈블러 지시어인데 프로시져를 호출할 때  파
      라메터까지 넘겨주는 기능과 또 다른 여러가지 기능으로 마치  고
      급언어에서 함수를 호출하도록 할 수 있도록 하였다. 이에 대해서
      는 다음 강좌 때 설명하기로 하겠다.

        위의 addup3라는 프로시져는 3개의 숫자를  더하는  프로시져이
      다. 3개의 숫자는 파라메터로 받는데 VARARG를  써서  파라메터의
      수를  가변적으로 받고 있다. invoke addup3, 3, 5, 2, 4라고  하
      면  addup3라는  프로시져를  부르게  되며  처음에  나오는  3은
      argcount라는 파라메터 값으로 들어가게 된다. addup3 프로시져에
      서 argcount 값은 가변적인 파라메터의 갯수를 나타내는 데  첫번
      째 3은 뒤 나오는 더해질 숫자(5, 2, 4)들의  갯수를  나타내는
      것이다.  addup3  프로시져에서는  .WHILE  루프에서   add   ax,
      arg1[si]으로 각 파라메터를 더하게 되어 있으며 si+2를 함으로써
      (위의 inc si 명령어 2개) 다음 파라메터를 읽어올 수 있게  되어
      있다. 루프의 종료 조건으로 argcount를 매 루프마다 1씩  줄임으
      로써 파라메터의 갯수를 가변적으로 할 수 있게 했다.
       위의 예제에서 invoke addup3, 5, 1, 2, 3, 4, 5라고 하면 1부터
      5까지 다섯개의 숫자를 더하게 됨을 볼 수 있다.


 ───────────────────────────────────────
 제목: [강좌] MASM 6.0 사용법 7/8
 ------------------------------------------------------------------------------

[MASM6.0/7] 확장된 프로시져 기능(2)


  고급언어에서 지역 변수(local variables)는 대부분 스택에 저장된다.
어셈블리 언어에서도 지역 변수를 구현할 수 있는데, 맨처음에는 기본적
으로 어셈블리 언어에서 지역 변수를 구현하는 방법과  다음에는  LOCAL
지시어를 이용하여 자동적으로 지역 변수를 만들고 이용하는 방법에  대
해 알아보겠다.

1.1 기본적인 지역 변수 사용법
  우선 지역 변수를 사용하려면 프로시져의 시작 부분에 지역 변수를 위
  한 공간을 스택에 마련해야 한다. 그런다음 각각의 지역 변수는  스택
  에 존재하는 위치를 통해서 참조할 수 있다. 프로시져의 끝에는  스택
  포인터를 적절히 바꾸어 줌으로써 지역 변수를 위해 할당된 스택 공간
  을 없애주어야 한다.

  다음의 예재는 지역 변수를 위해서 스택에 공간을 마련하는 것과 base
  pointer를 통해서([bp-2]) 지역변수를 참조하는 방법을  보여주고  있
  다.

                push    ax              ; 인수를 스택에 저장
                call    task
                ...
                ...
  task          PROC    NEAR
                push    bp              ; save base pointer
                mov     bp, sp          ; bp<--sp
                sub     sp, 2           ; 지역 변수를 위해 스택
                                        ; 공간을 확보
                ...
                ...
                mov     WORD PTR [bp-2], 3  ; 지역 변수를 3으로       
      
                                            ; 초기화한다.
                add     ax, [bp-2]      ; 지역변수를 ax에 더함
                sub     [bp+4], ax      ; 인수에서 ax를 뺌
                ...
                ...
                mov     sp, bp          ; 로컬 변수를 위한 공간을
                                        ; 없앤다.
                pop     bp              ; restore base pointer
                ret     2
  task          ENDP

  위의 예제 끝부분에서 mov sp, bp함으로써 SP의 원래값으로  바꾸어주
  고 있다. mov sp, bp는 sp의 내용이 프로시져안에서 바뀔 때 필요하게
  되는데 일반적으로 지역 변수를 할당할 때 위에서 sub sp,  2와  같이
  sp의 내용이 바뀌게 되므로 필요하게 되는 것이다. ret 2에서 2는상
  수로써 스택 포인터를 상수만큼 더함으로써 스택 프레임을 없애는  역
  할을 한다. 바로 전 강좌에서 언급했듯이 PASCAL 같은 언어는  이렇게
  프로시져 안에서 ret n(n은 상수)를 이용해서 인수를 위한 스택  프레
  임을 없애게 되어 가변 파라메터를 사용할 수 없게 된다.  이해가  잘
  안되면 바로 전 강좌를 살펴보기 바란다.

  예제의 명령문에 따른 스택의 상태를 그림으로 보면 쉽게  이해가  갈
  것이다.

    1. call task 하기전             2. call task 후 
                                    
    상위    |           |            상위    |           |
    메모리  +-----------+            메모리  +-----------+
            |  인  수   |<-SP                |  인  수   |
            +-----------+                    +-----------+
            |           |                    | 리턴 주소 |<-SP
            +-----------+                    +-----------+
            |           |                    |           |
            +-----------+                    +-----------+
            |           |                    |           |
            +-----------+                    +-----------+
     하위   |           |             하위   |           |
     메모리 +-----------+             메모리 +-----------+
            |           |                    |           |


   3. push bp                         4. sub sp, 2 한 후 
      mov bp, sp 한후
                       
  상위    |           |               상위    |            |      
  메모리  +-----------+               메모리  +------------+      
          |  인   수  |<-BP+4                 |  인  수    |<-BP+4
          +-----------+                       +------------+      
          | 리턴 주소 |                       | 리턴 주소  |
          +-----------+                       +------------+      
          | 이전 BP값 |<-BP/SP                | 이전 BP값  |<-BP
          +-----------+                       +------------+      
          |           |                       |지역변수공간|<-BP-2    
          +-----------+                       +------------+      
   하위   |           |               하위    |            |
   메모리 +-----------+               메모리  +------------+      
          |           |                       |            |


  5. mov sp, bp                        6. ret 2 한 후
     pop bp  한후                             

  상위    |           |               상위    |           |->SP
  메모리  +-----------+               메모리  +-----------+      
          |  인   수  |                       |           |
          +-----------+                       +-----------+      
          | 리턴 주소 |<-SP                   |           |
          +-----------+                       +-----------+      
          |           |                       |           |
          +-----------+                       +-----------+      
          |           |                       |           |      
          +-----------+                       +-----------+      
   하위   |           |                하위   |           |
   메모리 +-----------+                메모리 +-----------+      
          |           |                       |           |      


1.2 LOCAL 지시어를 통한 지역 변수 사용
  LOCAL 지시어를 사용하면 지역 변수를 사용하는 것이 훨씬 쉽고  시간
  도 절약되며 프로그램이 보기에도 쉬워진다. 이 지시어를 사용하여 지
  역 변수의 이름과 타입을 주면 어셈블러가 자동으로 스택에  얼마만큼
  의 공간이 필요한지를 계산하고 그에 따라서 그만큼의 공간을  확보하
  기위해 SP를 적절히 맞추어주는 명령어를 생성한다. 그리고  프로시져
  의 끝, 즉 프로시져가 리턴하기 전에 SP를 다시 원래의  값을  되돌려
  주는 명령어를 생성하게 된다. 즉, 위의 1.1의  예제에서  프로시져의
  맨처음과 끝의 명령어들('...'으로 양끝을 구분했음)을 자동적으로 생
  성하게 된다. 그럼 LOCAL 지시어를 사용하여 1.1의 예제를 다시  고쳐
  써보자.

  task          PROC    NEAR    arg:WORD
                LOCAL   loc:WORD
                ...
                ...
         mov     loc, 3              ; 지역 변수를 3으로       
      
                                            ; 초기화한다.
                add     ax, loc             ; 지역변수를 ax에 더함
                sub     arg, ax             ; 인수에서 ax를 뺌
                ...
                ...
                ret
  task          ENDP

  LOCAL 지시어는 PROC 지시어 바로 다음에 와야되며 LOCAL 지시어 전에
  어떠한 명령어도 오면 안된다. LOCAL 지시어의 구문은 다음과 같다.

  LOCAL vardef[,vardef]...

  각각의 vardef는 지역 변수를 정의하게 되는데 다음과 같이 구성된다.
  label [[count]][:qualifiedtype]

    구성 요소              설  명
  ------------           ----------
    label                 지역 변수의 이
    count                 주어진 label로 몇개를 할당할 것인가를  나
                          타낸다. 쉽게 말해서 배열과 같은 기능을 하
                          는 것이다. count의 양쪽에 '[]'을 표시해야
                          한다.  count가  명시되지  않으면   하나의
                          element로 간주한다.
    qualifiedtype         WORD, BYTE와 같은 타입을 말한다.

    지역 변수 리스트가 한줄을 넘어서게 되면 줄의 끝에  콤마를  찍고
    다음줄에 계속 나열하거나 다시 LOCAL 지시어를 쓰면 된다.

    어셈블러는 지역 변수를 초기화하지 않으므로 따로  초기화를  하는
    프런瀏 코드를 넣어주어야 한다. 다음의 예제는  LOCAL  지시어를
    이용하여 배열형의 지역 변수를 생성하고 0으로 초기화하는  예제이
    다.

    arraysz     EQU     20
    aprocPROC    USES di
                LOCAL   var1[arraysz]:WORD, var2:WORD
                ...
                ...
    ; 지역 변수를 0으로 초기화한다.
                push    ss
                pop     es              ; ss=es
                lea     di, var1        ; ES:DI는 array를 가르키게
                                        ; 된다.
                mov     cx, arraysz     ; 카운트
                sub     ax, ax         
                retp    stosw           ; 0으로 초기화
  ...
                ...
                ret
    aproc       ENDP

    스택에 존재하는 지역 변수를 지역 변수의 이름으로 참조를  하더래
    도 어셈블러는 지역 변수를 BP로부터의 상대적 위치로 간주하게  된
    다. 그러므로 다른 프로시져는 이 지역 변수를 참조할 수  없게  된
    다. 그런데 지역 변수를 사용할 때 주의할 점이 있는데 그것은 지역
    변수가 BP에 대한 상대적 위치로 나타내어지므로 BX랑은 결합이  될
    수 없다는 점이다. BX와  BP는  SI와  DI랑  결합할  수  있다.  즉
    [BP+BX+2]와 같은 표현은 안되고 [BP+SI+2]나  [BP+DI+2]는  가능하
    다.
    다음의 예제를 살펴보고 무엇이 잘못ed낮나 알아보자.

    index       EQU     10
    test        PROC    NEAR
                LOCAL   array[index]:WORD
                ...
                ...
                mov     bx, index
    ;           mov     array[bx], 5    ; 잘못된 문장

    위의 예제에서 array[bx]는  [bp+bx+arrayoffset]같이 bp와 bx가 결
    합하므로 잘못된 것이다. 이러한 에러는 지역 변수가  bp의  상대적
    위치로 표현된다는 것을 잘 모르면 찾아내기 어려우므로 이와  같은
    점을 알아둬야한다.


 ───────────────────────────────────────
 제목: [강좌] MASM 6.0 사용법 8/8
 ------------------------------------------------------------------------------

[MASM6.0/8] 확장된 프로시져 기능(3)

  저번 강좌에서 설명했듯이 호출 관행(calling convention)에  따라서
파라메터를 스택에 넣는 방식은 달라지게 된다. 그러나 앞으로  설명하
게될 INVOKE 지시어를 이용하면 호출 관행에 상관없이 자동적으로 알맞
게 파라메터를 스택에 넣어주고,  프로시져 호출에 관련된 여러가지 일
들을 관리해주는 기능을 한다.
  INVOKE 지시어를 이용하려면, 먼저 PROC 지시어로 프로시져가 정의되
어야 한다. 또한, C언어에서와 같이  함수의  프로토타입(prototype)을
앞에다 정의할 수 있는데, 이때는 PROTO라는  지시어를  이용하게된다.
이는 어셈블러에게 함수가 필요로하는 인수의 갯수와 타입을  알려주어
나중에 INVOKE로 함수가 호출되었을 때 건내받는 인수의 갯수와 타입을
자동적으로 검사하도록 한다.


  1.1 프로시져의 프로토타입(prototype) 정의하기
    위에서 말했듯이 MASM 6.0에서의 프로토타입은 C 언어나 다른 고급
    언의 프로토타입과 비슷한 기능을 하게된다. 프로시져의  프로토타
    입은 프로시져의 이름, 타입, 그리고 옵션으로 파라메터의 모든 이
    름을 넣을 수가 명시할 수 있다. 프로토타입은 대체적으로  프로그
    램의 맨 처음부분이나 따로 분리된 인클루드(include) 파일에 위치
    한다. 프로토타입은 특히 다른 모듈이나 다른 언어에서 프로시져가
    호출될 경우 유용한데, 이는 어셈블러가 타입이나 갯수가 맞지  않
    는 인수들을 체크할 수 있도록 하기 때문이다.

    프로시져의 프로토타입을 정의하는 것은 옵션으로 C처럼 꼭 해야하
    는 것은 아니다. 그러므로, MASM 5.X이하의  버젼  방식인  PROC과
    CALL이용하여 프로시져를 호출, 이용할 수 있다.

    프로토타입의 정의 구문은 프로시져 정의 구문에서 레지스터  리스
    트와 prologuearg 리스트, 프로시져의 scope를 제외하고는  동일하
    다. 또한, 프로토타입은 프로시져와 같이 언어종류(langtype)와 거
    리(distance)를 나타내는 속성을 정의할 수 있는데 이 속성들이 생
    략되면 .MODEL 이나 OPTION LANGUAGE 명령문에 준거하여 정해진다.
    또한 프로시져에서 정의되는 :VARARG는 옵션으로, 써도되고 안써도
    된다.

    한 함수에 대해, PROC문과 PROTO문이 같이 나오면, 둘의 속성과 파
    라메터의 갯수, 파라메터의 타입이 일치되어야 한다. 프로토타입을
    정의하는 가장쉬운 방법은  에디터로 PROC문을 카피한 후  PROC을
    PROTO로 바꾸어주고 USES reglist와 prologuearg, visibility를 지
    우는 것이다.

    예)

    ; Procedure prototypes

    addup       PROTO   NEAR C, argount:WORD, arg2:WORD, arg3:WORD

    myproc      PROTO   FAR C, argcount:WORD, arg2:VARARG

    ; Procedure declarations

    addup       PROC   NEAR C, argount:WORD, arg2:WORD, arg3:WORD

    myproc      PROC   FAR C PUBLIC <callcount> USES di, si,
                argcount:WORD,
       arg2:VARARG

    INVOKE로 프로시져를 호출하면, 어셈블러는 INVOKE에서 주어진  인
    수와 PROC에서 정의된 인수를 비교하여 체크한다. 만약에 둘  사이
    의 타입이 맞이 않으면, MASM은 에라를 내거나,  INVOKE로  주어진
    타입을 프로시져에서 요구하는 타입으로 맞게 바꾸게 된다.


  1.2 INVOKE로 프로시져 호출하기
    INVOKE는 인수를 스택에 넣고 프로시져를 호출하는 일련의  프로세
    서 명령어를 자동적으로 생성한다. INVOKE는 또한 프로시져를 호출
    할 때 다음과 같은 작업을 자동적으로 하게된다.

    * 인수를 기대되는 타입으로 바꾸어준다.
    * 인수를 올바른 순서로 스택에 넣는다.
    * 프로시져가 수행을 마친후 스택 프레임을 없앤다.

    INVOKE의 구문은 다음과 같다.

    INVOKE expression, [,arguments]

    여기서 expression은 프로시져의 이름을  나타낸다.   arguments는
    12*2 와 같은 expression이거나, 레지스터, 또는 ADDR로  나타내어
    지는 exprssion이다.

    예)
    addup       PROTO   NEAR C, argount:WORD, arg2:WORD, arg3:WORD

    myproc      PROTO   FAR C, argcount:WORD, arg2:VARARG

    addup       PROC   NEAR C, argount:WORD, arg2:WORD, arg3:WORD

    myproc      PROC   FAR C PUBLIC <callcount> USES di, si,
                argcount:WORD,
                arg2:VARARG

    프로토타입과 프로시져 구문이 위와 같이 주어졌을  때   INVOKE는
    다음과 같이 쓸 수 있다.

    INVOKE      addup,  ax, x, y
    INVOKE      myproc, bx, cx, 100, 10

    어셈블러는 타입이 안맞는 인수들을 적당한 타입에 맞추어  바꾸어
    줄 수 있다. 예를 들면 위의 addup 프로시져는 WORD형타입을 요구
    하지만 아래의 경우도 문제없이 쓸 수 있다.

    * BYTE, SBYTE, WORD, SWORD
    * 8 비트 또는 16비트 레지스터
    * -32K 부터 64K 까지의 상수
    * NEAR 포인터

    만약에 요구되는 타입보다 넘어오는 인수의 타입이 크기가 작을 경
    우, 어셈블러는 넘어오는 타입의 크기를 늘리게 된다.

    1.2.1 에라 찾기
      어셈블러가 인수의 크기를 늘리려고 할 때, 어떤 레지스터를  이
      용하게 되는데, 이는 레지스터값으로 들어오는 인수의 값과 중복
      될 수 있어서 이는 에라의 원인이 된다. 예를 들면 다음과  같이
      C 언어 타입의 호출이 있다고 하자.

      INVOKE myprocA, ax, cx, 100, arg

      위에서 arg가 BYTE 크기의 인수이고 myprocA는 다 WORD크기의 인
      수를 요구한다고 하면 어셈블러는 BYTE크기의 arg를 WORD 크기로
      늘려서 스택에 넣어야 한다. 즉, 다음과 같은  코드를  생성하게
      된다.

                mov     al, DGROUP:arg
                xor     ah, ah
                push    ax

      C는 저번 강좌에서 말했듯이, 인수를 역순으로  스택에  넣는다.
      즉 위의 예에서는 arg, 100, cx, ax 순으로 넣는다. 그러나 위의
      코드처럼 arg를 늘리게 될 때 쓰는 레지스터가 ax이므로  맨처음
      의 인수, ax는 중복되게 되므로 내용이 달라지게 된다. 이  경우
      에는 어셈블러가 에라 메세지를 내게 된다. 이런 경우은  invoke
      를 다시 적당히 고쳐주어야 한다.

      INVOKE는 되도록이면 인수의 크기를 늘이기 위해 적은  레지스터
      를 사용한다. 그러나 인수를 늘리게 되면, 불가피하게  레지스터
      를 사용하게 되는데, AX 레지스터를 사용하게 되고, 어쩔때는 DX
      레지스터도 사용하게 된다. 결과적으로 AL, AH,  AX는  빈번하게
      사용되므로 이 레지스터를 이용하여 인수를 넘기는 것은  되도록
      이면 삼가하는 게 좋고 많이 쓰이지 않는 DX같은 레지스터를  쓰
      는 것이 좋다.

    1.2.2 생성된 코드 검사
      INVOKE를 이용하면 위와 같이 인수의 크기를 늘이게 되는 작업을
      하는 코드를 자동적으로 생성하게 된다. 자동적으로 생성된 코드
      를 확인하려면 코맨드 라인(command-line) 옵션에서 /Sg  옵션을
      이용해서 리스트 파일을 보면 어떻게 코드가 생성ed낮는지  확인할
      수 있다.


      강좌를 마치면서...

      사실은 강좌를 몇번 더 해야되는데, 제가 곧 군입대를 하게 되어
      서 강좌를 이쯤 마치게 되니 죄송하게 되었습니다. 변변치  않은
      강좌를 게으르게 쓰느라고 미안한 점도 많네요.  그동안  도움이
      되셨는지 모르겠습니다. 송구스런 마음으로 이만 강좌를  마칠까
      합니다.

'C/C++언어' 카테고리의 다른 글

함수포인터란 ?  (0) 2007.09.06
[본문스크랩] 파일 입.출력  (0) 2007.09.06
[본문스크랩] VC++ Article  (0) 2007.09.06
어셈블리 초급 기초 #1(정태식님)  (0) 2007.09.06
어셈블리 초급 기초 #2(정태식님)  (0) 2007.09.06

+ Recent posts