File Index
Files and directories
Sign in
FILE

bpf_doc.py

File preview with syntax highlighting.

linux-6.19.14/scripts/bpf_doc.py PYTHON
   1#!/usr/bin/env python3
   2# SPDX-License-Identifier: GPL-2.0-only
   3#
   4# Copyright (C) 2018-2019 Netronome Systems, Inc.
   5# Copyright (C) 2021 Isovalent, Inc.
   6
   7# In case user attempts to run with Python 2.
   8from __future__ import print_function
   9
  10import argparse
  11import json
  12import re
  13import sys, os
  14import subprocess
  15
  16helpersDocStart = 'Start of BPF helper function descriptions:'
  17
  18class NoHelperFound(BaseException):
  19    pass
  20
  21class NoSyscallCommandFound(BaseException):
  22    pass
  23
  24class ParsingError(BaseException):
  25    def __init__(self, line='<line not provided>', reader=None):
  26        if reader:
  27            BaseException.__init__(self,
  28                                   'Error at file offset %d, parsing line: %s' %
  29                                   (reader.tell(), line))
  30        else:
  31            BaseException.__init__(self, 'Error parsing line: %s' % line)
  32
  33
  34class APIElement(object):
  35    """
  36    An object representing the description of an aspect of the eBPF API.
  37    @proto: prototype of the API symbol
  38    @desc: textual description of the symbol
  39    @ret: (optional) description of any associated return value
  40    """
  41    def __init__(self, proto='', desc='', ret=''):
  42        self.proto = proto
  43        self.desc = desc
  44        self.ret = ret
  45
  46    def to_dict(self):
  47        return {
  48            'proto': self.proto,
  49            'desc': self.desc,
  50            'ret': self.ret
  51        }
  52
  53
  54class Helper(APIElement):
  55    """
  56    An object representing the description of an eBPF helper function.
  57    @proto: function prototype of the helper function
  58    @desc: textual description of the helper function
  59    @ret: description of the return value of the helper function
  60    """
  61    def __init__(self, proto='', desc='', ret='', attrs=[]):
  62        super().__init__(proto, desc, ret)
  63        self.attrs = attrs
  64        self.enum_val = None
  65
  66    def proto_break_down(self):
  67        """
  68        Break down helper function protocol into smaller chunks: return type,
  69        name, distincts arguments.
  70        """
  71        arg_re = re.compile(r'((\w+ )*?(\w+|...))( (\**)(\w+))?$')
  72        res = {}
  73        proto_re = re.compile(r'(.+) (\**)(\w+)\(((([^,]+)(, )?){1,5})\)$')
  74
  75        capture = proto_re.match(self.proto)
  76        res['ret_type'] = capture.group(1)
  77        res['ret_star'] = capture.group(2)
  78        res['name']     = capture.group(3)
  79        res['args'] = []
  80
  81        args    = capture.group(4).split(', ')
  82        for a in args:
  83            capture = arg_re.match(a)
  84            res['args'].append({
  85                'type' : capture.group(1),
  86                'star' : capture.group(5),
  87                'name' : capture.group(6)
  88            })
  89
  90        return res
  91
  92    def to_dict(self):
  93        d = super().to_dict()
  94        d["attrs"] = self.attrs
  95        d.update(self.proto_break_down())
  96        return d
  97
  98
  99ATTRS = {
 100    '__bpf_fastcall': 'bpf_fastcall'
 101}
 102
 103
 104class HeaderParser(object):
 105    """
 106    An object used to parse a file in order to extract the documentation of a
 107    list of eBPF helper functions. All the helpers that can be retrieved are
 108    stored as Helper object, in the self.helpers() array.
 109    @filename: name of file to parse, usually include/uapi/linux/bpf.h in the
 110               kernel tree
 111    """
 112    def __init__(self, filename):
 113        self.reader = open(filename, 'r')
 114        self.line = ''
 115        self.helpers = []
 116        self.commands = []
 117        self.desc_unique_helpers = set()
 118        self.define_unique_helpers = []
 119        self.helper_enum_vals = {}
 120        self.helper_enum_pos = {}
 121        self.desc_syscalls = []
 122        self.enum_syscalls = []
 123
 124    def parse_element(self):
 125        proto    = self.parse_symbol()
 126        desc     = self.parse_desc(proto)
 127        ret      = self.parse_ret(proto)
 128        return APIElement(proto=proto, desc=desc, ret=ret)
 129
 130    def parse_helper(self):
 131        proto    = self.parse_proto()
 132        desc     = self.parse_desc(proto)
 133        ret      = self.parse_ret(proto)
 134        attrs    = self.parse_attrs(proto)
 135        return Helper(proto=proto, desc=desc, ret=ret, attrs=attrs)
 136
 137    def parse_symbol(self):
 138        p = re.compile(r' \* ?(BPF\w+)$')
 139        capture = p.match(self.line)
 140        if not capture:
 141            raise NoSyscallCommandFound
 142        end_re = re.compile(r' \* ?NOTES$')
 143        end = end_re.match(self.line)
 144        if end:
 145            raise NoSyscallCommandFound
 146        self.line = self.reader.readline()
 147        return capture.group(1)
 148
 149    def parse_proto(self):
 150        # Argument can be of shape:
 151        #   - "void"
 152        #   - "type  name"
 153        #   - "type *name"
 154        #   - Same as above, with "const" and/or "struct" in front of type
 155        #   - "..." (undefined number of arguments, for bpf_trace_printk())
 156        # There is at least one term ("void"), and at most five arguments.
 157        p = re.compile(r' \* ?((.+) \**\w+\((((const )?(struct )?(\w+|\.\.\.)( \**\w+)?)(, )?){1,5}\))$')
 158        capture = p.match(self.line)
 159        if not capture:
 160            raise NoHelperFound
 161        self.line = self.reader.readline()
 162        return capture.group(1)
 163
 164    def parse_desc(self, proto):
 165        p = re.compile(r' \* ?(?:\t| {5,8})Description$')
 166        capture = p.match(self.line)
 167        if not capture:
 168            raise Exception("No description section found for " + proto)
 169        # Description can be several lines, some of them possibly empty, and it
 170        # stops when another subsection title is met.
 171        desc = ''
 172        desc_present = False
 173        while True:
 174            self.line = self.reader.readline()
 175            if self.line == ' *\n':
 176                desc += '\n'
 177            else:
 178                p = re.compile(r' \* ?(?:\t| {5,8})(?:\t| {8})(.*)')
 179                capture = p.match(self.line)
 180                if capture:
 181                    desc_present = True
 182                    desc += capture.group(1) + '\n'
 183                else:
 184                    break
 185
 186        if not desc_present:
 187            raise Exception("No description found for " + proto)
 188        return desc
 189
 190    def parse_ret(self, proto):
 191        p = re.compile(r' \* ?(?:\t| {5,8})Return$')
 192        capture = p.match(self.line)
 193        if not capture:
 194            raise Exception("No return section found for " + proto)
 195        # Return value description can be several lines, some of them possibly
 196        # empty, and it stops when another subsection title is met.
 197        ret = ''
 198        ret_present = False
 199        while True:
 200            self.line = self.reader.readline()
 201            if self.line == ' *\n':
 202                ret += '\n'
 203            else:
 204                p = re.compile(r' \* ?(?:\t| {5,8})(?:\t| {8})(.*)')
 205                capture = p.match(self.line)
 206                if capture:
 207                    ret_present = True
 208                    ret += capture.group(1) + '\n'
 209                else:
 210                    break
 211
 212        if not ret_present:
 213            raise Exception("No return found for " + proto)
 214        return ret
 215
 216    def parse_attrs(self, proto):
 217        p = re.compile(r' \* ?(?:\t| {5,8})Attributes$')
 218        capture = p.match(self.line)
 219        if not capture:
 220            return []
 221        # Expect a single line with mnemonics for attributes separated by spaces
 222        self.line = self.reader.readline()
 223        p = re.compile(r' \* ?(?:\t| {5,8})(?:\t| {8})(.*)')
 224        capture = p.match(self.line)
 225        if not capture:
 226            raise Exception("Incomplete 'Attributes' section for " + proto)
 227        attrs = capture.group(1).split(' ')
 228        for attr in attrs:
 229            if attr not in ATTRS:
 230                raise Exception("Unexpected attribute '" + attr + "' specified for " + proto)
 231        self.line = self.reader.readline()
 232        if self.line != ' *\n':
 233            raise Exception("Expecting empty line after 'Attributes' section for " + proto)
 234        # Prepare a line for next self.parse_* to consume
 235        self.line = self.reader.readline()
 236        return attrs
 237
 238    def seek_to(self, target, help_message, discard_lines = 1):
 239        self.reader.seek(0)
 240        offset = self.reader.read().find(target)
 241        if offset == -1:
 242            raise Exception(help_message)
 243        self.reader.seek(offset)
 244        self.reader.readline()
 245        for _ in range(discard_lines):
 246            self.reader.readline()
 247        self.line = self.reader.readline()
 248
 249    def parse_desc_syscall(self):
 250        self.seek_to('* DOC: eBPF Syscall Commands',
 251                     'Could not find start of eBPF syscall descriptions list')
 252        while True:
 253            try:
 254                command = self.parse_element()
 255                self.commands.append(command)
 256                self.desc_syscalls.append(command.proto)
 257
 258            except NoSyscallCommandFound:
 259                break
 260
 261    def parse_enum_syscall(self):
 262        self.seek_to('enum bpf_cmd {',
 263                     'Could not find start of bpf_cmd enum', 0)
 264        # Searches for either one or more BPF\w+ enums
 265        bpf_p = re.compile(r'\s*(BPF\w+)+')
 266        # Searches for an enum entry assigned to another entry,
 267        # for e.g. BPF_PROG_RUN = BPF_PROG_TEST_RUN, which is
 268        # not documented hence should be skipped in check to
 269        # determine if the right number of syscalls are documented
 270        assign_p = re.compile(r'\s*(BPF\w+)\s*=\s*(BPF\w+)')
 271        bpf_cmd_str = ''
 272        while True:
 273            capture = assign_p.match(self.line)
 274            if capture:
 275                # Skip line if an enum entry is assigned to another entry
 276                self.line = self.reader.readline()
 277                continue
 278            capture = bpf_p.match(self.line)
 279            if capture:
 280                bpf_cmd_str += self.line
 281            else:
 282                break
 283            self.line = self.reader.readline()
 284        # Find the number of occurences of BPF\w+
 285        self.enum_syscalls = re.findall(r'(BPF\w+)+', bpf_cmd_str)
 286
 287    def parse_desc_helpers(self):
 288        self.seek_to(helpersDocStart,
 289                     'Could not find start of eBPF helper descriptions list')
 290        while True:
 291            try:
 292                helper = self.parse_helper()
 293                self.helpers.append(helper)
 294                proto = helper.proto_break_down()
 295                self.desc_unique_helpers.add(proto['name'])
 296            except NoHelperFound:
 297                break
 298
 299    def parse_define_helpers(self):
 300        # Parse FN(...) in #define ___BPF_FUNC_MAPPER to compare later with the
 301        # number of unique function names present in description and use the
 302        # correct enumeration value.
 303        # Note: seek_to(..) discards the first line below the target search text,
 304        # resulting in FN(unspec, 0, ##ctx) being skipped and not added to
 305        # self.define_unique_helpers.
 306        self.seek_to('#define ___BPF_FUNC_MAPPER(FN, ctx...)',
 307                     'Could not find start of eBPF helper definition list')
 308        # Searches for one FN(\w+) define or a backslash for newline
 309        p = re.compile(r'\s*FN\((\w+), (\d+), ##ctx\)|\\\\')
 310        fn_defines_str = ''
 311        i = 0
 312        while True:
 313            capture = p.match(self.line)
 314            if capture:
 315                fn_defines_str += self.line
 316                helper_name = capture.expand(r'bpf_\1')
 317                self.helper_enum_vals[helper_name] = int(capture.group(2))
 318                self.helper_enum_pos[helper_name] = i
 319                i += 1
 320            else:
 321                break
 322            self.line = self.reader.readline()
 323        # Find the number of occurences of FN(\w+)
 324        self.define_unique_helpers = re.findall(r'FN\(\w+, \d+, ##ctx\)', fn_defines_str)
 325
 326    def validate_helpers(self):
 327        last_helper = ''
 328        seen_helpers = set()
 329        seen_enum_vals = set()
 330        i = 0
 331        for helper in self.helpers:
 332            proto = helper.proto_break_down()
 333            name = proto['name']
 334            try:
 335                enum_val = self.helper_enum_vals[name]
 336                enum_pos = self.helper_enum_pos[name]
 337            except KeyError:
 338                raise Exception("Helper %s is missing from enum bpf_func_id" % name)
 339
 340            if name in seen_helpers:
 341                if last_helper != name:
 342                    raise Exception("Helper %s has multiple descriptions which are not grouped together" % name)
 343                continue
 344
 345            # Enforce current practice of having the descriptions ordered
 346            # by enum value.
 347            if enum_pos != i:
 348                raise Exception("Helper %s (ID %d) comment order (#%d) must be aligned with its position (#%d) in enum bpf_func_id" % (name, enum_val, i + 1, enum_pos + 1))
 349            if enum_val in seen_enum_vals:
 350                raise Exception("Helper %s has duplicated value %d" % (name, enum_val))
 351
 352            seen_helpers.add(name)
 353            last_helper = name
 354            seen_enum_vals.add(enum_val)
 355
 356            helper.enum_val = enum_val
 357            i += 1
 358
 359    def run(self):
 360        self.parse_desc_syscall()
 361        self.parse_enum_syscall()
 362        self.parse_desc_helpers()
 363        self.parse_define_helpers()
 364        self.validate_helpers()
 365        self.reader.close()
 366
 367###############################################################################
 368
 369class Printer(object):
 370    """
 371    A generic class for printers. Printers should be created with an array of
 372    Helper objects, and implement a way to print them in the desired fashion.
 373    @parser: A HeaderParser with objects to print to standard output
 374    """
 375    def __init__(self, parser):
 376        self.parser = parser
 377        self.elements = []
 378
 379    def print_header(self):
 380        pass
 381
 382    def print_footer(self):
 383        pass
 384
 385    def print_one(self, helper):
 386        pass
 387
 388    def print_all(self):
 389        self.print_header()
 390        for elem in self.elements:
 391            self.print_one(elem)
 392        self.print_footer()
 393
 394    def elem_number_check(self, desc_unique_elem, define_unique_elem, type, instance):
 395        """
 396        Checks the number of helpers/syscalls documented within the header file
 397        description with those defined as part of enum/macro and raise an
 398        Exception if they don't match.
 399        """
 400        nr_desc_unique_elem = len(desc_unique_elem)
 401        nr_define_unique_elem = len(define_unique_elem)
 402        if nr_desc_unique_elem != nr_define_unique_elem:
 403            exception_msg = '''
 404The number of unique %s in description (%d) doesn\'t match the number of unique %s defined in %s (%d)
 405''' % (type, nr_desc_unique_elem, type, instance, nr_define_unique_elem)
 406            if nr_desc_unique_elem < nr_define_unique_elem:
 407                # Function description is parsed until no helper is found (which can be due to
 408                # misformatting). Hence, only print the first missing/misformatted helper/enum.
 409                exception_msg += '''
 410The description for %s is not present or formatted correctly.
 411''' % (define_unique_elem[nr_desc_unique_elem])
 412            raise Exception(exception_msg)
 413
 414class PrinterRST(Printer):
 415    """
 416    A generic class for printers that print ReStructured Text. Printers should
 417    be created with a HeaderParser object, and implement a way to print API
 418    elements in the desired fashion.
 419    @parser: A HeaderParser with objects to print to standard output
 420    """
 421    def __init__(self, parser):
 422        self.parser = parser
 423
 424    def print_license(self):
 425        license = '''\
 426.. Copyright (C) All BPF authors and contributors from 2014 to present.
 427.. See git log include/uapi/linux/bpf.h in kernel tree for details.
 428.. 
 429.. SPDX-License-Identifier: Linux-man-pages-copyleft
 430.. 
 431.. Please do not edit this file. It was generated from the documentation
 432.. located in file include/uapi/linux/bpf.h of the Linux kernel sources
 433.. (helpers description), and from scripts/bpf_doc.py in the same
 434.. repository (header and footer).
 435'''
 436        print(license)
 437
 438    def print_elem(self, elem):
 439        if (elem.desc):
 440            print('\tDescription')
 441            # Do not strip all newline characters: formatted code at the end of
 442            # a section must be followed by a blank line.
 443            for line in re.sub('\n$', '', elem.desc, count=1).split('\n'):
 444                print('{}{}'.format('\t\t' if line else '', line))
 445
 446        if (elem.ret):
 447            print('\tReturn')
 448            for line in elem.ret.rstrip().split('\n'):
 449                print('{}{}'.format('\t\t' if line else '', line))
 450
 451        print('')
 452
 453    def get_kernel_version(self):
 454        try:
 455            version = subprocess.run(['git', 'describe'], cwd=linuxRoot,
 456                                     capture_output=True, check=True)
 457            version = version.stdout.decode().rstrip()
 458        except:
 459            try:
 460                version = subprocess.run(['make', '-s', '--no-print-directory', 'kernelversion'],
 461                                         cwd=linuxRoot, capture_output=True, check=True)
 462                version = version.stdout.decode().rstrip()
 463            except:
 464                return 'Linux'
 465        return 'Linux {version}'.format(version=version)
 466
 467    def get_last_doc_update(self, delimiter):
 468        try:
 469            cmd = ['git', 'log', '-1', '--pretty=format:%cs', '--no-patch',
 470                   '-L',
 471                   '/{}/,/\\*\\//:include/uapi/linux/bpf.h'.format(delimiter)]
 472            date = subprocess.run(cmd, cwd=linuxRoot,
 473                                  capture_output=True, check=True)
 474            return date.stdout.decode().rstrip()
 475        except:
 476            return ''
 477
 478class PrinterHelpersRST(PrinterRST):
 479    """
 480    A printer for dumping collected information about helpers as a ReStructured
 481    Text page compatible with the rst2man program, which can be used to
 482    generate a manual page for the helpers.
 483    @parser: A HeaderParser with Helper objects to print to standard output
 484    """
 485    def __init__(self, parser):
 486        self.elements = parser.helpers
 487        self.elem_number_check(parser.desc_unique_helpers, parser.define_unique_helpers, 'helper', '___BPF_FUNC_MAPPER')
 488
 489    def print_header(self):
 490        header = '''\
 491===========
 492BPF-HELPERS
 493===========
 494-------------------------------------------------------------------------------
 495list of eBPF helper functions
 496-------------------------------------------------------------------------------
 497
 498:Manual section: 7
 499:Version: {version}
 500{date_field}{date}
 501
 502DESCRIPTION
 503===========
 504
 505The extended Berkeley Packet Filter (eBPF) subsystem consists in programs
 506written in a pseudo-assembly language, then attached to one of the several
 507kernel hooks and run in reaction of specific events. This framework differs
 508from the older, "classic" BPF (or "cBPF") in several aspects, one of them being
 509the ability to call special functions (or "helpers") from within a program.
 510These functions are restricted to a white-list of helpers defined in the
 511kernel.
 512
 513These helpers are used by eBPF programs to interact with the system, or with
 514the context in which they work. For instance, they can be used to print
 515debugging messages, to get the time since the system was booted, to interact
 516with eBPF maps, or to manipulate network packets. Since there are several eBPF
 517program types, and that they do not run in the same context, each program type
 518can only call a subset of those helpers.
 519
 520Due to eBPF conventions, a helper can not have more than five arguments.
 521
 522Internally, eBPF programs call directly into the compiled helper functions
 523without requiring any foreign-function interface. As a result, calling helpers
 524introduces no overhead, thus offering excellent performance.
 525
 526This document is an attempt to list and document the helpers available to eBPF
 527developers. They are sorted by chronological order (the oldest helpers in the
 528kernel at the top).
 529
 530HELPERS
 531=======
 532'''
 533        kernelVersion = self.get_kernel_version()
 534        lastUpdate = self.get_last_doc_update(helpersDocStart)
 535
 536        PrinterRST.print_license(self)
 537        print(header.format(version=kernelVersion,
 538                            date_field = ':Date: ' if lastUpdate else '',
 539                            date=lastUpdate))
 540
 541    def print_footer(self):
 542        footer = '''
 543EXAMPLES
 544========
 545
 546Example usage for most of the eBPF helpers listed in this manual page are
 547available within the Linux kernel sources, at the following locations:
 548
 549* *samples/bpf/*
 550* *tools/testing/selftests/bpf/*
 551
 552LICENSE
 553=======
 554
 555eBPF programs can have an associated license, passed along with the bytecode
 556instructions to the kernel when the programs are loaded. The format for that
 557string is identical to the one in use for kernel modules (Dual licenses, such
 558as "Dual BSD/GPL", may be used). Some helper functions are only accessible to
 559programs that are compatible with the GNU General Public License (GNU GPL).
 560
 561In order to use such helpers, the eBPF program must be loaded with the correct
 562license string passed (via **attr**) to the **bpf**\\ () system call, and this
 563generally translates into the C source code of the program containing a line
 564similar to the following:
 565
 566::
 567
 568	char ____license[] __attribute__((section("license"), used)) = "GPL";
 569
 570IMPLEMENTATION
 571==============
 572
 573This manual page is an effort to document the existing eBPF helper functions.
 574But as of this writing, the BPF sub-system is under heavy development. New eBPF
 575program or map types are added, along with new helper functions. Some helpers
 576are occasionally made available for additional program types. So in spite of
 577the efforts of the community, this page might not be up-to-date. If you want to
 578check by yourself what helper functions exist in your kernel, or what types of
 579programs they can support, here are some files among the kernel tree that you
 580may be interested in:
 581
 582* *include/uapi/linux/bpf.h* is the main BPF header. It contains the full list
 583  of all helper functions, as well as many other BPF definitions including most
 584  of the flags, structs or constants used by the helpers.
 585* *net/core/filter.c* contains the definition of most network-related helper
 586  functions, and the list of program types from which they can be used.
 587* *kernel/trace/bpf_trace.c* is the equivalent for most tracing program-related
 588  helpers.
 589* *kernel/bpf/verifier.c* contains the functions used to check that valid types
 590  of eBPF maps are used with a given helper function.
 591* *kernel/bpf/* directory contains other files in which additional helpers are
 592  defined (for cgroups, sockmaps, etc.).
 593* The bpftool utility can be used to probe the availability of helper functions
 594  on the system (as well as supported program and map types, and a number of
 595  other parameters). To do so, run **bpftool feature probe** (see
 596  **bpftool-feature**\\ (8) for details). Add the **unprivileged** keyword to
 597  list features available to unprivileged users.
 598
 599Compatibility between helper functions and program types can generally be found
 600in the files where helper functions are defined. Look for the **struct
 601bpf_func_proto** objects and for functions returning them: these functions
 602contain a list of helpers that a given program type can call. Note that the
 603**default:** label of the **switch ... case** used to filter helpers can call
 604other functions, themselves allowing access to additional helpers. The
 605requirement for GPL license is also in those **struct bpf_func_proto**.
 606
 607Compatibility between helper functions and map types can be found in the
 608**check_map_func_compatibility**\\ () function in file *kernel/bpf/verifier.c*.
 609
 610Helper functions that invalidate the checks on **data** and **data_end**
 611pointers for network processing are listed in function
 612**bpf_helper_changes_pkt_data**\\ () in file *net/core/filter.c*.
 613
 614SEE ALSO
 615========
 616
 617**bpf**\\ (2),
 618**bpftool**\\ (8),
 619**cgroups**\\ (7),
 620**ip**\\ (8),
 621**perf_event_open**\\ (2),
 622**sendmsg**\\ (2),
 623**socket**\\ (7),
 624**tc-bpf**\\ (8)'''
 625        print(footer)
 626
 627    def print_proto(self, helper):
 628        """
 629        Format function protocol with bold and italics markers. This makes RST
 630        file less readable, but gives nice results in the manual page.
 631        """
 632        proto = helper.proto_break_down()
 633
 634        print('**%s %s%s(' % (proto['ret_type'],
 635                              proto['ret_star'].replace('*', '\\*'),
 636                              proto['name']),
 637              end='')
 638
 639        comma = ''
 640        for a in proto['args']:
 641            one_arg = '{}{}'.format(comma, a['type'])
 642            if a['name']:
 643                if a['star']:
 644                    one_arg += ' {}**\\ '.format(a['star'].replace('*', '\\*'))
 645                else:
 646                    one_arg += '** '
 647                one_arg += '*{}*\\ **'.format(a['name'])
 648            comma = ', '
 649            print(one_arg, end='')
 650
 651        print(')**')
 652
 653    def print_one(self, helper):
 654        self.print_proto(helper)
 655        self.print_elem(helper)
 656
 657
 658class PrinterSyscallRST(PrinterRST):
 659    """
 660    A printer for dumping collected information about the syscall API as a
 661    ReStructured Text page compatible with the rst2man program, which can be
 662    used to generate a manual page for the syscall.
 663    @parser: A HeaderParser with APIElement objects to print to standard
 664             output
 665    """
 666    def __init__(self, parser):
 667        self.elements = parser.commands
 668        self.elem_number_check(parser.desc_syscalls, parser.enum_syscalls, 'syscall', 'bpf_cmd')
 669
 670    def print_header(self):
 671        header = '''\
 672===
 673bpf
 674===
 675-------------------------------------------------------------------------------
 676Perform a command on an extended BPF object
 677-------------------------------------------------------------------------------
 678
 679:Manual section: 2
 680
 681COMMANDS
 682========
 683'''
 684        PrinterRST.print_license(self)
 685        print(header)
 686
 687    def print_one(self, command):
 688        print('**%s**' % (command.proto))
 689        self.print_elem(command)
 690
 691
 692class PrinterHelpersHeader(Printer):
 693    """
 694    A printer for dumping collected information about helpers as C header to
 695    be included from BPF program.
 696    @parser: A HeaderParser with Helper objects to print to standard output
 697    """
 698    def __init__(self, parser):
 699        self.elements = parser.helpers
 700        self.elem_number_check(parser.desc_unique_helpers, parser.define_unique_helpers, 'helper', '___BPF_FUNC_MAPPER')
 701
 702    type_fwds = [
 703            'struct bpf_fib_lookup',
 704            'struct bpf_sk_lookup',
 705            'struct bpf_perf_event_data',
 706            'struct bpf_perf_event_value',
 707            'struct bpf_pidns_info',
 708            'struct bpf_redir_neigh',
 709            'struct bpf_sock',
 710            'struct bpf_sock_addr',
 711            'struct bpf_sock_ops',
 712            'struct bpf_sock_tuple',
 713            'struct bpf_spin_lock',
 714            'struct bpf_sysctl',
 715            'struct bpf_tcp_sock',
 716            'struct bpf_tunnel_key',
 717            'struct bpf_xfrm_state',
 718            'struct linux_binprm',
 719            'struct pt_regs',
 720            'struct sk_reuseport_md',
 721            'struct sockaddr',
 722            'struct tcphdr',
 723            'struct seq_file',
 724            'struct tcp6_sock',
 725            'struct tcp_sock',
 726            'struct tcp_timewait_sock',
 727            'struct tcp_request_sock',
 728            'struct udp6_sock',
 729            'struct unix_sock',
 730            'struct task_struct',
 731            'struct cgroup',
 732
 733            'struct __sk_buff',
 734            'struct sk_msg_md',
 735            'struct xdp_md',
 736            'struct path',
 737            'struct btf_ptr',
 738            'struct inode',
 739            'struct socket',
 740            'struct file',
 741            'struct bpf_timer',
 742            'struct mptcp_sock',
 743            'struct bpf_dynptr',
 744            'struct iphdr',
 745            'struct ipv6hdr',
 746    ]
 747    known_types = {
 748            '...',
 749            'void',
 750            'const void',
 751            'char',
 752            'const char',
 753            'int',
 754            'long',
 755            'unsigned long',
 756
 757            '__be16',
 758            '__be32',
 759            '__wsum',
 760
 761            'struct bpf_fib_lookup',
 762            'struct bpf_perf_event_data',
 763            'struct bpf_perf_event_value',
 764            'struct bpf_pidns_info',
 765            'struct bpf_redir_neigh',
 766            'struct bpf_sk_lookup',
 767            'struct bpf_sock',
 768            'struct bpf_sock_addr',
 769            'struct bpf_sock_ops',
 770            'struct bpf_sock_tuple',
 771            'struct bpf_spin_lock',
 772            'struct bpf_sysctl',
 773            'struct bpf_tcp_sock',
 774            'struct bpf_tunnel_key',
 775            'struct bpf_xfrm_state',
 776            'struct linux_binprm',
 777            'struct pt_regs',
 778            'struct sk_reuseport_md',
 779            'struct sockaddr',
 780            'struct tcphdr',
 781            'struct seq_file',
 782            'struct tcp6_sock',
 783            'struct tcp_sock',
 784            'struct tcp_timewait_sock',
 785            'struct tcp_request_sock',
 786            'struct udp6_sock',
 787            'struct unix_sock',
 788            'struct task_struct',
 789            'struct cgroup',
 790            'struct path',
 791            'const struct path',
 792            'struct btf_ptr',
 793            'struct inode',
 794            'struct socket',
 795            'struct file',
 796            'struct bpf_timer',
 797            'struct mptcp_sock',
 798            'struct bpf_dynptr',
 799            'const struct bpf_dynptr',
 800            'struct iphdr',
 801            'struct ipv6hdr',
 802    }
 803    mapped_types = {
 804            'u8': '__u8',
 805            'u16': '__u16',
 806            'u32': '__u32',
 807            'u64': '__u64',
 808            's8': '__s8',
 809            's16': '__s16',
 810            's32': '__s32',
 811            's64': '__s64',
 812            'size_t': 'unsigned long',
 813            'struct bpf_map': 'void',
 814            'struct sk_buff': 'struct __sk_buff',
 815            'const struct sk_buff': 'const struct __sk_buff',
 816            'struct sk_msg_buff': 'struct sk_msg_md',
 817            'struct xdp_buff': 'struct xdp_md',
 818    }
 819    # Helpers overloaded for different context types.
 820    overloaded_helpers = [
 821        'bpf_get_socket_cookie',
 822        'bpf_sk_assign',
 823    ]
 824
 825    def print_header(self):
 826        header = '''\
 827/* This is auto-generated file. See bpf_doc.py for details. */
 828
 829/* Forward declarations of BPF structs */'''
 830
 831        print(header)
 832        for fwd in self.type_fwds:
 833            print('%s;' % fwd)
 834        print('')
 835
 836        used_attrs = set()
 837        for helper in self.elements:
 838            for attr in helper.attrs:
 839                used_attrs.add(attr)
 840        for attr in sorted(used_attrs):
 841            print('#ifndef %s' % attr)
 842            print('#if __has_attribute(%s)' % ATTRS[attr])
 843            print('#define %s __attribute__((%s))' % (attr, ATTRS[attr]))
 844            print('#else')
 845            print('#define %s' % attr)
 846            print('#endif')
 847            print('#endif')
 848        if used_attrs:
 849            print('')
 850
 851    def print_footer(self):
 852        footer = ''
 853        print(footer)
 854
 855    def map_type(self, t):
 856        if t in self.known_types:
 857            return t
 858        if t in self.mapped_types:
 859            return self.mapped_types[t]
 860        print("Unrecognized type '%s', please add it to known types!" % t,
 861              file=sys.stderr)
 862        sys.exit(1)
 863
 864    seen_helpers = set()
 865
 866    def print_one(self, helper):
 867        proto = helper.proto_break_down()
 868
 869        if proto['name'] in self.seen_helpers:
 870            return
 871        self.seen_helpers.add(proto['name'])
 872
 873        print('/*')
 874        print(" * %s" % proto['name'])
 875        print(" *")
 876        if (helper.desc):
 877            # Do not strip all newline characters: formatted code at the end of
 878            # a section must be followed by a blank line.
 879            for line in re.sub('\n$', '', helper.desc, count=1).split('\n'):
 880                print(' *{}{}'.format(' \t' if line else '', line))
 881
 882        if (helper.ret):
 883            print(' *')
 884            print(' * Returns')
 885            for line in helper.ret.rstrip().split('\n'):
 886                print(' *{}{}'.format(' \t' if line else '', line))
 887
 888        print(' */')
 889        print('static ', end='')
 890        if helper.attrs:
 891            print('%s ' % (" ".join(helper.attrs)), end='')
 892        print('%s %s(* const %s)(' % (self.map_type(proto['ret_type']),
 893                                      proto['ret_star'], proto['name']), end='')
 894        comma = ''
 895        for i, a in enumerate(proto['args']):
 896            t = a['type']
 897            n = a['name']
 898            if proto['name'] in self.overloaded_helpers and i == 0:
 899                    t = 'void'
 900                    n = 'ctx'
 901            one_arg = '{}{}'.format(comma, self.map_type(t))
 902            if n:
 903                if a['star']:
 904                    one_arg += ' {}'.format(a['star'])
 905                else:
 906                    one_arg += ' '
 907                one_arg += '{}'.format(n)
 908            comma = ', '
 909            print(one_arg, end='')
 910
 911        print(') = (void *) %d;' % helper.enum_val)
 912        print('')
 913
 914
 915class PrinterHelpersJSON(Printer):
 916    """
 917    A printer for dumping collected information about helpers as a JSON file.
 918    @parser: A HeaderParser with Helper objects
 919    """
 920
 921    def __init__(self, parser):
 922        self.elements = parser.helpers
 923        self.elem_number_check(
 924            parser.desc_unique_helpers,
 925            parser.define_unique_helpers,
 926            "helper",
 927            "___BPF_FUNC_MAPPER",
 928        )
 929
 930    def print_all(self):
 931        helper_dicts = [helper.to_dict() for helper in self.elements]
 932        out_dict = {'helpers': helper_dicts}
 933        print(json.dumps(out_dict, indent=4))
 934
 935
 936class PrinterSyscallJSON(Printer):
 937    """
 938    A printer for dumping collected syscall information as a JSON file.
 939    @parser: A HeaderParser with APIElement objects
 940    """
 941
 942    def __init__(self, parser):
 943        self.elements = parser.commands
 944        self.elem_number_check(parser.desc_syscalls, parser.enum_syscalls, 'syscall', 'bpf_cmd')
 945
 946    def print_all(self):
 947        syscall_dicts = [syscall.to_dict() for syscall in self.elements]
 948        out_dict = {'syscall': syscall_dicts}
 949        print(json.dumps(out_dict, indent=4))
 950
 951###############################################################################
 952
 953# If script is launched from scripts/ from kernel tree and can access
 954# ../include/uapi/linux/bpf.h, use it as a default name for the file to parse,
 955# otherwise the --filename argument will be required from the command line.
 956script = os.path.abspath(sys.argv[0])
 957linuxRoot = os.path.dirname(os.path.dirname(script))
 958bpfh = os.path.join(linuxRoot, 'include/uapi/linux/bpf.h')
 959
 960# target -> output format -> printer
 961printers = {
 962    'helpers': {
 963        'rst': PrinterHelpersRST,
 964        'json': PrinterHelpersJSON,
 965        'header': PrinterHelpersHeader,
 966    },
 967    'syscall': {
 968        'rst': PrinterSyscallRST,
 969        'json': PrinterSyscallJSON
 970    },
 971}
 972
 973argParser = argparse.ArgumentParser(description="""
 974Parse eBPF header file and generate documentation for the eBPF API.
 975The RST-formatted output produced can be turned into a manual page with the
 976rst2man utility.
 977""")
 978argParser.add_argument('--header', action='store_true',
 979                       help='generate C header file')
 980argParser.add_argument('--json', action='store_true',
 981                       help='generate a JSON')
 982if (os.path.isfile(bpfh)):
 983    argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h',
 984                           default=bpfh)
 985else:
 986    argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h')
 987argParser.add_argument('target', nargs='?', default='helpers',
 988                       choices=printers.keys(), help='eBPF API target')
 989
 990def error_die(message: str):
 991    argParser.print_usage(file=sys.stderr)
 992    print('Error: {}'.format(message), file=sys.stderr)
 993    exit(1)
 994
 995def parse_and_dump():
 996    args = argParser.parse_args()
 997
 998    # Parse file.
 999    headerParser = HeaderParser(args.filename)
1000    headerParser.run()
1001
1002    if args.header and args.json:
1003        error_die('Use either --header or --json, not both')
1004
1005    output_format = 'rst'
1006    if args.header:
1007        output_format = 'header'
1008    elif args.json:
1009        output_format = 'json'
1010
1011    try:
1012        printer = printers[args.target][output_format](headerParser)
1013        # Print formatted output to standard output.
1014        printer.print_all()
1015    except KeyError:
1016        error_die('Unsupported target/format combination: "{}", "{}"'
1017                    .format(args.target, output_format))
1018
1019if __name__ == "__main__":
1020    parse_and_dump()
~/ Read-only file preview.