16 bit mode 是以 segment based 的方式 access memory。32 bit mode 將 logic address 轉成 physical address 的方式跟 16 bit mode 不同。在 32 bit mode 下,segment register 的值代表的是 global descriptor table 中某個 segment descriptor 的 index。

segment descriptor 是個 8 byte 的 structure,包含以下資訊:

  • base address (32 bits):定義 segment 從 physical memory 的起始位置
  • segment limit (20 bit):segment 的 size
  • flags:這些 flag 會影響 CPU 如何解讀 segment,例如 privilige level 跟這塊 segment 是不是 read-only 或 write-only 等。

下圖是 segment descriptor 的結構:

最簡單的 segment configuration 稱為 basic flat mode,是由兩個重疊的 segment 組成,一個是給 code 使用,另一個是放 data。這兩個 segment 會 cover 整個 4GB 的 memory。那也因為這兩個 segment 位置是重疊的,當然不可能有什麼保護機制(保護一個 segment 不被另一個 segment 亂搞之類的)。

GDT 的 descriptor entry 就是用來描述每個 segment 的。除了 code 跟 data segment 之外,GDT 的第一個 descriptor 必須要是 null descriptor,內容是 8 byte 的 0。這是在轉換到 protcted mode 時,如果不小心忘記設定 segment register,會讓我們剛好 access 到 null descriptor。而 CPU 在 access 到 null descriptor 的時候會 raise exception(interrupt)讓我們知道有錯誤。

code segment 的 descriptor configuration:

  • Base:0x0
  • Limit:0xffff
  • Present:1,表示這個 segment 是 present 在 memory 中。這個 config 之後會用於 virtual memory。
  • Privilege:0,ring 0 是最高 privilige
  • Descriptor type:1,code 或 data segment
  • Type:
    • Code:1,因為這是 code segment
    • Confirming:0,比較低 privilege 的 segment 不能 call 這個 segment 的 code,這是 memory protection 的關鍵
    • Readable:1,1 為 readable,0 為 execute only
    • Accessed:0,這個參數通常用在 virtual memory 的 debug
  • Other flags:
    • Granularity:1,這會將 Limit 乘以 4K(16 * 16 * 16),所以 0xffff 的 limit 會變成 0xffff000。
    • 32-bit default:1,因為我們的 segment 會放 32-bit code。0 則是 16-bit。這實際上是設定 operation 的 default size,例如 push 0x4 到 stack 的時候,會自動被擴展成 32-bit 的數字。
    • 64-bit code segment:0,32-bit CPU 不會用到
    • AVL:0,這是可以設給自己使用的 bit

因為我們的 data segment 跟 code segment 位置是重疊的,所以除了以下的 type flag 之外,data segment 的 configuration 跟 code segment 是一樣的:

  • Code:0
  • Expand down:0
  • Writable:1
  • Accessed:0

Define the GDT in Assembly

我們會用 db、dw、dd 這幾個指令來放 bytes、建立 GDT。

CPU 需要知道 GDT 有多長,我們會用 GDT descriptor 來描述 GDT,GDT descriptor 包含:

  • GDT size (16 bit)
  • GDT address (32 bit)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
gdt_start:

gdt_null: ; the mandatory null descriptor
dd 0x0 ; dd means define double word (4 bytes)
dd 0x0

gdt_code: ; the code segment descriptor
; base = 0x0, limit = 0xfffff
; 1st flags: (present)1 (privilege)00 (descriptor type)1 -> 1001b
; type flags: (code)1 (conforming)0 (readable)1 (accessed)0 -> 1010b
; 2nd flags: (granularity)1 (32-bit default)1 (64-bit seg)0 (AVL)0 -> 1100
dw 0xffff ; Limit (bits 0-15)
dw 0x0 ; Base (bits 0-15)
db 0x0 ; Base (bits 0-15)
db 10011010b ; 1st flags, type flags
db 11001111b ; 2nd flags, Limit (bits 16-19)
db 0x0 ; Base (bits 24-31)

gdt_data: ; the data segment descriptor
; Same as code segment except for the type flags:
; type flags: (code)0 (expand down)0 (writable)1 (accessed)0 -> 0010b
dw 0xffff ; Limit (bits 0-15)
dw 0x0 ; Base (bits 0-15)
db 0x0 ; Base (bits 0-15)
db 10010010b ; 1st flags, type flags
db 11001111b ; 2nd flags, Limit (bits 16-19)
db 0x0 ; Base (bits 24-31)

gdt_end:

; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; Size of GDT, always less one of the true size
dd gdt_start ; Start address of GDT

; segment descriptor offset
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

32-bit protected mode 與 16-bit real mode 的差別:

  • register 變成 32 bit
  • 多了兩個 general purpose 的 segment register,fs 跟 gs
  • 可以用 32-bit memory offset 了,所以可以 reference 到 4 GB 的 memory
  • memory segmentation 的機制變得更好,有以下優點:
    • 在 priviledge 比較高的 segment 的程式可以終止在 priviledge 比較低的 segment 的程式。
    • CPU 可以為 user process 使用 virtual memory 機制(像是 page),可以讓 memory 有更有效率的使用。
  • interrupt 變得更完善

要進入 32 bit protected mode,我們必須設置 GDT(global descriptor table),它定義了 memory segment 們跟它們的 protected-mode attribute。

另外進入 32 bit mode 後,就不能用 BIOS 了。因為進到 32 bit 沒有 BIOS 可用,32 bit OS 必須要自己提供所有硬體的 driver,才能使用這些硬體。

如何在螢幕上顯示訊息

我們要使用 32 bit mode 又沒有 BIOS 的情況下,首先遇到一個問題:怎麼在螢幕上顯示訊息呢?就像前面的 print_string routine。

display device 可以被設定成 text 模式與 graphics 模式兩種。而什麼東西會顯示在螢幕上——也就是螢幕的哪個 pixel 要亮——是由一塊表示螢幕上每個 pixel 的 memory 區域控制的。因此我們要在螢幕上顯示東西,等同在那塊 memory 寫入資料。兩種模式會用不同的 memory 區域,操作時要注意。

在 text 模式下,我們只需要在一塊特定的 memory 裡指定我們要印的字元以及其 attribute 即可畫出一個字元。這塊 memory 從 0xb8000 開始,用兩個 bit 表示一個字元,第一個 bit 是要印字元的 ASCII code,第二個 bit 則是 attribute,例如顏色。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[bits 32]

VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f

print_string_pm:
pusha
mov edx, VIDEO_MEMORY

print_string_pm_loop:
mov al, [ebx]
mov ah, WHITE_ON_BLACK

; if encounter null terminate
cmp al, 0
je print_string_pm_done

; write data into video memory, 1 char has 2 bits in video memory
mov [edx], ax

; index of string and video memory
add ebx, 1
add edx, 2

jmp print_string_pm_loop

print_string_pm_done:
popa
ret

首先要知道 hard disk 長怎樣、如何指定讀取哪個位置的資料,參考Day 3 進入 32 bit 模式並導入 C 語言。

使用 BIOS 讀取 hard disk

BIOS 提供我們讀取 hard disk 的 routine,讓我們不用煩惱 chip 跟 bus 等硬體對讀取 hard disk 資料的問題。(BIOS 提供了一層抽象!)

要 call 這個 routine 是 intterupt 0x13 並且將 ah 設為 0x02。另外必須設置幾個 register 的值,好讓 BIOS 知道我們要讀取哪些 sector、要把讀到的資料放到 memory 的哪個位置:

  • dl:第幾個 drive,由 0 開始
  • ch:cylinder 編號
  • dh:磁片的哪一面,由 0 開始
  • cl:第幾個 sector,由 1 開始
  • al:讀取幾個 sector
  • es:預設存放資料的 memory 位置的 segment register
  • bx:預設存放資料的 memory 位置的 offset

BIOS 可能因為各種原因沒有成功讀取資料,因此我們需要處理錯誤的狀況。BIOS 在讀取有錯誤時會將 carry flag set 起來,另外在 al register 會有實際讀取多少 sector 的值。我們可以用以下程式來處理讀取 disk 的相關錯誤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
int 0x13

jc disk_error ; jump if carry flag was set

cmp al, <# of sector expected> ; 與我們預期讀取的 sector 數量比較
jne disk_error ; 不相等時跳到處理 error 的區域

disk_error:
mov bx, DISK_ERROR_MSG
call print_string
jmp $

DISK_ERROR_MSG: db "Disk read error!", 0

read hard disk routine

isk_load.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
disk_load:
push dx

mov ah, 0x02 ; BIOS read sector function
mov al, dh ; Read DH sectors
mov ch, 0x00 ; Select cylinder 0
mov dh, 0x00 ; Select head 0
mov cl, 0x02 ; Start reading from second sector
int 0x13 ; BIOS interrupt

jc disk_error

pop dx ; restore dx from the stack
cmp dh, al ; if al (sectors read) != dh (sectors expected)
jne disk_error ; display error message
ret

disk_error:
mov bx, DISK_ERROR_MSG
call print_string
jmp $

DISK_ERROR_MSG db "Disk read error!", 0

print_hex.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
; prints the value of DX as hex.
print_hex:
pusha

mov ax, 5
loop: ; for (ax = 5; ax >= 2; ax--)
cmp ax, 2
jl end_loop
mov cx, 0
shr dx, 1 ; dx >> 1
jnc end_if_1
add cx, 1 ; if cf == 1, cx += 1
end_if_1:

shr dx, 1 ; dx >> 1
jnc end_if_2
add cx, 2 ; if cf == 1, cx += 2
end_if_2:

shr dx, 1 ; dx >> 1
jnc end_if_3
add cx, 4 ; if cf == 1, cx += 4
end_if_3:

shr dx, 1 ; dx >> 1
jnc end_if_4
add cx, 8 ; if cf == 1, cx += 8
end_if_4:

; only bx can be used as an index register
mov bx, HEX_OUT
add bx, ax ; bx = HEX_OUT + ax

cmp cx, 10
jl less_ten
mov byte [bx], 'a' ; [bx] = 'a'
sub cx, 10 ; cx -= 10
less_ten:
add [bx], cx ; [bx] += cx

sub ax, 1 ; ax--
jmp loop
end_loop:

mov bx, HEX_OUT
call print_string

popa
ret

print_string.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
print_string:           ; function name
pusha ; push all register to stack to preserve them

mov ah, 0x0e ; tty mode

loop_start:
cmp byte [bx], 0 ; compare [bx] which is one byte to zero, for null terminating char
je loop_end ; if [bx] == 0, end loop
mov al, [bx] ; move char printed to al
int 0x10 ; call print interrupt
add bx, 1 ; bx + 1, move to next char
jmp loop_start ; run loop body again

loop_end:
popa ; restore all register
ret ; return to callee

boot_sector_load_disk.asm

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[org 0x7c00]

mov [BOOT_DRIVE], dl ; BIOS stores our boot drive in dl, so it's best to remember this for later

mov bp, 0x8000 ; set stack
mov sp, bp

mov bx, 0x9000 ; Load 2 sectors to 0x0000(es):0x9000(bx)
mov dh, 2
mov dl, [BOOT_DRIVE]
call disk_load

mov dx, [0x9000] ; print first loaded word
call print_hex

mov dx, [0x9000 + 512] ; print first word from the second loaded sector
call print_hex

jmp $

%include "print_string.asm"
%include "print_hex.asm"
%include "disk_load.asm"

; global variables
HEX_OUT: db '0x0000', 0
BOOT_DRIVE: db 0

times 510-($-$$) db 0
dw 0xaa55

times 256 dw 0xdada
times 256 dw 0xface

當 CPU 執行在 16-bit mode 時,表示 register 的最大 size 為 16 bit。這表示最大的 address 是 0xffff,也就是 memory 最大只能到 64KB。

那如果 memory 的 size 超過 64KB 該怎麼辦呢?

在 16-bit 下,我們可以用 special register cs、ds、ss 以及 es――稱為 segment register――來超過 64KB 的 memory size 限制。

我們將 memory 想像成被切成好幾塊 segment,這些 segment 以 segment register 為 index。當我們指定某個 16-bit address 時,CPU 會自動以某個 segment register 及我們指定的 address 計算出真正的 memory address。

其中這「某個」segment register 如何決定的?除非特別指定,否則 CPU 會以 instruction 的 context 來決定要用哪個 segment register。例如 mov ax, [0x34ef] 的 instruction 就會預設使用 data segment ds。

那麼 CPU 是怎麼計算實際 address 的呢?

CPU 會將 segment register 的值乘 16,再加上我們指定的 address(也就是 offset)得到實際 address。因為我們通常用 16 進位表示 register 的值,例如 0x32,乘 16 就是將 16 進位的數字左移一格,變成 0x320。如果我們設定 ds 為 0x32,則 mov ax, [0x20] 的 [0x20] 實際 address 是 0x320 + 0x20 = 0x340。

另外,segment register 無法直接用 mov 來 assign 值,需要先將值放到 general register,再 mov 到 segment register 裡,如下:

1
2
mov ax, 0x123
mov ds ds, ax

segment-based 的 address 方式讓我們可以使用更多 memory,因為總共的 address 可以有 0xffff * 16 + 0xffff 個,也就是 1MB 多一點。

寫一個可以印出 hex 內容的 function。

shr 是 bitwise 的 right shift instruction。右移的最後一個 bit 會放到 carry flag 裡。可以用 jc 跟 jnc 來做 branch,jc 是如果 carry flag 有被 set 則 jump,後者是沒 set 的情況會 jump。

第一版:處理最後一個字

這個版本可以印出 0x0006。基本概念是用 right shift 得知 dx 最右邊的 bit 是 0 還是 1,以 4 個 bit 為一單位處理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
[org 0x7c00]

mov dx, 0x1fb6
call print_hex
jmp $

; prints the value of DX as hex.
print_hex:
pusha

; only bx can be used as an index register
mov bx, HEX_OUT
add bx, 5 ; bx = HEX_OUT + 5

shr dx, 1 ; dx >> 1
jnc end_if_1
mov cx, 1 ; if cf == 1
add byte [bx], cl ; [bx] += 1
end_if_1:

shr dx, 1 ; dx >> 1
jnc end_if_2
mov cx, 2
add byte [bx], cl ; [bx] += 2
end_if_2:

shr dx, 1 ; dx >> 1
jnc end_if_3
mov cx, 4
add byte [bx], cl
end_if_3:

shr dx, 1 ; dx >> 1
jnc end_if_4
mov cx, 8
add byte [bx], cl
end_if_4:


mov bx, HEX_OUT
call print_string

popa
ret

%include "print_string.asm"

; global variables
HEX_OUT: db '0x0000', 0

; padding and magic number
times 510-($-$$) db 0
dw 0xaa55

然後遇到超過 10 就會錯…因為 ASCII code 在 '9' 後面不是 'a'。

第二版

修正 10 以上的錯誤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
[org 0x7c00]

mov dx, 0x1fb6
call print_hex
jmp $

; prints the value of DX as hex.
print_hex:
pusha

mov ax, 5
; only bx can be used as an index register
mov bx, HEX_OUT
add bx, ax ; bx = HEX_OUT + ax

mov cx, 0
shr dx, 1 ; dx >> 1
jnc end_if_1
add cx, 1 ; if cf == 1
end_if_1:

shr dx, 1
jnc end_if_2
add cx, 2
end_if_2:

shr dx, 1
jnc end_if_3
add cx, 4
end_if_3:

shr dx, 1
jnc end_if_4
add cx, 8
end_if_4:

cmp cx, 10
jl less_ten
mov byte [bx], 'a'
sub cx, 10
less_ten:
add [bx], cx

mov bx, HEX_OUT
call print_string

popa
ret

%include "print_string.asm"

; global variables
HEX_OUT: db '0x0000', 0

; padding and magic number
times 510-($-$$) db 0
dw 0xaa55

第三版:加上外層 loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
[org 0x7c00]

mov dx, 0x1fb6
call print_hex
jmp $

; prints the value of DX as hex.
print_hex:
pusha

mov ax, 5
loop: ; for (ax = 5; ax >= 2; ax--)
cmp ax, 2
jl end_loop
mov cx, 0
shr dx, 1 ; dx >> 1
jnc end_if_1
add cx, 1 ; if cf == 1, cx += 1
end_if_1:

shr dx, 1 ; dx >> 1
jnc end_if_2
add cx, 2 ; if cf == 1, cx += 2
end_if_2:

shr dx, 1 ; dx >> 1
jnc end_if_3
add cx, 4 ; if cf == 1, cx += 4
end_if_3:

shr dx, 1 ; dx >> 1
jnc end_if_4
add cx, 8 ; if cf == 1, cx += 8
end_if_4:

; only bx can be used as an index register
mov bx, HEX_OUT
add bx, ax ; bx = HEX_OUT + ax

cmp cx, 10
jl less_ten
mov byte [bx], 'a' ; [bx] = 'a'
sub cx, 10 ; cx -= 10
less_ten:
add [bx], cx ; [bx] += cx

sub ax, 1 ; ax--
jmp loop
end_loop:

mov bx, HEX_OUT
call print_string

popa
ret

%include "print_string.asm"

; global variables
HEX_OUT: db '0x0000', 0

; padding and magic number
times 510-($-$$) db 0
dw 0xaa55

第四版:拆成多個檔案

print_string.asm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
print_string:           ; function name
pusha ; push all register to stack to preserve them

mov ah, 0x0e ; tty mode

loop_start:
cmp byte [bx], 0 ; compare [bx] which is one byte to zero, for null terminating char
je loop_end ; if [bx] == 0, end loop
mov al, [bx] ; move char printed to al
int 0x10 ; call print interrupt
add bx, 1 ; bx + 1, move to next char
jmp loop_start ; run loop body again

loop_end:
popa ; restore all register
ret ; return to callee

print_hex.asm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
; prints the value of DX as hex.
print_hex:
pusha

mov ax, 5
loop: ; for (ax = 5; ax >= 2; ax--)
cmp ax, 2
jl end_loop
mov cx, 0
shr dx, 1 ; dx >> 1
jnc end_if_1
add cx, 1 ; if cf == 1, cx += 1
end_if_1:

shr dx, 1 ; dx >> 1
jnc end_if_2
add cx, 2 ; if cf == 1, cx += 2
end_if_2:

shr dx, 1 ; dx >> 1
jnc end_if_3
add cx, 4 ; if cf == 1, cx += 4
end_if_3:

shr dx, 1 ; dx >> 1
jnc end_if_4
add cx, 8 ; if cf == 1, cx += 8
end_if_4:

; only bx can be used as an index register
mov bx, HEX_OUT
add bx, ax ; bx = HEX_OUT + ax

cmp cx, 10
jl less_ten
mov byte [bx], 'a' ; [bx] = 'a'
sub cx, 10 ; cx -= 10
less_ten:
add [bx], cx ; [bx] += cx

sub ax, 1 ; ax--
jmp loop
end_loop:

mov bx, HEX_OUT
call print_string

popa
ret

%include "print_string.asm"

boot_sect.asm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[org 0x7c00]

mov dx, 0x1fb6
call print_hex
jmp $

%include "print_hex.asm"

; global variables
HEX_OUT: db '0x0000', 0

; padding and magic number
times 510-($-$$) db 0
dw 0xaa55

接下來我們可以把所有東西組合起來,寫一個 function 用迴圈印出字串!

boot_sect_print_main.asm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[org 0x7c00]

mov bx, HELLO_MSG
call print_string

mov bx, GOODBYE_MSG
call print_string

jmp $

%include "print_string.asm"

; data
HELLO_MSG:
db 'Hello, World!', 0
GOODBYE_MSG:
db 'Goodbye!', 0

; padding and magic number
times 510-($-$$) db 0
dw 0xaa55

print_string.asm:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
print_string:           ; function name
pusha ; push all register to stack to preserve them

mov ah, 0x0e ; tty mode

loop_start:
cmp byte [bx], 0 ; compare [bx] which is one byte to zero, for null terminating char
je loop_end ; if [bx] == 0, end loop
mov al, [bx] ; move char printed to al
int 0x10 ; call print interrupt
add bx, 1 ; bx + 1, move to next char
jmp loop_start ; run loop body again

loop_end:
popa ; restore all register
ret ; return to callee

組譯再執行,可以看到以下畫面:

nasm 可以 include file,如下所示:

1
2
3
4
%include "print.asm"	; will be replaced by content of the file

...
call my_print ; my_print is in print.asm

在 CPU 的層級,call function 就是 jump 到 routine 的 address 再 jump 回原本所在的 address 繼續執行。

caller 跟 callee 會說好要怎麼傳參數,例如放在某個 register。

caller 的 address 也需要被儲存,這樣 callee 執行完才能回到 caller 繼續執行。CPU 以 register ip 記錄目前要執行的 instruction 的 address,但是我們不能直接 access register ip。

CPU 提供兩個 instruction 來 call function:call 與 ret。call 會將 caller 的 address push 到 stack 接著 jump 到 callee 執行,ret 則會從 stack pop 出 address 並 jump 回到 caller 繼續執行。

接下來我們要考慮的是 register 的內容可能會被 callee 修改,那麼如何在 callee 執行結束後回到 caller 時 register 能夠有原本 caller 所擁有的值?

callee 可以將所有它用到的 register 在開始執行任何工作前先 push 進 stack,等到要 return 回 caller 前再全部 pop 回 register,這樣 caller 繼續執行時在 register 裡就有原本它所使用的值。

為了方便,CPU 提供兩個 instruction 能夠一次 push / pop 所有 register 的值到 stack:pusha 以及 popa。

就是 jump。

jump 有 unconditional jump 跟 conditional jump。

conditional jump 會配合 comparison instruction 使用,例如:

1
2
3
4
5
6
7
8
cmp ax, 3
je then_block
mov bx, 1
jmp the_end

then_block:
mov bx, 2
the_end:

等同:

1
2
3
4
5
6
if (ax == 3) {
bx = 2
}
else {
bx = 1
}

comparison instruction 在比較後會設置 flags register 的值,接著 conditional jump instruction 就能依照 flags register 的內容決定要跳到哪執行。

對於 cmp x, y,conditional jump 有以下幾種:

1
2
3
4
5
6
je	target	; jump if equal (x == y)
jne target ; jump if not equal (x != y)
jl target ; jump if less than (x < y)
jle target ; jump if less than or equal (x <= y)
jg target ; jump if greater than (x > y)
jge target ; jump if greater than or equal (x >= y)

將產生某種類物件的邏輯跟動作包成一個 class。

這樣的 class 為一個 factory class,它的 method 依據 input 產生出對應的具體 object。如果要新增不同的物件只需修改該 method。

UML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Factory {
+createOperate()
}

abstract class Operate {
+X
+Y
+GetResult()
}

class OperateAdd {
+GetResult()
}

class OperateSub {
+GetResult()
}

Factory -> Operate
OperateAdd -up-|> Operate
OperateSub -up-|> Operate

如果要增加 OperateMul 表示乘法,只需要加一個 class OperateMul 並且在 Factory::createOperate() 的 switch 增加一個 case 即可。這樣增加乘法完全不會動到加法及減法。