[英]Why does passing a char to a function change it's value in c?
I am currently following this workbook on build an operating system. 我目前正在遵循此工作簿来构建操作系统。
My intention is to write a 64-bit kernel. 我的意图是编写一个64位内核。 I have got as far as loading the "kernel" code and writing individual characters to the frame buffer while in text mode.
在文本模式下,我可以加载“内核”代码并将单个字符写入帧缓冲区。
My problem appears when I add a level of indirection to writing a single character to the frame buffer by wrapping the code in a function. 当我通过将代码包装在函数中来添加一个间接级别以将单个字符写入帧缓冲区时,就会出现我的问题。 It would appear that the char value passed into the function is being corrupted in some way.
似乎传递给函数的char值已以某种方式被破坏。
I have three files: 我有三个文件:
; 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);
}
The .text section is linked to start from 0x1000 which is where the bootloader expects the kernel to start. 链接.text节以从0x1000开始,这是引导加载程序期望内核启动的位置。
The linker.ld script is linker.ld脚本是
SECTIONS
{
. = 0x1000;
.text : { *(.text) } /* Kernel is expected at 0x1000 */
}
The Make file that puts this all together is: 将所有内容组合在一起的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
I've taken a screen shot of the output that I am getting. 我已经获得了输出的屏幕快照。 I expect 'A' to appear in the 0th column of the 1st row and for 'B' to appear on the 1st column of the 0th row.
我希望'A'出现在第一行的第0列中,而'B'出现在第0行的第1列中。 For some reason I am getting another character.
由于某种原因,我得到了另一个角色。
.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
I can reproduce your exact output if the code is modified to be: 如果将代码修改为以下内容,则可以重现您的确切输出:
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);
The difference being in the last line ('B', GREY_ON_BLACK, 1, 0);
区别在于最后一行
('B', GREY_ON_BLACK, 1, 0);
. 。 Originally you had
('B', GREY_ON_BLACK, 0, 1);
最初您有
('B', GREY_ON_BLACK, 0, 1);
. 。 This is in line with what you described you were trying to do when you said:
这与您说的话要描述的内容一致:
I've taken a screen shot of the output that I am getting.
我已经获得了输出的屏幕快照。 I expect 'A' to appear in the 0th column of the 1st row and for 'B' to appear on the 1st column of the 0th row.
我希望'A'出现在第一行的第0列中,而'B'出现在第0行的第1列中。
I gather you may have posted the wrong code in this question. 我收集到您可能在此问题中发布了错误的代码。 This is the output I get:
这是我得到的输出:
It seems you are new to OS development. 看来您是OS开发的新手。 Your bootloader code only places the CPU into 32-bit protected mode, but to run a 64-bit kernel you need to be in 64-bit longmode.
您的引导加载程序代码仅将CPU置于32位保护模式下,但是要运行64位内核,您需要处于64位长模式。 If you are just getting started I'd suggest falling back to writing a 32-bit kernel for purposes of learning at this early stage.
如果您刚刚起步,我建议您回过头来编写32位内核,以便在此早期阶段学习。 At the bottom I have a 64-bit long mode section with a link to a longmode tutorial that could be used to modify your bootloader to run 64-bit code.
在底部,我有一个64位长模式部分,其中包含指向长模式教程的链接,该链接可用于修改您的引导加载程序以运行64位代码。
You are experiencing an issue primarily related to the fact that you are generating 64-bit code with GCC but you are running it in 32-bit protected mode according to your bootloader code. 您遇到的问题主要与以下事实有关:您正在使用GCC生成64位代码,但根据引导加载程序代码,您正在32位保护模式下运行它。 64-bit code generation running in 32-bit protected mode may appear to execute, but it will do it incorrectly.
在32位保护模式下运行的64位代码生成似乎可以执行,但是这样做会不正确。 In simple OSes where you are simply displaying to the video display you may often see unexpected output as a side effect.
在简单的OS中,您只是将其显示在视频显示器上,所以您经常会看到意外的输出,这是副作用。 Your program could triple fault the machine, but you got unlucky that the side effect seemed to display something on the video display.
您的程序可能会使机器出现三重故障,但不幸的是,副作用似乎在视频显示器上显示了一些东西。 You may have been under the false impression that things were working as they should when they really weren't.
您可能会误以为事情在实际上不是真正应有的状态下就可以正常工作。
This question is somewhat similar to another Stackoverflow question. 这个问题有点类似于另一个Stackoverflow问题。 After the original poster of that question made available a complete example it became clear that it was his issue.
在该问题的原始张贴者提供了一个完整的例子之后,很明显这是他的问题。 Part of my answer to him to resolve the issue was as follows:
我对他的解决问题的部分回答如下:
Likely Cause of Undefined Behavior
行为不确定的可能原因
After all the code and the make file were made available in EDIT 2 it became clear that one significant problem was that most of the code was compiled and linked to 64-bit objects and executables.
在EDIT 2中提供了所有代码和make文件之后,很明显的一个重要问题是,大多数代码都已编译并链接到64位对象和可执行文件。 That code won't work in 32-bit protected mode.
该代码无法在32位保护模式下工作。
In the make file make these adjustments:
在make文件中进行以下调整:
- When compiling with GCC you need to add
-m32
option使用GCC编译时,您需要添加
-m32
选项- When assembling with GNU Assembler ( as ) targeting 32-bit objects you need to use
--32
使用针对32位对象的GNU汇编器 ( as )进行组装时,您需要使用
--32
- When linking with LD you need to add the
-melf_i386
option与LD链接时,您需要添加
-melf_i386
选项- When assembling with NASM targeting 32-bit objects you need to change
-f elf64
to-f elf32
与针对32位对象的NASM组装时,需要将
-f elf64
更改为-f elf32
With that in mind you can alter your Makefile
to generate 32-bit code. 考虑到这一点,您可以更改
Makefile
以生成32位代码。 It could look like: 它可能看起来像:
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
I gather when you started having issues with your code you ended up trying 0xb8002 as the address for your video memory. 当您开始遇到代码问题时,我收集了您最终尝试将0xb8002作为您的视频内存地址的信息。 It should be 0xb8000.
它应该是0xb8000。 You'll need to modify:
您需要修改:
#define FRAME_BUFFER_ADDRESS 0xb8002
To be: 成为:
#define FRAME_BUFFER_ADDRESS 0xb8000
Making all these changes should resolve your issues. 进行所有这些更改应该可以解决您的问题。 This is what the output I got looked like after the changes mentioned above:
这是经过上述更改后我得到的输出:
In write_memory
you use: 在
write_memory
您可以使用:
unsigned char * memory = (unsigned char *) address;
Since you are using 0xb8000 that is memory mapped to the video display you should mark it as volatile
since a compiler could optimize things away not knowing that there is a side effect to writing to that memory (namely displaying characters on a display). 由于您使用的是映射到视频显示器的内存0xb8000,因此您应将其标记为
volatile
因为编译器可以在不知道写入该内存有副作用(即在显示器上显示字符)的情况下优化性能。 You might wish to use: 您可能希望使用:
volatile unsigned char * memory = (unsigned char *) address;
In your bootloader.asm
You really should explicitly set the A20 line on. 在您的
bootloader.asm
您确实应该显式设置A20线。 You can find information about doing that in this OSDev Wiki article . 您可以在此OSDev Wiki文章中找到有关执行此操作的信息 。 The status of the A20 line at the point a bootloader starts executing may vary between emulators.
引导加载程序开始执行时,A20线路的状态可能会因仿真器而异。 Failure to set it on could cause issues if you try to access memory areas on an odd numbered megabyte boundary (like 0x100000 to 0x1fffff, 0x300000 to 0x1fffff etc).
如果尝试访问奇数兆字节边界上的内存区域(例如0x100000至0x1fffff,0x300000至0x1fffff等),则无法将其设置为打开可能会导致问题。 Accesses to the odd numbered megabyte memory regions will actually read data from the even numbered memory region just below it.
对奇数兆字节存储区的访问实际上将从其正下方的偶数存储区读取数据。 This is usually not behaviour you want.
这通常不是您想要的行为。
If you want to run 64-bit code you will need to place the processor into 64-bit long mode. 如果要运行64位代码,则需要将处理器置于64位长模式。 This is a bit more involved than entering 32-bit protected mode.
这比进入32位保护模式要复杂得多。 Information on 64-bit longmode can be found in the OSDev wiki .
有关64位长模式的信息,请参见OSDev Wiki 。 Once properly in 64-bit longmode you can use 64-bit instructions generated by GCC .
一旦正确地处于64位长模式,您就可以使用GCC生成的64位指令。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.