Прочитайте записать сектор с жесткого диска с int 13h
У меня есть простая программа. Он должен прочитать первый сектор с жесткого диска (не mbr) и записать его в 0 сектор (mbr). Но это не работает. Я думаю, что это связано с неправильным DAP. Благодарю.
[bits 16]
[org 0x7c00]
;clear screen
start:
mov ax, 0x3
int 0x10
;reset the hard drive
xor ah, ah
mov dl, 0x80
int 0x13
jnz error
;read the second sector
mov si, DAP
mov ah, 0x42
int 0x13
mov si, data
call print_string
jmp $
DAP:
db 0x10 ;size of DAP
db 0x0 ;zero
db 0x1 ;number of sectors to read
db 0x0 ;zero
;point to memory
dw 0x0 ;offset
dw 0x0 ;segment
dq 0x1 ;disk address
DAP2:
db 0x10
db 0x0
db 0x1
db 0x0
dw 0x0
dw 0x0
dd 0x0
dd 0x0
print_string:
mov ax, 0xb800
mov es, ax
xor di, di
mov cx, 8
rep movsw
ret
data: db 'H',2,'e',2,'l',2,'l',2
error:db 'E',2,'r',2,'r',2
times 510 - ($ - $$) db 0
dw 0xaa55
UPD: новый код
[bits 16]
[org 0x7c00]
;clear screen
start:
; mov ah, 0
; push ax
; pop ds
mov ax, 0x3
int 0x10
;reset the hard drive
xor ah, ah
mov dl, 0x80
int 0x13
jc error
;read the second sector
mov si, DAP
mov ah, 0x42
int 0x13
mov si, data
call print_string
jmp $
DAP:
db 0x10 ;size of DAP
db 0x0 ;zero
db 0x1 ;number of sectors to read
db 0x0 ;zero
;point to memory
dw 0x0 ;offset
dw 0x8c00 ;segment
dq 0x1 ;disk address
DAP2:
db 0x10
db 0x0
db 0x1
db 0x0
dw 0x0
dw 0x8c00
dq 0x2
print_string:
mov ax, 0xb800
mov es, ax
xor di, di
mov si, 0x8c00
mov cx, 8
rep movsw
ret
data: db 'H',2,'e',2,'l',2,'l',2
error:db 'E',2,'r',2,'r',2
endp:
times 510 - ($ - $$) db 0
dw 0xaa55
PS Я использую Bochs.
4 ответа
Немного некрофилии; надеюсь, ваши навыки сборки улучшились за это время. Но на всякий случай...
Цитируя @Alexey Frunze, "Вам нужно обратить внимание на то, что вы делаете". В дополнение к ошибкам, описанным в других ответах, вот некоторые из моих наблюдений:
Ваш эмулятор слишком добрый
Ваш код выглядит как загрузчик. Вы предполагаете, что BIOS загрузит ваш код в
0x0000:0x7C00
, но вы не можете быть уверены, что он на самом деле не загружается в0x07C0:0000
или любой другой эквивалентный адрес. Читайте о сегментации.Вы не можете инициализировать регистры любого сегмента. Вам, вероятно, это сойдет с рук, потому что ваш эмулятор добрый и правильно инициализирует
cs
,ds
, а такжеes
в0x0000
,
Вы можете решить обе эти проблемы следующим образом:
[bits 16]
[org 0x7C00]
jmp 0x0000:start_16 ; ensure cs == 0x0000
start_16:
; initialise essential segment registers
xor ax, ax
mov ds, ax
mov es, ax
Фундаментальные недоразумения
В случае ошибки вы переходите непосредственно к строке, а не к исполняемому коду. Господь знает только, что сделает компьютер, если это произойдет.
Вы проверяете возвращаемое значение (CF) сброса привода, но не считываете само чтение. В случае сбоя чтения, вы должны перезагрузить накопитель и повторить попытку чтения. Сделайте это в цикле на несколько попыток (скажем, 3), если диск работает. Если сброс диска не удастся, скорее всего, что-то более серьезное не так, и вам следует внести залог.
Более безопасный подход
Я бы предложил использовать int 0x13, ah = 0x02
, Вы используете расширенную функцию BIOS, которая может поддерживаться не во всех системах (поддержка эмулятора может быть ненадежной, не говоря уже о ленивых реализациях BIOS, найденных на некоторых современных аппаратных средствах). Вы находитесь в реальном режиме - вам не нужно делать что-то необычное. Было бы лучше перейти в защищенный режим с долгосрочной целью написания драйвера PM для обработки дискового ввода-вывода.
Пока вы находитесь в реальном режиме, здесь есть отдельная функция, которая будет считывать один или несколько секторов с диска, используя простые функции BIOS. Если вы заранее не знаете, какой сектор (ы) вам нужен, вам придется добавить дополнительные проверки, чтобы обеспечить многодорожечное чтение.
; read_sectors_16
;
; Reads sectors from disk into memory using BIOS services
;
; input: dl = drive
; ch = cylinder[7:0]
; cl[7:6] = cylinder[9:8]
; dh = head
; cl[5:0] = sector (1-63)
; es:bx -> destination
; al = number of sectors
;
; output: cf (0 = success, 1 = failure)
read_sectors_16:
pusha
mov si, 0x02 ; maximum attempts - 1
.top:
mov ah, 0x02 ; read sectors into memory (int 0x13, ah = 0x02)
int 0x13
jnc .end ; exit if read succeeded
dec si ; decrement remaining attempts
jc .end ; exit if maximum attempts exceeded
xor ah, ah ; reset disk system (int 0x13, ah = 0x00)
int 0x13
jnc .top ; retry if reset succeeded, otherwise exit
.end:
popa
retn
Ваша функция печати предполагает цветной монитор (путем записи в видеопамять в 0xB8000). Опять вы в реальном режиме. Будь проще. Используйте сервис BIOS:
; print_string_16
;
; Prints a string using BIOS services
;
; input: ds:si -> string
print_string_16:
pusha
mov ah, 0x0E ; teletype output (int 0x10, ah = 0x0E)
mov bx, 0x0007 ; bh = page number (0), bl = foreground colour (light grey)
.print_char:
lodsb ; al = [ds:si]++
test al, al
jz .end ; exit if null-terminator found
int 0x10 ; print character
jmp .print_char ; repeat for next character
.end:
popa
retn
Пример использования
load_sector_2:
mov al, 0x01 ; load 1 sector
mov bx, 0x7E00 ; destination (might as well load it right after your bootloader)
mov cx, 0x0002 ; cylinder 0, sector 2
mov dl, [BootDrv] ; boot drive
xor dh, dh ; head 0
call read_sectors_16
jnc .success ; if carry flag is set, either the disk system wouldn't reset, or we exceeded our maximum attempts and the disk is probably shagged
mov si, read_failure_str
call print_string_16
jmp halt ; jump to a hang routine to prevent further execution
.success:
; do whatever (maybe jmp 0x7E00?)
read_failure_str db 'Boot disk read failure!', 13, 10, 0
halt:
cli
hlt
jmp halt
Последний, но тем не менее важный...
Ваш загрузчик не настроил стек. Код, который я предоставил, использует стек для предотвращения сбоев в реестре. Перед загрузчиком доступно почти 30 КБ (< 0x7C00), так что вы можете просто сделать это где-то в начале вашего загрузчика:
xor ax, ax
cli ; disable interrupts to update ss:sp atomically (AFAICT, only required for <= 286)
mov ss, ax
mov sp, 0x7C00
sti
Уф! Много переварить. Заметьте, я пытался сохранить гибкость автономных функций, чтобы вы могли повторно использовать их в других 16-битных программах реального режима. Я бы посоветовал вам попытаться написать более модульный код и придерживаться этого подхода, пока вы не станете более опытным.
Например, если вы не можете использовать расширенную функцию чтения, возможно, вам следует написать функцию, которая принимает DAP или указатель на нее в стеке. Конечно, вы будете тратить впустую пространство кода, выталкивая туда данные в первую очередь, но как только они появятся, вы можете просто настроить необходимые поля для последующих чтений, вместо того, чтобы много DAP занимало память. Пространство стека может быть восстановлено позже.
Не расстраивайтесь, сборка занимает много времени, и чудовищное внимание к деталям... нелегко, когда ломаешь все это на работе, поэтому в моем коде могут быть ошибки!:)
Прежде всего, вам нужно проверить cf
и не zf
чтобы увидеть, если вызов BIOS успешно. Поправьте jnz error
,
Во-вторых, вы, кажется, полагаетесь на ds
быть равным 0. Это не обязательно будет 0. Установите его в 0.
То же самое для flags.df
, не гарантируется, что будет 0. Установите его на 0. Проверьте документацию на rep
, movs*
а также cld
,
В-третьих, вы просите BIOS прочитать сектор и записать его в физический адрес 0 в памяти. Тем самым вы перезаписываете таблицу векторов прерываний (которая начинается там и занимает 1 КБ) и повреждает систему, требуя перезагрузки. Выберите лучший адрес. Лучше всего будет сразу после окончания загрузочного сектора в памяти. Но вам также нужно убедиться, что стек не существует, поэтому вам нужно также установить стек в известном месте.
Вы должны обратить внимание на то, что вы делаете.
Пример минимального NASM BIOS, который загружает сектор с кодом и переходит на него без проверки ошибок и DAP:
use16
org 0x7C00
; For greater portability you should
; do further initializations here like setup the stack and segments.
; Load stage 2 to memory.
mov ah, 0x02
mov al, 1
; This may not be necessary as many BIOS setup is as an initial state.
mov dl, 0x80
mov ch, 0
mov dh, 0
mov cl, 2
mov bx, stage2
int 0x13
jmp stage2
; Magic bytes.
times ((0x200 - 2) - ($ - $$)) db 0x00
dw 0xAA55
stage2:
; Print 'a'.
mov ax, 0x0E61
int 0x10
cli
hlt
; Pad image to multiple of 512 bytes.
times ((0x400) - ($ - $$)) db 0x00
Скомпилируйте и запустите:
nasm -f bin -o main.img main.asm
qemu-system-i386 main.img
Ожидаемый результат: a
выводится на экран, а затем программа останавливается.
Проверено на Ubuntu 14.04.
Пример Saner GAS с использованием сценария компоновщика и более правильной инициализации (регистры сегментов, стек) на моем GitHub.
load:
; Load sectors routine : bootdrv-drive , snum-sectors to load
;ES:BX where to load ex: mov ax,0000h mov es,ax mov bx, 7c00h
push bx
push ds
mov verify,0h
.reset:
cmp verify,5h
je Err1
add verify,1h
mov ax, 0 ; Reset Disk
mov dl, [BootDrv] ; Drive to reset
int 13h ;
jc .reset ; Failed -> Try again
pop ds
pop bx
mov verify,0h
.read:
cmp verify,5h
je Err1
add verify,1h
mov ah, 2 ; Interrupt 13h,2 => Read disk sectors
mov al, snum ; how many sectors to read
mov cx, fsect ; cl-first sector to be r/w ch-track containing sector
mov dh, head ; head=0
mov dl, [BootDrv] ; Drive=boot drive
int 13h ; Read! ES:BX = data from disk
jc .read ; failed -> Try again
retn