![](/img/trans.png)
[英]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.