如何从x86汇编代码调用C语言函数?

问题描述 投票:0回答:1

我最近正在尝试自学操作系统并玩弄xv6操作系统进行教学。我使用的版本是来自 GitHub 的 x86 版本。 我一直在做的是在启动系统时尝试使用2级分页。为此,我在 main.c 中创建了页表和页目录,如下所示:

__attribute__((__aligned__(PGSIZE)))
pde_t entrypgdir[NPDENTRIES];

__attribute__((__aligned__(PGSIZE)))
pte_t entrypgtable[NPTENTRIES];

然后在entry.S中,我有一些汇编代码来初始化这两个数组,如下所示:

# The xv6 kernel starts executing in this file. This file is linked with
# the kernel C code, so it can refer to kernel symbols such as main().
# The boot block (bootasm.S and bootmain.c) jumps to entry below.
        
# Multiboot header, for multiboot boot loaders like GNU Grub.
# http://www.gnu.org/software/grub/manual/multiboot/multiboot.html
#
# Using GRUB 2, you can boot xv6 from a file stored in a
# Linux file system by copying kernel or kernelmemfs to /boot
# and then adding this menu entry:
#
# menuentry "xv6" {
#   insmod ext2
#   set root='(hd0,msdos1)'
#   set kernel='/boot/kernel'
#   echo "Loading ${kernel}..."
#   multiboot ${kernel} ${kernel}
#   boot
# }

#include "asm.h"
#include "memlayout.h"
#include "mmu.h"
#include "param.h"

# Multiboot header.  Data to direct multiboot loader.
.p2align 2
.text
.globl multiboot_header
multiboot_header:
  #define magic 0x1badb002
  #define flags 0
  .long magic
  .long flags
  .long (-magic-flags)

# By convention, the _start symbol specifies the ELF entry point.
# Since we haven't set up virtual memory yet, our entry point is
# the physical address of 'entry'.
.globl _start
_start = V2P_WO(entry)

# Entering xv6 on boot processor, with paging off.
.globl entry
entry:
  # Turn on page size extension for 4Mbyte pages
  // I comment the following to not use bigger pages
  #movl    %cr4, %eax
  #orl     $(CR4_PSE), %eax
  #movl    %eax, %cr4
  # Set page directory

  //My assembly code to initialize a page table starts
  #set up the first page table page

  xor %esi, %esi
1:
  movl %esi, %eax
  shll $12, %eax
  orl $(PTE_P|PTE_W), %eax
  movl $(V2P_WO(entrypgtable)), %edi
  movl %esi, %ebx
  shll $2, %ebx
  addl %ebx, %edi
  movl %eax, (%edi)
  incl %esi
  cmpl $1024, %esi
  jb 1b

  # Set page directory

  movl $0, %esi
  movl %esi, %ebx
  shll $2, %ebx
  movl $(V2P_WO(entrypgdir)), %edi
  addl %ebx, %edi
  movl $(V2P_WO(entrypgtable)), (%edi)
  orl $(PTE_P | PTE_W), (%edi)

  movl $512, %esi
  movl %esi, %ebx
  shll $2, %ebx
  movl $(V2P_WO(entrypgdir)), %edi
  addl %ebx, %edi
  movl $(V2P_WO(entrypgtable)), (%edi)
  orl $(PTE_P | PTE_W), (%edi)
  //My assembly code to initialize a page table ends

  movl    $(V2P_WO(entrypgdir)), %eax
  movl    %eax, %cr3
  # Turn on paging.
  movl    %cr0, %eax
  orl     $(CR0_PG|CR0_WP), %eax
  movl    %eax, %cr0

  # Set up the stack pointer.
  movl $(stack + KSTACKSIZE), %esp

  # Jump to main(), and switch to executing at
  # high addresses. The indirect call is needed because
  # the assembler produces a PC-relative instruction
  # for a direct jump.
  mov $main, %eax
  jmp *%eax

.comm stack, KSTACKSIZE

如你所见,基本思想很简单:原来的xv6系统在初始化系统时使用4mb大页面,这样他们只需要页目录就可以将内核代码映射到第一个物理页面。我禁用该选项并使用 4kb 页面,然后相应地创建一个新页表,然后将新页表放入页目录数组中。 如果我的理解正确的话,整个过程应该相当于下面的C代码:

__attribute__((__aligned__(PGSIZE)))
pde_t entrypgdir[NPDENTRIES];

__attribute__((__aligned__(PGSIZE)))
pte_t entrypgtable[NPTENTRIES];

void init_entrypgdir(void) {
    for (int i = 0; i < NPTENTRIES; i++) {
        entrypgtable[i] = (i << 12) | PTE_P | PTE_W;
    }
    entrypgdir[0] = (uint)(V2P_WO(entrypgtable)) | PTE_P | PTE_W;
    entrypgdir[KERNBASE>>PDXSHIFT] = (uint)(V2P_WO(entrypgtable)) | PTE_P | PTE_W;
}

上面的代码位于main.c中,它与entry.S链接在一起。我已经确认entry.S可以访问我新创建的init_entrypgdir()函数。

现在问题来了:如果我想摆脱自己编写的汇编代码,而使用上面的C代码来初始化xv6怎么办?有没有办法用我的 C 代码替换我的汇编代码并仍然成功初始化操作系统?

我尝试将entry.S重写为:

#include "asm.h"
#include "memlayout.h"
#include "mmu.h"
#include "param.h"

# Multiboot header.  Data to direct multiboot loader.
.p2align 2
.text
.globl multiboot_header
multiboot_header:
  #define magic 0x1badb002
  #define flags 0
  .long magic
  .long flags
  .long (-magic-flags)

# By convention, the _start symbol specifies the ELF entry point.
# Since we haven't set up virtual memory yet, our entry point is
# the physical address of 'entry'.
.globl _start
_start = V2P_WO(entry)

# Entering xv6 on boot processor, with paging off.
.globl entry
entry:
  # Turn on page size extension for 4Mbyte pages
  // I comment the following to not use bigger pages
  #movl    %cr4, %eax
  #orl     $(CR4_PSE), %eax
  #movl    %eax, %cr4

  // see if this works
  call V2P_WO(init_entrypgdir)

  # Set page directory
  movl    $(V2P_WO(entrypgdir)), %eax
  movl    %eax, %cr3
  # Turn on paging.
  movl    %cr0, %eax
  orl     $(CR0_PG|CR0_WP), %eax
  movl    %eax, %cr0

  # Set up the stack pointer.
  movl $(stack + KSTACKSIZE), %esp

  # Jump to main(), and switch to executing at
  # high addresses. The indirect call is needed because
  # the assembler produces a PC-relative instruction
  # for a direct jump.
  mov $main, %eax
  jmp *%eax

.comm stack, KSTACKSIZE

然后,在 GDB 中,我在第 108、110 和 112 行创建了断点。

正如您在屏幕截图中看到的,断点 112 永远不会被命中,并且 qemu 监视器会像这样冻结

我想知道为什么112号线从来没有运行过?我的for循环有什么问题吗?

c assembly x86 qemu xv6
1个回答
0
投票
  1. entry
    开始时,电脑是
    0x10_000c
    (地址是我编译的
    kernel
    的elf入口,可能和其他的不一样)
  2. -fno-pic
    中的
    CFLAGS
    中有
    Makefile
    ,这意味着编译出来的C代码必须使用绝对地址
  3. kernel.ld
    指定所有部分都从
    0x8010_0000
    开始。
  4. entry
    有两个重要的功能,初始化页表和从
    0x10_0000
    跳转到
    0x8010_0000
    。注意,elf文件
    kernel
    被读取到物理地址
    0x10_0000
    在设置页表之前,
    0x8010_0000
    是一个无效地址

我尝试过这样的可能方法:

void init_entrypgdir(void) {
  register pde_t volatile *entrypgdir = (pde_t *) V2P_WO(entrypgdir);
  register pte_t volatile *entrypgtable = (pte_t *) V2P_WO(entrypgtable);

  for (int i = 0; i < NPTENTRIES; i++) {
    entrypgtable[i] = (i << 12) | PTE_P | PTE_W;
  }
  entrypgdir[0] = (uint)(entrypgtable) | PTE_P | PTE_W;
  entrypgdir[KERNBASE>>PDXSHIFT] = (uint)(entrypgtable) | PTE_P | PTE_W;
}

register
建议编译器将此变量放入寄存器中,
volatile *
建议编译器它是内存访问,不要优化为空。你可以尝试去掉这两个关键字,你会发现该函数什么也没做。

而且不幸的是,通过这次更新,页表的初始化没有问题,但是调用时出现问题

userinit
:(

© www.soinside.com 2019 - 2024. All rights reserved.