Please Enable JavaScript!
Gon[ Enable JavaScript ]

메모리 내용을 읽어내어 키보드 상태 알아내기 - 4가지 방법

C# & MFC

메모리 내용을 읽어내어 키보드 상태 알아내기 - 4가지 방법 


키보드 상태는 메모리의 특정 번지를 읽으면 알 수 있는데, 그런 방식으로 키보드의 상태를 알아내는 방법 4가지를
소개하겠습니다.

#include   /* bioskey() 함수 */
#include    /* int86() 함수, REGS 공용체 */
 
void read_keydata1(unsigned char *a,unsigned char *b,unsigned char *c)
/* 포인터와 캐스트 연산자를 이용 */
{
   int temp;
 
   temp=*(unsigned int far *)0x00400017;  
   /* 0040:0017 번지에서 2 Byte 읽어내기 */ 
   *a=temp & 0xff;         
   /* 하위 바이트 - 상위 바이트값이 들어있음 (역워드 방식) */ 
   *b=(temp>>8) & 0xff;    
   /* 상위 바이트 - 하위 바이트값이 들어있음 */
   *c=*(unsigned char far *)0x00400096;   
   /* 0040:0096 번지에서 1 Byte 읽어내기 */
}
 
void read_keydata2(unsigned char *a,unsigned char *b,unsigned char *c)
/* peek() / peekb() 함수 이용 */
{
   int temp;
 
   temp=peek(0x0040,0x0017);   
   /* 0040:0017 번지에서 2 Byte 읽어내는 함수 */
   *a=temp & 0xff;            
   /* & 연산으로 하위바이트만 추출 */  
   *b=(temp>>8) & 0xff;      
   /* 상위 바이트 추출 */
   *c=peekb(0x0040, 0x0096);   
   /* 0040:0096 번지에서 1 Byte 읽어내는 함수 */ 
}
 
void read_keydata3(unsigned char *a, unsigned char *b)
/* 인터럽트 0x16(ah:0x12) 사용 */
{
   int temp;
   union REGS r;
 
   r.h.ah=0x12;
   temp=int86(0x16,&r,&r);
 
   *a=temp & 0xff;
   *b=(temp>>8) & 0xff;
}
 
unsigned char read_keydata4(void)
/* bioskey(2) 이용 */
{
   return ((unsigned char)bioskey(2));
}
 
void main()
{
   unsigned char *first,*second,*third;
 
   read_keydata1(first, second, third);
   printf("(1) pointer : %d\n",*first);
   printf("(2) pointer : %d\n",*second);
   printf("(3) pointer : %d\n\n",*third);
 
   read_keydata2(first, second, third);
   printf("(1) peekb : %d\n",*first);
   printf("(2) peekb : %d\n",*second);
   printf("(3) peekb : %d\n\n",*third);
 
   read_keydata3(first, second);
   printf("(1) int86 : %d\n",*first);
   printf("(2) int86 : %d\n\n",*second);
 
   printf("(1) bioskey(2) : %d\n",read_keydata4());
}

1. 프로그램 설명

현재 shift 키와 토글키들이 눌러져 있는 상태인지 아닌지를 체크하는 방법은 간단하다. ROM-BIOS 데이터
영역의 0040:0017 과 0040:0018 의 두 바이트와 0040:0096 의 한 바이트를 체크하면 된다. 각 키들의 상태가
비트단위로 나타나 있기 때문에, 각 비트값을 체크하면 현재 어떤 키가 눌려져 있는지 알아낼 수 있다.
그외 0040:0097 의 한 바이트를 체크하면 키보드 LED의 상태를 알아낼 수도 있다.
키보드에 관련된 정보는 다음의 4개의 주소를 통해 대부분 알아낼 수 있다.

0040:0017 번지는 86키보드와 확장된 키보드(101/102 키보드) 모두 값이 같고, 0040:0018 번지는 86키보드와
확장된 키보드가 값이 다르다. 아래의 표는 확장된 키보드 용이다. 0040:0096 번지를 체크해보면 확장키보드가
설치되었는지 알아낼 수 있다.


메모리 주소 0040:0017 의 정보

비트

설명

7
6
5
4
3
2
1
0

Insert (1 : on, 0 : off)
Caps Lock (1 : on, 0 : off)
Num Lock (1 : on, 0 : off)
Scroll Lock (1 : on, 0 : off)
Alt ( 1 : press )
Ctrl ( 1 : press )
왼쪽 Shift ( 1 : press )
오른쪽 Shift ( 1 : press )


메모리 주소 0040:0018 의 정보

비트

설명

7
6
5
4
3
2
1
0

Sys Req ( 1 : press )
Caps Lock ( 1 : press )
Num Lock ( 1 : press )
Scroll Lock ( 1 : press )
오른쪽 Alt ( 1 : press )
오른쪽 Ctrl ( 1 : press )
왼쪽 Alt ( 1 : press )
왼쪽 Ctrl ( 1 : press )


메모리 주소 0040 : 0096 의 정보

비트

설명

4
3
2

확장 키보드 ( 1: 설치됨 )
오른쪽 Alt ( 1 : press )
오른쪽 Ctrl ( 1 : press )


메모리 주소 0040 : 0097 의 정보

비트

설명

2
1
0

Caps Lock LED ( 1 : 켜짐, 0 : 꺼짐 )
Num Lock LED ( 1 : 켜짐, 0 : 꺼짐 )
Scroll Lock LED ( 1 : 켜짐, 0 : 꺼짐 )


① read_keydata1()

- 이 함수는 포인터와 캐스트 연산자를 써서 메모리 내용을 직접 읽어낸 것이다.
(unsigned int far *)형을 사용해서 뒤의 0x00400017이 주소라는 것을 알린 다음, 간접지정연산자인 *를
사용해서 그 주소의 내용을 읽어낸다. unsigned int를 사용했으므로 그 주소에서부터 2바이트를 읽어내는
것이다. unsigned char을 쓰면 1바이트만 읽어낸다.

0040:0017번지에서 0040:0018번지까지 읽어낸 2바이트의 내용을 temp라는 임시변수에 저장하는데,
저장될 때 역워드 방식에 따라 상위 바이트와 하위 바이트가 바꾸어 저장된다.
그 다음 *a 에 temp의 하위바이트를, *b에 상위바이트를 저장했다. 역워드 방식에 따라 *a 에는
0040:0017의 내용이, *b에는 0040:0018의 내용이 들어있다.

*c에는 0040:0096 번지에서 한 바이트만 읽어낸 값이 저장된다.

② read_keydata2()

* int peek(unsigned segment, unsigned offset);
* char peekb(unsigned segment, unsigned offset);
- peek() 함수는 메모리에서 segment:offset 으로 정해진 주소의 내용을 2바이트 읽어내고, peekb() 함수는
peek()함수와 비슷한 역할을 하지만, 메모리의 내용을 1바이트만 읽어낸다.

③ read_keydata3()

- INT 16h는 키보드를 제어하는 소프트웨어 인터럽트이다. 하드웨어 인터럽트 INT 09h 에 의해서 키입력이
이루어 진 후, 롬 바이오스 데이터 영역의 원형 키보트 버퍼에 값이 저장되면, INT 16h에 의해서 그 값을 읽어들일
수 있다.

( INT 16h )  
    * 입력 : AH=02h (모든 키보드의 상태를 감지)
    * 입력 : AH=12h
            (0040:0096 번지에 확장 키보드가 인스톨되었다는 표시가 있을 때만 사용 가능)
 
   ** 출력 : AH : 0040:0018 번지의 데이터값이 입력
                AL : 0040:0017 번지의 데이터값이 입력
            (단, AL의 값은 입력값 AH=02h/12h 상관없이 같고, AH의 값은 86키보드와 확장키보드가 값이 다르다. )

④ read_keydata4()

- bioskey(0)

: 버퍼로부터 키입력을 받아 스캔코드값을 리턴하고,  버퍼를 지운다.
만약 버퍼에서 지울 내용이 없다면 키보드에서 입력이 있을 때까지 기다렸다가 지운다.

- bioskey(1)

: 버퍼로부터 키입력을 받아 스캔코드값을 리턴하는데, 버퍼는 지우지 않는다.
만약 버퍼가 비어있다면 0을 리턴한다.

- bioskey(2)

: 0040:0017 번지의 내용(BIOS의 shift 상태 flag)을 읽어 그 값을 리턴한다.
bioskey() 함수는 int형(2바이트)을 리턴하는데, 그 중 하위바이트가 원하는 내용이므로 &연산을 이용해서
하위바이트만 추출해 낼 수 있도 있고, 산술 변환 규칙을 이용할 수도 있다. demotion의 경우 상위 비트가
잘려나가므로 캐스트연산자를 써서 하위바이트만 구하는 것이다.

2. 역워드 방식

정수 1998을 메모리에 저장한다고 생각해보자.

위의 정수가 메모리에 저장될 때에는 상위 바이트, 하위 바이트의 순으로 저장되는 것이 아니라 그 반대로 하위 바이트,
상위 바이트 순으로, 즉 역순으로 저장된다. 위의 예에서 1998은 0xCE, 0x07 과 같이 역순으로 저장된다.
이러한 저장방식을 역워드 방식이라고 한다. 부호있는(signed)형에서 MSB는 부호비트로 사용된다.
MSB가 1이면 음수, 0이면 양수라고 판단한다.

3. 산술 변환 규칙

- 산술변환 규칙 우선순위 표에서 화살표 방향이 promotion이고, 반대방향이 demotion 이다.

① promotion
- 부호확장(sign extension)에 의한다. 즉 변환되기 전의 데이터형이 부호있는(signed) 형이고,
그 값이 음수(MSB가 1)이면 2의 보수형태를 계속 유지하기 위하여 확장되는 비트를 1로 채운다. 부호있는 형이고,
0이상의 수치이면(MSB가 0)이면 확장되는 비트를 0으로 채운다. 반대로 부호없는 형이면 확장되는 비트를 언제나
0으로 채운다.

- 정수형이 부동형으로 변환될 경우에는 정밀도에 극히 미미한 손실이 있을 수 있다.
- float형이 double형으로 바뀌면 확장되는 가수부를 0으로 채운다.
- enum이 변환될 때 enum이 부호없는 형이면 unsigned형으로 변환된다.

② demotion
- 변환되기 전의 상위비트가 잘려나간다.

4. 세그먼트(segment)와 오프셋(offset)

IBM PC에서는 1MB의 메모리를 관리하기 위해 세그먼테이션 (Segmentation)기법을 사용한다. 세그먼트는
1MB의 메모리를 64KB로 나눈 조각으로 PC가 메모리를 사용하는 단위이다.
(64KB는 16비트의 세그먼트 레지스터가 가리킬 수 있는 메모리의 최대 크기이다.) 또한 세그먼트 시작주소에서의
상대적인 위치로 오프셋을 잡아 원하는 번지를 표현한다.
이렇게 메모리 관리를 세그먼트와 오프셋으로 나눈 이유는 1M의 메모리 주소가 0000h ∼FFFFFh 까지이며,
16비트 레지스터 한 개로는 0000h ∼ FFFFh 까지밖에 표현할 수 없기 때문에, 16비트 레지스터 2개를 사용해서
주소를 처리하기 위해서이다. 만약 2개의 16비트 레지스터를 사용한다면 32비트이므로 0 ∼ FFFFFFFFh 까지를
표현할 수 있다. 여기서 총 20비트만 사용하면 되므로 2개의 레지스터를 중첩되게 해서 사용한다.

세그먼트와 오프셋을 이용한 주소계산은 다음과 같다.

     segment * 0x10
  +  offset 
  ------------------
    절대 주소

세그먼트의 단위는 오프셋의 단위보다 10h만큼 크다. 즉, 세그먼트의 값을 왼쪽으로 4비트 shift시킨 것이다.
예를 들어, A123F 번지를 나타내는데 있어 A000:123F 로 해도 되고, A123:000F 로 표현을 해도 된다.
이처럼 여러 가지 방법으로 원하는 번지를 표현할 수 있다.

보통 세그먼트와 오프셋에서는 원하는 번지의 첫머리에 1000을 곱한 것을 세그먼트로, 뒤의 4자리를 오프셋으로 한다.

Posted by 녹두장군

댓글을 달아 주세요