xref: /linux/scripts/bpf_doc.py (revision bdfa82f5b8998a6311a8ef0cf89ad413f5cd9ea4)
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            'struct btf_ptr',
792            'struct inode',
793            'struct socket',
794            'struct file',
795            'struct bpf_timer',
796            'struct mptcp_sock',
797            'struct bpf_dynptr',
798            'const struct bpf_dynptr',
799            'struct iphdr',
800            'struct ipv6hdr',
801    }
802    mapped_types = {
803            'u8': '__u8',
804            'u16': '__u16',
805            'u32': '__u32',
806            'u64': '__u64',
807            's8': '__s8',
808            's16': '__s16',
809            's32': '__s32',
810            's64': '__s64',
811            'size_t': 'unsigned long',
812            'struct bpf_map': 'void',
813            'struct sk_buff': 'struct __sk_buff',
814            'const struct sk_buff': 'const struct __sk_buff',
815            'struct sk_msg_buff': 'struct sk_msg_md',
816            'struct xdp_buff': 'struct xdp_md',
817    }
818    # Helpers overloaded for different context types.
819    overloaded_helpers = [
820        'bpf_get_socket_cookie',
821        'bpf_sk_assign',
822    ]
823
824    def print_header(self):
825        header = '''\
826/* This is auto-generated file. See bpf_doc.py for details. */
827
828/* Forward declarations of BPF structs */'''
829
830        print(header)
831        for fwd in self.type_fwds:
832            print('%s;' % fwd)
833        print('')
834
835        used_attrs = set()
836        for helper in self.elements:
837            for attr in helper.attrs:
838                used_attrs.add(attr)
839        for attr in sorted(used_attrs):
840            print('#ifndef %s' % attr)
841            print('#if __has_attribute(%s)' % ATTRS[attr])
842            print('#define %s __attribute__((%s))' % (attr, ATTRS[attr]))
843            print('#else')
844            print('#define %s' % attr)
845            print('#endif')
846            print('#endif')
847        if used_attrs:
848            print('')
849
850    def print_footer(self):
851        footer = ''
852        print(footer)
853
854    def map_type(self, t):
855        if t in self.known_types:
856            return t
857        if t in self.mapped_types:
858            return self.mapped_types[t]
859        print("Unrecognized type '%s', please add it to known types!" % t,
860              file=sys.stderr)
861        sys.exit(1)
862
863    seen_helpers = set()
864
865    def print_one(self, helper):
866        proto = helper.proto_break_down()
867
868        if proto['name'] in self.seen_helpers:
869            return
870        self.seen_helpers.add(proto['name'])
871
872        print('/*')
873        print(" * %s" % proto['name'])
874        print(" *")
875        if (helper.desc):
876            # Do not strip all newline characters: formatted code at the end of
877            # a section must be followed by a blank line.
878            for line in re.sub('\n$', '', helper.desc, count=1).split('\n'):
879                print(' *{}{}'.format(' \t' if line else '', line))
880
881        if (helper.ret):
882            print(' *')
883            print(' * Returns')
884            for line in helper.ret.rstrip().split('\n'):
885                print(' *{}{}'.format(' \t' if line else '', line))
886
887        print(' */')
888        print('static ', end='')
889        if helper.attrs:
890            print('%s ' % (" ".join(helper.attrs)), end='')
891        print('%s %s(* const %s)(' % (self.map_type(proto['ret_type']),
892                                      proto['ret_star'], proto['name']), end='')
893        comma = ''
894        for i, a in enumerate(proto['args']):
895            t = a['type']
896            n = a['name']
897            if proto['name'] in self.overloaded_helpers and i == 0:
898                    t = 'void'
899                    n = 'ctx'
900            one_arg = '{}{}'.format(comma, self.map_type(t))
901            if n:
902                if a['star']:
903                    one_arg += ' {}'.format(a['star'])
904                else:
905                    one_arg += ' '
906                one_arg += '{}'.format(n)
907            comma = ', '
908            print(one_arg, end='')
909
910        print(') = (void *) %d;' % helper.enum_val)
911        print('')
912
913
914class PrinterHelpersJSON(Printer):
915    """
916    A printer for dumping collected information about helpers as a JSON file.
917    @parser: A HeaderParser with Helper objects
918    """
919
920    def __init__(self, parser):
921        self.elements = parser.helpers
922        self.elem_number_check(
923            parser.desc_unique_helpers,
924            parser.define_unique_helpers,
925            "helper",
926            "___BPF_FUNC_MAPPER",
927        )
928
929    def print_all(self):
930        helper_dicts = [helper.to_dict() for helper in self.elements]
931        out_dict = {'helpers': helper_dicts}
932        print(json.dumps(out_dict, indent=4))
933
934
935class PrinterSyscallJSON(Printer):
936    """
937    A printer for dumping collected syscall information as a JSON file.
938    @parser: A HeaderParser with APIElement objects
939    """
940
941    def __init__(self, parser):
942        self.elements = parser.commands
943        self.elem_number_check(parser.desc_syscalls, parser.enum_syscalls, 'syscall', 'bpf_cmd')
944
945    def print_all(self):
946        syscall_dicts = [syscall.to_dict() for syscall in self.elements]
947        out_dict = {'syscall': syscall_dicts}
948        print(json.dumps(out_dict, indent=4))
949
950###############################################################################
951
952# If script is launched from scripts/ from kernel tree and can access
953# ../include/uapi/linux/bpf.h, use it as a default name for the file to parse,
954# otherwise the --filename argument will be required from the command line.
955script = os.path.abspath(sys.argv[0])
956linuxRoot = os.path.dirname(os.path.dirname(script))
957bpfh = os.path.join(linuxRoot, 'include/uapi/linux/bpf.h')
958
959# target -> output format -> printer
960printers = {
961    'helpers': {
962        'rst': PrinterHelpersRST,
963        'json': PrinterHelpersJSON,
964        'header': PrinterHelpersHeader,
965    },
966    'syscall': {
967        'rst': PrinterSyscallRST,
968        'json': PrinterSyscallJSON
969    },
970}
971
972argParser = argparse.ArgumentParser(description="""
973Parse eBPF header file and generate documentation for the eBPF API.
974The RST-formatted output produced can be turned into a manual page with the
975rst2man utility.
976""")
977argParser.add_argument('--header', action='store_true',
978                       help='generate C header file')
979argParser.add_argument('--json', action='store_true',
980                       help='generate a JSON')
981if (os.path.isfile(bpfh)):
982    argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h',
983                           default=bpfh)
984else:
985    argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h')
986argParser.add_argument('target', nargs='?', default='helpers',
987                       choices=printers.keys(), help='eBPF API target')
988
989def error_die(message: str):
990    argParser.print_usage(file=sys.stderr)
991    print('Error: {}'.format(message), file=sys.stderr)
992    exit(1)
993
994def parse_and_dump():
995    args = argParser.parse_args()
996
997    # Parse file.
998    headerParser = HeaderParser(args.filename)
999    headerParser.run()
1000
1001    if args.header and args.json:
1002        error_die('Use either --header or --json, not both')
1003
1004    output_format = 'rst'
1005    if args.header:
1006        output_format = 'header'
1007    elif args.json:
1008        output_format = 'json'
1009
1010    try:
1011        printer = printers[args.target][output_format](headerParser)
1012        # Print formatted output to standard output.
1013        printer.print_all()
1014    except KeyError:
1015        error_die('Unsupported target/format combination: "{}", "{}"'
1016                    .format(args.target, output_format))
1017
1018if __name__ == "__main__":
1019    parse_and_dump()
1020