Những vấn đề chính với mã này là:
- ES: BX đã được trỏ đến phân khúc sai: bù đắp để nạp kernel vào
- ngành sai đã được nạp nên hạt nhân không phải là những gì đã được dự kiến
Người đầu tiên là trong mã này:
mov bx,0x7E00
mov es,bx
xor bx,bx
Câu hỏi muốn tải ngành từ đĩa đến 0x0000:0x7E00
(ES: BX). Mã này đặt ES2 ES: BX thành 0x7E00:0x0000
giải quyết đến địa chỉ thực của 0x7E000
((0x7E00 < < 4) + 0x0000). Tôi nghĩ rằng ý định là để tải 0x07E0
vào ES sẽ mang lại địa chỉ thực của 0x7E00
((0x07E0 < < 4) + 0x0000). Bạn có thể tìm hiểu thêm về 16:16 tính toán địa chỉ bộ nhớ here. Nhân các phân đoạn bằng 16 là giống như chuyển nó sang trái 4 bit.
Vấn đề thứ hai trong mã là ở đây:
mov ah,0x02 ;function
mov al,0x1 ;sectors to read
mov ch,0x0 ;tracks
mov cl,0x2 ;sector number
mov dh,0x0 ;head
int 0x13
Số cho ngành 512 khối thứ hai trên đĩa là 2, không 1. Vì vậy, để khắc phục các mã trên bạn cần phải thiết lập CL phù hợp:
mov cl,0x2 ;sector number
Mẹo tổng thể phát triển Bootloader
các vấn đề khác có thể đi lên chạy mã trên giả lập khác nhau, Máy ảo và phần cứng vật lý thực sự mà cần được giải quyết là:
- Khi BIOS nhảy sang mã của bạn, bạn không thể dựa vào CS, DS, ES, SS, SP sổ đăng ký có giá trị hợp lệ hoặc dự kiến.Chúng sẽ được thiết lập một cách thích hợp khi bộ nạp khởi động của bạn bắt đầu. Bạn chỉ có thể được đảm bảo rằng bộ nạp khởi động của bạn sẽ được tải và chạy từ địa chỉ vật lý 0x00007c00 và số ổ đĩa khởi động được nạp vào thanh ghi DL.
- Đặt SS: SP vào bộ nhớ mà bạn biết sẽ không xung đột với hoạt động của mã của riêng bạn. BIOS có thể đã đặt con trỏ ngăn xếp mặc định của nó ở bất kỳ đâu trong megabyte đầu tiên của RAM có thể sử dụng và địa chỉ. Không có sự đảm bảo nào về vị trí đó và liệu nó có phù hợp với mã bạn viết hay không.
- Cờ chỉ đường được sử dụng bởi
lodsb
, movsb
v.v ... có thể được đặt hoặc xóa. Nếu cờ hướng được đặt không đúng SI/DI sổ đăng ký có thể được điều chỉnh sai hướng. Sử dụng STD
/CLD
để đặt nó theo hướng bạn muốn (CLD = forward/STD = backwards). Trong trường hợp này, mã giả định chuyển động về phía trước, vì vậy, một mã nên sử dụng CLD
. Hơn về vấn đề này có thể được tìm thấy trong một instruction set reference
- Khi nhảy đến một hạt nhân nó thường là một ý tưởng tốt để FAR JMP với nó để nó đúng cách đặt CS: IP đến giá trị mong đợi. Điều này có thể tránh các vấn đề với mã hạt nhân có thể làm tuyệt đối gầnJMPs và GỌI SỐ trong cùng một phân đoạn.
- Nếu nhắm mục tiêu bộ tải khởi động của bạn cho mã 16 bit hoạt động trên bộ xử lý 8086/8088 (VÀ cao hơn), tránh sử dụng thanh ghi 32 bit trong mã lắp ráp. Sử dụng AX/BX/CX/DX/SI/DI/SP/BP thay vì EAX/EBX/ECX/EDX/ESI/EDI/ESP/EBP. Mặc dù không phải là một vấn đề trong câu hỏi này, nó đã là một vấn đề cho những người khác đang tìm kiếm sự giúp đỡ. Bộ vi xử lý 32 bit có thể sử dụng thanh ghi 32 bit ở chế độ thực 16 bit, nhưng 8086/8088/80286 không thể vì chúng là bộ vi xử lý 16 bit mà không truy cập vào thanh ghi 32 bit mở rộng.
- FS và GS đăng ký phân đoạn đã được thêm vào 80386+ CPU. Tránh chúng nếu bạn có ý định nhắm mục tiêu 8086/8088/80286.
Để giải quyết mục đầu tiên và thứ hai mã này có thể được sử dụng gần sự bắt đầu của bộ nạp khởi động:
xor ax,ax ; We want a segment of 0 for DS for this question
mov ds,ax ; Set AX to appropriate segment value for your situation
mov es,ax ; In this case we'll default to ES=DS
mov bx,0x8000 ; Stack segment can be any usable memory
cli ; Disable interrupts to circumvent bug on early 8088 CPUs
mov ss,bx ; This places it with the top of the stack @ 0x80000.
mov sp,ax ; Set SP=0 so the bottom of stack will be @ 0x8FFFF
sti ; Re-enable interrupts
cld ; Set the direction flag to be positive direction
Một vài điều cần lưu ý. Khi bạn thay đổi giá trị của thanh ghi SS (trong trường hợp này là qua MOV
), bộ xử lý giả sử tắt các ngắt cho lệnh đó và giữ chúng cho đến sau hướng dẫn sau đây. Thông thường, bạn không cần phải lo lắng về việc tắt ngắt nếu bạn cập nhật SS theo sau ngay lập tức bằng cách cập nhật SP.Có một lỗi trong bộ vi xử lý 8088 rất sớm, nơi điều này đã không được vinh danh vì vậy nếu bạn đang nhắm mục tiêu các môi trường rộng nhất có thể nó là một đặt cược an toàn để vô hiệu hóa rõ ràng và kích hoạt lại chúng. Nếu bạn không có ý định làm việc trên một lỗi 8088 thì có thể xóa các hướng dẫn CLI
/STI
trong mã ở trên. Tôi biết về lỗi này đầu tay với công việc tôi đã làm vào giữa những năm 80 trên máy tính gia đình của tôi.
Điều thứ hai cần lưu ý là cách tôi thiết lập ngăn xếp. Đối với những người mới đến 8088/8086 lắp ráp 16 bit, ngăn xếp có thể được thiết lập vô số cách. Trong trường hợp này, tôi đặt phần trên cùng của ngăn xếp (phần thấp nhất trong bộ nhớ) tại 0x8000
(SS). Sau đó tôi đặt con trỏ ngăn xếp (SP) thành 0
. Khi bạn push something on the stack trong chế độ thực 16 bit, bộ xử lý đầu tiên sẽ giảm con trỏ ngăn xếp xuống 2 và sau đó đặt 162 bit WORD tại vị trí đó. Do đó, lần đẩy đầu tiên vào ngăn xếp sẽ là 0x0000-2 = 0xFFFE (-2). Sau đó, bạn sẽ có một số SS: SP trông giống như 0x8000:0xFFFE
. Trong trường hợp này, ngăn xếp chạy từ 0x8000:0x0000
đến 0x8000:0xFFFF
.
Khi xử lý ngăn xếp chạy trên 8086 (không áp dụng cho bộ xử lý 80286,80386+), nên đặt con trỏ ngăn xếp (SP) thành số chẵn. Trên 8086 ban đầu nếu bạn đặt SP thành số lẻ, bạn sẽ phải trả 4 clock cycle penalty cho mọi quyền truy cập vào không gian ngăn xếp. Kể từ khi 8088 có một bus dữ liệu 8 bit, hình phạt này không tồn tại, nhưng tải 162 bit từ trên 8086 mất 4 chu kỳ đồng hồ trong khi nó mất 8 chu kỳ đồng hồ trên 8088 (hai bộ nhớ 8 bit đọc).
Cuối cùng, nếu bạn muốn thiết lập một cách rõ ràng CS: IP để CS được thiết lập đúng vào thời điểm các JMP là đầy đủ (để kernel) sau đó nó được khuyến khích để làm một FAR JMP (Xem Các hoạt động ảnh hưởng đến đăng ký phân khúc/FAR Jump). Trong NASM cú pháp JMP
sẽ trông như thế này:
jmp 0x07E0:0x0000
Một số (ví dụ: MASM/MASM32) lắp ráp không có hỗ trợ trực tiếp để mã hóa một FAR Jmp vì vậy một cách thức mà nó có thể được thực hiện bằng tay là như thế này:
db 0x0ea ; Far Jump instruction
dw 0x0000 ; Offset
dw 0x07E0 ; Segment
Nếu sử dụng GNU lắp ráp nó sẽ trông giống như:
ljmpw $0x07E0,$0x0000
Trước khi ra bất kỳ BIOS ngắt bạn cần phải thiết lập stack ('SS' và 'SP' registe rs). Bạn cũng cần phải gọi 'CLD' hoặc' STD' trước khi sử dụng các hàm lodsb và các hàm liên quan. Mã của bạn giả định tăng tự động nên sau khi thiết lập cuộc gọi ngăn xếp 'CLD'. Bạn cũng không có vẻ để thiết lập đăng ký 'DS' với một phân đoạn thích hợp (lodsb có khả năng sẽ không hoạt động nếu không). Nó không phải là rõ ràng từ những gì bạn đã trình bày những gì điểm gốc cho hình ảnh hạt nhân của bạn đã được thiết lập để. Makefile của bạn hoặc lệnh bạn sử dụng để liên kết, biên dịch, tạo hình ảnh đĩa sẽ có lợi trong câu hỏi của bạn. –
'mov bx, 0x7E00'; 'mov es, bx',' xor bx, bx' có vẻ như nó có thể sai. Mã của bạn cho thấy bạn có ý định tải sector từ đĩa tại địa chỉ vật lý '0x0000: 0x7E00' =' 0x7E00', nhưng bạn đặt phân đoạn 'ES':' BX' thành '0x7E00' và' BX' thành 0 trong một địa chỉ vật lý của 0x7E00 << 4 + 0x0000 = '0x7E000'. Tôi nghĩ rằng bạn có nghĩa là để đặt 'ES' để' 0x07E0' mà sẽ mang lại một địa chỉ vật lý của 0x07E0 << 4 + 0x0000 = địa chỉ vật lý '0x7E00'. Bạn về cơ bản nạp hạt nhân của bạn cao hơn nhiều vào bộ nhớ hơn bạn dự định.Thử thay đổi '' mov bx, 0x7E00' thành 'mov bx, 0x07E0' –
Ký quỹ của hạt nhân của tôi là sector 0x1. Tôi đang biên dịch với việc sử dụng 'nasm bootloader.asm -o bootloader.img '(bootloader.asm là mã nằm trong chủ đề chính). sau đó thiết lập bootloader.img làm đĩa mềm trong VirtualBox. về 'lodsb' nó đã làm việc khi thay vào đó' jc' tôi đã sử dụng 'jnc' để hiển thị thông báo lỗi khi đọc thực sự kết thúc thành công. – vakus