에러 검출

 

    데이터가 전송될 때 전송되는 데이터는 근본적으로 에러가 발생한다. 따라서 정확한 데이터 전송을 위해서는 에러를 검출하고 교정하는 과정이 필수적으로 수반되어야하며 이것은 전송하고자 하는 데이터에 얼마간의 정보를 추가로 전송하여 에러를 교정하며 가장 간단한 방법으로는 parity bit가 있다. Packet을 송신하는 경우에도 패킷의 에러를 보완하는 수단이 필요한데 대표적인 방법으로는 체크섬(checksum)CRC(Cyclic Redundancy Checks)가 있다.

 

(a) parity bit  

    Parity bit는 전송하고자하는 데이터의 각 문자에 1 비트를 더하여 전송하는 방법으로 2가지 종류의 parity bit가 있다. even parity는 전체 bit에서 1의 개수가 짝수가 되도록 parity bit을 정하는 것인데 예를 들어 만약 데이터 bit에서 1의 개수가 홀수이면 parity bit 1로 정한다. Odd parity는 전체 bit에서 1의 개수가 홀수가 되도록 parity bit을 정하는 방법이다. 이렇게 parity bit를 정하여 데이터를 송신하면 수신 측에서는 수신된 데이터의 전체 bit를 계산하여 parity bit를 다시 계산할 수 있음으로 데이터에 에러 발생 여부를 알 수 있다. 그러나 parity bit는 에러 발생 여부만 알 수 있지 에러를 수정할 수는 없다.

 

(b) Checksum

    Checksum packet 속의 하나의 field로 구성하며 데이터를 이진수(binary number)의 연속으로 간주하여 그 이진수의 합을 계산하는 방법이다. 예를 들어 16 bits checksum을 사용하는 경우 데이터를 16 bits씩 나누어 16진수로 표현한 다음 그 합을 계산하여 16으로 나눈 나머지에 해당되는 값 checksum으로 사용하는 방법이다. 이 방법의 장점은 우선 checksum의 크기가 작아 checksum 1개로 packet 전체를 조사할 수 있음으로 오버헤드를 줄일 수 있고 덧셈 연산만을 수행함으로 계산이 용이하다.  

 

사용자 삽입 이미지

 

 

 

 

 

 

 

    그러나 checksum은 발생 가능한 모든 에러를 검출할 수 없다는 단점이 있다. 예를 들어 0001001000110001로 표현되는 데이터를 송신하는 도중 0011000000010011로 변형된 경우, 두 경우의 checksum 4 bits씩 나누어 계산하면 같은 값을 가짐으로 에러를 검출할 수 없다.

 

사용자 삽입 이미지
       

 

 

 

 

 

 

 

 

(c) CRC

    에러 검출의 일반적인 과제는 packet에 보다 적은 양의 정보를 추가하여 보다 많은 에러를 검출하고자 하는 것으로 checksum 보다 많이 사용되는 방법으로 CRC가 있다. CRC shift register라는 전기회로를 사용하여 각 비트에 "Exclusive OR" 연산을 위한 게이트를 설치하여 CRC 값을 구하는 것으로 계산이 빠르다는 장점이 있다. 실제 CRC 구현은 16-bit 혹은 32-bit CRC를 사용한다.

 

사용자 삽입 이미지

Posted by 응이

댓글을 달아 주세요


컴파일을 잘하기 위해서는 단순히 컴파일러의 옵션 몇 개를 더 안다고 잘하게 되는 것은 아니다. 컴파일을 잘하기 위해서는 컴파일 과정이 어떻게 이루어지는가에 대해서 반드시 이해해야 한다. 필자가 이번 장에서 컴파일 과정을 알려주고자 하는 이유는 컴파일 과정을 이해하면 C 소스가 컴파일되지 않을 때 문제 해결 능력을 키울 수 있고 원하는 바이너리를 쉽게 만들 수가 있기 때문이다.

필자는 이 컴파일 과정이 이 장 전체에 있어서 가장 중요한 장이라고 생각한다. 다소 지루 할지도 모르겠지만 컴파일 과정에 대해서 이해하고 나면 여러분의 프로그래밍 실력이 한 단계 성숙된 것을 느낄 수 있을 거라고 확신한다. 먼저 앞으로 사용할 gcc 컴파일러에 대해서 설명하겠다.

gcc는 GNU에서 만든 C 컴파일러다. 수많은 옵션만큼이나 기능이 풍부하여 원하는 바이너리를 쉽게 만들 수 있기 때문에 응용 프로그램뿐만 아니라 운영체제, 부트 로더 등도 다른 컴파일러에 비해 쉽게 만들 수 있다.

또한 gcc는 현존하는 어떤 컴파일러보다 많은 CPU 아키텍처를 지원한다. ARM, DEC, AVR, i386, PPC, SPARC, M68XX 등 수 없이 많은 아키텍처를 지원하기 때문에 원하는 어떤 CPU의 크로스 컴파일러도 쉽게 찾아서 사용할 수 있다.

gcc가 C 컴파일러라고 해서 C 소스만을 컴파일할 수 있는 것은 아니다. C++ 소스도 컴파일할 수 있고 심지어 Fortran, ada, Objective-C도 컴파일할 수 있다. 그것은 gcc의 구조적 특성 때문에 가능한 일이다. 그럼 gcc는 어떠한 구조적 특성 때문에 여러 언어를 컴파일 가능한 것일까?

흔히들 gcc 컴파일러가 C 컴파일러라고 알고 있겠지만 정답을 이야기하자면 엄밀한 의미에서 /usr/bin/gcc는 C 컴파일러가 아니다. 방금 위에서 GNU에서 만든 C 컴파일러라고 말해 놓고 다시 C 컴파일러가 아니라고 말하니 필자가 가증스럽겠지만 엄연한 사실이다.

/usr/bin/gcc는 내부적으로 전처리기인 cpp0을 호출하여 전처리 과정을 수행하고, 진짜 C 컴파일러인 cc1을 호출해서 컴파일한 후, 어셈블러인 as를 호출해서 오브젝트 코드로 만들고, 마지막으로 링커인 ld 또는 collect2를 호출해서 오브젝트 코드를 링크하여 실행 파일로 만들어낸다.

즉 gcc는 실제 컴파일 과정을 담담하는 것이 아니라 전처리기와 C 컴파일러, 어셈블러, 링커를 각각 호출하는 역할만을 담당하는 것이다. 그런 의미에서 볼 때 /usr/bin/gcc는 C 컴파일러라고 볼 수 없고 진짜 C 컴파일러를 말하자면 /usr/lib/gcc-lib/i386-redhat-linux/3.2.2/cc1을 들 수 있다.

/usr/lib/gcc-lib/i386-redhat-linux/3.2.2 디렉토리에 가보면 여러 실행 파일들이 있는데 각 실행 파일들의 역할은 다음과 같다.

• cpp0: 전처리기
• cc1: C 컴파일러
• cc1obj: Objective-C 컴파일러
• cc1plus: C++ 컴파일러
• f771: 포트란 컴파일러
• jc1: java 컴파일러
• collect2: 링크

아래 그림은 like.c 소스 파일이 있을 때 gcc가 like.c 파일을 어떤 과정을 통해 컴파일하는지에 대해서 그림으로 나타낸 모습이다.




[그림 3-5] gcc 컴파일 과정

[그림 3-5]에서 gcc는 cpp0(C PreProcesser)를 호출하여 전처리 과정을 거쳐 like.c 파일을 like.i 파일로 만든다. like.i 파일은 C 컴파일러인 cc1에 의하여 어셈블리 코드인 like.s로 컴파일되고 이후 like.s는 as 어셈블러에 의해 어셈블 과정을 거쳐 like.o 오브젝트 파일로 만들어진다.

like.o 파일은 다시 링크인 collect2가 libc.a와 같은 표준 C 라이브러리와 링크하여 최종적으로 실행 파일인 like 파일을 만들게 된다. gcc에 의한 C 소스 컴파일 과정은 크게 이와 같은 방식으로 이루어진다. 만약 like.c가 C 소스가 아니라 like.cc와 같이 C++ 소스였다면 cpp0에 의해 생성되는 전처리 과정 파일이 like.i가 아니라 like.ii이고 cc1 C 컴파일러 대신 cc1plus C++ 컴파일러가 사용되게 된다.

독자들의 이해를 돕기 위해 실제로 이러한 컴파일 과정이 일어나는지 확인해보겠다. 먼저 기존에 사용하던 like.c 파일이 아래와 같이 있다.

[예제 3-3] like.c 소스 파일 내용

#include

int main()
{
        printf("I like you!\n");
        return 0;
}


그리고 아래 명령어로 컴파일해본다.

gcc -v --save-temps -o like like.c


위 명령에서 -v 옵션은 컴파일되는 과정을 화면으로 출력하라는 옵션이고 --save-temps 옵션은 컴파일 과정에서 발생되는 중간 파일을 지우지 않고 저장하라는 명령이다.

gcc는 컴파일 과정 시 생성되는 전처리 파일(like.i)과 어셈블리 파일(like.s)을 /tmp 디렉토리에 생성하고 지워 버리는데 --save-temps 옵션을 주게 되면 중간에 생성한 파일을 지우지 않고 현재 디렉토리에 저장하게 된다. 위 명령을 내려보면 [그림 3-6]과 같은 메시지가 출력됨을 확인할 수 있다.



[그림 3-6] like.c 컴파일 메세지

[그림 3-6]에서 확인할 수 있듯이 gcc는 내부적으로 cpp0와 cc1, as, collect2를 각각 호출함을 알 수 있다. 그리고 --save-temps 옵션에 의해서 ls 명령을 내려보면 컴파일 중간 과정에서 생성된 like.i 파일과 like.s 파일이 보존되었음을 확인할 수 있다.


[그림 3-7] 컴파일 과정에서 생성된 like.i 파일과 like.s

like.i 파일과 like.s 파일을 각각 열어보면 like.i 파일에는 전처리 과정이 끝난 후 C 소스 파일이 있는 것을 알 수 있고 like.s 파일은 컴파일 과정을 거친 후의 어셈블리 파일이 있는 것을 확인할 수 있다.


[그림 3-8] 전처리 과정이 끝난 like.i 파일의 내용


[그림 3-9] 컴파일 과정이 끝난 like.s 파일의 내용

이러한 like.i 파일과 like.s 파일은 컴파일 중간 과정의 산물로써 컴파일러 오류로 인한 문제 또는 전처리 과정에서의 오류로 인한 문제가 발생했을 때 유용하게 사용할 수 있다. 예를 들면 다음과 같은 상황이다.

[예제 3-4] like.h 소스 파일 내용

struct my_struct {
        int a;
} my;

#define my      MY


[예제 3-5] like.c 소스 파일 내용

#include "like.h"

int main()
{
        my.a = 0;
        printf("I like you! my.a = %d\n", my.a);
        return 0;
}


독자들은 위와 같은 코드가 왜 문제가 있는지 단번에 알 수 있을 것이다. like.h에서 my_struct 타입의 구조체 my를 정의했는데 바로 밑에서 define으로 인해 앞으로 my로 쓰이는 모든 문자는 MY로 대체하게 되어있다. 그래서 like.c에서 my를 사용하게 되면 전처리 과정 내에서 MY로 치환되어 버리기 때문에 MY가 정의되지 않아 컴파일 오류가 발생하게 된다.

그럼 위 예제들을 컴파일하게 되면 어떠한 오류 메시지가 출력될까, 똑똑한 gcc가 알아서 위 예제의 어떤 부분에 문제가 있다고 알려줄 수 있을까? 다음은 컴파일 메시지다.


[그림 3-10] gcc 오류 메시지

당연하게도 gcc는 MY가 정의되지 않았다는 오류 메시지를 보여주었다. C 소스만 놓고 볼 때 위 소스에서 대문자 MY가 나올 일은 없다. 물론 우리는 위 소스의 어떤 부분이 문제가 있는지 알고 있는 상태고 헤더 파일로 like.h 밖에 include하지 않았기 때문에 문제를 모르더라도 한번에 찾을 수 있을 것이다. 그렇지만 이런 상황을 한번 가정해보자. 자신이 직접 짜지 않은 소스를 수정하여 컴파일하고자 하는데 그 소스 파일은 여러 헤더 파일을 include하고 있고 또한 include한 헤더 파일 내에서 다른 헤더 파일을 include하고 있다.

컴파일하는데 소스에서는 분명히 사용하지 않은 심볼이 정의되지 않았다는 오류 메시지가 출력된다. 가령 위와 같이 MY가 정의되지 않았다고 출력되는 것처럼 말이다.

어떻게 보면 정말 쉽게 찾기 어려운 오류가 될 수 있다. --save-temps 옵션을 모를 경우에 말이다. --save-temps 옵션을 사용하여 중간에 생성되는 파일을 저장하고 전처리 과정을 거치고 난 파일에서 확인해보면 우리는 어떤 부분에서 문제가 발생했는지 한번에 알 수 있다.
[그림 3-11]은 위 like.c 파일의 전처리 과정 산물인 like.i 파일의 내용이다.


[그림 3-11] like.i 파일의 내용

위와 같은 현상은 드물지만 가끔씩 발생하는 현상으로 한번 발생하면 정말 문제를 해결하기가 힘들다. 그러나 이제 독자들은 컴파일 과정이 어떤 철차로 진행되는지 알았을 것이고 그 부산물은 무엇인지 알았기 때문에 이러한 문제가 발생해도 문제를 찾는데 어려움이 없을 거라고 본다. like.s 파일은 cc1 C 컴파일러로 컴파일한 결과 파일이다. 이러한 like.s 어셈블리 파일은 as 어셈블러에 의해 어셈블되게 되면 like.s에 있는 어셈블리 명령대로 인스트럭션으로 변환되어 like.o 바이너리로 된다.

like.s와 같은 어셈블리 파일은 독자들이 어떤 프로그램을 작성하느냐에 따라서 매우 유용할 수 있고 한번도 쳐다보지 않을 수도 있다. 만약 여러분이 응용 프로그램을 제작한다면 중간 과정에서 생성된 어셈블리 파일은 C 소스 중간에 인라인 어셈블리를 사용하지 않은 한 거의 볼 필요가 없을 것이다. 그러나 독자들이 운영체제나 부트 로더와 같이 시스템 아키텍처의 특성을 많이 타는 프로그램을 제작하고자 한다면 거의 매번 컴파일 시마다 어셈블리 파일을 확인해야 할 것이다.

운영체제나 부트 로더의 제작에 있어서 컴파일러는 더 이상 우리들의 든든한 친구가 아니라 변덕쟁이 여자 친구가 되고 만다. 컴파일러가 만들어 내는 코드에 항상 관심을 가지고 주의를 기울여야 컴파일러가 만들어내는 예기치 못한 오류를 잡아낼 수 있다.


Posted by 응이

댓글을 달아 주세요

함수를 호출하게 되면 그 파라미터(인자)들을 전달해야 됩니다.

그러기 위해서 파라미터를 스택에 저장해서 전달하게 되는데

저장하는 순서는 항상 오른쪽에서 왼쪽으로 합니다.

그러니까 첫번째 파라미터가 항상 제일 마지막에 저장됩니다.

그렇게 함으로써 SP+6이 항상 첫번째 인자를 가르키도록 하는 것입니다. (어셈블리어 관점입니다.)

아무튼 차이점은 그 함수가 종료했을 때 스택에 저장된 파라미터를 누가 지우냐하는 문제가

남습니다. 가장 좋은 것은 함수가 지우는 것입니다.

그렇게 함으로써 좋은 점은 코드의 길이가 짧아진다는 것입니다.

(아니면 함수를 호출할 때마다 그 뒷부분에 스택에서 인자를 지우는 부분이 따라야 겠죠?)

이렇게 함수가 인자를 스택에서 지우는 방식을 stdcall이라고 합니다.

하지만 가변인자의 경우(예를 들어 sprintf처럼)에는 함수를 호출한 쪽에서 지우는 것이

안전합니다. 어떤 인자들을 저장했는지 호출한 쪽은 정확히 알지만 함수쪽에서 불분명합니다.

정확히 말하자면 사용자가 형변환등을 실수로 해서 함수가 다른 데이터형으로 인식하거나해서

잘못 지우면 어떤 결과가 날지 모르니까 호출한 쪽에서 지우니 편이 안전하다는 거죠.

정리를 하면 stdcall은 함수가 인자를 스택에서 지우는 형태

cdecl은 호출한 쪽에서 인자를 스택에서 지우는 형태로

stdcall은 일반적인 Windows API, CALLBACK 함수에 사용되고

cdecl은 Windows API 중 가변인자일 경우에 사용됩니다.

그리고 참고로 보통 C++에서 위의 것을 다 생략하면 thiscall이 사용되는데

(thiscall은 키워드가 아니니 사용하지 마세요.)

방법은 cdecl과 동일하고 차이점은 추가로 this 포인터가 제일 마지막에 스택에 저장된다는

것입니다.

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


함수의 호출 관례에 대해서 궁금해 하시는 분들이 많은것 같아
부족한 실력이지만 정리해서 올립니다..많은 도음이 됐음 하네요^^
우선,함수의 호출관례(function calling convention)란,
함수의 파라미터를 스택(stack)에 푸시(push)하는 순서,푸시하는쪽 및 이름 변화(name mangling)을 명시한 것입니다..

표로 정리 해보면..

변경자 푸시(push)순서 팝(pop)하는쪽 이름변화
_cdecl Right first Caller '_'prepended
_fastcall Left first Callee '@'prepended
_pascal Left first Callee Uppercase
_stdcall Right first Callee No change

Right first는 함수의 파라미터를 오른쪽에서 왼쪽으로 평가하여 스택에 푸시하는것을 나타냅니다.

Caller/Callee는 호출하는쪽 /호출 당하는쪽 에서 스택 동작을 하는것을 의미하구요..

이름 변화란.. 함수 이름 오버로딩..(이름이 같은 함수를 구분하기 위해서 컴파일러는 이름 장식(name mangling)작업을 하죠..) 에서 쓰이는데(어셈브러 소스를 보시면 알수 있습니다..)
c방식의 경우 명칭 앞에 언더스코어_가 붙는다는 애기죠.

Windows API(Application programning Interface)함수는 모두 WIN32방식(_stdcall)을 사용합니다..
이것은 파라미터를 팝하는 쪽이 Callee이기 때문이죠..(아마도 윈도우즈 운영체제에 이미 존재하는 함수들에 대해서 일관된 파라미터 팝 방식을 적용함으로써 얻어지는 최적화의 이점 때문인듯..)
만약 호출하는 쪽에서 파라미터를 팝해야 한다면 ,실행 코드를 생성할때 컴파일러는 이부분의 코드를 생성해야 되겠죠.. 컴파일러의 부담이 커지는것은 둘째치고(실행파일의 크기증가),운영체제의 관할 영역을 건드리는 보호차원에서 문제가 될수 있습니다..

모든 C++컴파일러에서 기본값은 cdecl 입니다..
콜백함수에 명시적으로 _stdcall 즉,WIN32혹은 WINAPI를 지정해야하는 이유죠...
(*참고)WIN32는 windows.h에 선언된 _stdcall의 매크로입니다.
WIN32에서 _pascal은 _stdcall로 대치되었습니다.

Windows응용프로그램의 시작함수인 WinMain()은 _stdcall이지만
WIN32콘솔 응용프로그램에서 시작함수인 main()은 여전히 _cdecl입니다.

호출관례에 대한 예를 들어보면..

#include <iostream.h>

void _pascal f (int i,int j) {
cout<< i << " " << j <<endl;
}
void _cdecl g(int i ,int j) {
cout<< i << " " << j <<endl;
}

void main() {
int i,j;

i=1; j=2;
f(i==j,i=j) ;//왼쪽에서 오른쪽으로 평가(left to right evaluation)
g(i==j,i=j);//오른쪽에서 왼쪽으로 평가(right to left evaluation)
}

실행 결과는 0,2
1,2

pascal방식은 파라미터 푸시를 왼쪽에서 오른쪽으로 한다는 사실을 주의해야겠죠..
f(i==j,i=j);에서 i==j가 먼저 평가되어 거짓이 되어서 "0"이 출력 되는것입니다..

windows에서 더이상 pascal방식은 존재 하지 않습니다._stdcall로 대치되었습니다.

Posted by 응이

댓글을 달아 주세요

Visual C++ 컴파일을 위한 실행 파일 cl.exe 를 사용하면 c 소스코드를 어셈블리로 변경할 수 있습니다.

c:\> cl.exe /FAsc main.c

main.cod 파일이 생성됨




1.컴파일
ml /c /coff /Zi 파일명.asm

/coff (common object file format :파일 형태)
/Zi


2.링킹
link /debug /subsystem:console /entry:start /out:파일이름.exe  파일이름.obj io.obj kernel32.lib

/subsystem:console (콘솔 창 형태의 출력을 하는 프로그램 )
/entry:start (프로그램 시작지점을 지정한다.)
/out:파일이름.exe (실행파일 명을 지정)
파일이름.obj io.obj kernel32.lib (오브젝트 파일을 링킹 시킨다.)


3.실행

 

Posted by 응이

댓글을 달아 주세요

===  C에서 함수 포인터 ===



==============================================================

#include <stdio.h>

#define IN
#define OUT

void Func(int);

typedef void (*PF)(int);

int main()
{
  PF PF_Func;

  PF_Func = Func;

  (*PF_Func)(55);  

  return 0;
}


OUT void Func(IN int Temp)
{
  printf("%d\n", Temp);
}

==============================================================



===  C++에서 함수 포인터 ===

#include <stdio.h>


class C_Func
{
  public:
    void C_Print(int Temp, int Temp_1)
    {
      printf("[%d] [%d]\n", Temp, Temp_1);
    }
};



typedef void (C_Func::*FP)(int, int);

int main()
{
  C_Func A;

  FP fp = &C_Func::C_Print;
 
  (A.*fp)(500, 800);


  return 0;  
}

==============================================================

Posted by 응이

댓글을 달아 주세요

닷넷 계열의 최신 컴파일러에서는 기존의 scanf 같은 함수가 지정된 메모리 영역을 오버하여 입력받는 위험에 대한 대용 함수로...scanf_s 라는 함수를 사용하라고 경고하는 것
입니다...

scanf 대신에...scanf_s  함수를 사용하세요...

- MSDN -

int scanf_s(
   const char *format [,
      argument]...
);

int main( void )
{
   int      i, result;
   float    fp;
   char     c, s[81];
   wchar_t  wc, ws[81];

   scanf_s( "%d %f %c %C %s %S", &i, &fp, &c, 1, &wc, 1, s, 80, ws, 80 );


   return 0;
}

Posted by 응이

댓글을 달아 주세요

VS6.0

툴스 -> 옵션 -> 디렉토리 탭 -> 인클루드,라이브러리 경로 설정

프로젝트-> 세팅 -> 링크탭 -> Lib-> 라이브러리 파일명 추가


VS 2005


도구 ->옵션 ->프로젝트및솔루션 ->디렉토리 -> 일클루드,라이브러리 경로 설정

속성 -> 구성속성 -> 링커 -> 명령줄 -> 추카옵션 에서 라이브러리 파일명 추가


코드에 직접 추가

#pragma comment(lib,"라이브러리 파일명.lib")

Posted by 응이

댓글을 달아 주세요

CMonitor 소스

C or C++ 2009.01.13 09:22

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <windows.h>

#define  DR_NUM     10   // 출력할 Register 개수
#define  MAX_PROGRAM_SIZE  0x10000   // 적재할 프로그램의 최대 크기
#define SECTION_SIZE  512   

#define  DEFAULT_DISPLAY_LINE 16  // 메모리 출력 시 기본 출력 행 수
#define  ROW_LINE    16  // 메모리 출력 행 수


// 입력할 수 있는 명령 목록
#define  CommandDisplay   0x00 // Command Display when wrong Command
#define  MemoryDisplay    0x01 // Memory Display Command
#define  MemoryModify    0x02 // Memory Modify Command
#define  RegisterDisplay  0x03 // Register Display Command
#define  ProgramLoad   0x04 // Executable Program Load
#define  ProgramRun    0x05 // Loaded Program Execute
#define  MemoryClear   0x06 // Memory Clear Command
#define  CodeDisplay   0x07 // Code Area Display Command
#define  DataDisplay   0x08 // Data Area Display Command
#define  StackDisplay   0x09 // Stack Area Display Command
#define  MemoryStatusDisplay 0xFD // Memory Status Display Command
#define  NoInput    0xFE // 입력이 없을 경우
#define  ExitProgram   0xFF // Exit Command


char *R_NAME[] =      // 레지스터 상태를 출력할 때 사용되는 배열
{
 "EFL", "EIP", "EDI", "ESI", "EBP",
 "ESP", "EBX", "EDX", "ECX", "EAX"
};


typedef struct       // 레지스터 정보가 저장되는 구조체
{
 int  efl;
 int  eip;
 int  edi;
 int  esi;
 int  ebp;
 int  esp;
 int  ebx;
 int  edx;
 int  ecx;
 int  eax;
} context;


struct         // 명령어와 숫자를 대응시키기 위해 사용
{          // 미리 Define된 값과 대응
 unsigned char *name;
 unsigned char f_num;
} GROUP[] =
{
 "Q\n" ,  ExitProgram,
 "QUIT\n",  ExitProgram,
 "MC\n",  MemoryClear,
 "MD\n",  MemoryDisplay,
 "MM\n",  MemoryModify,
 "R\n" ,  RegisterDisplay,
 "P\n" ,  MemoryStatusDisplay,
 "LOAD\n",  ProgramLoad,
 "GO\n",   ProgramRun,
 "\n"  ,  NoInput,
 "CODE\n",  CodeDisplay,
 "DATA\n",  DataDisplay,
 "STACK\n",  StackDisplay,

 "HELP\n",  CommandDisplay,
 0   ,  0,
};

//enum으로 수정할 것!!!
unsigned char  File_Name[255];   // 읽어올 파일 이름의 임시 저장소
static unsigned char  *mem;    // 동적할당의 시작 위치
static unsigned char  *mem_end;   // 동적할당의 끝 위치
static unsigned char  *code;    // 프로그램 저장공간의 시작 위치 -code
static unsigned char  *data;    // 프로그램 저장공간의 시작 위치  -data
unsigned char  Load_Flag;    // 프로그램이 로드 유무 표시
unsigned char  Display_Flag;   // 메모리 값 출력시 계속해서 출력하는
         // 경우를 위해
int    File_DS;     // 저수준 파일의 데스크립터
static context  stat_old;     // 기존의 레지스터 값 저장

// 어셈블리로 구현한 함수
extern
 unsigned char md(int);  // Memory Display Function By Assembly
extern void mm(int, char);    // Memory Modify Function By Assembly
extern void STST(context *);   // STore STatus
extern void LDST(context *);   // LoaD STatus


// C로 구현한 함수
void AddressDisplay();      // 함수, 변수 등의 주소 값 출력
void MDFunction(unsigned int *);   // 주소를 입력 받아 그 곳의 값 출력
void MMFunction(unsigned int *);   // 주소를 입력 받아 그 곳의 값 수정
void RFunction();       // 레지스터 상태 출력
void PRFunction();       // Program Run Function
void CDFunction();       // Command Display Function
void go();         // Program Run Function
void load();        // Program Load Function
void Clear_mem();       // Dynamic Memory Clear Function
unsigned char StringToNum(char *);   // Command String to Number Function
void MSDFunction(unsigned int *, unsigned int); // 주소의 값 출력
void Address_Check(unsigned int *);  // Address Check Function


void main ()
{
 unsigned char  out  = 0;   // 명령의 Define된 변환 값 저장
 unsigned char  Command[255];   // 사용자가 사용하는 명령어를 저장
 unsigned int  addr = 0;   // 메모리 주소값을 입력 받아 저장
 context     stat_new;  // 레지스터 값의 임시 저장

 mem = (unsigned char *)malloc(MAX_PROGRAM_SIZE * 2); // 128Kbyte 동적 할당

 if (NULL == mem)
 {
  printf("메모리 동적 할당에 실패하였습니다.\n");
  printf("모니터 프로그램을 종료합니다.\n");
  return;
 }
 else
 {
  mem_end  = (unsigned char *)(mem + (MAX_PROGRAM_SIZE * 2) - 1);
  code  = (unsigned char *)
   (((unsigned int)mem & 0xFFFF0000) + MAX_PROGRAM_SIZE);
  data  = code + 0x2000;
  Clear_mem();
 }

 STST(&stat_old);     // 초기 레지스터 상태를 저장

 printf("\n\nMonitor Program is Starting....\n");
 AddressDisplay();
 RFunction();
 printf("stat_old's Address = [0x%08X]\n", &stat_old);

 while (1)
 {
  fflush(stdin);
  putchar('>');     // 프롬프트 출력
  putchar(' ');     // 프롬프트 출력

  fgets(Command, 255, stdin);
  out = StringToNum(Command);

  switch (out)
  {
  case MemoryClear:    // 동적 할당 메모리의 초기화
   Clear_mem();
   continue;

  case CommandDisplay:   // 명령 목록을 보여줌
   CDFunction();
   continue;

  case MemoryDisplay:   // Memory Display
   MDFunction(&addr);
   continue;

  case MemoryModify:   // Memory Modify
   MMFunction(&addr);
   continue;

  case RegisterDisplay:  // Register status Display
   RFunction();
   continue;

  case MemoryStatusDisplay:  // 프로그램 주소 정보를 출력
   AddressDisplay();
   continue;

  case ProgramLoad:    // 실행할 프로그램을 적재
   load();
   continue;

  case ProgramRun:    // 적재된 프로그램을 실행
   PRFunction();
   continue;

  case CodeDisplay:    // 코드 영역 출력
   addr = (unsigned int)(code);
   MSDFunction(&addr, DEFAULT_DISPLAY_LINE);
   continue;

  case DataDisplay:    // 데이터 영역 출력
   addr = (unsigned int)(code + 0x2000);
   MSDFunction(&addr, DEFAULT_DISPLAY_LINE);
   continue;

  case StackDisplay:    // 스택 영역 출력
   addr = (unsigned int)(mem_end - 0x00000100 + 0x00000001);
   MSDFunction(&addr, DEFAULT_DISPLAY_LINE);
   Display_Flag = 0;
   continue;


  case NoInput:    // 입력 없이 엔터만 입력할 경우
   if (1 == Display_Flag)
   {
    MSDFunction(&addr, DEFAULT_DISPLAY_LINE);
   }
   else
   {
   }
   continue;

  case ExitProgram:   // 종료 문자를 사용하였을 경우
  default:
   break;
  }
  break;
 }

 free(mem);       // 동적 할당 제거
 mem   = 0;     // 재사용 금지
 mem_end  = 0;     // 재사용 금지
 code   = 0;     // 재사용 금지
 return;
}


void AddressDisplay()      // Address Display Function
/*******************************************************************************
기능  : 현재 프로그램의 주소 정보를 출력
인수  : void
반환값  : void
*******************************************************************************/

{
 Display_Flag = 0;
 printf("\nCode Start Address   : 0x%08X\n", main);
 printf(  "Data Start Address   : 0x%08X\n", R_NAME);
 printf(  "Program Load Address : 0x%08X\n", code);
 printf(  "Dynamic Memory area  : 0x%08X to 0x%08X (%dKBytes)\n"
  , mem, mem_end, (mem_end - mem + 1)/1024);

 return;
}


unsigned char StringToNum(char *name)    // String To Number Function
/*******************************************************************************
기능  : 명령을 입력하였을 때 대응하는 Define 숫자로 변환
인수  : 문자열형의 명령어
반환값  : 명령어에 대응하는 hex형 값
*******************************************************************************/

{
 int loop_temp = strlen(name);

 while (1)
 {
  if (0 >= loop_temp)
  {
   break;
  }
  else
  {
   --loop_temp;
   *(name + loop_temp) = toupper(*(name + loop_temp));
  }
 }

 loop_temp = 0;

 while (1) // 구조체의 끝에 도달하면 종료
 {
  if (0 == strcmp(name, (*(GROUP + loop_temp)).name))
   // 일치하는 단어가 검색되었을 경우
  {
   break;
  }
  else if(0 == (*(GROUP + loop_temp)).f_num)
  {
   break;
  }
  else
  {
   ++loop_temp;
  }
 }

 return (*(GROUP + loop_temp)).f_num;
 // 일치하는 단어와 대응되는 숫자를 반환
 // 일치하는 단어가 없으면 0반환
}


void MDFunction(unsigned int *addr)    // Memory Display  Function
/*******************************************************************************
기능  : 주소값을 입력 받아서 그 주소에 있는 값을 출력
인수  : 입력받은 값을 저장할 장소
반환값  : void
*******************************************************************************/

{
 printf("메모리 주소를 입력하세요(0x%08X~0x%08X) : ", mem, mem_end);
 scanf("%x", addr);
 MSDFunction(addr, DEFAULT_DISPLAY_LINE);   // 출력 디폴트 라인 수로 출력
}


void MSDFunction(unsigned int *addr, unsigned int print_line)
// Memory Status Display Function
/*******************************************************************************
기능  : 인수로 넘겨받은 주소로부터 ROW_LINE개 단위를 1줄로 print_line의 숫자 만큼
화면에 출력
인수  : 출력할 대상이 있는 곳의 주소값, 메모리 출력시 출력 라인 수
반환값  : void
*******************************************************************************/

{
 unsigned int  loop_temp  = 0;   // 반복문을 위한 임시 변수 선언
 unsigned char  memory_dump[ROW_LINE];   // 반환된 메모리값을 임시로 저장

 Address_Check(addr);

 if (0 == Display_Flag)
 {
  return;
 }
 else
 {
  printf(" Address   ");     // 화면 상단 자리 표시를 위한 출력
 }

 while (loop_temp < ROW_LINE)    // Hex 부분 출력
 {
  printf("%02X ", loop_temp++);
 }

 putchar(' ');
 loop_temp = 0;

 while (loop_temp < ROW_LINE)    // ASCII 부분 출력
 {
  printf("%X", loop_temp++);
 }

 putchar('\n');

 while (0 < print_line)      // Data 부분 출력 루프 시작
 {
  Address_Check(addr);
  if (0 == Display_Flag)
  {
   return;
  }
  else
  {
   loop_temp = 0;
   printf("0x%08X ", *addr);   // 메모리 주소 출력
  }

  while (1)           // 메모리 읽어 오기 및 hex 출력 시작
  {
   // 메모리 주소에 위치한 값을 배열에 저장
   *(memory_dump + loop_temp) = md(*addr);

   // 배열에 저장된 값 출력
   printf("%02X ", *(memory_dump + loop_temp));

   ++loop_temp;
   ++(*addr);

   if (0 == (loop_temp % ROW_LINE)) // ROW_LINE개를 출력하였으면 loop 종료
   {
    break;
   }
   else
   {
   }
  }          // 메모리 읽어 오기 및 hex 출력 끝

  putchar(' ');
  loop_temp = 0;

  while (1)        // ASCII 부분 출력 루프 시작
  {
   if (0 == *(memory_dump + loop_temp))
   {
    putchar('.');     // 널 문자 대치
   }
   else if (32 > *(memory_dump + loop_temp))
   {
    putchar('*');     // 제어 문자 대치
   }
   else if (127 <= *(memory_dump + loop_temp))
   {
    putchar('*');     // 그래픽 문자 대치
   }
   else
   {
    putchar(*(memory_dump + loop_temp));
   }

   ++loop_temp;

   if (0 == (loop_temp % ROW_LINE)) // ROW_LINE개를 출력하였으면 loop 종료
   {
    break;
   }
   else
   {
   }
  }          // ASCII 부분 출력 루프 끝

  putchar('\n');
  --print_line;
 }           // Data 출력 루프 끝

 putchar('\n');
}


void MMFunction(unsigned int *addr)   // Memory Modify Function
/*******************************************************************************
기능  : 수정할 대상이 잇는 곳의 주소를 인수로 받아서 그 곳의 값을 변경하고
변경한 내용을 출력
인수  : 수정할 대상이 있는 곳의 주소값
반환값  : void
*******************************************************************************/

{
 unsigned char out = 0;
 MDFunction(addr);
 *addr = *addr - (DEFAULT_DISPLAY_LINE * ROW_LINE);
 printf("변경하고 싶은 값(16진수)을 입력하세요 : ");
 scanf("%x", &out);
 mm(*addr, out);
 out = md(*addr);
 MSDFunction(addr, 1);

 return;
}


void RFunction()         // Register status display Function
/*******************************************************************************
기능  : 레지스터의 상태를 화면으로 출력
인수  : void
반환값  : void
*******************************************************************************/

{
 unsigned int  i = DR_NUM;
 unsigned int  *temp = (unsigned int *)&stat_old;
 context   stat_new;

 Display_Flag = 0;

 printf("-------------- 프로그램 실행 초기의 레지스터 상태 -------------\n");
 while(1)
 {
  if (i == 0)
  {
   break;
  }
  else
  {
   --i;
   printf("%s ADDRESS VALUE : 0x%08X   ", *(R_NAME + i), *(temp + i));  
  }

  if (0 == i % 2)
  {
   putchar('\n');
  }
  else
  {
  }
 }

 STST(&stat_new);
 temp = (unsigned int *)&stat_new;
 i = DR_NUM;

 printf("-----------------     현재 레지스터 상태    -------------------\n");
 while(1)
 {
  if (i == 0)
  {
   break;
  }
  else
  {
   --i;
   printf("%s ADDRESS VALUE : 0x%08X   ", *(R_NAME + i),*(temp + i));
  }

  if (0 == i % 2)
  {
   putchar('\n');
  }
  else
  {
  }
 }

 printf("---------------------------------------------------------------\n");
 return;
}


void CDFunction()        // Command Display Function
/*******************************************************************************
기능  : 이 프로그램의 사용법을 출력
인수  : void
반환값  : void
*******************************************************************************/

{
 Display_Flag = 0;
 printf("메모리 디버거 명령어\n");
 printf("R         : Register Value Display\n");
 printf("P         : Memory Status Display\n");
 printf("MC        : Memory Clear\n");
 printf("MM        : Memory Modify\n");
 printf("MD        : Memory Display\n");
 printf("LOAD      : Program Load\n");
 printf("GO        : Loaded Program Execute\n");
 printf("CODE      : Code Area Display\n");
 printf("DATA      : Data Area Display\n");
 printf("STACK     : Stack Area Display\n");
 printf("HELP      : Help Message Display\n");
 printf("QUIT(Q)   : Exit Program\n");

 return;
}


void PRFunction()       // Program Run Function
/*******************************************************************************
기능   : 프로그램이 메모리에 적재되었는지 검사하고 적재되어 있으면 실행
인수   : void
반환값   : void
*******************************************************************************/

{
 Display_Flag = 0;
 if (1 == Load_Flag)     // 프로그램 적재 유무 판별
 {
  go();
 }
 else
 {
  printf("외부 프로그램이 적재되어 있지 않습니다.\n");
  printf("load 명령을 사용하여 프로그램을 적재하시길 바랍니다.\n");
 }

 return;
}


void load()        // Program Load Function
/*******************************************************************************
기능   : 프로그램의 이름을 입력받아 그 프로그램을 64K만큼 할당한 임의의 주소
공간에 적재
인수   : void
반환값  : void
*******************************************************************************/

{
 int    Read_Num ;
 int   Header_Size;
 IMAGE_DOS_HEADER  *dhp;
 IMAGE_NT_HEADERS32  *php;
 IMAGE_FILE_HEADER  *fhp;
 IMAGE_OPTIONAL_HEADER32 *ohp;
 
 Clear_mem();
 

 printf("\n읽어 들일 파일 이름을 입력하세요 : ");
 scanf("%s", File_Name);
 File_DS = open(File_Name, O_RDONLY | O_BINARY);

 if (0 > File_DS)
 {
  printf("파일을 찾을 수 없거나 읽을 수 없습니다.\n");
  printf("파일이 존재하는지 확인해 주세요.\n");
  printf("(경로명 사이에 '\'를 '\\'로 바꾸어 보십시오)\n");
  return;
 }
 else
 {
  Read_Num = read(File_DS, code, MAX_PROGRAM_SIZE);
 }

 if(0 > Read_Num)
 {
   printf("파일이 존재하지만 읽을 수 없습니다.\n");
  close(File_DS);  
  return;
 }
 else
 {
   dhp = (IMAGE_DOS_HEADER       *) code;
         php = (IMAGE_NT_HEADERS        *) (code+(dhp->e_lfanew));
         fhp = (IMAGE_FILE_HEADER         *) ((char *)php+sizeof(php->Signature));
         ohp = (IMAGE_OPTIONAL_HEADER32  *)((char *)fhp+sizeof(IMAGE_FILE_HEADER));
   
         Header_Size = ohp->SizeOfHeaders;
         Clear_mem();
 }

 
 if (0 > lseek(File_DS, Header_Size, SEEK_SET))
 {
  printf("파일의 헤더정보 Skip 실패!!\n");
  printf("파일을 적재할 수 없습니다..\n");
  close(File_DS);  
  return;
 }
 else
 {
  Read_Num = read(File_DS, code, SECTION_SIZE);
 }

 if(0 > Read_Num)
 {
   printf("파일이 존재하지만 읽을 수 없습니다.\n");
   close(File_DS);  
         return;
 }
 else
 {
  Read_Num = read(File_DS, data, SECTION_SIZE);
 }

 close(File_DS);    

 if(0 > Read_Num)
 {
   printf("파일이 존재하지만 읽을 수 없습니다.\n");
         return;
 }
 else
 {
  File_DS  = 0;
  Load_Flag = 1;
  printf("파일을 성공적으로 메모리에 적재하였습니다.\n");
  printf("읽어 들인 파일의 크기는 [%d]Bytes입니다.\n\n", Read_Num);
 }

 
 return;
}


void go()        // Program Runing Function
/*******************************************************************************
기능  : 동적할당 영역에 저장된 프로그램을 실행
인수  : void
반환값  : void
*******************************************************************************/

{
 context  stat_new;

 stat_new.eax = (int)(&stat_old);
 stat_new.ebx = 0;
 stat_new.ecx = 0;
 stat_new.edx = 0;
 stat_new.esi = 0;
 stat_new.edi = 0;
 stat_new.esp = (int)(mem_end + 1);
 stat_new.ebp = 0;
 stat_new.eip = (int)(code);
 stat_new.efl = 0;

 printf("적재된 프로그램을 실행합니다...\n");
 LDST(&stat_new);
 printf("SYSTEM PANIC!!\n");
}


void Clear_mem()      // Dynamic Memory Clear Function
/*******************************************************************************
기능  : 동적할당 영역에 저장된 값들을 초기화
인수  : void
반환값  : void
*******************************************************************************/

{
 unsigned char  *temp = mem;
 Load_Flag = 0;      // 로드된 프로그램을 제거하므로 0으로 설정
 Display_Flag = 0;

 while (1)
 {
  *temp = 0;
  if (mem_end == temp)
  {
   break;
  }
  else
  {
   ++temp;
  }
 }
}


void Address_Check(unsigned int *addr)    // Address Check Function
/*******************************************************************************
기능  : 파라미터가 출력할 수 있는 범위의 주소 값인지 검사
인수  : 검사할 주소
반환값  : void
*******************************************************************************/

{
 if ((unsigned int)mem > *addr)
 {
  printf("출력할 수 없는 주소 공간입니다.\n");

 }
 else if ((unsigned int)mem_end < *addr)
 {
  printf("출력할 수 없는 주소 공간입니다.\n");
  Display_Flag = 0;
 }
 else
 {
  Display_Flag = 1;
  return;
 }

 Display_Flag = 0;
 return;
}



Posted by 응이

댓글을 달아 주세요

1

Dream come true.
응이

달력

태그목록