Using the NewBasic Compiler to create UEFI bootable images

The NewBasic Compiler will now create bootable EFI PE files using FASM as the assembler

For example, here is some source code to create a simple Hello World app.  The system will boot
and simply display "Hello, World!" to the screen.  This is a modified version of the source
that will be in the next edition of FYSOS: The System Core, which will include detailed instructions on how to create a boot loader using the UEFI system.

boot.c
/*             Author: Benjamin David Lunt
 *                     Forever Young Software
 *                     Copyright (c) 1984-2024
 *  
 *  This code is included on the disc that is included with the book
 *   FYSOS: The System Core, and is for that purpose only.  You have the
 *   right to use it for learning purposes only.  You may not modify it for
 *   redistribution for any other purpose unless you have written permission
 *   from the author.
 *
 *  You may modify and use it in your own projects as long as they are
 *   for non profit only and not distributed.  Any project for profit that 
 *   uses this code must have written permission from the author.
 */

/*
 *  This is the main C source file for a demo bootable image for UEFI.
 *  This code will show how to clear the screen and print chars to the screen.
 *
 *  To use:
 *   You need a GPT formatted disk image with at least one partition entry
 *   formatted to FAT32 (FAT16 works with most EFI systems), with the following
 *    files in the root directory:
 *     boot.efi
 *     startup.nsh
 *   Then boot the image using an EFI compatible emulator such as Oracle VM VirtualBox
 *  (See the MGPTPART utility included with this book for creating a GPT image)
 *
 *  To Build:
 *   You need the NewBasic C Compiler found at:  https://www.fysnet.net/newbasic.htm
 *    and the Flat Assembler found at: https://flatassembler.net/
 *   Then use the following two command lines to build 'boot.efi'
 *
 *   nbc boot.c -fasm -efi
 *   fasm boot.asm
 */

#pragma proc(486)     // allow atleast 486 instructions
#pragma proc(long)    // we want 32-bit offsets
#pragma ptype(pmode)  //.pmode (all EFI code is in pmode)

#include "ctype.h"
#include "efi_32.h"

/*
 * include the remaining parts of the library
 */
#include "efi_32.c"
#include "conout.c"


/*
 * efi_main()
 * this is what gets called by the EFI boot services
 */
EFI_STATUS efi_main(EFI_HANDLE ImageHandle, struct EFI_SYSTEM_TABLE *SystemTable) {
  
  // initialize our library code
  if (!InitializeLib(ImageHandle, SystemTable))
    return 1;
  
  // clear the screen
  cls();
  
  // print the Hello World string
  puts(L"Hello, World!");
  
  // freeze
  while (1)
    _asm ("hlt \n");
  
  // done
  return EFI_SUCCESS;
}
conout.c
/*
 * cls()
 * call the efi's console out protocol to clear the screen
 */
void cls(void) {
  gSystemTable->ConOut->ClearScreen(gSystemTable->ConOut);
}

/*
 * puts()
 * call the efi's console out protocol to write a string to the screen
 */
void puts(const wchar_t *text) {
  gSystemTable->ConOut->OutputString(gSystemTable->ConOut, text);
}
efi_32.c
/*
 * these two hold our global Image handle and System Table handle
 *  each passed to us by the efi at efi_main()
 */
EFI_HANDLE gImageHandle;
struct EFI_SYSTEM_TABLE *gSystemTable;

/*
 * we call this in efi_main() to check the signature to be sure
 *  we are actually using a known and valid efi system, and to
 *  set our global variables.
 */
bool InitializeLib(EFI_HANDLE ImageHandle, struct EFI_SYSTEM_TABLE *SystemTable) {
  
  // checking the signature
  if ((SystemTable->Hdr.Signature[0] != EFI_SYSTEM_TABLE_SIGNATURE) ||
      (SystemTable->Hdr.Signature[1] != EFI_SYSTEM_TABLE_SIGNATURE2))
        return FALSE;
  
  // set the global variables
  gImageHandle = ImageHandle;
  gSystemTable = SystemTable;
  
  // successfully initialized our code
  return TRUE;
}
efi_32.h
#pragma pack(push, 1)

typedef unsigned int EFI_STATUS;
typedef void *EFI_HANDLE;

#define  EFI_SUCCESS   0

#define  EFI_SYSTEM_TABLE_SIGNATURE       0x20494249
#define  EFI_SYSTEM_TABLE_SIGNATURE2      0x54535953
#define  EFI_RUNTIME_SERVICES_SIGNATURE   0x544E5552
#define  EFI_RUNTIME_SERVICES_SIGNATURE2  0x56524553

struct EFI_TABLE_HEADER {
  bit32u Signature[2];
  bit32u Revision;
  bit32u HeaderSize;
  bit32u CRC32;
  bit32u Reserved;
};

struct EFI_GUID {
  bit32u  Data1;
  bit16u  Data2;
  bit16u  Data3;
  bit8u   Data4[8]; 
};

struct EFI_CONFIGURATION_TABLE {
  struct EFI_GUID VendorGuid;
  void *VendorTable;
};

struct EFI_SYSTEM_TABLE {
  struct EFI_TABLE_HEADER Hdr;
  bit16u    *FirmwareVendor;
  bit32u     FirmwareRevision;

  EFI_HANDLE ConsoleInHandle;
  struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL *ConIn;

  EFI_HANDLE ConsoleOutHandle;
  struct SIMPLE_TEXT_OUTPUT_INTERFACE *ConOut;

  EFI_HANDLE StandardErrorHandle;
  struct SIMPLE_TEXT_OUTPUT_INTERFACE *StdErr;

  struct EFI_RUNTIME_SERVICES *RuntimeServices;
  
  void  *BootServices;
  
  bit32u NumberOfTableEntries;
  
  struct EFI_CONFIGURATION_TABLE *ConfigurationTable;
};

/*
 * The Simple Text Output Interface protocol
 *  we only "define" the two used functions.  if you use
 *  any of the others, you will need to define their parameters.
 *  (see the efi_boot source folder for a more detailed and
 *   an already defined set.)
 */
struct SIMPLE_TEXT_OUTPUT_INTERFACE {
  void  *Reset;
  void  (*OutputString)(bit16u *text, struct SIMPLE_TEXT_OUTPUT_INTERFACE *ConOut);
  void  *TestString;
  void  *QueryMode;
  void  *SetMode;
  void  *SetAttribute;
  void (*ClearScreen)(struct SIMPLE_TEXT_OUTPUT_INTERFACE *ConOut);
  void  *SetCursorPosition;
  void  *EnableCursor;
  void  *Mode;
};

/*
 * The Simple Text Output Interface protocol
 */
struct EFI_SIMPLE_TEXT_INPUT_PROTOCOL {
  void  *Reset;
  void  *ReadKeyStroke;
  void  *WaitForKey;
};

/*
 * The Runtime Serives
 *  (see the efi_boot source folder for a more detailed and
 *   an already defined set.)
 */
struct EFI_RUNTIME_SERVICES {
  struct EFI_TABLE_HEADER Hdr;
  void  *GetTime;
  void  *SetTime;
  void  *GetWakeUpTime;
  void  *SetWakeUpTime;
  void  *SetVirtualAddressMap;
  void  *ConvertPointer;
  void  *GetVariable;
  void  *GetNextVariableName;
  void  *SetVariable;
  void  *GetNextHighMonotonicCount;
  void  *ResetSystem;
};

#pragma pack(pop)

/*
 * Our prototypes
 */
bool InitializeLib(EFI_HANDLE ImageHandle, struct EFI_SYSTEM_TABLE *SystemTable);
void cls(void);
void puts(wchar_t *);
ctype.h
// standard true and false
#define TRUE   1
#define FALSE  0
#define NULL   ((void *) 0)

typedef unsigned  char      bool;

// size of memory operands
typedef   signed  char      bit8s;     // 8 bit signed char
typedef unsigned  char      bit8u;     // 8 bit unsigned char
typedef   signed short      bit16s;    // 16 bit signed word
typedef unsigned short      bit16u;    // 16 bit unsigned word
typedef   signed  long      bit32s;    // 32 bit signed double word
typedef unsigned  long      bit32u;    // 32 bit unsigned double word
startup.nsh
fs0:
boot.efi
Please note that the modifications I made to NBC are just enough to compile the code I needed to show how to create an EFI bootable image, as well as the source listed above. If I didn't need a function call, I simply declared it as a "void *FunctionName". Further modifications to NBC and a more detailed source code will be available when the next edition of this book is released. Thanks.