Thursday, June 25, 2015

Use C Library in EDK II UEFI Driver

When we use C library in EDK II UEFI Driver, build failure occurs. Please refer UEFI原理與編程 for the detail. This page describes my way to solve it.

We want to use sprintf() of C library in UEFI driver to provide the driver information (e.g., build-date). I like to write the code as below,

#include <stdio.h>

EFI_STATUS
EFIAPI
LibCDxeEntryPoint (
  IN EFI_HANDLE ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  CHAR8 Info [256];
            
  sprintf (Info, "Build Date: %s", __DATE__);

  return EFI_SUCCESS;
}  

, but build failure occurs. Please,

1. Add the following statements in your package DSC file.

These statements declare that we will build C library for UEFI driver.

[LibraryClasses.Common.UEFI_DRIVER]  
  ShellLib|ShellPkg/Library/UefiShellLib/UefiShellLib.inf
  FileHandleLib|MdePkg/Library/UefiFileHandleLib/UefiFileHandleLib.inf  
  SortLib|MdeModulePkg/Library/UefiSortLib/UefiSortLib.inf
  
  LibC|StdLib/LibC/LibC_ForDriver.inf
  #LibC|StdLib/LibC/LibC.inf
  LibCType|StdLib/LibC/Ctype/Ctype.inf  
  LibLocale|StdLib/LibC/Locale/Locale.inf  
  LibSignal|StdLib/LibC/Signal/Signal.inf  
  LibStdio|StdLib/LibC/Stdio/Stdio.inf  
  LibStdLib|StdLib/LibC/StdLib/StdLib.inf
  LibString|StdLib/LibC/String/String.inf  
  LibTime|StdLib/LibC/Time/Time.inf  
  LibUefi|StdLib/LibC/Uefi/Uefi.inf  
  LibWchar|StdLib/LibC/Wchar/Wchar.inf  
  
  LibGen|StdLib/PosixLib/Gen/LibGen.inf  
  LibIIO|StdLib/LibC/Uefi/InteractiveIO/IIO.inf

  LibContainer|StdLib/LibC/Containers/ContainerLib.inf   
  
  LibGdtoa|StdLib/LibC/gdtoa/gdtoa.inf  
  DevConsole|StdLib/LibC/Uefi/Devices/daConsole.inf
  DevUtility|StdLib/LibC/Uefi/Devices/daUtility.inf

2. Use LibC_ForDriver.inf instead of LibC.inf.

We removes ShellCEntryLib from LibC_ForDriver.inf. If we don't do it, the error occurs.

error 1001: Module type [UEFI_DRIVER] is not supported by library instance [...\MdePkg\Library\UefiApplicationEntryPoint\UefiApplicationEntryPoint.inf]

LibC_ForDriver.inf

[LibraryClasses]
#  ShellCEntryLib
  UefiLib
  BaseLib
  BaseMemoryLib
  MemoryAllocationLib
  LibStdLib
  LibStdio
  LibString
  DevConsole

3. Add dummy main().

After we follow the steps, the link error occurs, the main symbol is not defined. Please add dummy main() in your driver.

int
main (
  IN int Argc,
  IN char **Argv
  )
{
  return 0;
}

Now we can build the UEFI driver with C standard library.

-Count

Wednesday, June 17, 2015

Make Your Code Portable

The following are principles to make your code portable.

1) Use C instead of C++.

If you want your library more portable, please use C instead of C++.

2) Use C standard library.

Because EDK II supports C standard library, we just use it.

Please refer Use C Library in EDK II UEFI Driver

3) As far as possible, allocate memory space for a returned parameter before calling a public function.

For example,

EFI_STATUS
CompressData (
  IN UINT8 *Buffer,
  IN UINTN BufferSize,
  OUT UINT8 **Output,
  OUT UINTN *OutputSize
  );

This Compress function allocates memory space for the returned Output. The problem is that the memory management mechanisms are different in environments. In UEFI DXE environment, gBS->AllocatePool() supports the memory space allocation. In SMM mode, UEFI provides SmmAllocatePool(). In Windows KMDF, we should use WdfMemoryCreate to allocate memory space.

Therefore please rework the function as below otherwise we need to create many stubs to wrap the memory management functions for different environments.

EFI_STATUS
GetCompressedDataSize (
  IN UINT8 *Buffer,
  IN UINTN BufferSize,
  OUT UINTN *OutputSize
  );

EFI_STATUS
CompressData (
  IN UINT8 *Buffer,
  IN UINTN BufferSize,
  OUT UINT8 *Output,
  IN UINTN OutputSize
  );

4) The sizes of fields in C struct must be determined.

Don't use UINTN or ulong, the sizes are undetermined, in your struct. If you define struct in UEFI environment, please use UINT8, UINT16, UINT32, CHAR8, CHAR16 of which sizes are always same in IA32 and X64.

-Count

Use C++ in EDK II

There are two ways to write C++ code in EDK II.
1. Make your whole files as CPP
2. Low level files are CPP, High level files are C.

I prefer the second way because we always use C to develop UEFI driver and sometimes we want to reuse C++ components.

Please refer the UEFI原理与编程 for the detail of C++ in EDK II, where my idea comes from.

Below is my source code of UEFI application that uses C++ directly.

CppTest.inf -

The INF file defines the UEFI application, CppTest.c calls C functions of Wrap.cpp that wraps C++ of MyObject.cpp.

[Sources]
  CppTest.c
  MyObject.cpp
  MyObject.h
  Wrap.cpp
  Wrap.h

MyObject.h -

Declare the class MY_OBJECT.

class MY_OBJECT
{
  private:
    UINT8 Value1;
    UINT8 Value2;

  public:

    MY_OBJECT (UINT8 Value1, UINT8 Value2);
    UINT8 GetValue1 ();
    UINT8 GetValue2 ();    
    ~MY_OBJECT ();
};

MyObject.cpp - 

Implement the class MY_OBJECT.

MY_OBJECT::MY_OBJECT (
  IN UINT8 Value1, 
  IN UINT8 Value2
  )
{
  this->Value1 = Value1;
  this->Value2 = Value2;
}

UINT8 
MY_OBJECT::GetValue1 ()
{
  return this->Value1;
}

UINT8 
MY_OBJECT::GetValue2 ()
{
  return this->Value2;
}

MY_OBJECT::~MY_OBJECT ()
{
  this->Value1 = 0;
  this->Value2 = 0;
}

Wrap.h - 

Declare the C functions that wraps the class MY_OBJECT. The term, extern "C", declares that the following functions are name mangling in C. The value of __cplusplus is 1 when CPP file includes the H file so that the term enables. That is why we use #ifdef __cplusplus to wrap the term.

#ifdef __cplusplus
extern "C" {
#endif

VOID 
EFIAPI
CreateMyObject (VOID);

EFI_STATUS
EFIAPI
GetValuesOfMyObject (
  OUT CHAR8 *Buffer,
  IN UINTN BufferSize
  );
  
VOID 
EFIAPI
DestroyMyObj (VOID);  

#ifdef __cplusplus
}
#endif

Wrap.cpp -

Implement the C wrapping functions. We use the term, extern "C", previous to each function.It is interesting that we implement new and delete operators. If we don't do it, link errors about new and delete occurs.

MY_OBJECT *mMyObject;

void * operator new (size_t Size)
{
  VOID *RetVal;
  RetVal = AllocatePool (Size);
  return RetVal;
}

void operator delete (void *p)
{
  FreePool (p);
}

extern "C"
VOID 
EFIAPI
CreateMyObject (VOID)
{
  mMyObject = new MY_OBJECT (10, 20);
}

extern "C"
EFI_STATUS
EFIAPI
GetValuesOfMyObject (
  OUT CHAR8 *Buffer,
  IN UINTN BufferSize
  )
{
  UINTN i;
  i = 0;

  if (i == BufferSize) {
    return EFI_BUFFER_TOO_SMALL;
  }
  Buffer[i++] = mMyObject->GetValue1 ();

  if (i == BufferSize) {
    return EFI_BUFFER_TOO_SMALL;
  }
  Buffer[i++] = mMyObject->GetValue2 ();
  
  return EFI_SUCCESS;
}  

extern "C"
VOID 
EFIAPI
DestroyMyObj (VOID)
{
  delete mMyObject;
}

CppTest.c -

The C main program that tests the C wrapping function.

int
main (
  IN int Argc,
  IN char **Argv
  )
{
  UINT8 Buffer [10];
  MY_STRUCT *MyStruct;
  
  CreateMyObject ();
  
  GetValuesOfMyObject (Buffer, 10);
  printf ("Buffer [0] = %d\n", Buffer [0]);
  printf ("Buffer [1] = %d\n", Buffer [1]);
  
  DestroyMyObj ();
  
  return 0;
}


-Count

Wednesday, June 3, 2015

Link C++ Library in EDK II Module

It is hard to write C++ code in cpp and put it in EDK II INF file to build a driver with EDK2 build system because build failures occur. There are two ways to solve it.

1) We use native C++ compiler (e.g., VC++) to pack C++ code in a library with wrapped pure C function first, copy the library in your UEFI driver source code, and put the lib file in the EDK II INF file.

2) We write C++ code in a cpp file and to put the cpp file in the driver INF file.

This page describes the first way. I will describe the second way in another page, Use C++ in EDK II.

When we select the first way, build failure occurs on link errors,

error LNK2001: unresolved external symbol "void __cdecl `eh vector constructor iterator' ...

error LNK2001: unresolved external symbol "void __cdecl `eh vector destructor iterator' ...

The root cause is, we use new/delete operator to create/destroy an object array. VC++ needs the both symbols to handle the new-delete-object-array operations. The errors don't occur when building a Windows program because Windows environment provides symbols in a Windows specific library. But the link error occurs in UEFI building environment because it lacks the library.

I cannot find a regular and simple solution. My workaround is to modify C++ code to avoid to use new operator in class array.

MY_OBJ *ObjArray = new MY_OBJ [100];
delete[] ObjArray;                  

I use malloc/free instead of new/delete as below for an object array;

MY_OBJ **ObjArray = (MY_OBJ **) malloc (sizeof (MY_OBJ *) * 100);
for (int i = 0; i < 100; i++) {                                  
  ObjArray [i] = new MY_OBJ;                                     
}                                                                
                                                                 
for (int i = 0; i < 100; i++) {                                  
  delete ObjArray [i];                                           
}                                                                
free (ObjArray);                                                 

The regular solution is to use C struct instead of C++ class for platform portability.

Another link error occurs as below.

error LNK2001: unresolved external symbol __CxxFrameHandler3

The symbol __CxxFrameHandler3 is to check buffer overflow. The UEFI building environment lacks the symbol so that the link error occurs.

My solution is to create a dummy __CxxFrameHandler3 function in UEFI source code to avoid the link error.

void __CxxFrameHandler3 (void)
{
  return;
}

-Count