繁体   English   中英

为什么将char传递给函数会更改其在c中的值?

[英]Why does passing a char to a function change it's value in c?

我目前正在遵循此工作簿来构建操作系统。

我的意图是编写一个64位内核。 在文本模式下,我可以加载“内核”代码并将单个字符写入帧缓冲区。

当我通过将代码包装在函数中来添加一个间接级别以将单个字符写入帧缓冲区时,就会出现我的问题。 似乎传递给函数的char值已以某种方式被破坏。

我有三个文件:

引导加载程序

; bootloader.asm
[org 0x7c00]
KERNEL_OFFSET equ 0x1000

mov bp, 0x9000
mov sp, bp

; load the kernel from boot disk
mov bx, KERNEL_OFFSET
mov dl, dl ; boot drive is set to dl
mov ah, 0x02 ; bios read sector
mov al, 15 ; read 15 sectors    
mov ch, 0x00 ; cylinder 0
mov cl, 0x02 ; read from 2nd sector
mov dh, 0x00 ; select head 0
int 0x13

; THERE COULD BE ERRORS HERE BUT FOR NOW ASSUME IT WORKS

; switch to protected mode
cli

lgdt [gdt.descriptor]

mov eax, cr0
or eax, 1
mov cr0, eax
jmp CODE_SEGMENT:start_protected_mode

[bits 32]
start_protected_mode:
    mov ax, DATA_SEGMENT
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp, 0x90000
    mov esp, ebp

    call KERNEL_OFFSET
    jmp $

[bits 16]
gdt: ; Super Simple Global Descriptor Table
.start:
.null:
    dd 0x0
    dd 0x0
.code:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10011010b
    db 11001111b
    db 0x0
.data:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0
.end:
.descriptor:
    dw .end - .start
    dd .start

CODE_SEGMENT equ gdt.code - gdt.start
DATA_SEGMENT equ gdt.data - gdt.start

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

引导内核

[bits 32]
[extern main]
[global _start]
_start:
    call main
    jmp $

内核

// LEGACY MODE VIDEO DRIVER
#define FRAME_BUFFER_ADDRESS 0xb8002
#define GREY_ON_BLACK 0x07
#define WHITE_ON_BLACK 0x0f

void write_memory(unsigned long address, unsigned int index, unsigned char value)
{
    unsigned char * memory = (unsigned char *) address;
    memory[index] = value;
}

unsigned int frame_buffer_offset(unsigned int col, unsigned int row)
{
    return 2 * ((row * 80u) + col);
}

void write_frame_buffer_cell(unsigned char c, unsigned char a, unsigned int col, unsigned int row)
{
    unsigned int offset = frame_buffer_offset(col, row);
    write_memory(FRAME_BUFFER_ADDRESS, offset, c);
    write_memory(FRAME_BUFFER_ADDRESS, offset + 1, a);
}

void main()
{
    unsigned int offset = frame_buffer_offset(0, 1);
    write_memory(FRAME_BUFFER_ADDRESS, offset, 'A');
    write_memory(FRAME_BUFFER_ADDRESS, offset + 1, GREY_ON_BLACK);

    write_frame_buffer_cell('B', GREY_ON_BLACK, 0, 1);
}

链接.text节以从0x1000开始,这是引导加载程序期望内核启动的位置。

linker.ld脚本是

SECTIONS
{
    . = 0x1000;

    .text : { *(.text) } /* Kernel is expected at 0x1000 */
}

将所有内容组合在一起的Make文件是:

bootloader.bin: bootloader.asm
    nasm -f bin bootloader.asm -o bootloader.bin

bootkernel.o: bootkernel.asm
    nasm -f elf64 bootkernel.asm -o bootkernel.o

kernel.o: kernel.c
    gcc-6 -Wextra -Wall -ffreestanding -c kernel.c -o kernel.o

kernel.bin: bootkernel.o kernel.o linker.ld
    ld -o kernel.bin -T linker.ld bootkernel.o kernel.o --oformat binary

os-image: bootloader.bin kernel.bin
    cat bootloader.bin kernel.bin > os-image

qemu: os-image
    qemu-system-x86_64 -d guest_errors -fda os-image -boot a

我已经获得了输出的屏幕快照。 我希望'A'出现在第一行的第0列中,而'B'出现在第0行的第1列中。 由于某种原因,我得到了另一个角色。

屏幕截图

gcc-6 -S kernel.c的输出

    .file   "kernel.c"
    .text
    .globl  write_memory
    .type   write_memory, @function
write_memory:
.LFB0:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movq    %rdi, -24(%rbp)
    movl    %esi, -28(%rbp)
    movl    %edx, %eax
    movb    %al, -32(%rbp)
    movq    -24(%rbp), %rax
    movq    %rax, -8(%rbp)
    movl    -28(%rbp), %edx
    movq    -8(%rbp), %rax
    addq    %rax, %rdx
    movzbl  -32(%rbp), %eax
    movb    %al, (%rdx)
    nop
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE0:
    .size   write_memory, .-write_memory
    .globl  frame_buffer_offset
    .type   frame_buffer_offset, @function
frame_buffer_offset:
.LFB1:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    movl    %edi, -4(%rbp)
    movl    %esi, -8(%rbp)
    movl    -8(%rbp), %edx
    movl    %edx, %eax
    sall    $2, %eax
    addl    %edx, %eax
    sall    $4, %eax
    movl    %eax, %edx
    movl    -4(%rbp), %eax
    addl    %edx, %eax
    addl    %eax, %eax
    popq    %rbp
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE1:
    .size   frame_buffer_offset, .-frame_buffer_offset
    .globl  write_frame_buffer_cell
    .type   write_frame_buffer_cell, @function
write_frame_buffer_cell:
.LFB2:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $32, %rsp
    movl    %esi, %eax
    movl    %edx, -28(%rbp)
    movl    %ecx, -32(%rbp)
    movb    %dil, -20(%rbp)
    movb    %al, -24(%rbp)
    movl    -32(%rbp), %edx
    movl    -28(%rbp), %eax
    movl    %edx, %esi
    movl    %eax, %edi
    call    frame_buffer_offset
    movl    %eax, -4(%rbp)
    movzbl  -20(%rbp), %edx
    movl    -4(%rbp), %eax
    movl    %eax, %esi
    movl    $753666, %edi
    call    write_memory
    movzbl  -24(%rbp), %eax
    movl    -4(%rbp), %edx
    leal    1(%rdx), %ecx
    movl    %eax, %edx
    movl    %ecx, %esi
    movl    $753666, %edi
    call    write_memory
    nop
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE2:
    .size   write_frame_buffer_cell, .-write_frame_buffer_cell
    .globl  main
    .type   main, @function
main:
.LFB3:
    .cfi_startproc
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset 6, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movl    $1, %esi
    movl    $0, %edi
    call    frame_buffer_offset
    movl    %eax, -4(%rbp)
    movl    -4(%rbp), %eax
    movl    $65, %edx
    movl    %eax, %esi
    movl    $753666, %edi
    call    write_memory
    movl    -4(%rbp), %eax
    addl    $1, %eax
    movl    $7, %edx
    movl    %eax, %esi
    movl    $753666, %edi
    call    write_memory
    movl    $0, %ecx
    movl    $1, %edx
    movl    $7, %esi
    movl    $66, %edi
    call    write_frame_buffer_cell
    nop
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
.LFE3:
    .size   main, .-main
    .ident  "GCC: (Ubuntu 6.2.0-3ubuntu11~16.04) 6.2.0 20160901"
    .section    .note.GNU-stack,"",@progbits

如果将代码修改为以下内容,则可以重现您的确切输出:

unsigned int offset = frame_buffer_offset(0, 1);
write_memory(FRAME_BUFFER_ADDRESS, offset, 'A');
write_memory(FRAME_BUFFER_ADDRESS, offset + 1, GREY_ON_BLACK);

write_frame_buffer_cell('B', GREY_ON_BLACK, 1, 0);

区别在于最后一行('B', GREY_ON_BLACK, 1, 0); 最初您有('B', GREY_ON_BLACK, 0, 1); 这与您说的话要描述的内容一致:

我已经获得了输出的屏幕快照。 我希望'A'出现在第一行的第0列中,而'B'出现在第0行的第1列中。

我收集到您可能在此问题中发布了错误的代码。 这是我得到的输出:

在此处输入图片说明


看来您是OS开发的新手。 您的引导加载程序代码仅将CPU置于32位保护模式下,但是要运行64位内核,您需要处于64位长模式。 如果您刚刚起步,我建议您回过头来编写32位内核,以便在此早期阶段学习。 在底部,我有一个64位长模式部分,其中包含指向长模式教程的链接,该链接可用于修改您的引导加载程序以运行64位代码。


导致异常行为的主要问题

您遇到的问题主要与以下事实有关:您正在使用GCC生成64位代码,但根据引导加载程序代码,您正在32位保护模式下运行它。 在32位保护模式下运行的64位代码生成似乎可以执行,但是这样做会不正确。 在简单的OS中,您只是将其显示在视频显示器上,所以您经常会看到意外的输出,这是副作用。 您的程序可能会使机器出现三重故障,但不幸的是,副作用似乎在视频显示器上显示了一些东西。 您可能会误以为事情在实际上不是真正应有的状态下就可以正常工作。

这个问题有点类似于另一个Stackoverflow问题。 在该问题的原始张贴者提供了一个完整的例子之后,很明显这是他的问题。 我对他的解决问题的部分回答如下:

行为不确定的可能原因

EDIT 2中提供了所有代码和make文件之后,很明显的一个重要问题是,大多数代码都已编译并链接到64位对象和可执行文件。 该代码无法在32位保护模式下工作。

在make文件中进行以下调整:

  • 使用GCC编译时,您需要添加-m32选项
  • 使用针对32位对象的GNU汇编器as )进行组装时,您需要使用--32
  • LD链接时,您需要添加-melf_i386选项
  • 与针对32位对象的NASM组装时,需要将-f elf64更改为-f elf32

考虑到这一点,您可以更改Makefile以生成32位代码。 它可能看起来像:

bootloader.bin: bootloader.asm
    nasm -f bin bootloader.asm -o bootloader.bin

bootkernel.o: bootkernel.asm
    nasm -f elf32 bootkernel.asm -o bootkernel.o

kernel.o: kernel.c
    gcc-6 -m32 -Wextra -Wall -ffreestanding -c kernel.c -o kernel.o

kernel.bin: bootkernel.o kernel.o linker.ld
    ld -melf_i386 -o kernel.bin -T linker.ld bootkernel.o kernel.o --oformat binary

os-image: bootloader.bin kernel.bin
    cat bootloader.bin kernel.bin > os-image

qemu: os-image
    qemu-system-x86_64 -d guest_errors -fda os-image -boot a

当您开始遇到代码问题时,我收集了您最终尝试将0xb8002作为您的视频内存地址的信息。 它应该是0xb8000。 您需要修改:

#define FRAME_BUFFER_ADDRESS 0xb8002

成为:

#define FRAME_BUFFER_ADDRESS 0xb8000

进行所有这些更改应该可以解决您的问题。 这是经过上述更改后我得到的输出:

在此处输入图片说明


其他观察

write_memory您可以使用:

unsigned char * memory = (unsigned char *) address;

由于您使用的是映射到视频显示器的内存0xb8000,因此您应将其标记为volatile因为编译器可以在不知道写入该内存有副作用(即在显示器上显示字符)的情况下优化性能。 您可能希望使用:

volatile unsigned char * memory = (unsigned char *) address;

在您的bootloader.asm您确实应该显式设置A20线。 您可以在此OSDev Wiki文章中找到有关执行此操作的信息 引导加载程序开始执行时,A20线路的状态可能会因仿真器而异。 如果尝试访问奇数兆字节边界上的内存区域(例如0x100000至0x1fffff,0x300000至0x1fffff等),则无法将其设置为打开可能会导致问题。 对奇数兆字节存储区的访问实际上将从其正下方的偶数存储区读取数据。 这通常不是您想要的行为。


64位长模式

如果要运行64位代码,则需要将处理器置于64位长模式。 这比进入32位保护模式要复杂得多。 有关64位长模式的信息,请参见OSDev Wiki 一旦正确地处于64位长模式,您就可以使用GCC生成的64位指令。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM