窗花

前几天看《小说月报》,一篇讲述的是研究生毕业的漂亮女硕士,为了完成女导师的愿望,毅然决然选择从政的故事。只不过这是主旋律小说,必须有光明的尾巴:一直期望她能成为自己“李万姬”的老领导患了癌症,向革命老前辈们报到前说出了所有的事情,于是全部矛盾迎刃而解。最后女主角镀金完成,换了岗位,抱得男朋友满载而归。比较有意思的是故事中有一个情节,讲的是他们那里支柱企业是无纺布生产厂,受到经济危机影响,贷款出了问题,然后女主角想办法筹集资金,拖欠了一下当地干部和教师的工资,又下达集资的命令。看到资金的来源,我觉得这篇小说还真是写实。确实,对于教师这个职业,没有好处的很多时候不会想起来你是公务员,但是要是有需要体现精神的时候又会被推到前面。

又想起来很久之前的一个科幻小说,讲述人类发送太阳能电池到太空作为新能源,但是太空中有尘埃,每隔一段就需要清理。发送博士之类风险太大薪水又不能太低,最后作为特产,农民工就被送到太空,从事这份高工资的工作,也算是人上人了。小说还真是传承社会精神面貌的镜子。回头我找本书好好研究下宋朝的生活,顺便批判一下封建社会的腐朽的生活方式。

我这里说的窗花并非那种传统手工图样,而是凝结在窗户上的水汽形成的美丽图样。

0f53c8d303a1add716cbc1cbef375ee8

我的一位同学,是小学老师,前一段她每天早晨都会在朋友圈中晒出办公室窗花的照片。听起来很浪漫,现实中意味着寒冷—-办公室没有暖气和空调。听起来难以置信,不过据她所说就是这样。此外,因为种种安全上的考量严禁各种非生物能源转换,意思是除了蹦跳多穿衣服多吃东西之外严禁使用电器。于是她便只能望着美丽的窗花偶发感慨聊以自慰了。

以前我在的公司也有过这样的事情:大冬天为了省电不开空调,HW工程师冻的手都没有办法握稳电烙铁。具体是这样的,冬季是我国江南地区传统的用电高峰,企业在用电上会有一些限制,于是老板就在空调上打起来主意。最大的问题是老板的房间是在整栋楼的中间,好比上下各有一层脂肪,大约是感受不到多少寒冷,而我们研发非常不幸的处在顶楼,不开空调很冷啊。我们找了一楼的总务很多次,总务总是指着墙上的一直温度计辩解还没有降低到18度,由拒绝开,我甚至一直在怀疑,他那只温度计是特殊定制的,显示值会比正常的温度高那么两三度。再后来终于开放中央空调,但是还想了一个非常奇特的规则:整点的时候供电10分钟。意思是,比如九点到九点十分,中央空调是有电的,但是需要手工用遥控器打开之。于是,每逢快到整点,研发人员就像神经病一样相互提醒马上准备开空调,能暖和一段。偶然忘记了大家更是垂足顿胸。

时间一长,大家都受不鸟了。就开始研究解决方法了。做HW的工程师夏天的时候可以做几只风扇并联起来,但是冬天的时候没有太合适的工具取暖,热风枪吹起来只有外焦里生的效果。电烙铁的加热区域也太小,没办法满足四肢的需要…….通常 HW 搞不定的就要靠SW了。于是,我旁边的妹子提出:太TMD冷了,自己买个暖风机,然后我也附议了一下,一起买吧。忽然想起来那次似乎是我第一次在京东上购物。根据自己的体积,我挑选了一个2KW的暖风机,旁边的妹子挑选了一个2.5KW的。头天下午的订单,第二天就送货上门签收交易完成,那也是我第一次被京东的配送速度震撼到。用起来感觉不错,是风暖型的,中午熄灯之后吹上去暖洋洋的,我恨不得搬着一张床钻到桌子下面去。等我们用了一段,看看没啥动静,办公室的人也都一窝蜂的去买,到了最后几乎是人手一只。连研发的老大也悄悄买了一个低调的塞在座位下面:高调的理念通常只适用在别人身上,真正到了自己这边舒服一些比什么都强。大约也是到了这个岁数所谓天降大人于私人也不那么重要。没有大志终究比大痔或者冻疮强。

再后来,总务想管来着,羞羞答答的提出了除了费电之外的全部理由,比如:不安全,不环保等等。只是研发没人鸟。想必总务也是有所顾忌的,一方面大冬天不给开空调本来就不占理;另一方面也许是实在讲不过研发的人吧。再后来也就不了了之,我们吹着暖风迎来了春天………..

事后,我曾经认真的总结,这个事情之所以能成,很大程度上是总务对我们有所顾忌,也就睁一只眼闭一只眼了;更高一点层次来说,研发人员算是稀缺,上面也不愿意太过分。再后面发生太过分的事情,研发3个月走了一半的。

从另外一个角度来说,我同学那样的小学老师,生活是很稳定的,一辈子不用担心你会被开除或者因为某些变故失去工作,而这种稳定付出的代价就是自由。

Step to UEFI (33) —– FindFile 枚举目录下所有文件

研究一下如何枚举目录下的全部文件,查到了2个函数 ShellFindFirstFile 和 ShellFindNextFile。下面就研究如何使用这两个函数。

这两个函数在 \ShellPkg\Include\Library\ShellLib.h 中有定义

/** Retrieve first entry from a directory.

  This function takes an open directory handle and gets information from the
  first entry in the directory.  A buffer is allocated to contain
  the information and a pointer to the buffer is returned in *Buffer.  The
  caller can use ShellFindNextFile() to get subsequent directory entries.

  The buffer will be freed by ShellFindNextFile() when the last directory
  entry is read.  Otherwise, the caller must free the buffer, using FreePool,
  when finished with it.

  @param[in]  DirHandle         The file handle of the directory to search.
  @param[out] Buffer            The pointer to the buffer for the file's information.

  @retval EFI_SUCCESS           Found the first file.
  @retval EFI_NOT_FOUND         Cannot find the directory.
  @retval EFI_NO_MEDIA          The device has no media.
  @retval EFI_DEVICE_ERROR      The device reported an error.
  @retval EFI_VOLUME_CORRUPTED  The file system structures are corrupted.
  @return Others                Status of ShellGetFileInfo, ShellSetFilePosition,
                                or ShellReadFile.

  @sa ShellReadFile
**/
EFI_STATUS
EFIAPI
ShellFindFirstFile (
  IN      SHELL_FILE_HANDLE       DirHandle,
     OUT  EFI_FILE_INFO         **Buffer
  );


/** Retrieve next entries from a directory.

  To use this function, the caller must first call the ShellFindFirstFile()
  function to get the first directory entry.  Subsequent directory entries are
  retrieved by using the ShellFindNextFile() function.  This function can
  be called several times to get each entry from the directory.  If the call of
  ShellFindNextFile() retrieved the last directory entry, the next call of
  this function will set *NoFile to TRUE and free the buffer.

  @param[in]  DirHandle         The file handle of the directory.
  @param[out] Buffer            The pointer to buffer for file's information.
  @param[out] NoFile            The pointer to boolean when last file is found.

  @retval EFI_SUCCESS           Found the next file, or reached last file
  @retval EFI_NO_MEDIA          The device has no media.
  @retval EFI_DEVICE_ERROR      The device reported an error.
  @retval EFI_VOLUME_CORRUPTED  The file system structures are corrupted.
**/
EFI_STATUS
EFIAPI
ShellFindNextFile(
  IN SHELL_FILE_HANDLE                      DirHandle,
  OUT EFI_FILE_INFO              *Buffer,
  OUT BOOLEAN                    *NoFile
  )
{
  //
  // pass to file handle lib
  //
  return (FileHandleFindNextFile(DirHandle, Buffer, NoFile));
}

 

阅读说明可以发现,调用之后会分配一个Buffer,然后由调用者来负责释放。

开始写程序之后很快问题就来了,下面这样很简单的程序会让我的模拟器一次次崩溃。

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_FILE_HANDLE   DirHandle;
  RETURN_STATUS     Status;
  EFI_FILE_INFO     *FileInfo = NULL;
  BOOLEAN			NoFile=FALSE;
  
  Status = ShellOpenFileByName(L"fsnt0:\\testz", (SHELL_FILE_HANDLE *)&DirHandle,
                               EFI_FILE_MODE_READ , 0);

  if(Status != RETURN_SUCCESS) {
        Print(L"OpenFile failed!\n");
		return EFI_SUCCESS;
      }							   

  ShellFindFirstFile(DirHandle,&FileInfo);
  PrintFileInfo(FileInfo);
  free(FileInfo);
  while (FALSE==NoFile) 
    {
		Status=ShellFindNextFile(DirHandle,FileInfo,&NoFile);
		if (EFI_SUCCESS == Status)
			{
			}
		Print(L"No File[%d]\n",NoFile);	
	}
 
  return EFI_SUCCESS;
}

 

百思不得其解,在程序中加入输出函数,跟踪一下。出错的部分在ShellFindNextFile(DirHandle,FileInfo,&NoFile); 中。追踪进入 \ShellPkg\Library\UefiFileHandleLib\UefiFileHandleLib.c 的 FileHandleFindNextFile。

/** Retrieve next entries from a directory.

  To use this function, the caller must first call the FileHandleFindFirstFile()
  function to get the first directory entry.  Subsequent directory entries are
  retrieved by using the FileHandleFindNextFile() function.  This function can
  be called several times to get each entry from the directory.  If the call of
  FileHandleFindNextFile() retrieved the last directory entry, the next call of
  this function will set *NoFile to TRUE and free the buffer.

  @param[in]  DirHandle         The file handle of the directory.
  @param[out] Buffer            The pointer to buffer for file's information.
  @param[out] NoFile            The pointer to boolean when last file is found.

  @retval EFI_SUCCESS           Found the next file, or reached last file
  @retval EFI_NO_MEDIA          The device has no media.
  @retval EFI_DEVICE_ERROR      The device reported an error.
  @retval EFI_VOLUME_CORRUPTED  The file system structures are corrupted.
**/
EFI_STATUS
EFIAPI
FileHandleFindNextFile(
  IN EFI_FILE_HANDLE          DirHandle,
  OUT EFI_FILE_INFO          *Buffer,
  OUT BOOLEAN                *NoFile
  )
{
  EFI_STATUS    Status;
  UINTN         BufferSize;

  //
  // ASSERTs for DirHandle or Buffer or NoFile poitners being NULL
  //
  ASSERT (DirHandle != NULL);
  ASSERT (Buffer    != NULL);
  ASSERT (NoFile    != NULL);

  //
  // This BufferSize MUST stay equal to the originally allocated one in GetFirstFile
  //
  BufferSize = FIND_XXXXX_FILE_BUFFER_SIZE;

  //
  // read in the info about the next file
  //
  Status = FileHandleRead (DirHandle, &BufferSize, Buffer);
  ASSERT(Status != EFI_BUFFER_TOO_SMALL);
  if (EFI_ERROR(Status)) {
    return (Status);
  }

  //
  // If we read 0 bytes (but did not have erros) we already read in the last file.
  //
  if (BufferSize == 0) {
    FreePool(Buffer);
    *NoFile = TRUE;
  }

  return (EFI_SUCCESS);
}

 

确定是 FreePool(Buffer); 导致的。看到这个代码感觉很奇怪,明明是让 caller 负责释放,为什么他在里面要干这事情?再阅读函数说明“ If the call of
FileHandleFindNextFile() retrieved the last directory entry, the next call of
this function will set *NoFile to TRUE and free the buffer.” 原来这个函数设计的意思是:调用FileHandleFindNextFile 不断取得文件时,如果你中间停止了,那么请自行释放Buffer;但是如果你一直调用到了最后一个文件,那么函数本身会帮你释放掉而不需要你来做。

最终的程序不复杂

#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;

void PrintFileInfo(EFI_FILE_INFO     *FileInfo)
{
  Print(L"Filesize [%ld] bytes\n",FileInfo-> FileSize);
  Print(L"PhysicalSize [%ld] bytes\n",FileInfo-> PhysicalSize);  

  Print(L"File Create date [%d-%d-%d %d-%d-%d]\n",
		FileInfo-> CreateTime.Year,
		FileInfo-> CreateTime.Month,
		FileInfo-> CreateTime.Day,
		FileInfo-> CreateTime.Hour,
		FileInfo-> CreateTime.Minute,
		FileInfo-> CreateTime.Second);    
		
  Print(L"File last accessed date [%d-%d-%d %d-%d-%d]\n",
		FileInfo-> LastAccessTime.Year,
		FileInfo-> LastAccessTime.Month,
		FileInfo-> LastAccessTime.Day,
		FileInfo-> LastAccessTime.Hour,
		FileInfo-> LastAccessTime.Minute,
		FileInfo-> LastAccessTime.Second);   		
		
  Print(L"File last modification date [%d-%d-%d %d-%d-%d]\n",
		FileInfo-> ModificationTime.Year,
		FileInfo-> ModificationTime.Month,
		FileInfo-> ModificationTime.Day,
		FileInfo-> ModificationTime.Hour,
		FileInfo-> ModificationTime.Minute,
		FileInfo-> ModificationTime.Second);   		

  Print(L"File Name [%s]\n",&FileInfo->FileName[0]);  
}

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_FILE_HANDLE   DirHandle;
  RETURN_STATUS     Status;
  EFI_FILE_INFO     *FileInfo = NULL;
  BOOLEAN			NoFile=FALSE;
  
  Status = ShellOpenFileByName(L"fsnt0:", (SHELL_FILE_HANDLE *)&DirHandle,
                               EFI_FILE_MODE_READ , 0);

  if(Status != RETURN_SUCCESS) {
        Print(L"OpenFile failed!\n");
		return EFI_SUCCESS;
      }							   

  ShellFindFirstFile(DirHandle,&FileInfo);
  while (TRUE!=NoFile) 
    {
	    PrintFileInfo(FileInfo);
		Status=ShellFindNextFile(DirHandle,FileInfo,&NoFile);
	}
  return EFI_SUCCESS;
}

 

运行结果

findfile

完整代码下载

FindFile

Step to UEFI (32) —– GetFileInfo

这里介绍一个能够获得文件基本信息的函数: GetFileInfo

可以在 \ShellPkg\Include\Library\ShellLib.h 看到定义

/**
  This function will retrieve the information about the file for the handle
  specified and store it in allocated pool memory.

  This function allocates a buffer to store the file's information. It is the
  caller's responsibility to free the buffer.

  @param[in] FileHandle         The file handle of the file for which information is
                                being requested.

  @retval NULL                  Information could not be retrieved.

  @return                       The information about the file.
**/
EFI_FILE_INFO*
EFIAPI
ShellGetFileInfo (
  IN SHELL_FILE_HANDLE          FileHandle
  );

 

需要FileHandle作为输入函数,输出结果是 EFI_FILE_INFO 结构体。 这个结构体可以在
\MdePkg\Include\Guid\FileInfo.h 这个文件中看到(同时在 \EdkCompatibilityPkg\Foundation\Efi\Protocol\FileInfo\FileInfo.h 里面也有一个定义,只是这个定义未参加编译)。需要注意,调用的函数负责给结果分配一块内存,你自己的程序要负责释放这块内存的。

typedef struct {
  ///
  /// The size of the EFI_FILE_INFO structure, including the Null-terminated FileName string.
  ///
  UINT64    Size;
  ///
  /// The size of the file in bytes.
  ///
  UINT64    FileSize;
  ///
  /// PhysicalSize The amount of physical space the file consumes on the file system volume.
  ///
  UINT64    PhysicalSize;
  ///
  /// The time the file was created.
  ///
  EFI_TIME  CreateTime;
  ///
  /// The time when the file was last accessed.
  ///
  EFI_TIME  LastAccessTime;
  ///
  /// The time when the file's contents were last modified.
  ///
  EFI_TIME  ModificationTime;
  ///
  /// The attribute bits for the file.
  ///
  UINT64    Attribute;
  ///
  /// The Null-terminated name of the file.
  ///
  CHAR16    FileName[1];
} EFI_FILE_INFO;

 

看这个结构体可以得知,我们能够获得文件的大小,创建时间,修改时间属性文件名等等。

对于时间的定义 EFI_TIME 可以在 \BaseTools\Source\C\Include\Common\UefiBaseTypes.h 看到。相比之前我们看过的 time_t ,这个结构体是很单纯的定义,不需要换算:

// EFI Time Abstraction:
//  Year:       2000 - 20XX
//  Month:      1 - 12
//  Day:        1 - 31
//  Hour:       0 - 23
//  Minute:     0 - 59
//  Second:     0 - 59
//  Nanosecond: 0 - 999,999,999
//  TimeZone:   -1440 to 1440 or 2047
//
typedef struct {
  UINT16  Year;
  UINT8   Month;
  UINT8   Day;
  UINT8   Hour;
  UINT8   Minute;
  UINT8   Second;
  UINT8   Pad1;
  UINT32  Nanosecond;
  INT16   TimeZone;
  UINT8   Daylight;
  UINT8   Pad2;
} EFI_TIME;

 

对于 Attribute 的定义,在 \MdePkg\Include\Protocol\SimpleFileSystem.h

//
// File attributes
//
#define EFI_FILE_READ_ONLY  0x0000000000000001ULL
#define EFI_FILE_HIDDEN     0x0000000000000002ULL
#define EFI_FILE_SYSTEM     0x0000000000000004ULL
#define EFI_FILE_RESERVED   0x0000000000000008ULL
#define EFI_FILE_DIRECTORY  0x0000000000000010ULL
#define EFI_FILE_ARCHIVE    0x0000000000000020ULL
#define EFI_FILE_VALID_ATTR 0x0000000000000037ULL

 

最后,写一个程序验证一下

#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;

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  EFI_FILE_HANDLE   FileHandle;
  RETURN_STATUS     Status;
  EFI_FILE_INFO     *FileInfo = NULL;
  
  Status = ShellOpenFileByName(L"fsnt0:", (SHELL_FILE_HANDLE *)&FileHandle,
                               EFI_FILE_MODE_READ , 0);

  if(Status != RETURN_SUCCESS) {
        Print(L"OpenFile failed!\n");
		return EFI_SUCCESS;
      }							   

  FileInfo = ShellGetFileInfo( (SHELL_FILE_HANDLE)FileHandle);	

  Print(L"Filesize [%ld] bytes\n",FileInfo-> FileSize);
  Print(L"PhysicalSize [%ld] bytes\n",FileInfo-> PhysicalSize);  

  Print(L"File Create date [%d-%d-%d %d-%d-%d]\n",
		FileInfo-> CreateTime.Year,
		FileInfo-> CreateTime.Month,
		FileInfo-> CreateTime.Day,
		FileInfo-> CreateTime.Hour,
		FileInfo-> CreateTime.Minute,
		FileInfo-> CreateTime.Second);    
		
  Print(L"File last accessed date [%d-%d-%d %d-%d-%d]\n",
		FileInfo-> LastAccessTime.Year,
		FileInfo-> LastAccessTime.Month,
		FileInfo-> LastAccessTime.Day,
		FileInfo-> LastAccessTime.Hour,
		FileInfo-> LastAccessTime.Minute,
		FileInfo-> LastAccessTime.Second);   		
		
  Print(L"File last modification date [%d-%d-%d %d-%d-%d]\n",
		FileInfo-> ModificationTime.Year,
		FileInfo-> ModificationTime.Month,
		FileInfo-> ModificationTime.Day,
		FileInfo-> ModificationTime.Hour,
		FileInfo-> ModificationTime.Minute,
		FileInfo-> ModificationTime.Second);   		

  Print(L"File Name [%s]\n",&FileInfo->FileName[0]);  
		
  free(FileInfo);  // Free up the buffer from ShellGetFileInfo()  
  
  return EFI_SUCCESS;
}

 

getfileinfo

代码下载:
GetFileInfo

参考:

1.本文的例子参考 \ShellPkg\Library\UefiShellLevel3CommandsLib\Touch.c

Step to UEFI (31) —– CLIB:Rand 随机数生成

很多时候我们期望使用随机数来测试我们的结果,在C99中有给出2个随机函数

int rand(void);

void srand(unsigned int seed);

CLIB中,在 \StdLib\Include\stdlib.h 有如下定义

/* ################  Pseudo-random sequence generation functions  ######### */

/** The rand function computes a sequence of pseudo-random integers in the
    range 0 to RAND_MAX.

    @return   The rand function returns a pseudo-random integer.
**/
int     rand(void);

/** The srand function uses the argument as a seed for a new sequence of
    pseudo-random numbers to be returned by subsequent calls to rand.

    If srand is then called with the same seed value, the sequence of
    pseudo-random numbers shall be repeated. If rand is called before any calls
    to srand have been made, the same sequence shall be generated as when srand
    is first called with a seed value of 1.
**/
void    srand(unsigned seed);

 

前者rand 是“伪随机”数,因为生成随机数的种子是固定的,所以每次生成的序列都是相同的。比如,我们用下面的程序段测试:

  printf("1st time generated by rand()\n");
  for (i=0;i<5;i++)   
  {
	printf("%d ",rand());
  }
  printf("\n");

  printf("2nd time generated by rand()\n");
  for (i=0;i<5;i++)   
  {
	printf("%d ",rand());
  }
  printf("\n");

 

得到的结果如下,可以看出来:生成的数值之间是随机的,但是每次运行生成的序列是一样的。这也就是“伪随机”的由来。

rand

此外,在程序的开始处,运行 srand 指定一个种子,就可以让 rand生成“随机数”。运行 srand的结果如下:

srand

进一步分析随机函数,可以在 \StdLib\LibC\StdLib\Rand.c 看到

static UINT32 next = 1;

/** Compute a pseudo-random number.
  *
  * Compute x = (7^5 * x) mod (2^31 - 1)
  * without overflowing 31 bits:
  *      (2^31 - 1) = 127773 * (7^5) + 2836
  * From "Random number generators: good ones are hard to find",
  * Park and Miller, Communications of the ACM, vol. 31, no. 10,
  * October 1988, p. 1195.
**/
int
rand()
{
  INT32 hi, lo, x;

  /* Can't be initialized with 0, so use another value. */
  if (next == 0)
    next = 123459876;
  hi = next / 127773;
  lo = next % 127773;
  x = 16807 * lo - 2836 * hi;
  if (x < 0)
    x += 0x7fffffff;
  return ((next = x) % ((UINT32)RAND_MAX + 1));
}

void
srand(unsigned int seed)
{
  next = (UINT32)seed;
}

 

就是说当我们使用 rand 函数的时候,种子 static UINT32 next = 1; 默认为 1.而当我们首先给定一个种子的时候,next会被替换为我们指定的值。从而达到随机的目的。(话说感觉这样做出来的随机似乎也不是很可靠。如果我们有足够多的序列,完全也能推测出随机种子,也能预测出下面将要出现的数值)

一般随机种子都是直接取当前的时间,于是写一个是程序验证:

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

#include  <stdio.h>
#include  <stdlib.h>
#include  <wchar.h>
#include  <time.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

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

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  INTN i;
  time_t t;

  printf("1st time generated by rand()\n");
  for (i=0;i<5;i++)   
  {
	printf("%d ",rand());
  }
  printf("\n");

  printf("2nd time generated by rand()\n");
  for (i=0;i<5;i++)   
  {
	printf("%d ",rand());
  }
  printf("\n");

  printf("Generated by srand()\n");
  time(&t);
  srand(t);
  for (i=0;i<5;i++)   
  {
	printf("%d ",rand());
  }
  printf("\n");
  
  return EFI_SUCCESS;
}

 

这个程序工作正常,不过有一个“奇怪”的现象:当你连续运行这个程序的时候,2nd time generated by rand() 生成的序列有时候会相同,您说这是为什么呢?

randfail

完整程序代码下载
rand

参考:

1.http://blog.csdn.net/zhenyongyuan123/article/details/5810253 C99标准库函数

Step to UEFI (30) —- UEFI 下SMBIOS的读取

有朋友提出一个问题:UEFI 下如何读取SMBIOS信息?

smbiosq

查阅了一下资料,在 EFI_CONFIGURATION_TABLE 中有 SMBIOS_TABLE_GUID 可以用来获得SMBIOS信息。此外,推荐看一下 http://blog.chinaunix.net/uid-20757375-id-3531318.html 。

可供直接参考的代码有 ShellPkg\Library\UefiShellDebug1CommandsLib\SmbiosView 如果你看着觉得过于复杂的话,还可以参考 https://github.com/fpmurphy/UEFI-Utilities 。这个程序结构清晰,功能简单。每种不足是直接下载的代码有一些错误之类的,需要修正。下面就是修改之后的代码(代码很多是从EDK中直接copy出来的,因为装一些头文件和调用会很复杂,用到的只是那么一两个函数,所以最简单的就是直接copy,放在代码中了.其中头文件 smbios.h 可以在\MdePkg\Include\IndustryStandard 中找到)

//
//  Copyright (c) 2012  Finnbarr P. Murphy.   All rights reserved.
//
//  Display  Firmware Information Vendor, Version and Release Date via SMBIOS
//
//  Any source code included from EDK2 is copyright Intel Corporation
// 
//  Licence: BSD License
//
 
 
#define GUID EFI_GUID     // hack for SmBios.h
#include "IndustryStandard/SmBios.h"       // from EDK2. GNU_EFI libsmbios.h is defective
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/MemoryAllocationLib.h>

#define EFI_GLOBAL_VARIABLE \
  { \
    0x8BE4DF61, 0x93CA, 0x11d2, {0xAA, 0x0D, 0x00, 0xE0, 0x98, 0x03, 0x2B, 0x8C } \
  }

#define EFI_SMBIOS_TABLE_GUID \
  { \
    0xeb9d2d31, 0x2d88, 0x11d3, {0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d} \
  }
  
#define BUFSIZE 64
 
#define INVALID_HANDLE  (UINT16) (-1)
#define DMI_INVALID_HANDLE  0x83
#define DMI_SUCCESS 0x00
 
STATIC SMBIOS_TABLE_ENTRY_POINT *mSmbiosTable = 0; //NULL;
STATIC SMBIOS_STRUCTURE_POINTER m_SmbiosStruct;
STATIC SMBIOS_STRUCTURE_POINTER *mSmbiosStruct = &m_SmbiosStruct;
 
EFI_GUID  SMBIOSTableGuid = EFI_SMBIOS_TABLE_GUID;

extern EFI_SYSTEM_TABLE			 *gST;

int
Strlen(const char *str)
{
    const char *s;
 
    for (s = str; *s; ++s)
        ;
    return (s - str);
}
 
 
//
// Modified from EDK2 source code.  Copyright Intel Corporation.
//
CHAR8*
LibGetSmbiosString ( SMBIOS_STRUCTURE_POINTER *Smbios,
                     UINT16 StringNumber)
{
    UINT16  Index;
    CHAR8   *String;
 
    //ASSERT (Smbios != NULL);
 
    String = (CHAR8 *) (Smbios->Raw + Smbios->Hdr->Length);
 
    for (Index = 1; Index <= StringNumber; Index++) {
        if (StringNumber == Index) {
            return String;
        }
        for (; *String != 0; String++)
             ;
        String++;
 
        if (*String == 0) {
            Smbios->Raw = (UINT8 *)++String;
            return NULL;
        }
    }
 
    return NULL;
}
 
 
//
// Modified from EDK2 source code. Copyright Intel Corporation.
//
EFI_STATUS
LibGetSmbiosStructure ( UINT16 *Handle,
                        UINT8  **Buffer,
                        UINT16 *Length)
{
     SMBIOS_STRUCTURE_POINTER  Smbios;
     SMBIOS_STRUCTURE_POINTER  SmbiosEnd;
     UINT8 *Raw;
 
     if (*Handle == INVALID_HANDLE) {
          *Handle =  mSmbiosStruct->Hdr->Handle;
          return DMI_INVALID_HANDLE;
     }
 
     if ((Buffer == NULL) || (Length == NULL)) {
          Print(L"Invalid handle\n");
          return DMI_INVALID_HANDLE;
     }
 
     *Length = 0;
     Smbios.Hdr = mSmbiosStruct->Hdr;
     SmbiosEnd.Raw = Smbios.Raw + mSmbiosTable->TableLength;
     while (Smbios.Raw < SmbiosEnd.Raw) {
          if (Smbios.Hdr->Handle == *Handle) {
             Raw = Smbios.Raw;
             LibGetSmbiosString (&Smbios, (UINT16) (-1));
             *Length = (UINT16) (Smbios.Raw - Raw);
             *Buffer = Raw;
             if (Smbios.Raw < SmbiosEnd.Raw) {
                  *Handle = Smbios.Hdr->Handle;
             } else {
                  *Handle = INVALID_HANDLE;
             }
             return DMI_SUCCESS;
         }
         LibGetSmbiosString (&Smbios, (UINT16) (-1));
    }
 
    *Handle = INVALID_HANDLE;
 
    return DMI_INVALID_HANDLE;
}
 
 
CHAR16 *
ASCII_to_UCS2(const char *s, int len)
{
    CHAR16 *ret = NULL;
    int i;
 
    ret = AllocateZeroPool(len*2 + 2);
    if (!ret)
        return NULL;
 
    for (i = 0; i < len; i++)
        ret[i] = s[i];
 
    return ret;
}

BOOLEAN
EfiCompareGuid (
  IN EFI_GUID *Guid1,
  IN EFI_GUID *Guid2
  )
/*++

Routine Description:

  Compares two GUIDs

Arguments:

  Guid1 - guid to compare

  Guid2 - guid to compare

Returns:
  TRUE     if Guid1 == Guid2
  FALSE    if Guid1 != Guid2

--*/
{
  UINTN Index;

  //
  // compare byte by byte
  //
  for (Index = 0; Index < 16; ++Index) {
    if (*(((UINT8*) Guid1) + Index) != *(((UINT8*) Guid2) + Index)) {
      return FALSE;
    }
  }
  return TRUE;
}

 
EFI_STATUS
EfiLibGetSystemConfigurationTable (
  IN EFI_GUID *TableGuid,
  OUT VOID **Table
  )
/*++

Routine Description:

  Get table from configuration table by name

Arguments:

  TableGuid       - Table name to search
  
  Table           - Pointer to the table caller wants

Returns: 

  EFI_NOT_FOUND   - Not found the table
  
  EFI_SUCCESS     - Found the table

--*/
{
  UINTN Index;

  *Table = NULL;
  for (Index = 0; Index < gST->NumberOfTableEntries; Index++) {
    if (EfiCompareGuid (TableGuid, &(gST->ConfigurationTable[Index].VendorGuid))==TRUE) {
      *Table = gST->ConfigurationTable[Index].VendorTable;
      return EFI_SUCCESS;
    }
  }

  return EFI_NOT_FOUND;
}

/**
  Compares two memory buffers of a given length.

  @param  DestinationBuffer The first memory buffer.
  @param  SourceBuffer      The second memory buffer.
  @param  Length            Length of DestinationBuffer and SourceBuffer memory
                            regions to compare. Must be non-zero.

  @return 0                 All Length bytes of the two buffers are identical.
  @retval Non-zero          The first mismatched byte in SourceBuffer subtracted from the first
                            mismatched byte in DestinationBuffer.

**/
INTN
EFIAPI
InternalMemCompareMemZ (
  IN      CONST VOID                *DestinationBuffer,
  IN      CONST VOID                *SourceBuffer,
  IN      UINTN                     Length
  )
{
  while ((--Length != 0) &&
         (*(INT8*)DestinationBuffer == *(INT8*)SourceBuffer)) {
    DestinationBuffer = (INT8*)DestinationBuffer + 1;
    SourceBuffer = (INT8*)SourceBuffer + 1;
  }
  return (INTN)*(UINT8*)DestinationBuffer - (INTN)*(UINT8*)SourceBuffer;
}

/**
  Compares the contents of two buffers.

  This function compares Length bytes of SourceBuffer to Length bytes of DestinationBuffer.
  If all Length bytes of the two buffers are identical, then 0 is returned.  Otherwise, the
  value returned is the first mismatched byte in SourceBuffer subtracted from the first
  mismatched byte in DestinationBuffer.
  
  If Length > 0 and DestinationBuffer is NULL, then ASSERT().
  If Length > 0 and SourceBuffer is NULL, then ASSERT().
  If Length is greater than (MAX_ADDRESS - DestinationBuffer + 1), then ASSERT().
  If Length is greater than (MAX_ADDRESS - SourceBuffer + 1), then ASSERT().

  @param  DestinationBuffer A pointer to the destination buffer to compare.
  @param  SourceBuffer      A pointer to the source buffer to compare.
  @param  Length            The number of bytes to compare.

  @return 0                 All Length bytes of the two buffers are identical.
  @retval Non-zero          The first mismatched byte in SourceBuffer subtracted from the first
                            mismatched byte in DestinationBuffer.
                            
**/
INTN
EFIAPI
CompareMem (
  IN CONST VOID  *DestinationBuffer,
  IN CONST VOID  *SourceBuffer,
  IN UINTN       Length
  )
{
  if (Length == 0 || DestinationBuffer == SourceBuffer) {
    return 0;
  }

  return InternalMemCompareMemZ (DestinationBuffer, SourceBuffer, Length);
}
 
 
EFI_STATUS
UefiMain (EFI_HANDLE image, EFI_SYSTEM_TABLE *systab)
{
    EFI_STATUS status = EFI_SUCCESS;
    CHAR16 *ptr;
    CHAR8  *str;
    UINT16  Handle;
    UINTN   Index;
    UINT16  Length;
    UINT8   *Buffer;
    SMBIOS_STRUCTURE_POINTER SmbiosStruct;
 
    //InitializeLib(image, systab);
 
    status = EfiLibGetSystemConfigurationTable(&SMBIOSTableGuid, (VOID**)&mSmbiosTable);
    if ((status != EFI_SUCCESS || mSmbiosTable == NULL) ||
        (CompareMem (mSmbiosTable->AnchorString, "_SM_", 4) != 0)) {
        Print(L"ERROR: SMBIOS table not found.\n");
        return status;
    }
 
    mSmbiosStruct->Raw  = (UINT8 *) (UINTN) (mSmbiosTable->TableAddress);
 
    Print(L"SMBIOS Ver: %x.%x  Rev: %x  Table Count: %d\n",
            mSmbiosTable->MajorVersion,
            mSmbiosTable->MinorVersion,
            mSmbiosTable->EntryPointRevision,
            mSmbiosTable->NumberOfSmbiosStructures);
 
    Handle  = INVALID_HANDLE;
    LibGetSmbiosStructure (&Handle, NULL, NULL);
 
    // loop though the tables looking for a type 0 table.
    for (Index = 0; Index < mSmbiosTable->NumberOfSmbiosStructures; Index++) {
        if (Handle == INVALID_HANDLE) {
            break;
        }
        if (LibGetSmbiosStructure (&Handle, &Buffer, &Length) != DMI_SUCCESS) {
            break;
        }
        SmbiosStruct.Raw = Buffer;
        if (SmbiosStruct.Hdr->Type == 0) {     // Type 0 - BIOS
 
             /* vendor string */
             str = LibGetSmbiosString(&SmbiosStruct, 1);
             ptr = ASCII_to_UCS2(str, Strlen(str));
             Print(L"Firmware Vendor: %s\n", ptr);   
             FreePool(ptr);
                        
             /* version string */
             str = LibGetSmbiosString(&SmbiosStruct, 2);
             ptr = ASCII_to_UCS2(str, Strlen(str));
             Print(L"Firmware Version: %s\n", ptr);
             FreePool(ptr);
 
             /* release string */
             str = LibGetSmbiosString(&SmbiosStruct, 3);
             ptr = ASCII_to_UCS2(str, Strlen(str));
             Print(L"Firmware Release: %s\n", ptr);
             FreePool(ptr);
 
             break;
        }
    }
 
    return status;
}


 

运行结果

smbios

对比 SMBIOSVIEW

smbiosview

smbiosview2

可以看到二者是相同的。

代码下载
GetVer

顺便多说一点:

SMBIOS中可以获得一些硬件信息,比如当前的内存插槽信息等等。但是,因为这个是BIOS在POST过程中加入的,很可能出现错误,甚至就是忘记做这个东西(MS的软件非常不信任BIOS提供的信息,如果有可能他们宁可自己重新获取一下)。如果只是想做个参考,并且你的软件需要兼容大量的设备,从这里获得信息是完全没问题的(比如MemTest86);但是如果你期望准确一些,最好还是自己手工做一下。另外一方面,如果你使用了从SMBIOS获得信息的方法,结果在客户现场遇到了问题,不妨考虑一下是否为 SMBIOS 不正确导致的。

VirtualBox 4.3.XXXX遇到的一个问题

最近又在Virtualbox下面装 Ubuntu,在设置共享目录的时候出现了一个错误,百思不得其解。

vbe

Command: sudo mount -t vboxsf

Error:

mount: wrong fs type, bad option, bad superblock on /home/h0ward/share,
missing codepage or helper program, or other error
In some cases useful info is found in syslog – try
dmesg | tail or so

上网搜了一圈,最后看到了靠谱的解释,说是 VirtualBox 4.3.10 r93012 里面有个小错误,vboxsf的链接存在问题。【参考:http://www.puppychau.com/archives/278】

最省事的解决方法就是升级VirtualBox然后升级Ubuntu里面的VBoxGuessAdditions。然后问题就搞定了…….

Step to UEFI (29) —– ShellPrintEx

之前使用TC的时候,有一个控制光标的函数 GotoXY(int,int) 【参考1 参考2】 可以很方便的控制光标在屏幕指定的位置写字符。我在CLIB中找了一大圈也没有踪影,后来查了一下 C99 标准【参考 3】,这个并非标准函数,所以也没有实现。

翻阅代码发现 Shell下面也有一个类似的函数 ShellPrintEx。在 \ShellPkg\Include\Library\ShellLib.h 申明如下:

/**
  Print at a specific location on the screen.

  This function will move the cursor to a given screen location and print the specified string.

  If -1 is specified for either the Row or Col the current screen location for BOTH
  will be used.

  If either Row or Col is out of range for the current console, then ASSERT.
  If Format is NULL, then ASSERT.

  In addition to the standard %-based flags as supported by UefiLib Print() this supports
  the following additional flags:
    %N       -   Set output attribute to normal
    %H       -   Set output attribute to highlight
    %E       -   Set output attribute to error
    %B       -   Set output attribute to blue color
    %V       -   Set output attribute to green color

  Note: The background color is controlled by the shell command cls.

  @param[in] Col        The column to print at.
  @param[in] Row        The row to print at.
  @param[in] Format     The format string.
  @param[in] ...        The variable argument list.

  @return EFI_SUCCESS           The printing was successful.
  @return EFI_DEVICE_ERROR      The console device reported an error.
**/
EFI_STATUS
EFIAPI
ShellPrintEx(
  IN INT32                Col OPTIONAL,
  IN INT32                Row OPTIONAL,
  IN CONST CHAR16         *Format,
  ...
  );

 

从上面可以看出,我们可以用这个函数来控制写入的位置。同时他还扩展了几个 Format.

写个程序实验一下

#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;

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  
  //%H       -   Set output attribute to highlight.	
	ShellPrintEx(4,4,L"%H%S",L"LAB-Z [highlight]");
  //  %B       -   Set output attribute to blue color.	
	ShellPrintEx(6,6,L"%B%S",L"LAB-Z [blue color]");  
  //  %V       -   Set output attribute to green color.
	ShellPrintEx(8,8,L"%V%S",L"LAB-Z [green color]");  
  //  %N       -   Set output attribute to normal.
	ShellPrintEx(10,10,L"%N%S",L"LAB-Z [normal]");    
  //  %E       -   Set output attribute to error.	
	ShellPrintEx(12,12,L"%E%S",L"LAB-Z [error]");     
	
  return EFI_SUCCESS;
}

 

运行结果

29

需要特别注意的是:Error 是黄色,HightLight 是白色……..

看一下具体的实现代码,在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c

EFI_STATUS
EFIAPI
ShellPrintEx(
  IN INT32                Col OPTIONAL,
  IN INT32                Row OPTIONAL,
  IN CONST CHAR16         *Format,
  ...
  )
{
  VA_LIST           Marker;
  EFI_STATUS        RetVal;
  if (Format == NULL) {
    return (EFI_INVALID_PARAMETER);
  }
  VA_START (Marker, Format);
  RetVal = InternalShellPrintWorker(Col, Row, Format, Marker);
  VA_END(Marker);
  return(RetVal);
}

 

调用的 InternalShellPrintWorker 也在同一个文件中

EFI_STATUS
EFIAPI
InternalShellPrintWorker(
  IN INT32                Col OPTIONAL,
  IN INT32                Row OPTIONAL,
  IN CONST CHAR16         *Format,
  IN VA_LIST              Marker
  )

 

其中控制写入位置的是

gST->ConOut->SetCursorPosition(gST->ConOut, gST->ConOut->Mode->CursorColumn - 1, gST->ConOut->Mode->CursorRow);

 

而控制写入颜色的是

        switch (*(ResumeLocation+1)) {
          case (L'N'):
            gST->ConOut->SetAttribute(gST->ConOut, OriginalAttribute);
            break;
          case (L'E'):
            gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(EFI_YELLOW, ((OriginalAttribute&(BIT4|BIT5|BIT6))>>4)));
            break;
          case (L'H'):
            gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(EFI_WHITE, ((OriginalAttribute&(BIT4|BIT5|BIT6))>>4)));
            break;
          case (L'B'):
            gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(EFI_BLUE, ((OriginalAttribute&(BIT4|BIT5|BIT6))>>4)));
            break;
          case (L'V'):
            gST->ConOut->SetAttribute(gST->ConOut, EFI_TEXT_ATTR(EFI_GREEN, ((OriginalAttribute&(BIT4|BIT5|BIT6))>>4)));
            break;
          default:

 

可以看出来,Error 是黄色,HightLight 是白色……..

完整代码下载

PrintEx

参考:

1.http://zhidao.baidu.com/link?url=gKNVf5BWdsVs8-NfWFmp_lbGXQNUAa5UISVTGyMusQgHQDedSwWA6ZydJIwRsEuMgd05OHN-4qR_iDNxSUkTb_

gotoxy(int x, int y);
包含头文件为:
#include

2.http://zhidao.baidu.com/link?url=knYo-KswVT4e3IOWvZc54LXR6Vh_hcfLBA6sLLKOMQImC4KN6pPoeF4pqg4QKO5cQk4PRe4FPydcBq5ZuVDprK

包含在conio.h TC2.0中应该是这个文件引入的

3.http://blog.csdn.net/zhenyongyuan123/article/details/5810253 C99标准库函数

Step to UEFI (28) —– Shell GetCurDir 补遗

上一篇给出了GetCurDir的方法,不过这样玩多多少少看起来让人觉得不够简洁。于是又在EDK2 ShellPkg中翻了一下,在\ShellPkg\Include\Library\ShellLib.h 中有定义 ShellGetCurrentDir 函数

/**
  Retreives the current directory path.

  If the DeviceName is NULL, it returns the current device's current directory
  name. If the DeviceName is not NULL, it returns the current directory name
  on specified drive.

  @param[in] DeviceName         The name of the file system to get directory on.

  @retval NULL                  The directory does not exist.
  @retval != NULL               The directory.
**/
CONST CHAR16*
EFIAPI
ShellGetCurrentDir (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  );

 

看一下具体实现在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c

/**
  Retreives the current directory path

  If the DeviceName is NULL, it returns the current device's current directory
  name. If the DeviceName is not NULL, it returns the current directory name
  on specified drive.

  @param DeviceName             the name of the drive to get directory on

  @retval NULL                  the directory does not exist
  @return != NULL               the directory
**/
CONST CHAR16*
EFIAPI
ShellGetCurrentDir (
  IN CHAR16                     * CONST DeviceName OPTIONAL
  )
{
  //
  // Check for UEFI Shell 2.0 protocols
  //
  if (gEfiShellProtocol != NULL) {
    return (gEfiShellProtocol->GetCurDir(DeviceName));
  }

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

  return (NULL);
}

 

就是说它内部实际上是直接判断了当前哪个Protocol能用,就用对应的来实现取得当前目录的功能。于是,直接使用它就能避免我们需要特别选择 Protocol 的烦恼。尝试编写程序如下

#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;

int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  CHAR16 *R=0;
  
  R=(CHAR16 *)ShellGetCurrentDir(NULL);
  
  Print(L"%s",ShellGetCurrentDir(0));
  return EFI_SUCCESS;
}

 

同时给出对应的 INF,如果你再编译时出现Link之类的错误,最大的可能就是头文件和INF中没有引用对应的库支持。

## @file
#   A simple, basic, application showing how the Hello application could be
#   built using the "Standard C Libraries" from StdLib.
#
#  Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
#  This program and the accompanying materials
#  are licensed and made available under the terms and conditions of the BSD License
#  which accompanies this distribution. The full text of the license may be found at
#  http://opensource.org/licenses/bsd-license.
#
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
##

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = getcurdir2
  FILE_GUID                      = 4ea97c46-7491-4dfd-0028-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#   
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  GetCurDir2.c

[Packages]
  StdLib/StdLib.dec   
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec 


[LibraryClasses]
  LibC
  LibStdio
  ShellCEntryLib   
  ShellLib
  
[Protocols]
	
[BuildOptions]

 

从功能上来说,和之前的完全一样

28

GetCurDir2

Step to UEFI (27) —– Shell GetCurDir

翻看 Shell Specification【参考 1】,发现上面介绍了一个Shell下取得当前目录的函数

getcurdir

看起来这个函数很简单,于是动手写程序。注意到 \ShellPkg\Library\UefiShellLib\UefiShellLib.c 中有声明,于是直接 extern EFI_SHELL_PROTOCOL *gEfiShellProtocol; 过来用。之所以能这样做,是因为这个位置是 Shell 程序在编译过程中特别加入的头,具体请参考之前的文章。

编译完成之后,进入虚拟环境实验,非常奇怪,每次运行都会出现错误提示,然后NT32就会崩溃掉。

getcurdirerror

真相到底是什么呢?一检查,gEfiShellProtocol == 0 。还要追根溯源,到定义他的地方去查看。

代码在 \ShellPkg\Library\UefiShellLib\UefiShellLib.c

为了Debug,在关键的地方添加上的一点代码,输出信息

  Status = gBS->OpenProtocol(
    ImageHandle,
    &gEfiShellParametersProtocolGuid,
    (VOID **)&gEfiShellParametersProtocol,
    ImageHandle,
    NULL,
    EFI_OPEN_PROTOCOL_GET_PROTOCOL
   );
  if (EFI_ERROR(Status)) {
    gEfiShellParametersProtocol = NULL;
  }
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestA\n\r"); //LabZDebug
  if (gEfiShellParametersProtocol == NULL || gEfiShellProtocol == NULL) {
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestB\n\r"); //LabZDebug
    //
    // Moved to seperate function due to complexity
    //
    Status = ShellFindSE2(ImageHandle);

    if (EFI_ERROR(Status)) {
      DEBUG((DEBUG_ERROR, "Status: 0x%08x\r\n", Status));
      mEfiShellEnvironment2 = NULL;
    }
    Status = gBS->OpenProtocol(ImageHandle,
                               &gEfiShellInterfaceGuid,
                               (VOID **)&mEfiShellInterface,
                               ImageHandle,
                               NULL,
                               EFI_OPEN_PROTOCOL_GET_PROTOCOL
                              );
    if (EFI_ERROR(Status)) {
      mEfiShellInterface = NULL;
    }
  }
if (0==gEfiShellProtocol) { 
SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestE\n\r"); //LabZDebug
  }

SystemTable->ConOut->OutputString(SystemTable->ConOut,L"TestC\n\r"); //LabZDebug

 

输出的结果是: TestA TestB TestE 和 TestC。 就是说这段代码尝试取 Shell Protocol 但是没有取到。再仔细看一下代码,这里没有取得到。因此,此路不通。再向下看发现初始化了EFI_SHELL_ENVIRONMENT2 *mEfiShellEnvironment2,再看了一下他的定义,在\ShellPkg\Include\Protocol\EfiShellEnvironment2.h 中

/// EFI_SHELL_ENVIRONMENT2 protocol structure.
typedef struct {
  SHELLENV_EXECUTE                        Execute;
  SHELLENV_GET_ENV                        GetEnv;
  SHELLENV_GET_MAP                        GetMap;
  SHELLENV_ADD_CMD                        AddCmd;
  SHELLENV_ADD_PROT                       AddProt;
  SHELLENV_GET_PROT                       GetProt;
  SHELLENV_CUR_DIR                        CurDir;
  SHELLENV_FILE_META_ARG                  FileMetaArg;
  SHELLENV_FREE_FILE_LIST                 FreeFileList;

  //
  // The following services are only used by the shell itself.
  //
  SHELLENV_NEW_SHELL                      NewShell;
  SHELLENV_BATCH_IS_ACTIVE                BatchIsActive;

  SHELLENV_FREE_RESOURCES                 FreeResources;

  //
  // GUID to differentiate ShellEnvironment2 from ShellEnvironment.
  //
  EFI_GUID                                SESGuid;
  //
  // Major Version grows if shell environment interface has been changes.
  //
  UINT32                                  MajorVersion;
  UINT32                                  MinorVersion;
  SHELLENV_ENABLE_PAGE_BREAK              EnablePageBreak;
  SHELLENV_DISABLE_PAGE_BREAK             DisablePageBreak;
  SHELLENV_GET_PAGE_BREAK                 GetPageBreak;

  SHELLENV_SET_KEY_FILTER                 SetKeyFilter;
  SHELLENV_GET_KEY_FILTER                 GetKeyFilter;

  SHELLENV_GET_EXECUTION_BREAK            GetExecutionBreak;
  SHELLENV_INCREMENT_SHELL_NESTING_LEVEL  IncrementShellNestingLevel;
  SHELLENV_DECREMENT_SHELL_NESTING_LEVEL  DecrementShellNestingLevel;
  SHELLENV_IS_ROOT_SHELL                  IsRootShell;

  SHELLENV_CLOSE_CONSOLE_PROXY            CloseConsoleProxy;
  HANDLE_ENUMERATOR                       HandleEnumerator;
  PROTOCOL_INFO_ENUMERATOR                ProtocolInfoEnumerator;
  GET_DEVICE_NAME                         GetDeviceName;
  GET_SHELL_MODE                          GetShellMode;
  SHELLENV_NAME_TO_PATH                   NameToPath;
  SHELLENV_GET_FS_NAME                    GetFsName;
  SHELLENV_FILE_META_ARG_NO_WILDCARD      FileMetaArgNoWildCard;
  SHELLENV_DEL_DUP_FILE                   DelDupFileArg;
  SHELLENV_GET_FS_DEVICE_PATH             GetFsDevicePath;
} EFI_SHELL_ENVIRONMENT2;

因此,尝试一下SHELLENV_CUR_DIR. 这个函数头文件也在同一个文件中

/**
  This function returns a string array containing the current directory on
  a given device.

  If DeviceName is specified, then return the current shell directory on that
  device.  If DeviceName is NULL, then return the current directory on the
  current device.  The caller us responsible to free the returned string when
  no longer required.

  @param[in] DeviceName         The name of the device to get the current
                                directory on, or NULL for current device.

  @return String array with the current directory on the current or specified device.

**/
typedef
CHAR16*
(EFIAPI *SHELLENV_CUR_DIR) (
  IN CHAR16 *DeviceName OPTIONAL
  );

 

最后的 C 代码如下

#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_ENVIRONMENT2        *mEfiShellEnvironment2;

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
int
EFIAPI
main (
  IN int Argc,
  IN char **Argv
  )
{
  Print(L"%s",mEfiShellEnvironment2->CurDir(0));
  return EFI_SUCCESS;
}

 

对应的 INF 代码如下

## @file
#   A simple, basic, application showing how the Hello application could be
#   built using the "Standard C Libraries" from StdLib.
#
#  Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
#  This program and the accompanying materials
#  are licensed and made available under the terms and conditions of the BSD License
#  which accompanies this distribution. The full text of the license may be found at
#  http://opensource.org/licenses/bsd-license.
#
#  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
#  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
##

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = getcurdir
  FILE_GUID                      = 4ea97c46-7491-4dfd-0027-747010f3ce5f
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib

#   
#  VALID_ARCHITECTURES           = IA32 X64 IPF
#

[Sources]
  GetCurDir.c

[Packages]
  StdLib/StdLib.dec   
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec 

[LibraryClasses]
  LibC
  LibStdio
  ShellCEntryLib   
  ShellLib

[Protocols]

[BuildOptions]

 

运行结果

getcurdir1

 

说明工作正常。

完整下载

GetCurDir

参考:

1.UEFI Shell Specification July 2, 2014 EFI_SHELL_PROTOCOL.GetCurDir() P24