为什么将char传递给函数会更改其在c中的值? [英] Why does passing a char to a function change it's value in c?

查看:95
本文介绍了为什么将char传递给函数会更改其在c中的值?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在关注该工作簿有关构建操作系统的信息.

I am currently following this workbook on build an operating system.

我的意图是编写一个64位内核.在文本模式下,我可以加载内核"代码并将单个字符写入帧缓冲区.

My intention is to write a 64-bit kernel. I have got as far as loading the "kernel" code and writing individual characters to the frame buffer while in text mode.

当我通过将代码包装在函数中来添加一个间接级别来将单个字符写入帧缓冲区时,就会出现我的问题.似乎传递给函数的char值已以某种方式被破坏.

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.

我有三个文件:

; 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);
}

.text部分被链接为从0x1000开始,这是引导加载程序期望内核启动的位置.

The .text section is linked to start from 0x1000 which is where the bootloader expects the kernel to start.

linker.ld脚本是

The linker.ld script is

SECTIONS
{
    . = 0x1000;

    .text : { *(.text) } /* Kernel is expected at 0x1000 */
}

将所有内容组合在一起的Make文件是:

The Make file that puts this all together is:

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

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. 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

推荐答案

如果代码被修改为:

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);.这与您说的话所描述的意图一致:

The difference being in the last line ('B', GREY_ON_BLACK, 1, 0);. Originally you had ('B', GREY_ON_BLACK, 0, 1); . This is in line with what you described you were trying to do when you said:

我已经获得了输出的屏幕快照.我希望'A'出现在第一行的第0列中,而'B'出现在第0行的第1列中.

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.

我收集到您可能在此问题中发布了错误的代码.这是我得到的输出:

I gather you may have posted the wrong code in this question. This is the output I get:

您似乎是OS开发的新手.您的引导加载程序代码仅将CPU置于32位保护模式,但是要运行64位内核,您需要处于64位长模式.如果您才刚刚起步,我建议您回过头来编写32位内核,以便在此早期阶段学习.在底部,我有一个 64位长模式部分,其中包含指向长模式教程的链接,该链接可用于修改您的引导加载程序以运行64位代码.

It seems you are new to OS development. 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. 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. 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.

您遇到的问题主要与以下事实有关:您正在使用 GCC 生成64位代码,但是根据您的引导加载程序代码,它正在32位保护模式下运行.在32位保护模式下运行的64位代码生成似乎可以执行,但是这样做会不正确.在简单的OS中,您只是将其显示在视频显示屏上,所以您经常会看到意外的输出,这是副作用.您的程序可能会使机器出现三重故障,但不幸的是,副作用似乎在视频显示器上显示了一些东西.您可能会误以为事情在实际上不是真正应有的状态下就可以正常工作.

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. 64-bit code generation running in 32-bit protected mode may appear to execute, but it will do it incorrectly. In simple OSes where you are simply displaying to the video display you may often see unexpected output as a side effect. 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.

这个问题有点类似于另一个 Stackoverflow 问题.在该问题的原始张贴者提供了一个完整的例子之后,很明显这是他的问题.我为他解决该问题的部分答案如下:

This question is somewhat similar to another Stackoverflow question. 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:

未定义行为的可能原因

EDIT 2 中提供了所有代码和make文件之后,很明显的一个重要问题是,大多数代码都已编译并链接到64位对象和可执行文件.该代码无法在32位保护模式下工作.

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. That code won't work in 32-bit protected mode.

在make文件中进行以下调整:

In the make file make these adjustments:

  • 使用 GCC 进行编译时,您需要添加-m32选项
  • 使用针对32位对象的 GNU汇编器( as )进行组装时,您需要使用--32
  • LD 链接时,您需要添加-melf_i386选项
  • 与针对32位对象的 NASM 组装时,需要将-f elf64更改为-f elf32
  • When compiling with GCC you need to add -m32 option
  • When assembling with GNU Assembler (as) targeting 32-bit objects you need to use --32
  • When linking with LD you need to add the -melf_i386 option
  • When assembling with NASM targeting 32-bit objects you need to change -f elf64 to -f elf32

请记住,您可以更改Makefile以生成32位代码.它可能看起来像:

With that in mind you can alter your Makefile to generate 32-bit code. 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

当您开始遇到代码问题时,我收集了您最终尝试将0xb8002用作视频内存的地址的信息.它应该是0xb8000.您需要修改:

I gather when you started having issues with your code you ended up trying 0xb8002 as the address for your video memory. It should be 0xb8000. You'll need to modify:

#define FRAME_BUFFER_ADDRESS 0xb8002

成为:

#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:

write_memory中,您使用:

unsigned char * memory = (unsigned char *) address;

由于您使用的是0xb8000,它是映射到视频显示器的内存,因此您应将其标记为volatile,因为编译器可以在不知道写入该内存有副作用的情况下优化性能(即在屏幕上显示字符).展示).您可能希望使用:

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). You might wish to use:

volatile unsigned char * memory = (unsigned char *) address;


在您的bootloader.asm中,您确实应该明确将A20线设置为开.您可以在 OSDev Wiki文章中找到有关执行此操作的信息.引导加载程序开始执行时,A20线路的状态可能会因仿真器而异.如果尝试访问奇数兆字节边界上的内存区域(例如0x100000至0x1fffff,0x300000至0x1fffff等),则无法将其设置为打开可能会导致问题.对奇数兆字节存储区的访问实际上将从其正下方的偶数存储区读取数据.这通常不是您想要的行为.


In your bootloader.asm You really should explicitly set the A20 line on. You can find information about doing that in this OSDev Wiki article. The status of the A20 line at the point a bootloader starts executing may vary between emulators. 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). 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位代码,则需要将处理器置于64位长模式.这比进入32位保护模式要复杂得多.有关64位长模式的信息,可以在 OSDev Wiki 中找到.一旦正确地处于64位长模式,您就可以使用 GCC 生成的64位指令.

If you want to run 64-bit code you will need to place the processor into 64-bit long mode. This is a bit more involved than entering 32-bit protected mode. Information on 64-bit longmode can be found in the OSDev wiki. Once properly in 64-bit longmode you can use 64-bit instructions generated by GCC.

这篇关于为什么将char传递给函数会更改其在c中的值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆