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

출처 : 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
 [1] 제목 : 감히 어셈블러 강의를 해볼까 합니다.
 올린이 : 까망벌레(정태식  )    94/10/24 17:25    읽음 : 599  관련자료 없음

안녕하세요...
후후....아직 가입도 안된 상태에서 무슨 글을 올린다는것이.
조금 그렇네요,
하지만 여기 와 보니깐 어셈블러에 대한 강의가 하나도 없기에
제가 먼저 싲가해 볼까 합니다.
저도 사실 아는거 별로 없지만 그래도 이것만이라도 여러분과 함께
나누고 싶어서 .....
우선 어셈블러에 들어가기 앞서 하드웨어의 기본적 구성에 대해
올릴까 합니다. 조금 지겹더라도 읽어 주세요...
그리고 혹시 모르시는 점이나, 아님 궁금한점,
또는 흉을 보실일이나 분제점 등이 있다면,
과감히 말씀 하셔요~
헤헤~
그럼 시삽님에게 양해를 구하며 시작 해 보겠습니다.
처음 시작하는 것은 [초급-기초]로서 여기서는 하드웨어의
기본적인 구성에 대해서 아주 간략히 소개해 드립니다.
그 다음 [초급-실습] 에서는 직접 프로그램들을 해 보면서
각각에 대해 관련된 설명들을 해 나가겠습니다.
그리고 [초급-보충]은 설명 중간중간에 말하기에는
너무 양이 많고 중요한 것들을 따로 묶어서 설명을 하겠습니다.

그 다음 과정으로 물론 [중급]이겠지요....
여기서는 프로시져 및 함수들, 그리고 C언어와의 링크등을
설명할 계획이고요, 또한 의사명령에 대해 알아보겠습니다.

[상급]과정은 과연 제가 할 수 있을지는 모르지만...
헤헤.,...아직 까지는 저도 상급과정을 공부하는 중이라...
아마 한다고 하면 여기서는 시스템 프로그래밍 기법에 대해
할 계획 입니다.
시스템 프로그램이란 SUPER VGA메모리및 직접 제어, 또는
모뎀이나 클럭주파수 등과 같은 것들을 다루어 볼까 합니다.
사실 저로서는 자신이 없네요....
저도 딸리니깐요...헤헤~
일단은 하는데 까지 해보겠습니다.


 [3] 제목 : [초급-기초] 맛보기...
 올린이 : 까망벌레(정태식  )    94/10/24 17:28    읽음 : 828  관련자료 없음


맛보기...


어떤 프로그램을 실행시키게 되면, 과연 컴퓨터는 그 과정을 어떻게 처리하는지

에 대해 일단 간단히 설명을 드리겠습니다.

프로그램은 DOS 에서 화일이라는 방법으로 디스크에 저장되어 있습니다.

하지만 컴퓨터 자체에서 이 프로그램을 수행할려면, 디스크 상에서 직접 수행을

하지는 못함니다. 그래서 이 프로그램을 램(이하 메모리)으로 읽어 들이게 됩니다.

그럼 메모리로 읽어 들인것 만으로 수행이 가능 할까요?

그렇지는 않습니다. 왜냐하면 램에서는 연산기능 ( 예를 들면 덧셈 뺄셈 등등 )

을 직접 하는 기능이 없기 때문 입니다. 램에서는 그냥 데이터를 보존하는 기능

밖에는 없습니다. 이런 점에서 디스크와 별다른 차이점은 없습니다. 하지만 램이 꼬옥

필요한 이유중의 하나는 데이터를 가져오거나 보관할때 디스크와는 속도가 차원이

다르게 빠르다는 것이지요.            그럼 왜 프로그램을 수행하는데 있어서

이러한 연산기능이 꼬옥 필요할까요? 그건 모든 프로그램들은 근본적으로 어떠한

주어진 데이터를 더하거나 빼거나 해서 수행되어지는 일련의 과정들이라고 할 수 있기

때문입니다.

그럼 과연 어디서 이러한 연산들을 수행하는가 하면, 여러분도 아시다 시피 바로

컴퓨터의 가장 핵심부분이라고 할 수 있는 CPU라는 곳에서 수행하고 있습니다.

정리해서 총괄적으로 다시 말하자면, 일단 어떤 프로그램을 실행하기 위해서는

디스크로 부터 화일의 내용을 메모리로 읽어 들여 그 메모리에 저장되어 있는

데이터                                                                                                                                                                                                                                                           
레지스터란 과연 무엇인가?

비유를 하자면, 연습장이라고 할 수 있습니다. 물론 이 '연습장'이라는 단어가

레지스터의 모든 기능을 나타내어 주는 단어는 결코 아닙니다. 그냥 레지스터가

담당하는 수많은 기능들의 한 부분(기능)만을 뜻하는 것입니다.

그리고 메모리라는 것은 어떤 주어진 데이타 목록 이라고나 할까요.

( 여기서 데이타는 컴퓨터에게 직접 수행 명령을 주는 명령어도 포함 됩니다 )

우리는 이 목록에서 원하는 값을 얻기 위해 연습장에다가 쓰고 지우고 하게

됩니다. 그래서 최종적으로 원하는 값을 얻게 되지요.

사실 레지스터라는것은 별거 없습니다. 메모리와 거의 같은 구조로 되어 있고,

여기에 그 메모리(레지스터) 속에 들어 있는 데이터를 연산하는 기능이 하나

추가된 것이라고 생각 하시면 간단합니다.


예를 들어 '3+4=' 이라는 문제의 답을 원한다고 합시다.

이것은 우선 어떤 화일(디스크)에다가 저장을 시켜야 겠지요.

그럼 일단 컴퓨터에서는 이 '3+4='라는 '데이터'를 메모리로 읽어 오게 됩니다.

( 엄격히 말하자면 여기서 데이터라는 것은 '3' 과 '4'뿐이고 '+' 나 '='라는 것은

컴퓨터에게 주는 연산 명령이라고 할수 있습니다. 하지만 이 '데이터'와 '연산명령'을

일단은 메모리로 모두 읽어오게 됩니다 )

다음으로 이 첫번째 값인 '3'을 레지스터에 집어넣게 됩니다. 그 다음 '+' 라는 것을

읽어 옴으로써 '아.. 3 에다가 어떤 값을 더하라는 것이구나' 라고 생각하고

준비합니다. 그 다음에 오는 '4' 를 보구서 미리 준비했던것 같이 바로 '3'에다가

'4'를 더해서 그 결과를 얻게 되는 것입니다.

그 결과를 어디에 놓는지는 나중에 설명 드리겠습니다.


( 혹시 램(RAM) 에 대해서 모르시는 분이 계시지는 않겠지요 )


흐~~~ 역시 버벅거리는 경향이 있네요...

여러분꼐서 읽어 보시고 부족한 점이나 궁금하신 점이 있으시면 과감히 물어보세요

사사로운것도 좋습니다.

그리고 흉을 보셔도 괜찮습니다. ( 뽀드득 ) 히~~~

다음에는 메모리(RAM)에 대해 간단히 설명해 드리겠습니다.









 [4] 제목 : [초급-기초] 메모리에 대해서 #1
 올린이 : 까망벌레(정태식  )    94/10/24 17:30    읽음 : 627  관련자료 없음

두번째로 메모리에 대해 설명을 드리고자 합니다.

어셈블러 강의를 한답시고 램, 아니면 이상한 것들만 주욱 늘어놓는지에 대해

투덜거리시는 분이 계실 지도 모르지만... 사실 이런것들을 제껴 놓고 어셈블러에

대해 설명한다는 건 불가능 하다고 생각하기 때문 입니다. 다른 어떤 교재를 봐도

다아 이렇게 시작해요...히힛~


그럼 메모리에 대해서...

메모리가 몰까.... 아마 램이 1메가라는 둥 4메가라는 둥.... 이런 말들은 마니

들어보셨을줄로 압니다. 이게 바로 메모리라는 것이지요. 뒤에 '메가'라는 것은

그 메모리가 저장할 수 있는 데이터 양을 말하는 것입니다.

메모리의 구조에 대해 말씀드리겠습니다.


1. 비트, 바이트???

아마도 여러분은 '비트' 또는 '바이트' 라는 용어에 대해 한번쯤은 들어보셨을 줄로

압니다. ( 처음 듣는 거라도 상관은 없지요 ... )

과연 이 비트(bit)라는 것은 무엇일까..... 여러분 2진수에 대해 아시죠?

갑자기 왠 2진수냐고요? 흐...그거에 대해 또 설명을 드리지요.

컴퓨터 ( 이하 PC ) 는 모든 움직임이 전기로 되도록 만들어 졌습니다. 하지만

멍청하게도 이 PC라는 것이 전기가 '흐른다', '안흐른다' 라는 것 밖에는 구분을

못합니다.

사람 같으면 전기의 세기정도를 구분할 수 있지만 아쉽게도 그걸 구분하지는

못합니다. 그래서 PC는 이 '흐른다', '안흐른다' 로 모든 것을 인식할 수 밖에는

없지요. ( 보통 이것을 '스위치'를 '켯다' 또는 '껏다' 라고 비유하기도 합니다.)

여기서 2진수와의 공통점을 발견할 수 있습니다. 2진수에서 역시 0 과 1의

두가지 숫자밖에 사용할 수 없고, 이 두가지 숫자로 모든 수를 표현 할 수 밖에

없지요. 그래서 이 두가지 공통점을 연결해서 '흐른다 = 1', '안흐른다 = 0' 으로

보통 약속되어져 있습니다. 그래서 PC 자체에서 일어나는 과정들을 2진수로 표현하게

됩니다.


다시 '비트' 로 넘어오지요...

우선 10진수 54라는 것이 메모리에 어떤 모양으로  저장 되는지 살펴보겠습니다.

그냥 54라고 저장하면 된다고요? 헤헤헤.... 어떻게요? PC는 0과 1이라는 숫자만

사용할 수 있다고 했는데....

그럼 이 54를 2진수로 바꿔 보겠습니다.

110110

이렇게 바뀌지요. 그럼 이걸 어떻게 저장하지요?

아까 '스위치'라는 걸 예를 들었는데요, 이 각각의 자릿수의 숫자


'1' , '1' , '0' , '1' , '1' , '0'


들을 각각의 스위치로 대비시켜 볼 수 있습니다.

'1' 이라는 숫자는 스위치를 '켜다', '0' 은 '끄다' 로 정한다면


'켜다' , '켜다' , '끄다' , '켜다' , '켜다' , '끄다'   <-- 스위치

  1        1         0       1         1        0     <-- 2진수


라고 이쁘게 바뀌었지요? 그리고 스위치가 전부 6개가 사용 되었습니다.

이렇게 하면 드디어 PC에서 알아 들을 수 있는 방법으로 바뀌게 되었습니다.

메모리에는 이런 스위치가 수도없이 들어 있습니다.

이런 스위치들의 어떤 부분이 위와 같이 고정되어 있다고 한다면, PC는 메모리를

보면서 스위치들의 조합 상태를 보고 언제라도 54라는 숫자를 꺼내올 수가 있는 거죠.

이제 '비트'의 정의를 내리겠습니다.

위의 예에서 스위치가 전부 6개가 사용되었다고 했지요?

이 비트라는 것은 '스위치' 하나를 지칭하는 단어 입니다.

위의 예를 본다면 '10진수 54를 나타내기 위해서 전부 6비트가 사용되었다'

라고 말 할수 있습니다.

메모리라는 것이 바로 이 수 많은 스위치들의 집합체인 것입니다.

그럼 바이트(Byte)는 ?

바로 이 비트 8개를 묶어 1바이트라고 합니다.

예를 들어 연필 12개를 1타스 라고 부르는 것과 동등 합니다.

정리 하자면


8 bit  =  1 byte

1024 byte  =  1 Kbyte

1024 Kbyte  =  1 Mbyte


라고 요약할 수 있겠지요...


2. 메모리 처리방식

PC의 처리 방법은 이 'bit'라는 개념 보다는 'byte'라는 개념을 주로 사용

합니다. 다시 말하면 만약 이 54라는 10진 숫자를 2진수로 바꾸면 , 110110

이라는 6개의 비트만으로 표현이 가능 하지만, PC 자체에서는 00110110 과같이

항상 8개의 비트를 묶어서 저장하게 됩니다.  좀더 구체적으로 만약 54 와 3이라는

숫자를 연달아 메모리에 저장하게 되면

110110 11   이라고 저장하면 비트(메모리)를 조금만 사용해도 되는것을

구지 00110110 00000011 이렇게 항상 각각의 데이터들을 8비트, 즉 1바이트로

저장하게 된다는 뜻입니다.

이유는 원래 생겨먹은게 그렇게 생겨 머거서 그렇죠 머...히히~

(자세한건 다음 기회에... )

그래서 앞으로 모든 수행은 ( 특별한 경우 이외에는 ) 이 byte 개념으로 설명이

됩니다.


3. ASCII 코드에 관해

그럼 과연  영문자 ( abcdefg ) 와 같은 것들은 메모리에 어떻게 저장이

될까.....라고 궁금해 하신다면,

컴퓨터는 숫자밖에 인식을 못합니다. 그래서 이 영문자들도 다른 컴퓨터가 인식할 수

있는 숫자로 대치해 주어야만 합니다.

일단 이것부터 설명을 드리지요... character (문자) 의 개념에 대해서 말이지요.

이부분을 잘 이해하셔야만 합니다. 사실 이해하기 쉽도록 설명하기가 여간 곤란한

내용이 아닐 수 없네요...하지만 용기를 가지고...

PC에서는 모든 문자, 예를 들면

a 부터 z, A 부터 Z, 0 부터 9, 또는 ~!#@$#$%^&*()_+=-\| 와 같은 특수 문자들...

이 모든 문자들에 각기 값이 주어져 있습니다. 이것은 IBM PC 어느 컴퓨터에서나

각 대응되는 값들은 항상 같습니다.

( 참고로 숫자 0 부터 9들도 모두 문자로 취급 합니다 )

예를 들어 ,

a = 97

b = 98

...

2 = 50

A = 65

B = 66

..

$ = 36

...

이와 같이 각 문자들에는 고유 값들이 있습니다.

이것은 전 세계 표준으로 어딜가나 같습니다.

( 대부분 어셈블리어 책에는 이런 ASCII코드 표가 수록되어 있습니다. )

특히 주의 하실 점 두가지는 PC에서는 영문 대문자와 소문자의 대응 값(ASCII 코드

값) 이 다르게 주어진다는 것입니다.

그리고 또 하나는 숫자 5 와 문자 5는 엄연히 종류가 다른 내용입니다.


그럼 문자 'a'라는 것이 메모리에 어떻게 저장 되는지 알아 봅시다.

a 라는 문자의 ACSII코드 값은 97 이라고 정의 되어 있습니다.

그럼 이것을 2진수로 바꾸어 보겠습니다.

01100001  이렇게 바뀌지요...

그럼 끝 입니다. 이걸 바로 메모리에 저장 시키면 되는 거지요...

하지만 여기서 문제가 하나 발생 합니다.

과연 PC가 메모리에서 01100001이라는 값을 받아들일때 과연 이것을 10진수로 97이라는 값으로 받아들이냐... 아니면 이것을 문자 'a'로 받아들이냐가 발생합니다.

97이라는 값으로 받아들이냐... 아니면 이것을 문자 'a'로 받아들이냐가 발생합니다.

하지만 걱정 마시길....사용자 측에서 만약 이것을 문자로 취급해라 하면 문자로

취급하고 숫자(값)으로 취급해라 하면 값으로 취급한다는 것입니다.

다시 말해 만약 97이라는 값을 저장 시켰다고 했을때, 사용자가 이것을 값이 아닌

문자로 취급해라 하면 이 값이 문자로 바뀌어서 취급된다는 점...

반대로 'a'라고 저장을 시키더라도 메모리에는 97이라는 값이 저장되게 되므로 이를

값으로 취급하라고 하면 값으로 취급한다는 것입니다.

이제 문자 'I have 35$' ( 이거 문법에 맞는지는 모르지만 ) 를 메모리에 어떻게

저장 시키는지 알아봅니다.

I = 73, h = 104, a = 97, v = 121, e = 101, 3 = 51, 5 = 53, $ = 36

이렇게 정의 되어 있습니다. 그리고 문자들 사이의 Blank(공백)은 0으로 값이 정해져

있습니다. 이걸 토대로 메모리에 저장 되는 형태를 살펴 보지요...

01001001 00000000 01101000 01100001 01111001 01100101 00000000

   I       공백      h         a        v        e      공백


00110011 00110101 00100100

   3         5        $


이와 같이 메모리에 순서대로 저장되어 집니다.

여기서 하나 중요한 점은 한 문자를 기억 하는데는 8비트, 즉 1바이트의 메모리를

사용한다는 점을 알 수 있습니다.

그리고 영문 모두 합치고 숫자 그리고 특수문자 모두 합쳐바야 문자의 종류가 255개가

되질 않습니다. ( 126까지에서 모든 사용 가능한 문자가 대응될 수 있습니다)

그럼 127번 부터는 어떤 문자가 되응 되어 있을까요?

그건 확장 ACSII코드라고 하는 특수 문자가 대응되어지고 있습니다.

보통 우리들이 키보드 상으로는 입력할 수 없는 문자들.. 예를 들어 하트 모양이나

네모, 또는 직선, 십자표시 등등이 대응되어 있습니다.

만약 이런 문자들을 화면상에 찍어보고 싶다 하신 분들은,

일단 ALT키를 누르고 계세요, 그다음 오른쪽 숫자 키패드에서 228을 친 다음 알트키를

때 보세요...그럼 아마 시그마 기호가 찍힐 겁니다. 이런 식으로 확장 아스키 문자도

사용 할 수 있습니다.


제가 보기에도 설명이 좀 미흡한데....모르시는 점 계시면 물어보시길...

메모리에 관한 설명은 다음에 계속 됩니다.



 [5] 제목 : [초급-기초] 메모리에 대해서 #2
 올린이 : 까망벌레(정태식  )    94/10/24 19:41    읽음 : 540  관련자료 없음

1. 메모리 찾아가기 ...

혹시 1메가의 메모리에는 전부 몇개의 스위치(비트)가 있는지 한번 계산해

봅시다.

겨산해 보면......8388608개의 어마어마한 숫자가 나옵니다.

이걸 byte로 바꾼다고 해도 1048576byte 라는 무시 못할 숫자가 나옵니다.

전에도 말씀 드렸다 시피, PC에서는 대부분의 데이터 처리를 byte로 한다고

말씀드렸습니다.

예를 들어 큰 건물을 생객하 보죠. 여기에는 전부 1048576개라는 갯수의 방이 있

다고 해봅시다. 그리고 각각의 방에 순서대로 1번 부터 2,3,4,5....1048576 이렇게

번호를 매길 수 있겠죠. 그리고 각 방은 크기가 모두 같습니다. 그럼 이 각각의

방들에 어떤 물건들을 넣어 놓습니다. 그리고 모든 방들이 꽈악 찼습니다.

여러분은 만약 여기서 지금 필요로 하는 물건을 꺼내기 위해서는 이렇게 많은

방들 중에 필요로 하는 물건이 어디에 있는지 어떻게 알수 있죠?

당연히 그 물건이 들어있는 방의 번호를 알 고 있다면 아주 쉽게 찾아 올 수

있습니다. 마찬가지로 메모리도 이와 같이 각각의 바이트 마다 번호가 매겨져

있다고 할 수 있습니다. 그리고 각 방의 크기는 1바이트의 크기로 정해져 있는

것이지요.

이것을 3차원 적이 아닌 1차원 적으로 생각하시는 것이 훨씬 편합니다.

다음과 같이 ...

 5번방  6번방  7번방  8번방  9번방  10번방.....

;------------------------------------------

; 사과 ;  배  ; 휴지 ; 담배 ; 성냥 ;  꽃  ; .......

;------------------------------------------

그럼 성냥을 꺼내오기 위해서는 9번방을 찾아가서 꺼내오면 돼겠지요.

그럼 메모리로 시선을 바꾸어 보겠습니다.

만약 메모리에 'IBM PC 486' 이란 문자들을 메모리로 기억 시켰다고 하면,

  15  16  17  18 19  20  21  22  23  24   <-- 방 번호

;----------------------------------------

; I ; B ; M ;   ; P ; C ;   ; 4 ; 8 ; 6 ; ................

;----------------------------------------

 이와 같이 그림으로 나타낼 수 있습니다. 만약 pc에서 지금 4라는 문자를

가져오고 싶다고 한다면 그냥 22번 방에 있는 내용을 읽어오면 되는 겁니다.

보통 위와 같이 15, 16, 17,18 과 같은 숫자들을 주소(Address)라고 명합니다.


-- 참고 -- 16진수에 대해

컴퓨터에서 주로 사용하는 숫자의 진수는 2진수라고 했습니다.

하지만 2진수라는 것은 PC에서 보기에 사용하기 좋지만 사실 사용자가 보기에는

정말 까다롭고 번거롭기 그지없는 진수 입니다. 그럼 이 2진수라는 숫자를 좀 더

사용자 쪽에서 사용하기 편한 숫자로 대치해 보는 것도 좋겠지요. 그럼 이걸 10

진수로 바꾼다고 한다면 물론 사용자 쪽에서는 더이상 바랄것이 없겠지요.

하지만 이 10진수와 2진수와의 상호간 변환은 많은 계산을 필요로 한다는 문제점을

안고 있습니다. 그 대신 사용하는 진수가 바로 16진수라는 것입니다.

이것은 2진수와의 상호 변환이 매우 간단하기 때문 입니다.

가령 11101001 이라는 1바이트 숫자를 16진수로 바꾼다고 한다면, 아마 이걸

10진수로 바꾸어서 다시 16진수로 바꾸는 방법이 있을 수 있겠지요. 하지만

이런 방법 보다 더욱 편한 방법이 있습니다.

바로 이 2진수 숫자들을 오른쪽에서 부터 4개씩 짤라가는 것입니다.

1110, 1001 이렇게 말이지요 그럼 이 뒷부분의 숫자를 16진수로 바꾸면

9 라는 숫자가 나옵니다. 그리고 앞의 것은 14가 나오죠. 이걸 각각 16진수로 하면

9 와 E 가 됩니다. 그럼 이걸 그대로 쓰면 9E 라는 16진수로 쉽게 변환 되는 것이자요


1110 1001   <-- 2진수

  9    E    <-- 16진수

이렇기 때문에 PC에서는 16진수를 많이 사용하게 됩니다.



2. 세그먼트(Segment) 와 오프셋(Offset)

그럼 PC에서는 이러한 주소들을 어떻게 관리하는지에 대해 알아보도록 하겠습니다.

우선 레지스터에 관해 조금만 말씀드리겠습니다.

CPU에는 레지스터라는 장소가 있습니다. 전에 말씀드렸다시피 레지스터라는것은

메모리와 거의 차이가 없다고 했습니다. 그래서 레지스터라는 것은 데이터를 저장

하고 그것을 연산하는 것인데, 역시 데이터를 저장하는데 있어서 그 용량(크기)

의 제한이 있다고 할수 있습니다.

모든 레지스터들은 그 크기가 65535로 제한이 되어 있습니다. 이걸 2진수로 바꾸면

1111111111111111 이 되어 전부 16비트가 됩니다. 즉 바로 2바이트의 크기 제한을

가지고 있습니다. ( 참고로 2바이트를 보통 1워드라고 불릅니다 )

16진수로 바꾸면 FFFF 로 제한이 되어 있다고 할 수 있죠.


메모리 주소를 알아내고 찾고 하기 위해서는 일단 레지스터라는 곳에 메모리

주소를 넣어놓으면 그 주소에 있는 데이터를 가지고 작업을 하게 되는 것이지요.

그럼 여기서 문제가 생갑니다. 그건 바로 1048576개의 주소들을 각각 1대1로

대응되도록 해야 하는데, 레지스터의 제한은 65535로 제한되어 있다는 것입니다.

만약 3450번지에 있는 데이터를 조작하기 위해서는 레지스터에 먼저 3450이라는

수를 넣어주면 되지만, 98500번지에 있는 데이터를 조작하기 위해서는 65535로

제한되어 있는 레지스터에서는 불가능 하게 됩니다.

그럼 어떻게 하면 될까요?

바로 레지스터 2개를 사용하면 됩니다. 즉 2개의 레지스터들이 메모리의 주소를

지시하게 하면 가능하게 되는 것이지요.

그럼 2개의 레지스터, 즉 4바이트의 용량으로 컨트롤 할 수 있는 메모리의 크기는

4294967295바이트, 즉 약 4095메가의 메모리를 컨트롤 할 수 있다는 결론이 나옵니다.

충분하지요? 하지만, 하지만 말이죠 도스 상에서 직접 컨트롤 할 수 있는 메모리의

크기는 위의 숫자와는 다른 약 1메가의 메모리밖에 컨트롤 할 수 없습니다.

왜 그럴까요? 분명 2개의 레지스터가 메모리주소를 지시하는것만은 틀림 없습니다.


여기에 바로 세그먼트와 오프셋이라는 개념이 나옵니다.

바로 위의 두개가 메모리주소를 조작하는데 사용되는 용어 입니다.

잠시,절대주소 라는 것이 있습니다. 각 메모리의 방에는 각각 고유의 숫자가 정해져

있습니다. 이 숫자들을 절대주소 라고 부릅니다. 위에서 보았듯이 이 절대주소를

나타내기 위해서는 하나의 레지스터만으로는 불가능 하겠죠?

세그먼트와 오프셋 모두 각각 1워드(2바이트)의 크기를 가지고 있습니다.

이 두개가 과연 어떻게 메모리의 절대 주소를 나타낼까....

여기선 간단한 계산이 필요 합니다.

예를 들어 만약 세그먼트가 1FE4 이고 오프셋이 43DA 라고 한다면 ,

( 물론 16진수 입니다 )

절대 주소를 구하는 방법은 식으로 나타내자면

세그먼트 * 10(16진수에서의 10입니다) + 오프셋

이렇게 하면 절대 주소가 나오는 것이지요.

위의 예를 가지고 계산해 보면

세그먼트에 우선 16진수 10을 곱해 봅니다

1FE4 * 10 = 1FE40

그리고 여기에 오프셋을 더하면

1FE40 + 43DA = 2421A

그럼 2421A가 바로 절대 주소를 나타내는 값입니다.

10진수로 바꾸면 147994 의 절대주소값이 되는 것입니다.

이것을 보통 1FE4:43DA 이렇게 씁니다.

앞에것이 세그먼트, 뒤에것이 오프셋이 되는 것입니다.

참고로 여기서 주소 지정의 비 유일성이라는것이 있는데,

이것은 다음에 설명을 드리지요...

 그리고 다음에는 레지스터에 관해 대략적으로 설명을 드리지요..




 [6] 제목 : [초급-기초] 레지스터에 대해서
 올린이 : 까망벌레(정태식  )    94/10/25 02:55    읽음 : 524  관련자료 없음

레지스터에 관해...

전에 말씀 드렸듯이 메모리로만 읽어오는 것으로는 프로그램이 수행되어

지는 것은 아니라고 말씀 드렸습니다. 그리고 이 프로그램을 직접 수행하고

처리하는 곳이 바로 레지스터라는 말씀도 드렸습니다.

레지스터라는 것을 따로 설명하기에는  많은 무리가 따르기 때문에 후에

직접 프로그램을 실습해가면서 익히도록 하겠습니다.

그런 의미에서 이 내용들에 너무 매달리지 마시라는 부탁을 드리고 싶습니다.



어셈블리어를 하는데 있어서 가장 중요하고 자주 쓰이는 것이

바로 이 레지스터들 입니다.

'들' 에서도 알 수 있듯이 레지스터라는 것이 하나가 아닌 여러가지가 있다는

걸 알 수 있습니다. 그럼 간단히 모든 레지스터들의 구조를 그림으로 간략히

그려보겠습니다.


EU : Execution Unit

;---------;

; AH ; AL ;   AX

;---------;                       BIU : Bus Interface Unit

; BH ; BL ;   BX                  ;----------;

;---------;                       ;    CS    ;

; CH ; CL ;   CX                  ;----------;

;---------;                       ;    DS    ;

; DH : DL ;   DX                  ;----------;

;---------;                       ;    SS    ;

;   SP    ;                       ;----------;

;---------;                       ;    ES    ;

;   BP    ;                       ;----------;

;---------;

;   SI    ;

;---------;

;   DI    ;

;---------;


;---------;

;   IP    ;

;---------;


Flag Register

;-----------------------------------------------;

;  ;  ;  ;  ;OF;DF;IF;TF;SF;ZF;  ;AF;  ;PF;  ;CF;

;-----------------------------------------------;


<< 참고 서적 : IBM PC Assembly Language and Programming  ( Peter Abel ) >>


대략적 구조를 그려 봤습니다. 이것만 가지구는 전혀 모르시겠지요?

그럼 위의 그림을 가지고 설명을 하겠습니다.


1. 대이타 레지스터 ( Data Registers )

데이타 제리스터란 바로 AX, BX, CX, DX 이 네개의 레지스터들을 한꺼번에

가르키는 용어 입니다.

구지 이렇게 용어를 정해서 나누는 이유는 확실하게 이런 레지스터들의 기능이

구분되기 때문에 주저없이 이렇게 나누고 있는 것입니다.

그럼 과연 데이타 레지스터들의 용도와 구조는 어떻게 되어 있을까...

일단 정리를 해 보면


 AX : 누산기 ( 산술연산, 포트로 부터의 I/O )

    I/O --> ( Input, Output ) 의 약칭

 BX : 베이스 ( 데이타 구조의 시작 주소 )

 CX : 카운트 ( 카운트, 비트 쉬프트 카운트 )

 DX : 데이타 ( 산술연산, I/O주소 )


<< 참고 서적 : Microsoft Macro Assembler Bible ( Barkakati & Hyde ) >>


이렇게 개요적으로 말할 수 있습니다.

일단은 이렇게 넘어가기로 하지요. 자세한 기능들은 하나하나 설명하기엔 너무나

방대하고 또 아직까지 거기까진 좀 무리라는 생각때문 입니다.


그럼 다음으로 이 레지스터들의 생김새에 대해 말하지요.

일단 위의 그림에서 보면 네모칸 오른쪽에 AX, BX와 같이 써 있고 네모칸 안에는

AH AL, BH BL 이라고 써 있는데요, 이건 무엇을 의미 하냐면, 전에 말씀 드렸듯이

레지스터라는 것의 크기가 1워드라는 말씀을 드렸을겁니다. 그리고 또한 PC의

데이터 처리는 1바이트 단위로 이루어 진다고도 말씀 드렸고요.

그런것들을 연결시키면, AX를 예를 들어서, AX 는 2바이트고 이것을 반으로 쪼개

(쪼개면 1바이트씩 짤리겠지요) 서 워드단위 연산뿐이 아닌 바이트 단위의 연산도

가능해게 하여 준다는 것입니다. 다시 말해


             AX = 16bit ( 2byte )

;------------------------------------------;

; AH = 8bit ( 1byte ) ; AL = 8bit (1byte ) ;

;------------------------------------------;


그림과 같이 AX라는 것은 2바이트의 크기로 2바이트 연산도 가능 하지만,

AH, AL 등과 같이 각각 1바이트씩으로 쪼개어 1바이트 연산도 수행한다는 것입니다.

( 여기서 AH 의 H 는 'High' , AL 의 'L'은 'Low' 를 의미합니다 )

그럼 레지스터의 연산 기능을 이용해 예를 보여 드리겠습니다.

만약 04F2 + 2F4A 라는 덧셈을 수행한다고 치면, 이 두 수는 모두 2바이트의 크기로

연산이 되며 결과또한 3447 이라는 2바이트로 나옵니다. 그래서 이와 같은 것은

워드 연산이기 때문에 바로 AX를 사용해 덧셈을 하게 됩니다.

그리고 3447 를 예를 들면, 이것은 2바이트 크기 이므로 AX레지스터에 들어갈 때는

1바이트씩 짤라서 AH에는 상위 34 가 , 그리고 AL에는 하위 47이 들어가게 됩니다.


레지스터의 변화            명령어

    ;---------;

 AX ; 04 ; F2 ;            MOV    AX, 04F2

    ;---------;            ( AX 레지스터에 04F2를 넣어라 )


    ;---------;

 DX ; 2F ; 4A ;            MOV    DX, 2F4A   

    ;---------;            ( DX 레지스터에 2F4A를 넣어라 )


    ;---------;

 AX ; 34 ; 47 ;            ADD    AX, DX

    ;---------;            ( AX 와 DX 를 더하여서 그 결과를 AX에 넣어라 )


다음과 같이 레지스터가 변하게 됩니다. 물론 위에 나온 명령어 들에는 신경쓰지

마시고 그 아래 괄호로 되어있는 설명만 아시면 됩니다.

( 흐..가능하면 명령어는 사용하지 않을려고 했는데... )


그리고 만약 2C + A2 와 같은 연산이라면, 피연산자와 결과 값 모두 1바이트 크기

이므로 ( 결과 : CE ) 이것은 1바이트 연산만으로 가능 합니다.


    ;----;

 AL ; 2C ;                  MOV AL, 2C

    ;----;                  ( AL레지스터에 2C를 넣어라 )


    ;----;

 DL ; A2 ;                  MOV DL, A2

    ;----;                  ( DL 레지스터에 A2를 넣어라 )


    ;----;

 AL ; CE ;                  ADD AL, DL

    ;----;                  ( AL 과 DL 을 더하여 결과를 AL에 넣어라 )


아시겠죠?

그리고 또 한가지.

만약 9B + C3 과 같은 경우는 어떨까요? 이것은 피연산자가 1바이트인것에 대해

결과값이  15E  와 같이 2바이트의 크기가 되어 버립니다.

이런 경우는 다음에 설명 드리도록 하겠습니다.



2. SP, BP, SI, DI, 세그먼트 레지스터 ( CS, DS, SS, ES ), IP


이제 다음 나머지의 레지스터에 관하여 간략히 설명 드리겠습니다.

이것들은 모두 메모리에 관련된 레지스터들로 앞서 배운 메모리 주소에

관한 것들입니다.

AX 등의 레지스터와는 달리 이들은 모두 1워드 단위의 처리밖에 할 수 없

습니다. 그 이유는 메모리의 오프셋 및 세그먼트들의 주소는 모두 1워드를

기본으로 조작이 되어지기 때문 입니다.

그리고 CS, DS, SS, ES 들은 모두 세그먼트를 조작하는 레지스터들입니다.

여기에 한가지 IP 라는 것이 포함 되는데 이것은 CS와 항상 함께 짝을

이루는 레지스터 입니다.

( 마찬가지로 SS 와 SP또한 짝을 이룹니다 )

여기서는 일단 간단한 표만을 그리고 끝내겠습니다.

이유는 이것들을 지금 설명한다고 해 봤자, 더 복잡하고 헥갈리기만 할 뿐

이기 때문 입니다. 이것들에 대한 설명은 차차 프로그램 예를 들어가면서

설명을 해 드리겠습니다.


;-------------------------------------------------------;

; SP : Stack Pointer     : 스텍의 현재 탑(Top)의 오프셋 ;

; BP : Base Pointer      : 스텍으로의 프레임 포인터     ;

; SI : Source Index      : 스트링 명령어                ;

; DI : Destination Index : 스트링 명령어                ;

;-------------------------------------------------------;


;------------------------;

; CS : Code Segment      ;

; DS : Data Segment      ;

; SS : Stack Segment     ;

; ES : Extra Segment     ;

;------------------------;


;--------------------------;

; IP : Instruction Pointer ;

;--------------------------;



3. 플래그 레지스터

플래그 레지스터라는 것은 우리가 어떤 프로그래밍을 하고서 그 프로그램을

수행할 때 PC 내에서 발생되는 눈에 드러나지 않는 여러가지 상태들을

나타내어 주고 또 사용자가 직접 채크할 수 있도록 하여 주는 레지스터 입니다.

이것 또한 간단히 넘어가겠습니다.


Flag Register

;---------------------------;

; OF : Overflow Flag        ;

; DF : Direction Flag       ;

; IF : Interrupt Flag       ;

; TF : Trap Flag            ;

; SF : Sign Flag            ;

; ZF : Zero Flag            ;

; AF : Auxiliary Carry Flag ;

; PF : Parity Flag          ;

; CF : Carry Flag           ;

;---------------------------;


후후...이렇게 쓴 제 자신도 잘 모르겠는데 여러분은 어떻겠습니까...

그냥 부담없이 한번 읽어 보시고 넘어가세요...

차차 차근차근 설명을 할테니까요...

^_^

^

 [7] 제목 : [초급-실습] 프로그램 하나 ( 소스 & 설명 )#1
 올린이 : 까망벌레(정태식  )    94/10/26 00:41    읽음 : 610  관련자료 없음

이제 DEBUG 라는 것으로 간단한 어셈블리어 프로그래밍을 해보겠습니다.


음...DEBUG라는게 과연 어디에 쓰이는 것인지 우선 말하자면,

DEBUG 말그대로 버그를 잡은 데에 쓰이는 것입니다. 도스상에서 지원되는

이런 버그잡는거 말고라도 터보 디버그같은 여러가지 유틸리티가 있습니다.

하나하나 단계별로 실행을 시켜가며 과연 무슨 버그가 있나 하고

레지스터나 플래그 등등을 일일이 살펴보며 확인할 수 있는 유틸중의 하나죠....


헤...혹시 '버그'가 먼지 모르시는 분....을 위해 간단히...

프로그래머가 작성한 프로그램 중에 발생할 수 있는 대표적인 것 중에

ERROR와 WORNING이 있고 또 여기에 가장 골치아픈 버그라는 것이 있지요.

에러가 나면 물론 프로그램 실행이 불가능 하게 되는건 당연 하고요,

WORNNING같은 것은 실행하는데 지장은 없지만 자칫 엉뚱한 결과가 출력될수도

있는 위험성을 안고 있을때 생기는 메시지 입니다. 이 두가지(ERROR,WORNNING)

모두는 컴파일러나 링크 ( 컴파일과 링크에 대해서는 다음 기회에...) 시에

알아서 프로그래머에게 알려 줍니다. 하지만 이 놈(버그)은 위의 두가지 와는

달리 어떠한 출력 메시지도 없이 실행이 되지만 엉뚱한 결과가 나오거나 자칫

시스템이 다운 되어 버리는 경우도 있습니다.

이럴때는 귀찮지만 일일이 프로그램 소스를 검사 하던가, 그래도 안되면

이 DEBUG라는 것을 돌려가지구 버그를 잡게 됩니다.


저희 학교 교수님의 예를 들자면, 이분께서 5000라인 가까이 되는 C프로그램을

작성을 했는데 에러나 경고가 전혀 없이 결과값이 엉뚱하게 나오더랍니다.

그래서 그 5000라인 이나 되는 소스를 일일이 검사하고 검사해도 전혀 이상이

없었답니다. 그것을 1주일 동안 꼬박 밤새가며 찾다가 드디어 찾았는데,

그것이 무엇이냐...하믄


if ( i == 0 ) { 이라고 해야 하는 걸

if ( i = 0 ) { 이라고 했다는 것입니다.


그걸 알았을 때의 그 황당함....

꼬옥 이런걸 버그라고하지는 않습니다만, 이런것도 버그가 될 수 있다는 것이지요.


그럼 다시 원점으로 돌아와서...

아마 여러분들 컴퓨터에는 모두 이 DEBUG라는 것이 있을겁니다.

흐....도스 없이 컴을 사용하시는 분은 없겠지요?


그럼 이 DEBUG를 돌려 봅시다.


C:\DOS>debug

-


이와 같이 화면에는 '-' 표시가 찍힙니다. 이건 바로 디버그를 사용할

준비가 되었다는 것을 의미 합니다. 그럼 다음과 같이 입력을 해 보세요.

( 대문자는 자동으로 표시되는 것이고, 소문자는 직접 입력을 하는 것입니다. )


-a

0A48:0100 mov ax,0123

0A48:0103 add ax,0025

0A48:0106 mov bx,ax

0A48:0108 add bx,ax

0A48:010A mov cx,bx

0A48:010C sub cx,ax

0A48:010E sub ax,ax

0A48:0110                    <--- 여기서는 그냥 엔터만 칩니다.

-


그럼 간단한 예제 프로그램 입력을 마쳤습니다.

여기서 한가지, 앞의 숫자 0A48:0100 에서 ':'앞의 0A48은 시스템 마다 다를

수 있습니다.

( '다를 수 있습니다' 가 아니고 아마 10중 8,9는 다를 것입니다 )

그건 신경쓰지 마시고......

일단 이들이 맞게 입력이 되었는지 확인을 해 바야겠죠.

그럼 또 다음과 같이...




-u100

0A48:0100 B82301           MOV AX,0123

0A48:0103 052500           ADD AX,0025

0A48:0106 89C3             MOV BX,AX

0A48:0108 01C3             ADD BX,AX

0A48:010A 89D9             MOV CX,BX

0A48:010C 29C1             SUB CX,AX

0A48:010E 29C0             SUB AX,AX

........

-

( 바로 위의 '....'은 그 뒤로도 몇가지 비슷한 형태로 주루룩 나온다는 것을

말합니다. 이 부분은 시스템 마다 다른 내용이 출력 되기 때문에 별로 신경쓸

것은 없습니다. 우리가 필요한 부분은 위에 쓴 저 내용뿐입니다. )


제대로 되었는지요? 잘못되었다고요? 그럼 'Q'로 디버그에서 빠져 나온뒤 다시

시작해 보세요...( 다른 방법이 있긴 하지만 일단은 그렇게 하시기를... )

사실 위의 프로그램은 실행 시켜도 아무런 결과 없이 시스템만 다운 됩니다.

절대 실행 시키지 마시길....헤헤~~


그리고 한번 다음과 같이 입력을 해보세요..


-d100

0A48:0100  B8 23 01 05 25 00 89 C3-01 C3 89 D9 29 C1 29 C0   .#..%.......).).

0A48:0110  8B C6 F7 D0 D3 48 DA 2B-D0  .....등등 주루룩~~~


이렇게 출력이 됩니다.

내용은 다를 수 있지만 첫째줄의 모든 내용은 모두 같습니다.

( 설명은 조금 후에... )


그럼 이제 본격적으로 위의 프로그램이 과연 제대로 돌아가는지, 또 어떻게

레지스터들이 변하는지 확인을 해 봐야 겠죠?

그럼 일단 프로그램이 실행되기 전의 레지스터들의 상태를 체크해 봅시다.


-r

AX=0000  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000

DS=0A48  ES=0A48  SS=0A48  CS=0A48  IP=0100   NV UP EI PL NZ NA PO NC

0A48:0100 B82301        MOV     AX,0123

-


다음과 같이 출력이 될것입니다.

하지만 위에 DS, ES, SS, CS 값들은 모두 컴퓨터 마다 다르게 출력이 될겁니다.

하지만 위의 DS, ES, SS, CS 네개의 값이 모두 동일하다는 것은 어느 PC나

같습니다.

그럼 하나씩 단계적으로 실행을 해 보지요.

( 출력에 대한 설명은 조금 후에 하겠습니다. )

( '()' 안의 숫자는 실제로는 출력이 되지 않는 내용 입니다.

  이 숫자는 설명의 편의상 순서를 정하기 위해 사용 했습니다 )

-t

(1)

AX=0123  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000

DS=0A48  ES=0A48  SS=0A48  CS=0A48  IP=0103   NV UP EI PL NZ NA PO NC

0A48:0100 B82301        ADD     AX,0025

-t

(2)

AX=0148  BX=0000  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000

DS=0A48  ES=0A48  SS=0A48  CS=0A48  IP=0106   NV UP EI PL NZ NA PO NC

0A48:0100 B82301        MOV     BX,AX

-t

(3)

AX=0148  BX=0148  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000

DS=0A48  ES=0A48  SS=0A48  CS=0A48  IP=0108   NV UP EI PL NZ NA PO NC

0A48:0100 B82301        ADD     BX,AX

-t

(4)

AX=0148  BX=0290  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000

DS=0A48  ES=0A48  SS=0A48  CS=0A48  IP=010A   NV UP EI PL NZ NA PO NC

0A48:0100 B82301        MOV     CX,BX

-t

(5)

AX=0148  BX=0290  CX=0290  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000

DS=0A48  ES=0A48  SS=0A48  CS=0A48  IP=010C   NV UP EI PL NZ NA PO NC

0A48:0100 B82301        SUB     CX,AX

-t

(6)

AX=0148  BX=0290  CX=0148  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000

DS=0A48  ES=0A48  SS=0A48  CS=0A48  IP=010E   NV UP EI PL NZ NA PO NC

0A48:0100 B82301        SUB     AX,AX

-t

(7)

AX=0000  BX=0290  CX=0148  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000

DS=0A48  ES=0A48  SS=0A48  CS=0A48  IP=0110   NV UP EI PL NZ NA PO NC

0A48:0100 B82301        MOV     AX,SI <-- 이부분은 다를 수 있습니다.

-


흐...뭐가 먼지... 정신이 없죠?

그럼 이제 이 결과 값들을 가지고 한번 설명을 해 보겠습니다.


우선 이같이 실행도 안되는 프로그램을 뭐하러 짰는고....라고 생각하신다면,

사실 이 프로그램을 예로 든 이유는 프로그램에 따라서 변하는 레지스터의

값들을 확인해 보기 위해서 입니다.


우선 위에서 보았던 'u'에 대해서 나오는 결과들에 대해 말씀 드리지요...

'u'는 'unassemble'의 약자로 'assemble'의 반대적인 의미를 가집니다.

위에서 보았던 'd'의 결과들과 'u'의 결과들에서 발견할 수 있는 공통점이

있지요? 그건 바로 'u'의 결과중에 보이는 '0A48:0100' 과 ' MOV     AX,0123'

사이에 있는 'B82301'이라는 것이 보이죠?

그럼 바로 이 위치에 있는 문자들을 주욱 모아 보면....


B82301 052500 89C3 01C3 89D9 29C1 29C0


와 같은 내용이 됩니다.

그리고 'd'의 결과 값들의 내용을 보면...


B8 23 01 05 25 00 89 C3-01 C3 89 D9 29 C1 29 C0


와 같습니다. 어때요? 전자의 내용을 두글자씩 분리해 정리하면 후자의 내용과

꼬옥 들어맞는 것을 알 수 있습니다.


그럼 과연 위의 이상한 숫자와 문자들은 무엇을 의미 할까.....

'd'라는 것은 'Dump'의 약자로 즉, 현재 메모리의 내용을 보여주는 명령입니다.

바로 화면에 주루룩 나오는 내용들이 메모리의 현재 내용이라는 것이지요.


그럼 위의 내용을 분석해 보겠습니다.

일단 'B82301'을 시작해 보죠...

이것은 바로 기계어 코드로 'MOV AX,0123'이라는 명령어를 기계어로 바꾼

것입니다. 그럼 왜 이것을 기계어로 바꾸어야 할까요...

그것을 'MOV AX,0123'이라는 명령어는 PC에서는 전혀 알아 듣지 못하는 내용

입니다. 그래서 이것을 PC에서 알아들을 수 있는 내용으로 바꾸기 위해

위와 같은 기계어 코드를 생성하게 된 것입니다. 어셈블러는 이와 같이

사용자가 입력하여준 어셈블리어 언어를 바로 기계어 코드로 변환 하여 주는

역할을 합니다. 사실 사람이 직접 이 기계어 코드를 사용해서 프로그래밍

한다면 더없이 좋겠지요...하지만....그 기계어 코드를 일일이 다아 왜우는

사람은 아마 없을 겁니다. ( 제가 알기로는 말이죠..흐흐 )

그래서 좀 더 사용자가 편리하게 프로그래밍을 할 수 있도록 영문으로 되어진

'MOV' 와 같은 명령어를 만들어 내게 된 것입니다.

C언어나, 베이직 등등 모든 프로그래밍 언어들이 바로 프로그래머들을 위해

좀 더 편리하게 프로그래밍 할 수 있도록 만들어 졌습니다.

결과적으로 모든 언어는 최종적으로 바로 위와 같은 기계어 코드로 변환을

하게 된다는 것입니다.


다시 'B82301'이라는 것을 뚜러지게 봅시다... ( '뻥~' )

모두 3바이트로 된 내용 입니다.

'B8' <-- 요것은 과연 어떤 어셈블리어 명령어를 기계어로 변환한 것일까...

답은 간단...'MOV AX' 라는 것을 변환한 기계어 코드 입니다.

하필 'MOV' 가 아니고 'MOV AX' 를 통채로 변환을 했느냐...하시는 분들을 위해

잠시....

솔직히 'MOV' 따로 'AX'따로 각각 한 바이트의 기계어 코드로 변환을 한다고

하면 알아보기도 쉽고 좋겠죠...하지만 여기에 하나 단점이 발생합니다.

만약 'MOV' 가 기계어 코드로 'C3' 이라고 하고 'AX'는 '25' 라고 칩시다

그럼 'MOV AX'라는 것을 기계어 코드로 바꾸게 되면 결과적으로


C325 라는 결과가 되겠지요?


과연..... 'B8'이라는 코드가 'C325'라는 코드로 변환이 됨에 있어서

원래는 1바이트 만으로 되었던 것이 2바이트로 늘어나 버렸습니다.

이건 쓸데 없는 메모리 낭비만 가져올 뿐입니다.


혹시나 왜, 'MOV' 를 'C' 라는 것으로 하고 'AX'를 '2' 라고 한다면 역시

같은 1바으트로 변환이 가능하지 않냐고 하실 분이 계실 지도 모릅니다.

제 대답은 '벌러덩~' 입니다. 왜냐하면 전에도 말씀 드렸듯이 모든 PC에서

수행되는 기본 단위는 바이트 단위라고 했습니다. 그러므로 'C'라는 것은

완전한 하나의 바이트가 되질 못하기 때문에 처리를 할 수 없게 됩니다.


그럼 혹시 'MOV BX' 같은 것은 또 다른 1바이트의 기계어 코드가 있느냐..

라는 질문에 '예' 입니다. 그럼 그 많은 명령어와 레지스터들의 조합들을

이렇게 따로따로 코드를 정한다면 1바이트 가지고는 부족하게 되는건 아닌지..

하지만 걱정 마세요... 1바이트로 안된다면 2바이트를 써서 하면 되는 거니까..

메모리 낭비? 하지만 일단은 프로그램이 제대로 도라가는게 중요하자나요..

가급적이면 PC에서 메모리를 적게 먹는 방향으로 설계되었을 줄로 압니다.

됐지요?


이제 뒤의 '2301' 에 대해 말씀 드리지요.

이건 가만히 살펴보면 입력하는 데이터 값인 '0123' 과 숫자는 모두 같은데,

대신 순서가 바뀌었다는 것을 알 수 있습니다.

즉 '01' 과 '23' 각각 1바이트의 내용이 서로 자리바꿈을 했음을 알 수

있습니다. 이 이유는 메모리 구조상 원래 이렇게 저장이 되어지도록 됴습니다.

그 '이유'라는 것은 다음으로 넘기기로 하지요...히힛~~

어쨋든 '2301'은 AX레지스터에 넣을 값을 알려주는 것입니다.


휴~~~ 일단 1행은 마쳤습니다.

역시 다음 행들 또한 같은 맥락으로 이해 하시면 되고요...주루룩~~~

왜 어셈블리어 명령어에 대한 설명은 안하냐고요?

후후...이 프로그램을 해보는 목적은 명령어 익히기가 아니구여

바로 메모리의 실제 모습과 레지스터들을 이해하기 위해 해 보는 것이여요..



이제 'd'라는 명령으로 나오는 출력값들이 무엇을 의미 하는지 알겠죠?

바로 프로그램을 기계어 코드로 바꾸어서 메모리로 저장 시킨 모양을

알려주는 것입니다.

그럼 'd'로 나오는 출력들 중에 왼쪽의 숫자 '0A48:0100' 같은 것들은 무엇을

의미 할까요...

이것들은 바로 메모리의 주소값을 표시해 주는 것입니다.

왼쪽이 세그먼트가 되고 오른쪽이 오프셋이 되는 것입니다.

그리고 제일 오른쪽의 알 수 없는 문자들은 무엇인가 하믄...

변환된 기계어 코드는 어디까지나 그냥 숫자들 입니다.

이 숫자들은 전에 말씀드렸던 아스키 코드로 1바이트씩 표시해 준 것입니다.

'23' 같은 것은(16진수) 아스키 코드표를 찾아보면 '#'로 정해져 있습니다.

나머지 들도 마찬가지구요. 그리고 '.'이 상당히 많은데, 이것들은

표준 아스키 코드 이외의 확장 아스키 코드들을 그냥 모두'.' 로 표시한

것입니다.


다음에는  'r' 과 't' 로 나오는 출력들을 가지고 설명을 드리겠습니다.





 







 [8] 제목 : [초급-실습] 프로그램 하나 ( 소스 & 설명 )#2
 올린이 : 까망벌레(정태식  )    94/10/29 20:06    읽음 : 460  관련자료 없음

후.....넘 늦게 올려서 죄송 합니다.

그럼 다시 그 소스를 가지고 설명을 계속 하겠습니다.

이번에는 'r' 명령과 't'명령에 대해 설명한다고 했지요?

그럼 해보겠습니다.


우선 데이타, 스텍, 코드 세그먼트에 대해 설명을 해 드리겠습니다.

여러분도 보시다 시피 소스에서 입력하여준 명령어들 'mov, add, sub'등등

이것들도 우선은 메모리에 저장되어 지는것을 보았지요?

이와 같이 PC에 어떠한 연산 명령을 내려 주는 것들을 저장하는 메모리 영역을

코드 세그먼트라고 부릅니다.

말그대로 메인 프로그램 이라고나 할까요.

전에 말씀드렸다 시피 메모리 구조는 세그먼트와 오프셋으로 구성되어 진다고

했습니다. 그럼 과연 한 세그먼트의 크기는 얼마나 될까요?

그것은 오프셋의 크기와 관련이 됩니다.

다음과 같은 경우를 생각해 보지요.

만약 메모리에 'abcdefg'라고 차례대로 저장을 시킨다고 해 봅시다.

그럼 이것들이 과연 메모리에 어떠한 모양으로 저장이 되어지는지 아시겠지요?


;--------------------------------

; a ; b ; c ; d ; e ; f ; g ;   ;

;--------------------------------

다음과 같습니다.

여기서 만약 'a'라는 문자가 저장된 곳이 바로 0123:53F2 라고 가정을 해 봅시다

그럼 세그먼트는 '0123'이 되고 오프셋은 '53F2' 라고 할 수 있습니다.

그럼 'b'가 저장된 곳은 주소가 어떻게 될까요...

그건 '0123:53F3'으로 세그먼트가 아닌 오프셋이 하나 증가한 메모리 주소를

갖습니다. 'c'역시 차례대로 '0123:53TF4'가 되는건 예측할 수 있죠.

이와 같이 하나의 세그먼트에는 오프셋의 범위가 '0000' 부터 'FFFF' 까지 됩니다.

그러므로 바로 오프셋의 범위가 하나의 세그먼트의 크기라고 할 수 있는 것입니다.]

이걸 계산해 보면 약 64K 바이트의 메모리가 됩니다.

정의해서, 하나의 세그먼트 크기는 64K의 용량을 갖는다는 것을 알 수 있습니다.

그래서 이 세그먼트 내에 이 코드들이 들어간다는 것이지요.

그럼 1메가의 메모리에는 과연 몇개의 세그먼트가 존재 할 수 있는지 계산해 보죠.

1 메가면 1024K바이트라는 것은 알고 있습니다.

그럼 계산은 간단.


1024 / 64 = 64


라는걸 알 수 있습니다. 그러므로 '1메가의 메모리 안에는 64개의 64K바이트를

같은 세그먼트가 존재 한다.' 라고 할 수 있습니다.


그럼 이와 같이 세그먼트들을 나누는데 있어서 왜 데이타 라느니, 코드라느니

이런것들을 나누느냐, 라는 질문에

만약 프로그램이 메모리에 저장되어지는데 있어서 그 크기가 64K를 넘어간다고

생각해 보세요. 그럼 이것을 어떻게 처리해야 하느냐....

물론 두개의 세그먼트를 사용하면 되겠지요.

그럼 이 두개의 세그먼트를 어떻게 사용해야 효율적일까요?

보통 만약 두개의 세그먼트를 사용한다고 하면, 전에 예를 들었던 '3+4'에서

'3'과 '4'는 데이터 라고 했고 '+'는 연산 명령이라고 했지요?

이와 같은 덧셈을 1000개 수행해야 한다고 해 보세요.

덧셈인데, 수 많은 데이터 들을 일일이 다아 더하는 명령을 주어도 되지만

데이타 영역을 따로 만들어 주고, 여기에 이 데이터 영역에 있는 값들을

꺼내다가 계산한다면, 훨씬 효율적이 될 수 있습니다.


여기서 데이터 영역이 저장되어지는 세그먼트를 데이타 세그먼트(Data Segment)

라고 부르고 바로 연산 명령 같은 것이 저장되어지는 세그먼트를 코드 세그먼트

(Code Segment) 라고 부릅니다.

물론 이 두가지(데이타, 연산명령) 를 하나의 세그먼트 안에 넣어 주어도 됩니다.

그리고 위의 내용에선 분명 스텍 세그먼트(Stack Segment) 라는것이 있다고 했습니다.

이 스텍이라는 것은 나중에 설명을 드리겠습니다.


그럼 대충 세그먼트라는 것에 대해 이해가 가셨을 줄로 믿습니다.


그럼 이제 IP ( Instruction Pointer ) 가 무엇인지 설명을 또 드리죠..

PC 상의 레지스터 상태를 보여주는 'r'명령을 내려 보면 알 수 있듯이

모든 레지스터의 저장된 값들이 나옵니다. 거기에 보면 'IP'라는 것이 있습니다.

이것은 보통 초기에는 '100'이라고 나옵니다. 이것은 오프셋 값을 나타낸 것으로,

무엇의 오프셋이냐 하믄, 여러분이 직접 넣어준 프로그램을 'u'명령으로 보면

'MOV AX,0123'이라는 프로그램이 바로 오프셋 100에 저장이 되어 있다고 나옵니다.

그럼  'IP'와 프로그램의 가장 선두 부분의 오프셋 값이 같다는 것을 알 수 있죠.

또 'r' 명령후 나오는 값들중에 제일 마지막 부분에 ' MOV AX,0123'이라고 써 있는

부분이 있죠.. 그것은 지금 상태에서  프로그램을 실행시키면, 다음으로 실행이

되어지는 프로그램을 나타내는 것입니다.


이제 't'명령을 실행시켜 보면 ( 't' 는 'Trace'의 약자로 프로그램을 하나씩

단독적으로 실행시켜 나가면서 레지스터의 상태를 확인해 볼 수 있습니다 )

일단은 'MOV AX,0123'이라는 것을 실행 시킵니다. 그럼 다음으로 실행시키는 명령

이 'ADD AX,0025'라고 표시해 줍니다. (표시된 것은 실행시킨 프로그램이 아닌

앞으로 실행된 프로그램이라는 것을 나타냅니다 )

그럼 이때까지 't'를 한번 해 주었습니다.

소스에서 보면 (1)번에 해당하는 내용 입니다.

그럼 여기서 이제 'IP'값을 보세요.

변했죠?

증가했음을 알 수 있습니다. 그럼 얼마나 증가를 했습니까?

'MOV AX,0123'이 차지하는 메모리는 3바이트라고 했습니다

그리고 그 다음 명령행이 저장되어진 메모리 주소는 'MOV AX,0123'이 저장되어진

오프셋 값에 바로 이 크기인 3바이트 증가한 '103'이라는 것을 알 수 있습니다.

다시 말해 'ADD AX,0025'라는 명령이 저장되어진 메모리 주소가 '103'부터 시작된

다는 것을 알 수 있습니다.

그럼 'IP'레지스터의 '103'과 값이 같지요...

(2) 번 (3) 번 모두 그 'IP'레지스터의 값과 지금까지 실행하고 다음으로 실행되

어질 메모리의 오프셋 값이 동일하다는 것을 알 수 있습니다.


여기서 보듯이 'IP'라는 레지스터는 프로그램 소스를 실행 시키는데 있어서

프로세서 (CPU) 가 과연 그 많은 메모리 중에 어떤곳에 실행시켜야 될 프로그램들이

있는지 프로세서에 알려 주어야 합니다.

그것을 알아야 프로그램이 실행이 되어지지요.

그럼 과연 프로세서가 어느것을 보구서 메모리의 어떤 부분에 실행되어질 프로그램

이 있는지 알 수 있을까요?

그것은 'CS'레지스터에 저장되어진 값으로 프로그램이 들어 있는 세그먼트를 알아

내고 'IP'레지스터에 저장되어진 값으로 프로그램이 들어있는 메모리의 오프셋

값을 알아 냅니다.

그럼 프로그램을 한 라인 실행시키면 그 명령을 수행하고 다시 프로세서는

'CS'값과 'IP'값을 확인 해 보고, 그 다음 명령를 수행하게 됩니다.

예를 들어 보죠.

전의 소스를 보면서....

'IP' 가 100인 상태, 그리고 'CS'가 '0A48', 에서 세그먼트 '0A48' 오프셋

100번지에 있는 'MOV AX,0123'을 실행하게 됩니다.

그럼 다시 그 다음 명령인 'ADD AX,0025'를 실행시키기 위해선 'IP'값이 3바이트

증가된 '103'이 되어야 합니다. 그것은 사용자가 할 필요 없이 PC가 알아서

해 줍니다. 그럼 프로세서는 그 두가지의 레지스터를 확인해 보고 다음으로

실행시켜야 하는 명령을 수행하게 됩니다.

그래서 'CS'와 'IP' 가 항상 짝을 이룬다는 말을 하게 됩니다.


데이타 세그먼트 또한 마찬가지 입니다.

그 예는 다음 프로그램 소스에서 알아보도록 하겠습니다.

















그리고 프로그램 모델이라는 것이 있습니다.

'small' 'compact' 'large' 'huge' 등등의 모델들이 있습니다.



 [9] 제목 : [초급-보충] 스텍?
 올린이 : 까망벌레(정태식  )    94/11/05 06:47    읽음 : 410  관련자료 없음

그럼 스텍 (STACK)에 관해 설명을 드리겠습니다.

스텍은 말 그대로 '쌓다'라는 의미를 지니고 있습니다.

쌓아서 무얼 할까요?

아마 '하노이의 탑' 이라는 유명한 문제를 기억하고 계실 겁니다.

세개의 막대중에 한개의 막대예 크기가 다른 원반들이 여러개 역삼각형 모양으로

꽂혀 있습니다. 이 원반들을 다른 막대에 삼각형 형태( 제일 아래에 가장 큰 원반,

그리고 위로 순서대로 크기가 점점 작은 원반을 쌓는 형태)로 순서대로 나열하는

것인데, 원반은 한번에 하나씩 밖에 옮기지 못합니다.

그리고 원반이 두개 이상 쌓여 있을때는 제일 아래것을 꺼내기 위해서는 위에서

부터 차레차레 하나씩 빼낸 다음 마지막으로 제일 아래것을 꺼낼 수 있는 거죠.


여기서 보듯이 이 구조를 LIFO( LAST-IN-FIRST-OUT ) 이라는 용어를 정의 할 수

있습니다. 우리말로 후입전출 이 됩니다.

이것은 가장 나중에 드러간 것이 가장 먼저 나온다는 뜻입니다.

그러니깐 1,2,3,4,5 번으로 번호가 매겨진 접시가 있다고 생각 해 보죠.

이걸 착착 포개서 제일 아래에 5번 그 위에 4번...이렇게 제일 위에 마지막으로

1번 접시가 오게 됩니다. 그럼 이걸 하나하나 꺼내게 되면 가장 나중에 올려 놓은

1번 접시가 제일 먼저 꺼내어 지게 됩니다.

그리고 제일 처음에 포개어 놓은 5번 접시는 제일 마지막에 꺼내어 지게 되는 것

이지요.

이걸 잘 이해 하셨다면, 스텍이란 것도 별 문제 없습니다.

좀 더 스텍에 가까운 모양을 예를 들면,

딱 동전만한 지름의 원통이 있는데 그 한쪽이 막혀있고 나머지 한 쪽은 뚤려

있습니다.

여기에 동전을 넣게 되면 하나씩 차례로 들어가죠.

그리고 이것을 하나씩 차례로 꺼낼땐 들어갈                                                                                                                痼都求.


이제 이 예를 메모리로 그대로 옴겨 보죠.


;---------------;

;       ;       ; FFFF

;---------------;

;       ;       ; FFFD

;---------------;

;       ;       ; FFFB

;---------------;

;       ;       ; FFF9


메모리의 형태를 그림으로 나타낸 겁니다.

왜 여기서 주소값이 FFFF 에서 그 다음이 FFFE 가 아닌 FFFD 가 되느냐고 의아해

하신다면, 스텍의 처리는 항상 1바이트가 아닌 1워드로 처리 된다는 것입니다.

그림에서 보면 한 칸의 메모리를 1바이트씩 둘로 나눈 것이죠.

그리고 그림의 바로 윗부분이 막혀 있고 아랫 부분이 뚤려 있다고 생각합시다.


여기에 동전 대신에 어떠한 데이터들을 순서대로 넣는다고 해 보죠.

데이터 값을 1034, 325D, 35F3 이렇게 순서대로 넣게 되면,


;---------;

; 10 ; 34 ; FFFF

;---------;

; 32 ; 5D ; FFFD

;---------;

; 35 ; F3 ; FFFB

;---------;

;    ;    ; FFF9


위의 그림과 같이 들어가게 됩니다.

그럼 만약 위의 데이터 중에 1034를 꺼내기 위해서는 ...

물론 깡통이라고 생각하면, 위의 뚜껑을 따고 꺼내는 방법도 있지만...

헤헤...이건 메모리니깐...

일단은 그 아래에 있는 두 워드의 데이터 들을 꺼내고 나서야 비로소 우리가 원하

는 1034 라는 데이터 값을 꺼낼 수 있게 됩니다.


;---------;         ;---------;         ;---------;         ;---------;

; 10 ; 34 ;         ; 10 ; 34 ;         ; 10 ; 34 ;         ;    ;    ;

;---------;         ;---------;         ;---------;         ;---------;

; 32 ; 5D ;         ; 32 ; 5D ;         ;    ;    ;         ;    ;    ;

;---------;         ;---------;         ;---------;         ;---------;

; 35 ; F3 ;         ;    ;    ;         ;    ;    ;         ;    ;    ;

;---------;         ;---------;         ;---------;         ;---------;

;    ;    ;         ;    ;    ;         ;    ;    ;         ;    ;    ;


원래의 메모리       여기서 35F3         그 다음 325D        드디어 1034를

                    을 먼저 꺼낸다.     를 꺼내고,          꺼내게 된다.


잘 이해가 가셨는지요....


그럼 어셈블리 명령어를 가지고 설명을...


        PUSH    AX

        PUSH    DX

        PUSH    CX

        PUSH    BX


일단 이와 같은 명령어를 주었다면, 가장 먼저 있는 AX레지스터의 값을 스텍에

쌓아 놓게 됩니다. 그 다음은 물론 DX이고, 다음은 CX, BX 순서로 스텍에 쌓이

겠죠.


예로 각 레지스터의 값들이 위의 명령어를 하기 전에 다음과 같이 들어있다고

가정 합시다.

AX = 0001

BX = 0002

CX = 0003

DX = 0004


그림을 그려보면


;---------;         ;---------;         ;---------;         ;---------;

; 00 ; 01 ; FFFF    ; 00 ; 01 ;         ; 00 ; 01 ;         ; 00 ; 01 ;

;---------;         ;---------;         ;---------;         ;---------;

;    ;    ; FFFD    ; 00 ; 04 ;         ; 00 ; 04 ;         ; 00 ; 04 ;

;---------;         ;---------;         ;---------;         ;---------;

;    ;    ; FFFB    ;    ;    ;         ; 00 ; 03 ;         ; 00 ; 03 ;

;---------;         ;---------;         ;---------;         ;---------;

;    ;    ; FFF9    ;    ;    ;         ;    ;    ;         ; 00 ; 02 ;


 PUSH AX             PUSH DX             PUSH CX              PUSH BX

 SP = FFFD           SP = FFFB           SP = FFF9            SP = FFF7


다음과 같이 순서대로 드러 갑니다.

그럼 위의 그림에서 SP란 무언가....

SP 란 STACK POINTER의 약자로서 IP와 같은 기능을 합니다.

PUSH AX와 같은 경우 PC는 과연 이 AX의 값을 어디에 쌓아 넣으라는 것인지를

이 SP 를 보고 알게 됩니다. PUSH AX를 하기 전에는 SP 는 FFFF를 가리키게 되므로

pc는 '아, AX레지스터의 값을 FFFF번지에 있는 메모리로 넣으라는 말이구나' 라고

인식을 하게 됩니다. 그리고 저장되는 값은 항상 2바이트 크기 이므로, 다음에

저장할 곳을 지정하게 될 때는 2바이트 하위 쪽인 FFFD 를 가리키게 되는 것이지요

근데 여기서 하나 주목 할 것은 SP 는 IP와는 반대로 갈수록 POINTER 값이 감소

한다는 것입니다. 이것은 스텍의 특성으로 언제나 이와 같이 상위메모리로 부터

하위 메모리로 차근차근 저장을 하게 되도록 만들어져 있습니다.

그럼 이 값들을 꺼내는 것은....


        POP     BX

        POP     CX     

        POP     DX

        POP     AX


이와 같이 했다고 합시다.

POP명령은 스텍에 쌓여 있는 데이터들을 하나씩 순서대로 꺼내오는 명령으로

가장 먼저 POP BX 부터 시작을 하게 됩니다.

여기서 하나 또 관심있게 볼 것은,

PUSH 와는 순서가 반대가 되었다는 것입니다.

PUSH 에서는 AX,DX,CX,BX 순으로 넣었는데, POP에서는 거꾸로

BX,CX,DX,AX 순으로 꺼내 온다는 것입니다.


;---------;         ;---------;         ;---------;         ;---------;

; 00 ; 01 ; FFFF    ; 00 ; 01 ;         ; 00 ; 01 ;         ;    ;    ;

;---------;         ;---------;         ;---------;         ;---------;

; 00 ; 04 ; FFFD    ; 00 ; 04 ;         ;    ;    ;         ;    ;    ;

;---------;         ;---------;         ;---------;         ;---------;

; 00 ; 03 ; FFFB    ;    ;    ;         ;    ;    ;         ;    ;    ;

;---------;         ;---------;         ;---------;         ;---------;

;    ;    ; FFF9    ;    ;    ;         ;    ;    ;         ;    ;    ;


 POP BX              POP CX              POP DX              POP AX

 SP = FFF9           SP = FFFB           SP = FFFD           SP = FFFF


다음과 같이 원래의 레지스터 값들이 다시 각 레지스터에 들어가게 됩니다.

근데 만약 혹시, PUSH 를 위와 같이 한 후에 POP 명령을 다음과 같이 했다면?


        POP     AX

        POP     BX

        POP     CX

        POP     DX


우선 POP 명령은 무조건 스텍에 싸여 있는 데이터 들을 꺼내오게 됩니다.

그리고 그 뒤에 있는 레지스터에 그 값을 무조건 집어 넣게 됩니다.

그래서 결과적으로 각 레지스터에는 다음과 같은 값들이 들어가게 됩니다.


        AX = 0002

        BX = 0003

        CX = 0004

        DX = 0001


이런 결과가 되어 버립니다.


그럼 스텍이라는 것이 왜 필요할까요?

그것에 대한 설명은 다음에 여러개의 프로시져를 사용한 예에서 자세히 설명을

드리겠습니다.



 [10] 제목 : [초급-실습] 프로그램 두울 #1
 올린이 : 까망벌레(정태식  )    94/11/05 06:48    읽음 : 453  관련자료 없음

이번엔 좀 더 실질적인 프로그램을 하나 해 보도록 하겠습니다.

이건 화일의 Attribute(속성)을 바꾸는 것으로 화일을 읽기 전용,

또는 읽기 전용 해제 를 위한 프로그램 입니다.

우선은 소스를 써 보죠.


        .MODEL  SMALL

        .CODE

        ORG     100H


PROTECT PROC    NEAR

        MOV     DX,82H

        MOV     DI,82H

        MOV     AL, 13

        MOV     CX,12

REPNE   SCASB

        MOV     BYTE PTR [DI-1],0

        MOV     AL,1

        MOV     CX,1

        MOV     AH,43H

        INT     21H

        INT     20H

PROTECT ENDP

        END     PROTECT


위의 것은 화일을 읽기 전용으로 세팅 하여 주는 프로그램 소스 입니다.


        .MODEL  SMALL

        .CODE

        ORG     100H

       

RELEASE PROC    NEAR

        MOV     DX,82H

        MOV     DI,82H

        MOV     AL,13

        MOV     CX,12

REPNE   SCASB

        MOV     BYTE PTR [DI-1],0

        MOV     AL,1

        MOV     CX,0

        MOV     AH,43H

        INT     21H

        INT     20H

RELEASE ENDP


        END     RELEASE


그리고 위의 것은 프로텍트(읽기전용)해제 프로그램 입니다.

( 이 소스는 피터 노턴의 ADVANCED ASSEMBLY LANGUAGE 에서 발췌한 것입니다.)


위의 것을 실행시키기 위해 서는 일단은 간단한 에디터로 위와 같이 입력을 하여

줍니다. 그리고 확장자가 ASM인 화일로 만들어 줍니다.

꼬옥 ASM일 필요는 없습니다만, 보통 어셈블리 프로그램의 소스는 ASM이라는

확장자를 쓰는게 일반 입니다.

위의 것을 파일로 저장한다음,

메크로 어셈블러나 터보 어셈블러로 실행 가능한 파일로 만들어야 합니다.

여러분께서 혹시 메크로 어셈블러나 터보 어셈블러가 없다면, 별수 없죠

구해서 해 보는수 밖에....헤헤..


MASM [FILENAME]


이렇게 해 주면 무슨 메시지가 나오는데 , 그냥 엔터를 치세요.

그럼 화일이 하나 생기는데, 확장자가 OBJ인 화일이 생깁니다.

이 화일은 여러분이 짠 프로그램 소스를 기계어 코드로 바꾸어 준 화일 입니다.

이것은 그 자체로는 바로 실행이 불가능 합니다.

실행하기 위해서는 링크라는 것을 시켜 주어야 하는데, 이것은


LINK [FILENAME]


이렇게 해 주면 됩니다.

그럼 EXE화일이 생겼을 겁니다.

( 위의 예는 메크로 어셈블러를 예로 들은 것입니다. 만약 터보어셈블러로 한다면

MASM --> TASM, LINK --> LINK 로 하여 주면 됩니다. )


만약 EXE2BIN 이라는 유틸이 있다면,


EXE2BIN [FILENAME] [FILENAME]


으로 해 주면 EXE화일을 COM화일로 바꾸어 줍니다.

구지 EXE화일을 COM 화일로 바꾸는 이유는 COM화일이 EXE보다 용량도 훨씬 작고

메모리에 로드되는 속도 또한 매우 빠르기 때문 입니다.

그렇다고 해서 모든 소스들이 COM화일로 변환이 가능 한 것은 아닙니다.

그 이유는 다음으로 미루기로 하죠...


그럼 한번 실행을 해 보죠.

만약 만든 화일이름이 'PROTECT'와 'RELEASE'라고 가정하고,


PROTECT [FILENAME]


그리고 한번 그 화일을 DELETE 해 보세요.

지우기 불가능이라는 메시지가 나올 겁니다.

그리고 다시 그 화일을


RELEASE [FILENAME]


이렇게 한 후,

DELETE해 보면 이번엔 지워 지는 것을 볼 수 있습니다.


그럼 프로그램 소스를 다시 보도록 하죠.


여기서 중요한 것은 각 라인의 명령어의 해설이 아닌 어셈블리어 프로그램 작성의

틀을 알아보기 위한 것이므로, 자세한 명령어 소개는 미루도록 합니다.


보면 위에 .MODEL SMALL 이라는 부분이 있습니다.

이것은 프로그램의 모델을 결정하여 주는 것으로, 코드 또는 데이터 같은 것들의

크기에 따라 하나의 세그먼트에서 각각의 코드와 데이터, 및 스텍이 들어갈 수 있는

크기라면 이 모델을 써 주게 됩니다. 즉 코드와 데이터 각각의 크기가 메모리에 로드

됐을때 한개의 세그먼트 크기인 64Kbyte 미만일때는 small모델로 하게 됩니다.

그리고 만약 데이터 영역이 하니의 세그먼트 크기를 초과 하고, 코드의 크기는

그 미만이라고 한다면,  그 모델은 COMPACT모델이 됩니다.

이와 같은 것을 표로 나타내면,


TINY : .COM 화일 형태

SMALL : 데이터와 코드를 각각 하나의 세그먼트에 저장

MEDIUM : 데이터는 64K 이하, 코드는 64K이상 가능

COMPACT : 데이터는 64K 이상(복수 배열 가능), 코드는 64K 이하

LARGE : 데이타와 코드가 64K 이상, 단일 배열이 아닐 경우

HUGE : 데이터, 코드, 데이터 배열이 64K이상


그냥 한번 읽고 넘어 가세요.

그리고 보통 HUGE모델은 거의 쓰이지 않습니다.

그리고 TASM에서는 HUGE모델을 지원하지 않는다고 합니다.


그 다음으로 .CODE 라는 것이 있습니다.

이것은 .CODE 다음에 오는 것이 바로 코드들이라는 것을 알려 줍니다.

즉, 본 프로그램으로 실질적인 명령어들이 온다는 뜻이지요.

그렇다고 데이터 영역이 올 수 없는 것은 아닙니다.

마찬가지로 .DATA라는 것도 따로 있으니깐요...


그 다음 ORG 100H 라는 것이 있는데, 이것은 본 프로그램이 메모리에 로드되는

오프셋 번지를 16진수 100번지부터 하라는 뜻입니다.

구지 이것을 부치는 이유는, 그 소스를 링크 시켜 실행 가능한 화일로 만들때

이 화일을 COM화일로 하기 위해서는 ORG 100H를 써 주게 되고, EXE 화일을

만들때에는 그것을 써 줄 필요가 없습니다.

그 이유는 나중에 COM화일과 EXE 화일의 차이점을 설명할 때 해 드리겠습니다.

참고로 오프셋 번지 0000 부터 0100H 까지의 영역은 COM화일일 경우에 한해서

PSP ( PROGRAM SEGMENT PREFIX ) 영역이라고 해서 프로그램 로드시 필요한

여러가지 정보들을 담게 됩니다.

이 또한 자세한 세부 사항은 다음 기회로...


좀 설명이 많이 어려워 진것 같습니다.

여기서 모르시는 부분이 있다면 질문란에 질문 하여 주시기 바랍니다...

그럼 아주 자세하게 설명을 해 드리지요..


소스 설명은 다음에 계속..... 2 부를 기대해 주세용~~헤헤...









 [11] 제목 : [초급-실습] 프로그램 두울 #2
 올린이 : 까망벌레(정태식  )    94/11/08 04:05    읽음 : 377  관련자료 없음

안냐세요./..

계속 강의를 해 볼꼐요...


바로 전에 올렸던 글을 다시 읽어보니..

훗..저도 잘 모르는 내용을 마구마구 올린거 가타서..

죄송~


다시 마음을 진정하고 좀 더 자세하게 써 보죠.


그럼 그 모델이라는 것에 대해 조금 더 설명을 하겠습니다.

과일 가계를 생각해 보죠.


사과랑, 배랑, 바나나... 세가지만 파는 과일 가계가 있습니다.

근데 이 가계는 너무 영세하고 작아서, 그리 많은 과일을 팔지는

못합니다.

그래서 구지 다른 전문 판매점 처럼 사과 코너, 배 코너..등등

이렇게 코너를 나누어 팔 필요가 없지요.

그래서 그냥 바구니 하나 가따 놓고 이 바구니 않에 사과니 배니 바나나니..

등등 한꺼번에 담아놓고 판다고나 할까요.

그러니깐, 바구니는 하나만 있으면 세가지 모두 가따놓고 팔 수 있게 되는거죠.


그리고 또 조금 큰 상점을 가 봅시다.

여기에선 역시 사과, 배 , 바나나를 파는데, 그 양이 많아서 이 세가지를

한꺼번에 하나의 바구니에 도저히 담을 수 없습니다.

그래서 세개의 바구니를 가져다 놓고 하나는 사과 , 또 하나는 배...

이런 식으로 나누어서 담아놓고 팔고 있습니다.

전부 세개의 바구니를 사용해서 물건을 팔고 있군요.


그리고 마지막으로 아주 큰 상점을 가 봤습니다.

여기에선, 역시 세가지의 과일을 파는데, 사과 하나만 보더라도, 양이 엄청 많아

하나의 바구니만 가지고는 사과를 전부 담을수가 없습니다.

그래서 한 다섯개의 바구니에 사과를 넣고, 또 여섯개의 바구니에 배를 넣고..

이렇게 아주 많은 바구니가 준비되어 있습니다.


그럼 위의 예를 봐서, 각 가계마다 파는 물건의 양에 따라 바구니의 수가

각기 다릅니다. 이게 바로 모델이라는 계념 입니다.


사과는 코드를 말하고

배는 데이터

바나나는 스텍을 말한다고 하면

바구니는 바로 세그먼트라고 할 수 있습니다.

즉 영세한 가계 ( 코드, 데이터, 스텍 모두 양이 적은) 에서는

많은 바구니가 필요치 않고, 오직 하나의 바구니 ( 세그먼트 ) 만 있으면

물건을 팔 수 있습니다.


그리고 좀 더 많은 과일을 파는 가계에서는 그 종류별로 각각 하나씩의

바구니 ( 세그먼트 ) 를 준비해서 팔게 됩니다.


아주 큰 가계는 여러개의 바구니 ( 세그먼트 ) 를 필요로 하는건 당연 하지요.


만약 가계도 조그맣고, 파는 과일도 얼마 없는데 바구니를 여러개 가져다 놓고

텅텅 빈 바구니에 사과 하나 , 배 하나 .. 이런 식으로 판다면, 공간낭비만

가져올 뿐이죠...


이해가 가셨나요?

그리고 아주 영세한 가계 ( 하나의 세그먼트 안에 코드, 데이타, 스텍이

모두 들어가는 ) 가 바로 com화일로 변환이 가능 하다는 것입니다.


바구니의 갯수와 과일의 양이 잘 낭비없이 조화되게 배치하는것이 매우 중요하다는

것은 두말 할 필요도 없죠....


그럼 이제 .code 라는 것에대해 또한 보충 설명을 드리겠습니다.

이것은 프로그램의 코드가 오는 부분이라고 말씀 드렸습니다.

또한 여기에 데이타가 올 수도 있다고 했습니다.

그리고 따로 .data 라는 지시어가 있다는 말도 했구요..

그럼 과연 이 차이가 무엇인지...


'데이타가 올 수 있다' 에서 데이타라는 것은 바로 다른 프로그램에서

보는 바와 같이 '변수'를 선언하여 주는 곳이 바로 데이터 영역이 됩니다.


C 언어에서 보면


int a = 1, b = 4;

char c = 45, d = 64;


이와 간은 기능의 변수들을 선언하여 주는 것이 바로 데이터를 정의 하는 곳입니다.

물론 어셈블러에선 위와 같은 형식으로 쓰지는 않죠.

그냥 어셈블러 에서 사용하는 방법을 써 보자면,


a        dw        1

b        dw        4

c        db        45

d        db        64


이와 같이 선언을 하게 되는 것입니다.

그럼 과연 이 변수 선언을 '.code'안에서 하는 것과

'.data' 에서 하는 것은 무슨 차이가 있을까요


이것은 바로 데이터 영역을 따로 하나의 세그먼트로 분리시키느냐 마느냐

하는 차이 입니다.

'.code' 안에서 변수 선언을 하여 주면 이 변수들이 갖는 데이터는 바로

코드가 들어있는 세그먼트 안에 저장하게 됩니다.

즉 코드와 데이터가 하나의 세그먼트 안에 공존하게 된다는 것입니다.


반면 '.data' 이 안에 선언되어진 변수들은 따로 세그먼트를 하나

만들어서 그곳에 이 변수들의 데이터 값들을 저장하게 되는 것입니다.

즉, 코드가 하나의 세그먼트를 갖고, 또한 데이터 영역이 하나의 세그먼트를

갖게 되어 2개의 세그먼트를 사용하게 되는 것입니다.


스텍도 같은 형식으로 선언이 되어 집니다.


그래서 '.code'안에 모든 변수가 선언이 되면, 그 소스는 바로 하나의 세그먼트

밖에 갖지 못하는 com화일로 변환이 가능하게 되는 것입니다.


그리고 'org 100h'

이것은 com화일로 변화할 때 써 주는 것이라고 했는데요.

프로그램이 돌아가기 위해서는 프로세서에게 프로그램이 읽혀진 상태,

즉, 프로그램이 어떠한 형태로 되어 있나, 또는 그 밖에 여러가지 정보를

미리 알려 주어야 합니다. 이럴 경우 com화일일 경우에는 하나의 세그먼트 밖에

처리를 하지 못하기 때문에 그 하나의 세그먼트 안에 이러한 정보도 들어가게

되는 것이지요.

그래서 그 정보를 저장하기 위해 0000번지 부터 0100h 번지까지 이 정보를 넣

어주게 되는 것입니다.

즉, 본 프로그램은 오프셋 100h 번지부터 시작하라는 뜻이 됩니다.


반면 exe화일 같은 경우에는 여러개의 세그먼트를 사용할 수 있기 때문에,

구지 하나의 세그먼트에 이 PSP영역과 코드, 또는 데이터 영역을 함께 넣을

필요가 없게 되는 것입니다.


이제 다음 단계로 넘어가죠.

가만히 보면 프로그램 내용이 'PROTECT PROC      NEAR' 에 둘러 싸여 있는 것을

볼 수 있습니다. 이것은 C언어에서의 '{' '}' 와 같은 역할을 한다고 생각하면

됩니다. 이것을 프로시져라고 부르는데, C에서의 함수의 개념과 같은

개념이라고 생각하시면 편합니다. 마찬가지로 C에서 여러개의 함수가 사용

되는 것처럼 어셈블리어에서는 이 프로시져 라는 개념으로 여러개의 프로시져가

사용될 수 있다는 뜻입니다. 베이식에서는 서브루틴과 비슷한 개념 입니다.


C언어를 잘 모르시는 분들은 일단은 그냥

'아 어셈블리어의 프로그램 형태는 대충 이런 것이구나' 라고 넘어가세요..

나중에 여러개의 프로시져를 사용하는 예에서 확실히 설명을 드리겠습니다.

그냥 외우세요...이런걸 꼬옥 부쳐 주어야 되는거라고...헤헤~


그리고 여기서 'PROC'다음에 'NEAR'라고 쓰여 있는걸 볼 수 있습니다.

뜻대로 하자면 '가깝다' 라고 되겠지요..

그럼 과연 머가 가까운걸까요...

이건 데이터 정의 및 코드들이 모두 하나의 세그먼트 안에 있을때 씁니다.

그러니깐 세그먼트 하나를 각각 하나의 아파트 건물에 비유 하면,

하나의 세그먼트 안에서 다른 오프셋 주소로 가게 되는건 바로 하나의 건물안에서

이집에서 바로 저 집으로 간다는 것이지요.

반면 하나의 세그먼트 안에서 다른 세그먼트로 간다는건, 즉 다른 건물로 옴겨 간

다는 의미가 됩니다.

생각해 보세요. 하나의 건물안에서 와따가따 하는게 가까운지 , 아니면 다른 건물

로 와따가따 하는게 가까운지...


COM화일 같은 경우에는 모두 이 NEAR 이지만 EXE 화일일 경우에는 FAR를 사용하게

됩니다. 구지 이런걸 미리 해 주는 이유는 프로세서가 주소를 참조할 때

NEAR일 경우에는 OFFSET만 참조하면 되지만, EXE같은 경우 에서는 오프셋 이외의

SEGMENT주소값까지 참조를 해야 하기 때문에 이와 같은 것을 써 주어야만 합니다.


끝에 'PROTECT ENDP'가 있는에 이것은 프로시져가 끝났다는 걸 알려 줍니다.

'END   PROTECT'또한 프로그램이 완전히 끝났다는걸 나타내 주는 것이고요...


그럼 다음 예제 에서는 변수를 선언하여 주는 예를 보이겠습니다.

( 원래 변수라고 하는것 자체가 우습지만... )


참고로 이 예제들 안의 어셈블리 명령어는 따로 하나씩 설명을 하겠습니다.


 [12] 제목 : [초금-명령] MOV 에 대해
 올린이 : 까망벌레(정태식  )    94/11/08 04:06    읽음 : 387  관련자료 없음

MOV 명령에 관해..


이 명령어는 아주 쉽기 때문에 간략히 하겠습니다.

말 그대로 '옮기다' 라는 뜻을 갖는 명령어 입니다.

그럼 MOV 명령어의 사용 예를 주욱 나열해 보겠습니다.


         MOV       DL,41H

         MOV       AH,2

         MOV       CH,41

         MOV       DL,'B'

         MOV       AH,BL

         MOV       AL,01001011B


위의 것들은 모두 1바이트 연산문으로 되어 있습니다.


MOV      DL,41H


이것은 말 그대로 DL레지스터 ( DX 의 하위 바이트 ) 에 16진수 41을

넣으라는 명령 입니다. 그럼 DL 레지스터에는 16진수 41이 들어가게 됩니다.

뒤의 'H'는 바로 16진수 라는 것을 나타냅니다.

아무것도 안부치면 10진수가 되고, 'B'라는 것을 부치게 되면 바로 2진수라는 의미가

됩니다.


MOV      AH,2


이것은 AH 레지스터에 2를 넣으라는 명령 입니다. '2' 라는 숫자는 10진수나

16진수나 같은 값을 나타내기 때문에 구분을 하지 않아도 됩니다.


MOV      CH,41


이것은 CH레지스터에 10진수 41을 넣으라는 명령 입니다.


MOV      DL,'B'


이것은 DL 레지스터에 아스키 코드 'B'의 아스키 값을 넣어주게 됩니다.

즉 DL에 문자 'B'를 넣어주기 위해서는 꼭 'B'의 아스키 값을 찾아서 일일이

넣어줄 필요 없이 그냥 문자를 써어 넣어 주어도 된다는 것을 말합니다.


MOV      AH,BL


이것은 레지스터 AH에 레지스터 BL에 들어있는 값을 넣어 주라는 명령 입니다.

이와 같이 MOV명령은 레지스터에 숫자를 즉치대입 하는것 말고도 바로 레지스터

에 레지스터의 값을 바로 넣을수 있습니다.

( 예외로 이와 같은 것이 될 수 없는 경우도 있습니다...그것은 조금 후에.. )


MOV      AL,01001011B


이것은 AL 레지스터에 2진수 01001011이라는 값을 넣어주라는 명령 입니다.



위와 같이 MOV 명령은 좌변에 우변의 값을 넣어주게 됩니다.


다음과 같은 명령은 잘못된 것입니다.


         MOV       23H,AL

         MOV       AL,DX

         MOV       AL,F3H


위와 같은 명령은 잘몬된 것으로


MOV      23H,AL


이것은 23이라는 상수에 AL레지스터 안의 값을 넣어주라는 뜻이 되므로

틀린 것이 됩니다.


MOV      AL,DX


이것은 AL이 1바이트인데 반하여, DX는 1워드의 크기 이므로 데이터 간의 전송시

데이터 형이 다르므로 이것은 틀린 것이 됩니다.


MOV      AL,F3H


그럼 이건 왜 틀린가...

이건 AL 보다는 F3H라는 것이 잘못 되어서 틀린 것입니다.

F3이라는 16진수를 AL에 넣으라는 것인데, 여기서 5EH 나 32H 같은 것은 되는데

F3H 라는 것은 어셈블러가 이것이 과연 변수를 말하는 것인지..아님 16진수를

말하는 것인지 알 수 없게 됩니다.

그래서 16진수 중 앞에 영문자가 먼저 오는 경우는 항상 '0'을 먼저 써 주어야

됩니다.


즉, 'F3H' 이것을 '0F3H'이렇게 써 주어야만 하는 것입니다.



또 하나 레지스터간의 데이터 전송에 있어서 불가능한 것이 있습니다.


MOV      DS,CS


이와 같은 경우가 있습니다.

DS, CS 모두 세그먼트 주소를를 나타내는 레지스터 입니다.

이런것은 바로 레지스터간의 데이터 전송이 되지 않습니다.

위와 같은 결과를 원할 경우에는


MOV      AX,CS

MOV      DS,AX


위와 같이 두 줄로 늘려서 중간 매개체인 AX레지스터를 사용하여 전송을 하여

주어야만 합니다.

꼬옥 AX일 필요는 없습니다. BX,CX,DX 모두 가능 합니다.



그리고 MOV 명령중엔 주소 지정 방식이라는 것이 있는데...


MOV      AX,[0432]


이와 같은 형태가 있습니다. 이런것을 주소 지정 방식이라고 하는데,

이것에 대해서는 다음 프로그램 소스를 하나 본 후 따로 설명을 드리겠습니다.




 [15] 제목 : [초급-실습] 프로그램 세엣 #1
 올린이 : 까망벌레(정태식  )    94/11/09 05:58    읽음 : 375  관련자료 없음


이번엔 조금 긴 프로그램을 하나 해 보도록 하겠습니다.



    .model tiny

    .code

    org 100h


start:  jmp st1


info    dw  ?

yes db  'exist',13,10,'$'

no  db  'not exist',13
  ,'$'

flo_msg db  'Floppy is $'

co_msg  db  'Coprocessor is $'

mod_msg db  'Video Mode is $'

m1_msg  db  '40X25 Color Mode',13,10,'$'

m2_msg  db  '80X25 Color Mode',13,10,'$'

m3_msg  db  '80X25 Mono Mode',13,10,'$'


st1 proc    near

    int 11h

    mov info,ax

    mov ax,cs

    mov es,ax


    call    floppy

    call    copro

    call    mode

    int 20h

st1 endp



floppy  proc    near

    lea dx,flo_msg

    call    prt

    mov bx,info

    and bx,0000000000000001b

    cmp bx,00

    je  nofloppy

    lea dx,yes

    call    prt

    ret

nofloppy:      

    lea dx,no

    call    prt

    ret

floppy  endp



copro   proc    near

    mov dx,offset co_msg

    call    prt

    mov bx,info

    and bx,0000000000000010b

    cmp bx,00

    je  nocopro

    mov dx,offset yes

    call    prt

    ret

nocopro:

    mov dx,offset no

    call    prt

    ret

copro   endp



mode    proc    near

    lea dx,mod_msg

    call    prt

    mov bx,info

    and bx,0000000000110000b

    cmp bx,0000000000010000b

    je  mod1

    cmp bx,0000000000100000b

    je  mod2

    cmp bx,0000000000110000b

    je  mod3

mod1:

    lea dx,m1_msg

    call    prt

    ret

mod2:

    lea dx,m2_msg

    call    prt

    ret

mod3:

    lea dx,m3_msg

    call    prt

    ret

mode   endp



prt proc    near

    xor ax,ax

    mov ah,09h

    int 21h

    ret

prt endp

    end start




좀 길죠?

이것은 지금 PC의 상태를 표시해 주는 것으로


Floppy 의 유무

Coprocessor 의 유무

Video Mode 의 상태


이 세가지를 채크하여 상태를 나타내어 주는 프로그램 입니다.

( 후...이거 짜느라고 한참 해맸네요. )

여기서는 변수의 정의, 간접주소의 지정 방식, 여러개의 프로시져 호출,

그리고 인터럽트에 대해 배워볼까 합니다.

그리고 프로시져 호출시의 스텍 변화 같은 것도 다루고요...

그럼 이걸 에디터로 짠 후에 info1.asm 으로 저장 하세요

그리고


masm info1.asm


link info1.obj


exe2bin info1.exe info1.com


이렇게 하고서 info1.com을 실행해 보시면 세가지의 정보를 얻을 수 있습니다.


그럼 이 프로그램에 대한 설명은 다음에 하죠..


참고 : 사실 프로시져 호출 후에 push 로 레지스터를 보존해야 하는데,

       짧은 프로그램이기 때문에 그런 과정은 생략 했습니다.




 [16] 제목 : [초급-보충] 변수 선언에 대하여
 올린이 : 까망벌레(정태식  )    94/11/10 12:50    읽음 : 401  관련자료 없음


여러분의 성원(?)에 기픈 감사를 드리며...흐흐..

훌쩍훌쩍~~

감기 조심들 하세요...

( 푸르르~~~ 팽~ )


그럼 우선 변수의 정의에 대해 설명을 드리겠습니다.


변수 정의란 바로


info    dw      ?

yes     db      'exist',13,10,'$'

no      db      'not exist',13,10,'$'

flo_msg db      'Floppy is $'

co_msg  db      'Coprocessor is $'

mod_msg db      'Video Mode is $'

m1_msg  db      '40X25 Color Mode',13,10,'$'

m2_msg  db      '80X25 Color Mode',13,10,'$'

m3_msg  db      '80X25 Mono Mode',13,10,'$'


이 부분을 말하는 것으로,

일단은


yes    db    'exist',13,10,'$'


와 같은 경우 앞의 'yes'가 바로 변수의 이름을 말하는 것입니다.

즉 사람마다 이름이 있는 것과 같이 각 변수에도 각각의 이름이 있어야 하는

것이지요.

( 혹시 변수가 뭔지 모르시는 분은? 흐흐.. 각성을... )


그리고 'db'같은건 변수의 형태를 결정하여 주는 것입니다.

즉 db 란 'define byte'의 약자로 'yes'라는 변수의 형태는 바로 바이트 형이라는

것을 알려 줍니다.


그리고 마지막의 'exist' 하는 마지막 까지의 부분은 바로 변수의 내용을

설정하여 주는 것입니다.


그럼 하나씩...

우선은 변수의 형태를 결정하여 주는 것에 대해 어떻한 종류가 있는지 주욱

나열해 보도록 하지요.


DB      :      DEFINE BYTE      ( 1 BYTE )

DW      :      DEFINE WORD      ( 2BYTE = 1WORD )

DD      :      DOUBLE WORD      ( 4BYTE = 2WORD )

DF      :      DEFINE FARWORD   ( 6BYTE = 3WORD )   < 80386/ 80486 >

DQ      :      DEFINE QUADWORD  ( 8BYTE = 4WORD )

DT      :      DEFINE TENBYTES  ( 10BYTE = 5WORD )


다음과 같은 종류가 있습니다.


일단은 DB에 대한것 부터


               FLD1DB    DB    ?

               FLD2DB    DB    32

               FLD3DB    DB    20H

               FLD4DB    DB    01011001B

               FLD5DB    DB    10 DUP(0)

               FLD6DB    DB    'Personal Computer'

               FLD7DB    DB    '32654'

               FLD8DB    DB    01,'Jan',02,'Feb',03,'Mar'


( 발췌 : IBM PC ASSEMBLY LANGUAGE AND PROGRAMMING ( PETER ABEL ) )


위와 같은 여러가지의 형식으로 DB라는 형식의 변수를 정의하여 줄 수 있습니다.

그럼 첫번째 변수 'FLD1DB'에 대해서 말씀 드리자면,

뒤에 '?' 라는 것이 바로 변수의 값이라고 정의 되어 있습니다.

이것은 이 변수에 '?'가 들어가는 것이 결코 아닙니다.

이것은 '?' 라는 속 뜻 그대로 '모르겠다' 라는 의미의 '?' 가 됩니다.

즉, 'FLD1DB'라는 변수는 형태는 분명 1바이트 형인데, 그곳에다가 무얼

넣을지는 아직은 모르는 상태고, 그냥 'FLD1DB'라는 이름으로 메모리에 1바이트의

변수를 위한 공간을 잡아 달라는 얘기 입니다.

나중에 이 변수에 따로 어떻한 값이 들어갈지 지금 상태는 모르기 때문에

이와 같이 정의를 하여 주는 것입니다.


두번째 'FLD2DB'로 넘어가죠.

이것은 '32'라는 것을 변수에 넣으라는 뜻입니다.

즉, 십진수 32라는 값을 'FLD2DB' 라는 변수에 넣으라는 뜻이 됩니다.

간단하죠?


세번째 것은 'FLD3DB'인데,

이것은 'FLD2DB'와 별다른 것은 없습니다.

'20'뒤에 'H'를 붙여 줌으로서 'FLD3DB'라는 이름의 변수에 16진수 20을

넣으라는 것이 됩니다.


네번째 'FLD4DB' 도 역시 간단히

이 변수 안에 2진수 01011001 이라는 값을 넣으라는 뜻이 됩니다.


그리고 다섯번째 'FLD5DB' 이것은 좀 눈 여겨 볼 필요가 있습니다.

자세히 보면 'DUP'라는 것이 있습니다.

이것은 변수에 넣을 데이터가 아닌 변수에 어떠한 조치를 취해 주는 명령어의

일종입니다.

'10 DUP(0)' 이라고 되어 있는데요, 여기서 '10'이란것은 'FLD5DB'라는 변수의

이름으로 메모리 공간을 1바이트로 잡아 주는데 있어서 1바이트 공간을

10개 잡아 달라는 명령 입니다.

즉, 이것은 배열과 같은 기능을 하는 것입니다.

그리고 괄호 안의 '0'은 열개를 잡는데 있어서 그 10바이트의 내용을 모두

'0'으로 채워
 牝遮 ( 초기화를 하라는 ) 뜻이 됩니다.

만약 '10 DUP(43)' 이라고 한다면 10바이트의 메모리를 잡을때 10바이트 모두

각각 그 안의 내용이 '43'으로 하여 달라는 뜻이 됩니다.

이번것은 특별히 메모리 그림을 그려 드리겠습니다.


;---------------------------------------------------------------

; 00 ; 00 ; 00 ; 00 ; 00 ; 00 ; 00 ; 00 ; 00 ; 00 ;

;---------------------------------------------------------------

10 DUP (0) 의 경우


;--------------------------------------------------------------

; 43 ; 43 ; 43 ; 43 ; 43 ; 43 ; 43 ; 43 ; 43 ; 43 ;

;---------------------------------------------------------------

10 DUP (43) 의 경우


이와 같은 결과를 얻게 됩니다.


그리고 이렇게 저장 되어진 내용을 꺼내어 사용하는 법에 대해서는

메모리 주소 지정 방식을 설명할 때 자세히 설명을 드리도록 하겠습니다.



다음으로 'FLD6DB' 라는 명의 변수가 있습니다.

여기서는 변수에 대입 하여 주는 값이 상수가 아닌 문자열이 됩니다.

근데 여기서 위의 문자열을 보면 1바이트 크기를 훨씬 넘는 크기로

모두 17바이트의 크기를 갖는 문자열 입니다.

그럼 과연 하나의 바이트만 사용하도록 정의 되는 'DB'에서 이 17바이트나

되는 문자열을 집어넣으라는 말은 무슨뜻?

역시 이것은 위에서 설명을 드린 'DUP'의 개념을 생각하시면 됩니다.

즉 1 바이트씩 메모리에 저장을 하여 주는 것입니다.

모두 17바이트의 문자 이므로 메모리 상에도 마찬가지로 17바이트의 연속된

공간을 할당하고 여기에 각각 한 바이트(한 문자) 씩 넣어 주게 됩니다.

메모리 그림을 다시 그려 보죠.


;---------------------------------------------------------------------

; P ; e ; r ; s ; o ; n ; a ; l ;   ; C ; o ; m ; p ; u ; t ; e ; r ;

;----------------------------------------------------------------------


위와 같은 메모리 구조로 저장이 되어 집니다.

사실 엄격히 따지자면 메모리에 위와 같은 문자가 직접 들어가는 것은 아니구요

이 문자들을 모두 아스키 코드 값으로 변환을 하여 저장을 하게 됩니다.


;---------------------------------------------------------------------

; 50 ; 65 ; 72 ; 73 ; 6F ; 6E ; 61 ; 6C ; 20 ; 43 ; ....등등...

;---------------------------------------------------------------------

( 이 값들은 모두 16진수 입니다. )

이와 같이 메모리에 저장을 하게 됩니다.

역시 이 변수를 사용하는 법에 대해서는 메모리 주소지정 방식에 대해 설명을

드릴때 자세히 설명을 드리겠습니다.


다음으로 'FLD7DB' 가 있는데...

들어갈 데이터 값이 '32654'가 됩니다.

이것은 혹 착각을 하실 수도 있습니다만, 이 것은 32654라는 값을 나타내는

것이 아니고 문자 32654를 나타내는 것입니다.

즉 아스키 코드값이 메모리에 들어가게 되는 것이지요.

'FLD6DB'와 같이 하나씩 문자로 처리하여 메모리에 들어가게 되는 것입니다.


마지막으로 'FLD8DB'가 있습니다.

이것은 대입할 데이터가 마구마구 섞여 있습니다.

문자열과 숫자가 막 섞여 있는 예가 됩니다.

간단히 그림을 그려 보면 쉽습니다.


;---------------------------------------------------------

; 01 ; J ; a ; n ; 02 ; F ; e ; b ; 03 ; M ; a ; r ;

;---------------------------------------------------------

위와 같이 들어가는데, 이 역시 문자는 아스키 코드로 들어가게 되겠죠?


;----------------------------------------------------------------------

; 01 ; 4A ; 61 ; 6E ; 02 ; 46 ; 65 ; 62 ; 03 ; 4D ; 61 ; 72 ;

;----------------------------------------------------------------------


이와 같이 들어가게 됩니다.



이제 다음 단계로 'DW'라는 것에 대해서 알아 보겠습니다.

간단한 예를 보이죠.



          FLD1DW    DW    0FFF0H

          FLD2DW    DW    01011001B

          FLD3DW    DW    3,4,7,8,9

          FLD4DW    DW    5 DUP (0)

         

          FLD1DB    DB    '32654'

          FLD5DW    DW    FLD1DB


자...위와 같은 예를 들었습니다.

'DW'는 'DEFINE WORD' 라고 했다시피 2바이트, 즉 1워드의 메모리 공간을 할당하여

줍니다.


그럼 또 순서대로...


'FLD1DW' 이 변수는 메모리에 2바아트 크기의 메모리를 잡아주게 됩니다.

그리고 여기에 16진수 'FFF0' 이라는 2바이트 크기의 값이 들어가게 됩니다.

그림을 그려 보자면,


;------------------------

; 0F ; FF ;

;------------------------

이와 같이 들어가게 되는 것이지요.

( 여기서 또한 1바이트씩 내용이 앞뒤가 바뀌어 들어가는데 그 이유는 마지막에

  간단히 설명을 드기겠습니다. )




( 앞으로 메모리 그림을 직접 그리기 보다는 그냥 나열 식으로 표현을 하겠

  습니다.

  예를 들면 위와 같은 경우는


      FFF0


  이렇게 쓰고 'DB'에서 정의 했던 'Personal Computer' 같은 경우는 그냥


  50 65 72 73 6F 6E 61 6C 20 43 6F 6D 70 75 74 65 72


  와 같이 쓰겠습니다. 여기서는 1바이트씩 앞뒤가 바뀌는 것은 나타내지 않고

  그냥 보기 편한대로 나열하겠습니다.

  왜? ... 피곤하니깐! )


그럼 사실 이 변수를 정의할때 'FLD1DB    DB    0FFH,F0H' 와 같이 해주어도

메모리에 들어가는 형태는 똑같습니다.

하지만 여기서 중요한것 하나.

만약 위에 예에서 'FLD1DB'를 불러오게 되면은 사실 'FF'라는 1 바이트 만을

가져오게 됩니다. 즉 처리가 1바이트 크기로 된다는 것입니다.

하지만 'FLD1DW'같은 경우, 불러올때는 2바이트 크기인 'FFF0'을 한꺼번에

불러와 처리를 하게 되는 것입니다.


그럼 다음으로 'FLD2DW'를 보죠

이것또한 간단히 01011001 이라는 2진수 값을 2바이트 크기의 메모리에 넣으라는

것이 됩니다.


    0059


이와 같이 저장이 되는 것입니다.


'FLD3DW'를 보면,

각 값들을 ','(콤마) 로 구분을 하였습니다.

모두 5개의 다른 데이터 값이 주어 졌습니다.

이럴 경우에는 모두 5개의 워드를 메모리 공간으로 잡아 줍니다.

그리고 각각의 하나의 워드안에 콤마로 구분된 각각의 값들을 넣어 주게 됩니다.


    0003 0004 0007 0008 0009


위와 같은 형태로 저장이 되어 지는 것입니다.


이제 마지막으로 'FLD4DW'를 보도록 하겠습니다.

이 변수를 선언하기 전에 바로 'FLD1DB'라는 변수가 선언 되어 있고

'FLD4DW'에 넣는 변수값이 바로 'FLD1DB'라는 변수를 그대로 넣어주도록

되어 있습니다.

이런 경우는 생각과는 달리 'FLD1DB'의 내용이 들어가는 것이 아니고

바로 'FLD1DB'로 정의되었을때 잡혀지는 메모리의 주소값이 'FLD4DW'에 들어가도록

되어 있습니다.


다시 말해 만약 'FLD1DB'라고 변수를 선언 하여 주었을때, 그 할당되어진 메모리

주소가 '0103'이라고 한다면 'FLD4DW'에는 그 메모리의 주소값인

'0103'이 들어가게 된다는 뜻입니다.




다음으로 'DD', 'DF' , 'DQ', 'DT' 들이 있는데, 이것은 간단히 메모리에 들어가는

형태만을 설명드리고 필요에 따라 간단한 설명을 조금씩만 해 드리겠습니다.

방식은 모두 DB,DW 와 별 다를게 없기 때문입니다.



         FLD1DD    DD        ?

         FLD2DD    DD        32572

         FLD3DD    DD        14,49

         FLD4DD    DD        'PC'

        

         FLD1DB    DB        34

         FLD2DB    DB        65

         FLD5DD    DD        FLD2DB - FLD1DB



1. 'FLD1DD'


  00000000


2. 'FLD2DD'


  00007F3C

 

  32572의 십진수 값이 16진수로 변환되어 메모리에 들어갑니다.


3. 'FLD3DD'

 

  0000000E  00000031


  각 10진수 14 와 49 를 16진수로 변환하여 따로 더블워드에 넣게 됩니다.


4. 'FLD4DD'


  00005043

 

  아스키 문자 'PC'를 각각의 아스키 값으로 변환 하여 더블 워드 하나에 넣습니다.


  'P' = 50

  'C' = 43


5. 'FLD5DD'


  00000001


  이것은 위의 'DW'에서 비슷한 걸 본 적이 있지요?

  마찬가지로 'FLD1DB','FLD2DB'라는 두 개의 바이트형 변수가 'FLD5DD'의 선언

  전에 선언되어 져 있습니다.

  그리고 이 변수값은 1이 저장 되어 있습니다.

  왜 '1'일까요?

  변수에 넣어주는 값은 분명히 'FLD2DB - FLD1DB'라고 되어 있는데...

  65 - 34 의 값인 21이 들어가야 정상이 아닐까요?

  역시 위의 'FLD5DD'에 들어 가는 값은 메모리의 주소값이 되어 지는 것입니다.

  즉 'FLD2DB'와 'FLD1DB'라고 변수가 선언되어질 때의 메모리 주소값들의 그

  차이를 넣어주게 됩니다.

 

  위에서 보는 바와 같이 'FLD1DB'와 'FLD2DB'라는 두 개의 변수선언부는 바로

  연속적으로 되어 있습니다.

  그렇기 때문에 그 메모리 주소의 차이가 1이 되는 것입니다.

  이와 같은 경우의 두가지 예를 더 보여 드리겠습니다.


         FLD1DB    DB     32

         FLD1DD    DD     'PC'

         FLD2DB    DB     53

         FLD2DD    FLD2DB - FLD1DB


  이런 경우 과연 FLD2DD에는 무엇이 들어갈까요?

  답은 5 입니다.


  00000005


  이렇게요..

  즉 'FLD1DB' 와  'FLD2DB'사이에 더블워드형의 변수가 하나 더 선정이 되어

  있습니다. 그래서 'FLD2DB'까지의 변수 선언으로 되어지는 메모리 형태를 보면,


  20 00005043 35


  이런 형태가 됩니다.

  만약 'FLD1DB'라고 주어진 변수의 메모리 주소값이 105라고 가정한다면,

  그 다음에 선언되어진 더블워드 형식의 변수'FLD1DD'의 주소값은 106부터

  시작합니다.  그리고 그 다음에 오는 1바이트 형의 변수 'FLD2DB'가 시작하는

  변수의 주소값은 10A 가 되어집니다 ..

  그래서 결과 적으로는


  10A - 105


  가 되어 5가 들어가게 되는 것입니다.


  다른 예를 하나 더 보여드리겠습니다.


           FLD1DW    DW    'PC'

           FLD2DW    DW    45

           FLD1DD    DD    FLD2DW - FLD1DW


  그럼 위와 같은 경우는 'FLD1DD'에 어떠한 값이 들어가겠습니까?

 

  00000001 이라고요?

  '땡~~' 입니다.

  역시 위의 예에선 'FLD1DW'와 'FLD2DW'의 변수가 연속적으로 선언되어 있긴

  합니다. 하지만 아까와의 차이는 바로 바이트 형이 아닌 워드 형의 변수가

  선언 되어졌다는 것입니다.

  이걸 메모리에 들어가는 구조로 나타내 보죠


  5043 002D


  이렇게 들어 갑니다.

  이런 경우 'FLD1DW'가 잡혀있는 메모리의 주소값이 100 이라고 가정 한다면,

  'FLD2DW'가 시작하는 주소값은 102가 되어 집니다. 왜냐하면 'FLD1DW'의 크기가

  1 바이트가 아닌 2바이트의 크기를 차지하기 때문 입니다.

  결과적으로 'FLD1DD'에는


  00000002


  입니다.



 

DF 라는 형의 변수는 아마도 80386/80486 이상에서만 돌아가는 변수형인것

같습니다. 형식은 위와 같지만 아무래도 시스템의 제약이 따르기 때문에,

가능하면 사용하지 않는편이 좋을듯 싶습니다.



이제 마지막으로 DQ,DT 를 한꺼번에 몰아서 설명을...



0000000000000000        FLD1DQ    DQ    ?

474D000000000000        FLD2DQ    DQ    0474DH

3CF7000000000000        FLD3DQ    DQ    32572



00000000000000000000    FLD1DT    DT    ?

56341200000000000000    FLD2DT    DT    123456

43500000000000000000    FLD3DT    DT    'PC'



위와 같습니다..

왼쪽은 실질적으로 메모리에 들어가는 형태를 쓴 것입니다.


하지만...하나! 중요한 차이점.

이것은 바로 메모리에 들어가는 형태 입니다.


         FLD1DD    DD    'PC'

         FLD1DQ    DQ    'PC'

         FLD1DT    DT    'PC'


다음과 같이 각각 'PC'라는 것에 대해서 정의를 해 주었다고 해 봅시다.

그럼 각각 메모리의 저장 상태를 보면,


         00005043

         5043000000000000

         43500000000000000000


이와 같습니다.

다른점은?

물론 각 각의 크기가 다르다는 것은 당연한 사실이고요,

여기에 또 한가지 다른점은,

'DD'의 선언 같은경우는 형태가 '0000'다음에 '5043'이 들어갔다는 점입니다.

하지만 'DQ'는 5043 다음에 '0000' 이 왔다는 것이지요.

즉 'DD'는 메모리의 하위 바이트 부분에 실제의 값이 써 지지만, 'DQ'나 'DT'같은

것들은 메모리의 상위 바이트부터 채워 진다는 것입니다.

또한 'DQ'와 'DT'의 차이점은,

'DQ' 나 'DD'와 같은 것은 '5043'인 점에 반해

'DT'는 '4350' 과 같이 1 바이트씩 앞뒤가 서로 바뀌어 저장 된다는 점입니다.


   참고 : 전에 메모리의 저장형태에 대해 'DEBUG'를 사용할때 본 적이 있습니다.

          여기서는 가령 ' MOV CX,0123'인 경우 '2301'과 같이 들어가는 것을

          본 적이 있습니다. 이것은 메모리가 아래로 갈 수록 그 주소값이 증가

          하는 형태이기 때문에 실제 넣는 데이터의 앞 부분 '01'은 상위부분의

          메모리로, 뒷 부분'23'은 메모리의 하위부분에 들어가기 때문에 그런

          현상이 발생하게 되는 것입니다.


           ;------------------;

    FFFF   ;                  ;  상위 부분

           ;                  ;

           ;------------------;

           ;        01        ;

           ;------------------;

           ;        23        ;

           ;------------------;

           ;                  ;

    0000   ;   
             ;  하위 부분

           ;------------------;

             하나의 세그먼트


 

 다음에는 주소 지정 방식에 대해 설명을 해 보겠습니다.


                    


 [17] 제목 : [초급-보충] 주소지정방식에 대하여...
 올린이 : 까망벌레(정태식  )    94/11/19 11:07    읽음 : 358  관련자료 없음

후... 넘 늦게 올려서 죄송 합니다.

헐~

사정이 좀 있어가지구여

그럼 이번엔 계속 해서

주소 지정 방식에 대해 배워볼까 합니다.

참...

어떤분이 오타가 좀 있다구....흐흐~

사실 일일이 오타 찾아내기가....읔~~~

제가 넘 게을러서 말이죠...솔직히 그런거 찾아내기가 영 구찮네요...

흐흐...그래도 치명적인 오타는 없도록 할테니깐요..

구냥 세겨 들으세요....헤헤~~~




번지 지정 방식에는 일단은 두 가지가 있다고 봅니다.

그것은 직접 주소 지정 방식과, 간접 주소 지정 방식이라고 불리우고 있습니다.

음...하지만, 직접 주소 지정 방식은 별로 중요한 것은 없습니다.

그 이유는 별로 쓰이지도 않고 비 효율적이기 때문 이죠.

그래도 일단은 직접 주소 지정 방식에 대해 간단히 설명을 해 드리겠습니다.


그럼 과연 주소 지정 방식이란게 무엇인가?

헐~

그럼 그에 해당하는 예를 보이겠습니다.

여러분, 변수 선언을 보셨지요?

그럼 과연 그 변수 선언에서 그 변수들을 직접 가지고 와서 사용할땐

프로세서는 과연 어떻게 처리를 할까요?


         FLD1    DB    34


이와 같은 변수를 선언을 했다고 가정을 합시다.

그럼 메모리에는 이 변수를 위한 하나의 공간을 잡아 줍니다.

즉 메모리에 FLD1이라는 이름으로 1바이트 크기의 방을 잡고서, 거기에다 데이터

값인 '34'라는 값을 16진수로 변환 하여 22라는 값으로 넣게 됩니다.

그럼 이 34라는 숫자가 들어가 있는 곳도 메모리 이기 때문에 역시나 그 메모리의

고유의 주소값이 있는것은 당연 합니다.

( 모든 메모리는 각각의 고유의 주소값을 가지고 있기 때문이죠. )

그럼 그 메모리로 저장된 값이 다음과 같다고 가정을 해 봅시다.


         SEGMENT : 03A2

         OFFSET  : 0106


이와 같다고 가정을 하면,

 ( 사실 프로그램 소스가 실행 화일로 변환이 되어서 메모리로 로드될 때는 항상

   이 주소값이 변합니다. 즉, 실행할 때 마다 그 시스템의 상태에 따라 로드되는

   메모리의 값이 항상 일정하지는 않다는 것입니다. )

그림으로 나타내 보면


;---------------------------------------------------

; ?? ; ?? ; ?? ; 22 ; ?? ; ?? ; ?? ;

;---------------------------------------------------

 0103 0104 0105 0106 0107 0108 0109


여기서 '??' 란 어떠한 다른 ( 우리가 알 수 없는, 미리 들어 있던 쓰레기 값들 )

수가 들어 있다는 뜻이고, 우리가 원하는 값은 바로 106 번지의 22 입니다.


이와 같이 정의 되어진 변수를 사용하는 하나의 예를 잠깐 봅시다.


       MOV      AX, FLD1


이와 같은 명령어가 프로그램 내에 있다고 가정을 해 보면,

이 명령어가 들어있는 소스를 실행 가능한 화일로 만들고서 이를 메모리에 로드하게

되면 다음과 같이 변환이 됩니다.


       MOV      AX, [106]


위와 같이 변환이 됩니다.

즉,원래의 변수명은 사실 프로세서에게는 아무 의미도 없는 쓰레기에 불과 합니다.

전에 말씀 드렸던, 모든 명령어는 그에 해당하는 숫자로 바뀌어서 프로세서에게

인식을 시켜 준다고 말씀을 드렸습니다. 그래서 여기에서도 FLD1 이라는 영문자가

아닌 프로세서가 직접 인식을 할 수 있는 언어인 기계어 ( 숫자 ) 로 변환을 시켜

주게 됩니다.

그래서 위와 같은 경우는 바로 FLD1의 내용인 34라는 값이 메모리 주소인 106번지에

들어 있기 때문에 직접 이를 가져오도록 하여 주는 것입니다.

음...( 흐..넘 어렵당 )


'[]'로 둘러 싸여 있는 숫자인 '106'은 메모리의 주소값을 나타내는 것으로 '[]'

로 인해 프로세서는 이것이 바로 '106번지에 있는 데이터 값을 가져오라!' 라는

것으로 인식을 하게 됩니다.

즉, 프로세서는 변수명을 인식하지 못하기 때문에 어떠한 변수가 선언되어 지면,

그 변수이름으로 잡혀져 있는 메모리의 주소값을 프로세서에게 직접 알려 주어

그 메모리에 있는 데이터 내용을 인식하고, 가져오고, 여러가지 작업을 하게 되는

것입니다.


만약 프로그램 소스에서


      MOV      AX,FLD1


대신에


      MOV      AX,[106]


이라고 한다면 같은 결과가 나올꺼라고 한다면?

땡~~~

후자와 같은 경우를 보통 직접 주소 지정 방식이라고 하는데, 여기서는 위와같은

방식으로 프로그래밍을 하여 직접 실행을 하여 보면, 간혹 운 좋게 원래의 원하는

값인 34가 들어갈 수도 있지만, 대부분은 아마도 틀린 값이 들어가게 될겁니다.

이유는 간단.

앞서 말씀 드렸듯이, 프로그램이 메모리로 실행을 위해서 로드되면 그 로드되는

메모리의 위치가 항상 같지가 않다는 것입니다.

프로그램이 로드될때는 프로세서가 알아서 그 프로그램을 로드시키기에 알맞은

빈 메모리를 임의적으로 결정하기 때문에 그 위치는 수시로 변하게 됩니다.

그래서 위와 같은 표현은 결코 원하는 값을 기대하기는 어렵습니다.

그래서 이 보다는 간접주소 지정방식이 주로 사용됩니다.


하지만 직접 주소지정방식이 전혀 쓰이지 않는 것은 아닙니다.

그것은 가령 메모리의 어떻한 부분은 항상 MS-DOS를 사용하는 PC에서는 거의 같은

메모리 주소에 같은 정보가 들어가는 것도 있습니다.

하드 디스크의 크기라든가, 메모리의 크기, 날짜. 등등 이와 같은 것들은 시스템이

부팅이 될 때 메모리로 읽혀 들어가는 부분이 항상 같습니다.

그렇기 때문에 만약, 위와 같은 데이터들을 참조하기를 원할때는 직접 메모리 주소를

써서 이 들을 참조 할 필요가 있습니다.


그럼 이제 간접 주소 지정 방식에 대해 자세히 들어가 보도록 하겠습니다.


         FLD1      DB      34

         FLD2      DB      'R'

         FLD3      DB      'COMPUTER'


다음과 같이 변수를 선언 하였다고 해 봅시다.

그럼 메모리에는 다음과 같이 들어가겠죠?


      22 52 43 4F 4E 50 55 54 45 52


위와 같은 순서로 메모리에 들어가게 됩니다.

여기서 메모리의 주소값은 아직 정해지지 않은 상태 입니다.

주소들은 실행을 위해 메모리에 로드될때 정해 지게 되는 것입니다.

그럼 일단은


         MOV      AL,FLD1


과 같이 정의를 하였다고 해 봅시다.

이것은 아시다 시피 FLD1이라는 변수의 값인 34라는 값이 AL레지스터에 들어가게

됩니다.


         MOV      AL,FLD2


이 것도 마찬가지로 문자 'R'의 아스키 코드 값인 52라는 16진수가 AL 레지스터에

들어가게 됩니다.


         MOV      AL,FLD3


과연 위와 같은 것은 레지스터 AL에 어떻한 것이 들어갈까요?

답은 'C'라는 문자의 아스키 코드 값인 '43'이라는 16진수 숫자만이 들어가게 됩니다.

그 이유는 FLD3라는 변수는 1바이트 형 변수이기 때문에 'COMPUTER'이라는 8바이트의

내용을 모두 가져 오지는 못합니다.

그럼 여기서 여러분은 뭣하러 FLD3 에 'C'하나만 넣지 왜 쓸데없이 'COMPUTER'이라고

할 필요가 있느냐?

라고 질문 하신다면.

위에서 메모리에 들어가 있는 형태를 보면 알 수 있듯이 'COMPUTER'은 결코 따로

각각 한 문자 ( 1 바이트 ) 씩 떨어져 들어가는 법이 없이 모두 연속적으로 메모리에

들어가게 됩니다. 그래서 그 다음 문자인 'O'를 가져오고 싶다면 FLD3로 정의 되어진

변수의 그 다음 메모리 번지를 검색하면 'O'라는 문자를 가져올 수 있게 됩니다.


      FLD3      : 'C'

      FLD3 + 1  : 'O'

      FLD3 + 2  : 'M'

      FLD3 + 3  : 'P'

      .....


과 같이 사용한다면, 모든 문자를 참조 할 수 있겠죠.


      MOV      AL, FLD3 + 2


위와 같이 명령을 한다면, FLD3가 지정하고 있는 메모리의 데이터 내용인 'C'다음의

세번째 문자 'P'를 AL 레지스터에 넣게 되는 것입니다.

하지만 위와 같이 하면 'COMPUTER'의 전체 내용을 각각이 가져오는데 불편한 점이

있습니다.

그래서 FLD3라는 변수로 지정되어진 메모리의 주소값을 직접 가지고 와서 조작할 수

있는 기능이 있습니다.


      MOV      BX, OFFSET FLD3


라고 하면 BX레지스터에 FLD3로 정의 되어진 메모리의 주소값이 들어가게 됩니다.

그래서 이 BX레지스터의 값을 직접 변화 시켜가며 훨씬 빠르고 손쉽게 그 문자열을

조작 할 수 있게 됩니다.

 여기서 하나, BX 대신에 BL로 하면 안되는가....

안됩니다.

그 이유는 메모리의 주소값 ( 여기서는 오프셋 ) 이 1 바이트값이 아닌 항상 1워드

값을 가지는 이유 때문에 FLD3가 1바이트 형 변수이기는 하지만 그 변수의 데이터

값을 가져오는 것이 아닌 바로 그 변수의 ( 메모리의 ) OFFSET 값을 가져오기 때문에

항상 1워드 크기의 레지스터에 넣어야만 합니다.


위에서의 'OFFSET'이라는 의사 명령이 바로 위와 같은 역할을 하는 명령이 되는 것이지요.

이것이 좀 길다 하면


      LEA      BX,FLD3


라고 해도 결과는 똑같습니다.

가능하다면 'LEA'명령을 사용하는 것이 보기에 좋기 때문에 'LEA'명령을 사용하는 것이

바람직 하다고 할 수 있겠지요?


      LEA      BX,FLD3

      MOV      AL,[BX]


이와 같이 하면 우선은 BX 레지스터에는 FLD3의 주소값이 들어가게 됩니다.

그 다음 AL 레지스터에는 바로 BX레지스터가 지정하는 메모리의 주소에 들어있는

데이터 내용, 즉 'C'라는 문자가 들어가게 되는 것입니다.


      LEA      BX,FLD3

      INC      BX

      MOV      AL,[BX]


위와 같은 경우에는 역시 BX레지스터에 FLD3의 주소값이 들어가고

그 다음의 명령인 'INC'에 의해 BX레지스터의 값이 하나 증가하게 됩니다.

( INC 명령은 레지스터의 값에 1을 더하는 효과를 봅니다.

  만약 BX레지스터의 값이 0231 이라고 한다면 ' INC    BX' 에 의해 BX레지스터의

  값은 1이 증가한 0232가 되는 것입니다. )

그 다음에는 AL레지스터에 FLD3가 지정하고 있는 문자인 'C' 다음의 문자 'O'가

들어가게 됩니다.

이런 식으로 변수를 조작 하게 됩니다.


         FLD1      DB      34

         FLD2      DB      'R'

         FLD3      DB      'COMPUTER'


그럼 위와 같이 지정 되어진 프로그램에서 다음과 같은 경우를 생각해 보죠.


         LEA      BX,FLD1

         INC      BX

         MOV      AL,[BX]


위와 같이 명령을 한다면 과연 AX레지스터에는 어떻한 값이 들어갈까요.

FLD1이라는 변수는 오직 하나의 내용인 '34'라는 값을 지정하고 있습니다.

거기서 34라는 값이 저장되어진 메모리의 번지값 다음의 주소값에 있는 내용을

AL 레지스터에 넣으라고 되어 있는데요.

모순이죠?

하지만 프로그램상으로는 어떻한 에러 메시지도 출력하지 않고 잘 돌아 갑니다.

위와 같이 변수를 선언하여 주면 메모리에는 다음과 같이 들어가죠.


      22 52 43 4F 4E 50 55 54 45 52


위에서 FLD1이 저장되어진 곳이 첫번째인 '22'라는 값입니다.

( 34를 16진수로 바꾸면 22 가 되죠 )

여기서 FLD1이 가리키는 메모리의 주소값이 하나 증가하게 되면 바로 그 옆의 내용인

'52'를 가리키게 됩니다.

즉 FLD2가 지정하는 메모리의 주소값이 되어 버리게 됩니다.

그래서 위와 같은 프로그램에서는 AL 레지스터에 바로 'FLD2'의 데이터 값인

'C' 즉 '52'라는 값이 들어가게 되는 것입니다.

마찬가지로 'FLD1 + 3'을 하게 되면 '4F'를 지정하게 됩니다.

이것은 'FLD3'의 내용중의 하나인 'O'를 가리키게 됩니다.

이와 같이 되는 이유는 변수선언을 하게 되면 주로 연속되어서 여러개의 변수들이

메모리로 잡히기 때문에 위와 같이 해게 되면 그러한 값들을 얻게 되는 것입니다.

     

이제 선언되어진 변수값에 어떻한 값을 넣는 방법에 대해 설명을 하겠습니다.


      FLD1      DB      ?


위와 같이 선언되어진 변수는 메모리에 FLD1이라는 이름으로 메모리의 1바이트의

크기를 잡아주고 여기에는 어떻한 값이 들어갈지는 아직 모른다고 전에 말씀드렸습니다.

그럼 위와 같이 변수를 잡아 주는 이유는 무엇일까요?

그것은 어떻한 연산을 한 후에 그 결과 값들을 넣어주기 위한 것입니다.

물론 레지스터에 보관을 하면 됩니다만, 레지스터의 갯수가 제한 되어져 있기 때문에

저장할 데이터들이 매우 많을 경우에는 레지스터들이 감당을 할 수 없게 됩니다.

이럴때 남아도는 메모리를 이용하여 일시적으로 저장을 하여 다시 꺼내거나 조작을

하면 됩니다.

그런 의미에서 위와 같은 변수 선언을 아마 자주 사용하게 될겁니다.


그럼 이제 이와 같이 선언되어진 변수에 어떻한 값을 넣는 방법을 알아보죠.

방법은 간단 합니다.


           MOV       FLD1, AL


이와 같이 하여 주면 FLD1이라고 잡혀져 있는 메모리에 AL레지스터의 값을 넣어주게

됩니다. 다시 말해 메모리의 어떻한 부분 ( FLD1 이라고 이름지어진 ) 에 AL레지스터

값을 넣어 주게 됩니다.

그래서 다음에 FLD1을 참조하게 되면 위에서 넣어준 AL 레지스터의 값이 그대로 오게

되는 효과를 보게 됩니다.


           FLD1      DB      7 DUP (0)


위와 같이 선언되어진 변수가 있다고 칩시다.

그럼 메모리에는


  00 00 00 00 00 00 00


위와 같은 장소가 잡히게 됩니다.

이런 경우 이 변수는 배열과 같은 효과를 본다고 했습니다.

그럼 이 장소에 다음과 같은 값들을 넣는다면,


     FLD1      : 'A'

     FLD1 + 1  : 'B'

     FLD1 + 2  : 'C'

     FLD1 + 5  : 'D'


위와 같은 값들을 넣는다면 메모리의 구조는


  41 42 43 00 00 44 00


이렇게 들어 가겠죠?

이를 직접 어셈블리어 명령어로 하면


      LEA      BX,FLD1

      MOV      [BX],'A'

      MOV      [BX + 1], 'B'

      MOV      [BX + 2], 'C'

      MOV      [BX + 5], 'D'


위와 같이 하여 주면 우리가 원하는 결과를 얻을 수 있습니다.

여기서 하나 눈여겨 볼 것은

주소를 지정하는 방식에서 '[]'안에 직접적인 연산 명령을 줄 수도 있다는 점입니다

즉 '[BX + 5]' 와 같이 직접 그 안에 '+' 라는 연산 명령을 줄 수도 있다는 사실이죠.


마지막으로 다음과 같은 경우를 보죠.


      FLD1DB      DB      ?

      FLD2DB      DB      43


      MOV      FLD1DB, FLD2DB


이런 경우는 여지없이 꽝~~~ 'ERROR'메시지를 출력합니다.

그 이유는


      MOV      [105], [106]


위와 같은 명령어는 사용이 불가능 하기 때문 입니다.

이와 같은 맥락에서 그런 명령어는 사용이
   합니다.

그럼 위와 같은 결과를 얻기 위해서는 어떻게 해야 할까요?

그것은 다음과 같이 하면 됩니다.


      MOV      AL,FLD1DB

      MOV      FLD2DB,AL


위와 같이 하여 주면 원하는 결과를 얻을 수 있습니다.




에고...역시나 버벅...헐~

사실 주소지정 방식에 대해서 모두 설명 한 것은 아닙니다.

그 이유는 일단은 전에 올린 프로그램 소스를 분석하고, 이해하기 위해서 이기 때문에

분석 이외의 내용에 대해서는 나중에 따로 조금씩 추가로 설명을 해 드리겠습니다

앞으로의 설명을 모두 이런 맥락에서 해 나가겠습니다.


그럼 다음에는 여러개의 프로시져를 처리하고, 그 때의 스택의 변화등을 살펴 보겠

습니다.




 [18] 제목 : [초급-보충] 프로시져 및 스텍의 변화에 대해...
 올린이 : 까망벌레(정태식  )    94/11/24 00:09    읽음 : 326  관련자료 없음

음....이번에는 여러개의 프로시져에 대한 설명과 그에 따른 스텍의 변화 등을

설명 드리겠습니다.


일단은 프로시져에 대해 설명을 드리겠습니다.

음...프로시져란, C언어 에서의 '함수'와 같은 기능을 한다고 전에 말씀 드렸습니다.

즉, 어떠한 특수한 기능을 하는 명령어의 집합이라고 할 수 있죠.

다음 간단한 예제를 보이겠습니다.




    .MODEL SMALL

    .CODE

   

    ORG 100H


START      PROC    NEAR 

    MOV    DL,41H

    MOV    AH,02H

    INT    21H

START      ENDP

    END


위와 같은 프로그램을 하나 생각해 봅시다.

이 프로그램은 문자 'A'를 하나 출력하여 주는 프로그램 입니다.

또 다른 프로그램을 하나 보죠.



    .MODEL SMALL

    .CODE

   

    ORG 100H


START      PROC    NEAR 

    MOV    DL,41H

    CALL   PRINT_CHAR

    INT    20H

START      ENDP


PRINT_CHAR PROC    NEAR

    PUSH   AX

    MOV    AH,02H

    INT    21H

    POP    AX

    RET

PRINT_CHAR ENDP

    END


앞에것 보단 좀 길죠?

하지만 이 역시 위와 똑같은 결과, 즉 문자 'A'를 하나 출력하여 주는 프로그램 입니다.

그럼 이 두 프로그램의 차이는 무엇일까요?

위에것은 바로 프로시져를 사용하지 않고 최대한 짧고 간결하게 문자 'A'를 찍어주는

프로그램 입니다. 그리고 그 다음 후자의 것은 이와 같은 기능을 '프로시져'라는 것을

사용하여 문자를 찍어 주는 프로그램 입니다.


( 앞으로 전자의 프로그램을 1번 프로그램, 후자를 2번 프로그램 이라고 부르겠습니다.)


여기서 보듯 프로시져를 이용한 프로그래밍이 훨씬 더 길고 보기에 복잡해 보입니다.

그래서 구지 프로시져를 사용할 필요성이 있을까...라는 생각을 할 수도 있습니다.

하지만, 프로시져란 기능은 생각과는 달리 아주 막강한 기능을 발휘하여 주는

아주 편리한 것입니다.


2번 프로그램을 한번 보죠.


    MOV    DL,41


이것은 문자의 아스키 코드 값을 DL 레지스터에 넣어주는 것입니다.

그 이유는 프로세서가 DL 레지스터에 있는 아스키 코드값 ( 여기서는 16진수 41이고,

이 값을 문자로 하면 대문자 'A'가 됩니다.)을 보구서 여기에 해당하는 문자를 찍게

하기 위해 넣어주게 됩니다.


    CALL    PRINT_CHAR


이것에 의해 DL레지스터에 들어있는 값을 보고서 그 값에 해당하는 아스키 코드 문자

를 하나 찍어 주게 됩니다.


( 프로시져 : 흐~ 혹시 2번 프로그램에서 어떤게 프로시져인지 모르시는 분을 위해..

  2번 프로그램에서


      PRINT_CHAR    PROC    NEAR

      .....


      PRINT_CHAR    ENDP


  이 부분으로 싸여 있는 부분이 하나의 프로시져를 나타내는 것입니다.

  그러니깐 이 프로시져를 'PRINT_CHAR 프로시져' 라고 말 할 수 있는 것입니다.)


CALL을 사용하여 프로시져를 호출하게 되면 프로그램의 흐름은 바로 호출되어진

프로시져 내로 옮겨가게 되는 것입니다.

2번 프로그램을 한번 전체적으로 실행되어지는 순서를 보죠.


      MOV    DL,41H

     

      PUSH   AX       --;

      MOV    AL,02H     ;---> 이 부분이 바로 PRINT_CHAR이라는 이름의 프로시져

      INT    21H        ;     부분이 됩니다.

      POP    AX       --;     ( CALL PRINT_CHAR 에 의해 실행되는 부분입니다. )


      INT    20H


이를 보면 알 수 있듯이 PUSH,POP 을 제외하고는 1번 프로그램이랑 완전히 같은 순서로

실행이 되어진다는 것을 알 수 있습니다.


PRINT_CHAR 프로시져는 DL레지스터에 들어 있는 문자를 출력하여 주는 기능만을 하는

것입니다. 즉, 문자를 하나 찍는 명령어를 PRINT_CHAR이라는 이름으로 프로그래머가

직접 만들어 주는 경우와 같습니다.

MOV 라는 것이 어떠한 값을 옮겨 주는 것과 같이 PRINT_CHAR라는 명령어로 문자를

하나 출력하여 주는 명령어를 만들었다고 할 수 있습니다.

단지 이 명령어를 실행하기 위해서는 CALL이라는 명령어를 앞에 해 주어야 한다는

차이 뿐이라고 생각하시면 되겠지요.


그럼 이 같이 프로시져를 만들어서 사용하면 무슨 잇점이 있을까요?

만약 메인 프로그램 ( 2번 프로그램에서는 'START    PROC    NEAR' 부분으로 되어있는

곳이 됩니다. ) 에서 하나의 문자를 출력하는 기능을 여러군데에서 필요로 하게

된다면, 원래는 2줄의 명령문 집합을 하나의 명령으로 줄일 수 있게 됩니다.

다시 말해, 만약 프로그램에서 이와 같은 기능을 10군데에서 필요로 하게 된다면,

1번 프로그램과 같이 프로시져 없이 그냥 프로그래밍을 한다면 20라인이 더 추가되는

반면, 2번프로그램과 같이 프로시져를 사용하게 되면, 10라인으로 줄일 수 있게 됩니다.


    MOV    AH,02H

    INT    21H


이과 같이 두줄의 명령문을 필요할 때 마다 넣어주어야 하는 반면,


    CALL   PRINT_CHAR


이 같이 단 한줄의 명령문으로 똑 같은 기능을 기대할 수 있게 되는 것입니다.



이와 같이 생각한다면, 만약 어떠한 특수한 기능을 하는 명령어의 집합

 ( 예를 들어 진수를 변환하여 준다던가, 키보드로 부터 어떠한 문자를 하나 입력

   받는 기능이라든가.. ) 이 10라인 이상이 넘어가게 된다면, 그리고 이와 같은

필요로 하는 기능이 프로그램 여기저기 에서 자주 필요로 하게 된다면, 프로시져를

사용하지 않고서는 너무나 길어지고, 메모리 소비 또한 매우 많아 지게 됩니다.


위의 프로그램 예에서 'COMPUTER'라는 문자열을 출력한다고 하면,

1번 프로그램 에서는


    MOV    DL,43H

    MOV    AH,02H

    INT    21H

   

    MOV    DL,4FH

    MOV    AH,02H

    INT    21H


    MOV    DL,4EH

    MOV    AH,02H

    INT    21H

    ......등등


인 반면,

2번 프로그램 에서는


    MOV    DL,43H

    CALL   PRINT_CHAR

   

    MOV    DL,4FH

    CALL   PRINT_CHAR

  

    MOV    DL,4EH

    CALL   PRINT_CHAR

    ......등등


이 같이 꽤 간결하고, 알아 보기 쉽게 변환이 됩니다.

PRINT_CHAR 이라는 프로시져가 단순히


    MOV    AH,02H

    INT    21H

 

의 단 두줄이기 때문에 1번 프로그램과 같이 해도 해 볼만 하겠지만, 만약 50라인이

훨씬 넘어가는 프로시져라고 한다면 그 차이는 엄청나게 되는 것이지요.




그럼 이제 여기에 스텍이라는 개념을 끼워 넣어 보겠습니다.

스텍은 push, pop등에 의해 스텍에 넣거나 빼 올 수도 있습니다.

하지만 프로시져를 호출할 때나 인터럽트 ( 다음에 설명 ) 호출시에 프로세서가

스텍을 조작하게 됩니다.


이 프로그램을 실행 가능 화일로 만들어서 메모리에 읽어온 후에 이를 실행하게 되면,

우선은 IP 레지스터에 실행되어질 오프셋의 메모리 주소 값이 들어가게 됩니다.

전에도 말씀 드렸다 시피, 프로세서는 항상 IP 레지스터를 조사하여 다음에 실행되어질

프로그램의 내용이 있는 메모리의 오프셋 주소를 알아내어 찾아 가서 실행을 한다고

말씀 드렸습니다. 그럼 위의 프로그램 2번과 같은 경우 이것이 메모리로 읽어 들여

실행될 때의 메모리에 읽어들인 모양을 보겠습니다.


0A8B:0200 B241               MOV      DL,41

0A8B:0202 E80200             CALL     0207

0A8B:0205 CD20               INT      20

0A8B:0207 50                 PUSH     AX

0A8B:0208 B402               MOV      AH,02

0A8B:020A CD21               INT      21

0A8B:020C 58                 POP      AX

0A8B:020D C3                 RET


위와 같은 형태로 메모리에 로드 되어 집니다.

( 확인을 위해서는 DEBUG [FILENAME] 으로 읽어서 디버그의 프롬프트 '-'가 나왔을떠

  'U 200' 명령으로 확인을 해 볼 수 있습니다. )


우선 이것을 실행하기 전에 IP 레지스터는 200 을 가리키고 있습니다. 즉, 프로그램이

시작되어질 위치가 오프셋 200번지라는 뜻이 됩니다.

그리고 실행을 하면 우선은 첫번째 명령인 'MOV      DL,41'을 실행하게 됩니다.

이것을 실행한 후에 IP레지스터의 값은 그 다음 실행 명령이 들어 있는 메모리의

 오프셋 번지인 202 번지를 가리키게 됩니다.


그 다음 'CALL    0207' 이 것이 매우 중요한데, 이것은 바로


 'CALL      PRINT_CHAR' 부분이 컴파일 된 상태가 됩니다. 즉, 프로시져를 호출하게

되는데, 그 호출되는 프로시져의 이름 대신에 프로세서가 알 수 있는 메모리의 번지를

직접 가리키도록 변환이 되어 졌습니다.

이것을 실행하게 되면, IP레지스터는 바로 '207'번지를 가리키게 됩니다.

즉, 프로그램의 흐름을 중간에서 바꾸어 버리게 되는 것으로, 207번지로 바로 점프를

하게 되는 것입니다.


그럼 프로세서는 역시 IP레지스터를 보고서, 실행될 위치를 찾고서 207번지에 있는

'PUSH     AX'를 실행하게 됩니다. 그럼 역시 IP레지스터에는 그 다음 명령이 들어있는

'208'번지를 가리키게 됩니다.


이런 식으로 '20C'번지 까지 실행을 하면, IP레지스터는 그 다음 명령인 'RET'명령이

들어있는 '20D'를 가리키게 됩니다. 이 'RET'명령은 아까 'CALL    0207'로 프로시져를

호출하는 명령 그 다음 명령 부분으로 돌아가도록 하는 명령 입니다.

즉, 205번지인 'INT    20'으로 돌아가도록 하는 명령이 됩니다. 그럼 프로그램의

흐름은 아까 202번지 까지 실행이 되어 지다가 'CALL' 명령에 의해 207번지로 건너 뛴

다음, 'RET'명령에 의해 위치상으로 'CALL'명령 다음에 있는 명령으로 순서를 옮겨 가게

됩니다.


그럼 한번 생각을 해 봅시다.

CALL명령으로 207번지로 점프를 하게 됴습니다. 그 상태에서 'RET'명령을 만나게 되면,

'CALL'다음에 있는 'INT    20'이 있는 곳으로 IP레지스터를 바꾸어 주어야 프로그램의

흐름이 원래 상태로 돌아오게 됩니다. 즉,'RET'명령을 만나게 되면 IP레지스터에 205를

다시 넣어 주어야만 합니다.

다시 좀 더 자세히...

아래에 프로그램이 실행되어 짐에 따라 IP레지스터의 변화를 보여 보겠습니다.



         
                                             [200]   

0A8B:0200 B241               MOV      DL,41             [202]    (1)

0A8B:0202 E80200             CALL     0207              [207]    (2)

0A8B:0205 CD20               INT      20                         (8) 

0A8B:0207 50                 PUSH     AX                [208]    (3)

0A8B:0208 B402               MOV      AH,02             [20A]    (4)

0A8B:020A CD21               INT      21                [20C]    (5)

0A8B:020C 58                 POP      AX                [20D]    (6)

0A8B:020D C3                 RET                        [205]    (7)


'[]'안의 숫자가 바로 IP레지스터의 값을 나타냅니다.

여기서는  'MOV    DL,41'을 실행하면 실행한 다음 IP레지스터에 '202'가 들어가게

된다는 뜻입니다.

그리고 제일 오른쪽'()'안의 숫자는 순서대로 실행이 되어 지는 순서를 나타냅니다.


위에서 보면 'CALL' 명령을 실행 하면 IP 값이 207으로 변환이 됩니다. 만약 'CALL'

명령이 아니고 다른명령 ( 프로그램 실행 순서와 관계없는 명령들 )이 있었다고 한다면,

IP레지스터에는 205가 들어가는게 정상이지만, 여기서는 프로그램의 실행 순서를 바꾸게

되는 명령인 'CALL'이 있기 때문에 IP레지스터에는 '207'이 들어가게 됩니다.

그럼 프로세서는 'CALL'명령의 다음 실행 시킬 명령으로 'INT   20' 이 아닌

바로 207 번지에 있는 'PUSH    AX'를 실행하게 됩니다.

그 다음은 물론 ' MOV    AH,02'가 되겠죠.

그러다가 바로 'RET'명령을 만나면 다시 원래의 프로그램 실행 순서를 찾아게게 됩니다.

즉 순서가 뒤게뀌게 된 곳인 'CALL'이 있는곳, 그 다음의 위치인 205번지 'INT    20'을

실행하도록 하기 위해 IP레지스터에 205라는 값을 넣게 됩니다.


그럼 여기서 프로세서는 'RET'명령을 만난 후 돌아갈 위치를 어떻게 올바로 찾을 수

있는지...프로그램 상으로는 어떻한 부분에서도 직접 그 IP레지스터를 어느 다른 곳에

보관을 시키는 명령은 없습니다.

하지만 'RET'명령을 만나면 프로세서는 정확히 원래의 위치로 찾아가게 됩니다.

즉, 'CALL'명령을 만날 때의 그 위치( IP 레지스터의 값 )를 어디 다른 안전한

곳에 보관을 하였다가 'RET'명령을 만나게 되면, 다시 꺼내어 와서 IP레지스터에 다시

넣어주게 되는 것입니다.

그럼 역시 일시적으로 IP레지스터의 값을 보관하는 곳은 메모리가 되겠죠..

그 메모리 영역을 바로 스텍이라고 부릅니다.

스텍의 구조는 전에 설명을 드렸었죠.


CALL    207 을 실행하게 되면 프로세서는 스스로 스텍에 원래의 그 다음 오프셋

값인 205를 보관하게 됩니다. 즉


    PUSH      IP


를 스스로 하게 되는 것입니다.

그리고 다시 RET명령을 만나게 되면 스텍에 프쉬하였던 IP레지스터의 값을 다시 팝을

하여서 IP레지스터에 넣어주게 됩니다.


    POP       IP


와 같은 기능을 스스로 하게 됩니다.



그럼 프로그램 소스와 스텍 세그먼트의 변화등을 직접 살펴 보겠습니다.

보통 프로그램을 실행하게 되면 최초의 스텍 포인터 값은 'FFFF'번지를 가리키게 됩니다.

즉 COM화일인 경우 하나의 세그먼트만을 사용하게 되는데 그 세그먼트 내에서 가장

최 상위 번지인 'FFFF'번지를 스텍 포인터가 가리키게 됩니다.

그래서 제일 처음 스텍에 푸쉬하는 값은 메모리 번지 'FFFF'에 저장이 되어 집니다.

그 다음은 물론 스텍 포인터가 'FFFD'를 가리키는건 아시는 것이고요...


프로그램을 실행하게 되면, 프로세서는 일단 스텍에 '0000'이라는 값을 푸쉬하게

됩니다. 이것은 프로그래머가 프로그래밍을 하지 않아도 프로세서가 스스로 하는 작업

입니다. 이 이유는 인터럽트를 설명드릴때 간단히 설명을 드리겠습니다.

그래서 그럼 '0000'이라는 값이 푸쉬된 상태에서 프로그래머가 프로그래밍한 프로그램을

실행하게 됩니다. 하나의 워드값이 푸쉬 되었기 때문에 SP( 스텍 포인터 )의 값은

'FFFD' 를 가지게 됩니다.

그럼 프로그램 소스를 보면서...


                                                        [FFFD]   

0A8B:0200 B241               MOV      DL,41             [FFFD]    (1)

0A8B:0202 E80200             CALL     0207              [FFFB]    (2)

0A8B:0205 CD20               INT      20                [FFFD]    (8) 

0A8B:0207 50                 PUSH     AX                [FFF9]    (3)

0A8B:0208 B402               MOV      AH,02             [FFF9]    (4)

0A8B:020A CD21               INT      21                [FFF9]    (5)

0A8B:020C 58                 POP      AX                [FFFB]    (6)

0A8B:020D C3                 RET                        [FFFD]    (7)



그림으로 마지막 정리를 해 보겠습니다.

 


        (1)         (2)         (3)         (4)         (5)


  SP =  FFFD        FFFB        FFF9        FFFB        FFFD

      

      ;------;    ;------;    ;------;    ;------;    ;------;

 FFFF ; 0000 ;    ; 0000 ;    ; 0000 ;    ; 0000 ;    ; 0000 ;

      ;------;    ;------;    ;------;    ;------;    ;------;

 FFFD ;      ;    ; 0205 ;    ; 0205 ;    ; 0205 ;    ;      ;

      ;------;    ;------;    ;------;    ;------;    ;------;

 FFFB ;      ;    ;      ;    ;  AX  ;    ;      ;    ;      ;

      ;------;    ;------;    ;------;    ;------;    ;------;

 FFF9 ;      ;    ;      ;    ;      ;    ;      ;    ;      ;

      ;------;    ;------;    ;------;    ;------;    ;------;

 FFF7 ;      ;    ;      ;    ;      ;    ;      ;    ;      ;


     

(1)

   이것은 프로그램이 시작 되기 전에 스텍에 0000을 푸쉬한 상태를 나타냅니다.


(2)

   이것은 CALL명령을 한 후에 그 다음 메모리의 오프셋 값인 205 가 스텍에

   푸쉬된 상태를 나타냅니다.


(3) 

   이것은 'PUSH    AX'라는 명령에 의해 AX레지스터의 값이 스텍에 푸쉬되어진

   상태를 나타내고 있습니다.


(4)

   이것은 'POP    AX'에 의해 스텍에 들어있던 AX레지스터의 값을 다시 AX레지스터에

   넣어 주는 명령을 실행 한 후를 나타냅니다.


(5)

   이것은 'RET'명령에 의해 IP레지스터에 'CALL'명령 다음으로 실행되어질 프로그램의

   메모리 주소값을 다시 IP레지스터에 넣어준 후 그 상태가 됩니다.







참고 : 'PUSH    AX',  'POP     AX' 라는 부분에 대해서...


    이것은 프로그램을 실행하기 전에 미리 AX레지스터의 값을 보관하기 위해

    하여 주는 것입니다. 즉 AX레지스터의 값이 변하는 것을 방지하기 위해서지요.

    다음과 같은 경우를 보죠.




    MOV      AX,0123

    CALL     EXT1

    MOV      CX,AX

    ......


EXT1    PROC    NEAR

    MOV      AX,0206

    INT      21H

    RET

EXT1    ENDP

    ....


위와 같은 프로그램이 있다고 한다면, CALL명령에 의해 EXT1이라는 프로시져가 호출 되었

습니다. 여기서 하나, CALL명령이 실행되기 전의 AX레지스터의 값은 0123으로 되어

있음을 알 수 있습니다. ( MOV      AX,0123 에 의해서.. )

그럼 프로시저를 호출하게 되면 바로 'MOV      AX,0206'이라는 명령어를 실행하게

됩니다. 그럼 원래 AX레지스터에 있던 값인 '0123'이 날아가 버리고 다시 새로운

값인 '0206'이라는 값이 들어가게 됩니다.

그 후 프로시져를 끈낸 후 다시 본 프로그램으로 돌아와서 다음 명령을 실행하게

되는데, 여기서는 'MOV      CX,AX'라는 것이 됩니다. 그럼 과연 CX레지스터에는

어떻한 값이 들어가겠습니까? 원래 우리가 원했던 값은 '0123'이었는데 엉뚱한 값인

'0206'이 들어가 버리게 됩니다.

즉 프로시져 호출을 해서 그 안에서 AX레지스터를 사용하였기 때문에 원래의 AX레지스터

안에 가지고 있던 값을 잃어 버리게 되는 것입니다. 이것을 방지 하기 위해서

PUSH, POP 으로 레지스터의 값을 잃어 버리는 일 없이 보관을 할 수 있습니다.

수정된 프로그램을 보죠.



    MOV      AX,0123

    CALL     EXT1

    MOV      CX,AX

    ......


EXT1    PROC    NEAR

    PUSH     AX                  <--- AX 레지스터의 값을 스텍에 저장

    MOV      AX,0206

    INT      21H

    POP      AX                  <--- 스텍에 저장된 원래의 AX레지스터의 값을

    RET                               다시 AX레지스터로 복원

EXT1    ENDP

    ....


이와 같이 하면 CX레지스터에는 우리가 원했던 '0123'이라는 값이 들어갈 수 있게

됩니다.




음....솔직히 제가 다시 읽어 보아도 되게 답답 하네요...

좀 더 명쾌하게 설명을 하고 싶었는데...헐헐~

어셈을 배우시고자 하는 분들에게 이 부분은 좀 상당부분 이해하기 힘들거 같아

보입니다. 근데 솔직히 이거 읽으시는 분들이 과연 어떤 부분이 이해가 잘 안가시는지

제가 모르기 때문에, 정말 이해가 한 군데라도 안가는 부분이 있다면 질문 하세요...

자세히 다시 설명을 드리겠습니다.



그럼 다음에는 '프로그램 세엣'의 분석을 하기 위해 필요한 명령어 설명을 간단히

드린 후 세번째 프로그램 소스를 자세히 분석해 보도록 하겠습니다.

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

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

+ Recent posts