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

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

问题描述

我目前正在关注

gcc-6 -S kernel.c 的输出

 .file "kernel.c".文本.globl write_memory.type write_memory, @function写内存:.LFB0:.cfi_startprocpushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6movq %rdi, -24(%rbp)movl %esi, -28(%rbp)movl %edx, %eaxmovb %al, -32(%rbp)movq -24(%rbp), %raxmovq %rax, -8(%rbp)movl -28(%rbp), %edxmovq -8(%rbp), %raxaddq %rax, %rdxmovzbl -32(%rbp), %eaxmovb %al, (%rdx)没有popq %rbp.cfi_def_cfa 7、8退.cfi_endproc.LFE0:.size write_memory, .-write_memory.globl frame_buffer_offset.type frame_buffer_offset, @functionframe_buffer_offset:.LFB1:.cfi_startprocpushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6movl %edi, -4(%rbp)movl %esi, -8(%rbp)movl -8(%rbp), %edxmovl %edx, %eax卖 2 美元,%eaxaddl %edx, %eax卖 4 美元,%eaxmovl %eax, %edxmovl -4(%rbp), %eaxaddl %edx, %eax添加 %eax, %eaxpopq %rbp.cfi_def_cfa 7、8退.cfi_endproc.LFE1:.size frame_buffer_offset, .-frame_buffer_offset.globl write_frame_buffer_cell.type write_frame_buffer_cell, @functionwrite_frame_buffer_cell:.LFB2:.cfi_startprocpushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6subq 32 美元,%rspmovl %esi, %eaxmovl %edx, -28(%rbp)movl %ecx, -32(%rbp)movb %dil, -20(%rbp)movb %al, -24(%rbp)movl -32(%rbp), %edxmovl -28(%rbp), %eaxmovl %edx, %esimovl %eax, %edi调用 frame_buffer_offsetmovl %eax, -4(%rbp)movzbl -20(%rbp), %edxmovl -4(%rbp), %eaxmovl %eax, %esi移动 $753666,%edi调用 write_memorymovzbl -24(%rbp), %eaxmovl -4(%rbp), %edxleal 1(%rdx), %ecxmovl %eax, %edxmovl %ecx, %esi移动 $753666,%edi调用 write_memory没有离开.cfi_def_cfa 7、8退.cfi_endproc.LFE2:.size write_frame_buffer_cell, .-write_frame_buffer_cell.globl 主.type main, @function主要的:.LFB3:.cfi_startprocpushq %rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq %rsp, %rbp.cfi_def_cfa_register 6subq 16 美元,%rspmovl $1, %esimovl $0, %edi调用 frame_buffer_offsetmovl %eax, -4(%rbp)movl -4(%rbp), %eaxmovl $65, %edxmovl %eax, %esi移动 $753666,%edi调用 write_memorymovl -4(%rbp), %eaxaddl $1, %eaxmovl $7, %edxmovl %eax, %esi移动 $753666,%edi调用 write_memorymovl $0, %ecxmovl $1, %edxmovl $7, %esimovl $66, %edi调用 write_frame_buffer_cell没有离开.cfi_def_cfa 7、8退.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);写内存(FRAME_BUFFER_ADDRESS,偏移量,'A');写内存(FRAME_BUFFER_ADDRESS,偏移量+ 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 行的第一列.

我猜你可能在这个问题中发布了错误的代码.这是我得到的输出:

<小时>

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

<小时>

导致异常行为的主要问题

您遇到的问题主要与以下事实有关:您使用 GCC 生成 64 位代码,但您根据引导加载程序代码在 32 位保护模式下运行它.在 32 位保护模式下运行的 64 位代码生成可能看起来可以执行,但它会错误地执行.在简单的操作系统中,您只是在视频显示器上进行显示,您可能经常看到意外的输出作为副作用.您的程序可能会导致机器出现三重故障,但您不幸的是,副作用似乎在视频显示器上显示了一些内容.您可能有一种错误的印象,即事情实际上是在正常运行,但实际上并非如此.

这个问题有点类似于另一个

<小时>

其他观察

write_memory 中你使用:

unsigned char * memory = (unsigned char *) 地址;

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

volatile unsigned char * memory = (unsigned char *) 地址;

<小时>

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

<小时>

64 位长模式

如果您想运行 64 位代码,您需要将处理器置于 64 位长模式.这比进入 32 位保护模式要复杂一些.关于 64 位长模式的信息可以在 OSDev wiki 中找到.一旦正确进入 64 位长模式,您就可以使用 GCC 生成的 64 位指令.

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

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.

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.

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.

The linker.ld script is

SECTIONS
{
    . = 0x1000;

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

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

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.

Output of 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);. 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:

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:


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.


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

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:

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

In the make file make these adjustments:

  • 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

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

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

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:

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). 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. 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-bit long mode

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.

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

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