Pull request for UEFI sub-system for efi-2021-01-rc4

* Provide a tool to create a file with UEFI variables to preseed UEFI
   variable store.
 * Make size of UEFI variable store configurable.
 * Add man pages for commands 'bootefi' and 'button'.
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEEbcT5xx8ppvoGt20zxIHbvCwFGsQFAl/ffokACgkQxIHbvCwF
 GsRn6Q/+P/7dmaqvatjar+2WxZN3WU1irQnp+p7fo2+QmijkjLvvRcnpjxAk2DTT
 a6xAh/RCRcKQt9nDw1thxJ7v0jUJ5US1sNa2SyrwrDfhQvYxuliflJJlsihV6sg+
 GOBOh0cwePEGRbHg6cm4klLc6jM8m/K4PYR7g+cRnqhSlOp6KE2IEgzUKExN7rp5
 OJJI/jJ3WslF16n37//4+XULAC71PHPhxF0Nj09fMJIsNjuG5F1dLQ3yXGuFse64
 d8/WFQEp3/9JPqfvDJtkJttRyNEDLDnRwzpzfpnJtaQi4fHcHDUO3FVcyscWhZh2
 Hs1ULebd/b8pBYllPv/YSfnteNqaGMviALEW46Bx9ZYc9DQKT9f332JsDoIhsxwA
 +keROkT8IqopFpzVvlCp5Ox+eYiDffy9y+t4RP3eP/V8CFtHhJ3DeWrbiG7X75h4
 2BOH6cdBTeepG79qd7MoxLYOA+931DJ1gl6ZL/AfBOtz4vs2Ne0jlUYkKjSHvthV
 jrhBcyQJcUo2U2RnXxNaSkfN66xOpSw+n6PoVCW0yq/xnJmqz746F+t3xacL9uj6
 HpPziHI1SWLrAWc1JRB/LkWYrN0wGJIcAEoDG21fJ36GBVPqTCObAIOllf9l0+oY
 LUbEev12dj3HUockYfn4AqU4tdiUoQSnf6NJwV72n5gFQxez8GQ=
 =2GYO
 -----END PGP SIGNATURE-----

Merge tag 'efi-2021-01-rc4' of https://gitlab.denx.de/u-boot/custodians/u-boot-efi

Pull request for UEFI sub-system for efi-2021-01-rc4

* Provide a tool to create a file with UEFI variables to preseed UEFI
  variable store.
* Make size of UEFI variable store configurable.
* Add man pages for commands 'bootefi' and 'button'.
This commit is contained in:
Tom Rini 2020-12-20 14:55:59 -05:00
commit 46a4d75203
7 changed files with 598 additions and 1 deletions

View file

@ -680,6 +680,7 @@ S: Maintained
T: git https://gitlab.denx.de/u-boot/custodians/u-boot-efi.git T: git https://gitlab.denx.de/u-boot/custodians/u-boot-efi.git
F: doc/api/efi.rst F: doc/api/efi.rst
F: doc/uefi/* F: doc/uefi/*
F: doc/usage/bootefi.rst
F: drivers/rtc/emul_rtc.c F: drivers/rtc/emul_rtc.c
F: include/capitalization.h F: include/capitalization.h
F: include/charset.h F: include/charset.h
@ -697,6 +698,7 @@ F: test/unicode_ut.c
F: cmd/bootefi.c F: cmd/bootefi.c
F: cmd/efidebug.c F: cmd/efidebug.c
F: cmd/nvedit_efi.c F: cmd/nvedit_efi.c
F: tools/efivar.py
F: tools/file2include.c F: tools/file2include.c
EFI VARIABLES VIA OP-TEE EFI VARIABLES VIA OP-TEE

135
doc/usage/bootefi.rst Normal file
View file

@ -0,0 +1,135 @@
.. SPDX-License-Identifier: GPL-2.0+
.. Copyright 2020, Heinrich Schuchardt <xypron.glpk@gmx.de>
bootefi command
===============
Synopsis
--------
::
bootefi [image_addr] [fdt_addr]
bootefi bootmgr [fdt_addr]
bootefi hello [fdt_addr]
bootefi selftest [fdt_addr]
Description
-----------
The *bootefi* command is used to launch a UEFI binary which can be either of
* UEFI application
* UEFI boot services driver
* UEFI run-time services driver
An operating system requires a hardware description which can either be
presented as ACPI table (CONFIG\_GENERATE\_ACPI\_TABLE=y) or as device-tree
The load address of the device-tree may be provided as parameter *fdt\_addr*. If
this address is not specified, the bootefi command will try to fall back in
sequence to:
* the device-tree specified by environment variable *fdt\_addr*
* the device-tree specified by environment variable *fdtcontroladdr*
The load address of the binary is specified by parameter *image_address*. A
command sequence to run a UEFI application might look like
::
load mmc 0:2 $fdt_addr_r dtb
load mmc 0:1 $kernel_addr_r /EFI/grub/grubaa64.efi
bootefi $kernel_addr_r $fdt_addr_r
The last file loaded defines the image file path in the loaded image protocol.
Hence the executable should always be loaded last.
The value of the environment variable *bootargs* is converted from UTF-8 to
UTF-16 and passed as load options in the loaded image protocol to the UEFI
binary.
Note
UEFI binaries that are contained in FIT images are launched via the
*bootm* command.
UEFI boot manager
'''''''''''''''''
The UEFI boot manager is invoked by the *bootefi bootmgr* sub-command.
Here boot options are defined by UEFI variables with a name consisting of the
letters *Boot* followed by a four digit hexadecimal number, e.g. *Boot0001* or
*BootA03E*. The boot variable defines a label, the device path of the binary to
execute as well as the load options passed in the loaded image protocol.
If the UEFI variable *BootNext* is defined, it specifies the number of the boot
option to execute next. If no binary can be loaded via *BootNext* the variable
*BootOrder* specifies in which sequence boot options shalled be tried.
The values of these variables can be managed using the U-Boot command
*efidebug*.
UEFI hello world application
''''''''''''''''''''''''''''
U-Boot can be compiled with a hello world application that can be launched using
the *bootefi hello* sub-command. A session might look like
::
=> setenv bootargs 'Greetings to the world'
=> bootefi hello
Booting /MemoryMapped(0x0,0x10001000,0x1000)
Hello, world!
Running on UEFI 2.8
Have SMBIOS table
Have device tree
Load options: Greetings to the world
UEFI selftest
'''''''''''''
U-Boot can be compiled with UEFI unit tests. These unit tests are invoked using
the *bootefi selftest* sub-command.
Which unit test is executed is controlled by the environment variable
*efi\_selftest*. If this variable is not set, all unit tests that are not marked
as 'on request' are executed.
To show a list of the available unit tests the value *list* can be used
::
=> setenv efi_selftest list
=> bootefi selftest
Available tests:
'block image transfer' - on request
'block device'
'configuration tables'
...
A single test is selected for execution by setting the *efi\_selftest*
environment variable to match one of the listed identifiers
::
=> setenv efi_selftest 'block image transfer'
=> bootefi selftest
Some of the tests execute the ExitBootServices() UEFI boot service and will not
return to the command line but require a board reset.
Configuration
-------------
To use the *bootefi* command you must specify CONFIG\_CMD\_BOOTEFI=y.
The *bootefi hello* sub-command requries CMD\_BOOTEFI\_HELLO=y.
The *bootefi selftest* sub-command depends on CMD\_BOOTEFI\_SELFTEST=y.
See also
--------
* *bootm* for launching UEFI binaries packed in FIT images
* *booti*, *bootm*, *bootz* for launching a Linux kernel without using the
UEFI sub-system
* *efidebug* for setting UEFI boot variables

64
doc/usage/button.rst Normal file
View file

@ -0,0 +1,64 @@
.. SPDX-License-Identifier: GPL-2.0+
button command
==============
Synopsis
--------
::
button list
button <name>
Description
-----------
The button command is used to retrieve the status of a button. To show the
status of a button with name 'button1' you would issue the command
::
button button1
The status of the button is both written to the console as *ON* or *OFF* and
set in the return value variable *$?* as 0 (true) or 1 (false). To retrieve
the status of a button with name *button1* and to write it to environment
variable *status1* you would execute the commands
::
button button1
setenv status1 $?
A list of all available buttons and their status can be displayed using
::
button list
If a button device has not been probed yet, its status will be shown as
*<inactive>* in the list.
Configuration
-------------
To use the button command you must specify CONFIG_CMD_BUTTON=y and enable a
button driver. The available buttons are defined in the device-tree.
Return value
------------
The variable *$?* takes the following values
+---+-----------------------------+
| 0 | ON, the button is pressed |
+---+-----------------------------+
| 1 | OFF, the button is released |
+---+-----------------------------+
| 0 | button list was shown |
+---+-----------------------------+
| 1 | button not found |
+---+-----------------------------+
| 1 | invalid arguments |
+---+-----------------------------+

View file

@ -11,5 +11,7 @@ Shell commands
.. toctree:: .. toctree::
:maxdepth: 1 :maxdepth: 1
bootefi
bootmenu bootmenu
button
pstore pstore

View file

@ -91,7 +91,7 @@ efi_status_t efi_query_variable_info_int(u32 attributes,
#define EFI_VAR_FILE_NAME "ubootefi.var" #define EFI_VAR_FILE_NAME "ubootefi.var"
#define EFI_VAR_BUF_SIZE 0x4000 #define EFI_VAR_BUF_SIZE CONFIG_EFI_VAR_BUF_SIZE
/* /*
* This constant identifies the file format for storing UEFI variables in * This constant identifies the file format for storing UEFI variables in

View file

@ -77,6 +77,20 @@ config EFI_VAR_SEED_FILE
endif endif
config EFI_VAR_BUF_SIZE
int "Memory size of the UEFI variable store"
default 16384
range 4096 2147483647
help
This defines the size in bytes of the memory area reserved for keeping
UEFI variables.
When using StandAloneMM (CONFIG_EFI_MM_COMM_TEE=y) this value should
match the value of PcdFlashNvStorageVariableSize used to compile the
StandAloneMM module.
Minimum 4096, default 16384.
config EFI_GET_TIME config EFI_GET_TIME
bool "GetTime() runtime service" bool "GetTime() runtime service"
depends on DM_RTC depends on DM_RTC

380
tools/efivar.py Executable file
View file

@ -0,0 +1,380 @@
#!/usr/bin/env python3
## SPDX-License-Identifier: GPL-2.0-only
#
# EFI variable store utilities.
#
# (c) 2020 Paulo Alcantara <palcantara@suse.de>
#
import os
import struct
import uuid
import time
import zlib
import argparse
from OpenSSL import crypto
# U-Boot variable store format (version 1)
UBOOT_EFI_VAR_FILE_MAGIC = 0x0161566966456255
# UEFI variable attributes
EFI_VARIABLE_NON_VOLATILE = 0x1
EFI_VARIABLE_BOOTSERVICE_ACCESS = 0x2
EFI_VARIABLE_RUNTIME_ACCESS = 0x4
EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS = 0x10
EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS = 0x20
EFI_VARIABLE_READ_ONLY = 1 << 31
NV_BS = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS
NV_BS_RT = NV_BS | EFI_VARIABLE_RUNTIME_ACCESS
NV_BS_RT_AT = NV_BS_RT | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
DEFAULT_VAR_ATTRS = NV_BS_RT
# vendor GUIDs
EFI_GLOBAL_VARIABLE_GUID = '8be4df61-93ca-11d2-aa0d-00e098032b8c'
EFI_IMAGE_SECURITY_DATABASE_GUID = 'd719b2cb-3d3a-4596-a3bc-dad00e67656f'
EFI_CERT_TYPE_PKCS7_GUID = '4aafd29d-68df-49ee-8aa9-347d375665a7'
WIN_CERT_TYPE_EFI_GUID = 0x0ef1
WIN_CERT_REVISION = 0x0200
var_attrs = {
'NV': EFI_VARIABLE_NON_VOLATILE,
'BS': EFI_VARIABLE_BOOTSERVICE_ACCESS,
'RT': EFI_VARIABLE_RUNTIME_ACCESS,
'AT': EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS,
'RO': EFI_VARIABLE_READ_ONLY,
'AW': EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS,
}
var_guids = {
'EFI_GLOBAL_VARIABLE_GUID': EFI_GLOBAL_VARIABLE_GUID,
'EFI_IMAGE_SECURITY_DATABASE_GUID': EFI_IMAGE_SECURITY_DATABASE_GUID,
}
class EfiStruct:
# struct efi_var_file
var_file_fmt = '<QQLL'
var_file_size = struct.calcsize(var_file_fmt)
# struct efi_var_entry
var_entry_fmt = '<LLQ16s'
var_entry_size = struct.calcsize(var_entry_fmt)
# struct efi_time
var_time_fmt = '<H6BLh2B'
var_time_size = struct.calcsize(var_time_fmt)
# WIN_CERTIFICATE
var_win_cert_fmt = '<L2H'
var_win_cert_size = struct.calcsize(var_win_cert_fmt)
# WIN_CERTIFICATE_UEFI_GUID
var_win_cert_uefi_guid_fmt = var_win_cert_fmt+'16s'
var_win_cert_uefi_guid_size = struct.calcsize(var_win_cert_uefi_guid_fmt)
class EfiVariable:
def __init__(self, size, attrs, time, guid, name, data):
self.size = size
self.attrs = attrs
self.time = time
self.guid = guid
self.name = name
self.data = data
def calc_crc32(buf):
return zlib.crc32(buf) & 0xffffffff
class EfiVariableStore:
def __init__(self, infile):
self.infile = infile
self.efi = EfiStruct()
if os.path.exists(self.infile) and os.stat(self.infile).st_size > self.efi.var_file_size:
with open(self.infile, 'rb') as f:
buf = f.read()
self._check_header(buf)
self.ents = buf[self.efi.var_file_size:]
else:
self.ents = bytearray()
def _check_header(self, buf):
hdr = struct.unpack_from(self.efi.var_file_fmt, buf, 0)
magic, crc32 = hdr[1], hdr[3]
if magic != UBOOT_EFI_VAR_FILE_MAGIC:
print("err: invalid magic number: %s"%hex(magic))
exit(1)
if crc32 != calc_crc32(buf[self.efi.var_file_size:]):
print("err: invalid crc32: %s"%hex(crc32))
exit(1)
def _get_var_name(self, buf):
name = ''
for i in range(0, len(buf) - 1, 2):
if not buf[i] and not buf[i+1]:
break
name += chr(buf[i])
return ''.join([chr(x) for x in name.encode('utf_16_le') if x]), i + 2
def _next_var(self, offs=0):
size, attrs, time, guid = struct.unpack_from(self.efi.var_entry_fmt, self.ents, offs)
data_fmt = str(size)+"s"
offs += self.efi.var_entry_size
name, namelen = self._get_var_name(self.ents[offs:])
offs += namelen
data = struct.unpack_from(data_fmt, self.ents, offs)[0]
# offset to next 8-byte aligned variable entry
offs = (offs + len(data) + 7) & ~7
return EfiVariable(size, attrs, time, uuid.UUID(bytes_le=guid), name, data), offs
def __iter__(self):
self.offs = 0
return self
def __next__(self):
if self.offs < len(self.ents):
var, noffs = self._next_var(self.offs)
self.offs = noffs
return var
else:
raise StopIteration
def __len__(self):
return len(self.ents)
def _set_var(self, guid, name_data, size, attrs, tsec):
ent = struct.pack(self.efi.var_entry_fmt,
size,
attrs,
tsec,
uuid.UUID(guid).bytes_le)
ent += name_data
self.ents += ent
def del_var(self, guid, name, attrs):
offs = 0
while offs < len(self.ents):
var, loffs = self._next_var(offs)
if var.name == name and str(var.guid):
if var.attrs != attrs:
print("err: attributes don't match")
exit(1)
self.ents = self.ents[:offs] + self.ents[loffs:]
return
offs = loffs
print("err: variable not found")
exit(1)
def set_var(self, guid, name, data, size, attrs):
offs = 0
while offs < len(self.ents):
var, loffs = self._next_var(offs)
if var.name == name and str(var.guid) == guid:
if var.attrs != attrs:
print("err: attributes don't match")
exit(1)
# make room for updating var
self.ents = self.ents[:offs] + self.ents[loffs:]
break
offs = loffs
tsec = int(time.time()) if attrs & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS else 0
nd = name.encode('utf_16_le') + b"\x00\x00" + data
# U-Boot variable format requires the name + data blob to be 8-byte aligned
pad = ((len(nd) + 7) & ~7) - len(nd)
nd += bytes([0] * pad)
return self._set_var(guid, nd, size, attrs, tsec)
def save(self):
hdr = struct.pack(self.efi.var_file_fmt,
0,
UBOOT_EFI_VAR_FILE_MAGIC,
len(self.ents) + self.efi.var_file_size,
calc_crc32(self.ents))
with open(self.infile, 'wb') as f:
f.write(hdr)
f.write(self.ents)
def parse_attrs(attrs):
v = DEFAULT_VAR_ATTRS
if attrs:
v = 0
for i in attrs.split(','):
v |= var_attrs[i.upper()]
return v
def parse_data(val, vtype):
if not val or not vtype:
return None, 0
fmt = { 'u8': '<B', 'u16': '<H', 'u32': '<L', 'u64': '<Q' }
if vtype.lower() == 'file':
with open(val, 'rb') as f:
data = f.read()
return data, len(data)
if vtype.lower() == 'str':
data = val.encode('utf-8')
return data, len(data)
if vtype.lower() == 'nil':
return None, 0
i = fmt[vtype.lower()]
return struct.pack(i, int(val)), struct.calcsize(i)
def parse_args(args):
name = args.name
attrs = parse_attrs(args.attrs)
guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID
if name.lower() == 'db' or name.lower() == 'dbx':
name = name.lower()
guid = EFI_IMAGE_SECURITY_DATABASE_GUID
attrs = NV_BS_RT_AT
elif name.lower() == 'pk' or name.lower() == 'kek':
name = name.upper()
guid = EFI_GLOBAL_VARIABLE_GUID
attrs = NV_BS_RT_AT
data, size = parse_data(args.data, args.type)
return guid, name, attrs, data, size
def cmd_set(args):
env = EfiVariableStore(args.infile)
guid, name, attrs, data, size = parse_args(args)
env.set_var(guid=guid, name=name, data=data, size=size, attrs=attrs)
env.save()
def print_var(var):
print(var.name+':')
print(" "+str(var.guid)+' '+''.join([x for x in var_guids if str(var.guid) == var_guids[x]]))
print(" "+'|'.join([x for x in var_attrs if var.attrs & var_attrs[x]])+", DataSize = %s"%hex(var.size))
hexdump(var.data)
def cmd_print(args):
env = EfiVariableStore(args.infile)
if not args.name and not args.guid and not len(env):
return
found = False
for var in env:
if not args.name:
if args.guid and args.guid != str(var.guid):
continue
print_var(var)
found = True
else:
if args.name != var.name or (args.guid and args.guid != str(var.guid)):
continue
print_var(var)
found = True
if not found:
print("err: variable not found")
exit(1)
def cmd_del(args):
env = EfiVariableStore(args.infile)
attrs = parse_attrs(args.attrs)
guid = args.guid if args.guid else EFI_GLOBAL_VARIABLE_GUID
env.del_var(guid, args.name, attrs)
env.save()
def pkcs7_sign(cert, key, buf):
with open(cert, 'r') as f:
crt = crypto.load_certificate(crypto.FILETYPE_PEM, f.read())
with open(key, 'r') as f:
pkey = crypto.load_privatekey(crypto.FILETYPE_PEM, f.read())
PKCS7_BINARY = 0x80
PKCS7_DETACHED = 0x40
PKCS7_NOATTR = 0x100
bio_in = crypto._new_mem_buf(buf)
p7 = crypto._lib.PKCS7_sign(crt._x509, pkey._pkey, crypto._ffi.NULL, bio_in,
PKCS7_BINARY|PKCS7_DETACHED|PKCS7_NOATTR)
bio_out = crypto._new_mem_buf()
crypto._lib.i2d_PKCS7_bio(bio_out, p7)
return crypto._bio_to_string(bio_out)
# UEFI 2.8 Errata B "8.2.2 Using the EFI_VARIABLE_AUTHENTICATION_2 descriptor"
def cmd_sign(args):
guid, name, attrs, data, size = parse_args(args)
attrs |= EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS
efi = EfiStruct()
tm = time.localtime()
etime = struct.pack(efi.var_time_fmt,
tm.tm_year, tm.tm_mon, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec,
0, 0, 0, 0, 0)
buf = name.encode('utf_16_le') + uuid.UUID(guid).bytes_le + attrs.to_bytes(4, byteorder='little') + etime
if data:
buf += data
sig = pkcs7_sign(args.cert, args.key, buf)
desc = struct.pack(efi.var_win_cert_uefi_guid_fmt,
efi.var_win_cert_uefi_guid_size + len(sig),
WIN_CERT_REVISION,
WIN_CERT_TYPE_EFI_GUID,
uuid.UUID(EFI_CERT_TYPE_PKCS7_GUID).bytes_le)
with open(args.outfile, 'wb') as f:
if data:
f.write(etime + desc + sig + data)
else:
f.write(etime + desc + sig)
def main():
ap = argparse.ArgumentParser(description='EFI variable store utilities')
subp = ap.add_subparsers(help="sub-command help")
printp = subp.add_parser('print', help='get/list EFI variables')
printp.add_argument('--infile', '-i', required=True, help='file to save the EFI variables')
printp.add_argument('--name', '-n', help='variable name')
printp.add_argument('--guid', '-g', help='vendor GUID')
printp.set_defaults(func=cmd_print)
setp = subp.add_parser('set', help='set EFI variable')
setp.add_argument('--infile', '-i', required=True, help='file to save the EFI variables')
setp.add_argument('--name', '-n', required=True, help='variable name')
setp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)')
setp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID)
setp.add_argument('--type', '-t', help='variable type (values: file|u8|u16|u32|u64|str)')
setp.add_argument('--data', '-d', help='data or filename')
setp.set_defaults(func=cmd_set)
delp = subp.add_parser('del', help='delete EFI variable')
delp.add_argument('--infile', '-i', required=True, help='file to save the EFI variables')
delp.add_argument('--name', '-n', required=True, help='variable name')
delp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)')
delp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID)
delp.set_defaults(func=cmd_del)
signp = subp.add_parser('sign', help='sign time-based EFI payload')
signp.add_argument('--cert', '-c', required=True, help='x509 certificate filename in PEM format')
signp.add_argument('--key', '-k', required=True, help='signing certificate filename in PEM format')
signp.add_argument('--name', '-n', required=True, help='variable name')
signp.add_argument('--attrs', '-a', help='variable attributes (values: nv,bs,rt,at,ro,aw)')
signp.add_argument('--guid', '-g', help="vendor GUID (default: %s)"%EFI_GLOBAL_VARIABLE_GUID)
signp.add_argument('--type', '-t', required=True, help='variable type (values: file|u8|u16|u32|u64|str|nil)')
signp.add_argument('--data', '-d', help='data or filename')
signp.add_argument('--outfile', '-o', required=True, help='output filename of signed EFI payload')
signp.set_defaults(func=cmd_sign)
args = ap.parse_args()
args.func(args)
def group(a, *ns):
for n in ns:
a = [a[i:i+n] for i in range(0, len(a), n)]
return a
def join(a, *cs):
return [cs[0].join(join(t, *cs[1:])) for t in a] if cs else a
def hexdump(data):
toHex = lambda c: '{:02X}'.format(c)
toChr = lambda c: chr(c) if 32 <= c < 127 else '.'
make = lambda f, *cs: join(group(list(map(f, data)), 8, 2), *cs)
hs = make(toHex, ' ', ' ')
cs = make(toChr, ' ', '')
for i, (h, c) in enumerate(zip(hs, cs)):
print (' {:010X}: {:48} {:16}'.format(i * 16, h, c))
if __name__ == '__main__':
main()