diff options
author | Quentin Monnet <quentin.monnet@netronome.com> | 2018-04-25 13:16:52 -0400 |
---|---|---|
committer | Daniel Borkmann <daniel@iogearbox.net> | 2018-04-26 18:21:58 -0400 |
commit | 56a092c895054a6b423781d788339775bd2bda10 (patch) | |
tree | 754ae25c56e47d0005f8b396ec891270d7ea4a43 /scripts/bpf_helpers_doc.py | |
parent | 3f13de6d6f3cfabbc94e7e6495540cca46a8682f (diff) |
bpf: add script and prepare bpf.h for new helpers documentation
Remove previous "overview" of eBPF helpers from user bpf.h header.
Replace it by a comment explaining how to process the new documentation
(to come in following patches) with a Python script to produce RST, then
man page documentation.
Also add the aforementioned Python script under scripts/. It is used to
process include/uapi/linux/bpf.h and to extract helper descriptions, to
turn it into a RST document that can further be processed with rst2man
to produce a man page. The script takes one "--filename <path/to/file>"
option. If the script is launched from scripts/ in the kernel root
directory, it should be able to find the location of the header to
parse, and "--filename <path/to/file>" is then optional. If it cannot
find the file, then the option becomes mandatory. RST-formatted
documentation is printed to standard output.
Typical workflow for producing the final man page would be:
$ ./scripts/bpf_helpers_doc.py \
--filename include/uapi/linux/bpf.h > /tmp/bpf-helpers.rst
$ rst2man /tmp/bpf-helpers.rst > /tmp/bpf-helpers.7
$ man /tmp/bpf-helpers.7
Note that the tool kernel-doc cannot be used to document eBPF helpers,
whose signatures are not available directly in the header files
(pre-processor directives are used to produce them at the beginning of
the compilation process).
v4:
- Also remove overviews for newly added bpf_xdp_adjust_tail() and
bpf_skb_get_xfrm_state().
- Remove vague statement about what helpers are restricted to GPL
programs in "LICENSE" section for man page footer.
- Replace license boilerplate with SPDX tag for Python script.
v3:
- Change license for man page.
- Remove "for safety reasons" from man page header text.
- Change "packets metadata" to "packets" in man page header text.
- Move and fix comment on helpers introducing no overhead.
- Remove "NOTES" section from man page footer.
- Add "LICENSE" section to man page footer.
- Edit description of file include/uapi/linux/bpf.h in man page footer.
Signed-off-by: Quentin Monnet <quentin.monnet@netronome.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Diffstat (limited to 'scripts/bpf_helpers_doc.py')
-rwxr-xr-x | scripts/bpf_helpers_doc.py | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/scripts/bpf_helpers_doc.py b/scripts/bpf_helpers_doc.py new file mode 100755 index 000000000000..30ba0fee36e4 --- /dev/null +++ b/scripts/bpf_helpers_doc.py | |||
@@ -0,0 +1,421 @@ | |||
1 | #!/usr/bin/python3 | ||
2 | # SPDX-License-Identifier: GPL-2.0-only | ||
3 | # | ||
4 | # Copyright (C) 2018 Netronome Systems, Inc. | ||
5 | |||
6 | # In case user attempts to run with Python 2. | ||
7 | from __future__ import print_function | ||
8 | |||
9 | import argparse | ||
10 | import re | ||
11 | import sys, os | ||
12 | |||
13 | class NoHelperFound(BaseException): | ||
14 | pass | ||
15 | |||
16 | class ParsingError(BaseException): | ||
17 | def __init__(self, line='<line not provided>', reader=None): | ||
18 | if reader: | ||
19 | BaseException.__init__(self, | ||
20 | 'Error at file offset %d, parsing line: %s' % | ||
21 | (reader.tell(), line)) | ||
22 | else: | ||
23 | BaseException.__init__(self, 'Error parsing line: %s' % line) | ||
24 | |||
25 | class Helper(object): | ||
26 | """ | ||
27 | An object representing the description of an eBPF helper function. | ||
28 | @proto: function prototype of the helper function | ||
29 | @desc: textual description of the helper function | ||
30 | @ret: description of the return value of the helper function | ||
31 | """ | ||
32 | def __init__(self, proto='', desc='', ret=''): | ||
33 | self.proto = proto | ||
34 | self.desc = desc | ||
35 | self.ret = ret | ||
36 | |||
37 | def proto_break_down(self): | ||
38 | """ | ||
39 | Break down helper function protocol into smaller chunks: return type, | ||
40 | name, distincts arguments. | ||
41 | """ | ||
42 | arg_re = re.compile('^((const )?(struct )?(\w+|...))( (\**)(\w+))?$') | ||
43 | res = {} | ||
44 | proto_re = re.compile('^(.+) (\**)(\w+)\(((([^,]+)(, )?){1,5})\)$') | ||
45 | |||
46 | capture = proto_re.match(self.proto) | ||
47 | res['ret_type'] = capture.group(1) | ||
48 | res['ret_star'] = capture.group(2) | ||
49 | res['name'] = capture.group(3) | ||
50 | res['args'] = [] | ||
51 | |||
52 | args = capture.group(4).split(', ') | ||
53 | for a in args: | ||
54 | capture = arg_re.match(a) | ||
55 | res['args'].append({ | ||
56 | 'type' : capture.group(1), | ||
57 | 'star' : capture.group(6), | ||
58 | 'name' : capture.group(7) | ||
59 | }) | ||
60 | |||
61 | return res | ||
62 | |||
63 | class HeaderParser(object): | ||
64 | """ | ||
65 | An object used to parse a file in order to extract the documentation of a | ||
66 | list of eBPF helper functions. All the helpers that can be retrieved are | ||
67 | stored as Helper object, in the self.helpers() array. | ||
68 | @filename: name of file to parse, usually include/uapi/linux/bpf.h in the | ||
69 | kernel tree | ||
70 | """ | ||
71 | def __init__(self, filename): | ||
72 | self.reader = open(filename, 'r') | ||
73 | self.line = '' | ||
74 | self.helpers = [] | ||
75 | |||
76 | def parse_helper(self): | ||
77 | proto = self.parse_proto() | ||
78 | desc = self.parse_desc() | ||
79 | ret = self.parse_ret() | ||
80 | return Helper(proto=proto, desc=desc, ret=ret) | ||
81 | |||
82 | def parse_proto(self): | ||
83 | # Argument can be of shape: | ||
84 | # - "void" | ||
85 | # - "type name" | ||
86 | # - "type *name" | ||
87 | # - Same as above, with "const" and/or "struct" in front of type | ||
88 | # - "..." (undefined number of arguments, for bpf_trace_printk()) | ||
89 | # There is at least one term ("void"), and at most five arguments. | ||
90 | p = re.compile('^ \* ((.+) \**\w+\((((const )?(struct )?(\w+|\.\.\.)( \**\w+)?)(, )?){1,5}\))$') | ||
91 | capture = p.match(self.line) | ||
92 | if not capture: | ||
93 | raise NoHelperFound | ||
94 | self.line = self.reader.readline() | ||
95 | return capture.group(1) | ||
96 | |||
97 | def parse_desc(self): | ||
98 | p = re.compile('^ \* \tDescription$') | ||
99 | capture = p.match(self.line) | ||
100 | if not capture: | ||
101 | # Helper can have empty description and we might be parsing another | ||
102 | # attribute: return but do not consume. | ||
103 | return '' | ||
104 | # Description can be several lines, some of them possibly empty, and it | ||
105 | # stops when another subsection title is met. | ||
106 | desc = '' | ||
107 | while True: | ||
108 | self.line = self.reader.readline() | ||
109 | if self.line == ' *\n': | ||
110 | desc += '\n' | ||
111 | else: | ||
112 | p = re.compile('^ \* \t\t(.*)') | ||
113 | capture = p.match(self.line) | ||
114 | if capture: | ||
115 | desc += capture.group(1) + '\n' | ||
116 | else: | ||
117 | break | ||
118 | return desc | ||
119 | |||
120 | def parse_ret(self): | ||
121 | p = re.compile('^ \* \tReturn$') | ||
122 | capture = p.match(self.line) | ||
123 | if not capture: | ||
124 | # Helper can have empty retval and we might be parsing another | ||
125 | # attribute: return but do not consume. | ||
126 | return '' | ||
127 | # Return value description can be several lines, some of them possibly | ||
128 | # empty, and it stops when another subsection title is met. | ||
129 | ret = '' | ||
130 | while True: | ||
131 | self.line = self.reader.readline() | ||
132 | if self.line == ' *\n': | ||
133 | ret += '\n' | ||
134 | else: | ||
135 | p = re.compile('^ \* \t\t(.*)') | ||
136 | capture = p.match(self.line) | ||
137 | if capture: | ||
138 | ret += capture.group(1) + '\n' | ||
139 | else: | ||
140 | break | ||
141 | return ret | ||
142 | |||
143 | def run(self): | ||
144 | # Advance to start of helper function descriptions. | ||
145 | offset = self.reader.read().find('* Start of BPF helper function descriptions:') | ||
146 | if offset == -1: | ||
147 | raise Exception('Could not find start of eBPF helper descriptions list') | ||
148 | self.reader.seek(offset) | ||
149 | self.reader.readline() | ||
150 | self.reader.readline() | ||
151 | self.line = self.reader.readline() | ||
152 | |||
153 | while True: | ||
154 | try: | ||
155 | helper = self.parse_helper() | ||
156 | self.helpers.append(helper) | ||
157 | except NoHelperFound: | ||
158 | break | ||
159 | |||
160 | self.reader.close() | ||
161 | print('Parsed description of %d helper function(s)' % len(self.helpers), | ||
162 | file=sys.stderr) | ||
163 | |||
164 | ############################################################################### | ||
165 | |||
166 | class Printer(object): | ||
167 | """ | ||
168 | A generic class for printers. Printers should be created with an array of | ||
169 | Helper objects, and implement a way to print them in the desired fashion. | ||
170 | @helpers: array of Helper objects to print to standard output | ||
171 | """ | ||
172 | def __init__(self, helpers): | ||
173 | self.helpers = helpers | ||
174 | |||
175 | def print_header(self): | ||
176 | pass | ||
177 | |||
178 | def print_footer(self): | ||
179 | pass | ||
180 | |||
181 | def print_one(self, helper): | ||
182 | pass | ||
183 | |||
184 | def print_all(self): | ||
185 | self.print_header() | ||
186 | for helper in self.helpers: | ||
187 | self.print_one(helper) | ||
188 | self.print_footer() | ||
189 | |||
190 | class PrinterRST(Printer): | ||
191 | """ | ||
192 | A printer for dumping collected information about helpers as a ReStructured | ||
193 | Text page compatible with the rst2man program, which can be used to | ||
194 | generate a manual page for the helpers. | ||
195 | @helpers: array of Helper objects to print to standard output | ||
196 | """ | ||
197 | def print_header(self): | ||
198 | header = '''\ | ||
199 | .. Copyright (C) All BPF authors and contributors from 2014 to present. | ||
200 | .. See git log include/uapi/linux/bpf.h in kernel tree for details. | ||
201 | .. | ||
202 | .. %%%LICENSE_START(VERBATIM) | ||
203 | .. Permission is granted to make and distribute verbatim copies of this | ||
204 | .. manual provided the copyright notice and this permission notice are | ||
205 | .. preserved on all copies. | ||
206 | .. | ||
207 | .. Permission is granted to copy and distribute modified versions of this | ||
208 | .. manual under the conditions for verbatim copying, provided that the | ||
209 | .. entire resulting derived work is distributed under the terms of a | ||
210 | .. permission notice identical to this one. | ||
211 | .. | ||
212 | .. Since the Linux kernel and libraries are constantly changing, this | ||
213 | .. manual page may be incorrect or out-of-date. The author(s) assume no | ||
214 | .. responsibility for errors or omissions, or for damages resulting from | ||
215 | .. the use of the information contained herein. The author(s) may not | ||
216 | .. have taken the same level of care in the production of this manual, | ||
217 | .. which is licensed free of charge, as they might when working | ||
218 | .. professionally. | ||
219 | .. | ||
220 | .. Formatted or processed versions of this manual, if unaccompanied by | ||
221 | .. the source, must acknowledge the copyright and authors of this work. | ||
222 | .. %%%LICENSE_END | ||
223 | .. | ||
224 | .. Please do not edit this file. It was generated from the documentation | ||
225 | .. located in file include/uapi/linux/bpf.h of the Linux kernel sources | ||
226 | .. (helpers description), and from scripts/bpf_helpers_doc.py in the same | ||
227 | .. repository (header and footer). | ||
228 | |||
229 | =========== | ||
230 | BPF-HELPERS | ||
231 | =========== | ||
232 | ------------------------------------------------------------------------------- | ||
233 | list of eBPF helper functions | ||
234 | ------------------------------------------------------------------------------- | ||
235 | |||
236 | :Manual section: 7 | ||
237 | |||
238 | DESCRIPTION | ||
239 | =========== | ||
240 | |||
241 | The extended Berkeley Packet Filter (eBPF) subsystem consists in programs | ||
242 | written in a pseudo-assembly language, then attached to one of the several | ||
243 | kernel hooks and run in reaction of specific events. This framework differs | ||
244 | from the older, "classic" BPF (or "cBPF") in several aspects, one of them being | ||
245 | the ability to call special functions (or "helpers") from within a program. | ||
246 | These functions are restricted to a white-list of helpers defined in the | ||
247 | kernel. | ||
248 | |||
249 | These helpers are used by eBPF programs to interact with the system, or with | ||
250 | the context in which they work. For instance, they can be used to print | ||
251 | debugging messages, to get the time since the system was booted, to interact | ||
252 | with eBPF maps, or to manipulate network packets. Since there are several eBPF | ||
253 | program types, and that they do not run in the same context, each program type | ||
254 | can only call a subset of those helpers. | ||
255 | |||
256 | Due to eBPF conventions, a helper can not have more than five arguments. | ||
257 | |||
258 | Internally, eBPF programs call directly into the compiled helper functions | ||
259 | without requiring any foreign-function interface. As a result, calling helpers | ||
260 | introduces no overhead, thus offering excellent performance. | ||
261 | |||
262 | This document is an attempt to list and document the helpers available to eBPF | ||
263 | developers. They are sorted by chronological order (the oldest helpers in the | ||
264 | kernel at the top). | ||
265 | |||
266 | HELPERS | ||
267 | ======= | ||
268 | ''' | ||
269 | print(header) | ||
270 | |||
271 | def print_footer(self): | ||
272 | footer = ''' | ||
273 | EXAMPLES | ||
274 | ======== | ||
275 | |||
276 | Example usage for most of the eBPF helpers listed in this manual page are | ||
277 | available within the Linux kernel sources, at the following locations: | ||
278 | |||
279 | * *samples/bpf/* | ||
280 | * *tools/testing/selftests/bpf/* | ||
281 | |||
282 | LICENSE | ||
283 | ======= | ||
284 | |||
285 | eBPF programs can have an associated license, passed along with the bytecode | ||
286 | instructions to the kernel when the programs are loaded. The format for that | ||
287 | string is identical to the one in use for kernel modules (Dual licenses, such | ||
288 | as "Dual BSD/GPL", may be used). Some helper functions are only accessible to | ||
289 | programs that are compatible with the GNU Privacy License (GPL). | ||
290 | |||
291 | In order to use such helpers, the eBPF program must be loaded with the correct | ||
292 | license string passed (via **attr**) to the **bpf**\ () system call, and this | ||
293 | generally translates into the C source code of the program containing a line | ||
294 | similar to the following: | ||
295 | |||
296 | :: | ||
297 | |||
298 | char ____license[] __attribute__((section("license"), used)) = "GPL"; | ||
299 | |||
300 | IMPLEMENTATION | ||
301 | ============== | ||
302 | |||
303 | This manual page is an effort to document the existing eBPF helper functions. | ||
304 | But as of this writing, the BPF sub-system is under heavy development. New eBPF | ||
305 | program or map types are added, along with new helper functions. Some helpers | ||
306 | are occasionally made available for additional program types. So in spite of | ||
307 | the efforts of the community, this page might not be up-to-date. If you want to | ||
308 | check by yourself what helper functions exist in your kernel, or what types of | ||
309 | programs they can support, here are some files among the kernel tree that you | ||
310 | may be interested in: | ||
311 | |||
312 | * *include/uapi/linux/bpf.h* is the main BPF header. It contains the full list | ||
313 | of all helper functions, as well as many other BPF definitions including most | ||
314 | of the flags, structs or constants used by the helpers. | ||
315 | * *net/core/filter.c* contains the definition of most network-related helper | ||
316 | functions, and the list of program types from which they can be used. | ||
317 | * *kernel/trace/bpf_trace.c* is the equivalent for most tracing program-related | ||
318 | helpers. | ||
319 | * *kernel/bpf/verifier.c* contains the functions used to check that valid types | ||
320 | of eBPF maps are used with a given helper function. | ||
321 | * *kernel/bpf/* directory contains other files in which additional helpers are | ||
322 | defined (for cgroups, sockmaps, etc.). | ||
323 | |||
324 | Compatibility between helper functions and program types can generally be found | ||
325 | in the files where helper functions are defined. Look for the **struct | ||
326 | bpf_func_proto** objects and for functions returning them: these functions | ||
327 | contain a list of helpers that a given program type can call. Note that the | ||
328 | **default:** label of the **switch ... case** used to filter helpers can call | ||
329 | other functions, themselves allowing access to additional helpers. The | ||
330 | requirement for GPL license is also in those **struct bpf_func_proto**. | ||
331 | |||
332 | Compatibility between helper functions and map types can be found in the | ||
333 | **check_map_func_compatibility**\ () function in file *kernel/bpf/verifier.c*. | ||
334 | |||
335 | Helper functions that invalidate the checks on **data** and **data_end** | ||
336 | pointers for network processing are listed in function | ||
337 | **bpf_helper_changes_pkt_data**\ () in file *net/core/filter.c*. | ||
338 | |||
339 | SEE ALSO | ||
340 | ======== | ||
341 | |||
342 | **bpf**\ (2), | ||
343 | **cgroups**\ (7), | ||
344 | **ip**\ (8), | ||
345 | **perf_event_open**\ (2), | ||
346 | **sendmsg**\ (2), | ||
347 | **socket**\ (7), | ||
348 | **tc-bpf**\ (8)''' | ||
349 | print(footer) | ||
350 | |||
351 | def print_proto(self, helper): | ||
352 | """ | ||
353 | Format function protocol with bold and italics markers. This makes RST | ||
354 | file less readable, but gives nice results in the manual page. | ||
355 | """ | ||
356 | proto = helper.proto_break_down() | ||
357 | |||
358 | print('**%s %s%s(' % (proto['ret_type'], | ||
359 | proto['ret_star'].replace('*', '\\*'), | ||
360 | proto['name']), | ||
361 | end='') | ||
362 | |||
363 | comma = '' | ||
364 | for a in proto['args']: | ||
365 | one_arg = '{}{}'.format(comma, a['type']) | ||
366 | if a['name']: | ||
367 | if a['star']: | ||
368 | one_arg += ' {}**\ '.format(a['star'].replace('*', '\\*')) | ||
369 | else: | ||
370 | one_arg += '** ' | ||
371 | one_arg += '*{}*\\ **'.format(a['name']) | ||
372 | comma = ', ' | ||
373 | print(one_arg, end='') | ||
374 | |||
375 | print(')**') | ||
376 | |||
377 | def print_one(self, helper): | ||
378 | self.print_proto(helper) | ||
379 | |||
380 | if (helper.desc): | ||
381 | print('\tDescription') | ||
382 | # Do not strip all newline characters: formatted code at the end of | ||
383 | # a section must be followed by a blank line. | ||
384 | for line in re.sub('\n$', '', helper.desc, count=1).split('\n'): | ||
385 | print('{}{}'.format('\t\t' if line else '', line)) | ||
386 | |||
387 | if (helper.ret): | ||
388 | print('\tReturn') | ||
389 | for line in helper.ret.rstrip().split('\n'): | ||
390 | print('{}{}'.format('\t\t' if line else '', line)) | ||
391 | |||
392 | print('') | ||
393 | |||
394 | ############################################################################### | ||
395 | |||
396 | # If script is launched from scripts/ from kernel tree and can access | ||
397 | # ../include/uapi/linux/bpf.h, use it as a default name for the file to parse, | ||
398 | # otherwise the --filename argument will be required from the command line. | ||
399 | script = os.path.abspath(sys.argv[0]) | ||
400 | linuxRoot = os.path.dirname(os.path.dirname(script)) | ||
401 | bpfh = os.path.join(linuxRoot, 'include/uapi/linux/bpf.h') | ||
402 | |||
403 | argParser = argparse.ArgumentParser(description=""" | ||
404 | Parse eBPF header file and generate documentation for eBPF helper functions. | ||
405 | The RST-formatted output produced can be turned into a manual page with the | ||
406 | rst2man utility. | ||
407 | """) | ||
408 | if (os.path.isfile(bpfh)): | ||
409 | argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h', | ||
410 | default=bpfh) | ||
411 | else: | ||
412 | argParser.add_argument('--filename', help='path to include/uapi/linux/bpf.h') | ||
413 | args = argParser.parse_args() | ||
414 | |||
415 | # Parse file. | ||
416 | headerParser = HeaderParser(args.filename) | ||
417 | headerParser.run() | ||
418 | |||
419 | # Print formatted output to standard output. | ||
420 | printer = PrinterRST(headerParser.helpers) | ||
421 | printer.print_all() | ||