Skip to content

Commit

Permalink
Feature implementation for EFI variable data parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
gahan9 committed Mar 22, 2024
1 parent 4a6f551 commit 589aab1
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 3 deletions.
10 changes: 8 additions & 2 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
History
=======

2.0.5 (2024-03-21)
------------------
- Feature Improvements:
- Base implementation for parse EFI variable data


2.0.4 (2024-02-28)
------------------
- Feature Improvements:
- JSON output capability has been introduced to enhance the online mode operation output.
- JSON output capability has been introduced to enhance the online mode operation output.

- Bug Fixes:
- Addressed an issue with CSV generation in the generate_csv() function, ensuring smoother functionality.

Expand Down
23 changes: 23 additions & 0 deletions parse_bin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import os

from xmlcli import XmlCli as cli
from xmlcli.common import bios_fw_parser

workspace_dir = r"D:\PublicShare\gahan\_debug\NVL_bios_data_bin"
bin_file = os.path.join(workspace_dir, "BIOS_NVL_S_Internal_0462.00_Dispatch_VS__PreProd.rom")
# bin_file = os.path.join(workspace_dir, "MTL_FSPWRAPPER_3184_01_R.rom")
output_xml = f"{bin_file}.xml"
output_json = f"{bin_file}.json"

uefi_parser = bios_fw_parser.UefiParser(bin_file=bin_file,
parsing_level=0,
base_address=0x0,
guid_to_store=[cli.fwp.gBiosKnobsDataBinGuid]
)
# parse binary
output_dict = uefi_parser.parse_binary()
output_dict = uefi_parser.sort_output_fv(output_dict)
# write content to json file
uefi_parser.write_result_to_file(output_json, output_dict=output_dict)

cli.savexml(output_xml, bin_file)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "xmlcli"
version = "2.0.2"
version = "2.0.5"
description = "UFFAF - UEFI Firmware Foundational Automation Framework (formerly Xml-Cli)"
authors = ["Gahan Saraya <[email protected]>"]
maintainers = ["Intel <[email protected]>"]
Expand Down
33 changes: 33 additions & 0 deletions src/xmlcli/common/bios_fw_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@

FirmwareVolumeGuid = namedtuple("FirmwareVolumeGuid", ["name", "guid", "method", "description"])

RAW_SECTION_EFI_INITIAL_OFFSET = 0x26


class UefiParser(object):
"""
Expand All @@ -80,7 +82,11 @@ def __init__(self, bin_file, parsing_level=0, **kwargs):
self.parsing_level = utils.PARSING_LEVEL_MAP.get(parsing_level, utils.PARSING_LEVEL_MAP.get(0))
self.user_specified_base_address = kwargs.get("base_address", None)
self.guid_to_store = kwargs.get("guid_to_store", []) # if passed guid_to_store, list of guid then store the respective binary by guid
if self.guid_to_store:
self.guid_to_store = [utils.guid_formatter(guid) for guid in self.guid_to_store]
log.info(f"GUID bins to store: {self.guid_to_store}")
self.parse_efi_variable = kwargs.get("parse_efi_variable", True)
self.efi_variables = {} # this data would be populated if parse_efi_variable set to `True`.
# reformat guids to specific format
self.stored_guids = {utils.guid_formatter(guid): [] for guid in self.guid_to_store}
log.debug(self.stored_guids)
Expand Down Expand Up @@ -465,6 +471,10 @@ def parse_ffs_section(self, buffer, buffer_pointer, end_point, ffs_data, _type,
end = align_buffer + section.get_section_size() # calculate end of the section buffer
# create name for section directory to store data/code parsed within it
section_dir = os.path.join(bin_dir, f"SECTION_0x{align_buffer:x}_to_0x{end:x}")
if ffs_guid in self.guid_to_store and section_tuple.name == "EFI_SECTION_RAW":
self.parse_efi_variable_data(buffer, buffer_pointer=buffer_pointer + RAW_SECTION_EFI_INITIAL_OFFSET,
end_point=end)
print(self.efi_variables)
if section_tuple.is_encapsulated: # encapsulation sections
# value can be 0x1 - EFI_SECTION_COMPRESSION, 0x2 -EFI_SECTION_GUID_DEFINED or 0x3 - EFI_SECTION_DISPOSABLE
nesting_level += 1
Expand Down Expand Up @@ -648,6 +658,29 @@ def read_guid_defined_section(self, buffer, guid, buffer_pointer, section, nesti
)
return result

def parse_efi_variable_data(self, buffer, buffer_pointer=0x0, end_point=0x0):
if not self.parse_efi_variable:
log.info("EFI Variable parsing Skipped")
return False
log.debug(f"=======> offset: 0x{buffer_pointer:X} end: 0x{end_point:X}")
efi_var_struct = structure.efi_variable_structure()
if efi_var_struct.cls_size + buffer_pointer <= end_point:
buffer.seek(buffer_pointer)
efi_var_data = efi_var_struct.read_from(buffer)
if efi_var_data.guid.guid != '00000000-0000-0000-0000000000000000' and efi_var_data.name_length and efi_var_data.data_length:
efi_var_struct = structure.efi_variable_structure(name_length=efi_var_data.name_length,
data_length=efi_var_data.data_length)
if efi_var_struct.cls_size + buffer_pointer <= end_point:
buffer.seek(buffer_pointer)
efi_var_data = efi_var_struct.read_from(buffer)
log.debug(efi_var_data)
_key = f"{efi_var_data.get_name}_{efi_var_data.guid.guid}"
self.efi_variables[_key] = efi_var_data.dump_dict()
return self.parse_efi_variable_data(buffer, buffer_pointer=buffer_pointer + efi_var_data.cls_size, end_point=end_point)
else:
print("No data available to parse")
return False

def sort_output_fv(self, input_dict=None):
from collections import OrderedDict
input_dict = input_dict if input_dict else self.output
Expand Down
41 changes: 41 additions & 0 deletions src/xmlcli/common/structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,47 @@ class FitEntry(utils.StructureHelper):
('Checksum', ctypes.c_uint8), # 1 byte
]


###############################################################################
# EFI Variable Structure
###############################################################################
def efi_variable_structure(name_length=0, data_length=0):
"""Structure for EFI Variable stored in NVRAM region
REF: edk2/BaseTools/Source/C/Include/Protocol/HiiFramework.h
:param name_length: Length data bytes for efi variable name
:param data_length: data size of the given variable
:return:
"""
class EfiVariableStructure(utils.StructureHelper):
_pack_ = 1
_fields_ = [
# read bits
("unknown", ctypes.ARRAY(ctypes.c_uint8, 0x21)), # Unknown size between two structures
## Offset -- 0x47
("State", ctypes.c_uint8), #--> 00
("Attribute", ctypes.c_uint32), #--> 00 00 00 00
("name_length", ctypes.c_uint32), #--> 00 00 00 0C
("data_length", ctypes.c_uint32), #--> 00 00 10 56
("guid", utils.Guid), # 16 Bytes
("name", ctypes.ARRAY(ctypes.c_uint8, name_length)), # 12 Bytes
("data", ctypes.ARRAY(ctypes.c_uint8, data_length)), # 4182 Byes
]

@property
def get_name(self):
return ''.join([chr(i) for i in self.name]).replace("\x00", "")

def get_value(self, name):
if name == "name":
return self.get_name
else:
return super().get_value(name)

return EfiVariableStructure()


# END:STRUCTURES #######################################################################################################


Expand Down
2 changes: 2 additions & 0 deletions src/xmlcli/common/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,8 @@ def array_to_int(val):
val = [f"{_val:0>2x}" if is_integer(_val) else _val.dump_dict() for _idx, _val in enumerate(list(val))]
if flag:
val = hex(int("".join(val[::-1]), 16))
else:
return ""
return val

def get_value(self, name):
Expand Down

0 comments on commit 589aab1

Please sign in to comment.