51. UEFI APP에 메뉴얼 추가하기(shell의 -?와 help 옵션)
- EFI_SHELL_PROTOCOL의 EFI_SHELL_GET_HELP_TEXT 함수
UEFI shell의 모든 명령어를 보면, -?
옵션을 추가하면, 도움말 메시지를 출력할 수 있다. 예를 들어,
Shell> reset -?
Resets the system.
RESET [-w [string]]
RESET [-s [string]]
RESET [-c [string]]
-s - Performs a shutdown.
-w - Performs a warm boot.
-c - Performs a cold boot.
string - Describes a reason for the reset.
NOTES:
1. This command resets the system.
2. The default is to perform a cold reset unless the -w parameter is
specified.
3. If a reset string is specified, it is passed into the Reset()
function, and the system records the reason for the system reset.
이번 장에서는 help/man 기능을 애플리케이션에 추가하는 방법을 알아볼 것이다.
man finding 및 파싱을 담당하는 Shell 모듈은 ShellManParser
이다.
ProcessManFile
함수를 자세히 보면, 다음과 같은 기능들이 있다.
gEfiHiiPackageListProtocolGuid
에 의한 개방형 이미지 프로토콜이를 찾아내면, 받은 패키지 목록을
gHiiDatabase -> NewPackageList
에 등록한다.HiiGetString
의 도움으로 가능한 모든 문자열 ID를 검토한다.ManFileFindTitleSection
함수가 일부 문자열에 대해 true를 반환하면,ManFileFindSections
를 실행한다.
ManFileFineTitleSection
은 기본적으로 특별한 man 형식의 문자열을 검색한다.
UEFI shell 규격에서 이 형식에 대해 자세히 알아볼 수 있다. https://uefi.org/sites/default/files/resources/UEFI_Shell_Spec_2_0.pdf


최소한의 manual로 앱 만들기
manual이 있는 애플리케이션을 만들어 보자.
shell 스크립트를 사용하여, 템플릿을 통해 애플리케이션 만들기
./createNewApp.sh HIIStringsMan
새로 만든 애플리케이션을 UefiLessonsPkg.dsc에 추가한다.
$ vi UefiLessonsPkg/UefiLessonsPkg.dsc
---
[Components]
UefiLessonsPkg/HIIStringsMan/HIIStringsMan.inf
기억할 것이라 생각하지만, ShellManParser
는 gEfiHiiPackageListProtocolGuid
프로토콜 데이터에서 maunal 문자열을 검색했다. 따라서 manual 문자열을 이미지 리소스 Section(HIIStringsMan.inf)에 직접 포함해야 한다.
$ cat UefiLessonsPkg/HIIStringsMan/HIIStringsMan.inf
---
[Defines]
...
UEFI_HII_RESOURCE_SECTION = TRUE
[Sources]
...
Strings.uni
다음은 문자열 파일에 최소화된 내용만을 담은 코드이다.
$ cat UefiLessonsPkg/HIIStringsMan/Strings.uni
---
#langdef en-US "English"
#string STR_HELP #language en-US ""
".TH HIIStringsMan 0 "Simple application with a manual inside."\r\n"
".SH NAME\r\n"
"HIIStringsMan application.\r\n"
애플리케이션을 빌드하고, 실행하려고 하면 더 이상 아무런 도움이 되지 않는다는 것을 볼 수 있다.
FS0:\> HIIStringsMan.efi -?
No help could be found for command 'HIIStringsMan.efi'.
FS0:\>
이는 프로그램이 문자열 토큰을 참조하지 않고, 빌드 프로세스(HIIStringsManStrDefs.h
)에서 최적화되었기 때문이다.
//
//Unicode String ID
//
// #define $LANGUAGE_NAME 0x0000 // not referenced
// #define $PRINTABLE_LANGUAGE_NAME 0x0001 // not referenced
// #define STR_HELLO_WORLD_HELP_INFORMATION 0x0002 // not referenced
이를 수정하려면, 다음 코드를 HIIStringsMan.c
에 추가하면 된다.
$ vi UefiLessonsPkg/HIIStringsMan/HIIStringsMan.c
---
GLOBAL_REMOVE_IF_UNREFERENCED EFI_STRING_ID mStringHelpTokenId = STRING_TOKEN(STR_HELP);
현재 문자열이 최적화되지 않았음을 빌드 이후에 확인할 수 있다.
//
//Unicode String ID
//
// #define $LANGUAGE_NAME 0x0000 // not referenced
// #define $PRINTABLE_LANGUAGE_NAME 0x0001 // not referenced
#define STR_HELP 0x0002
애플리케이션을 실행하고 나면, 다음과 같은 결과를 확인할 수 있다.
FS0:\> HIIStringsMan.efi -?
HIIStringsMan application.
우리의 Manual을 확장하기
manual에 들어갈 수 있는 모든 섹션을 추가해보자.
#langdef en-US "English"
#string STR_HELP #language en-US ""
".TH HIIStringsMan 0 "Simple application with a manual inside."\r\n"
".SH NAME\r\n"
"HIIStringsMan application.\r\n"
".SH SYNOPSIS\r\n"
"This is the synopsis section.\r\n"
".SH DESCRIPTION\r\n"
"This is the description section.\r\n"
".SH OPTIONS\r\n"
"This is the options section.\r\n"
".SH RETURN VALUES\r\n"
"This is the return values section.\r\n"
".SH ENVIRONMENT VARIABLES\r\n"
"This is the section for used environment variables\r\n"
".SH FILES\r\n"
"This is the section for files associated with the subject.\r\n"
".SH EXAMPLES\r\n"
"This is the section for examples and suggestions.\r\n"
".SH ERRORS\r\n"
"This is the section for errors reported by the command.\r\n"
".SH STANDARDS\r\n"
"This is the section for conformance to applicable standards.\r\n"
".SH BUGS\r\n"
"This is the section for errors and caveats.\r\n"
".SH CATEGORY\r\n"
"This is the section for categories.\r\n"
".SH CUSTOMSECTION\r\n"
"This is an example of a custom section.\r\n"
여기까지 빌드하고, 실행하면 다음 내용이 출력되는 것을 확인할 수 있다.
FS0:\> HIIStringsMan.efi -?
HIIStringsMan application.
This is the synopsis section.
This is the description section.
This is the options section.
This is the section for examples and suggestions.
보다시피, 넣어준 모든 섹션에 대한 print가 되지 않았다는 것을 알 수 있다. 그 이유에 대해 알아보자.
먼저 명령어에 -?
옵션을 추가해주면, 어떤 일이 일어나는지 알아보도록 하자. shell의 소스코드를 보면 shell이 -?
를 명령 인수 중 하나로, command 및 나머지 모든 인수를 Shell.c
의 도움말 명령으로 리다이렉트한다.
$ cat ShellPkg/Application/Shell/Shell.c
---
/**
Reprocess the command line to direct all -? to the help command.
if found, will add "help" as argv[0], and move the rest later.
@param[in,out] CmdLine pointer to the command line to update
**/
EFI_STATUS
DoHelpUpdate(
IN OUT CHAR16 **CmdLine
)
{
...
if (StrStr(CurrentParameter, L"-?") == CurrentParameter) {
CurrentParameter[0] = L' ';
CurrentParameter[1] = L' ';
NewCmdLineSize = StrSize(L"help ") + StrSize(*CmdLine);
NewCommandLine = AllocateZeroPool(NewCmdLineSize);
if (NewCommandLine == NULL) {
Status = EFI_OUT_OF_RESOURCES;
break;
}
//
// We know the space is sufficient since we just calculated it.
//
StrnCpyS(NewCommandLine, NewCmdLineSize/sizeof(CHAR16), L"help ", 5);
StrnCatS(NewCommandLine, NewCmdLineSize/sizeof(CHAR16), *CmdLine, StrLen(*CmdLine));
SHELL_FREE_NON_NULL(*CmdLine);
*CmdLine = NewCommandLine;
break;
}
...
}
프로그램에 직접 help
명령을 사용하면, 결과가 동일하게 출력되는지 확인할 수 있다.
FS0:\> help HIIStringsMan.efi
HIIStringsMan application.
This is the synopsis section.
This is the description section.
This is the options section.
This is the section for examples and suggestions.
이제 도움말 명령 소스 코드(Help.c
)를 살펴보자.
$ cat ShellPkg/Library/UefiShellLevel3CommmandsLib/Help.c
---
SHELL_STATUS
EFIAPI
ShellCommandRunHelp (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
...
//
// Get the section name for the given command name
//
if (ShellCommandLineGetFlag(Package, L"-section")) {
StrnCatGrow(&SectionToGetHelpOn, NULL, ShellCommandLineGetValue(Package, L"-section"), 0);
} else if (ShellCommandLineGetFlag(Package, L"-usage")) {
StrnCatGrow(&SectionToGetHelpOn, NULL, L"NAME,SYNOPSIS", 0);
} else if (ShellCommandLineGetFlag(Package, L"-verbose") || ShellCommandLineGetFlag(Package, L"-v")) {
} else {
//
// The output of help <command> will display NAME, SYNOPSIS, OPTIONS, DESCRIPTION, and EXAMPLES sections.
//
StrnCatGrow (&SectionToGetHelpOn, NULL, L"NAME,SYNOPSIS,OPTIONS,DESCRIPTION,EXAMPLES", 0);
}
...
}
여기에서 help
를 넣어줄 인수를 파싱하는 방법을 찾아볼 수 있다.
기본적으로 NAME, SYNOPSIS, OPTIONS, DESCRIPTION, EXAMPLES 섹션이 프린트된다.
-section <SECTION NAME>
인수를 사용해 특정 섹션을 출력할 수 있다.-verbose
혹은-v
인수를 사용해 모든 섹션들을 출력할 수 있다.
Shell에서 이 내용을 직접 확인해 볼 수 있다.
FS0:\> help HIIStringsMan.efi -v
HIIStringsMan application.
This is the synopsis section.
This is the description section.
This is the options section.
This is the return values section.
This is the section for used environment variables
This is the section for files associated with the subject.
This is the section for examples and suggestions.
This is the section for errors reported by the command.
This is the section for conformance to applicable standards.
This is the section for errors and caveats.
This is the section for categories.
This is an example of a custom section.
FS0:\> help HIIStringsMan.efi -section BUGS
This is the section for errors and caveats.
-?
옵션도 마찬가지다.
FS0:\> HIIStringsMan.efi -? -section "RETURN VALUES"
This is the return values section.
FS0:\> HIIStringsMan.efi -? -verbose
HIIStringsMan application.
This is the synopsis section.
This is the description section.
This is the options section.
This is the return values section.
This is the section for used environment variables
This is the section for files associated with the subject.
This is the section for examples and suggestions.
This is the section for errors reported by the command.
This is the section for conformance to applicable standards.
This is the section for errors and caveats.
This is the section for categories.
This is an example of a custom section.
ShellManParser가 호출되는 방식
ShellManParser
가 애플리케이션 문자열을 파싱한 것이라는 가정으로, 이번 장을 시작했었다. 도움말 프로그램이 이 모듈을 사용하여, 어떻게 끝나는 지 조사해보자.
모든 걸 따져보면, ShellCommandRunHelp
는 ShellPrintHelp
함수(Help.c
)를 호출한다.
$ cat ShellPkg/Library/UefiShellLevel3CommandsLib/Help.c
---
SHELL_STATUS
EFIAPI
ShellCommandRunHelp (
IN EFI_HANDLE ImageHandle,
IN EFI_SYSTEM_TABLE *SystemTable
)
...
Status = ShellPrintHelp(CommandToGetHelpOn, SectionToGetHelpOn, FALSE);
...
}
이 ShellPrintHelp 함수는 UefiShellLib.c에 정의되어 있다. 주로 EFI_SHELL_PROTOCOL
에서 GetHelpText
함수를 호출한다.
EFI_STATUS
EFIAPI
ShellPrintHelp (
IN CONST CHAR16 *CommandToGetHelpOn,
IN CONST CHAR16 *SectionToGetHelpOn,
IN BOOLEAN PrintCommandText
)
{
...
Status = gEfiShellProtocol->GetHelpText (CommandToGetHelpOn, SectionToGetHelpOn, &OutText);
...
}
다음은 UEFI Shell 규격에서의 위 함수에 대한 설명이다.
EFI_SHELL_PROTOCOL.GetHelpText()
Summary:
Return help information about a specific command.
Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_SHELL_GET_HELP_TEXT) (
IN CONST CHAR16 *Command,
IN CONST CHAR16 *Sections,
OUT CHAR16 **HelpText
);
Parameters:
Command Points to the null-terminated UEFI Shell command name.
Sections Points to the null-terminated comma-delimited section names to return. If NULL, then all sections will be returned.
HelpText On return, points to a callee-allocated buffer containing all specified help text.
EDKII의 EFI_SHELL_PROTOCOL
structure에 대한 프로토타입은 Shell.h
에 나와있다.
$ cat MdePkg/Include/Protocol/Shell.h
---
typedef struct _EFI_SHELL_PROTOCOL {
...
EFI_SHELL_GET_HELP_TEXT GetHelpText;
...
} EFI_SHELL_PROTOCOL;
이 프로토콜에 대한 초기화는 ShellProtocol.c
에 있다.
$ cat ShellPkg/Application/Shell/ShellProtocol.c
---
EFI_SHELL_PROTOCOL mShellProtocol = {
...
EfiShellGetHelpText,
...
}
이 함수는 위의 동일한 파일에 정의되어 있으며, 정의를 읽어보면 ProcessManFile
모듈에서 함수를 호출하고 있음을 알 수 있다.
$ cat ShellPkg/Application/Shell/ShellProtocol.c
---
EFIAPI
EfiShellGetHelpText (
IN CONST CHAR16 *Command,
IN CONST CHAR16 *Sections OPTIONAL,
OUT CHAR16 **HelpText
)
{
CONST CHAR16 *ManFileName;
CHAR16 *FixCommand;
EFI_STATUS Status;
ASSERT (HelpText != NULL);
FixCommand = NULL;
ManFileName = ShellCommandGetManFileNameHandler (Command);
if (ManFileName != NULL) {
return (ProcessManFile (ManFileName, Command, Sections, NULL, HelpText));
} else {
if ( (StrLen (Command) > 4)
&& ((Command[StrLen (Command)-1] == L'i') || (Command[StrLen (Command)-1] == L'I'))
&& ((Command[StrLen (Command)-2] == L'f') || (Command[StrLen (Command)-2] == L'F'))
&& ((Command[StrLen (Command)-3] == L'e') || (Command[StrLen (Command)-3] == L'E'))
&& (Command[StrLen (Command)-4] == L'.')
)
{
FixCommand = AllocateZeroPool (StrSize (Command) - 4 * sizeof (CHAR16));
if (FixCommand == NULL) {
return EFI_OUT_OF_RESOURCES;
}
StrnCpyS (
FixCommand,
(StrSize (Command) - 4 * sizeof (CHAR16))/sizeof (CHAR16),
Command,
StrLen (Command)-4
);
Status = ProcessManFile (FixCommand, FixCommand, Sections, NULL, HelpText);
FreePool (FixCommand);
return Status;
} else {
return (ProcessManFile (Command, Command, Sections, NULL, HelpText));
}
}
}
Last updated