Step to UEFI (109)显示Shell 下面的历史信息

之前有网友询问过一个问题:他的程序调用了另外一个 Application,希望能够获得另外那个 Application的运行结果。最近,偶然看到了 Shell 下记录历史信息的功能,具体头文件在\ShellPkg\Application\Shell\ConsoleLogger.h 中有定义。

typedef struct _CONSOLE_LOGGER_PRIVATE_DATA{
  UINTN                             Signature;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   OurConOut;        ///< the protocol we installed onto the system table
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   *OldConOut;       ///< old protocol to reinstall upon exiting
  EFI_HANDLE                        OldConHandle;     ///< old protocol handle
  UINTN                             ScreenCount;      ///< How many screens worth of data to save
  CHAR16                            *Buffer;          ///< Buffer to save data
  UINTN                             BufferSize;       ///< size of buffer in bytes

                                                      //  start row is the top of the screen
  UINTN                             OriginalStartRow; ///< What the originally visible start row was
  UINTN                             CurrentStartRow;  ///< what the currently visible start row is

  UINTN                             RowsPerScreen;    ///< how many rows the screen can display
  UINTN                             ColsPerScreen;    ///< how many columns the screen can display

  INT32                             *Attributes;      ///< Buffer for Attribute to be saved for each character
  UINTN                             AttribSize;       ///< Size of Attributes in bytes

  EFI_SIMPLE_TEXT_OUTPUT_MODE       HistoryMode;      ///< mode of the history log
  BOOLEAN                           Enabled;          ///< Set to FALSE when a break is requested.
  UINTN                             RowCounter;       ///< Initial row of each print job.
} CONSOLE_LOGGER_PRIVATE_DATA;

 

这个功能实现的方法是,在 Shell 启动的时,将系统中的EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL 题材换成重新定义的,这样每次Application的输出都会被Shell截获存放在一个Buffer中,有需要的时候,再从 Buffer中取出(例如:使用 PageUp/PageDown 查看)。
对于我们来说,首先找到 Shell 的 Handle,之后枚举这个 Handle 上的全部protocol,找到SimpleTextOutProtocol,再根据找到的这个Protocol的偏移,反推算出CONSOLE_LOGGER_PRIVATE_DATA,最后就能访问到我们需要的CONSOLE_LOGGER_PRIVATE_DATA->buffer了。
代码如下,为了简单明了,我只显示Buffer最前面4行

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

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

extern EFI_HANDLE				 gImageHandle;

#define CR(Record, TYPE, Field, TestSignature)                                              \
    BASE_CR (Record, TYPE, Field)
	
typedef struct _CONSOLE_LOGGER_PRIVATE_DATA{
  UINTN                             Signature;
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   OurConOut;        ///< the protocol we installed onto the system table
  EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL   *OldConOut;       ///< old protocol to reinstall upon exiting
  EFI_HANDLE                        OldConHandle;     ///< old protocol handle
  UINTN                             ScreenCount;      ///< How many screens worth of data to save
  CHAR16                            *Buffer;          ///< Buffer to save data
  UINTN                             BufferSize;       ///< size of buffer in bytes

                                                      //  start row is the top of the screen
  UINTN                             OriginalStartRow; ///< What the originally visible start row was
  UINTN                             CurrentStartRow;  ///< what the currently visible start row is

  UINTN                             RowsPerScreen;    ///< how many rows the screen can display
  UINTN                             ColsPerScreen;    ///< how many columns the screen can display

  INT32                             *Attributes;      ///< Buffer for Attribute to be saved for each character
  UINTN                             AttribSize;       ///< Size of Attributes in bytes

  EFI_SIMPLE_TEXT_OUTPUT_MODE       HistoryMode;      ///< mode of the history log
  BOOLEAN                           Enabled;          ///< Set to FALSE when a break is requested.
  UINTN                             RowCounter;       ///< Initial row of each print job.
} CONSOLE_LOGGER_PRIVATE_DATA;

#define CONSOLE_LOGGER_PRIVATE_DATA_FROM_THIS(a) CR (a, CONSOLE_LOGGER_PRIVATE_DATA, OurConOut, CONSOLE_LOGGER_PRIVATE_DATA_SIGNATURE)


int
EFIAPI
main (
  IN int Argc,
  IN CHAR16 **Argv
  )
{
	EFI_STATUS	Status;

	EFI_HANDLE	*HandleBuffer=NULL;
	UINTN		BufferSize=0,i;
  
	EFI_HANDLE	TheHandle;
	EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *Dev;
	
	CONSOLE_LOGGER_PRIVATE_DATA       *ConsoleInfo;
	
	// Look up all gEfiShellProtocolGuid in the platform
	Status = gBS->LocateHandle(
		ByProtocol,
		&gEfiShellProtocolGuid,
		NULL,
		&BufferSize,
		HandleBuffer);
	if (Status == EFI_BUFFER_TOO_SMALL) {
		HandleBuffer = AllocateZeroPool(BufferSize);
		if (HandleBuffer == NULL) {
			return (EFI_BUFFER_TOO_SMALL);}
		//Get all the gEfiShellProtocolGuid Protocol in the system
		Status = gBS->LocateHandle(
			ByProtocol,
			&gEfiShellProtocolGuid,
			NULL,
			&BufferSize,
			HandleBuffer);
	}
	
	//Print(L"%d handles have been found!\n",(BufferSize / sizeof(EFI_HANDLE)));	

	if (BufferSize==0) {
			Print(L"No gEfiShellProtocolGuid found!\n"); 
			return EFI_SUCCESS; }
			
	//In fact there will be only one in system (in the Shell image)
	TheHandle=HandleBuffer[0];
	FreePool(HandleBuffer);

	//Get SimpleTextOutProtocol used by Shell
	Status = gBS->HandleProtocol (
					TheHandle,
					&gEfiSimpleTextOutProtocolGuid,
					&Dev);
	if (EFI_ERROR(Status))	{
		Print(L"Error when opening SimpleTextOutProtocol\n");
		return EFI_SUCCESS;
	}

	ConsoleInfo = CONSOLE_LOGGER_PRIVATE_DATA_FROM_THIS(Dev);	
	
	for (i=0;i<(ConsoleInfo->ColsPerScreen+2)*4;i++) {
		if (ConsoleInfo->Buffer[i] != 0) {Print(L"%c",ConsoleInfo->Buffer[i]);}
	}
	Print(L"\n");
	
	return EFI_SUCCESS;
}

 

运行结果,这是NT32模拟环境中的运行结果。同样我还在 Intel ApolloLake 上进行了实验,结果相同。

hs

完整的代码下载:

logbuffer

编译后的Application下载(IA32和X64)

lb

《Step to UEFI (109)显示Shell 下面的历史信息》有2个想法

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注