理解分割的问题(尝试写一个简单的OS)

Problems in understanding segmentation (Trying to write a simple OS)

我试图学习低级的东西,所以我认为也许学习 OS 的工作原理将是最好的选择,我开始学习: https://github.com/cfenollosa/os-tutorial

这是代码:

main.asm

[org 0x7c00]
mov bp, 0x9000
mov sp, bp
mov bx,0x1000
mov dh,1
call disk_load
call switch_to_pm

%include "print.asm"
%include "gdt.asm"
%include "32print.asm"
%include "32switch.asm"
%include "disk_func.asm"

[bits 32]
BEGIN_PM:
mov ebx, MSG_PROT_MODE
mov eax,0
mov ch,1
mov cl,0
call print_string
mov ch,0
call print_string
mov ebx,MSG_LOADED_Kernel
mov eax,30
mov cl,1
mov ch,0
call print_string
jmp 0x1000

MSG_PROT_MODE db "Test White ... ",0
MSG_LOADED_Kernel db " Test Red ... ",0
; bootsector
times 510-($-$$) db 0
dw 0xaa55

test_load:
mov ebx,MSG_PROMPT
sub ebx,test_load
add ebx,0x1000
mov cl,2
mov ch,0
mov eax,58
call CODE_SEG:print_string
jmp $

MSG_PROMPT db "Test Green ... ",0
times 512-($-test_load) db 0

print.asm

print:
pusha
mov ah,0x0e
start:
mov al,[bx]
cmp al,0
je done
int 0x10
add bx,1
jmp start
done:
popa
ret

disk_func.asm

disk_load:
pusha
mov ah,0x02 ;<= Read
mov al,dh ;<= Sectors to read
mov cl,0x02 ;<= Sector
mov ch,0x00 ;<= Cylinder
push edx
mov dh,0x00 ;<= Head number
int 0x13
pop edx
jc disk_error
cmp al, dh
jne sectors_error
popa
ret
disk_error:
mov ecx, DISK_ERROR
call print
mov dh, ah
jmp disk_loop

sectors_error:
mov ecx, SECTORS_ERROR
call print

disk_loop:
jmp $

DISK_ERROR:
db "Disk read error", 0

SECTORS_ERROR:
db "Incorrect number of sectors read", 0

gdt.asm

gdt_start:
dd 0x0
dd 0x0
;8 null bytes

; GDT for code segment. base = 0x00000000, length = 0xfffff
gdt_code:
dw 0xffff    ; segment length, bits 0-15
dw 0x0       ; segment base, bits 0-15
db 0x0       ; segment base, bits 16-23
db 10011010b ; flags (8 bits)
db 11001111b ; flags (4 bits) + segment length, bits 16-19
db 0x0       ; segment base, bits 24-31

; GDT for data segment. base and length identical to code segment
gdt_data:
dw 0xffff
dw 0x0
db 0x0
db 10010010b
db 11001111b
db 0x0

gdt_end:
gdt_desc:
dw gdt_end - gdt_start - 1 ; size (16 bit)
dd gdt_start ; address (32 bit)


CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

32switch.asm

[bits 16]
switch_to_pm:
cli
lgdt [gdt_desc]
mov eax, cr0
or eax, 0x1
mov cr0, eax
jmp CODE_SEG:init_pm

[bits 32]
init_pm:
mov ax, DATA_SEG
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax

mov ebp, 0x90000
mov esp, ebp

call BEGIN_PM

32print.asm

[bits 32]

VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f
RED_ON_BLACK equ 0x0c
GREEN_ON_BLACK equ 0x0a

print_string:
pusha
cmp cl,1
je skip_print32_red
cmp cl,2
je skip_print32_red
mov cl,0
skip_print32_red:
mov edx, VIDEO_MEMORY
add edx,eax
cmp ch,1
je clear_scrn

print_string_loop:
mov al, [ebx] ; [ebx] is the address of our character
print32_color:
cmp cl,1
je print_with_red
cmp cl,2
je print_with_green
mov ah, WHITE_ON_BLACK
jmp print32_skip_red
print_with_red:
mov ah, RED_ON_BLACK
jmp print32_skip_red
print_with_green:
mov ah, GREEN_ON_BLACK
print32_skip_red:
cmp al, 0 ; check if end of string
je print_string_done
mov [edx], ax ; store character + attribute in video memory
add ebx, 1 ; next char
add edx, 2 ; next video memory position
jmp print_string_loop
print_string_done:
popa
ret

clear_scrn:
mov al, 0x20
mov ah, WHITE_ON_BLACK
mov [edx], ax ; store character + attribute in video memory
add edx, 2 ; next video memory position
cmp edx, 0xb8492
je print_string_done
jmp clear_scrn

make_32print_red:
mov cl, 1
jmp print32_color

在 32switch.asm 文件中,我了解到因为 init_pm 以 [位 32] 开头并使用 32 位寄存器,所以跳转到它需要 CODE_SEG 因为 CODE_SEG启用 32 位但后来我不明白为什么它不需要用 CODE_SEG 调用 BEGIN_PM 因为 BEGIN_PM 也使用 32 位寄存器。也可以在 BEGIN_PM 中调用 print_string 并跳转到 test_load 也不需要 CODE_SEG 但在 test_load 中必须使用 CODE_SEG 来调用 print_string 否则什么都不做

"test_load is going to be read from hdd to memory and jump to 0x1000 in BEGIN_PM is the jump to test_load"

我真的很困惑,有人可以向我解释为什么在那些地方需要它而在其他地方不需要吗? 还有为什么从 test_load 调用打印字符串需要 CODE_SEG 但从 BEGIN_PM 调用它不需要?

我正在使用 nasm 进行汇编,而 qemu 用于 运行 二进制文件

我对汇编和低级别的东西不熟悉,所以我可能理解错了

编辑: 我试图以某种方式解决它并理解它,我发现 test_load 如果我这样做

mov edx,print_string
call edx

它有效但是

call print_string

没有

我明白了 因为 test_load 和 print 都组装在一起 NASM 编译它们就好像它们在执行时将保持原样,但 test_load 实际上将从硬盘读取并放入内存中的 0x1000所以他们的距离会和NASM想的不一样 当我这样做时

call print_string

和NASM组装它,它使用这个

e8 21 fe ff ff          call   0xfffffe26

fffffe26 代表 -474 从调用到 print_string 的距离,如果它们完全按照写入的方式放入内存中,它将完全正常工作 但由于 test_load 在 0x1000 加载,它应该是 (-1000+512)+(-474) 所以

call print_string

不会工作

原因

mov edx
call edx

有效是因为它调用地址本身并且不再关心距离 它与带有CODE_SEG的那个相同,因为它认为作为远跳它调用地址而不使用距离