ふふーん

一緒にやりたくなったのよ!あなたと、調合を!

OVMF_VARSをパースするやつ

OVMF_VARS.fdがどういうフォーマットで値を保存しているのかみてみた。

$ hexdump -C OVMF_VARS.fd | head -n20
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  8d 2b f1 ff 96 76 8b 4c  a9 85 27 47 07 5b 4f 50  |.+...v.L..'G.[OP|
00000020  00 00 02 00 00 00 00 00  5f 46 56 48 ff fe 04 00  |........_FVH....|
00000030  48 00 19 f9 00 00 00 02  20 00 00 00 00 10 00 00  |H....... .......|
00000040  00 00 00 00 00 00 00 00  78 2c f3 aa 7b 94 9a 43  |........x,..{..C|
00000050  a1 80 2e 14 4e c3 77 92  b8 df 00 00 5a fe 00 00  |....N.w.....Z...|
00000060  00 00 00 00 aa 55 3c 00  07 00 00 00 00 00 00 00  |.....U<.........|
00000070  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000080  00 00 00 00 00 00 00 00  08 00 00 00 04 00 00 00  |................|
00000090  11 40 70 eb 02 14 d3 11  8e 77 00 a0 c9 69 72 3b  |.@p......w...ir;|
000000a0  4d 00 54 00 43 00 00 00  01 00 00 00 aa 55 3c 00  |M.T.C........U<.|
000000b0  07 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000c0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000000d0  14 00 00 00 02 00 00 00  61 df e4 8b ca 93 d2 11  |........a.......|
000000e0  aa 0d 00 e0 98 03 2b 8c  42 00 6f 00 6f 00 74 00  |......+.B.o.o.t.|
000000f0  4f 00 72 00 64 00 65 00  72 00 00 00 00 00 ff ff  |O.r.d.e.r.......|
00000100  aa 55 3f 00 07 00 00 00  00 00 00 00 00 00 00 00  |.U?.............|
00000110  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000120  00 00 00 00 12 00 00 00  3e 00 00 00 61 df e4 8b  |........>...a...|
00000130  ca 93 d2 11 aa 0d 00 e0  98 03 2b 8c 42 00 6f 00  |..........+.B.o.|

なんかググったら出てきた http://www.linux-kvm.org/downloads/lersek/ovmf-whitepaper-c770f8c.txt とか EDK2とかをみつつパースする。

最初のやつをみると、変数保存してる領域は128KBで、後半の64KBはスペアエリアになっているので実質最初の64KBだけ見ればいいらしい。 64KBのうちVariable storeが56KBでその後ろにEvent logとWorking blockが4KBずつある。

とりあえずSetVariableの実装を探す。

// MdeModulePkg/Universal/Variable/RuntimeDxe/VariableDxe.c

...

EFI_STATUS
EFIAPI
VariableServiceInitialize (
  IN EFI_HANDLE                         ImageHandle,
  IN EFI_SYSTEM_TABLE                   *SystemTable
  )
{
  EFI_STATUS                            Status;
  EFI_EVENT                             ReadyToBootEvent;
  EFI_EVENT                             EndOfDxeEvent;

...

  SystemTable->RuntimeServices->GetVariable         = VariableServiceGetVariable;
  SystemTable->RuntimeServices->GetNextVariableName = VariableServiceGetNextVariableName;
  SystemTable->RuntimeServices->SetVariable         = VariableServiceSetVariable;
  SystemTable->RuntimeServices->QueryVariableInfo   = VariableServiceQueryVariableInfo;

...

}

みたいなのがあったのでVariableServiceSetVariableを探す。

// MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c

EFI_STATUS
EFIAPI
VariableServiceSetVariable (
  IN CHAR16                  *VariableName,
  IN EFI_GUID                *VendorGuid,
  IN UINT32                  Attributes,
  IN UINTN                   DataSize,
  IN VOID                    *Data
  )
{

...

}

なんかいろいろやっているが、実際に変数を書いているのはUpdateVariableっぽい。

// MdeModulePkg/Universal/Variable/RuntimeDxe/Variable.c

EFI_STATUS
UpdateVariable (
  IN      CHAR16                      *VariableName,
  IN      EFI_GUID                    *VendorGuid,
  IN      VOID                        *Data,
  IN      UINTN                       DataSize,
  IN      UINT32                      Attributes      OPTIONAL,
  IN      UINT32                      KeyIndex        OPTIONAL,
  IN      UINT64                      MonotonicCount  OPTIONAL,
  IN OUT  VARIABLE_POINTER_TRACK      *CacheVariable,
  IN      EFI_TIME                    *TimeStamp      OPTIONAL
  )
{

...

}

こっちもなんかいろいろやっている。 今知りたかったのは変数をどういうフォーマットで書き出しているかというところだけなのでそれらしい構造体を探す。

// MdeModulePkg/Include/Guid/VariableFormat.h

///
/// Variable Store region header.
///
typedef struct {
  ///
  /// Variable store region signature.
  ///
  EFI_GUID  Signature;
  ///
  /// Size of entire variable store,
  /// including size of variable store header but not including the size of FvHeader.
  ///
  UINT32  Size;
  ///
  /// Variable region format state.
  ///
  UINT8   Format;
  ///
  /// Variable region healthy state.
  ///
  UINT8   State;
  UINT16  Reserved;
  UINT32  Reserved1;
} VARIABLE_STORE_HEADER;

///
/// Single Variable Data Header Structure.
///
typedef struct {
  ///
  /// Variable Data Start Flag.
  ///
  UINT16      StartId;
  ///
  /// Variable State defined above.
  ///
  UINT8       State;
  UINT8       Reserved;
  ///
  /// Attributes of variable defined in UEFI specification.
  ///
  UINT32      Attributes;
  ///
  /// Size of variable null-terminated Unicode string name.
  ///
  UINT32      NameSize;
  ///
  /// Size of the variable data without this header.
  ///
  UINT32      DataSize;
  ///
  /// A unique identifier for the vendor that produces and consumes this varaible.
  ///
  EFI_GUID    VendorGuid;
} VARIABLE_HEADER;

VARIABLE_STORE_HEADERがVariable storeのヘッダーで、OVMF_VARS.fdのファイル先頭がこれ。 それぞれの変数のヘッダーがVARIABLE_STORE_HEADER。

ただし、hexdumpしたやつとこの構造体が合わなくて、実際にはAttributesの後ろによくわからん28バイトが入っている。 VARIABLE_STORE_HEADERのほうも実際のファイル先頭と合わなくて、実際にはOvmfPkg/VarStore.fdf.incを参照したほうがよさそう。

VARIABLE_HEADERのStateは最初に変数が書き込まれるときにVAR_ADDED(0x3f)に設定されて、削除されるときにVAR_IN_DELETED_TRANSITION(0xfe)とかVAR_DELETED(0xfd)とかとビットの論理積をとっている。 Stateが0x3fのやつは生きてて0x3cのやつは削除済み。

ということで、雑にパースするやつ。

#!/usr/bin/python3
"""Parse OVMF_VARS.fd."""

import ctypes
import logging


logger = logging.getLogger(__name__)


VARIABLE_DATA = 0x55aa

VAR_IN_DELETED_TRANSITION = 0xfe
VAR_DELETED = 0xfd
VAR_HEADER_VALID_ONLY = 0x7f
VAR_ADDED = 0x3f


class VariableStoreHeader(ctypes.Structure):
    """Variable store header."""

    _pack_ = 1
    _fields_ = [('efi_firmware_volume_header', ctypes.c_ubyte * 16),
                ('filesystem_guid', ctypes.c_ubyte * 16),
                ('fv_length', ctypes.c_ulonglong),
                ('signature', ctypes.c_ubyte * 8),
                ('header_length', ctypes.c_ushort),
                ('checksum', ctypes.c_ushort),
                ('ext_header_offset', ctypes.c_uint),
                ('block_count', ctypes.c_uint),
                ('bytes_per_block', ctypes.c_uint),
                ('block_map_end', ctypes.c_uint * 2),
                ('efi_authenticated_variable_guid', ctypes.c_ubyte * 16),
                ('nv_storage_variable_size', ctypes.c_uint),
                ('formatted', ctypes.c_ubyte),
                ('healthy', ctypes.c_ubyte),
                ('reserved1', ctypes.c_ushort),
                ('reserved2', ctypes.c_uint)]


class VariableHeader(ctypes.Structure):
    """Variable header."""

    _pack_ = 1
    _fields_ = [('start_id', ctypes.c_ushort),
                ('state', ctypes.c_ubyte),
                ('reserved', ctypes.c_ubyte),
                ('attributes', ctypes.c_uint),
                ('padding', ctypes.c_ubyte * 28),
                ('name_size', ctypes.c_uint),
                ('data_size', ctypes.c_uint),
                ('vendor_guid', ctypes.c_byte * 16)]


def parse_header():
    """Parse OVMF_VARS header."""
    sz = ctypes.sizeof(VariableStoreHeader)
    logger.debug('store header size: 0x{:x}'.format(sz))

    with open('OVMF_VARS.fd', 'rb') as f:
        b = f.read(sz)
        header = VariableStoreHeader.from_buffer_copy(b)
        logger.debug(' '.join(map(lambda x: '{:02x}'.format(x),
                                  header.filesystem_guid)))


def parse_variables():
    """Parse variables."""
    sz = ctypes.sizeof(VariableHeader)
    logger.debug('variable header size: 0x{:x}'.format(sz))

    with open('OVMF_VARS.fd', 'rb') as f:
        # skip variable store header
        f.seek(ctypes.sizeof(VariableStoreHeader))

        while True:
            deleted = False

            b = f.read(sz)
            header = VariableHeader.from_buffer_copy(b)

            logger.debug('start id: {:x}'.format(header.start_id))
            logger.debug('state: {:x}'.format(header.state))
            logger.debug('attributes: {:x}'.format(header.attributes))
            logger.debug('name size: {:x}'.format(header.name_size))
            logger.debug('data size: {:x}'.format(header.data_size))

            # end of variables
            if header.start_id != VARIABLE_DATA:
                break

            if header.state != VAR_ADDED:
                deleted = True

            # ???
            if any(map(lambda x: bool(x), header.padding)):
                logger.warning('found nonzero value in padding.')
                logger.warning(' '.join(map(lambda x: '{:02x}'.format(x),
                                            header.padding)))

            b = f.read(header.name_size)
            namelen = header.name_size // 2
            char16str = (ctypes.c_ushort * namelen).from_buffer_copy(b)
            char8str = (ctypes.c_char * namelen)(*char16str)
            name = ctypes.cast(char8str, ctypes.c_char_p).value.decode('ascii')
            if not deleted:
                logger.info('name: {}'.format(name))

            b = f.read(header.data_size)
            if not deleted:
                logger.info(' '.join(map(lambda x: '{:02x}'.format(x), b)))

            # aligned by 4 bytes
            pos = f.tell()
            if pos % 4 != 0:
                f.seek(4 - (pos % 4), 1)


def main():
    """Execute main routine."""
    logger.addHandler(logging.StreamHandler())
    logger.setLevel(logging.INFO)

    parse_header()
    parse_variables()


if __name__ == '__main__':
    main()

おわり。