[英]Why in C does taking input using int format to a char change the value of another char variable?
[英]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列中。 由於某種原因,我得到了另一個角色。
.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位長模式。 這比進入32位保護模式要復雜得多。 有關64位長模式的信息,請參見OSDev Wiki 。 一旦正確地處於64位長模式,您就可以使用GCC生成的64位指令。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.