간단한 'hello world' 부트섹터부터 보호모드 스위치, 그리고 디스플레이 까지 말한다.
BabyStep1
다음 코드는 플로피로부터 가장 간단한 부팅 코드의 예제다.
NASM에서 어셈블되고 partcopy를 사용하여 플로피에 복사된다.
그러면 플로피로부터 부트할 수 있다.
; nasmw boot.asm -f bin -o boot.bin
; partcopy boot.bin 0 200 -f0
어셈 코드
hang:
jmp hang
times 512-($-$$) db 0
CPU는 리얼 모드와 BIOS가 0000:7c00에 위치하는 코드에서 시작한다. "times 512..."은 NASM에게 512 바이트를 0로 채워라는 명령이다. 그리고 partcopy는 플로피에 512바이트를 쓴다. (200 in hex = 512) 그것을 변경한다면 partcopy가 말라죽는 것을 보게 될 것이다 ㅠ.ㅜ
종종, 코드 끝에 boot signature(0xAA55) 라고 불리는 것을 보게 될 것이다. BIOS들의 이전 버전들은 디스크에서 부트 섹터를 구별하기 위해 boot signature를 찾았다. 현재에는 명백히 불필요하다. 만약 필요하다면, 마지막 라인에 이렇게 대체해 사용한다.
times 510-($-$$) db 0 ;2 bytes less now
dw 0xAA55
그러나 내가 정말로 강조하는 것은 한번 부팅시켜보고, 커서가 빈 스크린에 행복하게 깜빡일 때 2가지를 알아채야 한다. 하나는 플로피 모터가 꺼지는 것과, 다른 하나는 리부트를 위해 Crtl-Alt-Del를 눌릴 수 있어야 하는 것이다.
포인트는 인터럽트(int 0x09)가 여전히 생성되어 있다는 것이다.
재미삼아, interrupt flag를 지워보자
cli
hang:
jmp hang
times 510-($-$$) db 0
dw 0xAA55
플로피 모터가 꺼지지 않는 것과 Ctrl-Alt-Del로 리부트되지 않는 것을 알 수 있다.
여기서 루프를 제거하고, 단지 0로 섹터를 채우는 것에 의해 더이상 줄이려고 한다면,
BIOS는 그것에 대해 어떤 것을 말할 것이다.
"Operating System Not Found".
BabyStep2
Quick review:
- BIOS에 의해 로드되는 부트 섹터는 512바이트이다.
- 디스크 부트 섹터에 있는 코드는 BIOS에 의해 0000:7c00에 로드된다.
- 머신은 리얼모드에서 시작한다.
- CLI가 없으면 CPU는 interrupt가 존재하는 것을 알아낸다.
- 많은(전부는 아니지만) BIOS interrupt는 리얼모드 세그먼트 값을 가지는 DS를 요구한다.
이것이 많은 BIOS interrupt가 보호 모드에서 작동하지 않는 이유이다.
스크린에 출력하는 int 10h/ah=0eh를 사용하려 한다면, 문자들을 출력하기 위해 seg:offset가 올바른지 확인해야할 필요가 있다.
0000:7c00 또는 07c0:0000를 사용한다면 문제되지가 않지만,
ORG를 사용할 경우, 일어나는 일에 대해 알고 있어야한다.
mov ax, 0x07c0
mov ds, ax
mov si, msg
ch_loop:lodsb
or al,al ;zero=end of str
jz hang ;get out
mov ah,$0e
int $10
jmp ch_loop
hang:
jmp hang
msg db 'Welcome to Macintosh', 13, 10, 0
dw 0xAA55
times 510-($-$$) db 0
다음은 ORG 버젼이다. 무엇을 DS에 넣을 것인가를 알아야 한다.
[ORG 0x7c00]
xor ax, ax ;make it zero
mov ds, ax
mov si, msg
ch_loop:lodsb
or al,al ;zero=end of str
jz hang ;get out
mov ah,$0e
int $10
jmp ch_loop
hang:
jmp hang
msg db 'Welcome to Macintosh', 13, 10, 0
dw 0xAA55
times 510-($-$$) db 0
새로운 주제 : 전형적인 procedure는 코드로 부터 CALL/RET 같은 것을 사용하여 구분되었다.
[ORG 0x7c00]
xor ax, ax ;make it zero
mov ds, ax
mov si, msg
call bios_print
hang:
jmp hang
msg db 'Welcome to Macintosh', 13, 10, 0
bios_print:
lodsb
or al,al ;zero=end of str
jz done ;get out
mov ah,$0e
int $10
jmp bios_print
done:
ret
dw 0xAA55
times 510-($-$$) db 0
몇가지 설명할 수 없는 이유로 때문에, SI 로딩하고 procedure로 점프하는 방법은 나를 놀라게 했다. 운좋게 나같은 사이코들을 위해, NASM의 매크로는 파라메터 패싱을 허용하고 있다.
%macro BiosPrint 1
mov si, word %1
ch_loop:lodsb
or al,al ;zero=end of str
jz done ;get out
mov ah,$0e
int $10
jmp ch_loop
done:
%endmacro
[ORG 0x7c00]
xor ax, ax ;make it zero
mov ds, ax
BiosPrint msg
hang:
jmp hang
msg db 'Welcome to Macintosh', 13, 10, 0
dw 0xAA55
times 510-($-$$) db 0
만일의 경우 코드는 길어지고, 읽지못하게 될 수 있다. 메인 코드의 시작에 파일을 include 함으로써, 다른 파일로 분할할 수 있다.
jmp main
%include "othercode.inc"
main:
;... rest of your code here
BabyStep3
기계어에서 Opcode
; nasmw encode.asm -f bin -o encode.bin
mov cx, 0xFF
dw 0xAA55
times 510-($-$$) db 0
디스크에 partcopy를 하지 않는다.
단지, DEBUG를 연다. ( MS*에서는. 리눅스 유저들은 Hexdump가 좋다)
C:osdevdebug encode.bin
바이너리 파일을 보기 위해 '-' 이후에 'd'를 타이핑한다.
('?'는 help, 'q'는 quit)
이와 같은 것을 보게 될 것이다.
OAE3:0100 B9 FF 00 00 00 00 etc...
MOV에 대한 opcode를 찾으려면 여기를 보라 htp://www.baldwin.cx/386htm/MOV.htm
"17.2.2.1 Opcode" : http://www.baldwin.cx/386htm/s17_02.htm
바꾸어 말하면, 덤프에서 보이는 것은 유니크 레지스터 넘버(unique register number)(CX = 1)가 기본 opcode 값인 'B8'에 주어진 'B9'를 더한다.
하지만, CX를 ECX로 대체할 시, 발생하는 일을 보자.
mov ecx, 0xFF
dw 0xAA55
times 510-($-$$) db 0
OAE3:0100 66 B9 FF 00 00 00 00 etc...
'66'은 32Bit operand size를 가지는데, NASM이 바이너리 파일은 16Bit로 어셈블되므로, 모순된다.
모드를 변경하기 위해 BITS directive를 사용한다면 같은 일이 발생한다.
하지만 operland의 사이즈와는 다르다.
[BITS 32]
mov cx, 0xFF
dw 0xAA55
times 510-($-$$) db 0
이것으로 프로세서의 모드가 실제적으로 변경되지 않는다.
단지, 이것은 다음의 byte 해석을 돕기 위한 것이다.
Addresses
주소 인코딩(Address encoding)은 더 복잡한 bit이다.
mov cx, [temp]
temp db 0x99
dw 0xAA55
times 510-($-$$) db 0
OAE3:0100 8B 0E 04 00 99 00 00 00 etc...
'8B'는 opcode이다.
'0E'는 opcode를 해석하는 데 도와주는 ModR/M이다.
"17.2.1 ModR/M and SIB Bytes"를 참고 : http://www.baldwin.cx/386htm/s17_02.htm
(Fig. 17-2를 보라) 다른 필드를 가지는 이 byte를 해석하기 위한 룰은, 운좋게 Table 17-2는 그것을 쉽게 만들었다.
'0E'를 보고, 왼쪽에 있는 "disp16"를 볼 것이다. operand는 16bit offset으로 해석된 의미이다.
'04 00'은 16-bit offset이다. 0x0004가 왜 거꾸로인지 혼란스러울지 모른다. Intel Processor는 리틀 인디안(little endian) 이기 때문이다.
'99'는 물론, 0x0004에 있는 byte의 값이다.(8B는 0x0000에 있다)
Be aware of another prefix called the Address size Override Prefix '67' which the assembler generates when there is a discrepancy just like with '66' above. (해석 불가 ㅡㅅㅡ;;)
16-bit 리얼 모드에서 32-bit 보호 모드로 전환하기 위해 코드는 변경될 것이다.
BabyStep4
이 예제는 문자열과 메모리의 내용(비디오 메모리에서 문자열의 첫글자)을 출력한다.
이것은 BIOS를 사용하지 않고 텍스트 모드에서 스크린에 출력하는 예제를 의미한다. 게다가 변환된 hex는 화면에 출력될 수 있다. 그러면 레지스터와 메모리 값을 체킹할 수 있는 것이다.
stack을 더했지만, 그것을 사용하여 끝내지 않았다. 그러나, 아마도 곧 사용할 것이기 때문에 남겨두었다.
;=====================================
; nasmw boot.asm -f bin -o boot.bin
; partcopy boot.bin 0 200 -f0
[ORG 0x7c00] ; offset에 더한다.
xor ax, ax ; ax를 제로로 만든다.
mov ds, ax ; DS=0
mov ss, ax ; 스택은 0에서 시작한다.
mov sp, 0x9c00 ; 200h 지나서 코드가 시작한다.
mov ax, 0xb800 ; 텍스트 비디오 메모리를 저장한다.
mov es, ax
mov si, msg ; 텍스트 스트링을 보여준다.
call sprint
mov ax, 0xb800 ; 비디오 메모리를 본다.
mov gs, ax
mov bx, 0x0000 ; 'W'=57 attrib=0F
mov ax, [gs:bx]
mov word [reg16], ax ;Register를 본다.
call printreg16
hang:
jmp hang
;----------------------
dochar:
call cprint ; 문자하나를 출력한다.
sprint:
lodsb ; 문자열(si)에서 문자 하나를 al에 저장한다.
cmp al, 0
jne dochar ; 0이 아니면 문자를 출력한다.
add byte [ypos], 1 ; 한 라인 밑으로 (CR)
mov byte [xpos], 0 ; 왼쪽 끝으로 이동 (LF)
ret
cprint:
mov ah, 0x0F ; attrib = white on black
mov cx, ax ; 문자의 속성을 저장한다.
movzx ax, byte [ypos]
mov dx, 160 ; 각 문자는 2바이트를 잡아먹는다(문자 + 속성) 2*80 = 160
mul dx ; 80컬럼이기 때문(dx는 한 라인의 정보를 저장하는 것 같다)
movzx bx, byte [xpos]
shl bx, 1 ; 속성을 skip하기 위해 2를 곱한다.
mov di, 0 ; 비디오 메모리의 시작
add di, ax ; y좌표를 더한다.
add di, bx ; x좌표를 더한다.
mov ax, cx ; 문자와 속성을 불러온다.
stosw ; 문자와 속성을 쓴다.
add byte [xpos], 1 ; 오른쪽으로 한칸 이동
ret
;------------------------------------
printreg16:
mov di, outstr16
mov ax, [reg16]
mov si, hexstr
mov cx, 4 ; four places
hexloop:
rol ax, 4 ; 가장 왼쪽은 (4바이트 왼쪽으로 로테이트)
mov bx, ax ;
and bx, 0x0f ; 가장 오른쪽이 된다.
mov bl, [si + bx] ; index into hexstr
mov [di], bl
inc di
dec cx
jnz hexloop
mov si, outstr16
call sprint
ret
;------------------------------------
xpos db 0
ypos db 0
hexstr db '0123456789ABCDEF'
outstr16 db '0000', 0 ;register value string
reg16 dw 0 ; pass values to printreg16
msg db "What are you doing, Dave?", 0
dw 0xAA55
times 510-($-$$) db 0
;==================================
※ http://www.mega-tokyo.com/osfaq2/index.php/BabyStep 에서 가져왔습니다. 그대로 해석하기 보다는 저가 공부하고 있는 입장에서 저가 보기 편하게 해석했습니다^^
'개발 끄적임들 > 케케묵어버린 것들' 카테고리의 다른 글
aTracker (Wireless Network MicaZ를 위한 tinyOS 설치와 사용) (0) | 2005.07.13 |
---|---|
[펌]Example Boot Sector (0) | 2005.07.11 |
SE 실험을 위한 트랙백 표준 참고 페이지 (0) | 2005.06.15 |
리눅스 바통 넘기기... (0) | 2005.06.13 |
참고 사이트 (0) | 2005.06.07 |