마지막으로 지금까지 구한 값을 비트 연산을 통하여 바이트로 만들어 메모리에 저장하고, x86 기계 코드가 저장된 메모리 영역의 오프셋 값을 가지는 m_uwX86BytePos를 증가시킴으로써 ADCW 변환은 끝난다.
STB 명령은 바이트 레지스터의 내용을 TVM 데이터 세그먼트에 저장하는 명령이다. 이 명령은 x86의 MOV 명령으로 처리가 가능하다. MOV 명령으로 특정 번지를 액세스하기 위해서는 <표 3>의 모드가 제공된다.
TVM 데이터 세그먼트를 간단히 접근하기 위해 TVM은 데이터 세그먼트를 EBP에 저장하고 EBP로부터 변수 값을 참조한다. TVM의 데이터 세그먼트의 크기가 최대 64KB이므로 변위가 16비트 이상을 가져야 한다. 그러므로 32비트 시스템에서는 MOD=10, R/M=101을 사용하여 접근하고, 16비트 시스템에서는 MOD=10, R/M=110을 사용하여 접근한다. EBP로 메모리를 접근하기 위해서 TVM 데이터 세그먼트의 베이스 어드레스를 EBP에 저장하는 기계 코드를 프로그램 시작 지점에 삽입해야 한다. 만약 TVM 데이터 세그먼트가 0x00374738일 경우 결과적으로 다음의 인스트럭션을 생성해야 한다.
MOV EBP, 00374738H
MOV 명령은 데이터를 레지스터에 저장하기 위해서 <그림 3>의 형식을 제공한다.

EBP에 저장해야 하므로 w는 1이고 <표 2>에 따라 REG는 101이다. 그러므로 첫 번째 바이트는 10111101(0xBD)이다. 두 번째 바이트부터 다섯 번째 바이트는 TVM 데이터 세그먼트의 번지가 저장됨으로써 앞의 인스트럭션의 기계 코드가 생성되게 된다. 다음은 그 기능을 하는 함수의 내용이다.
void CX86Port::InitialDataSegmentToBP()
{
*(m_pubCodeSegment + m_uwX86BytePos) = 0xBD;
*(UDWORD*)(m_pubCodeSegment + m_uwX86BytePos + 1) =
(UDWORD)m_stSegment->ubDataSegment;
m_uwX86BytePos += 5;
}
EBP를 이와 같이 초기화시켜 주는 x86 기계 코드를 넣었으므로 이제 본격적으로 STB 명령을 구현해 보자. STB 명령을 x86 기계 코드로 변환하기 위해 x86의 MOV 명령을 사용한다. MOV 명령은 레지스터와 메모리 간의 데이터 이동을 위해 <그림 4>와 같은 형식을 제공한다.

STB 명령은 바이트 레지스터의 내용을 읽어 메모리에 저장하기 때문에 d는 0이고 w도 0이 된다. 또한 앞에서 밝혔듯이 32비트 시스템에서는 MOD=10, R/M=101을 사용하고, 16비트 시스템은 MOD=10, R/M=110을 사용하여 접근한다. 변위는 32비트 시스템에서 4바이트, 16비트 시스템에는 2바이트가 쓰인다. <리스트 2>는 STB를 x86 기계 코드로 변환하는 함수의 내용이다.

OPCode는 100010B(0x22)이며 16비트 시스템과 32비트 시스템에서 적절히 컴파일 되도록 전처리기를 사용하여 서로 다르게 처리되었다. 또한 EBP를 써서 접근해야 하므로 어드레스 크기를 16비트로 인식하는 Instruction Prefix(0x67)가 쓰이지 않았다.
LDB는 단지 데이터의 이동 방향만 반대이므로 d에 1을 대입하고 앞의 코드를 그대로 수행하면 LDB의 기계 코드도 완성된다. TVM의 JMP 명령은 코드 세그먼트의 특정 오프셋으로 실행 위치를 이동하는 명령이다. 이 명령은 x86의 JMP 명령으로 처리할 수 있다. x86에서 JMP 명령은 8비트의 상대적인 위치로 이동할 수 있는 형식을 제공하는데, TVM의 코드 세그먼트의 크기가 64KB이므로 모든 영역을 이동하기 위해선 16비트의 오프셋이 필요하다. 그래서 레지스터의 값으로 이동하는 형식을 사용해서 이동한다. <그림 5>는 레지스터 값으로 이동하는 JMP 명령의 형식이다.
점프할 위치를 저장할 레지스터는 TVM에서 사용하지 않는 ESI 레지스터를 사용한다. <리스트 3>은 JMP 명령을 x86 기계 코드로 변환하는 함수의 내용이다.
InsertJumpPosToESI는 ESI에 점프할 위치를 저장하는 기계 코드를 생성하는 함수이다. 이동할 오프셋이 0x00374738일 경우 다음의 인스트럭션이 기계 코드로 변환된다. 앞에서 살펴본 InitialData SegmentToBP() 함수와 구현 방법은 거의 같다.
MOV ESI, 00374738H
ESI 레지스터로부터 점프하기 위해 <표 1>과 <표 2>에 따라 MOD는 11이고 R/M은 110이므로 0xE6이 된다. CALL 명령도 JMP 명령과 거의 비슷하다. 단지 차이가 있다면 점프하기 전에 내부적으로 IP를 스택에 저장하는 과정이 추가된다는 점이다. <그림 6>은 레지스터가 가리키는 값으로 이동하는 CALL 명령의 형식이다.
JMP 명령과 차이가 있다면 MOD와 R/M 사이의 값이 010로 바뀌었다는 것뿐이다. CALL 명령의 처리는 JMP 명령과 거의 같다. TVM의 PUSHW 명령은 x86의 PUSH 명령으로 변환된다. <그림 7>은 레지스터를 저장하기 위한 PUSH 명령의 형식이다. PUSHW 명령은 <리스트 4>와 같이 처리된다.
이상으로 TVM 인스트럭션의 종류별로 대표적인 것들의 처리 과정을 살펴보았다. 과정을 보면 단지 해당 CPU가 제공해주는 형식에 맞춰 저장했을 뿐이다. 다른 시스템으로의 포팅도 이와 비슷하다. 기계 코드의 실행은 MAIN으로 지정된 프로그램 엔트리 포인트(TVM 어셈블러에서 다룸)로 인라인 어셈블 명령을 써서 점프해 줌으로써 간단히 해결된다. 다음으로 인터럽트(interrupt) 처리 부분을 살펴보도록 하자.





Posted by Dual


