【问题标题】:Why does my kernel crash when I add more arguments to a function?当我向函数添加更多参数时,为什么我的内核会崩溃?
【发布时间】:2021-12-15 06:51:38
【问题描述】:

我正在尝试用 C 构建一个自定义内核,我想在屏幕上放置一个彩色像素。
我已经切换到保护模式并将VGA模式设置为0x13,所以我用C编写了一个函数,在屏幕右上角放置一个粉红色像素:

#include <stdint.h>

#define VGA_MODE 0x13
#define VRAM 0xA0000
#define MAX_HEIGHT 200
#define MAX_LENGTH 320

void PutPixel(uint8_t color, uint16_t x)
{
    // Return if x greater than 320
    if(x > MAX_LENGTH) return;

    unsigned char* ptr = (unsigned char*) VRAM;

    ptr += x;

    *ptr = color;

    return;
}

void KERNEL_MAIN()
{
    PutPixel(5, 320);

    return;
}

这段代码运行得非常好,直到我想在我的 PutPixel 函数中添加一个 y 偏移作为参数,以便在另一行上绘制一个像素:

#include <stdint.h>

#define VGA_MODE 0x13
#define VRAM 0xA0000
#define MAX_HEIGHT 200
#define MAX_LENGTH 320

void PutPixel(uint8_t color, uint16_t x, uint16_t y)
{
    // Return if x greater than 320
    if(x > MAX_LENGTH) return;

    unsigned char* ptr = (unsigned char*) VRAM;

    ptr += x;

    *ptr = color;

    return;
}

void KERNEL_MAIN()
{
    PutPixel(5, 320, 1);

    return;
}

即使我没有实际使用该参数,当我在 VirtualBox 中运行代码时,它也会立即崩溃,并且在 VirtualBox 调试器中出现以下错误:

dbgf event: DBGFSTOP (hyper)
File:     VINF_EM_TRIPLE_FAULT
Line:     0
Function: <NULL>
eax=000a013e ebx=00007eae ecx=00000005 edx=00000005 esi=00000000 edi=0000fff0
eip=00000083 esp=0002ffac ebp=490002ff iopl=0 nv up di pl nz na pe nc
cs=0008 ds=0010 es=0010 fs=0010 gs=0010 ss=0010               eflags=00200002
0008:00000083 f0 53                   Illegal opcode
VBoxDbg>

我可以得到一些帮助吗?

编辑:我发现堆栈被一个字节弄乱了,所以我 反汇编了函数调用,它看起来像这样:

%0000000000007ee7 55                      push ebp
%0000000000007ee8 48                      dec eax
%0000000000007ee9 89 e5                   mov ebp, esp
%0000000000007eeb 48                      dec eax
%0000000000007eec 83 ec 10                sub esp, byte 000000010h
%0000000000007eef 89 d0                   mov eax, edx
%0000000000007ef1 44                      inc esp               ;???
%0000000000007ef2 89 c2                   mov edx, eax
%0000000000007ef4 88 4d 10                mov byte [ebp+010h], cl
%0000000000007ef7 66 89 45 18             mov word [ebp+018h], ax
%0000000000007efb 89 d0                   mov eax, edx
%0000000000007efd 66 89 45 20             mov word [ebp+020h], ax
%0000000000007f01 66 81 7d 18 40 01       cmp word [ebp+018h], word 00140h
%0000000000007f07 77 1c                   jnbe +01ch (000007f25h)
%0000000000007f09 48                      dec eax
%0000000000007f0a c7 45 f8 00 00 0a 00    mov dword [ebp-008h], 0000a0000h
%0000000000007f11 0f b7 45 18             movzx eax, [ebp+018h]
%0000000000007f15 48                      dec eax
%0000000000007f16 01 45 f8                add dword [ebp-008h], eax
%0000000000007f19 48                      dec eax
%0000000000007f1a 8b 45 f8                mov eax, dword [ebp-008h]
%0000000000007f1d 0f b6 55 10             movzx edx, byte [ebp+010h]
%0000000000007f21 88 10                   mov byte [eax], dl
%0000000000007f23 eb 01                   jmp +001h (000007f26h)
%0000000000007f25 90                      nop
%0000000000007f26 48                      dec eax
%0000000000007f27 83 c4 10                add esp, byte 000000010h
%0000000000007f2a 5d                      pop ebp
%0000000000007f2b c3                      retn

当我在 0x7ef1 处丢弃 inc esp 指令时,retn 会正确弹出返回地址,但为什么这条指令一开始就在那里?

【问题讨论】:

  • 尝试单步执行之前的代码。您正在执行非法指令;问题是你最初是如何到达那个地址的。 C 代码似乎没有任何问题,因此问题很可能与之前的初始化有关,或者可能与您的链接器配置有关。您需要显示错误实际所在的代码 - 最好是从引导开始的minimal reproducible example
  • 特别是,在您调用KERNEL_MAIN() 的代码中,它返回后会做一些明智的事情吗?
  • 某事试图将f0 53 作为指令执行。根据EIP (00000083) 的值,您似乎跟随错误指针进入实模式IVT 所在的空间。一些虚拟机 BIOS 使用 f000:ff53 作为未使用中断的向量,看起来您正在尝试执行该字节序列的一部分。很可能您在某处有某种堆栈损坏,但给定的代码不足以弄清楚是如何发生的。
  • 您是否使用stdcall 或其他带有堆栈参数的被调用者弹出调用约定?如果是这样,调用者和被调用者在 args 的数量上存在分歧,将返回 ESP 指向调用者不期望的位置。 (这就是为什么使用这种调用约定的工具链用foo@4 或任何指示它弹出多少字节的东西来装饰函数名,以获取链接错误而不是运行时错误是正常的,所以你不太可能有这个问题。)跨度>
  • 那个反汇编是 64 位代码。单字节 inc/dec 指令被重新用作长模式下的 REX 前缀。所以你试图在 32 位模式下执行 64 位代码,显然它没有做正确的事情。您需要修复编译器选项来构建 32 位代码。 (或者,如果你的内核应该是 64 位的,那么在调用它之前切换到长模式。)

标签: c assembly x86 virtualbox


【解决方案1】:

我在添加编译器选项时找到了解决方案:

gcc -mno-red-zone

现在可以完美运行了,谢谢!

【讨论】:

  • 这不可能是唯一的变化。以前,您处于 32 位模式,故障转储仅显示 32 位寄存器。但是没有标准的 32 位调用约定首先具有红色区域,只有 x86-64 System V 有(在所有 x86 32 和 64 位 ABI 中)。但是,是的,如果您正在构建 64 位内核,you do need -mno-red-zone。 (可能还有-mcmodel=kernel,还有-mno-sse 和其他选项,使编译器将自身限制为仅接触整数regs。)
猜你喜欢
  • 2020-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-09
  • 2023-02-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多