【问题标题】:Bochs: assembly far jump got lost in bogus memory area (invalid opcode error)Bochs:汇编远跳转在伪造的内存区域中丢失(无效的操作码错误)
【发布时间】:2016-07-21 02:51:27
【问题描述】:

我开始在(NASM)汇编中开发一个小型玩具操作系统,只是为了我的娱乐。 我编写了一个引导加载程序,它将第一个(也是唯一一个)文件从名为“kernel.sys”的 FAT12 文件系统加载到偏移量0x7E00 的内存中。 在实模式下,内核只通过 BIOS 设置合适的视频模式,进入 32-bit (Protected) 模式。这就是我的问题所在。

首先,我设置了一个带有 3 个描述符(空、环 0 代码、环 0 数据)的 GDT,并将其直接加载到内存区域0x0500。然后我使用LGDT 指令告诉处理器,然后我在CR0 寄存器中设置PE 位,我想通过远跳转进入保护模式以设置适当的段(0x08 - 代码段在GDT) 和指令指针。

第一个版本在 QEMU 中工作,但在 Bochs 中没有。 Bochs 需要在远跳之前设置段,所以我在我的代码中修改了这个:直接在远跳之前,我从我的 GDT 中加载带有数据段的选择器。但是,由于“操作码无效”错误,Bochs 仍然无法进入保护模式。

请帮我解决这个错误!

这是我的内核代码: (请注意标签b32 从未到达!)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;                                                                  ;;
;;                           16-BIT ENTRY                           ;;
;;                                                                  ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

use16
org 0x7e00
jmp start

sys_gdt        equ 0x00000500
sys_gdt_ring0c equ 0x00000508
sys_gdt_ring0d equ 0x00000510
sys_gdtr       equ 0x00000518

start:
    cli

    mov ax, 0
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov sp, 0x1000
    sti

    mov ax, 3
    int 0x10

set_a20:
    in al, 0x64
    test al, 2
    jnz set_a20

    mov al, 0xd1
    out 0x64, al

test_a20:
    in al, 0x64
    test al, 2
    jnz test_a20

    mov al, 0xdf
    out 0x60, al

    mov dword [sys_gdt+0], 0
    mov dword [sys_gdt+4], 0

    mov word [sys_gdt_ring0c+0], 0xffff
    mov word [sys_gdt_ring0c+2], 0
    mov byte [sys_gdt_ring0c+4], 0
    mov byte [sys_gdt_ring0c+5], 10011010b
    mov byte [sys_gdt_ring0c+6], 01001111b
    mov byte [sys_gdt_ring0c+7], 0

    mov word [sys_gdt_ring0d+0], 0xffff
    mov word [sys_gdt_ring0d+2], 0
    mov byte [sys_gdt_ring0d+4], 0
    mov byte [sys_gdt_ring0d+5], 10010010b
    mov byte [sys_gdt_ring0d+6], 01001111b
    mov byte [sys_gdt_ring0d+7], 0

    mov word  [sys_gdtr+0], sys_gdtr-sys_gdt-1
    mov dword [sys_gdtr+2], sys_gdt

    cli
    lgdt [sys_gdtr] ;; :96

    mov eax, cr0
    or eax, 0x1
    mov cr0, eax

    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax
    mov esp, 0x90000

    jmp 0x08:b32

use32

b32:
    mov cx, 5
    jmp $

这是 Bochs 日志:

00014041550i[BIOS ] Booting from 0000:7c00
00015625085e[CPU0 ] write_virtual_checks(): write beyond limit, r/w
00015625085i[CPU0 ] CPU is in protected mode (active)
00015625085i[CPU0 ] CS.d_b = 32 bit
00015625085i[CPU0 ] SS.d_b = 32 bit
00015625085i[CPU0 ] EFER   = 0x00000000
00015625085i[CPU0 ] | RAX=0000000060000010  RBX=0000000000000204
00015625085i[CPU0 ] | RCX=0000000000090000  RDX=0000000000000fff
00015625085i[CPU0 ] | RSP=0000000000090000  RBP=0000000000000000
00015625085i[CPU0 ] | RSI=00000000000e018e  RDI=0000000000008000
00015625085i[CPU0 ] |  R8=0000000000000000   R9=0000000000000000
00015625085i[CPU0 ] | R10=0000000000000000  R11=0000000000000000
00015625085i[CPU0 ] | R12=0000000000000000  R13=0000000000000000
00015625085i[CPU0 ] | R14=0000000000000000  R15=0000000000000000
00015625085i[CPU0 ] | IOPL=0 id vip vif ac vm rf nt of df if tf sf zf af PF cf
00015625085i[CPU0 ] | SEG selector     base    limit G D
00015625085i[CPU0 ] | SEG sltr(index|ti|rpl)     base    limit G D
00015625085i[CPU0 ] |  CS:0008( 0001| 0|  0) 00000000 000fffff 0 1
00015625085i[CPU0 ] |  DS:0010( 0002| 0|  0) 00000000 000fffff 0 1
00015625085i[CPU0 ] |  SS:0010( 0002| 0|  0) 00000000 000fffff 0 1
00015625085i[CPU0 ] |  ES:0010( 0002| 0|  0) 00000000 000fffff 0 1
00015625085i[CPU0 ] |  FS:0010( 0002| 0|  0) 00000000 000fffff 0 1
00015625085i[CPU0 ] |  GS:0010( 0002| 0|  0) 00000000 000fffff 0 1
00015625085i[CPU0 ] |  MSR_FS_BASE:0000000000000000
00015625085i[CPU0 ] |  MSR_GS_BASE:0000000000000000
00015625085i[CPU0 ] | RIP=0000000000007ebb (0000000000007eb9)
00015625085i[CPU0 ] | CR0=0x60000011 CR2=0x0000000000000000
00015625085i[CPU0 ] | CR3=0x00000000 CR4=0x00000000
00015625085i[CPU0 ] 0x0000000000007eb9>> add byte ptr ds:[eax], al : 0000
00015625085i[CMOS ] Last time is 1459506108 (Fri Apr  1 12:21:48 2016)
00015625085i[     ] restoring default signal behavior
00015625085i[CTRL ] quit_sim called with exit code 1

这是我的引导程序:

use16
jmp start

    OEMLabel          db 'SYRACUSE'
    BytesPerSector    dw 512
    SectorsPerCluster db 1
    ReservedForBoot   dw 1
    NumberOfFats      db 2
    RootDirEntries    dw 224
    LogicalSectors    dw 2880
    MediumByte        db 0xf0
    SectorsPerFat     dw 9
    SectorsPerTrack   dw 18
    Heads             dw 2
    HiddenSectors     dd 0
    LargeSectors      dd 0
    DriveNo           dw 0
    Signature         db 41
    VolumeID          dd 0
    VolumeLabel       db 'Syracuse1.0'
    FileSystem        db 'FAT12   '

chs_lba:
    sub ax, 2
    xor cx, cx
    mov cl, byte [SectorsPerCluster]
    mul cx
    add ax, word [datasector]
    ret

lba_chs:
    xor dx, dx
    div word [SectorsPerTrack]
    inc dl
    mov byte [absoluteSector], dl
    xor dx, dx
    div word [Heads]
    mov byte [absoluteHead], dl
    mov byte [absoluteTrack], al
    ret

print:
    pusha
    mov ah, 0xe
.repeat:
    lodsb
    cmp al, 0
    je .done
    int 0x10
    jmp short .repeat
.done:
    popa
    ret

read_sectors:
    mov di, 5
.loop:
    pusha

    call lba_chs
    mov ah, 2
    mov al, 1
    mov ch, byte [absoluteTrack]
    mov cl, byte [absoluteSector]
    mov dh, byte [absoluteHead]
    mov dl, byte [DriveNo]
    int 0x13
    jnc .done

    xor ax, ax
    int 0x13

    dec di
    popa
    jnz .loop
    int 0x18
.done:
    popa

    inc ax
    add bx, word [BytesPerSector]
    loop read_sectors
    ret

start:
    cli
    mov ax, 0x07c0
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ax, 0
    mov ss, ax
    mov sp, 0xffff
    sti

load_root:
    xor cx, cx
    xor dx, dx
    mov ax, 32
    mul word [RootDirEntries]
    div word [BytesPerSector]
    xchg ax, cx

    mov al, byte [NumberOfFats]
    mul word [SectorsPerFat]
    add ax, word [ReservedForBoot]
    mov word [datasector], ax
    add word [datasector], cx

    mov bx, 0x0200
    call read_sectors

    mov cx, word [RootDirEntries]
    mov di, 0x0200

.loop:
    push cx

    mov cx, 11
    mov si, kernel

    push di
    rep cmpsb
    pop di
    je load_fat

    pop cx
    add di, 32
    loop .loop

    jmp failure

load_fat:
    mov dx, word [di+0x001a]
    mov word [cluster], dx

    xor ax, ax
    mov al, byte [NumberOfFats]
    mul word [SectorsPerFat]
    mov cx, ax

    mov ax, word [ReservedForBoot]

    mov bx, 0x0200
    call read_sectors

    mov ax, 0x7e00
    mov es, ax
    mov bx, 0x0000

load_kernel:
    mov ax, word [cluster]
    call chs_lba
    xor cx, cx
    mov cl, byte [SectorsPerCluster]
    call read_sectors

    mov ax, word [cluster]
    mov cx, ax
    mov dx, ax
    shr dx, 1
    add cx, dx
    mov bx, 0x0200
    add bx, cx
    mov dx, word [bx]
    test ax, 1
    jnz .odd

.even:
    and dx, 0000111111111111b
    jmp .done

.odd:
    shr dx, 4

.done:
    mov word [cluster], dx
    cmp dx, 0x0ff0
    jb load_kernel

    pusha
    mov di, 0x7e00
    xor ax, ax
    mov cx, 512
    rep stosb


execute_kernel:
    ;push word 0x7e00
    ;push word 0x0000
    ;retf

    jmp 0x7e00:0x0000

failure:
    mov si, msg
    call print

    mov ah, 0
    int 0x16
    int 0x19

absoluteSector db 0
absoluteHead   db 0
absoluteTrack  db 0

datasector dw 0
cluster    dw 0

kernel db 'KERNEL  SYS'
msg    db 'MISSING KERNEL. Press any key to reboot...', 0xA, 0xD, 0

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

【问题讨论】:

  • 在 bochs 2.6.8 中工作正常。我希望当你说你加载到地址 0x7E00:0x0000 时这只是一个错字。
  • 哦。我已经加载它以抵消0x7E00。还在为你工作吗?
  • 好吧,我没有你的加载器,所以我在0x7C00 加载它作为引导扇区,但它有效,我不明白为什么它不能在其他任何地方工作。另外,请检查您的清单/反汇编以查看 7eb9 的内容,因为 bochs 似乎认为您在那里有 00 00(这就是错误的地方)。更不用说只使用内置调试器和单步。
  • 嗯。我在那里附上了我的启动代码。 7eb9 是标签b32 在物理内存中的地址,我猜。
  • 不,您已将其加载到 segment 0x7E00 这是物理地址 0x7E000 但代码需要物理地址 0x7E00

标签: assembly x86 kernel protected-mode bochs


【解决方案1】:

您知道实模式寻址使用16*segment+offset 作为物理地址,对吧?您在0x7E00:0000 加载代码,因此是物理地址0x7E000(注意3 个零)。但是您的内核需要地址 0x7E00(注意 2 个零)。

您的代码有双重错误。首先,您实际上跳转到偏移量0,因此您应该使用org 0(这是默认设置)。其次,必须为实模式段调整物理地址,即jmp dword 0x8:b32+0x7e000。这将修复当前代码,但 32 位部分将再次使用错误的org

你让自己的生活变得不必要地复杂。通常的最佳实践是将代码加载到前 64k 内的地址中,您可以在其中使用段 0 和 16 位偏移,这些偏移在实际和平面保护模式下都直接映射到物理内存。因此,我建议加载到0:0x8000

【讨论】:

  • 哦,真的。正如您所说,0:0x8000 仍在工作,但到目前为止,我有一个问题。我们知道引导加载程序将内核加载到ES:BX 指向的地址,所以如果ES = 0BX = 0x8000,并且内核包含ORG 0x8000,则代码完全可以工作。但是如果我设置了ES:BX = 0:0x7e00ORG 0x7e00,那么我的内核就不能工作,但是为什么呢?
  • 不确定你的代码现在看起来如何,但原来的代码在0x7e00 处归零了一个块,这样就可以清除你加载的内核;)不知道你为什么这样做。
  • 我的意思是它已经被删除,在我当前的加载器代码中,rep stosb 指令已经消失了。但是如果我将内核加载到0:0x7e00,那现在也行不通。我还在内核中更正了ORG
  • 因为你可能将 FAT 加载到同一个地方?
  • 没有。我猜你这么说是因为在标签load_fat 下有一个指向0:0x7e00,但是,如果你看到这个指向更准确,你会注意到代码会将ES:BX 指向内核所在的地址准确地加载到。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-07
  • 2022-01-11
  • 2012-03-01
  • 2018-07-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多