aboutsummaryrefslogtreecommitdiff
path: root/scripts/bpf_doc.py
diff options
context:
space:
mode:
authorNick Terrell <terrelln@fb.com>2022-12-13 16:21:55 -0800
committerNick Terrell <terrelln@fb.com>2022-12-13 16:21:55 -0800
commit4f2c0a4acffbec01079c28f839422e64ddeff004 (patch)
tree06ada4a8a6d94a94c93944806041b8c994cebfc5 /scripts/bpf_doc.py
parent88a309465b3f05a100c3b81966982c0f9f5d23a6 (diff)
parent830b3c68c1fb1e9176028d02ef86f3cf76aa2476 (diff)
Merge branch 'main' into zstd-linus
Diffstat (limited to 'scripts/bpf_doc.py')
-rwxr-xr-xscripts/bpf_doc.py222
1 files changed, 179 insertions, 43 deletions
diff --git a/scripts/bpf_doc.py b/scripts/bpf_doc.py
index a6403ddf5de7..d5c389df6045 100755
--- a/scripts/bpf_doc.py
+++ b/scripts/bpf_doc.py
@@ -10,6 +10,9 @@ from __future__ import print_function
import argparse
import re
import sys, os
+import subprocess
+
+helpersDocStart = 'Start of BPF helper function descriptions:'
class NoHelperFound(BaseException):
pass
@@ -47,6 +50,10 @@ class Helper(APIElement):
@desc: textual description of the helper function
@ret: description of the return value of the helper function
"""
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.enum_val = None
+
def proto_break_down(self):
"""
Break down helper function protocol into smaller chunks: return type,
@@ -87,21 +94,26 @@ class HeaderParser(object):
self.line = ''
self.helpers = []
self.commands = []
+ self.desc_unique_helpers = set()
+ self.define_unique_helpers = []
+ self.helper_enum_vals = {}
+ self.desc_syscalls = []
+ self.enum_syscalls = []
def parse_element(self):
proto = self.parse_symbol()
- desc = self.parse_desc()
- ret = self.parse_ret()
+ desc = self.parse_desc(proto)
+ ret = self.parse_ret(proto)
return APIElement(proto=proto, desc=desc, ret=ret)
def parse_helper(self):
proto = self.parse_proto()
- desc = self.parse_desc()
- ret = self.parse_ret()
+ desc = self.parse_desc(proto)
+ ret = self.parse_ret(proto)
return Helper(proto=proto, desc=desc, ret=ret)
def parse_symbol(self):
- p = re.compile(' \* ?(.+)$')
+ p = re.compile(' \* ?(BPF\w+)$')
capture = p.match(self.line)
if not capture:
raise NoSyscallCommandFound
@@ -127,16 +139,15 @@ class HeaderParser(object):
self.line = self.reader.readline()
return capture.group(1)
- def parse_desc(self):
+ def parse_desc(self, proto):
p = re.compile(' \* ?(?:\t| {5,8})Description$')
capture = p.match(self.line)
if not capture:
- # Helper can have empty description and we might be parsing another
- # attribute: return but do not consume.
- return ''
+ raise Exception("No description section found for " + proto)
# Description can be several lines, some of them possibly empty, and it
# stops when another subsection title is met.
desc = ''
+ desc_present = False
while True:
self.line = self.reader.readline()
if self.line == ' *\n':
@@ -145,21 +156,24 @@ class HeaderParser(object):
p = re.compile(' \* ?(?:\t| {5,8})(?:\t| {8})(.*)')
capture = p.match(self.line)
if capture:
+ desc_present = True
desc += capture.group(1) + '\n'
else:
break
+
+ if not desc_present:
+ raise Exception("No description found for " + proto)
return desc
- def parse_ret(self):
+ def parse_ret(self, proto):
p = re.compile(' \* ?(?:\t| {5,8})Return$')
capture = p.match(self.line)
if not capture:
- # Helper can have empty retval and we might be parsing another
- # attribute: return but do not consume.
- return ''
+ raise Exception("No return section found for " + proto)
# Return value description can be several lines, some of them possibly
# empty, and it stops when another subsection title is met.
ret = ''
+ ret_present = False
while True:
self.line = self.reader.readline()
if self.line == ' *\n':
@@ -168,44 +182,125 @@ class HeaderParser(object):
p = re.compile(' \* ?(?:\t| {5,8})(?:\t| {8})(.*)')
capture = p.match(self.line)
if capture:
+ ret_present = True
ret += capture.group(1) + '\n'
else:
break
+
+ if not ret_present:
+ raise Exception("No return found for " + proto)
return ret
- def seek_to(self, target, help_message):
+ def seek_to(self, target, help_message, discard_lines = 1):
self.reader.seek(0)
offset = self.reader.read().find(target)
if offset == -1:
raise Exception(help_message)
self.reader.seek(offset)
self.reader.readline()
- self.reader.readline()
+ for _ in range(discard_lines):
+ self.reader.readline()
self.line = self.reader.readline()
- def parse_syscall(self):
+ def parse_desc_syscall(self):
self.seek_to('* DOC: eBPF Syscall Commands',
'Could not find start of eBPF syscall descriptions list')
while True:
try:
command = self.parse_element()
self.commands.append(command)
+ self.desc_syscalls.append(command.proto)
+
except NoSyscallCommandFound:
break
- def parse_helpers(self):
- self.seek_to('* Start of BPF helper function descriptions:',
+ def parse_enum_syscall(self):
+ self.seek_to('enum bpf_cmd {',
+ 'Could not find start of bpf_cmd enum', 0)
+ # Searches for either one or more BPF\w+ enums
+ bpf_p = re.compile('\s*(BPF\w+)+')
+ # Searches for an enum entry assigned to another entry,
+ # for e.g. BPF_PROG_RUN = BPF_PROG_TEST_RUN, which is
+ # not documented hence should be skipped in check to
+ # determine if the right number of syscalls are documented
+ assign_p = re.compile('\s*(BPF\w+)\s*=\s*(BPF\w+)')
+ bpf_cmd_str = ''
+ while True:
+ capture = assign_p.match(self.line)
+ if capture:
+ # Skip line if an enum entry is assigned to another entry
+ self.line = self.reader.readline()
+ continue
+ capture = bpf_p.match(self.line)
+ if capture:
+ bpf_cmd_str += self.line
+ else:
+ break
+ self.line = self.reader.readline()
+ # Find the number of occurences of BPF\w+
+ self.enum_syscalls = re.findall('(BPF\w+)+', bpf_cmd_str)
+
+ def parse_desc_helpers(self):
+ self.seek_to(helpersDocStart,
'Could not find start of eBPF helper descriptions list')
while True:
try:
helper = self.parse_helper()
self.helpers.append(helper)
+ proto = helper.proto_break_down()
+ self.desc_unique_helpers.add(proto['name'])
except NoHelperFound:
break
+ def parse_define_helpers(self):
+ # Parse FN(...) in #define __BPF_FUNC_MAPPER to compare later with the
+ # number of unique function names present in description and use the
+ # correct enumeration value.
+ # Note: seek_to(..) discards the first line below the target search text,
+ # resulting in FN(unspec) being skipped and not added to self.define_unique_helpers.
+ self.seek_to('#define __BPF_FUNC_MAPPER(FN)',
+ 'Could not find start of eBPF helper definition list')
+ # Searches for one FN(\w+) define or a backslash for newline
+ p = re.compile('\s*FN\((\w+)\)|\\\\')
+ fn_defines_str = ''
+ i = 1 # 'unspec' is skipped as mentioned above
+ while True:
+ capture = p.match(self.line)
+ if capture:
+ fn_defines_str += self.line
+ self.helper_enum_vals[capture.expand(r'bpf_\1')] = i
+ i += 1
+ else:
+ break
+ self.line = self.reader.readline()
+ # Find the number of occurences of FN(\w+)
+ self.define_unique_helpers = re.findall('FN\(\w+\)', fn_defines_str)
+
+ def assign_helper_values(self):
+ seen_helpers = set()
+ for helper in self.helpers:
+ proto = helper.proto_break_down()
+ name = proto['name']
+ try:
+ enum_val = self.helper_enum_vals[name]
+ except KeyError:
+ raise Exception("Helper %s is missing from enum bpf_func_id" % name)
+
+ # Enforce current practice of having the descriptions ordered
+ # by enum value.
+ seen_helpers.add(name)
+ desc_val = len(seen_helpers)
+ if desc_val != enum_val:
+ raise Exception("Helper %s comment order (#%d) must be aligned with its position (#%d) in enum bpf_func_id" % (name, desc_val, enum_val))
+
+ helper.enum_val = enum_val
+
def run(self):
- self.parse_syscall()
- self.parse_helpers()
+ self.parse_desc_syscall()
+ self.parse_enum_syscall()
+ self.parse_desc_helpers()
+ self.parse_define_helpers()
+ self.assign_helper_values()
self.reader.close()
###############################################################################
@@ -235,6 +330,25 @@ class Printer(object):
self.print_one(elem)
self.print_footer()
+ def elem_number_check(self, desc_unique_elem, define_unique_elem, type, instance):
+ """
+ Checks the number of helpers/syscalls documented within the header file
+ description with those defined as part of enum/macro and raise an
+ Exception if they don't match.
+ """
+ nr_desc_unique_elem = len(desc_unique_elem)
+ nr_define_unique_elem = len(define_unique_elem)
+ if nr_desc_unique_elem != nr_define_unique_elem:
+ exception_msg = '''
+The number of unique %s in description (%d) doesn\'t match the number of unique %s defined in %s (%d)
+''' % (type, nr_desc_unique_elem, type, instance, nr_define_unique_elem)
+ if nr_desc_unique_elem < nr_define_unique_elem:
+ # Function description is parsed until no helper is found (which can be due to
+ # misformatting). Hence, only print the first missing/misformatted helper/enum.
+ exception_msg += '''
+The description for %s is not present or formatted correctly.
+''' % (define_unique_elem[nr_desc_unique_elem])
+ raise Exception(exception_msg)
class PrinterRST(Printer):
"""
@@ -251,27 +365,7 @@ class PrinterRST(Printer):
.. Copyright (C) All BPF authors and contributors from 2014 to present.
.. See git log include/uapi/linux/bpf.h in kernel tree for details.
..
-.. %%%LICENSE_START(VERBATIM)
-.. Permission is granted to make and distribute verbatim copies of this
-.. manual provided the copyright notice and this permission notice are
-.. preserved on all copies.
-..
-.. Permission is granted to copy and distribute modified versions of this
-.. manual under the conditions for verbatim copying, provided that the
-.. entire resulting derived work is distributed under the terms of a
-.. permission notice identical to this one.
-..
-.. Since the Linux kernel and libraries are constantly changing, this
-.. manual page may be incorrect or out-of-date. The author(s) assume no
-.. responsibility for errors or omissions, or for damages resulting from
-.. the use of the information contained herein. The author(s) may not
-.. have taken the same level of care in the production of this manual,
-.. which is licensed free of charge, as they might when working
-.. professionally.
-..
-.. Formatted or processed versions of this manual, if unaccompanied by
-.. the source, must acknowledge the copyright and authors of this work.
-.. %%%LICENSE_END
+.. SPDX-License-Identifier: Linux-man-pages-copyleft
..
.. Please do not edit this file. It was generated from the documentation
.. located in file include/uapi/linux/bpf.h of the Linux kernel sources
@@ -295,6 +389,30 @@ class PrinterRST(Printer):
print('')
+ def get_kernel_version(self):
+ try:
+ version = subprocess.run(['git', 'describe'], cwd=linuxRoot,
+ capture_output=True, check=True)
+ version = version.stdout.decode().rstrip()
+ except:
+ try:
+ version = subprocess.run(['make', 'kernelversion'], cwd=linuxRoot,
+ capture_output=True, check=True)
+ version = version.stdout.decode().rstrip()
+ except:
+ return 'Linux'
+ return 'Linux {version}'.format(version=version)
+
+ def get_last_doc_update(self, delimiter):
+ try:
+ cmd = ['git', 'log', '-1', '--pretty=format:%cs', '--no-patch',
+ '-L',
+ '/{}/,/\*\//:include/uapi/linux/bpf.h'.format(delimiter)]
+ date = subprocess.run(cmd, cwd=linuxRoot,
+ capture_output=True, check=True)
+ return date.stdout.decode().rstrip()
+ except:
+ return ''
class PrinterHelpersRST(PrinterRST):
"""
@@ -305,6 +423,7 @@ class PrinterHelpersRST(PrinterRST):
"""
def __init__(self, parser):
self.elements = parser.helpers
+ self.elem_number_check(parser.desc_unique_helpers, parser.define_unique_helpers, 'helper', '__BPF_FUNC_MAPPER')
def print_header(self):
header = '''\
@@ -316,6 +435,8 @@ list of eBPF helper functions
-------------------------------------------------------------------------------
:Manual section: 7
+:Version: {version}
+{date_field}{date}
DESCRIPTION
===========
@@ -348,8 +469,13 @@ kernel at the top).
HELPERS
=======
'''
+ kernelVersion = self.get_kernel_version()
+ lastUpdate = self.get_last_doc_update(helpersDocStart)
+
PrinterRST.print_license(self)
- print(header)
+ print(header.format(version=kernelVersion,
+ date_field = ':Date: ' if lastUpdate else '',
+ date=lastUpdate))
def print_footer(self):
footer = '''
@@ -478,6 +604,7 @@ class PrinterSyscallRST(PrinterRST):
"""
def __init__(self, parser):
self.elements = parser.commands
+ self.elem_number_check(parser.desc_syscalls, parser.enum_syscalls, 'syscall', 'bpf_cmd')
def print_header(self):
header = '''\
@@ -509,6 +636,7 @@ class PrinterHelpers(Printer):
"""
def __init__(self, parser):
self.elements = parser.helpers
+ self.elem_number_check(parser.desc_unique_helpers, parser.define_unique_helpers, 'helper', '__BPF_FUNC_MAPPER')
type_fwds = [
'struct bpf_fib_lookup',
@@ -549,6 +677,10 @@ class PrinterHelpers(Printer):
'struct socket',
'struct file',
'struct bpf_timer',
+ 'struct mptcp_sock',
+ 'struct bpf_dynptr',
+ 'struct iphdr',
+ 'struct ipv6hdr',
]
known_types = {
'...',
@@ -598,6 +730,10 @@ class PrinterHelpers(Printer):
'struct socket',
'struct file',
'struct bpf_timer',
+ 'struct mptcp_sock',
+ 'struct bpf_dynptr',
+ 'struct iphdr',
+ 'struct ipv6hdr',
}
mapped_types = {
'u8': '__u8',
@@ -689,7 +825,7 @@ class PrinterHelpers(Printer):
comma = ', '
print(one_arg, end='')
- print(') = (void *) %d;' % len(self.seen_helpers))
+ print(') = (void *) %d;' % helper.enum_val)
print('')
###############################################################################