assembly - 如何在 NASM 中编写 'Hello_world' EFI 应用程序?

标签 assembly x86-64 nasm portable-executable uefi

我正在尝试在 NASM 中编写“Hello_world”EFI 应用程序,但似乎无法使其正常工作。当我运行应用程序(在 VirtualBox 中)时,它不打印任何内容。它只是挂起。这是我的代码:

Bits 64
DEFAULT REL

START:
PE:
HEADER_START:
STANDARD_HEADER:
    .DOS_SIGNATURE              db 'MZ'                                                             ; The DOS signature. This is apparently compulsory
    .DOS_HEADERS                times 60-($-STANDARD_HEADER) db 0                                   ; The DOS Headers. Probably not needed by UEFI
    .SIGNATURE_POINTER          dd .PE_SIGNATURE - START                                            ; Pointer to the PE Signature
    .DOS_STUB                   times 64 db 0                                                       ; The DOS stub. Fill with zeros
    .PE_SIGNATURE               db 'PE', 0x00, 0x00                                                 ; This is the pe signature. The characters 'PE' followed by 2 null bytes
    .MACHINE_TYPE               dw 0x8664                                                           ; Targetting the x64 machine
    .NUMBER_OF_SECTIONS         dw 3                                                                ; Number of sections. Indicates size of section table that immediately follows the headers
    .CREATED_DATE_TIME          dd 1657582794                                                       ; Number of seconds since 1970 since when the file was created
    .SYMBOL_TABLE_POINTER       dd 0x00                                                             ; Pointer to the symbol table. There should be no symbol table in an image so this is 0
    .NUMBER_OF_SYMBOLS          dd 0x00                                                             ; Because there are no symbol tables in an image
    .OPTIONAL_HEADER_SIZE       dw OPTIONAL_HEADER_STOP - OPTIONAL_HEADER_START                     ; Size of the optional header
    .CHARACTERISTICS            dw 0b0010111000100010                                               ; These are the attributes of the file

OPTIONAL_HEADER_START:
    .MAGIC_NUMBER               dw 0x020B                       ; PE32+ (i.e. pe64) magic number
    .MAJOR_LINKER_VERSION       db 0                            ; I'm sure this isn't needed. So set to 0
    .MINOR_LINKER_VERSION       db 0                            ; This too
    .SIZE_OF_CODE               dd CODE_END - CODE              ; The size of the code section
    .INITIALIZED_DATA_SIZE      dd DATA_END - DATA              ; Size of initialized data section
    .UNINITIALIZED_DATA_SIZE    dd 0x00                         ; Size of uninitialized data section
    .ENTRY_POINT_ADDRESS        dd EntryPoint - START           ; Address of entry point relative to image base when the image is loaded in memory
    .BASE_OF_CODE_ADDRESS       dd CODE - START                 ; Relative address of base of code
    .IMAGE_BASE                 dq 0x400000                     ; Where in memory we would prefer the image to be loaded at
    .SECTION_ALIGNMENT          dd 0x1000                       ; Alignment in bytes of sections when they are loaded in memory. Align to page boundry (4kb)
    .FILE_ALIGNMENT             dd 0x1000                       ; Alignment of sections in the file. Also align to 4kb
    .MAJOR_OS_VERSION           dw 0x00                         ; I'm not sure UEFI requires these and the following 'version woo'
    .MINOR_OS_VERSION           dw 0x00                         ; More of these version thingies are to follow. Again, not sure UEFI needs them
    .MAJOR_IMAGE_VERSION        dw 0x00                         ; Major version of the image
    .MINOR_IMAGE_VERSION        dw 0x00                         ; Minor version of the image
    .MAJOR_SUBSYSTEM_VERSION    dw 0x00                         ; 
    .MINOR_SUBSYSTEM_VERSION    dw 0x00                         ;
    .WIN32_VERSION_VALUE        dd 0x00                         ; Reserved, must be 0
    .IMAGE_SIZE                 dd END - START                  ; The size in bytes of the image when loaded in memory including all headers
    .HEADERS_SIZE               dd HEADER_END - HEADER_START    ; Size of all the headers
    .CHECKSUM                   dd 0x00                         ; Hoping this doesn't break the application
    .SUBSYSTEM                  dw 10                           ; The subsystem. In this case we're making a UEFI application.
    .DLL_CHARACTERISTICS        dw 0b000011110010000            ; I honestly don't know what to put here
    .STACK_RESERVE_SIZE         dq 0x200000                     ; Reserve 2MB for the stack... I guess...
    .STACK_COMMIT_SIZE          dq 0x1000                       ; Commit 4kb of the stack
    .HEAP_RESERVE_SIZE          dq 0x200000                     ; Reserve 2MB for the heap... I think... :D
    .HEAP_COMMIT_SIZE           dq 0x1000                       ; Commit 4kb of heap
    .LOADER_FLAGS               dd 0x00                         ; Reserved, must be zero
    .NUMBER_OF_RVA_AND_SIZES    dd 0x10                         ; Number of entries in the data directory

    DATA_DIRECTORIES:
        EDATA:
            .address            dd 0                                        ; Address of export table
            .size               dd 0                                        ; Size of export table
        IDATA:
            .address            dd 0                                        ; Address of import table
            .size               dd 0                                        ; Size of import table
        RSRC:
            .address            dd 0                                        ; Address of resource table
            .size               dd 0                                        ; Size of resource table
        PDATA:
            .address            dd 0                                        ; Address of exception table
            .size               dd 0                                        ; Size of exception table
        CERT:
            .address            dd 0                                        ; Address of certificate table
            .size               dd 0                                        ; Size of certificate table
        RELOC:
            .address            dd END - START                              ; Address of relocation table
            .size               dd 0                                        ; Size of relocation table
        DEBUG:
            .address            dd 0                                        ; Address of debug table
            .size               dd 0                                        ; Size of debug table
        ARCHITECTURE:
            .address            dd 0                                        ; Reserved. Must be 0
            .size               dd 0                                        ; Reserved. Must be 0
        GLOBALPTR:
            .address            dd 0                                        ; RVA to be stored in global pointer register
            .size               dd 0                                        ; Must be 0
        TLS:
            .address            dd 0                                        ; Address of TLS table
            .size               dd 0                                        ; Size of TLS table
        LOADCONFIG:
            .address            dd 0                                        ; Address of Load Config table
            .size               dd 0                                        ; Size of Load Config table
        BOUNDIMPORT:
            .address            dd 0                                        ; Address of bound import table
            .size               dd 0                                        ; Size of bound import table
        IAT:
            .address            dd 0                                        ; Address of IAT
            .size               dd 0                                        ; Size of IAT
        DELAYIMPORTDESCRIPTOR:
            .address            dd 0                                        ; Address of delay import descriptor
            .size               dd 0                                        ; Size of delay import descriptor
        CLRRUNTIMEHEADER:
            .address            dd 0                                        ; Address of CLR runtime header
            .size               dd 0                                        ; Size of CLR runtime header
        RESERVED:
            .address            dd 0                                        ; Reserved, must be 0
            .size               dd 0                                        ; Reserved, must be 0

OPTIONAL_HEADER_STOP:
HEADER_END:


SECTION_HEADERS:
    SECTION_CODE:
        .name                       db ".text", 0x00, 0x00, 0x00
        .virtual_size               dd CODE_END - CODE
        .virtual_address            dd CODE - START
        .size_of_raw_data           dd CODE_END - CODE
        .pointer_to_raw_data        dd CODE - START
        .pointer_to_relocations     dd 0                                    ; Set to 0 for executable images
        .pointer_to_line_numbers    dd 0                                    ; There are no COFF line numbers
        .number_of_relocations      dw 0                                    ; Set to 0 for executable images
        .number_of_line_numbers     dw 0                                    ; Should be 0 for images
        .characteristics            dd 0x70000020                           ; Need to read up more on this

    SECTION_DATA:
        .name                       db ".data", 0x00, 0x00, 0x00
        .virtual_size               dd DATA_END - DATA
        .virtual_address            dd DATA - START
        .size_of_raw_data           dd DATA_END - DATA
        .pointer_to_raw_data        dd DATA - START
        .pointer_to_relocations     dd 0
        .pointer_to_line_numbers    dd 0
        .number_of_relocations      dw 0
        .number_of_line_numbers     dw 0
        .characteristics            dd 0xD0000040

    SECTION_RELOC:
        .name                       db ".reloc", 0x00, 0x00
        .virtual_size               dd 0
        .virtual_address            dd 0
        .size_of_raw_data           dd 0
        .pointer_to_raw_data        dd END - START
        .pointer_to_relocations     dd 0
        .pointer_to_line_numbers    dd 0
        .number_of_relocations      dw 0
        .number_of_line_numbers     dw 0
        .characteristics            dd 0xC2000040

times 4096-($-PE)   db 0
CODE:
    ; The code begins here with the entry point
    EntryPoint:

        ; First order of business is to store the values that were passed to us by EFI
        mov [EFI_IMAGE_HANDLE], rcx
        mov [EFI_SYSTEM_TABLE], rdx

        ; Locate OutputString of the TEXT_OUTPUT_PROTOCOL
        add rdx, EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
        mov rcx, [abs rdx]                                              ; This is the first parameter to the call
        mov rdx, [abs rdx]                                              ; Now rdx points to simple text output protocol
        add rdx, EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OutputString           ; Now rdx points to output string
        mov rax, [abs rdx]                                              ; We'll later do `call rax`

        lea rdx, [hello_message]                                        ; The string to be printed
        sub rsp, 40                                                     ; Shadow space on the stack before the call
        call rax

        mov eax, EFI_SUCCESS
        retn

        align 4096

CODE_END:

; Data begins here
DATA:
    EFI_IMAGE_HANDLE    dq 0x00                                     ; EFI will give use this in rcx
    EFI_SYSTEM_TABLE    dq 0x00                                     ; And this in rdx
    hello_message db __utf16__ `Hello_world\0`                      ; EFI strings are UTF16 and null-terminated

    align 4096
DATA_END:
END:

; Define the needed EFI constants and offsets here.
EFI_SUCCESS                                         equ 0
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL                     equ 64                    
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_Reset               equ 0
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OutputString        equ 8

假设文件保存为“hello.asm”,我使用 nasm -f bin -o hello.efi hello.asm 进行汇编。我可能做错了什么?

P.S:我知道我可以使用结构来计算偏移量,但我想自己进行计算。

P.P.S:这是我在网上找到的一个有效的 FASM 版本:http://x86asm.net/articles/uefi-programming-first-steps/index.html#Building-an-UEFI-application .我使用这个在线工具 (https://manalyzer.org/) 进行检查,发现它的入口点位于 .data 部分。现在我很好奇它的工作原理和原因。

最佳答案

多亏了 osdev forum 的人,我才能够解决这个问题. .HEADERS_SIZE 应该包括章节标题并且应该是 .FILE_ALIGNMENT 的倍数。这意味着 HEADER_END: 行应该紧接在 times 4096-($-PE) db 0 之后,因为我正在通过执行 .HEADERS_SIZE 计算 HEADER_END - HEADER_START

总而言之,这是对我有用的修改后的代码:

Bits 64
DEFAULT REL

START:
PE:
HEADER_START:
STANDARD_HEADER:
    .DOS_SIGNATURE              db 'MZ'                                                             ; The DOS signature. This is apparently compulsory
    .DOS_HEADERS                times 60-($-STANDARD_HEADER) db 0                                   ; The DOS Headers. Probably not needed by UEFI
    .SIGNATURE_POINTER          dd .PE_SIGNATURE - START                                            ; Pointer to the PE Signature
    .DOS_STUB                   times 64 db 0                                                       ; The DOS stub. Fill with zeros
    .PE_SIGNATURE               db 'PE', 0x00, 0x00                                                 ; This is the pe signature. The characters 'PE' followed by 2 null bytes
    .MACHINE_TYPE               dw 0x8664                                                           ; Targetting the x64 machine
    .NUMBER_OF_SECTIONS         dw 3                                                                ; Number of sections. Indicates size of section table that immediately follows the headers
    .CREATED_DATE_TIME          dd 1657582794                                                       ; Number of seconds since 1970 since when the file was created
    .SYMBOL_TABLE_POINTER       dd 0x00                                                             ; Pointer to the symbol table. There should be no symbol table in an image so this is 0
    .NUMBER_OF_SYMBOLS          dd 0x00                                                             ; Because there are no symbol tables in an image
    .OPTIONAL_HEADER_SIZE       dw OPTIONAL_HEADER_STOP - OPTIONAL_HEADER_START                     ; Size of the optional header
    .CHARACTERISTICS            dw 0b0010111000100010                                               ; These are the attributes of the file

OPTIONAL_HEADER_START:
    .MAGIC_NUMBER               dw 0x020B                       ; PE32+ (i.e. pe64) magic number
    .MAJOR_LINKER_VERSION       db 0                            ; I'm sure this isn't needed. So set to 0
    .MINOR_LINKER_VERSION       db 0                            ; This too
    .SIZE_OF_CODE               dd CODE_END - CODE              ; The size of the code section
    .INITIALIZED_DATA_SIZE      dd DATA_END - DATA              ; Size of initialized data section
    .UNINITIALIZED_DATA_SIZE    dd 0x00                         ; Size of uninitialized data section
    .ENTRY_POINT_ADDRESS        dd EntryPoint - START           ; Address of entry point relative to image base when the image is loaded in memory
    .BASE_OF_CODE_ADDRESS       dd CODE - START                 ; Relative address of base of code
    .IMAGE_BASE                 dq 0x400000                     ; Where in memory we would prefer the image to be loaded at
    .SECTION_ALIGNMENT          dd 0x1000                       ; Alignment in bytes of sections when they are loaded in memory. Align to page boundry (4kb)
    .FILE_ALIGNMENT             dd 0x1000                       ; Alignment of sections in the file. Also align to 4kb
    .MAJOR_OS_VERSION           dw 0x00                         ; I'm not sure UEFI requires these and the following 'version woo'
    .MINOR_OS_VERSION           dw 0x00                         ; More of these version thingies are to follow. Again, not sure UEFI needs them
    .MAJOR_IMAGE_VERSION        dw 0x00                         ; Major version of the image
    .MINOR_IMAGE_VERSION        dw 0x00                         ; Minor version of the image
    .MAJOR_SUBSYSTEM_VERSION    dw 0x00                         ; 
    .MINOR_SUBSYSTEM_VERSION    dw 0x00                         ;
    .WIN32_VERSION_VALUE        dd 0x00                         ; Reserved, must be 0
    .IMAGE_SIZE                 dd END - START                  ; The size in bytes of the image when loaded in memory including all headers
    .HEADERS_SIZE               dd HEADER_END - HEADER_START    ; Size of all the headers
    .CHECKSUM                   dd 0x00                         ; Hoping this doesn't break the application
    .SUBSYSTEM                  dw 10                           ; The subsystem. In this case we're making a UEFI application.
    .DLL_CHARACTERISTICS        dw 0b000011110010000            ; I honestly don't know what to put here
    .STACK_RESERVE_SIZE         dq 0x200000                     ; Reserve 2MB for the stack... I guess...
    .STACK_COMMIT_SIZE          dq 0x1000                       ; Commit 4kb of the stack
    .HEAP_RESERVE_SIZE          dq 0x200000                     ; Reserve 2MB for the heap... I think... :D
    .HEAP_COMMIT_SIZE           dq 0x1000                       ; Commit 4kb of heap
    .LOADER_FLAGS               dd 0x00                         ; Reserved, must be zero
    .NUMBER_OF_RVA_AND_SIZES    dd 0x10                         ; Number of entries in the data directory

    DATA_DIRECTORIES:
        EDATA:
            .address            dd 0                                        ; Address of export table
            .size               dd 0                                        ; Size of export table
        IDATA:
            .address            dd 0                                        ; Address of import table
            .size               dd 0                                        ; Size of import table
        RSRC:
            .address            dd 0                                        ; Address of resource table
            .size               dd 0                                        ; Size of resource table
        PDATA:
            .address            dd 0                                        ; Address of exception table
            .size               dd 0                                        ; Size of exception table
        CERT:
            .address            dd 0                                        ; Address of certificate table
            .size               dd 0                                        ; Size of certificate table
        RELOC:
            .address            dd END - START                              ; Address of relocation table
            .size               dd 0                                        ; Size of relocation table
        DEBUG:
            .address            dd 0                                        ; Address of debug table
            .size               dd 0                                        ; Size of debug table
        ARCHITECTURE:
            .address            dd 0                                        ; Reserved. Must be 0
            .size               dd 0                                        ; Reserved. Must be 0
        GLOBALPTR:
            .address            dd 0                                        ; RVA to be stored in global pointer register
            .size               dd 0                                        ; Must be 0
        TLS:
            .address            dd 0                                        ; Address of TLS table
            .size               dd 0                                        ; Size of TLS table
        LOADCONFIG:
            .address            dd 0                                        ; Address of Load Config table
            .size               dd 0                                        ; Size of Load Config table
        BOUNDIMPORT:
            .address            dd 0                                        ; Address of bound import table
            .size               dd 0                                        ; Size of bound import table
        IAT:
            .address            dd 0                                        ; Address of IAT
            .size               dd 0                                        ; Size of IAT
        DELAYIMPORTDESCRIPTOR:
            .address            dd 0                                        ; Address of delay import descriptor
            .size               dd 0                                        ; Size of delay import descriptor
        CLRRUNTIMEHEADER:
            .address            dd 0                                        ; Address of CLR runtime header
            .size               dd 0                                        ; Size of CLR runtime header
        RESERVED:
            .address            dd 0                                        ; Reserved, must be 0
            .size               dd 0                                        ; Reserved, must be 0

OPTIONAL_HEADER_STOP:

SECTION_HEADERS:
    SECTION_CODE:
        .name                       db ".text", 0x00, 0x00, 0x00
        .virtual_size               dd CODE_END - CODE
        .virtual_address            dd CODE - START
        .size_of_raw_data           dd CODE_END - CODE
        .pointer_to_raw_data        dd CODE - START
        .pointer_to_relocations     dd 0                                    ; Set to 0 for executable images
        .pointer_to_line_numbers    dd 0                                    ; There are no COFF line numbers
        .number_of_relocations      dw 0                                    ; Set to 0 for executable images
        .number_of_line_numbers     dw 0                                    ; Should be 0 for images
        .characteristics            dd 0x70000020                           ; Need to read up more on this

    SECTION_DATA:
        .name                       db ".data", 0x00, 0x00, 0x00
        .virtual_size               dd DATA_END - DATA
        .virtual_address            dd DATA - START
        .size_of_raw_data           dd DATA_END - DATA
        .pointer_to_raw_data        dd DATA - START
        .pointer_to_relocations     dd 0
        .pointer_to_line_numbers    dd 0
        .number_of_relocations      dw 0
        .number_of_line_numbers     dw 0
        .characteristics            dd 0xD0000040

    SECTION_RELOC:
        .name                       db ".reloc", 0x00, 0x00
        .virtual_size               dd 0
        .virtual_address            dd END - START
        .size_of_raw_data           dd 0
        .pointer_to_raw_data        dd END - START
        .pointer_to_relocations     dd 0
        .pointer_to_line_numbers    dd 0
        .number_of_relocations      dw 0
        .number_of_line_numbers     dw 0
        .characteristics            dd 0xC2000040

times 4096-($-PE)   db 0
HEADER_END:

CODE:
    ; The code begins here with the entry point
    EntryPoint:

        ; First order of business is to store the values that were passed to us by EFI
        mov [EFI_IMAGE_HANDLE], rcx
        mov [EFI_SYSTEM_TABLE], rdx

        ; Locate OutputString of the TEXT_OUTPUT_PROTOCOL
        add rdx, EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL
        mov rcx, [rdx]                                                  ; This is the first parameter to the call
        mov rdx, [rdx]                                                  ; Now rdx points to simple text output protocol
        add rdx, EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OutputString           ; Now rdx points to output string
        mov rax, [rdx]                                                  ; We'll later do `call rax`

        lea rdx, [hello_message]                                        ; The string to be printed
        sub rsp, 32                                                     ; Shadow space on the stack before the call
        call rax

        add rsp, 32
        mov rax, EFI_SUCCESS

        ret
        align 4096

CODE_END:

; Data begins here
DATA:
    EFI_IMAGE_HANDLE    dq 0x00                                     ; EFI will give use this in rcx
    EFI_SYSTEM_TABLE    dq 0x00                                     ; And this in rdx
    hello_message db __utf16__ `Hello_world\0`                      ; EFI strings are UTF16 and null-terminated

    align 4096
DATA_END:
END:

; Define the needed EFI constants and offsets here.
EFI_SUCCESS                                         equ 0
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL                     equ 64                    
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_Reset               equ 0
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL_OutputString        equ 8

关于assembly - 如何在 NASM 中编写 'Hello_world' EFI 应用程序?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72947069/

相关文章:

gdb - 带汇编程序的gdb:带进位标志的打印状态

c - 使用您自己的页面错误处理程序

linux - assembly 上的清洁控制台

linux - 如何将字符串中的一个字符与 NASM x86_64 Linux 程序集中的另一个字符进行比较

assembly - ARM 汇编中的 "Hello world"

c - 硬件处理器计数器重置不正确

c++ - 如何从C++动态调用汇编函数?

unix - 64 位快速调用

assembly - NASM printf 打印 64 位双段错误

arrays - 在 NASM Assembly 中递归添加数组中的所有元素