Step to UEFI (77) —–改造 Stall和 MV

我们在 EDK2 的代码中能看到 Shell下部分命令的代码,这里介绍如何把这样的代码提取出来做成能够独立编译和运行的程序。简单起见,以 Stall 命令和 MV 命令为例。

经过试验,这些命令中使用到的大部分函数都可以在 ShellLib.h 中找到,我们要做的只是把这个文件copy一份到我们程序下面。
最后修改之后的程序如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>

#include  "ShellLib.h"

#define ASSERT(Expression)
  
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE		*gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

CONST CHAR16 STR_GEN_PROBLEM[] = L"Error. The argument '%s' is incorrect.\r\n";
CONST CHAR16 STR_GEN_TOO_FEW[] = L"Error. Too few arguments specified.\r\n";
CONST CHAR16 STR_GEN_TOO_MANY[]= L"Error. Too many arguments specified.\r\n";
CONST CHAR16 STR_GEN_PROBLEM_VAL[] = L"Error. The argument '%s' has incorrect value.\r\n";
CONST CHAR16 STR_STALL_FAILED[]    = L"Error. BootService Stall() failed with %r.\r\n";

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS          Status;
	LIST_ENTRY          *Package;
	CHAR16              *ProblemParam;
    SHELL_STATUS        ShellStatus;
	UINT64              Intermediate;
  
	//
  // parse the command line
  //
  Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR(Status)) {
    if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
      Print(STR_GEN_PROBLEM,ProblemParam);
      FreePool(ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT(FALSE);
    }
  }  else {
    if (ShellCommandLineGetRawValue(Package, 2) != NULL) {
      Print(STR_GEN_TOO_MANY);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else if (ShellCommandLineGetRawValue(Package, 1) == NULL) {
      Print(STR_GEN_TOO_FEW);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      Status = ShellConvertStringToUint64(
				ShellCommandLineGetRawValue(Package, 1), &Intermediate, FALSE, FALSE);
      if (EFI_ERROR(Status) || ((UINT64)(UINTN)(Intermediate)) != Intermediate) {
        Print(STR_GEN_PROBLEM_VAL, ShellCommandLineGetRawValue(Package, 1));
        ShellStatus = SHELL_INVALID_PARAMETER;
      } else {
        Status = gBS->Stall((UINTN)Intermediate);
        if (EFI_ERROR(Status)) {
          Print(STR_STALL_FAILED, Status);
          ShellStatus = SHELL_DEVICE_ERROR;
        }
      }
    }
    ShellCommandLineFreeVarList (Package);
  }
  
  return EFI_SUCCESS;
}

 

运行结果:

zstall

完整代码下载(特别注意,涉及到时钟的程序在NT32模拟环境中和实际环境中存在很大差别,不要在实际环境中使用为模拟环境编译的EFI文件)。
zStall

总结一下,如果想把一个命令改造为独立的程序,需要做下面的事情:

1. 增加 #define ASSERT(Expression) 这个定义,上面代码为了简单,我只是定义它为空
2. 拷贝 ShellLib.h 到你的代码目录下,然后用 “”直接使用
3. 改造程序中定义的字符串,这些字符串都是定义在 UNI 文件中。如果你没有多语言的需要,可以像我这样在代码中重新定义一次
4. 将所有的 ShellPrintHiiEx 都修改为 Print

下面再用 MV 命令的代码练习一下

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/UnicodeCollation.h>

#include  "ShellLib.h"

#define ASSERT(Expression)
  
extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE		*gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

CONST CHAR16 STR_GEN_PROBLEM[] =    L"Error. The argument '%s' is incorrect.\r\n";
CONST CHAR16 STR_GEN_TOO_MANY[]=    L"Error. Too many arguments specified.\r\n";
CONST CHAR16 STR_GEN_TOO_FEW[] =    L"Error. Too few arguments specified.\r\n";
CONST CHAR16 STR_GEN_NO_CWD[]  =    L"Error. No current directory is specified.\r\n";
CONST CHAR16 STR_GEN_FILE_NF[] =    L"Error. File '%s' was not found.\r\n";
CONST CHAR16 STR_GEN_ERR_FILE[]=    L"Error. File '%s' error: %r\r\n";
CONST CHAR16 STR_MV_INV_SUB[]  = L"Error. Cannot move a directory into itself or its subdirectory.\r\n";
CONST CHAR16 STR_MV_INV_RO[]   = L"Error. Cannot move a read-only File or Directory.\r\n";
CONST CHAR16 STR_MV_INV_CWD[]  = L"Error. Cannot move current working directory or its subdirectory.\r\n";
CONST CHAR16 STR_MV_INV_FS[]   = L"Error. Cannot move between file systems.\r\n";
CONST CHAR16 STR_GEN_NO_MEM[]  = L"Error. Memory is not available.\r\n";
CONST CHAR16 STR_GEN_ERR_UK[]  = L"Error: %r\r\n";
CONST CHAR16 STR_MV_OUTPUT[]   = L"Moving %s -> %s\r\n";
CONST CHAR16 STR_GEN_RES_OK[]  = L"- [ok]\r\n";
CONST CHAR16 STR_GEN_MARG_ERROR[] = L"Error. The destination '%s' is ambigious.\r\n";
CONST CHAR16 STR_GEN_FILE_ERROR[] = L"Error. The destination is an existant file '%s'.\r\n";
      
//copy from \ShellPkg\Library\BasePathLib\BasePathLib.c
/**
  Removes the last directory or file entry in a path by changing the last
  L'\' to a CHAR_NULL.

  @param[in, out] Path    The pointer to the path to modify.

  @retval FALSE     Nothing was found to remove.
  @retval TRUE      A directory or file was removed.
**/
BOOLEAN
EFIAPI
PathRemoveLastItem(
  IN OUT CHAR16 *Path
  )
{
  CHAR16        *Walker;
  CHAR16        *LastSlash;
  //
  // get directory name from path... ('chop' off extra)
  //
  for ( Walker = Path, LastSlash = NULL
      ; Walker != NULL && *Walker != CHAR_NULL
      ; Walker++
     ){
    if (*Walker == L'\\' && *(Walker + 1) != CHAR_NULL) {
      LastSlash = Walker+1;
    }
  }
  if (LastSlash != NULL) {
    *LastSlash = CHAR_NULL;
    return (TRUE);
  }
  return (FALSE);
}

/**
  Function to take a destination path that might contain wildcards and verify
  that there is only a single possible target (IE we cant have wildcards that
  have 2 possible destination).

  if the result is sucessful the caller must free *DestPathPointer.

  @param[in] DestDir               The original path to the destination.
  @param[in, out] DestPathPointer  A pointer to the callee allocated final path.
  @param[in] Cwd                   A pointer to the current working directory.

  @retval SHELL_INVALID_PARAMETER  The DestDir could not be resolved to a location.
  @retval SHELL_INVALID_PARAMETER  The DestDir could be resolved to more than 1 location.
  @retval SHELL_INVALID_PARAMETER  Cwd is required and is NULL.
  @retval SHELL_SUCCESS            The operation was sucessful.
**/
SHELL_STATUS
EFIAPI
GetDestinationLocation(
  IN CONST CHAR16               *DestDir,
  IN OUT CHAR16                 **DestPathPointer,
  IN CONST CHAR16               *Cwd
  )
{
  EFI_SHELL_FILE_INFO       *DestList;
  EFI_SHELL_FILE_INFO       *Node;
  CHAR16                    *DestPath;
  UINTN                     NewSize;
  UINTN                     CurrentSize;

  DestList = NULL;
  DestPath = NULL;

  if (StrStr(DestDir, L"\\") == DestDir) {
    if (Cwd == NULL) {
      return SHELL_INVALID_PARAMETER;
    }
    DestPath = AllocateZeroPool(StrSize(Cwd));
    if (DestPath == NULL) {
      return (SHELL_OUT_OF_RESOURCES);
    }
    StrCpy(DestPath, Cwd);
    while (PathRemoveLastItem(DestPath)) ;

    //
    // Append DestDir beyond '\' which may be present
    //
    CurrentSize = StrSize(DestPath);
    StrnCatGrow(&DestPath, &CurrentSize, &DestDir[1], 0);

    *DestPathPointer =  DestPath;
    return (SHELL_SUCCESS);
  }
  //
  // get the destination path
  //
  ShellOpenFileMetaArg((CHAR16*)DestDir, EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ|EFI_FILE_MODE_CREATE, &DestList);
  if (DestList == NULL || IsListEmpty(&DestList->Link)) {
    //
    // Not existing... must be renaming
    //
    if (StrStr(DestDir, L":") == NULL) {
      if (Cwd == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_INVALID_PARAMETER);
      }
      NewSize = StrSize(Cwd);
      NewSize += StrSize(DestDir);
      DestPath = AllocateZeroPool(NewSize);
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
      StrCpy(DestPath, Cwd);
      if (DestPath[StrLen(DestPath)-1] != L'\\' && DestDir[0] != L'\\') {
        StrCat(DestPath, L"\\");
      } else if (DestPath[StrLen(DestPath)-1] == L'\\' && DestDir[0] == L'\\') {
        ((CHAR16*)DestPath)[StrLen(DestPath)-1] = CHAR_NULL;
      }
      StrCat(DestPath, DestDir);
    } else {
      ASSERT(DestPath == NULL);
      DestPath = StrnCatGrow(&DestPath, NULL, DestDir, 0);
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
    }
  } else {
    Node = (EFI_SHELL_FILE_INFO*)GetFirstNode(&DestList->Link);
    //
    // Make sure there is only 1 node in the list.
    //
    if (!IsNodeAtEnd(&DestList->Link, &Node->Link)) {
      ShellCloseFileMetaArg(&DestList);
      Print(STR_GEN_MARG_ERROR, DestDir);
      return (SHELL_INVALID_PARAMETER);
    }
    if (ShellIsDirectory(Node->FullName)==EFI_SUCCESS) {
      DestPath = AllocateZeroPool(StrSize(Node->FullName)+sizeof(CHAR16));
      if (DestPath == NULL) {
        ShellCloseFileMetaArg(&DestList);
        return (SHELL_OUT_OF_RESOURCES);
      }
      StrCpy(DestPath, Node->FullName);
      StrCat(DestPath, L"\\");
    } else {
      //
      // cant move onto another file.
      //
      ShellCloseFileMetaArg(&DestList);
      Print(STR_GEN_FILE_ERROR,DestDir);
      return (SHELL_INVALID_PARAMETER);
    }
  }

  *DestPathPointer =  DestPath;
  ShellCloseFileMetaArg(&DestList);

  return (SHELL_SUCCESS);
}


/**
  Function to clean up paths.  
  
  - Single periods in the path are removed.
  - Double periods in the path are removed along with a single parent directory.
  - Forward slashes L'/' are converted to backward slashes L'\'.

  This will be done inline and the existing buffer may be larger than required 
  upon completion.

  @param[in] Path       The pointer to the string containing the path.

  @retval NULL          An error occured.
  @return Path in all other instances.
**/
CHAR16*
EFIAPI
PathCleanUpDirectories(
  IN CHAR16 *Path
  )
{
  CHAR16  *TempString;
  UINTN   TempSize;
  if (Path==NULL) {
    return(NULL);
  }

  //
  // Fix up the '/' vs '\'
  //
  for (TempString = Path ; TempString != NULL && *TempString != CHAR_NULL ; TempString++) {
    if (*TempString == L'/') {
      *TempString = L'\\';
    }
  }

  //
  // Fix up the ..
  //
  while ((TempString = StrStr(Path, L"\\..\\")) != NULL) {
    *TempString = CHAR_NULL;
    TempString  += 4;
    PathRemoveLastItem(Path);
    TempSize = StrSize(TempString);
    CopyMem(Path+StrLen(Path), TempString, TempSize);
  }
  if ((TempString = StrStr(Path, L"\\..")) != NULL && *(TempString + 3) == CHAR_NULL) {
    *TempString = CHAR_NULL;
    PathRemoveLastItem(Path);
  }

  //
  // Fix up the .
  //
  while ((TempString = StrStr(Path, L"\\.\\")) != NULL) {
    *TempString = CHAR_NULL;
    TempString  += 2;
    TempSize = StrSize(TempString);
    CopyMem(Path+StrLen(Path), TempString, TempSize);
  }
  if ((TempString = StrStr(Path, L"\\.")) != NULL && *(TempString + 2) == CHAR_NULL) {
    *(TempString + 1) = CHAR_NULL;
  }



  return (Path);
}

STATIC EFI_UNICODE_COLLATION_PROTOCOL   *mUnicodeCollation = NULL;

/**
  Function to compare 2 strings without regard to case of the characters.

  @param[in] Buffer1            Pointer to String to compare.
  @param[in] Buffer2            Pointer to second String to compare.

  @retval 0                     Buffer1 equal to Buffer2.
  @return < 0                   Buffer1 is less than Buffer2.
  @return > 0                   Buffer1 is greater than Buffer2.
**/
INTN
EFIAPI
StringNoCaseCompare (
  IN  CONST VOID             *Buffer1,
  IN  CONST VOID             *Buffer2
  )
{
  EFI_STATUS                Status;
  if (mUnicodeCollation == NULL) {
    Status = gBS->LocateProtocol(
      &gEfiUnicodeCollation2ProtocolGuid,
      NULL,
      (VOID**)&mUnicodeCollation);

    ASSERT(Status);
  }

  return (mUnicodeCollation->StriColl(
    mUnicodeCollation,
    *(CHAR16**)Buffer1,
    *(CHAR16**)Buffer2));
}

/**
  Function to validate that moving a specific file (FileName) to a specific
  location (DestPath) is valid.

  This function will verify that the destination is not a subdirectory of
  FullName, that the Current working Directory is not being moved, and that
  the directory is not read only.

  if the move is invalid this function will report the error to StdOut.

  @param FullName [in]    The name of the file to move.
  @param Cwd      [in]    The current working directory
  @param DestPath [in]    The target location to move to
  @param Attribute[in]    The Attribute of the file

  @retval TRUE        The move is valid
  @retval FALSE       The move is not
**/
BOOLEAN
EFIAPI
IsValidMove(
  IN CONST CHAR16   *FullName,
  IN CONST CHAR16   *Cwd,
  IN CONST CHAR16   *DestPath,
  IN CONST UINT64   Attribute
  )
{
  CHAR16  *Test;
  CHAR16  *Test1;
  CHAR16  *TestWalker;
  INTN    Result;
  UINTN   TempLen;
  if (Cwd != NULL && StrCmp(FullName, Cwd) == 0) {
    //
    // Invalid move
    //
    Print(STR_MV_INV_CWD);
    return (FALSE);
  }
  Test = NULL;
  Test = StrnCatGrow(&Test, NULL, DestPath, 0);
  TestWalker = Test;
  ASSERT(TestWalker != NULL);
  while(*TestWalker == L'\\') {
    TestWalker++;
  }
  while(TestWalker != NULL && TestWalker[StrLen(TestWalker)-1] == L'\\') {
    TestWalker[StrLen(TestWalker)-1] = CHAR_NULL;
  }
  ASSERT(TestWalker != NULL);
  ASSERT(FullName   != NULL);
  if (StrStr(FullName, TestWalker) != 0) {
    TempLen = StrLen(FullName);
    if (StrStr(FullName, TestWalker) != FullName                    // not the first items... (could below it)
      && TempLen <= (StrLen(TestWalker) + 1)
      && StrStr(FullName+StrLen(TestWalker) + 1, L"\\") == NULL) {
      //
      // Invalid move
      //
      Print(STR_MV_INV_SUB);
      FreePool(Test);
      return (FALSE);
    }
  }
  FreePool(Test);
  if (StrStr(DestPath, FullName) != 0 && StrStr(DestPath, FullName) != DestPath) {
    //
    // Invalid move
    //
    Print(STR_MV_INV_SUB);
    return (FALSE);
  }
  if ((Attribute & EFI_FILE_READ_ONLY) != 0) {
    //
    // invalid to move read only
    //
    Print(STR_MV_INV_RO);
    return (FALSE);
  }
  Test  = StrStr(FullName, L":");
  Test1 = StrStr(DestPath, L":");
  if (Test1 != NULL && Test  != NULL) {
    *Test  = CHAR_NULL;
    *Test1 = CHAR_NULL;
    Result = StringNoCaseCompare(&FullName, &DestPath);
    *Test  = L':';
    *Test1 = L':';
    if (Result != 0) {
      Print(STR_MV_INV_FS);
      return (FALSE);
    }
  }
  return (TRUE);
}

/**
  function to take a list of files to move and a destination location and do
  the verification and moving of those files to that location.  This function
  will report any errors to the user and continue to move the rest of the files.

  @param[in] FileList           A LIST_ENTRY* based list of files to move
  @param[out] Resp              pointer to response from question.  Pass back on looped calling
  @param[in] DestDir            the destination location

  @retval SHELL_SUCCESS             the files were all moved.
  @retval SHELL_INVALID_PARAMETER   a parameter was invalid
  @retval SHELL_SECURITY_VIOLATION  a security violation ocurred
  @retval SHELL_WRITE_PROTECTED     the destination was write protected
  @retval SHELL_OUT_OF_RESOURCES    a memory allocation failed
**/
SHELL_STATUS
EFIAPI
ValidateAndMoveFiles(
  IN CONST EFI_SHELL_FILE_INFO  *FileList,
  OUT VOID                      **Resp,
  IN CONST CHAR16               *DestDir
  )
{
  EFI_STATUS                Status;
  CHAR16                    *DestPath;
  CONST CHAR16              *Cwd;
  SHELL_STATUS              ShellStatus;
  CONST EFI_SHELL_FILE_INFO *Node;
  EFI_FILE_INFO             *NewFileInfo;
  CHAR16                    *TempLocation;
  UINTN                     NewSize;
  UINTN                     Length;
  VOID                      *Response;
  SHELL_FILE_HANDLE         DestHandle;
  CHAR16 STR_GEN_DEST_EXIST_OVR[] = L"Destination file already exists.  Overwrite? Yes, No, All, Cancel ";
  
  ASSERT(FileList != NULL);
  ASSERT(DestDir  != NULL);

  DestPath = NULL;
  Cwd      = ShellGetCurrentDir(NULL);
  Response = *Resp;

  //
  // Get and validate the destination location
  //
  ShellStatus = GetDestinationLocation(DestDir, &DestPath, Cwd);
  if (ShellStatus != SHELL_SUCCESS) {
    return (ShellStatus);
  }
  DestPath = PathCleanUpDirectories(DestPath);

  //
  // Go through the list of files and directories to move...
  //
  for (Node = (EFI_SHELL_FILE_INFO *)GetFirstNode(&FileList->Link)
    ;  !IsNull(&FileList->Link, &Node->Link)
    ;  Node = (EFI_SHELL_FILE_INFO *)GetNextNode(&FileList->Link, &Node->Link)
   ){
    if (ShellGetExecutionBreakFlag()) {
      break;
    }
    ASSERT(Node->FileName != NULL);
    ASSERT(Node->FullName != NULL);

    //
    // skip the directory traversing stuff...
    //
    if (StrCmp(Node->FileName, L".") == 0 || StrCmp(Node->FileName, L"..") == 0) {
      continue;
    }

    //
    // Validate that the move is valid
    //
    if (!IsValidMove(Node->FullName, Cwd, DestPath, Node->Info->Attribute)) {
      ShellStatus = SHELL_INVALID_PARAMETER;
      continue;
    }

    //
    // Chop off map info from "DestPath"
    //
    if ((TempLocation = StrStr(DestPath, L":")) != NULL) {
      CopyMem(DestPath, TempLocation+1, StrSize(TempLocation+1));
    }

    //
    // construct the new file info block
    //
    NewSize = StrSize(DestPath);
    NewSize += StrSize(Node->FileName) + SIZE_OF_EFI_FILE_INFO + sizeof(CHAR16);
    NewFileInfo = AllocateZeroPool(NewSize);
    if (NewFileInfo == NULL) {
      Print(STR_GEN_NO_MEM);
      ShellStatus = SHELL_OUT_OF_RESOURCES;
    } else {
      CopyMem(NewFileInfo, Node->Info, SIZE_OF_EFI_FILE_INFO);
      if (DestPath[0] != L'\\') {
        StrCpy(NewFileInfo->FileName, L"\\");
        StrCat(NewFileInfo->FileName, DestPath);
      } else {
        StrCpy(NewFileInfo->FileName, DestPath);
      }
      Length = StrLen(NewFileInfo->FileName);
      if (Length > 0) {
        Length--;
      }
      if (NewFileInfo->FileName[Length] == L'\\') {
        if (Node->FileName[0] == L'\\') {
          //
          // Don't allow for double slashes. Eliminate one of them.
          //
          NewFileInfo->FileName[Length] = CHAR_NULL;
        }
        StrCat(NewFileInfo->FileName, Node->FileName);
      }
      NewFileInfo->Size = SIZE_OF_EFI_FILE_INFO + StrSize(NewFileInfo->FileName);
      Print(STR_MV_OUTPUT, Node->FullName, NewFileInfo->FileName);

      if (!EFI_ERROR(ShellFileExists(NewFileInfo->FileName))) {
        if (Response == NULL) {
          ShellPromptForResponse(ShellPromptResponseTypeYesNoAllCancel, STR_GEN_DEST_EXIST_OVR,  &Response);
        }
        switch (*(SHELL_PROMPT_RESPONSE*)Response) {
          case ShellPromptResponseNo:
            FreePool(NewFileInfo);
            continue;
          case ShellPromptResponseCancel:
            *Resp = Response;
            //
            // indicate to stop everything
            //
            FreePool(NewFileInfo);
            FreePool(DestPath);
            return (SHELL_ABORTED);
          case ShellPromptResponseAll:
            *Resp = Response;
            break;
          case ShellPromptResponseYes:
            FreePool(Response);
            break;
          default:
            FreePool(Response);
            FreePool(NewFileInfo);
            FreePool(DestPath);
            return SHELL_ABORTED;
        }
        Status = ShellOpenFileByName(NewFileInfo->FileName, &DestHandle, EFI_FILE_MODE_READ|EFI_FILE_MODE_WRITE, 0);
        ShellDeleteFile(&DestHandle);
      }


      //
      // Perform the move operation
      //
      Status = ShellSetFileInfo(Node->Handle, NewFileInfo);

      //
      // Free the info object we used...
      //
      FreePool(NewFileInfo);

      //
      // Check our result
      //
      if (EFI_ERROR(Status)) {
        Print(STR_GEN_ERR_UK, Status);
        ShellStatus = SHELL_INVALID_PARAMETER;
        if (Status == EFI_SECURITY_VIOLATION) {
          ShellStatus = SHELL_SECURITY_VIOLATION;
        } else if (Status == EFI_WRITE_PROTECTED) {
          ShellStatus = SHELL_WRITE_PROTECTED;
        } else if (Status == EFI_OUT_OF_RESOURCES) {
          ShellStatus = SHELL_OUT_OF_RESOURCES;
        } else if (Status == EFI_DEVICE_ERROR) {
          ShellStatus = SHELL_DEVICE_ERROR;
        } else if (Status == EFI_ACCESS_DENIED) {
          ShellStatus = SHELL_ACCESS_DENIED;
        }
      } else {
        Print( L"%s", STR_GEN_RES_OK);
      }
    }
  } // for loop

  FreePool(DestPath);
  return (ShellStatus);
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  EFI_STATUS          Status;
  LIST_ENTRY          *Package;
  CHAR16              *ProblemParam;
  SHELL_STATUS        ShellStatus;
  UINTN               ParamCount;
  UINTN               LoopCounter;
  EFI_SHELL_FILE_INFO *FileList;
  VOID                *Response;

  ProblemParam        = NULL;
  ShellStatus         = SHELL_SUCCESS;
  ParamCount          = 0;
  FileList            = NULL;
  Response            = NULL;

  //
  // parse the command line
  //
  Status = ShellCommandLineParse (EmptyParamList, &Package, &ProblemParam, TRUE);
  if (EFI_ERROR(Status)) {
    if (Status == EFI_VOLUME_CORRUPTED && ProblemParam != NULL) {
      Print(STR_GEN_PROBLEM , ProblemParam);
      FreePool(ProblemParam);
      ShellStatus = SHELL_INVALID_PARAMETER;
    } else {
      ASSERT(FALSE);
    }
  } else {
    //
    // check for "-?"
    //
    if (ShellCommandLineGetFlag(Package, L"-?")) {
      ASSERT(FALSE);
    }

    switch (ParamCount = ShellCommandLineGetCount(Package)) {
      case 0:
      case 1:
        //
        // we have insufficient parameters
        //
        Print(STR_GEN_TOO_FEW);
        ShellStatus = SHELL_INVALID_PARAMETER;
        break;
      case 2:
        //
        // must have valid CWD for single parameter...
        //
        if (ShellGetCurrentDir(NULL) == NULL){
          Print(STR_GEN_NO_CWD);
          ShellStatus = SHELL_INVALID_PARAMETER;
        } else {
          Status = ShellOpenFileMetaArg((CHAR16*)ShellCommandLineGetRawValue(Package, 1), EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ, &FileList);
          if (FileList == NULL || IsListEmpty(&FileList->Link) || EFI_ERROR(Status)) {
            Print(STR_GEN_FILE_NF, ShellCommandLineGetRawValue(Package, 1));
            ShellStatus = SHELL_NOT_FOUND;
          } else  {
            //
            // ValidateAndMoveFiles will report errors to the screen itself
            //
            ShellStatus = ValidateAndMoveFiles(FileList, &Response, ShellGetCurrentDir(NULL));
          }
        }

        break;
      default:
        ///@todo make sure this works with error half way through and continues...
        for (ParamCount--, LoopCounter = 1 ; LoopCounter < ParamCount ; LoopCounter++) {
          if (ShellGetExecutionBreakFlag()) {
            break;
          }
          Status = ShellOpenFileMetaArg((CHAR16*)ShellCommandLineGetRawValue(Package, LoopCounter), EFI_FILE_MODE_WRITE|EFI_FILE_MODE_READ, &FileList);
          if (FileList == NULL || IsListEmpty(&FileList->Link) || EFI_ERROR(Status)) {
            Print(STR_GEN_FILE_NF, ShellCommandLineGetRawValue(Package, LoopCounter));
            ShellStatus = SHELL_NOT_FOUND;
          } else  {
            //
            // ValidateAndMoveFiles will report errors to the screen itself
            // Only change ShellStatus if it's sucessful
            //
            if (ShellStatus == SHELL_SUCCESS) {
              ShellStatus = ValidateAndMoveFiles(FileList, &Response, ShellCommandLineGetRawValue(Package, ParamCount));
            } else {
              ValidateAndMoveFiles(FileList, &Response, ShellCommandLineGetRawValue(Package, ParamCount));
            }
          }
          if (FileList != NULL && !IsListEmpty(&FileList->Link)) {
            Status = ShellCloseFileMetaArg(&FileList);
            if (EFI_ERROR(Status) && ShellStatus == SHELL_SUCCESS) {
              ShellStatus = SHELL_ACCESS_DENIED;
              Print(STR_GEN_ERR_FILE , ShellCommandLineGetRawValue(Package, 1), ShellStatus|MAX_BIT);
            }
          }
        }
        break;
    } // switch on parameter count

    if (FileList != NULL) {
      ShellCloseFileMetaArg(&FileList);
    }

    //
    // free the command line package
    //
    ShellCommandLineFreeVarList (Package);
  }

  SHELL_FREE_NON_NULL(Response);

  if (ShellGetExecutionBreakFlag()) {
    return (SHELL_ABORTED);
  }
  
  return EFI_SUCCESS;
}

 

运行结果:

zmv1

完整代码下载:

zMv

Step to UEFI (76) —–Dump ACPI DSDT

前面介绍了如何在 Shell下 Dump 系统中各种 ConfigurationTable的方法,这里介绍一下如何取得 ACPI 的 DSDT Table。
简单说一下原理:

1. 在ConfigurationTable中查找 GUID 为 ACPI_TABLE_GUID 和 EFI_ACPI_TABLE_GUID 的两个 Table. 检查获得 Table 的 Revision,只有 >=2 的才是我们需要的
2. 通过Table找到 XsdtAddress 这样我们能找到 XSDT Table
3. 在 XSDT 给出的Table中找到指向 FADT Table 的
4. 在 FADT 中直接给出了 DSDT 的地址
5. 最后将整个DSDT Table dump出来保存为文件即可

用图表简单描述上述过程

image001

代码看起来很复杂,主要是各种指针比较多。

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Library/BaseMemoryLib.h>

#include <Protocol/SimpleFileSystem.h>

#define ACPI_TABLE_GUID \
  { \
    0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \
  }

#define EFI_ACPI_TABLE_GUID \
  { \
    0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x0, 0x80, 0xc7, 0x3c, 0x88, 0x81 } \
  }

#define zDebug 1

///
/// RSD_PTR Revision (as defined in ACPI 5.0 spec.)
///
#define EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION 0x02  
			///< ACPISpec (Revision 5.0) says current value is 2
  
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_BOOT_SERVICES         *gBS;

#pragma pack(1)
// \MdePkg\Include\IndustryStandard\Acpi50.h
//
// ACPI 5.0 table structures
//

///
/// Root System Description Pointer Structure
///
typedef struct {
  UINT64  Signature;
  UINT8   Checksum;
  UINT8   OemId[6];
  UINT8   Revision;
  UINT32  RsdtAddress;
  UINT32  Length;
  UINT64  XsdtAddress;
  UINT8   ExtendedChecksum;
  UINT8   Reserved[3];
} EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER;

// \MdePkg\Include\IndustryStandard\Acpi10.h
///
/// The common ACPI description table header. 
/// This structure prefaces most ACPI tables.
///
typedef struct {
  UINT32  Signature;
  UINT32  Length;
  UINT8   Revision;
  UINT8   Checksum;
  UINT8   OemId[6];
  UINT64  OemTableId;
  UINT32  OemRevision;
  UINT32  CreatorId;
  UINT32  CreatorRevision;
} EFI_ACPI_DESCRIPTION_HEADER;

// \MdePkg\Include\IndustryStandard\Acpi50.h
///
/// ACPI 5.0 Generic Address Space definition
///
typedef struct {
  UINT8   AddressSpaceId;
  UINT8   RegisterBitWidth;
  UINT8   RegisterBitOffset;
  UINT8   AccessSize;
  UINT64  Address;
} EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE;

// \MdePkg\Include\Protocol\AcpiSystemDescriptionTable.h  
///
/// Fixed ACPI Description Table Structure (FADT)
///
typedef struct {
  EFI_ACPI_DESCRIPTION_HEADER             Header;
  UINT32                                  FirmwareCtrl;
  UINT32                                  Dsdt;
  UINT8                                   Reserved0;
  UINT8                                   PreferredPmProfile;
  UINT16                                  SciInt;
  UINT32                                  SmiCmd;
  UINT8                                   AcpiEnable;
  UINT8                                   AcpiDisable;
  UINT8                                   S4BiosReq;
  UINT8                                   PstateCnt;
  UINT32                                  Pm1aEvtBlk;
  UINT32                                  Pm1bEvtBlk;
  UINT32                                  Pm1aCntBlk;
  UINT32                                  Pm1bCntBlk;
  UINT32                                  Pm2CntBlk;
  UINT32                                  PmTmrBlk;
  UINT32                                  Gpe0Blk;
  UINT32                                  Gpe1Blk;
  UINT8                                   Pm1EvtLen;
  UINT8                                   Pm1CntLen;
  UINT8                                   Pm2CntLen;
  UINT8                                   PmTmrLen;
  UINT8                                   Gpe0BlkLen;
  UINT8                                   Gpe1BlkLen;
  UINT8                                   Gpe1Base;
  UINT8                                   CstCnt;
  UINT16                                  PLvl2Lat;
  UINT16                                  PLvl3Lat;
  UINT16                                  FlushSize;
  UINT16                                  FlushStride;
  UINT8                                   DutyOffset;
  UINT8                                   DutyWidth;
  UINT8                                   DayAlrm;
  UINT8                                   MonAlrm;
  UINT8                                   Century;
  UINT16                                  IaPcBootArch;
  UINT8                                   Reserved1;
  UINT32                                  Flags;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  ResetReg;
  UINT8                                   ResetValue;
  UINT8                                   Reserved2[3];
  UINT64                                  XFirmwareCtrl;
  UINT64                                  XDsdt;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPm1aEvtBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPm1bEvtBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPm1aCntBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPm1bCntBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPm2CntBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XPmTmrBlk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XGpe0Blk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  XGpe1Blk;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  SleepControlReg;
  EFI_ACPI_5_0_GENERIC_ADDRESS_STRUCTURE  SleepStatusReg;
} EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE;  
#pragma pack()

EFI_STATUS
PrintGuid (
  IN EFI_GUID *Guid
  )
  
/*++

Routine Description:

  This function prints a GUID to STDOUT.

Arguments:

  Guid    Pointer to a GUID to print.

Returns:

  EFI_SUCCESS             The GUID was printed.
  EFI_INVALID_PARAMETER   The input was NULL.

--*/
{
  if (Guid == NULL) {
	Print(L"Parameter error!\n");
    return EFI_INVALID_PARAMETER;
  }

  Print (
    L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x ",
    (unsigned) Guid->Data1,
    Guid->Data2,
    Guid->Data3,
    Guid->Data4[0],
    Guid->Data4[1],
    Guid->Data4[2],
    Guid->Data4[3],
    Guid->Data4[4],
    Guid->Data4[5],
    Guid->Data4[6],
    Guid->Data4[7]
    );
  return EFI_SUCCESS;
}

UINTN
CompareGUID (
  IN EFI_GUID *Guid1,
  IN EFI_GUID *Guid2
  )
  
/*++

Routine Description:

  This function compares 2 GUIDs

Arguments:

  Guid1    Pointer to 1st GUID.
  Guid2    Pointer to 12st GUID.

Returns:

  0             The GUID was same.
  1  		    The input was different.
  2				PARAMETER error.

--*/
{

  if ((Guid1 == NULL)||(Guid2 == NULL)) {
	Print(L"Parameter error!\n");
    return 2;
  }

  if ((Guid1->Data1 != Guid2->Data1) ||
	  (Guid1->Data2 != Guid2->Data2) ||
	  (Guid1->Data3 != Guid2->Data3) ||
      (Guid1->Data4[0] != Guid2->Data4[0]) ||
      (Guid1->Data4[1] != Guid2->Data4[1]) ||
	  (Guid1->Data4[2] != Guid2->Data4[2]) ||
      (Guid1->Data4[3] != Guid2->Data4[3]) ||
      (Guid1->Data4[4] != Guid2->Data4[4]) ||
	  (Guid1->Data4[5] != Guid2->Data4[5]) ||
      (Guid1->Data4[6] != Guid2->Data4[6]) ||
      (Guid1->Data4[7] != Guid2->Data4[7]))	
	{
		return 1;
	}
	return 0;
}

EFI_STATUS
SaveDSDTToFile (
	CHAR8	*P,
	UINTN	length
  )
  
/*++

Routine Description:

  Write a memory to a file

Arguments:

  P          Pointer to the contant
  length	 Sizeof the contant

Returns:

  EFI_SUCCESS             The GUID was printed.
  EFI_INVALID_PARAMETER   The input was NULL.

--*/
{
    EFI_STATUS          			Status;
	EFI_FILE_PROTOCOL 				*Root,*FileHandle=0;
	EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;	
	
	Status = gBS->LocateProtocol(
					&gEfiSimpleFileSystemProtocolGuid, 
					NULL,
					(VOID **)&SimpleFileSystem);
						
	if (EFI_ERROR(Status)) {
			Print(L"Cannot find EFI_SIMPLE_FILE_SYSTEM_PROTOCOL \r\n");
			return Status;	
		}

	Status = SimpleFileSystem->OpenVolume(SimpleFileSystem,&Root);
	if (EFI_ERROR(Status)) {
			Print(L"OpenVolume error \r\n");
			return Status;	
		}
   
	Status = Root -> Open(Root,
				&FileHandle,
				(CHAR16 *) L"dsdt.aml",
				EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE,
				0);
			
	if (EFI_ERROR(Status) || (FileHandle==0)) {
			Print(L"Open error \r\n");
			return Status;	
		}	

	Status = FileHandle -> Write(FileHandle, &length, P);
	
	Status  = FileHandle -> Close (FileHandle);
	
	return EFI_SUCCESS;
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	UINTN 		i,j,EntryCount;
	EFI_STATUS	Status;
	CHAR16		Sign[20];
	UINT64		*EntryPtr;
	EFI_GUID	AcpiTableGuid  = ACPI_TABLE_GUID;
	EFI_GUID	Acpi2TableGuid = EFI_ACPI_TABLE_GUID;
	EFI_CONFIGURATION_TABLE		*C=NULL;	
	EFI_ACPI_DESCRIPTION_HEADER				 		*XSDT,*Entry,*DSDT;
	EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE		*FADT;
	EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER	*Root;

  C=gST->ConfigurationTable;
  
  for (i=0;i<gST->NumberOfTableEntries-1;i++)
	{	  
		//Step1. Find the table for ACPI
		if ((CompareGUID(&C->VendorGuid,&AcpiTableGuid) == 0) ||
			(CompareGUID(&C->VendorGuid,&Acpi2TableGuid) == 0))
			{	
				Print(L"Found table:");
				Status=PrintGuid(&C->VendorGuid); 
				Print(L"@[0x%X]\n",C);
				
				Root=C->VendorTable;
				if (zDebug) {Print(L"ROOT SYSTEM DESCRIPTION @[0x%X]\n",Root);}
				
				if (zDebug) {
						ZeroMem(Sign,sizeof(Sign));
						for (j=0;j<8;j++) { Sign[j]=(Root->Signature >> (j*8) & 0xFF); }
						Print(L"Signature [%s]\n",Sign);
						Print(L"Revision [%d]\n",Root->Revision);
						ZeroMem(Sign,sizeof(Sign));
						for (j=0;j<6;j++) { Sign[j]= (Root->OemId[j] & 0xFF); }
						Print(L"OEMID [%s]\n",Sign);
					}
				
				Print(L"RSDT address= [0x%X], Length=[0x%X]\n",
										Root->RsdtAddress,Root->Length);
				Print(L"XSDT address= [0x%LX]\n",Root->XsdtAddress);
				
				// Step2. Check the Revision, we olny accept Revision >= 2
				if (Root->Revision>=EFI_ACPI_5_0_ROOT_SYSTEM_DESCRIPTION_POINTER_REVISION)
					{
						// Step3. Get XSDT address
						XSDT=(EFI_ACPI_DESCRIPTION_HEADER *)(UINTN) Root->XsdtAddress;
						EntryCount = (XSDT->Length - sizeof(EFI_ACPI_DESCRIPTION_HEADER)) 
											/ sizeof(UINT64);
						
						if (zDebug) {
							ZeroMem(Sign,sizeof(Sign));
							Sign[0]= (XSDT->Signature & 0xFF);
							Sign[1]= (XSDT->Signature >> 8 & 0xFF);
							Sign[2]= (XSDT->Signature >> 16 & 0xFF);
							Sign[3]= (XSDT->Signature >> 24 & 0xFF);
							Print(L"Sign [%s]\n",Sign);						
							Print(L"length [%d]\n",XSDT->Length);						
							Print(L"Counter [%d]\n",EntryCount);	
							}
						
						// Step4. Check the signature of every entry
						EntryPtr=(UINT64 *)(XSDT+1);
						for (j=0;j<EntryCount; j++,EntryPtr++)
						  {
							
							Entry=(EFI_ACPI_DESCRIPTION_HEADER *)((UINTN)(*EntryPtr));
							
							if (zDebug) {
								ZeroMem(Sign,sizeof(Sign));
								Sign[0]= (Entry->Signature & 0xFF);
								Sign[1]= (Entry->Signature >> 8 & 0xFF);
								Sign[2]= (Entry->Signature >> 16 & 0xFF);
								Sign[3]= (Entry->Signature >> 24 & 0xFF);
								Print(L"%d: [%s] @[%X]\n",j,Sign,Entry);
							 }
							
							// Step5. Find the FADT table
							if (Entry->Signature==0x50434146) { //'FACP'
								FADT = (EFI_ACPI_5_0_FIXED_ACPI_DESCRIPTION_TABLE *)(UINTN) Entry;
								
								// Step6. Get DSDT address
								DSDT = (EFI_ACPI_DESCRIPTION_HEADER *) (FADT->Dsdt);
								if (zDebug) {
									Print(L"DSDT table @[%X]\n",DSDT);
								}
								
								// Step7. Save DSDT as a file
								SaveDSDTToFile((CHAR8 *)DSDT,DSDT->Length);
							}
						  }
					}
			}
		C++;
	}
	
  return EFI_SUCCESS;
}

 

运行结果(注意:上述代码是在NT32模拟环境下编译通过的,但是因为模拟坏境中没有对应的 ACPI Table,所以必须在实体机上运行才有结果):

image002

最后得到的 DSDT.AML 我们可以直接使用 ASL 工具反编译。

完整代码下载

GetACPI

本文代码参阅了【参考1】,这里表示感谢。

参考:
1. http://blog.fpmurphy.com/2015/01/list-acpi-tables-from-uefi-shell.html List ACPI Tables From UEFI Shell
2. http://www.cnblogs.com/junzhkevin/archive/2013/02/25/2932801.html ACPI Tables (不确定是否为文章出处。这里有对ACPI Table进行简单的介绍,值得阅读)

Step to UEFI (75) —–取得 ConfigurationTable

EFI_SYSTEM_TABLE 中定义了 EFI_CONFIGURATION_TABLE *ConfigurationTable;

这个结构体定义如下:

typedef struct{
EFI_GUID  VendorGuid;
VOID  *VendorTable;
} EFI_CONFIGURATION_TABLE;

 

整体看起来就是这样:

image001

根据上面的原理可以编写程序枚举系统中的 ConfigurationTable,代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>


extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

EFI_STATUS
PrintGuid (
  IN EFI_GUID *Guid
  )
  
/*++

Routine Description:

  This function prints a GUID to STDOUT.

Arguments:

  Guid    Pointer to a GUID to print.

Returns:

  EFI_SUCCESS             The GUID was printed.
  EFI_INVALID_PARAMETER   The input was NULL.

--*/
{
  if (Guid == NULL) {
	Print(L"Parameter error!\n");
    return EFI_INVALID_PARAMETER;
  }

  Print (
    L"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x\n",
    (unsigned) Guid->Data1,
    Guid->Data2,
    Guid->Data3,
    Guid->Data4[0],
    Guid->Data4[1],
    Guid->Data4[2],
    Guid->Data4[3],
    Guid->Data4[4],
    Guid->Data4[5],
    Guid->Data4[6],
    Guid->Data4[7]
    );
  return EFI_SUCCESS;
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  UINTN 	i;
  EFI_STATUS	Status;
  EFI_CONFIGURATION_TABLE	*C=NULL;
  
  Print(L"[%d] Tables were found!\n",gST->NumberOfTableEntries);
  C=gST->ConfigurationTable;
  
  for (i=0;i<gST->NumberOfTableEntries-1;i++)
	{
		Status=PrintGuid(&C->VendorGuid);
		C++;
	}
  return EFI_SUCCESS;
}

 

然后我们尝试在模拟环境中运行,结果如下:

image002

我们再看看找到的这些GUID是什么意思:

#define TIANO_CUSTOM_DECOMPRESS_GUID  \
  { 0xA31280AD, 0x481E, 0x41B6, { 0x95, 0xE8, 0x12, 0x7F, 0x4C, 0x98, 0x47, 0x79 } }

#define EFI_CRC32_GUIDED_SECTION_EXTRACTION_GUID \
  { 0xFC1BCDB0, 0x7D31, 0x49aa, {0x93, 0x6A, 0xA4, 0x60, 0x0D, 0x9D, 0xD0, 0x83 } }

#define DXE_SERVICES_TABLE_GUID \
  { \
    0x5ad34ba, 0x6f02, 0x4214, {0x95, 0x2e, 0x4d, 0xa0, 0x39, 0x8e, 0x2b, 0xb9 } \
}
#define HOB_LIST_GUID \
  { \
    0x7739f24c, 0x93d7, 0x11d4, {0x9a, 0x3a, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \
  }

#define EFI_MEMORY_TYPE_INFORMATION_GUID \
  { 0x4c19049f,0x4137,0x4dd3, { 0x9c,0x10,0x8b,0x97,0xa8,0x3f,0xfd,0xfa } }

#define EFI_DEBUG_IMAGE_INFO_TABLE_GUID \
  { \
    0x49152e77, 0x1ada, 0x4764, {0xb7, 0xa2, 0x7a, 0xfe, 0xfe, 0xd9, 0x5e, 0x8b } \
  }
#define SMBIOS_TABLE_GUID \
  { \
    0xeb9d2d31, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \
  }

 

能看懂的只有SMBIOS…….我们再用实体机测试,结果如下:

image003

比上面的多了5个GUID,分别是:

#define LZMA_CUSTOM_DECOMPRESS_GUID  \
  { 0xEE4E5898, 0x3914, 0x4259, { 0x9D, 0x6E, 0xDC, 0x7B, 0xD7, 0x94, 0x03, 0xCF } }

#define EFI_TSC_FREQUENCY_GUID \
  { \
    0xdba6a7e3, 0xbb57, 0x4be7, { 0x8a, 0xf8, 0xd5, 0x78, 0xdb, 0x7e, 0x56, 0x87 } \
  }

#define ACPI_TABLE_GUID \
  { \
    0xeb9d2d30, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d } \
  }

#define EFI_ACPI_TABLE_GUID \
  { \
    0x8868e871, 0xe4f1, 0x11d3, {0xbc, 0x22, 0x0, 0x80, 0xc7, 0x3c, 0x88, 0x81 } \
  }

gEfiOfflineCrashDumpTblGuid             = { 0x3804CF02, 0x8538, 0x11E2, { 0x88, 0x47, 0x8D, 0xF1, 0x60, 0x88, 0x70, 0x9B } }

 

本文完整代码下载:

GetATBL

下一次会介绍 Shell 下如何获取ACPI Table。

Step to UEFI (74) —– 通过 OpenVolume访问FSx上的文件

题目有点绕口,简单的说目标就是:我打算用 EFI_SIMPLE_FILE_SYSTEM_PROTOCOL 中的 OpenVolume 打开 FsX: 上面的文件怎么办?
实现的思路是:

1. 查找系统中所有支持 FS Protocol的Device
2. 对于每一个有 FS Protocol 的 Device 用 DevicePathFromHandle 取得 DevicePath
3. 再用 GetFsName 功能取得 FS0 ,FS1 这样的名称,然后判断是否为我们希望的名称
4. 如果是的话,再取得这个设备上的 SimpleFileSystem protocol
5. 最后用 OpenVolue 打开文件。

具体代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

#include <Protocol/SimpleFileSystem.h>
#include <Protocol/BlockIo.h>
#include <Library/DevicePathLib.h>
#include <Library/HandleParsingLib.h>
#include <Library/SortLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/BaseMemoryLib.h>

extern EFI_BOOT_SERVICES         *gBS;

extern EFI_SHELL_ENVIRONMENT2    *mEfiShellEnvironment2;
extern EFI_HANDLE				 gImageHandle;

EFI_STATUS
EFIAPI
PerformSingleMappingDisplay(
  IN CONST EFI_HANDLE Handle
  )
{
	EFI_DEVICE_PATH_PROTOCOL  *DevPath;
	CHAR16                    *CurrentName;
	CHAR16					*FSNAME=L"fsnt1";
	EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SimpleFileSystem;
	EFI_FILE_INFO     		*FileInfo = NULL;
	EFI_STATUS                Status;
	EFI_FILE_PROTOCOL   		*FileProtocol;
	EFI_FILE_HANDLE     		FileHandle;
	UINTN 					FileDataLength;
	CHAR16 					*FileData;

	CurrentName = NULL;
    DevPath = DevicePathFromHandle(Handle);
  
  //4. Covnver DevicePath to FSx (E.x FS2, FSNT1.....)
  mEfiShellEnvironment2->GetFsName(DevPath,FALSE,&CurrentName);

  //5. If the "FSx" string is what we want 
  if (StrCmp(CurrentName,FSNAME)==0) {
		Print (L"%s \r\n", CurrentName);  		
		
		//6. Open the SimpleFileSystem Protocol on it
		Status = gBS->OpenProtocol(
								Handle,
                                &gEfiSimpleFileSystemProtocolGuid,
                                (VOID**)&SimpleFileSystem,
								gImageHandle,
								NULL,
								EFI_OPEN_PROTOCOL_GET_PROTOCOL
                                );
		if (EFI_ERROR(Status)) {
			Print (L"LocateProtocol SimpleFileSystem Error \r\n");  	
			return (EFI_NOT_FOUND);
		}
	   
	    //7. Use OpenVolue to get FileProtocol
		Status = SimpleFileSystem->OpenVolume(SimpleFileSystem, &FileProtocol);
		if (EFI_ERROR(Status)) {
			Print (L"SimpleFileSystem OpenVolume Error \r\n");
			return Status;
		}
    
		//8. At last we can operate file by FileProtocol
		Status = FileProtocol->Open(FileProtocol, 
                              &FileHandle, 
                              L"Hello.txt",
                              EFI_FILE_MODE_READ,
                              0);
		if (EFI_ERROR(Status)) {
			Print (L"FileProtocol Open Error [%r]\r\n",Status);
			return Status;
		}
    
		FileInfo = ShellGetFileInfo( (SHELL_FILE_HANDLE)FileHandle);	
		Print(L"Filesize [%ld] bytes\n",FileInfo-> FileSize);
		FileDataLength=(UINTN) FileInfo->FileSize;
	
		FileData  = AllocatePool((UINTN) FileInfo->FileSize);
		Status = FileHandle->Read(FileHandle, &FileDataLength, FileData );
		if (EFI_ERROR(Status)) {
			Print(L"Loading file error! \n");
		}
		
		FileData[FileDataLength/2 -1]=0x0;
		Print(L"File contants: [%s]",FileData);
		
		FreePool(FileData);
		FileProtocol->Close(FileHandle);
  }
	
  if ((CurrentName) != NULL) { FreePool((CurrentName)); CurrentName = NULL; }

  return EFI_SUCCESS;
}

int
EFIAPI
main (                                         
  IN int Argc,
  IN char **Argv
  )
{
  EFI_STATUS                Status;
  EFI_HANDLE                *HandleBuffer=NULL;
  UINTN                     BufferSize=0;
  UINTN                     LoopVar;
  BOOLEAN                   Found;

  //1. We have to use some function in SE2 
  //
  // UEFI 2.0 shell interfaces (used preferentially)
  //
  Status = gBS->OpenProtocol(
    gImageHandle,
    &gEfiShellProtocolGuid,
    (VOID **)&gEfiShellProtocol,
    gImageHandle,
    NULL,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL
   );
   
  if (EFI_ERROR(Status)) {
    //
    // Search for the shell protocol
    //
    Status = gBS->LocateProtocol(
      &gEfiShellProtocolGuid,
      NULL,
      (VOID **)&gEfiShellProtocol
     );
    if (EFI_ERROR(Status)) {
      gEfiShellProtocol = NULL;
     }
  }

  
  //
  //2. Look up all SimpleFileSystems in the platform
  //
  Status = gBS->LocateHandle(
    ByProtocol,
    &gEfiSimpleFileSystemProtocolGuid,
    NULL,
    &BufferSize,
    HandleBuffer);
	
  if (Status == EFI_BUFFER_TOO_SMALL) {
		HandleBuffer = AllocateZeroPool(BufferSize);
		if (HandleBuffer == NULL) {
			return (SHELL_OUT_OF_RESOURCES);
		}
		Status = gBS->LocateHandle(
			ByProtocol,
			&gEfiSimpleFileSystemProtocolGuid,
			NULL,
			&BufferSize,
			HandleBuffer);
   }

  //
  // Get the map name(s) for each one.
  //
  for ( LoopVar = 0, Found = FALSE
      ; LoopVar < (BufferSize / sizeof(EFI_HANDLE)) && HandleBuffer != NULL
      ; LoopVar ++
     ) {
	//3.Emulate every Handle which has SimpileFileSystem 
    Status = PerformSingleMappingDisplay(HandleBuffer[LoopVar]);
    if (!EFI_ERROR(Status)) {
      Found = TRUE;
    }
  }
  
  FreePool(HandleBuffer);
	
  return EFI_SUCCESS;
}

 

我是在虚拟机下实验的,运行之后打开并且读取 fsnt2:\hello.txt 的内容(内容是 www.lab-z.com123)。

openfs

特别注意的是:直接读取之后按照 CHAR16 的字符串来处理,但是读取内容没有 0x00 0x00的结尾。直接用Print 输出的时候字符串后面会有意料之外的字符。所以用下面这个语句 FileData[FileDataLength/2 -1]=0x0; 直接添加一个结尾。这也是为什么字符3被截掉的原因。

完整代码下载
OpenFSX

参考:

1. http://www.lab-z.com/esptest/ Step to UEFI (54) —– EFI_SIMPLE_FILE_SYSTEM_PROTOCOL 写文件
2. http://www.lab-z.com/shellfsx/ Step to UEFI (36) —– 枚举Shell下的全部盘符
3. http://www.lab-z.com/stu63/ Step to UEFI (63) —– 常用的字符串函数(下)
4. http://www.lab-z.com/nstring/ Step to UEFI (62) —– 常用的字符串函数(上)

Step to UEFI —– TIPs

每次编译 Application 之后生成的 efi 文件都在

\Build\AppPkg\DEBUG_MYTOOLS\IA32\AppPkg\Applications\APPNAME\APPNAME 这样的目录中,每次需要手工 copy 到虚拟出来的目录下,这样比较麻烦。

经过研究发现可以在 \AppPkg\AppPkg.dsc 中做如下修改

#OUTPUT_DIRECTORY = Build/AppPkg
OUTPUT_DIRECTORY = Build/NT32IA32

这样,每次最后生成 efi 文件之后就会有一个自动 copy 的动作。

tips

Step to UEFI (72) —– MP_Service_Protocol 获得CPU信息

UEFI本身不支持多线程,据说主要原因是:UEFI设计本身是为了启动硬件,做一些比较简单的事情,如果支持多线程会使得设计复杂度直线上升,出现问题也不容易调试。

不过UEFI本身是有对应的Protocol 的,称作 EFI_MP_SERVICES_PROTOCOL 。 具体的这个 Protocol 可以在 PI spec 中找到(UEFI Spec中没有)。下面来自【参考1】

image001

同样可以作为参考的还有EFI_Toolkit_2.0.0.1 中的process.c 程序。

程序很简单,首先 LocateProtocol 到EFI_MP_SERVICES_PROTOCOL。然后,用 Protocol 提供的EFI_MP_SERVICES_GET_NUMBER_OF_PROCESSORS 和EFI_MP_SERVICES_GET_PROCESSOR_INFO 来获得关于处理器的一些信息。需要注意的是EFI_MP_SERVICES_GET_PROCESSOR_INFO,输入的参数ProcessorNumber 意思是:当前要获得的 Processor 号,输入0 表示你要去的编号为 0 的Processor 的信息,输入3 表示你要去的编号为 3 的Processor 的信息,取值范围从0到NumberOfProcessors -1。

image002

最终的代码:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>
#include  <Pi/PiDxeCis.h>
#include  <Protocol/MpService.h>

EFI_GUID  gEfiMpServiceProtocolGuid = { 0x3fdda605, 0xa76e, 0x4f46, 
						{ 0xad, 0x29, 0x12, 0xf4, 0x53, 0x1b, 0x3d, 0x08 }};
  
extern EFI_BOOT_SERVICES         *gBS;

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	EFI_STATUS	Status;
	EFI_MP_SERVICES_PROTOCOL	*MP=NULL;
	UINTN   i;	
	UINTN	NumProcessors;
	UINTN	NumberOfEnabledProcessors;

	EFI_PROCESSOR_INFORMATION	ProcessorInfo;
	
	//Get MP_Service Protocol
	Status = gBS->LocateProtocol (&gEfiMpServiceProtocolGuid, NULL, (VOID**)&MP);
	if (EFI_ERROR (Status)) {
		Print(L"Unable to initialize MP protocol interface!");
		return EFI_UNSUPPORTED;
	}
	
	// Determine number of processors
	Status = MP->GetNumberOfProcessors( MP, &NumProcessors , &NumberOfEnabledProcessors );
	
	if (EFI_ERROR (Status))
	{
		Print( L"MP->GetNumEnabledProcessors:Unable to determine number of processors\n") ;
		return EFI_UNSUPPORTED;
	}	
	Print(L"Number of Processors %d\n",NumProcessors);
	Print(L"Number of Enabled Processors %d\n",NumberOfEnabledProcessors);
	
	//Get more information by GetProcessorInfo
	for (i=0;i<NumProcessors;i++)
	{
		Status = MP->GetProcessorInfo(MP, i , &ProcessorInfo);
		
		Print( L"Prcoessor #%d ACPI Processor ID = %lX, Flags = %x, Package = %x, Core = %x, Thread = %x \n", 
					i,
					ProcessorInfo.ProcessorId, 
					ProcessorInfo.StatusFlag,
					ProcessorInfo.Location.Package,
					ProcessorInfo.Location.Core,
					ProcessorInfo.Location.Thread);
	}
	
	return EFI_SUCCESS;
}

运行结果:

image003

完整的代码下载:

MPTest1

关于这部分程序,在《UEFI 原理与编程》第13章 深入了解多任务 有更详细的论述,如果你有这方面的需求,推荐直接阅读。

另外,如果你想获得更多CPU方面的信息,比如 Cache大小, 还可以通过 CPUID,但是非常不建议通过 SMBIOS 来获得,因为他并不可靠……

最后特别提醒:

开始编写程序时,我遇到了下面的错误,错误代码对于解决问题毫无帮助,最后经过分析发现是因为没有添加PiDxe.h头文件(感谢Marco指出)导致的。

c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2146: syntax error : missing ‘)’ before identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2081: ‘EFI_AP_PROCEDURE’ : name in formal parameter list illegal
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2061: syntax error : identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2059: syntax error : ‘;’
c:\edk\MdePkg\Include\Protocol/MpService.h(361) : error C2059: syntax error : ‘,’
c:\edk\MdePkg\Include\Protocol/MpService.h(367) : error C2059: syntax error : ‘)’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2146: syntax error : missing ‘)’ before identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2081: ‘EFI_AP_PROCEDURE’ : name in formal parameter list illegal
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2061: syntax error : identifier ‘Procedure’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2059: syntax error : ‘;’
c:\edk\MdePkg\Include\Protocol/MpService.h(459) : error C2059: syntax error : ‘,’
c:\edk\MdePkg\Include\Protocol/MpService.h(465) : error C2059: syntax error : ‘)’
c:\edk\MdePkg\Include\Protocol/MpService.h(623) : error C2061: syntax error : identifier ‘EFI_MP_SERVICES_STARTUP_ALL_APS’
c:\edk\MdePkg\Include\Protocol/MpService.h(624) : error C2061: syntax error : identifier ‘StartupThisAP’
c:\edk\MdePkg\Include\Protocol/MpService.h(624) : error C2059: syntax error : ‘;’
c:\edk\MdePkg\Include\Protocol/MpService.h(628) : error C2059: syntax error : ‘}’
NMAKE : fatal error U1077: ‘”C:\Program Files\Microsoft Visual Studio 9.0\Vc\bin\cl.exe”‘ : return code ‘0x2’
Stop.

参考:

1. Vol2_DXE CIS_1_3.pdf P174

Step to UEFI (71) —– 获得 USB 设备的 PID 和 VID

一个问题:如何获得 Shell 下面所有 USB 设备的 PID 和 VID ?

首先在网上搜索一下,找到【参考1】,上面使用了 USBIO Protocol。

typedef struct _EFI_USB_IO_PROTOCOL {
  EFI_USB_IO_CONTROL_TRANSFER            UsbControlTransfer;
  EFI_USB_IO_BULK_TRANSFER               UsbBulkTransfer;
  EFI_USB_IO_ASYNC_INTERRUPT_TRANSFER    UsbAsyncInterruptTransfer;
  EFI_USB_IO_SYNC_INTERRPUT_TRANSFER     UsbSyncInterruptTransfer;
  EFI_USB_IO_ISOCHRONOUS_TRANSFER        UsbIsochronousTransfer;
  EFI_USB_IO_ASYNC_ISOCHRONOUS_TRANSFER  UsbAsyncIsochronousTransfer;
  EFI_USB_IO_GET_DEVICE_DESCRIPTOR       UsbGetDeviceDescriptor;
  EFI_USB_IO_GET_CONFIG_DESCRIPTOR       UsbGetConfigDescriptor;
  EFI_USB_IO_GET_INTERFACE_DESCRIPTOR    UsbGetInterfaceDescriptor;
  EFI_USB_IO_GET_ENDPOINT_DESCRIPTOR     UsbGetEndpointDescriptor;
  EFI_USB_IO_GET_STRING_DESCRIPTOR       UsbGetStringDescriptor;
  EFI_USB_IO_GET_SUPPORTED_LANGUAGES     UsbGetSupportedLanguages;
  EFI_USB_IO_PORT_RESET                  UsbPortReset;
} EFI_USB_IO_PROTOCOL;

来自【参考2】。

我们在实体机上用 dh –p USBIO 命令看一下(注意:EDK自带的模拟环境没有设备挂USBIO 这个 Protocol,所以只能用实体机查看)。可以看到 USB设备上都有这个 Protocol,于是,一切都简单了。

image001

我们再把实体机启动到 Windows中,也是同样的顺序,分别是 USB Hub (这是我外接的一个 USB HUB),USB鼠标,USB键盘(这个键盘是一个复合设备,所以会显示为2个),最后是我的 U盘。多说两句对照上面的截图,我们可以看到USB鼠标上附加了一个 SimplePointer Protocol,键盘上有 TxtinEx/Txtin/ConIn Protocol ,U盘上附加了 DiskIo/BlkIo Protocol,后面我们会分别研究一下这些 Protocol 的使用。

image002

每一个USB设备上都有这个 Protocol 所以我们要用 LocateHandleBuffer 和HandleProtocol 这样的组合把所有有这个 Protocol 的设备都取下来,然后调用 UsbIO 中的UsbGetDeviceDescriptor 即可。
弄清楚原理整个程序还是比较简单的,如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <Protocol/UsbIo.h>

extern EFI_BOOT_SERVICES         *gBS;

EFI_GUID  gEfiUsbIoProtocolGuid   = 
	{ 0x2B2F68D6, 0x0CD2, 0x44CF, 
		{ 0x8E, 0x8B, 0xBB, 0xA2, 0x0B, 0x1B, 0x5B, 0x75 }};

UINTN GetUSB( )
{
  EFI_STATUS  Status;
  UINTN       HandleIndex, HandleCount;
  EFI_HANDLE  *DevicePathHandleBuffer = NULL;
  EFI_USB_IO_PROTOCOL 			*USBIO;
  EFI_USB_DEVICE_DESCRIPTOR     DeviceDescriptor;

  Status = gBS->LocateHandleBuffer(
                  ByProtocol,
                  &gEfiUsbIoProtocolGuid,
                  NULL,
                  &HandleCount,
                  &DevicePathHandleBuffer);

  if (EFI_ERROR(Status)) 
  {
    Print(L"ERROR : Get USBIO count fail.\n");
    return 0;
  }   

  for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++) 
  { 
     Status = gBS->HandleProtocol(
                      DevicePathHandleBuffer[HandleIndex],
                      &gEfiUsbIoProtocolGuid,
                      (VOID**)&USBIO);

      if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Open USBIO fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }

      Status = USBIO->UsbGetDeviceDescriptor(USBIO, &DeviceDescriptor);     
      if (EFI_ERROR(Status))
      {
        Print(L"ERROR : Usb Get Device Descriptor fail.\n");
        gBS->FreePool(DevicePathHandleBuffer);  
        return 0;
      }

      Print(L"VendorID = %04X, ProductID = %04X\n", 
                              DeviceDescriptor.IdVendor, 
                              DeviceDescriptor.IdProduct);      

  }
  gBS->FreePool(DevicePathHandleBuffer);       
  return HandleCount;
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
  GetUSB( );
  return EFI_SUCCESS;
}

最终运行结果如下:

image003

完整的代码和程序下载:
GetPIDVID

参考:
1. http://biosren.com/viewthread.php?tid=5925&highlight=usbio 請問如何透過DevicePath獲取對應的device全名?
2. http://wiki.phoenix.com/wiki/index.php/EFI_USB_IO_PROTOCOL EFI USB IO PROTOCOL 同样的,在 UEFI spec中也可以找到上面的定义

Step to UEFI (70) —– 获取 EDID 信息

之前介绍过使用 Arduino 用硬件的方法直接读取 EDID 【参考1】,这里介绍一下如何在 Shell下读取 EDID 信息。

我们使用 EFI_EDID_DISCOVERED_PROTOCOL 这个 Protocol 来获得信息。当然,类似的还可以使用 EFI_EDID_ACTIVE_PROTOCOL,都是没有问题的。

在要获取 EDID 信息之前,最好先使用 “DH -p EdidDiscovered” 命令来确定你当前的系统中存在 Edid 信息。例如:

edidpre

实现这个功能的代码如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <Protocol/EdidDiscovered.h>


extern EFI_BOOT_SERVICES         *gBS;
extern EFI_SYSTEM_TABLE			 *gST;
extern EFI_RUNTIME_SERVICES 	 *gRT;

extern EFI_HANDLE				 gImageHandle;

EFI_GUID gEfiEdidDiscoveredProtocolGuid = 

{ 0x1C0C34F6, 0xD380, 0x41FA, { 0xA0, 0x49, 0x8A, 0xD0, 0x6C, 0x1A, 0x66, 0xAA }};

EFI_STATUS GetEDIDInfo()
{
    EFI_STATUS  Status;
    EFI_HANDLE *EDIDHandleBuffer;
    UINTN EDIDHandleCount, index, i;
    EFI_EDID_DISCOVERED_PROTOCOL * EDID;
    
    Status = gBS->LocateHandleBuffer (ByProtocol, 
                                      &gEfiEdidDiscoveredProtocolGuid, 
                                      NULL, 
                                      &EDIDHandleCount, 
                                      &EDIDHandleBuffer); 
    if (EFI_ERROR (Status))
    {
      Print(L"ERROR : Can't get EdidDiscoveredProtocol.\n");   
      return FALSE;
    }
    
    Print(L"EDID count = %d\n", EDIDHandleCount);
    for(index = 0 ; index < EDIDHandleCount ; index++)
    {
      Status = gBS->OpenProtocol( EDIDHandleBuffer[index],
                                  &gEfiEdidDiscoveredProtocolGuid,
                                  (VOID**)&EDID,
                                  gImageHandle,
                                  NULL,
                                  EFI_OPEN_PROTOCOL_GET_PROTOCOL
                                  );
      if (EFI_ERROR (Status))
      {
        Print(L"ERROR : Open EDID Protocol failed.\n");   
        return FALSE;
      }          
      
      Print(L"%d\n", EDID->SizeOfEdid);
      for(i = 0 ; i < 128 ; i++)
      {
        Print(L"%02X ", EDID->Edid[i]);
		
		if ((i+1) % 16 ==0) { 
				Print(L"\n"); }
		else	
			    if ((i+1) % 8 ==0) { Print(L"- ");}
      }
      Print(L"\n");
                
      Status = gBS->CloseProtocol(EDIDHandleBuffer[index],
			&gEfiEdidDiscoveredProtocolGuid, gImageHandle, NULL); 
      
      if (EFI_ERROR (Status))
      {
        Print(L"ERROR : Close EDID device handle failed.\n");   
        return FALSE;
      }
    }    
  return EFI_SUCCESS;    
}

int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
 GetEDIDInfo();
 return EFI_SUCCESS;
}

 

上述程序运行结果

edidr

完整代码下载

GetEDID

比对之前获得的结果(我用的是同一个显示器,可以在【参考1】中看到),可以发现结果是相同的。

bq1

bq2

如果想程序解析每个值的具体含义,还按照【参考3】给出的例子。

参考:
1.http://www.lab-z.com/arduinoedid/ Arduino 读取显示器 EDID
2.http://biosren.com/thread-5921-1-1.html 求救如何取得EDID?
3.http://blog.fpmurphy.com/2012/09/accessing-edid-information-from-uefi-shell.html Accessing EDID Information From UEFI Shell

Step to UEFI (69) —– 动态加载修改Application

标题看起来非常拗口,具体来说描述起来就是下面的问题:

“我想写一个简单的程序,先把某个app的Load进内存,然后在内存里爆搜一个特征字串,搜到之后将该内存第一个字节替换。以下为代码片段,碰到一个问题就是,我搜到特征字串之后,修改其内存的内容一直改不了,请问各位大大,是不是UEFI有相应的保护策略,不能修改LoadImage的内存?我个人觉得是不应该,因为我是LoadImage的宿主,我Load的内存应该是可以被我修改的。请大牛们指教啊!!!

Status=gBS->LoadImage(TRUE, ImageHandle, DstDevicePath, NULL, 0, &DstImageHandle); //LoadImage
if (!EFI_ERROR(Status))
{
Print(L”Load Image success\n”);
}
Status=gBS->HandleProtocol(DstImageHandle, &gEfiLoadedImageProtocolGuid,(void **) &LoadedImage);
if (EFI_ERROR(Status)) {
Print(L”Can not retrieve a LoadedImageProtocol handle for ImageHandle\n”);
gBS->Exit(ImageHandle,EFI_SUCCESS,0,NULL);
}
//Get the loaded image base address
imageBase=LoadedImage->ImageBase;
size=LoadedImage->ImageSize;

temp=(char *)imageBase;

//Search the sig;, replace the first byte
for (; temp<(char *)imageBase+size; temp++) { if (*temp==0x55 && *(temp+1)==0x00 && *(temp+2)==0x45 && *(temp+3)==0x00) { Print(L"Find sig\n"); *temp=0x45; Print(L"addr %x\n",temp); break; } } 上述问题来自【参考1】 这个一个有趣的问题,也不知道那个朋友最后是否成功。根据上面的问题,我做一下实验。 首先,准备一个被修改的 App。当然,根据之前的知识,这个 App 不能使用 CLIB 库,目前为止我还是不知道为什么无法加载调用这个库的 Application.

#include <Uefi.h>
#include <Library/PcdLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiApplicationEntryPoint.h>


/**
  The user Entry Point for Application. The user code starts with this function
  as the real entry point for the application.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.  
  @param[in] SystemTable    A pointer to the EFI System Table.
  
  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
UefiMain (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
                                       
  CHAR8  *s1= "This code comes from www.lab-z.com";
  CHAR16 *s2=L"                                  ";
  CHAR16 *Result;
  
  Result=AsciiStrToUnicodeStr(s1,s2);
  Print(L"%s\n",s2);

  return EFI_SUCCESS;
}

 

代码非常简单,将一个 ASCII 字符串转化为 Unicode 的,然后显示出来。使用 ASCII 的原因是为了便于查找。

上面的程序编译之后,使用十六进制工具打开可以直接查看到 ASCII 字符。

exec3

然后,继续编写加载和修改的程序如下:

#include  <Uefi.h>
#include  <Library/UefiLib.h>
#include  <Library/ShellCEntryLib.h>

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>

#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

extern EFI_BOOT_SERVICES           	 *gBS;
extern EFI_SYSTEM_TABLE				 *gST;
extern EFI_RUNTIME_SERVICES 		 *gRT;

extern EFI_SHELL_PROTOCOL            *gEfiShellProtocol;
extern EFI_SHELL_ENVIRONMENT2 		 *mEfiShellEnvironment2;

extern EFI_HANDLE					 gImageHandle;
/**
  GET  DEVICEPATH
**/
EFI_DEVICE_PATH_PROTOCOL *
EFIAPI
ShellGetDevicePath (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  )
{
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    return (gEfiShellProtocol->GetDevicePathFromFilePath(DeviceName));
  }

  //
  // Check for EFI shell
  //
  if (mEfiShellEnvironment2 != NULL) {
    return (mEfiShellEnvironment2->NameToPath(DeviceName));
  }

  return (NULL);
}

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_DEVICE_PATH_PROTOCOL 	*DevicePath;
  EFI_HANDLE				NewHandle;
  EFI_STATUS				Status;
  UINTN			ExitDataSizePtr;  
  CHAR16 					*R=L"HelloWorld.efi";
  EFI_LOADED_IMAGE_PROTOCOL	*ImageInfo = NULL;
  CHAR8						*temp;
  
  Print(L"File [%s]\n",R);

  DevicePath=ShellGetDevicePath(R);

  //
  // Load the image with:
  // FALSE - not from boot manager and NULL, 0 being not already in memory
  //
  Status = gBS->LoadImage(
    FALSE,
    gImageHandle,
    DevicePath,
    NULL,
    0,
    &NewHandle);  

  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during LoadImage [%X]\n",Status);
    return (Status);
  }

  Status = gBS -> HandleProtocol (
						NewHandle,
						&gEfiLoadedImageProtocolGuid,
						&ImageInfo
						);
  Print(L"ImageBase [%lX]\n",ImageInfo->ImageBase);
  Print(L"ImageSize [%lX]\n",ImageInfo->ImageSize);

  temp=(char *)ImageInfo->ImageBase;
  //Search the sig;, replace the first byte
  for (; temp<(char *)ImageInfo->ImageBase+ImageInfo->ImageSize; temp++)
        {
				//"lab" 6C 61 62
                if (*temp==0x6C && *(temp+1)==0x61 && *(temp+2)==0x62)
                {
                        Print(L"Find sig\n");
                        Print(L"addr %x\n",temp);
						*(temp  )=0x2D;
						*(temp+1)=0x2D;
						*(temp+2)=0x2D;
                        break;
                } 
        }  
  
  
  //
  // now start the image, passing up exit data if the caller requested it
  //
  Status = gBS->StartImage(
                     NewHandle,
                     &ExitDataSizePtr,
                     NULL
              );
  if (EFI_ERROR(Status)) {
    if (NewHandle != NULL) {
      gBS->UnloadImage(NewHandle);
    }
	Print(L"Error during StartImage [%X]\n",Status);
    return (Status);
  }
  
  gBS->UnloadImage (NewHandle);  
  return EFI_SUCCESS;
}

 

加载部分的代码使用的是 【参考2】的框架,搜索部分的代码用的是前面问题中给出来的示例。我们在加载后的 Application 的空间中搜索 “lab” 字符串并且替换为 “—”。

运行结果如下,我们先执行了一次 HelloWorld.efi,可以看到他能正常打印字符串,之后再用我们的程序加载一次,可以看到字符串被修改掉了。

exec3r

看起来并没有什么保护之类的,轻而易举的改掉了 Application 的内容。猜测之前提出问题的朋友有可能是被加载的代码用到了 CLIB, 或者是代码中的字符串是按照 UNICODE 给出来的,所以无法找到。

这样的动态加载可以用在一些特殊的地方,比如,我见过一款 DOS 下的测试软件,有一个主程序 EXE 和 n多个独立的 EXE 构成。主程序可以调用其他的 EXE 进行测试,但是单独的 EXE 无法执行,这样的好处是开发时可以独立开发单独模块,分发之后有主程序进行控制只在需要的环境中运行。

本文提到的代码下载

exec3

HelloWorld

参考:

1. http://biosren.com/thread-4564-1-31.html
2. http://www.lab-z.com/efiloadedimageprotocol/ Step to UEFI (46) —– EFILOADEDIMAGEPROTOCOL的使用

Step to UEFI (67) —– zLib (上)

zlib 是一款开源的压缩解压库,在《UEFI原理与编程》第8章提到了他。我去书上提到的网站下载到了修改后的 zlib.inf 文件,然后尝试在AppPkg中重新编译之。

首先根据 zlib.inf 中[Sources]节给出的文件名提取出来需要用的文件。

[Sources]
#uefientry.c
adler32.c
crc32.c
deflate.c
infback.c
inffast.c
inflate.c
inftrees.c
trees.c
zutil.c
compress.c
uncompr.c
gzclose.c
gzlib.c
gzread.c
gzwrite.c

然后,把 zlib.inf 加入到 AppPkg.dsc 中。之后用 Build -a IA32 -p AppPkg\AppPkg.dsc 编译。编译过程中会出现很多错误,经过研究发现出现的都是一些 warning 而已,可以通过在文件头上加入编译开关来忽略掉。

#pragma warning(disable:4131)
#pragma warning(disable:4142)
#pragma warning(disable:4244)

 

* 很多 Warning 是因为老的语法格式导致的,所以不会对代码产生任何影响,比如下面这种,没有在函数名称中定义参数类型:

/* Open a gzip file either by name or file descriptor. */
local gzFile gz_open(path, fd, mode)
    const void *path;
    int fd;
    const char *mode;

 

此外,在 gzguts.h 中,需要删掉这一行。

#if defined(__TURBOC__) || defined(_MSC_VER) || defined(_WIN32)
  include <io.h>
#endif

 

完成上面的设置后,重新运行命令,可以正常编译:

zlibc

在 \Build\AppPkg\DEBUG_MYTOOLS\IA32\AppPkg\Applications\zsource\zlib\OUTPUT 下面看到 zlib.lib 文件

zlib

后续我们就可以在自己的程序中调用这个压缩库了。

本文修改后的 zlib zsource

修改之前的 zlib,版本是 1.2.8.0

zlib128