简体   繁体   English

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

[英]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 引导加载程序

; 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

bootkernel.asm 引导内核

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

kernel.c 内核

// 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. 由于某种原因,我得到了另一个角色。

屏幕截图

Output of gcc-6 -S kernel.c 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

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位代码。


Primary Issue Causing Unusual Behaviour 导致异常行为的主要问题

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: 这是经过上述更改后我得到的输出:

在此处输入图片说明


Other observations 其他观察

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. 这通常不是您想要的行为。


64-bit long mode 64位长模式

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.

相关问题 为什么在 C 中使用 int 格式将输入转换为 char 会改变另一个 char 变量的值? - Why in C does taking input using int format to a char change the value of another char variable? 将char **传递给函数,是否不更改其值,还是呢? - Passing char** into a function , doesn't change its values , or does it ? 当我将printf与%s和%c一起使用时,为什么打印的字符会改变? - Why does the printed char change when I use printf with %s and %c? Fortran 和 C 互操作性:将 char *string 作为函数的参数传递给 Fortran - Fortran and C interoperability: passing char *string as a function's argument to Fortran 为什么我的程序让我更改 C 中的 const char 的值? - Why does my program let me change a value of a const char in C? 传递给C函数时char数组的值丢失 - Losing value of char array when passing to C function C - 为什么 char 数组只返回放入其中的最后一个值? - C - Why does char array only return last value that's put into it? 将指针的值传递给不修改指针的函数后,为什么会更改它? - Why does the value of a pointer change after passing it to a function that does not modify the pointer? 为什么c的值不变? - Why does the value of c not change? 将Char数组传递给C函数 - Passing Char Array into a function in C
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM