首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >QEMU呼叫到错误的地址

QEMU呼叫到错误的地址
EN

Stack Overflow用户
提问于 2022-06-26 16:49:36
回答 1查看 69关注 0票数 0

我一直在做一个小的osdev项目。到目前为止,我已经开始使用A20、GDT、保护模式(32位)和磁盘加载来运行C代码,但是函数调用不起作用。我已经确认实际二进制没有问题(ndisasm -b 32 lizard.bin):

代码语言:javascript
运行
复制
... irrelevant bootloader code ...
00000200  8D4C2404          lea ecx,[esp+0x4]
00000204  83E4F0            and esp,byte -0x10
00000207  FF71FC            push dword [ecx-0x4]
0000020A  55                push ebp
0000020B  89E5              mov ebp,esp
0000020D  51                push ecx
0000020E  83EC14            sub esp,byte +0x14
00000211  C745F400000000    mov dword [ebp-0xc],0x0
00000218  83EC0C            sub esp,byte +0xc
0000021B  8D45F4            lea eax,[ebp-0xc]
0000021E  50                push eax
0000021F  E82F000000        call 0x253
00000224  83C410            add esp,byte +0x10
00000227  8945F4            mov [ebp-0xc],eax
0000022A  FA                cli
0000022B  F4                hlt
0000022C  83EC0C            sub esp,byte +0xc
0000022F  8D45F4            lea eax,[ebp-0xc]
00000232  50                push eax
00000233  E81B000000        call 0x253
00000238  83C410            add esp,byte +0x10
0000023B  8945F4            mov [ebp-0xc],eax
0000023E  83EC0C            sub esp,byte +0xc
00000241  8D45F4            lea eax,[ebp-0xc]
00000244  50                push eax
00000245  E809000000        call 0x253
0000024A  83C410            add esp,byte +0x10
0000024D  8945F4            mov [ebp-0xc],eax
00000250  90                nop
00000251  EBFD              jmp short 0x250
00000253  55                push ebp
00000254  89E5              mov ebp,esp
00000256  83EC10            sub esp,byte +0x10
00000259  FA                cli
0000025A  F4                hlt
0000025B  C745FC01000000    mov dword [ebp-0x4],0x1
00000262  8B55FC            mov edx,[ebp-0x4]
00000265  89D0              mov eax,edx
00000267  C1E002            shl eax,byte 0x2
0000026A  01D0              add eax,edx
0000026C  8945FC            mov [ebp-0x4],eax
0000026F  8B55FC            mov edx,[ebp-0x4]
00000272  89D0              mov eax,edx
00000274  C1E003            shl eax,byte 0x3
00000277  29D0              sub eax,edx
00000279  8945FC            mov [ebp-0x4],eax
0000027C  836DFC06          sub dword [ebp-0x4],byte +0x6
00000280  8B55FC            mov edx,[ebp-0x4]
00000283  89D0              mov eax,edx
00000285  C1E003            shl eax,byte 0x3
00000288  01D0              add eax,edx
0000028A  8945FC            mov [ebp-0x4],eax
0000028D  8B4508            mov eax,[ebp+0x8]
00000290  8B55FC            mov edx,[ebp-0x4]
00000293  8910              mov [eax],edx
00000295  8B45FC            mov eax,[ebp-0x4]
00000298  C9                leave
00000299  C3                ret

cli & hlt对用于使用qemu进行调试,qemu尚未在其上停止。正如您所看到的,3调用指令是完全正常的。但是,运行qemu和运行info registers会产生以下结果:

代码语言:javascript
运行
复制
QEMU 6.2.0 monitor - type 'help' for more information
(qemu) info registers
... irrelevant ...
EIP=00007e50 ... irrelevant ...
... irrelevant ...

如您所见,eip7e50,无限循环!这不应该发生,因为在函数调用(未触发)和函数(未触发)之后有clihlt指令。如果我使用gdb,在7e00上放置一个断点,内核的内存地址,在继续并使用si看到gdb进入函数调用之后,只有下一个指令在无限循环中!

最后,我会提供文件。

Makefile

代码语言:javascript
运行
复制
PRINTDIRECTORY        = --no-print-directory
BOOTLOADER-PARTFILE   = int/parts/boot.prt
BOOTLOADER-OBJECTFILE = int/boot.o
BOOTLOADER-SOURCEFILE = src/boot.s
KERNEL-PARTFILE       = int/parts/detailed-boot.prt
KERNEL-OBJECTFILE     = int/detailed-boot.o
KERNEL-SOURCEFILE     = src/detailed-boot.c
GCC                   = ~/opt/cross/bin/i686-elf-gcc
LD                    = ~/opt/cross/bin/i686-elf-ld
VM                    = qemu-system-i386
SYSFILE               = lizard.bin

full:
    make bootloader $(PRINTDIRECTORY)
    make kernel $(PRINTDIRECTORY)
    truncate -s 32768 ./int/parts/detailed-boot.prt
    make join $(PRINTDIRECTORY)
bootloader:
    as -o $(BOOTLOADER-OBJECTFILE) $(BOOTLOADER-SOURCEFILE)
    ld -o $(BOOTLOADER-PARTFILE) --oformat binary -e init $(BOOTLOADER-OBJECTFILE) -Ttext 0x7c00
kernel:
    $(GCC) -ffunction-sections -ffreestanding $(KERNEL-SOURCEFILE) -o $(KERNEL-OBJECTFILE) -nostdlib -Wall -Wextra -O0
    $(LD) -o $(KERNEL-PARTFILE) -Ttext 0x7e00 --oformat binary $(KERNEL-OBJECTFILE) -e main --script=LDfile -O 0 -Ttext-segment 0x7e00
join:
    cat $(BOOTLOADER-PARTFILE) $(KERNEL-PARTFILE) > $(SYSFILE)
run:
    $(VM) $(SYSFILE)
debug:
    $(VM) $(SYSFILE) -gdb tcp:localhost:6000 -S

LDfile

代码语言:javascript
运行
复制
ENTRY(main)
SECTIONS {
    . = 0x7e00;
    .text . : { *(.text) }
    .data . : { *(.data) }
    .bss  . : { *(.bss ) }
}

src/detailed-boot.c

代码语言:javascript
运行
复制
//#include "stdc/stdbool.h"
//#include "stdc/stdio.h"
asm(".code32");
int a(int *d);
int main() {
    int c = 0;
    c = a(&c);
    asm("cli");
    asm("hlt");
    c = a(&c);
    c = a(&c);
    while(1);
}
int a(int *d) {
    asm("cli");
    asm("hlt");
    int b = 1;
    b *= 5;
    b *= 7;
    b -= 6;
    b *= 9;
    *d = b;
    return b;
}
//#include "stdc/stdio.c"

src/boot.s

代码语言:javascript
运行
复制
.code16         # 16 bit mode
.global init    # make label init global

init:
    call enableA20
reset:

    mov $0x00, %ah # 0 = reset drive
    mov $0x80, %dl # boot disk
    int $0x13
    jc reset
load:
    mov $0x42, %ah                        # 42 = extended read

    mov $0x8000,             %si
    xor %bx,                 %bx

    movl $0x00007e00,         %ds:4 (%si,1)
    movl $0x00400010,         %ds:0 (%si,1)
    mov  %cs,                 %ds:6 (%si,1)
    movl $0x00000001,         %ds:8 (%si,1) # start sector in lba
    movl $0x00000000,         %ds:12(%si,1) # start sector in lba
    int  $0x13

    # 1. Disable interrupts
    cli
    # 2. Load GDT
    lgdt (gdt_descriptor)
    # set 32 bit mode
    mov %cr0, %eax
    or  $1,   %eax
    mov %eax, %cr0
    # Far jmp
    jmp %cs:(code32)

checkA20:
    push %ds

    xor %ax, %ax
    mov %ax, %ds

    movw $0xAA55, %ax
    movw $0x7DFE, %bx
    movw (%bx), %bx
    cmpw %ax, %bx
    jnz checkA20_enabled
checkA20_disabled:
    xor %ax, %ax
    jmp checkA20_done
checkA20_enabled:
    xor %ax, %ax
    inc %ax
checkA20_done:
    pop %ds
    ret


enableA20:
    call checkA20
    jnz enableA20_enabled

enableA20_int15:
    mov $0x2403, %ax                 # A20 gate support
    int $0x15
    jb enableA20_keyboardController  # INT 15 aint supported
    cmp $0, %ah
    jnz enableA20_keyboardController # INT 15 aint supported

    mov $0x2402, %ax                 # A20 status
    int $0x15
    jb enableA20_keyboardController  # couldnt get status
    cmp $0, %ah
    jnz enableA20_keyboardController # couldnt get status

    cmp $1, %al
    jz enableA20_enabled             # A20 is activated

    mov $0x2401, %ax                 # A20 activation
    int $0x15
    jb enableA20_keyboardController  # couldnt activate
    cmp $0, %ah
    jnz enableA20_keyboardController # couldnt activate

enableA20_keyboardController:
    call checkA20
    jnz enableA20_enabled

    cli

    call enableA20_wait
    mov $0xAD, %al
    out %al,   $0x64

    call enableA20_wait
    mov $0xD0, %al
    out %al,   $64

    call enableA20_wait2
    in  $0x60, %al
    push %eax

    call enableA20_wait
    mov $0xD1, %al
    out %al,   $0x64

    call enableA20_wait
    pop %eax
    or  $2, %al
    out %al, $0x60

    call enableA20_wait
    mov $0xAE, %al
    out %al,   $0x64

    call enableA20_wait
    sti

enableA20_fastA20:
    call checkA20
    jnz enableA20_enabled

    in $0x92, %al
    test $2,  %al
    jnz enableA20_postFastA20
    or  $2,    %al
    and $0xFE, %al
    out %al,   $92

enableA20_postFastA20:
    call checkA20
    jnz enableA20_enabled
    cli
    hlt
enableA20_enabled:
    ret
enableA20_wait:
    in   $0x64, %al
    test $2,    %al
    jnz enableA20_wait
    ret
enableA20_wait2:
    in   $0x64, %al
    test $1,    %al
    jnz enableA20_wait2
    ret
setGDT: ret
# NOTE limit is the length
# NOTE base is the start
# NOTE base + limit = last address
gdt_start:
gdt_null:
# null descriptor
    .quad 0
gdt_data:
    .word 0x01c8 # limit: bits 0-15
    .word 0x0000 # base:  bits 0-15
    .byte 0x00   # base:  bits 16-23
# segment presence: yes (+0x80)
# descriptor priviledge level: ring 0 (+0x00)
# descriptor type: code/data (+0x10)
# executable: no (+0x00)
# direction bit: grows up (+0x00)
# writable bit: writable (+0x02)
# accesed bit [best left 0, cpu will deal with it]: no (+0x00)
    .byte 0x80 + 0x10 + 0x02
# granularity flag: limit scaled by 4kib (+0x80)
# size flag: 32 bit pm (+0x40)
# long mode flag: 32pm/16pm/data (+0x00)
# reserved: reserved (+0x00)
    .byte 0x80 + 0x40 # flags: granularity @ 4-7 limit: bits 16-19 @ 0-3
    .byte 0x00 # base:  bits 24-31
gdt_code:
    .word 0x0100 # limit: bits 0-15
    .word 0x8000 # base:  bits 0-15
    .byte 0x1c   # base:  bits 16-23   
# segment presence: yes (+0x80)
# descriptor priviledge level: ring 0 (+0x00)
# descriptor type: code/data (+0x10)
# executable: yes (+0x08)
# conforming bit [0: only ring 0 can execute this]: no (+0x00)
# readable bit: yes (0x02)
# accessed bit [best left 0, cpu will deal with it]: no (0x00)
    .byte 0x80 + 0x10 + 0x08 + 0x02
# granularity flag: limit scaled by 4kib (+0x80)
# size flag: 32 bit pm (+0x40)
# long mode flag: 32pm/16pm/data (+0x00)
# reserved: reserved (+0x00)
    .byte 0x80 + 0x40 + 0x00   # flags: granularity @ 4-7 limit: bits 16-19 @ 0-3
    .byte 0x00                 # base:  bits 24-31
gdt_end:
gdt_descriptor:
    .word gdt_end - gdt_start - 1
    .long gdt_start

.code32
code32:
    mov %ds, %ax
    mov %ax, %ds
#   mov %ax, %ss
    mov %ax, %es
    mov %ax, %fs
    mov %ax, %gs

    movl $0x4000,  %ebp
    mov  %esp,     %ebp

    push $0x7e00
    ret
.fill 500-(.-init)
.quad 1
.word 1

.word 0xaa55
kernel:

我知道这不是最起码的情况,我很抱歉。最后,我将提供一个指向github:https://github.com/saltq144/lizard的链接和我遵循的交叉编译器教程:https://wiki.osdev.org/GCC_Cross-Compiler

为了解决一些问题:我还没有配置IDT,NMI会导致三重故障或跳转到垃圾,而不是循环。试图修改SS导致了我有限的测试的三重故障。我也同意.code32文件中的.c是没有意义的,但是交叉编译器是i686,所以64位代码不应该是一个问题,但是我会研究它。

注意:

使用内联程序集,我可以插入两个nop指令以允许函数调用工作。这不是一个理想的解决办法,但在这个问题得到充分解决之前,它必须发挥作用。编译器优化可能会破坏这一点,但它们还没有实现。

EN

回答 1

Stack Overflow用户

发布于 2022-06-27 07:32:44

当您切换到保护模式(在jmp %cs:(code32))时,CS将加载GDT中的“保护模式兼容”信息。

code32:开始时,所有其他段寄存器都包含真正的模式值。将与保护模式不兼容的真实模式值从DS (at mov %ds, %ax**)** )复制到所有数据段寄存器中。来自DS的实际模式值可能是0x0000。在保护模式下,引用“空描述符”。

这就是为什么您不能做mov %ax, %ss - CPU将不允许您使用“空描述符”的堆栈段(它将给您一个一般的保护错误)。因为您不使用保护模式兼容值加载SS,所以它使用的是来自真实模式的旧值--一个未知的基址、64 KiB段限制和16位默认堆栈指针大小。

这一切的后果是..。一旦您进行任何正常的内存访问(例如,push dword [ecx-0x4],它使用DS作为像[ds: ecx-0x4]一样的隐含段寄存器),您就会得到一个一般的保护错误,因为DS被设置为空描述符。由于尚未设置保护模式IDT,CPU将只使用用于IVT的值real模式,从而导致CPU认为未知的垃圾(在实模式中用于“中断0x1A”,而不是"interupt 0x0D“,因为IDT条目是常规保护故障处理程序的两倍大)。在此之后,没有一种简单的方法可以预测会发生什么(可能未知的垃圾不是保护模式的有效IDT条目,它会导致双重错误,可能是“足够有效”的IDT条目,并且开始在未知地址执行垃圾)。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72763473

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档