aboutsummaryrefslogtreecommitdiffstats
path: root/Lessons/Lesson_61/README.md
blob: faf7f48397261d74cfc1e8afed1e9939fb94feca (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
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
UEFI shell has a `dmpstore` command that helps to see content of UEFI variables.

If you'll look at the help of the `dmpstore` command you could see one more useful feature that this command presents. With this command it is possible to save UEFI variables to a file and load them back from such files:
```
FS0:\> dmpstore -?
...
DMPSTORE [-all | ([variable] [-guid guid])] [-s file]
DMPSTORE [-all | ([variable] [-guid guid])] [-l file]
...
  -s       - Saves variables to a file.
  -l       - Loads and sets variables from a file.
...
```
Let's try to use this mechanics to modify content of an existing UEFI variable. It can be a useful feature for the debug.

In some earlier lesson we've created the `ShowBootVariables.efi` application that displays boot sources based on the content of UEFU boot variables:
```
FS0:\> ShowBootVariables.efi
Boot0000
UiApp
Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(462CAA21-7614-4503-836E-8AB6F4662331)

Boot0001
UEFI QEMU DVD-ROM QM00003
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)

Boot0002*
EFI Internal Shell
Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(7C04A583-9E3E-4F1C-AD65-E05268D0B4D1)

Boot0003
UEFI QEMU HARDDISK QM00001
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
```

One of the variables that were parsed in this application is a `BootOrder` variable. Just in case you forgot:
```
The BootOrder variable contains an array of UINT16’s that make up an ordered list of the Boot####
options. The first element in the array is the value for the first logical boot option, the second element is
the value for the second logical boot option, etc. The BootOrder order list is used by the firmware’s
boot manager as the default boot order.
```

Print the content of the BootOrder variable
```
FS0:\> dmpstore BootOrder
Variable NV+RT+BS 'EFIGlobalVariable:BootOrder' DataSize = 0x08
  00000000: 00 00 01 00 02 00 03 00-                         *........*
```
This means that the order is:
```
Boot0000
Boot0001
Boot0002
Boot0003
```
Everything is like our `ShowBootVariables` application shows.

With a help of the `dmpstore` command we can dump the content of a `BootOrder` variable to a file:
```
FS0:\> dmpstore BootOrder -s BootOrder.bin
Save variable to file: BootOrder.bin.
Variable NV+RT+BS '8BE4DF61-93CA-11D2-AA0D-00E098032B8C:BootOrder' DataSize = 0x08
```

UEFI shell contains `hexedit` command in itself. With it we can see the content of a created file:

![hexediti_1.png](hexedit_1.png?raw=true "BootOrder.bin before modifications")

`hexedit` is a hex editor, you can see its help message with a `Ctrl+E` command:

![hexedit_help.png](hexedit_help.png?raw=true "hexedit help")

Exit help with `Ctrl-W`.

`dmpstore` command represents each variable with a following structure in a file:
```
{
  UINT32 NameSize;           // Size of the variable name in bytes
  UINT32 DataSize;           // Size of the variable data in bytes
  CHAR16 Name[NameSize/2];   // Variable name in CHAR16
  EFI_GUID Guid;             // Variable GUID
  UINT32 Attributes;         // Variable attributes
  UINT8 Data[DataSize];      // Variable data
  UINT32 Crc;                // CRC32 checksum for the record
}
```

Here is a file content with a highlight for the structure fileds:

![bootorder.png](bootorder.png?raw=true "bootorder")

Let's try to modify the file content changing the boot order to:
```
Boot0001
Boot0000
Boot0002
Boot0003
```

![hexedit_2.png](hexedit_2.png?raw=true "hexedit after modification")

Type `Ctrl+Q` to quit and enter `y` to save our modifications.

If you'll try to load the changed settings you would get an error:
```
FS0:\> dmpstore -l BootOrder.bin
Load and set variables from file: BootOrder.bin.
dmpstore: Incorrect file format.
dmpstore: No matching variables found. Guid 8BE4DF61-93CA-11D2-AA0D-00E098032B8C
```
This is happening because `UINT32 Crc` field of the record is not longer valid for the current record content.

Let's create an application `UpdateDmpstoreDump` to update CRC fields in the `dmpstore` dumps.

Once again as we would parse command shell arguments it is better to create a shell application. We would read and write files, therefore include `ShellLib` to the `LibraryClasses`:
`UefiLessonsPkg/UpdateDmpstoreDump/UpdateDmpstoreDump.inf`:
```
[Defines]
  INF_VERSION                    = 1.25
  BASE_NAME                      = UpdateDmpstoreDump
  FILE_GUID                      = d14fe21b-7dbf-40ff-96cb-5d6f5b63cda6
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 1.0
  ENTRY_POINT                    = ShellCEntryLib

[Sources]
  UpdateDmpstoreDump.c

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

[LibraryClasses]
  UefiLib
  ShellCEntryLib
  ShellLib
```

In the `UefiLessonsPkg/UpdateDmpstoreDump/UpdateDmpstoreDump.c` we start from reading dump file name from the command argument and opening the file with read and write attributes:
```
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>
#include <Library/ShellLib.h>
#include <Library/MemoryAllocationLib.h>

VOID Usage()
{
  Print(L"Recalculate CRCs for dmpstore command dump\n");
  Print(L"\n");
  Print(L"  UpdateDmpstoreDump <filename>\n");
}

INTN EFIAPI ShellAppMain(IN UINTN Argc, IN CHAR16 **Argv)
{
  if (Argc!=2) {
    Usage();
    return EFI_INVALID_PARAMETER;
  }

  SHELL_FILE_HANDLE FileHandle;

  CHAR16* Filename = Argv[1];
  EFI_STATUS Status = ShellOpenFileByName(
    Filename,
    &FileHandle,
    EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
    0
  );
  if (EFI_ERROR(Status)) {
    Print(L"Error! Can't open file %s\n", Filename);
    return Status;
  }

  ...

  Status = ShellCloseFile(&FileHandle);
  if (EFI_ERROR(Status)) {
    Print(L"Can't close file: %r\n", Status);
  }

  return EFI_SUCCESS;
}
```

The dump file can have many records in itself and the size of a record is not a constant, but depends on the record fields. Therefore the only way to fix all record CRCs is to step throught the file records until the file end:
```
  UINT64 FileSize;
  Status = ShellGetFileSize(FileHandle, &FileSize);
  if (EFI_ERROR(Status)) {
    Status = ShellCloseFile(&FileHandle);
    return SHELL_DEVICE_ERROR;
  }

  UINT64 FilePos = 0;
  while (FilePos < FileSize) {
    ...
  }
```

Here is a code to read record data and calculate its CRC32. It is pretty similar to the one that `dmpstore` command has it its `LoadVariablesFromFilefunction` (https://github.com/tianocore/edk2/blob/master/ShellPkg/Library/UefiShellDebug1CommandsLib/DmpStore.c):
```
UINTN ToReadSize;
UINT32 NameSize;
ToReadSize = sizeof(NameSize);
Status = ShellReadFile(FileHandle, &ToReadSize, &NameSize);
if (EFI_ERROR(Status) || (ToReadSize != sizeof(NameSize))) {
  Status = SHELL_VOLUME_CORRUPTED;
  break;
}
FilePos += ToReadSize;

UINT32 DataSize;
ToReadSize = sizeof(DataSize);
Status = ShellReadFile(FileHandle, &ToReadSize, &DataSize);
if (EFI_ERROR(Status) || (ToReadSize != sizeof(DataSize))) {
  Status = SHELL_VOLUME_CORRUPTED;
  break;
}
FilePos += ToReadSize;

UINTN RemainingSize = NameSize +
                      sizeof(EFI_GUID) +
                      sizeof(UINT32) +
                      DataSize;
UINT8* Buffer = AllocatePool(sizeof(NameSize) + sizeof(DataSize) + RemainingSize);
if (Buffer == NULL) {
  Status = SHELL_OUT_OF_RESOURCES;
  break;
}

*(UINT32*)Buffer = NameSize;
*((UINT32*)Buffer + 1) = DataSize;

ToReadSize = RemainingSize;
Status = ShellReadFile(FileHandle, &ToReadSize, (UINT32*)Buffer + 2);
if (EFI_ERROR(Status) || (ToReadSize != RemainingSize)) {
  Status = SHELL_VOLUME_CORRUPTED;
  FreePool (Buffer);
  break;
}
FilePos += ToReadSize;


UINT32 Crc32;
gBS->CalculateCrc32 (
   Buffer,
   sizeof(NameSize) + sizeof(DataSize) + RemainingSize,
   &Crc32
);

...

FreePool(Buffer);
```

To calculate the CRC32 checksum here we use `EFI_BOOT_SERVICES.CalculateCrc32()` function:
```
EFI_BOOT_SERVICES.CalculateCrc32()

Summary:
Computes and returns a 32-bit CRC for a data buffer.

Prototype:
typedef
EFI_STATUS
(EFIAPI *EFI_CALCULATE_CRC32)
 IN VOID *Data,
 IN UINTN DataSize,
 OUT UINT32 *Crc32
 );

Parameters:
Data 		A pointer to the buffer on which the 32-bit CRC is to be computed.
DataSize 	The number of bytes in the buffer Data.
Crc32 		The 32-bit CRC that was computed for the data buffer specified by Data and DataSize.

Description:
This function computes the 32-bit CRC for the data buffer specified by Data and DataSize. If the 32-bit CRC is computed, then it is returned in Crc32 and EFI_SUCCESS is returned.
```

When we have our CRC32 checksum we can update file content with a help of a `ShellWriteFile` function:
```
UINTN ToWriteSize = sizeof(Crc32);
Status = ShellWriteFile(
  FileHandle,
  &ToWriteSize,
  &Crc32
);
if (EFI_ERROR(Status) || (ToWriteSize != sizeof(Crc32))) {
  Print(L"Error! Not all data was written\n");
  FreePool(Buffer);
  break;
}
FilePos += ToWriteSize;
```

Build our application and use it on the `dmpstore` dump:
```
FS0:\> UpdateDmpstoreDump.efi BootOrder.bin
```

If you look at the file content again you would see that the CRC field has changed.

![hexedit_3.png](hexedit_3.png?raw=true "hexedit after CRC modification")

Now `dmpstore -l` would finish without errors:
```
FS0:\> dmpstore -l BootOrder.bin
Load and set variables from file: BootOrder.bin.
Variable NV+RT+BS '8BE4DF61-93CA-11D2-AA0D-00E098032B8C:BootOrder' DataSize = 0x08
```

You can see that variable content was modified:
```
FS0:\> dmpstore BootOrder
Variable NV+RT+BS 'EFIGlobalVariable:BootOrder' DataSize = 0x08
  00000000: 01 00 00 00 02 00 03 00-                         *........*
```

You can also use our `ShowBootVariables.efi` application to see the changes:
```
FS0:\> ShowBootVariables.efi
Boot0001
UEFI QEMU DVD-ROM QM00003
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)

Boot0000
UiApp
Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(462CAA21-7614-4503-836E-8AB6F4662331)

Boot0002*
EFI Internal Shell
Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(7C04A583-9E3E-4F1C-AD65-E05268D0B4D1)

Boot0003
UEFI QEMU HARDDISK QM00001
PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
```
__________________________

We can also verify that our program works in case when there are multiple variables in the dump file.

Just in case you have some persistent variables from the previous lesson delete all the variables under our GUID with a `dmpstore -d -guid <GUID>` command:
```
FS0:\> dmpstore -d -guid bb2a829f-7943-4691-a03a-f1f48519d7e6
dmpstore: No matching variables found. Guid 7C04A583-9E3E-4F1C-AD65-E05268D0B4D1

```

Create new variables and save them to a file:
```
FS0:\> SetVariableExample.efi HelloVar nb "Hello World"
Variable HelloVar was successfully changed
FS0:\> SetVariableExample.efi ByeVar nbr "Bye World"
Variable ByeVar was successfully changed
FS0:\> dmpstore -guid bb2a829f-7943-4691-a03a-f1f48519d7e6 -s MyVar.bin
Save variable to file: MyVar.bin.
Variable NV+RT+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:ByeVar' DataSize = 0x16
Variable NV+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:HelloVar' DataSize = 0x1A
```

Use hexedit to modify `World` string in both records of a dump file. Here I've just increased each letter code with 1.

Before:
```
00000000 0E 00 00 00 14 00 00 00  42 00 79 00 65 00 56 00  ........B.y.e.V.
00000010 61 00 72 00 00 00 9F 82  2A BB 43 79 91 46 A0 3A  a.r...??*?Cy?F?:
00000020 F1 F4 85 19 D7 E6 07 00  00 00 42 00 79 00 65 00  ???.??....B.y.e.
00000030 20 00 57 00 6F 00 72 00  6C 00 64 00 00 00 EC 24   .W.o.r.l.d...?$
00000040 78 CD 12 00 00 00 18 00  00 00 48 00 65 00 6C 00  x?........H.e.l.
00000050 6C 00 6F 00 56 00 61 00  72 00 00 00 9F 82 2A BB  l.o.V.a.r...??*?
00000060 43 79 91 46 A0 3A F1 F4  85 19 D7 E6 03 00 00 00  Cy?F?:???.??....
00000070 48 00 65 00 6C 00 6C 00  6F 00 20 00 57 00 6F 00  H.e.l.l.o. .W.o.
00000080 72 00 6C 00 64 00 00 00  97 82 10 13              r.l.d...??..
```
After:
```
00000000 0E 00 00 00 14 00 00 00  42 00 79 00 65 00 56 00  ........B.y.e.V.
00000010 61 00 72 00 00 00 9F 82  2A BB 43 79 91 46 A0 3A  a.r...??*?Cy?F?:
00000020 F1 F4 85 19 D7 E6 07 00  00 00 42 00 79 00 65 00  ???.??....B.y.e.
00000030 20 00 58 00 70 00 73 00  6D 00 65 00 00 00 EC 24   .X.p.s.m.e...?$
00000040 78 CD 12 00 00 00 18 00  00 00 48 00 65 00 6C 00  x?........H.e.l.
00000050 6C 00 6F 00 56 00 61 00  72 00 00 00 9F 82 2A BB  l.o.V.a.r...??*?
00000060 43 79 91 46 A0 3A F1 F4  85 19 D7 E6 03 00 00 00  Cy?F?:???.??....
00000070 48 00 65 00 6C 00 6C 00  6F 00 20 00 58 00 70 00  H.e.l.l.o. .X.p.
00000080 73 00 6D 00 65 00 00 00  97 82 10 13              s.m.e...??..
```

Use our program to update checksums:
```
FS0:\> UpdateDmpstoreDump.efi MyVar.bin
```

Now you can verify that our new dump indeed have changed both variables content:
```
FS0:\> dmpstore -guid bb2a829f-7943-4691-a03a-f1f48519d7e6
Variable NV+RT+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:ByeVar' DataSize = 0x14
  00000000: 42 00 79 00 65 00 20 00-57 00 6F 00 72 00 6C 00  *B.y.e. .W.o.r.l.*
  00000010: 64 00 00 00                                      *d...*
Variable NV+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:HelloVar' DataSize = 0x18
  00000000: 48 00 65 00 6C 00 6C 00-6F 00 20 00 57 00 6F 00  *H.e.l.l.o. .W.o.*
  00000010: 72 00 6C 00 64 00 00 00-                         *r.l.d...*

FS0:\> dmpstore -guid bb2a829f-7943-4691-a03a-f1f48519d7e6 -l MyVar.bin
Load and set variables from file: MyVar.bin.
Variable NV+RT+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:ByeVar' DataSize = 0x14
Variable NV+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:HelloVar' DataSize = 0x18

FS0:\> dmpstore -guid bb2a829f-7943-4691-a03a-f1f48519d7e6
Variable NV+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:HelloVar' DataSize = 0x18
  00000000: 48 00 65 00 6C 00 6C 00-6F 00 20 00 58 00 70 00  *H.e.l.l.o. .X.p.*
  00000010: 73 00 6D 00 65 00 00 00-                         *s.m.e...*
Variable NV+RT+BS 'BB2A829F-7943-4691-A03A-F1F48519D7E6:ByeVar' DataSize = 0x14
  00000000: 42 00 79 00 65 00 20 00-58 00 70 00 73 00 6D 00  *B.y.e. .X.p.s.m.*
  00000010: 65 00 00 00                                      *e...*
```

Keep in mind that if you change size of the variable data, you need to change the `UINT32 DataSize` field as well.