菜单

kaoqi
kaoqi
发布于 2026-08-25 / 7 阅读
0
0

GOP让屏幕变红

继续上一篇GOP系统表调用,本文章的主要目标是让屏幕变为一片红色。

它基于一条如下的逻辑链条:

1、由main函数传入的EFI系统表中可以找到BS表的指针

2、BS表指针用BS结构体解析后可以从中获得一个LocateProtocol函数,并通过一个GOP专用的GUID的值,找到GOP对应的protocol指针

3、GOP对应的protocol指针可以被解析为mode、blt、*Mode三个主要方向

4、对于*Mode,使用一个struct解析后可以获得当前分辨率、显示模式等信息

5、调用GOP中的blt,结合第4步中获取到的分辨率信息,使用一个符合pixel格式定义的红色将整个屏幕填满红色,并保持一定时间

在具体的函数实现中,定义反而是从后往前定义的,下面是一个具体的实现。

编译过程

后续文章中详细介绍了如何根据协议编写这个GOP功能,你还需要完成文件格式转换。

可以参考:http://kaoqi.xyz/archives/uefiluo-ji-bian-cheng

当你完成代码编写后,首先用如下命令编译为o文件:

aarch64-linux-gnu-gcc -fPIC -ffreestanding -fno-stack-protector -c GOP.c -o GOP.o

然后使用lds链接脚本,并使用如下命令链接:(链接脚本可以参考之前提及的文章)

aarch64-linux-gnu-ld -shared -Bsymbolic -T kquefi.lds GOP.o -o GOP.so

最后使用objcopy命令进行PE头格式的转换:

aarch64-linux-gnu-objcopy -j .text -j .data -j .rodata -j .reloc --target=pei-aarch64-little GOP.so GOP.efi

最后,加载到U盘的GPT分区表内,并通过UEFI固件指定的方式打开:

基本数据类型的定义

无论如何,各种数据的位宽总是要放在整个代码文件的最上面去定义的:

typedef unsigned long long UINT64;
typedef long long INT64;
typedef unsigned int UINT32;
typedef unsigned short UINT16;
typedef unsigned short CHAR16;
typedef unsigned char UINT8;
typedef UINT64 UINTN;
typedef INT64 INTN;
typedef void VOID;

这里的这些定义都十分的具体、明确了一系列的具体位宽。

有关于基本EFI函数返回值、基本函数指针,也需要一些基本的定义:

typedef UINT32 EFI_STATUS;
typedef VOID* EFI_HANDLE;

有关GUID这个特殊的数据变量也可以尽量放最前面:

typedef struct {
    UINT32  Data1;
    UINT16  Data2;
    UINT16  Data3;
    UINT8   Data4;
} EFI_GUID;

一些十分确定的表结构也可以放在最前面:

typedef struct {
    UINT64      Signature;
    UINT32      Revision;
    UINT32      HeaderSize;
    UINT32      CRC32;
    UINT32      Reserved;
} EFI_TABLE_HEADER;

GOP协议相关定义

GOP协议的层级结构为:

1、最上层可供BS表查询的PROTOCOL结构

2、由PROTOCOL获取的、用于解析BLT与当前显示屏信息的*Mode结构以及另外两个函数结构

3、由mode指针对应的一个mode信息表的结构

4、由blt结构解析中对应的PIXEL结构,即BltBuffer数组、一个BLT行动的枚举结构

//PIXEL的定义
typedef struct {
    UINT8                        Blue;
    UINT8                        Green;
    UINT8                        Red;
    UINT8                        Reserved;
} EFI_GRAPHICS_OUTPUT_BLT_PIXEL;

//BLT操作的枚举
typedef enum {
    EfiBltVideoFill,
    EfiBltVideoToBltBuffer,
    EfiBltBufferToVideo,
    EfiBltVideoToVideo,
    EfiGraphicsOutputBltOperationMax
} EFI_GRAPHICS_OUTPUT_BLT_OPERATION;

//PIXEL的枚举,由于UEFI原文里也没说这里具体应该对应啥,所以结合GRUB2的源码的话应该就这么枚举就OK
typedef enum {
    PixelRedGreenBlueReserved8BitPerColor,
    PixelBlueGreenRedReserved8BitPerColor,
    PixelBitMask,
    PixelBltOnly,
    PixelFormatMax
} EFI_GRAPHICS_PIXEL_FORMAT;

//PIXEL掩码的定义
typedef struct {
    UINT32              RedMask;
    UINT32              GreenMask;
    UINT32              BlueMask;
    UINT32              ReservedMask;
} EFI_PIXEL_BITMASK;

//mode指针反向解析的下级指针struct
typedef struct {
    UINT32 Version;
    UINT32 HorizontalResolution;
    UINT32 VerticalResolution;
    EFI_GRAPHICS_PIXEL_FORMAT PixelFormat;
    EFI_PIXEL_BITMASK PixelInformation;
    UINT32 PixelsPerScanLine;
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;

// 反向解析第1个
typedef EFI_STATUS 
(*EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE) (
    EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
    UINT32 ModeNumber,
    UINTN *SizeOfInfo,
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
);

// 反向解析第2个
typedef EFI_STATUS
(*EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE) (
    EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
    UINT32 ModeNumber
);

// 反向解析第3个,最重要的BLT部分
typedef EFI_STATUS 
(*EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT) (
    EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
    EFI_GRAPHICS_OUTPUT_BLT_PIXEL *BltBuffer, OPTIONAL
    EFI_GRAPHICS_OUTPUT_BLT_OPERATION BltOperation,
    UINTN SourceX,
    UINTN SourceY,
    UINTN DestinationX,
    UINTN DestinationY,
    UINTN Width,
    UINTN Height,
    UINTN Delta OPTIONAL
);

// 反向解析第4个,mode指针本体struct,可以在获取到GOP协议句柄后调用mode指针,完成解析
typedef struct {
    UINT32 MaxMode;
    UINT32 Mode;
    EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
    UINTN SizeOfInfo;
    UINT64 FrameBufferBase;
    UINTN FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;

//GOP协议由Locate找到后的本体struct
typedef struct EFI_GRAPHICS_OUTPUT_PROTOCOL {
    EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode;
    EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
    EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt;
    EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
} EFI_GRAPHICS_OUTPUT_PROTOCOL;

启动表与系统表的结构

main函数遵循如下的一个调用流程:

1、由main函数导入的EFI系统表指针

2、由EFI系统表struct解析得到的Boot Service启动表

3、由Boot Service启动表struct得到的LocateProtocol函数

4、由Locate函数获取到的GOP的protocol函数指针

其源代码也是一个反向的结构:

// 这是给定的EFI_WATCHDOG_TIMER函数定义
typedef
EFI_STATUS
(*EFI_SET_WATCHDOG_TIMER) (
    UINTN                          Timeout,
    UINT64                         WatchdogCode,
    UINTN                          DataSize,
    CHAR16                         *WatchdogData OPTIONAL
);

// 这是给定的LocateProtocol函数定义
typedef
EFI_STATUS
(*EFI_LOCATE_PROTOCOL) (
    EFI_GUID                            *Protocol,
    VOID                                *Registration OPTIONAL,
    VOID                                **Interface
);

// 然后定义BS表本身,注意,其中大部分的指针都被忽略掉了
typedef struct {
    EFI_TABLE_HEADER                 Hdr;
    UINT64                           _bufA[29]; 
    EFI_SET_WATCHDOG_TIMER           SetWatchdogTimer;
    UINT64                           _bufB[7];
    EFI_LOCATE_PROTOCOL              LocateProtocol;
    UINT64                           _bufC[5];
} EFI_BOOT_SERVICES;

//系统表本表结构体
typedef struct {
    EFI_TABLE_HEADER                 Hdr;
    CHAR16                           *FirmwareVendor;
    UINT32                           FirmwareRevision;
    EFI_HANDLE                       ConsoleInHandle;
    UINT32                           *ConIn;
    EFI_HANDLE                       ConsoleOutHandle;
    UINT32                           *ConOut;
    EFI_HANDLE                       StandardErrorHandle;
    UINT32                           *StdErr;
    UINT32                           *RuntimeServices;
    EFI_BOOT_SERVICES                *BootServices;
    UINTN                            NumberOfTableEntries;
    UINT32                           *ConfigurationTable;
} EFI_SYSTEM_TABLE;

这里在获取GOP的函数指针的时候,需要使用一个**函数的技巧。

对于void方法来说,1个指针*后跟变量,意味着传递一个函数句柄的副本给CPU(所有的函数入参,都不可能在函数内部修改入参的地址值,因为所有函数的入参,其在ISA指令集的本质是将入参的指针复制一份,分别保存为主参A地址的内容保存入参指针地址、形参B地址的内容保存入参指针地址,这么一回事)。无法在函数中修改一个入参的地址,这是C语言函数解释器的特性,也是基于C语言中函数的ISA翻译的定义来的。

main函数本体

在由系统表逐步获取到GOP中对应的功能函数之后,还需要补充main函数本身的一些机制。

1、main函数由系统表、启动表、GUID定义找到GOP协议

2、GOP协议获取Mode解析后,获取当前屏幕的分辨率信息、PIXEL格式信息

3、调用并定义一个pixel实例,调整BLT的EFI_GRAPHICS_OUTPUT_BLT_OPERATION操作码为EfiBltVideoFill,根据PIXEL格式定义一个纯色pixel并根据屏幕分辨率信息,填满整个屏幕

4、保持个10秒左右的时间

//这是整个efi文件的入口函数,也是后续链接器的entry点
//使用main函数时,会逐一反向用到上面定义的各种结构
EFI_STATUS *EFI_Main (EFI_HANDLE ImageHandle,EFI_SYSTEM_TABLE *SystemTable)
{
    //在main函数中可以直接定义一个gop的guid数据结构
    EFI_GUID gop_guid = {0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a}};

    //调用系统表、启动表,找GOP句柄
    EFI_GRAPHICS_OUTPUT_PROTOCOL *GOP = NULL;
    EFI_STATUS Status;

    SystemTable->BootServices;
    SystemTable->BootServices->SetWatchdogTimer(0, 0, 0, NULL);
    Status = SystemTable->BootServices->LocateProtocol(
        &gop_guid,
        NULL,
        (void**)&GOP
    );

    if (Status != 0 || GOP == NULL) {
        return Status; //GOP协议不可用直接返回
    }

    //获取GOP句柄后画画
    unsigned int width  = GOP->Mode->Info->HorizontalResolution;
    unsigned int height = GOP->Mode->Info->VerticalResolution;
    unsigned long long fb_base = GOP->Mode->FrameBufferBase;
    unsigned int *fb_ptr = (unsigned int*)(unsigned long)fb_base;

    unsigned int red_pixel;
    if (GOP->Mode->Info->PixelFormat == 1) {
        red_pixel = 0x00FF0000; // RGBA格式红色
    } else {
        red_pixel = 0x000000FF; // BGRA格式红色
    }

    //填充整个区域为红色
    unsigned int total_pixels = width * height;
    for (unsigned int i = 0; i < total_pixels; i++) {
        fb_ptr[i] = red_pixel;
    }
    return 0;
}


评论