簡體   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