aboutsummaryrefslogtreecommitdiffstats
path: root/Lessons/Lesson_51/README.md
blob: 350114c37f25908fa9b9d1a31cecde0992fc8d52 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
Every command in UEFI Shell has a help message which you can read if you supply command with a `-?` argument. For example:
```
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.
```

In this lesson we would investigate how we can add this help/man functionality to our application.

The Shell module responsible for the man finding and parsing is `ShellManParser`:
- https://github.com/tianocore/edk2/blob/master/ShellPkg/Application/Shell/ShellManParser.h
- https://github.com/tianocore/edk2/blob/master/ShellPkg/Application/Shell/ShellManParser.c

If you look close at the module `ProcessManFile` function you'll see that besides everything it tries to:
- open image protocol by `gEfiHiiPackageListProtocolGuid`
- if found, register recieved Package list with `gHiiDatabase->NewPackageList`
- go through all possible string IDs with a help of `HiiGetString`
- if `ManFileFindTitleSection` function returns true for some string, execute `ManFileFindSections`

`ManFileFindTitleSection` basically searches for a string that has a special man formatting.

You could learn more about this formatting from the UEFI Shell specification (https://uefi.org/sites/default/files/resources/UEFI_Shell_Spec_2_0.pdf)

![Manual Page Syntax1](Man1.png?raw=true "Manual page1")
![Manual Page Syntax2](Man2.png?raw=true "Manual page2")

# Create an app with a minimal manual

Let's create an application with a manual.

Use shell script to create app from template:
```
./createNewApp.sh HIIStringsMan
```

Add newly created app to the UefiLessonsPkg/UefiLessonsPkg.dsc:
```
[Components]
  UefiLessonsPkg/HIIStringsMan/HIIStringsMan.inf
```

As you remember `ShellManParser` searched for the manual strings in the `gEfiHiiPackageListProtocolGuid` protocol data. Therefore our manual strings we need to include directly in the image resource section UefiLessonsPkg/HIIStringsMan/HIIStringsMan.inf:
```
[Defines]
  ...
  UEFI_HII_RESOURCE_SECTION      = TRUE

[Sources]
  ...
  Strings.uni
```

Here is a minimal content for our strings file 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"
```

If you build our application and try to execute it, there wouldn't be any help now:
```
FS0:\> HIIStringsMan.efi -?
No help could be found for command 'HIIStringsMan.efi'.
FS0:\>
```

This is because our program don't reference string tokens and they got optimized in the build process `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/HIIStringsMan/HIIStringsMan/DEBUG/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
```

To fix it add this string to our `UefiLessonsPkg/HIIStringsMan/HIIStringsMan.c` file:
```
GLOBAL_REMOVE_IF_UNREFERENCED EFI_STRING_ID mStringHelpTokenId = STRING_TOKEN(STR_HELP);
```

You can verify after the build that now string is not optimized `Build/UefiLessonsPkg/RELEASE_GCC5/X64/UefiLessonsPkg/HIIStringsMan/HIIStringsMan/DEBUG/HIIStringsManStrDefs.h`:
```
//
//Unicode String ID
//
// #define $LANGUAGE_NAME                                       0x0000 // not referenced
// #define $PRINTABLE_LANGUAGE_NAME                             0x0001 // not referenced
#define STR_HELP                                             0x0002
```

If you execute our application you would get:
```
FS0:\> HIIStringsMan.efi -?
HIIStringsMan application.
```

# Expand our manual

Let's try to add all possible sections to our 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"
```

If you build and execute our app now, you would get:
```
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.
```

As you can see not all section were printed. Let's find out why.

First of all let's investigate what happens when we add `-?` to our command. If you look at the shell sources you'll see that if shell find `-?` as one of the command arguments it redirects command and all the rest arguments to the `help` command https://github.com/tianocore/edk2/blob/master/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;
      }
  ...
}
```

You can verify that result would be the same if you use `help` command directly to our program:
```
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.
```

Now look at the `help` command source code https://github.com/tianocore/edk2/blob/master/ShellPkg/Library/UefiShellLevel3CommandsLib/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);
        }
  ...
}
```

Here you can see how `help` parses its incoming arguments:
- by default the sections NAME, SYNOPSIS, OPTIONS, DESCRIPTION, EXAMPLES are printed,
- it is possible to print particular section with a `-section <SECTION NAME>` argument,
- it is possible to print all sections by supplying `-verbose` or `-v` argument

You can verify this in 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.
```
The same goes for the `-?`:
```
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.
```

# How the ShellManParser is called

We've started this lesson with the assumption that `ShellManParser` will parse our application strings. Let's investigate how `help` program would end up using this module.


In the end `ShellCommandRunHelp` would call `ShellPrintHelp` function https://github.com/tianocore/edk2/blob/master/ShellPkg/Library/UefiShellLevel3CommandsLib/Help.c:
```
SHELL_STATUS
EFIAPI
ShellCommandRunHelp (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
  ...
  Status = ShellPrintHelp(CommandToGetHelpOn, SectionToGetHelpOn, FALSE);
  ...
}
```

This function is defined https://github.com/tianocore/edk2/blob/master/ShellPkg/Library/UefiShellLib/UefiShellLib.c. It mainly call the `GetHelpText` function from the `EFI_SHELL_PROTOCOL`:
```
EFI_STATUS
EFIAPI
ShellPrintHelp (
  IN CONST CHAR16     *CommandToGetHelpOn,
  IN CONST CHAR16     *SectionToGetHelpOn,
  IN BOOLEAN          PrintCommandText
  )
{
  ...
  Status = gEfiShellProtocol->GetHelpText (CommandToGetHelpOn, SectionToGetHelpOn, &OutText);
  ...
}
```


Here is a description for this function from the UEFI Shell Specification:
```
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. 
```

Prototype for the `EFI_SHELL_PROTOCOL` structure in edk2 is in the https://github.com/tianocore/edk2/blob/master/MdePkg/Include/Protocol/Shell.h
```
typedef struct _EFI_SHELL_PROTOCOL {
  ...
  EFI_SHELL_GET_HELP_TEXT                   GetHelpText;
  ...
} EFI_SHELL_PROTOCOL;
```

And initialization for this protocol is in the https://github.com/tianocore/edk2/blob/master/ShellPkg/Application/Shell/ShellProtocol.c
```
EFI_SHELL_PROTOCOL         mShellProtocol = {
  ...
  EfiShellGetHelpText,
  ...
}
```

This function is defined in the same file above, and if you look at its definition you'll see that it is calling function from the `ProcessManFile` module.